From 419060d22af6da5a0ea35fe7063b72d7706a8d6e Mon Sep 17 00:00:00 2001 From: dogukani <38019578+dogukani@users.noreply.github.com> Date: Thu, 24 Sep 2026 02:30:11 +0300 Subject: [PATCH] fix: restore native Controls interactions and skip idle layout --- .../targets/win32/main/native/controls.cpp | 52 ++++++++++++++++--- .../targets/win32/main/win32_main.cpp | 9 ++++ .../targets/win32/main/win32_widgets.cpp | 15 +++++- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/packages/geastack-windows/targets/win32/main/native/controls.cpp b/packages/geastack-windows/targets/win32/main/native/controls.cpp index e660b2c..fbf837e 100644 --- a/packages/geastack-windows/targets/win32/main/native/controls.cpp +++ b/packages/geastack-windows/targets/win32/main/native/controls.cpp @@ -9,7 +9,7 @@ // stack views arrange their children, anchors pin a child to its parent's // edges, scroll views size their document to its content. // -// Layout runs once per frame from the target's frame loop (the callback +// Layout runs on dirty frames from the target's frame loop (the callback // installed through setNativeLayoutCallback), starting at the root view that // installRootView handed over, in layout px scaled to the window's DPI. @@ -268,16 +268,26 @@ HFONT fontFor(ViewPtr node, int fallbackSize = 13) return gea::win32::fontForFamily(L"Segoe UI", fallbackSize); } +void syncClickHandler(ViewPtr node) +{ + if (!node || !node->widget) return; + if (!node->onClick) { + node->widget->events.onClick = nullptr; + return; + } + node->widget->events.onClick = [node] { + if (node->onClick && node->onClick->handler) node->onClick->handler(); + }; +} + void ensureWidget(ViewPtr node, HWND parent) { if (!node->widget) { node->widget = gea::win32::createWidget(widgetKindFor(node->viewKind), parent); if (!node->widget) return; if (node->viewKind == ViewKind::Label) node->widget->clickTransparent = node->onClick == nullptr; + syncClickHandler(node); ViewObject *self = node; - node->widget->events.onClick = [self] { - if (self->onClick && self->onClick->handler) self->onClick->handler(); - }; node->widget->events.onTextChanged = [self](const std::wstring &text) { self->text = text; if (self->onChange && self->onChange->handler) self->onChange->handler(); @@ -672,7 +682,23 @@ void installLayout() { if (g_layoutInstalled) return; g_layoutInstalled = true; - gea::win32::setNativeLayoutCallback([](const RECT &area) { layoutRoot(area); }); + gea::win32::setNativeLayoutCallback([](const RECT &area) { + static RECT lastArea{}; + static double lastScale = 0; + const double currentScale = scale(); + if (!g_needsLayout && EqualRect(&lastArea, &area) && currentScale == lastScale) return; + lastArea = area; + lastScale = currentScale; + g_needsLayout = false; + layoutRoot(area); + }); +} + +void showAttachedTree(ViewPtr node) +{ + if (!node || !node->widget) return; + ShowWindow(node->widget->hwnd, node->hidden ? SW_HIDE : SW_SHOWNA); + for (ViewPtr child : node->children) showAttachedTree(child); } void attach(ViewPtr parent, ViewPtr child) @@ -688,6 +714,12 @@ void attach(ViewPtr parent, ViewPtr child) if (std::find(parent->children.begin(), parent->children.end(), child) == parent->children.end()) parent->children.push_back(child); HWND host = parent->widget ? (parent->viewKind == ViewKind::Scroll && parent->widget->document ? parent->widget->document : parent->widget->hwnd) : hostWindow(); if (parent->widget) ensureWidget(child, host); + for (ViewPtr ancestor = parent; ancestor; ancestor = ancestor->parent) { + if (ancestor == g_root) { + showAttachedTree(child); + break; + } + } g_needsLayout = true; gea::win32::requestFrame(); } @@ -922,8 +954,13 @@ bool WinView_get_hidden(WinView self) { ViewPtr node = view(self); return node ? void WinView_set_hidden(WinView self, bool value) { if (ViewPtr node = view(self)) { + if (node->hidden == value && (!node->widget || node->widget->style.hidden == value)) return; node->hidden = value; - if (node->widget) ShowWindow(node->widget->hwnd, value ? SW_HIDE : SW_SHOWNA); + if (node->widget) { + // Layout must not repeat the same visibility change. + node->widget->style.hidden = value; + ShowWindow(node->widget->hwnd, value ? SW_HIDE : SW_SHOWNA); + } markDirty(); } } @@ -1016,7 +1053,8 @@ void WinView_onClick(WinView self, WinCallback handler) { if (ViewPtr node = view(self)) { node->onClick = callback(handler); - if (node->widget) node->widget->clickTransparent = false; + syncClickHandler(node); + if (node->widget && node->viewKind == ViewKind::Label) node->widget->clickTransparent = node->onClick == nullptr; markDirty(); } } diff --git a/packages/geastack-windows/targets/win32/main/win32_main.cpp b/packages/geastack-windows/targets/win32/main/win32_main.cpp index f8ff8cc..4086d8e 100644 --- a/packages/geastack-windows/targets/win32/main/win32_main.cpp +++ b/packages/geastack-windows/targets/win32/main/win32_main.cpp @@ -625,6 +625,15 @@ LRESULT CALLBACK mainWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM l EndPaint(hwnd, &paint); return 0; } + case WM_COMMAND: + if (lParam) { + HWND control = reinterpret_cast(lParam); + HWND parent = GetParent(control); + // Reparented controls can still notify the window that created them. + if (parent && parent != hwnd && gea::win32::widgetFor(control) && gea::win32::widgetFor(parent)) + return SendMessageW(parent, message, wParam, lParam); + } + break; case WM_SIZE: if (g_booted && wParam != SIZE_MINIMIZED) { layoutNativeChrome(); diff --git a/packages/geastack-windows/targets/win32/main/win32_widgets.cpp b/packages/geastack-windows/targets/win32/main/win32_widgets.cpp index 0040495..747babc 100644 --- a/packages/geastack-windows/targets/win32/main/win32_widgets.cpp +++ b/packages/geastack-windows/targets/win32/main/win32_widgets.cpp @@ -393,7 +393,9 @@ void notifyCommand(Widget *parent, HWND control, WORD code) case WidgetKind::TextField: case WidgetKind::TextArea: if (code == EN_CHANGE) { - widget->lastText = widgetText(widget); + const std::wstring text = widgetText(widget); + if (text == widget->lastText) break; + widget->lastText = text; widget->style.text = widget->lastText; if (widget->events.onTextChanged) widget->events.onTextChanged(widget->lastText); } else if (code == EN_SETFOCUS) { @@ -574,7 +576,16 @@ LRESULT CALLBACK widgetProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lPara POINT point{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; const bool inside = PtInRect(&client, point) != FALSE; if (widget->events.onPressUp) widget->events.onPressUp(); - if (inside && widget->events.onClick) widget->events.onClick(); + if (inside) { + for (HWND current = hwnd; current; current = GetParent(current)) { + Widget *target = widgetFromWindow(current); + if (!target) break; + if (target->events.onClick) { + target->events.onClick(); + break; + } + } + } return 0; } case WM_CAPTURECHANGED: