Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@
*.exe
*.dll
*.so
*.dylib
*.dylib
build_test/
build/
build_verify/
CMakeCache.txt
CMakeFiles/
Makefile
cmake_install.cmake
2 changes: 1 addition & 1 deletion 3rdparty/xege
Submodule xege updated 1 files
+5 −9 src/font.cpp
13 changes: 12 additions & 1 deletion include/Widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 设置最大输入长度
Expand Down Expand Up @@ -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; ///< 是否正在进行平滑滚动动画
};

/**
Expand Down
87 changes: 82 additions & 5 deletions src/Widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
// 拖动时始终处理
Expand All @@ -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
Expand Down Expand Up @@ -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() ?
Expand Down Expand Up @@ -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<Panel*>(this->parent)) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down