Kaynağa Gözat

Fixes #2434. Dialog bounds bigger than Cols and Rows must be allowed to drag beyond left, right and bottom.

BDisp 2 yıl önce
ebeveyn
işleme
3937feb976
2 değiştirilmiş dosya ile 193 ekleme ve 30 silme
  1. 28 16
      Terminal.Gui/Core/Toplevel.cs
  2. 165 14
      UnitTests/TopLevels/ToplevelTests.cs

+ 28 - 16
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 {
 
 		/// <summary>
 		/// <see langword="true"/> if was already loaded by the <see cref="Application.Begin(Toplevel)"/>
-		/// <see langword="false"/>, otherwise. This is used to avoid the <see cref="View._needsDisplay"/>
-		/// having wrong values while this was not yet loaded.
+		/// <see langword="false"/>, otherwise.
 		/// </summary>
 		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);
 		}
+
+		///<inheritdoc/>
+		protected override void Dispose (bool disposing)
+		{
+			dragPosition = null;
+			base.Dispose (disposing);
+		}
 	}
 
 	/// <summary>

+ 165 - 14
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);
+		}
 	}
 }