diff --git a/Terminal.Gui/Views/SplitContainer.cs b/Terminal.Gui/Views/SplitContainer.cs index c10ca03eb..04bef1e81 100644 --- a/Terminal.Gui/Views/SplitContainer.cs +++ b/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; /// /// Creates a new instance of the SplitContainer class. /// 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 can be when adjusting /// . /// - public int Panel1MinSize { + public Pos Panel1MinSize { get { return panel1MinSize; } set { panel1MinSize = value; @@ -77,7 +74,16 @@ namespace Terminal.Gui { /// The minimum size can be when adjusting /// . /// - public int Panel2MinSize { get; set; } + public Pos Panel2MinSize { + get { + return panel2MinSize; + } + + set { + panel2MinSize = value; + Setup (); + } + } /// /// This determines if 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 (); } + /// + /// Considers as a candidate for + /// then either returns (if valid) or returns adjusted if invalid with respect to + /// or . + /// + /// + /// + 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; } } diff --git a/UICatalog/Scenarios/SplitContainerExample.cs b/UICatalog/Scenarios/SplitContainerExample.cs index 7ef6beda1..474adef44 100644 --- a/UICatalog/Scenarios/SplitContainerExample.cs +++ b/UICatalog/Scenarios/SplitContainerExample.cs @@ -11,6 +11,9 @@ namespace UICatalog.Scenarios { private MenuItem miVertical; + private MenuItem miShowBoth; + private MenuItem miShowPanel1; + private MenuItem miShowPanel2; /// /// 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() { diff --git a/UnitTests/SplitContainerTests.cs b/UnitTests/SplitContainerTests.cs index 81c7c4ce9..0d84dbf41 100644 --- a/UnitTests/SplitContainerTests.cs +++ b/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 () {