From 3937feb976a9b323a650c104cca60b9ae13c438d Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 20 Mar 2023 23:11:02 +0000 Subject: [PATCH] Fixes #2434. Dialog bounds bigger than Cols and Rows must be allowed to drag beyond left, right and bottom. --- Terminal.Gui/Core/Toplevel.cs | 44 ++++--- UnitTests/TopLevels/ToplevelTests.cs | 179 ++++++++++++++++++++++++--- 2 files changed, 193 insertions(+), 30 deletions(-) diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 75a8a822d..b7038268f 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -210,7 +210,7 @@ namespace Terminal.Gui { Application.GrabbingMouse += Application_GrabbingMouse; Application.UnGrabbingMouse += Application_UnGrabbingMouse; - + // TODO: v2 - ALL Views (Responders??!?!) should support the commands related to // - Focus // Move the appropriate AddCommand calls to `Responder` @@ -378,8 +378,7 @@ namespace Terminal.Gui { /// /// if was already loaded by the - /// , otherwise. This is used to avoid the - /// having wrong values while this was not yet loaded. + /// , otherwise. /// public bool IsLoaded { get; private set; } @@ -627,11 +626,15 @@ namespace Terminal.Gui { l = top.SuperView.Frame.Width; superView = top.SuperView; } - nx = Math.Max (x, 0); - nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx; var mfLength = top.Border?.DrawMarginFrame == true ? 2 : 1; - if (nx + mfLength > top.Frame.X + top.Frame.Width) { - nx = Math.Max (top.Frame.Right - mfLength, 0); + if (top.Frame.Width <= l) { + nx = Math.Max (x, 0); + nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx; + if (nx + mfLength > top.Frame.X + top.Frame.Width) { + nx = Math.Max (top.Frame.Right - mfLength, 0); + } + } else { + nx = x; } //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); bool m, s; @@ -640,7 +643,7 @@ namespace Terminal.Gui { mb = Application.Top.MenuBar; } else { var t = top.SuperView; - while (!(t is Toplevel)) { + while (t is not Toplevel) { t = t.SuperView; } m = ((Toplevel)t).MenuBar?.Visible == true; @@ -657,7 +660,7 @@ namespace Terminal.Gui { sb = Application.Top.StatusBar; } else { var t = top.SuperView; - while (!(t is Toplevel)) { + while (t is not Toplevel) { t = t.SuperView; } s = ((Toplevel)t).StatusBar?.Visible == true; @@ -669,9 +672,11 @@ namespace Terminal.Gui { l = s ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height; } ny = Math.Min (ny, l); - ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; - if (ny + mfLength > top.Frame.Y + top.Frame.Height) { - ny = Math.Max (top.Frame.Bottom - mfLength, 0); + if (top.Frame.Height <= l) { + ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; + if (ny + mfLength > top.Frame.Y + top.Frame.Height) { + ny = Math.Max (top.Frame.Bottom - mfLength, 0); + } } //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); @@ -698,15 +703,15 @@ namespace Terminal.Gui { var superView = EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny, out _, out StatusBar sb); bool layoutSubviews = false; - if ((top?.SuperView != null || (top != Application.Top && top.Modal) + if ((superView != top || top?.SuperView != null || (top != Application.Top && top.Modal) || (top?.SuperView == null && top.IsMdiChild)) - && (nx > top.Frame.X || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { + && (top.Frame.X + top.Frame.Width > Driver.Cols || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { - if ((top.X == null || top.X is Pos.PosAbsolute) && top.Bounds.X != nx) { + if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) { top.X = nx; layoutSubviews = true; } - if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Bounds.Y != ny) { + if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) { top.Y = ny; layoutSubviews = true; } @@ -994,6 +999,13 @@ namespace Terminal.Gui { { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } + + /// + protected override void Dispose (bool disposing) + { + dragPosition = null; + base.Dispose (disposing); + } } /// diff --git a/UnitTests/TopLevels/ToplevelTests.cs b/UnitTests/TopLevels/ToplevelTests.cs index c5a95a34e..559d90219 100644 --- a/UnitTests/TopLevels/ToplevelTests.cs +++ b/UnitTests/TopLevels/ToplevelTests.cs @@ -149,7 +149,6 @@ namespace Terminal.Gui.TopLevelTests { [AutoInitShutdown] public void Internal_Tests () { - Toplevel.dragPosition = null; // dragPosition is `static` and must be reset for each instance or unit tests will fail? var top = new Toplevel (); var eventInvoked = ""; @@ -200,7 +199,7 @@ namespace Terminal.Gui.TopLevelTests { Application.Begin (top); Assert.Equal (top, Application.Top); - // top is Application.Top without menu and status bar. + // Application.Top without menu and status bar. var supView = top.EnsureVisibleBounds (top, 2, 2, out int nx, out int ny, out MenuBar mb, out StatusBar sb); Assert.Equal (Application.Top, supView); Assert.Equal (0, nx); @@ -211,7 +210,7 @@ namespace Terminal.Gui.TopLevelTests { top.AddMenuStatusBar (new MenuBar ()); Assert.NotNull (top.MenuBar); - // top is Application.Top with a menu and without status bar. + // Application.Top with a menu and without status bar. top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb); Assert.Equal (0, nx); Assert.Equal (1, ny); @@ -221,20 +220,24 @@ namespace Terminal.Gui.TopLevelTests { top.AddMenuStatusBar (new StatusBar ()); Assert.NotNull (top.StatusBar); - // top is Application.Top with a menu and status bar. + // Application.Top with a menu and status bar. top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb); Assert.Equal (0, nx); - Assert.Equal (1, ny); + // The available height is lower than the Application.Top height minus + // the menu bar and status bar, then the top can go beyond the bottom + Assert.Equal (2, ny); Assert.NotNull (mb); Assert.NotNull (sb); top.RemoveMenuStatusBar (top.MenuBar); Assert.Null (top.MenuBar); - // top is Application.Top without a menu and with a status bar. + // Application.Top without a menu and with a status bar. top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb); Assert.Equal (0, nx); - Assert.Equal (0, ny); + // The available height is lower than the Application.Top height minus + // the status bar, then the top can go beyond the bottom + Assert.Equal (2, ny); Assert.Null (mb); Assert.NotNull (sb); @@ -252,7 +255,7 @@ namespace Terminal.Gui.TopLevelTests { supView = win.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb); Assert.Equal (Application.Top, supView); - // top is Application.Top without menu and status bar. + // Application.Top without menu and status bar. top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb); Assert.Equal (0, nx); Assert.Equal (0, ny); @@ -262,7 +265,7 @@ namespace Terminal.Gui.TopLevelTests { top.AddMenuStatusBar (new MenuBar ()); Assert.NotNull (top.MenuBar); - // top is Application.Top with a menu and without status bar. + // Application.Top with a menu and without status bar. top.EnsureVisibleBounds (win, 2, 2, out nx, out ny, out mb, out sb); Assert.Equal (0, nx); Assert.Equal (1, ny); @@ -272,10 +275,12 @@ namespace Terminal.Gui.TopLevelTests { top.AddMenuStatusBar (new StatusBar ()); Assert.NotNull (top.StatusBar); - // top is Application.Top with a menu and status bar. + // Application.Top with a menu and status bar. top.EnsureVisibleBounds (win, 30, 20, out nx, out ny, out mb, out sb); Assert.Equal (0, nx); - Assert.Equal (1, ny); + // The available height is lower than the Application.Top height minus + // the menu bar and status bar, then the top can go beyond the bottom + Assert.Equal (20, ny); Assert.NotNull (mb); Assert.NotNull (sb); @@ -289,7 +294,7 @@ namespace Terminal.Gui.TopLevelTests { win = new Window () { Width = 60, Height = 15 }; top.Add (win); - // top is Application.Top without menu and status bar. + // Application.Top without menu and status bar. top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb); Assert.Equal (0, nx); Assert.Equal (0, ny); @@ -299,7 +304,7 @@ namespace Terminal.Gui.TopLevelTests { top.AddMenuStatusBar (new MenuBar ()); Assert.NotNull (top.MenuBar); - // top is Application.Top with a menu and without status bar. + // Application.Top with a menu and without status bar. top.EnsureVisibleBounds (win, 2, 2, out nx, out ny, out mb, out sb); Assert.Equal (2, nx); Assert.Equal (2, ny); @@ -309,7 +314,7 @@ namespace Terminal.Gui.TopLevelTests { top.AddMenuStatusBar (new StatusBar ()); Assert.NotNull (top.StatusBar); - // top is Application.Top with a menu and status bar. + // Application.Top with a menu and status bar. top.EnsureVisibleBounds (win, 30, 20, out nx, out ny, out mb, out sb); Assert.Equal (20, nx); // 20+60=80 Assert.Equal (9, ny); // 9+15+1(mb)=25 @@ -1213,5 +1218,151 @@ namespace Terminal.Gui.TopLevelTests { }); Assert.Equal (scrollView, Application.MouseGrabView); } + + [Fact, AutoInitShutdown] + public void Dialog_Bounds_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_Left_Right_And_Bottom () + { + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem("File", new MenuItem [] { + new MenuItem("New", "", null) + }) + }); + + var sb = new StatusBar (new StatusItem [] { + new StatusItem(Key.N, "~CTRL-N~ New", null) + }); + var top = Application.Top; + top.Add (menu, sb); + var dialog = new Dialog ("Dialog", 20, 3, new Button ("Ok")); + Application.Begin (top); + ((FakeDriver)Application.Driver).SetBufferSize (40, 10); + Application.Begin (dialog); + Application.Refresh (); + Assert.Equal (new Rect (0, 0, 40, 10), top.Frame); + Assert.Equal (new Rect (10, 3, 20, 3), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" + File + + + ┌ Dialog ──────────┐ + │ [ Ok ] │ + └──────────────────┘ + + + + CTRL-N New ", output); + + Assert.Null (Application.MouseGrabView); + + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = 10, + Y = 3, + Flags = MouseFlags.Button1Pressed + }); + + Assert.Equal (dialog, Application.MouseGrabView); + + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = -11, + Y = -4, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + + Application.Refresh (); + Assert.Equal (new Rect (0, 0, 40, 10), top.Frame); + Assert.Equal (new Rect (0, 1, 20, 3), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" + File +┌ Dialog ──────────┐ +│ [ Ok ] │ +└──────────────────┘ + + + + + + CTRL-N New ", output); + + // Changes Top size to same size as Dialog more menu and scroll bar + ((FakeDriver)Application.Driver).SetBufferSize (20, 5); + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = -1, + Y = -1, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + + Application.Refresh (); + Assert.Equal (new Rect (0, 0, 20, 5), top.Frame); + Assert.Equal (new Rect (0, 1, 20, 3), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" + File +┌ Dialog ──────────┐ +│ [ Ok ] │ +└──────────────────┘ + CTRL-N New ", output); + + // Changes Top size smaller than Dialog size + ((FakeDriver)Application.Driver).SetBufferSize (19, 3); + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = -1, + Y = -1, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + + Application.Refresh (); + Assert.Equal (new Rect (0, 0, 19, 3), top.Frame); + Assert.Equal (new Rect (-1, 1, 20, 3), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" + File + Dialog ──────────┐ + [ Ok ] │", output); + + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = 18, + Y = 3, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + + Application.Refresh (); + Assert.Equal (new Rect (0, 0, 19, 3), top.Frame); + Assert.Equal (new Rect (18, 2, 20, 3), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" + File + + CTRL-N New ┌", output); + + // On a real app we can't go beyond the SuperView bounds + ReflectionTools.InvokePrivate ( + typeof (Application), + "ProcessMouseEvent", + new MouseEvent () { + X = 19, + Y = 4, + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }); + + Application.Refresh (); + Assert.Equal (new Rect (0, 0, 19, 3), top.Frame); + Assert.Equal (new Rect (19, 2, 20, 3), dialog.Frame); + TestHelpers.AssertDriverContentsWithFrameAre (@" + File + + CTRL-N New", output); + } } } \ No newline at end of file