From 6c8a04fb58e5517ff085c7bbb5fe9b25fd2a8dce Mon Sep 17 00:00:00 2001 From: Mike Strobel Date: Mon, 7 Sep 2026 14:45:10 -0500 Subject: [PATCH] fix(ui): TranslateFromScreen resolves the surface offset for any surface root (popup content GetPosition) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UIElement.TranslateFromScreen only subtracted the surface offset when the visual root was a Window or the Popup element itself. A popup's hosted child is its own surface root (the Popup element stays in the owner's tree), so inside a popup placed away from the screen origin MouseEventArgs.GetPosition came back offset by the popup's (Left, Top) — hover and clicks in a drawn control inside a BarPopupButton dropdown landed rows and columns away from the pointer. Resolve the surface through WindowManager.SurfaceForElement, exactly as TranslateToScreen already does, so the two are inverses for every surface kind. Regression test: a popup placed below an offset target — element→screen→element round-trips and the dispatched press reports element-local cells. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013qbF5ru6uh5w7fqhP3yoPE --- .../Windowing/WindowPopupTests.cs | 35 +++++++++++++++++++ Cursorial.UI/UIElement.cs | 11 +++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Cursorial.UI.Tests/Windowing/WindowPopupTests.cs b/Cursorial.UI.Tests/Windowing/WindowPopupTests.cs index 440d83d9..8f9d25b7 100644 --- a/Cursorial.UI.Tests/Windowing/WindowPopupTests.cs +++ b/Cursorial.UI.Tests/Windowing/WindowPopupTests.cs @@ -75,6 +75,41 @@ public void PlacementBottom_PositionsBelowTarget() Assert.Equal(ty + target.Bounds.Rows, popup.PopupSurface!.Top); } + [Fact] // inside a popup placed AWAY from the screen origin, screen↔element translation round-trips and + // MouseEventArgs.GetPosition reports element-local cells (the popup surface's offset is applied both ways) + public void PlacedPopup_TranslateFromScreen_AndGetPosition_AreElementLocal() + { + var host = NewHost(); + using var _ = host; + var spacer = new UIControls.Border { Width = 30, Height = 5 }; + var target = new UIControls.Button { Width = 10, Height = 1, Content = "open", Margin = new(7, 0, 0, 0) }; + var inner = new UIControls.Button { Width = 8, Height = 3, Content = "menu" }; + var popup = new Popup { Child = inner, PlacementTarget = target }; + var root = new UIControls.StackPanel(); + root.Children.Add(spacer); + root.Children.Add(target); + root.Children.Add(popup); + host.ShowRoot(root); + Assert.True(host.RunUntilIdle()); + + popup.Open(); + Assert.True(host.RunUntilIdle()); + var surface = popup.PopupSurface!; + Assert.True(surface.Left > 0 && surface.Top > 0, $"the popup should sit away from the origin, was ({surface.Left},{surface.Top})"); + + // Round trip: element → screen → element. + var (sx, sy) = inner.TranslateToScreen(2, 1); + Assert.Equal((surface.Left + 2, surface.Top + 1), (sx, sy)); + Assert.Equal((2, 1), inner.TranslateFromScreen(sx, sy)); + + // The dispatched press reports the same element-local cell. + CellPosition? seen = null; + inner.AddHandler(UIElement.MouseDownEvent, (_, e) => seen = e.GetPosition(inner), handledEventsToo: true); + host.SendMouseDown(sx, sy); + Assert.True(host.RunUntilIdle()); + Assert.Equal(new CellPosition(2, 1), seen); + } + [Fact] // an uncaptured press outside the popup light-dismisses it (Closed fires with LightDismiss) public void OutsidePress_LightDismisses() { diff --git a/Cursorial.UI/UIElement.cs b/Cursorial.UI/UIElement.cs index e9f2ab98..57029b78 100644 --- a/Cursorial.UI/UIElement.cs +++ b/Cursorial.UI/UIElement.cs @@ -896,12 +896,15 @@ public bool IsEffectivelyVisible { (column, row) = TranslateFromWindow(column, row); + // The surface offset is resolved the same way TranslateToScreen resolves it — through the window manager, + // for ANY surface root. A popup's hosted child is its own surface root (the Popup element itself stays in + // the owner's tree), so matching on `root is Popup` never fired for popup content: inside a popup placed + // away from the screen origin, MouseEventArgs.GetPosition came back offset by the popup's position. if (_visualRoot is {} root) { - if (root is Window { HostSurface: {} windowHost}) - (column, row) = (column - windowHost.Left, row - windowHost.Top); - else if (root is Popup { PopupSurface: {} popupHost}) - (column, row) = (column - popupHost.Left, row - popupHost.Top); + var surface = UIApplication.Current?.WindowManager?.SurfaceForElement(root); + if (surface is not null) + (column, row) = (column - surface.Left, row - surface.Top); } return (column, row);