Respect minimum panel sizes and scenario support for toggle collapse

This commit is contained in:
tznind
2022-12-24 11:22:04 +00:00
parent 45d697d305
commit b1c7efea7f
3 changed files with 201 additions and 21 deletions

View File

@@ -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;
}
}

View File

@@ -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()
{

View File

@@ -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 () {