Browse Source

Respect minimum panel sizes and scenario support for toggle collapse

tznind 2 years ago
parent
commit
b1c7efea7f

+ 65 - 17
Terminal.Gui/Views/SplitContainer.cs

@@ -9,17 +9,14 @@ namespace Terminal.Gui {
 		private bool panel2Collapsed;
 		private Pos splitterDistance = Pos.Percent (50);
 		private Orientation orientation = Orientation.Vertical;
-		private int panel1MinSize;
+		private Pos panel1MinSize = 0;
+		private Pos panel2MinSize = 0;
 
 		/// <summary>
 		/// Creates a new instance of the SplitContainer class.
 		/// </summary>
 		public SplitContainer ()
 		{
-			// Default to a border of 0 but not null so that user can
-			// more easily change size (without null references)
-			Border = new Border ();
-
 			splitterLine = new SplitContainerLineView (this);
 
 			this.Add (Panel1);
@@ -42,7 +39,7 @@ namespace Terminal.Gui {
 		/// The minimum size <see cref="Panel1"/> can be when adjusting
 		/// <see cref="SplitterDistance"/>.
 		/// </summary>
-		public int Panel1MinSize {
+		public Pos Panel1MinSize {
 			get { return panel1MinSize; }
 			set {
 				panel1MinSize = value;
@@ -77,7 +74,16 @@ namespace Terminal.Gui {
 		/// The minimum size <see cref="Panel2"/> can be when adjusting
 		/// <see cref="SplitterDistance"/>.
 		/// </summary>
-		public int Panel2MinSize { get; set; }
+		public Pos Panel2MinSize {
+			get {
+				return panel2MinSize;
+			}
+
+			set {
+				panel2MinSize = value;
+				Setup ();
+			}
+		}
 
 		/// <summary>
 		/// This determines if <see cref="Panel2"/> is collapsed.
@@ -129,7 +135,6 @@ namespace Terminal.Gui {
 			Clear ();
 			base.Redraw (bounds);
 		}
-		
 
 		private void Setup ()
 		{
@@ -157,6 +162,8 @@ namespace Terminal.Gui {
 				this.Add (Panel2);
 			}
 
+			splitterDistance = BoundByMinimumSizes (splitterDistance);
+
 			switch (Orientation) {
 			case Orientation.Horizontal:
 				splitterLine.X = 0;
@@ -202,16 +209,58 @@ namespace Terminal.Gui {
 			this.LayoutSubviews ();
 		}
 
+		/// <summary>
+		/// Considers <paramref name="pos"/> as a candidate for <see cref="splitterDistance"/>
+		/// then either returns (if valid) or returns adjusted if invalid with respect to
+		/// <see cref="Panel1MinSize"/> or <see cref="Panel2MinSize"/>.
+		/// </summary>
+		/// <param name="pos"></param>
+		/// <returns></returns>
+		private Pos BoundByMinimumSizes (Pos pos)
+		{
+			// if we are not yet initialized then we don't know
+			// how big we are and therefore cannot sensibly calculate
+			// how big the panels will be with a given SplitterDistance
+			if (!IsInitialized) {
+				return pos;
+			}
+
+			var availableSpace = Orientation == Orientation.Horizontal ? this.Bounds.Height : this.Bounds.Width;
+
+			var idealPosition = pos.Anchor (availableSpace);
+			var panel1MinSizeAbs = panel1MinSize.Anchor (availableSpace);
+			var panel2MinSizeAbs = panel2MinSize.Anchor (availableSpace);
+
+			// bad position because not enough space for panel1
+			if (idealPosition < panel1MinSizeAbs) {
+
+				// TODO: we should preserve Absolute/Percent status here not just force it to absolute
+				return (Pos)Math.Min (panel1MinSizeAbs, availableSpace);
+			}
+			
+			// bad position because not enough space for panel2
+			if(availableSpace - idealPosition <= panel2MinSizeAbs) {
+
+				// TODO: we should preserve Absolute/Percent status here not just force it to absolute
+
+				// +1 is to allow space for the splitter
+				return (Pos)Math.Max (availableSpace - (panel2MinSizeAbs+1), 0);
+			}
+
+			// this splitter position is fine, there is enough space for everyone
+			return pos;
+		}
+
 		private void SetupForCollapsedPanel ()
 		{
 			View toRemove = panel1Collapsed ? Panel1 : Panel2;
 			View toFullSize = panel1Collapsed ? Panel2 : Panel1;
 
 			if (this.Subviews.Contains (splitterLine)) {
-				this.Subviews.Remove (splitterLine);
+				this.Remove(splitterLine);
 			}
 			if (this.Subviews.Contains (toRemove)) {
-				this.Subviews.Remove (toRemove);
+				this.Remove (toRemove);
 			}
 			if (!this.Subviews.Contains (toFullSize)) {
 				this.Add (toFullSize);
@@ -239,7 +288,7 @@ namespace Terminal.Gui {
 				this.parent = parent;
 
 				base.AddCommand (Command.Right, () => {
-					return MoveSplitter (1,0);
+					return MoveSplitter (1, 0);
 				});
 
 				base.AddCommand (Command.Left, () => {
@@ -247,7 +296,7 @@ namespace Terminal.Gui {
 				});
 
 				base.AddCommand (Command.LineUp, () => {
-					return MoveSplitter (0,-1);
+					return MoveSplitter (0, -1);
 				});
 
 				base.AddCommand (Command.LineDown, () => {
@@ -267,7 +316,7 @@ namespace Terminal.Gui {
 				if (!CanFocus || !HasFocus) {
 					return base.ProcessKey (kb);
 				}
-				
+
 				var result = InvokeKeybindings (kb);
 				if (result != null)
 					return (bool)result;
@@ -296,7 +345,7 @@ namespace Terminal.Gui {
 					var location = moveRuneRenderLocation ??
 						new Point (Bounds.Width / 2, Bounds.Height / 2);
 
-					AddRune (location.X,location.Y, Driver.Diamond);
+					AddRune (location.X, location.Y, Driver.Diamond);
 				}
 
 			}
@@ -309,7 +358,7 @@ namespace Terminal.Gui {
 				}
 
 				if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed)) {
-					
+
 					// Start a Drag
 					SetFocus ();
 					Application.EnsuresTopOnFront ();
@@ -410,8 +459,7 @@ namespace Terminal.Gui {
 					} else {
 						parent.SplitterDistance = ConvertToPosFactor (newValue, parent.Bounds.Width);
 					}
-				}
-				else {
+				} else {
 					parent.SplitterDistance = newValue;
 				}
 			}

+ 45 - 3
UICatalog/Scenarios/SplitContainerExample.cs

@@ -11,6 +11,9 @@ namespace UICatalog.Scenarios {
 
 
 		private MenuItem miVertical;
+		private MenuItem miShowBoth;
+		private MenuItem miShowPanel1;
+		private MenuItem miShowPanel2;
 
 		/// <summary>
 		/// Setup the scenario.
@@ -21,10 +24,16 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1;
 
+			Win.Add (new Label ("This is a SplitContainer with a minimum panel size of 2.  Drag the splitter to resize:"));
+			Win.Add (new LineView (Orientation.Horizontal) { Y = 1 });
+
 			splitContainer = new SplitContainer {
+				Y = 2,
 				Width = Dim.Fill (),
 				Height = Dim.Fill (),
 				SplitterDistance = Pos.Percent (50), // TODO: get this to work with drag resizing and percents
+				Panel1MinSize = 2,
+				Panel2MinSize = 2,
 			};
 
 
@@ -52,12 +61,45 @@ namespace UICatalog.Scenarios {
 				miVertical = new MenuItem ("_Vertical", "", () => ToggleOrientation())
 				{
 					Checked = splitContainer.Orientation == Orientation.Vertical,
-					CheckType = MenuItemCheckStyle.Checked 
-				}})
-			});
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				new MenuBarItem ("_Show", new MenuItem [] {
+						miShowBoth = new MenuItem ("Both", "",()=>{
+							splitContainer.Panel1Collapsed = false;
+							splitContainer.Panel2Collapsed = false;
+							UpdateShowMenuCheckedStates();
+						}),
+						miShowPanel1 = new MenuItem ("Panel1", "", () => {
+
+							splitContainer.Panel2Collapsed = true;
+							UpdateShowMenuCheckedStates();
+						}),
+						miShowPanel2 = new MenuItem ("Panel2", "", () => {
+							splitContainer.Panel1Collapsed = true;
+							UpdateShowMenuCheckedStates();
+						}),							
+					})
+				}),
+
+			}) ;
+
+			UpdateShowMenuCheckedStates ();
+			
 			Application.Top.Add (menu);
 		}
 
+		private void UpdateShowMenuCheckedStates ()
+		{
+			miShowBoth.Checked = (!splitContainer.Panel1Collapsed) && (!splitContainer.Panel2Collapsed);
+			miShowBoth.CheckType = MenuItemCheckStyle.Checked;
+
+			miShowPanel1.Checked = splitContainer.Panel2Collapsed;
+			miShowPanel1.CheckType = MenuItemCheckStyle.Checked;
+
+			miShowPanel2.Checked = splitContainer.Panel1Collapsed;
+			miShowPanel2.CheckType = MenuItemCheckStyle.Checked;
+		}
+
 		public void ToggleOrientation()
 		{
 

+ 91 - 1
UnitTests/SplitContainerTests.cs

@@ -95,6 +95,53 @@ namespace UnitTests {
 			TestHelpers.AssertDriverContentsAre (looksLike, output);
 		}
 
+
+		[Fact, AutoInitShutdown]
+		public void TestSplitContainer_Vertical_Panel1MinSize_Absolute ()
+		{
+			var splitContainer = Get11By3SplitContainer ();
+			
+			splitContainer.EnsureFocus ();
+			splitContainer.FocusFirst ();
+			splitContainer.Panel1MinSize = 6;
+
+			// distance is too small (below 6)
+			splitContainer.SplitterDistance = 2;
+
+			// Should bound the value to the minimum distance
+			Assert.Equal(6,splitContainer.SplitterDistance);
+
+			splitContainer.Redraw (splitContainer.Bounds);
+
+			// so should ignore the 2 distance and stick to 6
+			string looksLike =
+@"
+111111│2222
+      ◊
+      │     ";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Keyboard movement on splitter should have no effect because it
+			// would take us below the minimum splitter size
+			splitContainer.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+			splitContainer.SetNeedsDisplay ();
+			splitContainer.Redraw (splitContainer.Bounds);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// but we can continue to move the splitter right if we want
+			splitContainer.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+			splitContainer.SetNeedsDisplay ();
+			splitContainer.Redraw (splitContainer.Bounds);
+	
+			looksLike =
+@"
+1111111│222
+       ◊
+       │     ";
+
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+		}
 		[Fact, AutoInitShutdown]
 		public void TestSplitContainer_Horizontal_Focused ()
 		{
@@ -132,10 +179,53 @@ namespace UnitTests {
 ─────◊─────
 22222222222";
 			TestHelpers.AssertDriverContentsAre (looksLike, output);
-
 		}
 
 
+		[Fact, AutoInitShutdown]
+		public void TestSplitContainer_Horizontal_Panel1MinSize_Absolute ()
+		{
+			var splitContainer = Get11By3SplitContainer ();
+
+			splitContainer.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal;
+			splitContainer.EnsureFocus ();
+			splitContainer.FocusFirst ();
+			splitContainer.Panel1MinSize = 1;
+
+			// 0 should not be allowed because it brings us below minimum size of Panel1
+			splitContainer.SplitterDistance = 0;
+			Assert.Equal((Pos)1,splitContainer.SplitterDistance);
+
+			splitContainer.Redraw (splitContainer.Bounds);
+
+			string looksLike =
+@"    
+11111111111
+─────◊─────
+22222222222";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// Now move splitter line down (allowed
+			splitContainer.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()));
+			splitContainer.Redraw (splitContainer.Bounds);
+			looksLike =
+@"    
+11111111111
+
+─────◊─────";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+
+			// And up 2 (only 1 is allowed because of minimum size of 1 on panel1)
+			splitContainer.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+			splitContainer.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+			splitContainer.Redraw (splitContainer.Bounds);
+			looksLike =
+@"    
+11111111111
+─────◊─────
+22222222222";
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
+		}
 		private SplitContainer Get11By3SplitContainer ()
 		{
 			var container = new SplitContainer () {