diff --git a/.gitignore b/.gitignore index e8e9762..862b993 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,11 @@ *.exe *.dll *.so -*.dylib \ No newline at end of file +*.dylib +build_test/ +build/ +build_verify/ +CMakeCache.txt +CMakeFiles/ +Makefile +cmake_install.cmake diff --git a/3rdparty/xege b/3rdparty/xege index 0ce3358..590e823 160000 --- a/3rdparty/xege +++ b/3rdparty/xege @@ -1 +1 @@ -Subproject commit 0ce335873fd81a79f82c5971823c16c9817174ba +Subproject commit 590e823dc8d8d3bf28eb8bce6480088d83c90cbf diff --git a/include/Widget.h b/include/Widget.h index 1126049..06f1a15 100644 --- a/include/Widget.h +++ b/include/Widget.h @@ -597,8 +597,9 @@ class InputBox : public Widget { /** * @brief 设置输入内容 * @param s 内容字符串 + * @param flag 内部标志,外界调用不应当填入 */ - void setContent(const std::wstring& s); + void setContent(const std::wstring& s,bool flag = false); /** * @brief 设置最大输入长度 @@ -1749,6 +1750,16 @@ class Box : public Panel { * @param s 缩放比例 */ void setScale(double s) override; + + /** + * @brief 处理鼠标事件,支持滚轮滚动(无滚动条) + */ + bool handleEvent(const mouse_msg& msg) override; + +private: + double targetBoxScrollPos_ = 0.0; ///< 目标滚动位置(0~1) + double boxScrollPos_ = 0.0; ///< 当前滚动位置(0~1,平滑插值) + bool smoothScrollActive_ = false; ///< 是否正在进行平滑滚动动画 }; /** diff --git a/src/Widget.cpp b/src/Widget.cpp index be83571..873ec30 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -373,7 +373,9 @@ bool Panel::handleEvent(const mouse_msg& msg){ // 处理滚动条拖动(即使鼠标在面板外也要处理) if (scrollBarEnabled_ && scrollBar_ && scrollBar_->isNeeded()) { - if(!msg.is_wheel() || (msg.is_wheel() && focusingWidget == this)){ + // 非滚轮事件(拖拽等)始终处理;滚轮事件仅在鼠标位于面板内时处理, + // 不受子控件焦点状态影响(否则InputBox获焦后无法滚动面板) + if(!msg.is_wheel() || (msg.is_wheel() && isin)){ double sbLeft = left + width - scrollBar_->getWidth(); double sbTop = top; // 拖动时始终处理 @@ -393,6 +395,18 @@ bool Panel::handleEvent(const mouse_msg& msg){ focusingWidget->deleteFocus(msg); } } + // 鼠标移出Panel区域时,通知子控件以便重置鼠标指针形状 + // (如InputBox的IDC_IBEAM→IDC_ARROW),仅在无拖动操作时执行。 + // 使用远离屏幕的合成坐标,而非实际鼠标坐标,以避免滚动超出视口的 + // 不可见子控件恰好命中真实鼠标位置而错误触发IDC_IBEAM。 + if (msg.is_move() && (mouseOwningFlag == nullptr || mouseOwningFlag == this)) { + mouse_msg leaveMsg = msg; + leaveMsg.x = -99999; + leaveMsg.y = -99999; + for (Widget* w : children) { + w->handleEvent(leaveMsg); + } + } return false; } // When clicking inside this Panel, if the focused widget is in another Panel, remove its focus @@ -1021,7 +1035,7 @@ void InputBox::draw(PIMAGE dst, double x, double y) { inv.setfocus(); wchar_t str[512]; inv.gettext(512, str); - setContent(str); + setContent(str,true); } std::wstring displayContent = IMECompositionString.size() ? @@ -1298,9 +1312,10 @@ bool InputBox::isInside(double x, double y) const { return true; } -void InputBox::setContent(const std::wstring& s) { +void InputBox::setContent(const std::wstring& s,bool flag) { if(content == s) return; content = s; + if(!flag) inv.settext(s.c_str()); needRedraw = true; if(this->parent != nullptr){ if (Panel* p = dynamic_cast(this->parent)) { @@ -3952,8 +3967,48 @@ void Box::draw(PIMAGE dst, double x, double y) { return; } - if (layout) layout->apply(*this); // 自动计算子控件位置 - + // 计算滚动偏移并进行平滑插值(与Panel/ScrollBar实现保持一致,但Box无滚动条) + double layoutScrollOffset = 0; + if (layout) { + LayoutResult extentResult = layout->apply(*this, 0); + double contentH = extentResult.contentMaxY - extentResult.contentMinY; + double viewH = height / (scale > 0 ? scale : 1.0); + double maxScroll = contentH - viewH; + + if (maxScroll > 0) { + // 平滑滚动:boxScrollPos_ 向 targetBoxScrollPos_ 插值(同ScrollBar逻辑) + double diff = targetBoxScrollPos_ - boxScrollPos_; + if (diff != 0) { + double lerpFactor = 0.18; + if (std::abs(diff) * maxScroll < 1.0) { + boxScrollPos_ = targetBoxScrollPos_; // 接近目标时直接到达(像素级精度) + } else { + boxScrollPos_ += diff * lerpFactor; + } + this->setDirty(); + if (!smoothScrollActive_) { + this->setAlwaysDirty(true); + smoothScrollActive_ = true; + } + } else if (smoothScrollActive_) { + this->setAlwaysDirty(false); + smoothScrollActive_ = false; + } + layoutScrollOffset = boxScrollPos_ * maxScroll; + } else { + // 内容未超出视口,重置滚动状态 + boxScrollPos_ = 0; + targetBoxScrollPos_ = 0; + if (smoothScrollActive_) { + this->setAlwaysDirty(false); + smoothScrollActive_ = false; + } + } + } + + // 应用布局(带滚动偏移) + if (layout) layout->apply(*this, layoutScrollOffset); + // 使用真正的透明色(PRGB32模式下alpha=0时RGB也应为0) setbkcolor_f(EGEARGB(0, 0, 0, 0), layer); cleardevice(layer); @@ -4032,6 +4087,28 @@ void Box::setScale(double s){ } } +bool Box::handleEvent(const mouse_msg& msg) { + // 滚轮事件:无滚动条的平滑滚动(与Panel/ScrollBar实现保持一致) + if (msg.is_wheel() && isInside(msg.x, msg.y)) { + if (layout) { + LayoutResult extentResult = layout->apply(*this, 0); + double contentH = extentResult.contentMaxY - extentResult.contentMinY; + double viewH = height / (scale > 0 ? scale : 1.0); + double maxScroll = contentH - viewH; + if (maxScroll > 0) { + double fixedPixels = 60.0; // 每次滚轮固定滚动60像素(同ScrollBar) + double step = fixedPixels / maxScroll * (msg.wheel / -120.0); + targetBoxScrollPos_ += step; + if (targetBoxScrollPos_ < 0) targetBoxScrollPos_ = 0; + if (targetBoxScrollPos_ > 1.0) targetBoxScrollPos_ = 1.0; + this->setDirty(); + } + } + return true; + } + return Panel::handleEvent(msg); +} + // ============ BoxBuilder 实现 ============ BoxBuilder& BoxBuilder::setIdentifier(const std::wstring& id) {