mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-30 17:57:57 +01:00
Respect minimum panel sizes and scenario support for toggle collapse
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user