From edd53173d1ed255161b486ae827d9648cab0479c Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 1 Apr 2023 20:27:49 +0100 Subject: [PATCH 1/7] Fixes #2478. Currently it isn't possible to draw over a modal view. --- Terminal.Gui/Core/Application.cs | 11 ++- Terminal.Gui/Core/Toplevel.cs | 3 + Terminal.Gui/Core/View.cs | 7 +- UnitTests/TopLevels/ToplevelTests.cs | 119 ++++++++++++++++++++++++++- 4 files changed, 136 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 1785d41c6..138bdda26 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -277,6 +277,14 @@ namespace Terminal.Gui { /// public static Action Iteration; + /// + /// This event is raised on each iteration of the after all processes are completed. + /// + /// + /// See also + /// + public static Action IterationComplete; + /// /// Returns a rectangle that is centered in the screen for the provided size. /// @@ -1262,6 +1270,7 @@ namespace Terminal.Gui { if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { + state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); Top.Redraw (Top.Bounds); foreach (var top in toplevels.Reverse ()) { if (top != Top && top != state.Toplevel) { @@ -1269,7 +1278,6 @@ namespace Terminal.Gui { top.Redraw (top.Bounds); } } - state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); } if (toplevels.Count == 1 && state.Toplevel == Top && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) @@ -1294,6 +1302,7 @@ namespace Terminal.Gui { && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { Top.Redraw (Top.Bounds); } + IterationComplete?.Invoke (); } static void EnsureModalOrVisibleAlwaysOnTop (Toplevel toplevel) diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index b7038268f..3061a951e 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -766,6 +766,9 @@ namespace Terminal.Gui { view.SetNeedsDisplay (view.Bounds); } } + + ClearLayoutNeeded (); + ClearNeedsDisplay (); } base.Redraw (Bounds); diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 8ea963ab1..51162a41d 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -939,8 +939,11 @@ namespace Terminal.Gui { /// public void SetSubViewNeedsDisplay () { + if (ChildNeedsDisplay) { + return; + } ChildNeedsDisplay = true; - if (container != null) + if (container != null && !container.ChildNeedsDisplay) container.SetSubViewNeedsDisplay (); } @@ -1485,7 +1488,7 @@ namespace Terminal.Gui { } /// - /// Removes the and the setting on this view. + /// Removes the and the setting on this view. /// protected void ClearNeedsDisplay () { diff --git a/UnitTests/TopLevels/ToplevelTests.cs b/UnitTests/TopLevels/ToplevelTests.cs index a742a88cb..75dd6f9db 100644 --- a/UnitTests/TopLevels/ToplevelTests.cs +++ b/UnitTests/TopLevels/ToplevelTests.cs @@ -1407,7 +1407,8 @@ namespace Terminal.Gui.TopLevelTests { }); var firstIteration = false; - Application.RunMainLoopIteration (ref rs, true, ref firstIteration); Assert.Equal (dialog, Application.MouseGrabView); + Application.RunMainLoopIteration (ref rs, true, ref firstIteration); + Assert.Equal (dialog, Application.MouseGrabView); Assert.Equal (new Rect (25, 7, 30, 10), dialog.Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" @@ -1449,5 +1450,121 @@ namespace Terminal.Gui.TopLevelTests { Application.End (rs); } + + [Fact, AutoInitShutdown] + public void Draw_A_Top_Subview_On_A_Dialog () + { + var top = Application.Top; + var win = new Window ("Window"); + top.Add (win); + Application.Begin (top); + ((FakeDriver)Application.Driver).SetBufferSize (20, 20); + + Assert.Equal (new Rect (0, 0, 20, 20), win.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌ Window ──────────┐ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────┘", output); + + var btnPopup = new Button ("Popup"); + btnPopup.Clicked += (s, e) => { + var viewToScreen = btnPopup.ViewToScreen (top.Frame); + var view = new View () { X = 1, Y = viewToScreen.Y + 1, Width = 18, Height = 5 }; + Application.IterationComplete += Application_IterationComplete; + top.Add (view); + + void Application_IterationComplete () + { + Assert.Equal (new Rect (1, 14, 18, 5), view.Frame); + + top.DrawFrame (view.Frame); + top.Move (2, 15); + View.Driver.AddStr ("One"); + top.Move (2, 16); + View.Driver.AddStr ("Two"); + top.Move (2, 17); + View.Driver.AddStr ("Three"); + + Application.IterationComplete -= Application_IterationComplete; + } + }; + var dialog = new Dialog ("Dialog", 15, 10, btnPopup); + var rs = Application.Begin (dialog); + + Assert.Equal (new Rect (2, 5, 15, 10), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌ Window ──────────┐ +│ │ +│ │ +│ │ +│ │ +│ ┌ Dialog ─────┐ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ [ Popup ] │ │ +│ └─────────────┘ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────┘", output); + + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = 9, + Y = 13, + Flags = MouseFlags.Button1Clicked + }); + + var firstIteration = false; + Application.RunMainLoopIteration (ref rs, true, ref firstIteration); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌ Window ──────────┐ +│ │ +│ │ +│ │ +│ │ +│ ┌ Dialog ─────┐ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ [ Popup ] │ │ +│┌────────────────┐│ +││One ││ +││Two ││ +││Three ││ +│└────────────────┘│ +└──────────────────┘", output); + + Application.End (rs); + } } } \ No newline at end of file From 4843fd020520d1c2eed21ded209302e42cddcf49 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 3 Apr 2023 00:28:00 +0100 Subject: [PATCH 2/7] Subscribing DrawContentComplete on the Application which allow draw over modal views. --- Terminal.Gui/Core/Application.cs | 10 +--- Terminal.Gui/Views/ContextMenu.cs | 8 +-- Terminal.Gui/Views/Menu.cs | 72 ++++++++++++------------ UnitTests/Menus/ContextMenuTests.cs | 84 +++++++++++++++++++++++++++- UnitTests/Menus/MenuTests.cs | 18 +++--- UnitTests/TopLevels/ToplevelTests.cs | 6 +- 6 files changed, 135 insertions(+), 63 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 138bdda26..156a2a829 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -277,14 +277,6 @@ namespace Terminal.Gui { /// public static Action Iteration; - /// - /// This event is raised on each iteration of the after all processes are completed. - /// - /// - /// See also - /// - public static Action IterationComplete; - /// /// Returns a rectangle that is centered in the screen for the provided size. /// @@ -1290,6 +1282,7 @@ namespace Terminal.Gui { if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded || MdiChildNeedsDisplay ()) { state.Toplevel.Redraw (state.Toplevel.Bounds); + state.Toplevel.OnDrawContentComplete (state.Toplevel.Bounds); if (DebugDrawBounds) { DrawBounds (state.Toplevel); } @@ -1302,7 +1295,6 @@ namespace Terminal.Gui { && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { Top.Redraw (Top.Bounds); } - IterationComplete?.Invoke (); } static void EnsureModalOrVisibleAlwaysOnTop (Toplevel toplevel) diff --git a/Terminal.Gui/Views/ContextMenu.cs b/Terminal.Gui/Views/ContextMenu.cs index df6da1de9..4a0ec5a5f 100644 --- a/Terminal.Gui/Views/ContextMenu.cs +++ b/Terminal.Gui/Views/ContextMenu.cs @@ -91,7 +91,7 @@ namespace Terminal.Gui { if (menuBar != null) { Hide (); } - container = Application.Current; + container = Application.Top; container.Closing += Container_Closing; container.Resized += Container_Resized; var frame = container.Frame; @@ -162,7 +162,7 @@ namespace Terminal.Gui { /// public void Hide () { - menuBar.CleanUp (); + menuBar?.CleanUp (); Dispose (); } @@ -194,7 +194,7 @@ namespace Terminal.Gui { set { var oldKey = key; key = value; - KeyChanged?.Invoke (this, new KeyChangedEventArgs(oldKey,key)); + KeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, key)); } } @@ -206,7 +206,7 @@ namespace Terminal.Gui { set { var oldFlags = mouseFlags; mouseFlags = value; - MouseFlagsChanged?.Invoke (this, new MouseFlagsChangedEventArgs(oldFlags,value)); + MouseFlagsChanged?.Invoke (this, new MouseFlagsChangedEventArgs (oldFlags, value)); } } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 1d3072200..91a75882d 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -477,6 +477,10 @@ namespace Terminal.Gui { WantMousePositionReports = host.WantMousePositionReports; } + if (Application.Current != null) { + Application.Current.DrawContentComplete += Current_DrawContentComplete; + } + // Things this view knows how to do AddCommand (Command.LineUp, () => MoveUp ()); AddCommand (Command.LineDown, () => MoveDown ()); @@ -511,9 +515,16 @@ namespace Terminal.Gui { return GetNormalColor (); } - // Draws the Menu, within the Frame public override void Redraw (Rect bounds) { + } + + // Draws the Menu, within the Frame + private void Current_DrawContentComplete (object sender, DrawEventArgs e) + { + if (barItems.Children == null) { + return; + } Driver.SetAttribute (GetNormalColor ()); DrawFrame (Bounds, padding: 0, fill: true); @@ -905,6 +916,14 @@ namespace Terminal.Gui { return base.OnEnter (view); } + + protected override void Dispose (bool disposing) + { + if (Application.Current != null) { + Application.Current.DrawContentComplete -= Current_DrawContentComplete; + } + base.Dispose (disposing); + } } /// @@ -1322,31 +1341,25 @@ namespace Terminal.Gui { switch (subMenu) { case null: // Open a submenu below a MenuBar - lastFocused = lastFocused ?? (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused); + lastFocused ??= (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused); if (openSubMenu != null && !CloseMenu (false, true)) return; if (openMenu != null) { - if (SuperView == null) { - Application.Current.Remove (openMenu); - } else { - SuperView.Remove (openMenu); - } + Application.Top.Remove (openMenu); openMenu.Dispose (); + openMenu = null; } // This positions the submenu horizontally aligned with the first character of the // menu it belongs to's text for (int i = 0; i < index; i++) pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + leftPadding + rightPadding; - openMenu = new Menu (this, Frame.X + pos, Frame.Y + 1, Menus [index]); + var locationOffset = SuperView == null ? new Point (0, 0) : new Point (SuperView.Frame.X, SuperView.Frame.Y); + openMenu = new Menu (this, Frame.X + pos + locationOffset.X, Frame.Y + 1 + locationOffset.Y, Menus [index]); openCurrentMenu = openMenu; openCurrentMenu.previousSubFocused = openMenu; - if (SuperView == null) { - Application.Current.Add (openMenu); - } else { - SuperView.Add (openMenu); - } + Application.Top.Add (openMenu); openMenu.SetFocus (); break; default: @@ -1368,17 +1381,14 @@ namespace Terminal.Gui { mbi [j + 2] = subMenu.Children [j]; } var newSubMenu = new MenuBarItem (mbi); - openCurrentMenu = new Menu (this, first.Frame.Left, first.Frame.Top, newSubMenu); + ViewToScreen (first.Frame.Left, first.Frame.Top, out int rx, out int ry); + openCurrentMenu = new Menu (this, rx, ry, newSubMenu); last.Visible = false; Application.GrabMouse (openCurrentMenu); } openCurrentMenu.previousSubFocused = last.previousSubFocused; openSubMenu.Add (openCurrentMenu); - if (SuperView == null) { - Application.Current.Add (openCurrentMenu); - } else { - SuperView.Add (openCurrentMenu); - } + Application.Current.Add (openCurrentMenu); } selectedSub = openSubMenu.Count - 1; if (selectedSub > -1 && SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) { @@ -1501,11 +1511,7 @@ namespace Terminal.Gui { switch (isSubMenu) { case false: if (openMenu != null) { - if (SuperView == null) { - Application.Current.Remove (openMenu); - } else { - SuperView?.Remove (openMenu); - } + Application.Top.Remove (openMenu); } SetNeedsDisplay (); if (previousFocused != null && previousFocused is Menu && openMenu != null && previousFocused.ToString () != openCurrentMenu.ToString ()) @@ -1564,11 +1570,7 @@ namespace Terminal.Gui { openCurrentMenu.SetFocus (); if (openSubMenu != null) { menu = openSubMenu [i]; - if (SuperView == null) { - Application.Current.Remove (menu); - } else { - SuperView.Remove (menu); - } + Application.Top.Remove (menu); openSubMenu.Remove (menu); menu.Dispose (); } @@ -1584,11 +1586,7 @@ namespace Terminal.Gui { { if (openSubMenu != null) { foreach (var item in openSubMenu) { - if (SuperView == null) { - Application.Current.Remove (item); - } else { - SuperView.Remove (item); - } + Application.Top.Remove (item); item.Dispose (); } } @@ -1757,7 +1755,8 @@ namespace Terminal.Gui { } if (mi.IsTopLevel) { - var menu = new Menu (this, i, 0, mi); + ViewToScreen (i, 0, out int rx, out int ry); + var menu = new Menu (this, rx, ry, mi); menu.Run (mi.Action); menu.Dispose (); } else { @@ -1878,7 +1877,8 @@ namespace Terminal.Gui { if (cx >= pos && cx < pos + leftPadding + Menus [i].TitleLength + Menus [i].Help.ConsoleWidth + rightPadding) { if (me.Flags == MouseFlags.Button1Clicked) { if (Menus [i].IsTopLevel) { - var menu = new Menu (this, i, 0, Menus [i]); + ViewToScreen (i, 0, out int rx, out int ry); + var menu = new Menu (this, rx, ry, Menus [i]); menu.Run (Menus [i].Action); menu.Dispose (); } else if (!IsMenuOpen) { diff --git a/UnitTests/Menus/ContextMenuTests.cs b/UnitTests/Menus/ContextMenuTests.cs index 9f1055cf9..03df8de50 100644 --- a/UnitTests/Menus/ContextMenuTests.cs +++ b/UnitTests/Menus/ContextMenuTests.cs @@ -228,7 +228,7 @@ namespace Terminal.Gui.MenuTests { var oldKey = Key.Null; var cm = new ContextMenu (); - cm.KeyChanged += (s,e) => oldKey = e.OldKey; + cm.KeyChanged += (s, e) => oldKey = e.OldKey; cm.Key = Key.Space | Key.CtrlMask; Assert.Equal (Key.Space | Key.CtrlMask, cm.Key); @@ -241,7 +241,7 @@ namespace Terminal.Gui.MenuTests { var oldMouseFlags = new MouseFlags (); var cm = new ContextMenu (); - cm.MouseFlagsChanged += (s,e) => oldMouseFlags = e.OldValue; + cm.MouseFlagsChanged += (s, e) => oldMouseFlags = e.OldValue; cm.MouseFlags = MouseFlags.Button2Clicked; Assert.Equal (MouseFlags.Button2Clicked, cm.MouseFlags); @@ -902,5 +902,85 @@ namespace Terminal.Gui.MenuTests { Assert.True (top.Subviews [1].ProcessKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ()))); Assert.Null (tf.ContextMenu.MenuBar); } + + [Fact, AutoInitShutdown] + public void Draw_A_ContextManu_Over_A_Dialog () + { + var top = Application.Top; + var win = new Window ("Window"); + top.Add (win); + Application.Begin (top); + ((FakeDriver)Application.Driver).SetBufferSize (20, 15); + + Assert.Equal (new Rect (0, 0, 20, 15), win.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌ Window ──────────┐ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────┘", output); + + var dialog = new Dialog ("Dialog") { X = 2, Y = 2, Width = 15, Height = 4 }; + dialog.Add (new TextField ("Test") { X = Pos.Center (), Width = 10 }); + var rs = Application.Begin (dialog); + + Assert.Equal (new Rect (2, 2, 15, 4), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌ Window ──────────┐ +│ │ +│ ┌ Dialog ─────┐ │ +│ │ Test │ │ +│ │ │ │ +│ └─────────────┘ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────┘", output); + + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = 9, + Y = 3, + Flags = MouseFlags.Button3Clicked + }); + + var firstIteration = false; + Application.RunMainLoopIteration (ref rs, true, ref firstIteration); + TestHelpers.AssertDriverContentsWithFrameAre (@" +┌ Window ──────────┐ +│ │ +│ ┌ Dialog ─────┐ │ +│ │ Test │ │ +┌─────────────────── +│ Select All Ctrl+ +│ Delete All Ctrl+ +│ Copy Ctrl+ +│ Cut Ctrl+ +│ Paste Ctrl+ +│ Undo Ctrl+ +│ Redo Ctrl+ +└─────────────────── +│ │ +└──────────────────┘", output); + + Application.End (rs); + } } } diff --git a/UnitTests/Menus/MenuTests.cs b/UnitTests/Menus/MenuTests.cs index cf8bd0b99..d1ed18b04 100644 --- a/UnitTests/Menus/MenuTests.cs +++ b/UnitTests/Menus/MenuTests.cs @@ -109,7 +109,7 @@ namespace Terminal.Gui.MenuTests { new MenuItem ("_New", "Creates new file.", New) }) }); - menu.MenuOpening += (s,e) => { + menu.MenuOpening += (s, e) => { Assert.Equal ("_File", e.CurrentMenu.Title); Assert.Equal ("_New", e.CurrentMenu.Children [0].Title); Assert.Equal ("Creates new file.", e.CurrentMenu.Children [0].Help); @@ -130,7 +130,7 @@ namespace Terminal.Gui.MenuTests { mi.Action (); Assert.Equal ("Copy", miAction); }; - menu.MenuClosing += (s,e) => { + menu.MenuClosing += (s, e) => { Assert.False (isMenuClosed); if (cancelClosing) { e.Cancel = true; @@ -196,7 +196,7 @@ Edit new MenuItem ("_Save", "Saves the file.", null, null) }) }); - menu.MenuOpened += (s,e) => { + menu.MenuOpened += (s, e) => { miCurrent = e.MenuItem; mCurrent = menu.openMenu; }; @@ -380,12 +380,12 @@ Edit }), new MenuBarItem ("_About", "Top-Level", () => miAction ="About") }); - menu.MenuOpening += (s,e) => mbiCurrent = e.CurrentMenu; + menu.MenuOpening += (s, e) => mbiCurrent = e.CurrentMenu; menu.MenuOpened += (s, e) => { miCurrent = e.MenuItem; mCurrent = menu.openCurrentMenu; }; - menu.MenuClosing += (s,e) => { + menu.MenuClosing += (s, e) => { mbiCurrent = null; miCurrent = null; mCurrent = null; @@ -1689,7 +1689,7 @@ Edit └──────────────────────────────────────┘", output); Assert.True (win.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); - win.Redraw (win.Bounds); + top.Redraw (top.Bounds); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────────────────────────┐ │ File Edit │ @@ -1701,7 +1701,7 @@ Edit └──────────────────────────────────────┘", output); Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); - win.Redraw (win.Bounds); + top.Redraw (top.Bounds); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────────────────────────┐ │ File Edit │ @@ -1713,7 +1713,7 @@ Edit └──────────────────────────────────────┘", output); Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); - win.Redraw (win.Bounds); + top.Redraw (top.Bounds); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────────────────────────┐ │ File Edit │ @@ -1725,7 +1725,7 @@ Edit └──────────────────────────────────────┘", output); Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); - win.Redraw (win.Bounds); + top.Redraw (top.Bounds); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌──────────────────────────────────────┐ │ File Edit │ diff --git a/UnitTests/TopLevels/ToplevelTests.cs b/UnitTests/TopLevels/ToplevelTests.cs index 75dd6f9db..c159184c6 100644 --- a/UnitTests/TopLevels/ToplevelTests.cs +++ b/UnitTests/TopLevels/ToplevelTests.cs @@ -1487,10 +1487,10 @@ namespace Terminal.Gui.TopLevelTests { btnPopup.Clicked += (s, e) => { var viewToScreen = btnPopup.ViewToScreen (top.Frame); var view = new View () { X = 1, Y = viewToScreen.Y + 1, Width = 18, Height = 5 }; - Application.IterationComplete += Application_IterationComplete; + Application.Current.DrawContentComplete += Current_DrawContentComplete; top.Add (view); - void Application_IterationComplete () + void Current_DrawContentComplete (object sender, DrawEventArgs e) { Assert.Equal (new Rect (1, 14, 18, 5), view.Frame); @@ -1502,7 +1502,7 @@ namespace Terminal.Gui.TopLevelTests { top.Move (2, 17); View.Driver.AddStr ("Three"); - Application.IterationComplete -= Application_IterationComplete; + Application.Current.DrawContentComplete -= Current_DrawContentComplete; } }; var dialog = new Dialog ("Dialog", 15, 10, btnPopup); From 31d0e159fd0f97afa9be284ef31b27b46c0d8842 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 3 Apr 2023 00:49:22 +0100 Subject: [PATCH 3/7] Fix forget change. --- Terminal.Gui/Views/Menu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 91a75882d..9e625a65e 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1388,7 +1388,7 @@ namespace Terminal.Gui { } openCurrentMenu.previousSubFocused = last.previousSubFocused; openSubMenu.Add (openCurrentMenu); - Application.Current.Add (openCurrentMenu); + Application.Top.Add (openCurrentMenu); } selectedSub = openSubMenu.Count - 1; if (selectedSub > -1 && SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) { From 6223d889c7ae63a1ab0196c67b95dc63073f1fa4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 3 Apr 2023 03:05:05 +0100 Subject: [PATCH 4/7] Fixes #2462. FindDeepestView should be a public static method on the View class. --- Terminal.Gui/Core/Application.cs | 37 +++-------------------------- Terminal.Gui/Core/View.cs | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 156a2a829..d8164b693 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -658,7 +658,7 @@ namespace Terminal.Gui { var rx = x - startFrame.X; var ry = y - startFrame.Y; if (top.Visible && top.Frame.Contains (rx, ry)) { - var deep = FindDeepestView (top, rx, ry, out resx, out resy); + var deep = View.FindDeepestView (top, rx, ry, out resx, out resy); if (deep == null) return FindDeepestMdiView (top, rx, ry, out resx, out resy); if (deep != MdiTop) @@ -671,37 +671,6 @@ namespace Terminal.Gui { return start; } - static View FindDeepestView (View start, int x, int y, out int resx, out int resy) - { - var startFrame = start.Frame; - - if (!startFrame.Contains (x, y)) { - resx = 0; - resy = 0; - return null; - } - - if (start.InternalSubviews != null) { - int count = start.InternalSubviews.Count; - if (count > 0) { - var rx = x - startFrame.X; - var ry = y - startFrame.Y; - for (int i = count - 1; i >= 0; i--) { - View v = start.InternalSubviews [i]; - if (v.Visible && v.Frame.Contains (rx, ry)) { - var deep = FindDeepestView (v, rx, ry, out resx, out resy); - if (deep == null) - return v; - return deep; - } - } - } - } - resx = x - startFrame.X; - resy = y - startFrame.Y; - return start; - } - static View FindTopFromView (View view) { View top = view?.SuperView != null && view?.SuperView != Top @@ -824,7 +793,7 @@ namespace Terminal.Gui { return; } - var view = FindDeepestView (Current, me.X, me.Y, out int rx, out int ry); + var view = View.FindDeepestView (Current, me.X, me.Y, out int rx, out int ry); if (view != null && view.WantContinuousButtonPressed) WantContinuousButtonPressedView = view; @@ -864,7 +833,7 @@ namespace Terminal.Gui { && me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) { var top = FindDeepestTop (Top, me.X, me.Y, out _, out _); - view = FindDeepestView (top, me.X, me.Y, out rx, out ry); + view = View.FindDeepestView (top, me.X, me.Y, out rx, out ry); if (view != null && view != MdiTop && top != Current) { MoveCurrent ((Toplevel)top); diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 51162a41d..de953fd49 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -3147,5 +3147,45 @@ namespace Terminal.Gui { return top; } + + /// + /// Finds which view that belong to the at the provided location. + /// + /// The view where to look for. + /// The column location. + /// The row location. + /// The found view column location. + /// The found view row location. + /// The view that belong to the provided location. + public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) + { + var startFrame = start.Frame; + + if (!startFrame.Contains (x, y)) { + resx = 0; + resy = 0; + return null; + } + + if (start.InternalSubviews != null) { + int count = start.InternalSubviews.Count; + if (count > 0) { + var rx = x - startFrame.X; + var ry = y - startFrame.Y; + for (int i = count - 1; i >= 0; i--) { + View v = start.InternalSubviews [i]; + if (v.Visible && v.Frame.Contains (rx, ry)) { + var deep = FindDeepestView (v, rx, ry, out resx, out resy); + if (deep == null) + return v; + return deep; + } + } + } + } + resx = x - startFrame.X; + resy = y - startFrame.Y; + return start; + } } } From 1badf8a58524dced46042221fc0a1381ed44be86 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 3 Apr 2023 03:06:15 +0100 Subject: [PATCH 5/7] Fix mouse issue in a modal view. --- Terminal.Gui/Core/Application.cs | 2 -- Terminal.Gui/Views/Menu.cs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index d8164b693..cc2d2a053 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -809,8 +809,6 @@ namespace Terminal.Gui { } if (mouseGrabView != null) { - view ??= mouseGrabView; - var newxy = mouseGrabView.ScreenToView (me.X, me.Y); var nme = new MouseEvent () { X = newxy.X, diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 9e625a65e..39d675e37 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -480,6 +480,7 @@ namespace Terminal.Gui { if (Application.Current != null) { Application.Current.DrawContentComplete += Current_DrawContentComplete; } + Application.RootMouseEvent += Application_RootMouseEvent; // Things this view knows how to do AddCommand (Command.LineUp, () => MoveUp ()); @@ -506,6 +507,22 @@ namespace Terminal.Gui { AddKeyBinding (Key.Enter, Command.Accept); } + private void Application_RootMouseEvent (MouseEvent me) + { + var view = View.FindDeepestView (this, me.X, me.Y, out int rx, out int ry); + if (view == this) { + var nme = new MouseEvent () { + X = rx, + Y = ry, + Flags = me.Flags, + View = view + }; + if (MouseEvent (nme) || me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Released) { + me.Handled = true; + } + } + } + internal Attribute DetermineColorSchemeFor (MenuItem item, int index) { if (item != null) { @@ -922,6 +939,7 @@ namespace Terminal.Gui { if (Application.Current != null) { Application.Current.DrawContentComplete -= Current_DrawContentComplete; } + Application.RootMouseEvent -= Application_RootMouseEvent; base.Dispose (disposing); } } From 75eca476d96f894497e421cf045e4e72864348d7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 3 Apr 2023 15:50:41 +0100 Subject: [PATCH 6/7] Done requested changes. --- Terminal.Gui/Core/View.cs | 17 ++++++++++------- UnitTests/Menus/ContextMenuTests.cs | 14 +++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index beeff72ea..ed749d67d 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -3160,14 +3160,17 @@ namespace Terminal.Gui { } /// - /// Finds which view that belong to the at the provided location. + /// Finds which view that belong to the superview at the provided location. /// - /// The view where to look for. - /// The column location. - /// The row location. - /// The found view column location. - /// The found view row location. - /// The view that belong to the provided location. + /// The superview where to look for. + /// The column location in the superview. + /// The row location in the superview. + /// The found view screen relative column location. + /// The found view screen relative row location. + /// + /// The view that was found at the and coordinates. + /// if no view was found. + /// public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) { var startFrame = start.Frame; diff --git a/UnitTests/Menus/ContextMenuTests.cs b/UnitTests/Menus/ContextMenuTests.cs index 03df8de50..7368ff1a4 100644 --- a/UnitTests/Menus/ContextMenuTests.cs +++ b/UnitTests/Menus/ContextMenuTests.cs @@ -907,14 +907,14 @@ namespace Terminal.Gui.MenuTests { public void Draw_A_ContextManu_Over_A_Dialog () { var top = Application.Top; - var win = new Window ("Window"); + var win = new Window (); top.Add (win); Application.Begin (top); ((FakeDriver)Application.Driver).SetBufferSize (20, 15); Assert.Equal (new Rect (0, 0, 20, 15), win.Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" -┌ Window ──────────┐ +┌──────────────────┐ │ │ │ │ │ │ @@ -930,15 +930,15 @@ namespace Terminal.Gui.MenuTests { │ │ └──────────────────┘", output); - var dialog = new Dialog ("Dialog") { X = 2, Y = 2, Width = 15, Height = 4 }; + var dialog = new Dialog () { X = 2, Y = 2, Width = 15, Height = 4 }; dialog.Add (new TextField ("Test") { X = Pos.Center (), Width = 10 }); var rs = Application.Begin (dialog); Assert.Equal (new Rect (2, 2, 15, 4), dialog.Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" -┌ Window ──────────┐ +┌──────────────────┐ │ │ -│ ┌ Dialog ─────┐ │ +│ ┌─────────────┐ │ │ │ Test │ │ │ │ │ │ │ └─────────────┘ │ @@ -964,9 +964,9 @@ namespace Terminal.Gui.MenuTests { var firstIteration = false; Application.RunMainLoopIteration (ref rs, true, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" -┌ Window ──────────┐ +┌──────────────────┐ │ │ -│ ┌ Dialog ─────┐ │ +│ ┌─────────────┐ │ │ │ Test │ │ ┌─────────────────── │ Select All Ctrl+ From 4745eb51ada68766837ceeb7a2b65c13800d18c0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 4 Apr 2023 18:29:02 +0100 Subject: [PATCH 7/7] Fix merge errors and bugs. --- Terminal.Gui/Core/Application.cs | 3 +-- Terminal.Gui/Core/View.cs | 21 ++++++++++----------- Terminal.Gui/Views/Menu.cs | 13 ++++++++++++- UnitTests/TopLevels/ToplevelTests.cs | 27 ++++++++++++++++++--------- 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index e6b21d65e..c72ec41f5 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -1230,7 +1230,7 @@ namespace Terminal.Gui { firstIteration = false; if (state.Toplevel != Top - && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { + && (!Top._needsDisplay.IsEmpty || Top._childNeedsDisplay || Top.LayoutNeeded)) { state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); Top.Redraw (Top.Bounds); foreach (var top in toplevels.Reverse ()) { @@ -1252,7 +1252,6 @@ namespace Terminal.Gui { if (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._childNeedsDisplay || state.Toplevel.LayoutNeeded || MdiChildNeedsDisplay ()) { state.Toplevel.Redraw (state.Toplevel.Bounds); - state.Toplevel.OnDrawContentComplete (state.Toplevel.Bounds); if (DebugDrawBounds) { DrawBounds (state.Toplevel); } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 4d0debe8e..b838da258 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1038,7 +1038,7 @@ namespace Terminal.Gui { // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm... frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! - + } //// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) { @@ -1047,7 +1047,7 @@ namespace Terminal.Gui { SetMinWidthHeight (); SetNeedsLayout (); SetNeedsDisplay (); - } + } } void TextFormatter_HotKeyChanged (object sender, KeyChangedEventArgs e) @@ -1139,12 +1139,12 @@ namespace Terminal.Gui { /// public void SetSubViewNeedsDisplay () { - if (ChildNeedsDisplay) { + if (_childNeedsDisplay) { return; } - ChildNeedsDisplay = true; - if (container != null && !container.ChildNeedsDisplay) - container.SetSubViewNeedsDisplay (); + _childNeedsDisplay = true; + if (_superView != null && !_superView._childNeedsDisplay) + _superView.SetSubViewNeedsDisplay (); } internal bool addingView; @@ -1493,7 +1493,7 @@ namespace Terminal.Gui { /// View-relative region for the frame to be drawn. /// The padding to add around the outside of the drawn frame. /// If set to it fill will the contents. - [ObsoleteAttribute ("This method is obsolete in v2. Use use LineCanvas or Frame instead instead.", false)] + [ObsoleteAttribute ("This method is obsolete in v2. Use use LineCanvas or Frame instead.", false)] public void DrawFrame (Rect region, int padding = 0, bool fill = false) { var scrRect = ViewToScreen (region); @@ -1726,7 +1726,7 @@ namespace Terminal.Gui { } /// - /// Removes the and the setting on this view. + /// Removes the and the setting on this view. /// protected void ClearNeedsDisplay () { @@ -3505,12 +3505,11 @@ namespace Terminal.Gui { resy = 0; return null; } - if (start.InternalSubviews != null) { int count = start.InternalSubviews.Count; if (count > 0) { - var rx = x - startFrame.X; - var ry = y - startFrame.Y; + var rx = x - (startFrame.X + start.GetBoundsOffset ().X); + var ry = y - (startFrame.Y + start.GetBoundsOffset ().Y); for (int i = count - 1; i >= 0; i--) { View v = start.InternalSubviews [i]; if (v.Visible && v.Frame.Contains (rx, ry)) { diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 39d675e37..c0c63b831 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -542,6 +542,9 @@ namespace Terminal.Gui { if (barItems.Children == null) { return; } + var savedClip = Driver.Clip; + Application.Driver.Clip = Application.Top.Frame; + Driver.SetAttribute (GetNormalColor ()); DrawFrame (Bounds, padding: 0, fill: true); @@ -640,6 +643,8 @@ namespace Terminal.Gui { } } } + Driver.Clip = savedClip; + PositionCursor (); } @@ -1372,7 +1377,13 @@ namespace Terminal.Gui { // menu it belongs to's text for (int i = 0; i < index; i++) pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + leftPadding + rightPadding; - var locationOffset = SuperView == null ? new Point (0, 0) : new Point (SuperView.Frame.X, SuperView.Frame.Y); + var superView = SuperView == null ? Application.Top : SuperView; + Point locationOffset; + if (superView.Border != null && superView.Border.BorderStyle != BorderStyle.None) { + locationOffset = new Point (superView.Frame.X + 1, superView.Frame.Y + 1); + } else { + locationOffset = new Point (superView.Frame.X, superView.Frame.Y); + } openMenu = new Menu (this, Frame.X + pos + locationOffset.X, Frame.Y + 1 + locationOffset.Y, Menus [index]); openCurrentMenu = openMenu; openCurrentMenu.previousSubFocused = openMenu; diff --git a/UnitTests/TopLevels/ToplevelTests.cs b/UnitTests/TopLevels/ToplevelTests.cs index cf91c5fb6..336597378 100644 --- a/UnitTests/TopLevels/ToplevelTests.cs +++ b/UnitTests/TopLevels/ToplevelTests.cs @@ -1385,14 +1385,14 @@ namespace Terminal.Gui.TopLevelTests { public void Draw_A_Top_Subview_On_A_Dialog () { var top = Application.Top; - var win = new Window ("Window"); + var win = new Window (); top.Add (win); Application.Begin (top); ((FakeDriver)Application.Driver).SetBufferSize (20, 20); Assert.Equal (new Rect (0, 0, 20, 20), win.Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" -┌ Window ──────────┐ +┌──────────────────┐ │ │ │ │ │ │ @@ -1416,7 +1416,13 @@ namespace Terminal.Gui.TopLevelTests { var btnPopup = new Button ("Popup"); btnPopup.Clicked += (s, e) => { var viewToScreen = btnPopup.ViewToScreen (top.Frame); - var view = new View () { X = 1, Y = viewToScreen.Y + 1, Width = 18, Height = 5 }; + var view = new View () { + X = 1, + Y = viewToScreen.Y + 1, + Width = 18, + Height = 5, + Border = new Border () { BorderStyle = BorderStyle.Single } + }; Application.Current.DrawContentComplete += Current_DrawContentComplete; top.Add (view); @@ -1424,28 +1430,31 @@ namespace Terminal.Gui.TopLevelTests { { Assert.Equal (new Rect (1, 14, 18, 5), view.Frame); - top.DrawFrame (view.Frame); + var savedClip = Application.Driver.Clip; + Application.Driver.Clip = top.Frame; + view.Redraw (view.Bounds); top.Move (2, 15); View.Driver.AddStr ("One"); top.Move (2, 16); View.Driver.AddStr ("Two"); top.Move (2, 17); View.Driver.AddStr ("Three"); + Application.Driver.Clip = savedClip; Application.Current.DrawContentComplete -= Current_DrawContentComplete; } }; - var dialog = new Dialog ("Dialog", 15, 10, btnPopup); + var dialog = new Dialog ("", 15, 10, btnPopup); var rs = Application.Begin (dialog); Assert.Equal (new Rect (2, 5, 15, 10), dialog.Frame); TestHelpers.AssertDriverContentsWithFrameAre (@" -┌ Window ──────────┐ +┌──────────────────┐ │ │ │ │ │ │ │ │ -│ ┌ Dialog ─────┐ │ +│ ┌─────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -1473,12 +1482,12 @@ namespace Terminal.Gui.TopLevelTests { var firstIteration = false; Application.RunMainLoopIteration (ref rs, true, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" -┌ Window ──────────┐ +┌──────────────────┐ │ │ │ │ │ │ │ │ -│ ┌ Dialog ─────┐ │ +│ ┌─────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │