diff --git a/ui/README.md b/ui/README.md index e8dcb499..83db6373 100644 --- a/ui/README.md +++ b/ui/README.md @@ -114,6 +114,7 @@ - 继续收窄候选窗顶部 `ni` 文本自身的占位宽度,避免 caret 虽然间距设小了但仍被文本槽位视觉上推远 - `Button` 点击后不再残留焦点高亮边框,保留按下态但去掉持续的描边强调 - `PopupHost` 现在会把按下/抬起鼠标事件转发给内部 trigger,因此像 `Open Quick Actions` 这样的弹层按钮也会显示正常的点击反馈 +- `Popup` 新增是否限制在视口内的开关;demo 里的页面内弹层已关闭该限制,因此滚动页面时弹出框会跟随锚点自然滚出可视区,而不是停在窗口边缘 - `CandidateList` 现支持基础键盘导航:弹层打开后会自动把焦点落到首个可聚焦内容上,候选列表可用 `Up/Down`、`Left/Right`、`Home/End`、`PageUp/PageDown` 切换高亮项 - demo 中的候选窗预览继续补上了底部分页/状态提示区,用来预演候选页码、当前高亮项和键盘操作提示的布局结构 - `Window` 新增公开的 `FocusVisual` 入口,方便 popup、候选窗、上下文菜单这类 overlay 内容在弹出后主动把焦点切到内部可交互控件 diff --git a/ui/demos/msimeui-demo/DemoScene.cpp b/ui/demos/msimeui-demo/DemoScene.cpp index 061485ac..3e6bae71 100644 --- a/ui/demos/msimeui-demo/DemoScene.cpp +++ b/ui/demos/msimeui-demo/DemoScene.cpp @@ -413,6 +413,7 @@ std::unique_ptr CreateDemoScene() auto popup = std::make_shared(popupContent); popup->SetMatchAnchorWidth(false); + popup->SetConstrainToViewport(false); popup->SetWidth(300.0f); popup->SetPadding({18.0f, 18.0f, 18.0f, 18.0f}); @@ -503,6 +504,7 @@ std::unique_ptr CreateDemoScene() auto candidatePopup = std::make_shared(candidatePopupStack); candidatePopup->SetMatchAnchorWidth(false); + candidatePopup->SetConstrainToViewport(false); candidatePopup->SetWidth(118.0f); candidatePopup->SetPadding({5.0f, 2.0f, 5.0f, 2.0f}); candidatePopup->SetBackgroundFill(D2D1::ColorF(0x252525)); diff --git a/ui/include/msimeui/Controls.h b/ui/include/msimeui/Controls.h index 4e1ae9d3..5f451ad2 100644 --- a/ui/include/msimeui/Controls.h +++ b/ui/include/msimeui/Controls.h @@ -102,6 +102,7 @@ class Popup : public Visual void SetPlacement(PopupPlacement placement); void SetOffset(float x, float y); void SetMatchAnchorWidth(bool matchAnchorWidth); + void SetConstrainToViewport(bool constrainToViewport); void SetBackgroundFill(const D2D1_COLOR_F &fill); void SetBorderColor(const D2D1_COLOR_F &border); void SetCornerRadius(float radius); @@ -124,6 +125,7 @@ class Popup : public Visual float offsetX_ = 0.0f; float offsetY_ = 8.0f; bool matchAnchorWidth_ = true; + bool constrainToViewport_ = true; D2D1_COLOR_F backgroundFill_ = D2D1::ColorF(0xFFFFFF); D2D1_COLOR_F borderColor_ = D2D1::ColorF(0xD6DCE5); float cornerRadius_ = 16.0f; diff --git a/ui/src/Controls.cpp b/ui/src/Controls.cpp index 2f98734c..d3e79ca2 100644 --- a/ui/src/Controls.cpp +++ b/ui/src/Controls.cpp @@ -526,6 +526,11 @@ void Popup::SetMatchAnchorWidth(bool matchAnchorWidth) matchAnchorWidth_ = matchAnchorWidth; } +void Popup::SetConstrainToViewport(bool constrainToViewport) +{ + constrainToViewport_ = constrainToViewport; +} + void Popup::SetBackgroundFill(const D2D1_COLOR_F &fill) { backgroundFill_ = fill; @@ -646,18 +651,18 @@ void Popup::LayoutOverlay(const SizeF &viewportSize) const float height = measured.height; float x = anchorRect_.x + offsetX_; - if (x + width > viewportSize.width - 8.0f) + if (constrainToViewport_ && x + width > viewportSize.width - 8.0f) { x = std::max(viewportSize.width - width - 8.0f, 8.0f); } float y = placement_ == PopupPlacement::AboveLeading ? (anchorRect_.y - height - offsetY_) : (anchorRect_.y + anchorRect_.height + offsetY_); - if (placement_ == PopupPlacement::AboveLeading && y < 8.0f) + if (constrainToViewport_ && placement_ == PopupPlacement::AboveLeading && y < 8.0f) { y = anchorRect_.y + anchorRect_.height + offsetY_; } - if (placement_ == PopupPlacement::BelowLeading && y + height > viewportSize.height - 8.0f) + if (constrainToViewport_ && placement_ == PopupPlacement::BelowLeading && y + height > viewportSize.height - 8.0f) { const float aboveY = anchorRect_.y - height - offsetY_; if (aboveY >= 8.0f) @@ -666,7 +671,10 @@ void Popup::LayoutOverlay(const SizeF &viewportSize) } } - y = std::clamp(y, 8.0f, std::max(viewportSize.height - height - 8.0f, 8.0f)); + if (constrainToViewport_) + { + y = std::clamp(y, 8.0f, std::max(viewportSize.height - height - 8.0f, 8.0f)); + } ArrangeInLayout({x, y, width, height}); }