From 23cc44ca48c7cda8d3a8cd7f8f8a2058e5f2cf80 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 5 Jun 2025 13:38:17 -0600 Subject: [PATCH] Fixes #4129 - `GetViewsUnderMouse` does not return anything (#4130) --- Terminal.Gui/App/IPopover.cs | 21 +++++- Terminal.Gui/App/PopoverBaseImpl.cs | 5 ++ Terminal.Gui/ViewBase/View.Layout.cs | 2 +- .../ViewBase/ViewportSettingsFlags.cs | 6 +- .../Application/ApplicationPopoverTests.cs | 68 ++++++++++++++++++- 5 files changed, 93 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/App/IPopover.cs b/Terminal.Gui/App/IPopover.cs index 652ca4614..7e86ffe77 100644 --- a/Terminal.Gui/App/IPopover.cs +++ b/Terminal.Gui/App/IPopover.cs @@ -26,7 +26,26 @@ namespace Terminal.Gui.App; /// /// /// -/// To implement a custom popover, inherit from or implement this interface directly. +/// Focus and Input:
+/// When visible, a popover receives focus and input events. If the user clicks outside the popover (and not on a +/// subview), +/// presses , or another popover is shown, the popover will be hidden +/// automatically. +///
+/// +/// Layout:
+/// When the popover becomes visible, it is automatically laid out to fill the screen by default. You can override +/// this behavior +/// by setting and in your derived class. +///
+/// +/// Mouse:
+/// Popovers are transparent to mouse events (see ), +/// meaning mouse events in a popover that are not also within a subview of the popover will not be captured. +///
+/// +/// Custom Popovers:
+/// To create a custom popover, inherit from and add your own content and logic. ///
/// public interface IPopover diff --git a/Terminal.Gui/App/PopoverBaseImpl.cs b/Terminal.Gui/App/PopoverBaseImpl.cs index 8c426a2db..1e9aacb71 100644 --- a/Terminal.Gui/App/PopoverBaseImpl.cs +++ b/Terminal.Gui/App/PopoverBaseImpl.cs @@ -26,6 +26,11 @@ namespace Terminal.Gui.App; /// by setting and in your derived class. /// /// +/// Mouse:
+/// Popovers are transparent to mouse events (see ), +/// meaning mouse events in a popover that are not also within a subview of the popover will not be captured. +///
+/// /// Custom Popovers:
/// To create a custom popover, inherit from and add your own content and logic. ///
diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index e9fc65fa7..9ec26a23a 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -1166,7 +1166,7 @@ public partial class View // Layout APIs result.AddRange (GetViewsUnderLocation (visiblePopover, screenLocation, excludeViewportSettingsFlags)); - if (result.Count > 1) + if (result.Count > 0) { return result; } diff --git a/Terminal.Gui/ViewBase/ViewportSettingsFlags.cs b/Terminal.Gui/ViewBase/ViewportSettingsFlags.cs index 10c0d763a..5eee4493b 100644 --- a/Terminal.Gui/ViewBase/ViewportSettingsFlags.cs +++ b/Terminal.Gui/ViewBase/ViewportSettingsFlags.cs @@ -153,11 +153,9 @@ public enum ViewportSettingsFlags /// Transparent = 0b_0001_0000_0000, - // BUGBUG: The API docs here are wrong: If a TransparentMouse View has subviews, those subviews WILL get mouse events. - // BUGBUG: That's an important feature that enables Popovers to work. /// - /// If set the View will be transparent to mouse events: Any mouse event that occurs over the View (and it's SubViews) will be passed to the - /// Views below it. + /// If set the View will be transparent to mouse events: Specifically, any mouse event that occurs over the View that is NOT occupied by a SubView + /// will not be captured by the View. /// /// Combine this with to get a view that is both visually transparent and transparent to the mouse. /// diff --git a/Tests/UnitTests/Application/ApplicationPopoverTests.cs b/Tests/UnitTests/Application/ApplicationPopoverTests.cs index 66395840a..798899cf4 100644 --- a/Tests/UnitTests/Application/ApplicationPopoverTests.cs +++ b/Tests/UnitTests/Application/ApplicationPopoverTests.cs @@ -2,9 +2,6 @@ public class ApplicationPopoverTests { - - - [Fact] public void Application_Init_Initializes_PopoverManager () { @@ -203,6 +200,71 @@ public class ApplicationPopoverTests Application.ResetState (true); } + // See: https://github.com/gui-cs/Terminal.Gui/issues/4122 + [Theory] + [InlineData (0, 0, new [] { "top" })] + [InlineData (10, 10, new string [] { })] + [InlineData (1, 1, new [] { "top", "view" })] + [InlineData (5, 5, new [] { "top" })] + [InlineData (6, 6, new [] { "popoverSubView" })] + [InlineData (7, 7, new [] { "top" })] + [InlineData (3, 3, new [] { "top" })] + public void GetViewsUnderMouse_Supports_ActivePopover (int mouseX, int mouseY, string [] viewIdStrings) + { + Application.ResetState (true); + // Arrange + Assert.Null (Application.Popover); + Application.Init (new FakeDriver ()); + Application.Top = new () + { + Frame = new (0, 0, 10, 10), + Id = "top" + }; + + View view = new () + { + Id = "view", + X = 1, + Y = 1, + Width = 2, + Height = 2, + }; // at 1,1 to 3,2 (screen) + + Application.Top.Add (view); + + PopoverTestClass popover = new () + { + Id = "popover", + X = 5, + Y = 5, + Width = 3, + Height = 3, + }; // at 5,5 to 8,8 (screen) + + View popoverSubView = new () + { + Id = "popoverSubView", + X = 1, + Y = 1, + Width = 1, + Height = 1, + }; // at 6,6 to 7,7 (screen) + + popover.Add (popoverSubView); + + Application.Popover?.Show (popover); + + List found = View.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); + + string [] foundIds = found.Select (v => v!.Id).ToArray (); + + Assert.Equal (viewIdStrings, foundIds); + + popover.Dispose (); + Application.Top.Dispose (); + Application.ResetState (true); + } + public class PopoverTestClass : PopoverBaseImpl { public List HandledKeys { get; } = [];