mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Remove TileView; use View.Arrangement instead (#4271)
* Initial plan * Remove TileView and refactor to use View.Arrangement Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix FileDialog container focus behavior - all tests passing Co-authored-by: tig <585482+tig@users.noreply.github.com> * Remove obsolete TileView comment from View.Hierarchy.cs Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add resizable splitter example to View.Arrangement docs Co-authored-by: tig <585482+tig@users.noreply.github.com> * Refactored `TreeView` and `TableView` containersto use View.Arrangment. Removed unused `_btnToggleSplitterCollapse` and related logic due to the new splitter design. Simplified collection initialization and feedback/state handling. Improved code readability by replacing magic strings and redundant null checks. Refactor FileDialog for null safety and UI improvements Enabled nullable reference types to improve null safety across the codebase. Refactored constants to follow uppercase naming conventions. Introduced nullable annotations for fields and method parameters. Updated test cases to reflect the removal of deprecated features. Skipped tests related to the removed splitter button. Made miscellaneous improvements, including adding comments and suppressing warnings. * Add "_Find:" label to FileDialog search field Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fixes Parallel unit test intermittent failure case. Removed the initialization of the `Navigation` object in the `ResetState` method of the `Application` class, indicating a potential shift in its lifecycle management. Enhanced comments to clarify the role of `Shutdown` as a counterpart to `Init`, emphasizing resource cleanup and defensive coding for multithreaded scenarios. Referenced Issue #537 for additional context. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig <tig@users.noreply.github.com>
This commit is contained in:
@@ -59,12 +59,12 @@ public class Notepad : Scenario
|
||||
_tabView.Style.ShowBorder = true;
|
||||
_tabView.ApplyStyleChanges ();
|
||||
|
||||
// Start with only a single view but support splitting to show side by side
|
||||
var split = new TileView (1) { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) };
|
||||
split.Tiles.ElementAt (0).ContentView.Add (_tabView);
|
||||
split.LineStyle = LineStyle.None;
|
||||
_tabView.X = 0;
|
||||
_tabView.Y = 1;
|
||||
_tabView.Width = Dim.Fill ();
|
||||
_tabView.Height = Dim.Fill (1);
|
||||
|
||||
top.Add (split);
|
||||
top.Add (_tabView);
|
||||
LenShortcut = new (Key.Empty, "Len: ", null);
|
||||
|
||||
var statusBar = new StatusBar (new [] {
|
||||
@@ -199,38 +199,10 @@ public class Notepad : Scenario
|
||||
tab.View.Dispose ();
|
||||
_focusedTabView = tv;
|
||||
|
||||
// If last tab is closed, open a new one
|
||||
if (tv.Tabs.Count == 0)
|
||||
{
|
||||
var split = (TileView)tv.SuperView.SuperView;
|
||||
|
||||
// if it is the last TabView on screen don't drop it or we will
|
||||
// be unable to open new docs!
|
||||
if (split.IsRootTileView () && split.Tiles.Count == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int tileIndex = split.IndexOf (tv);
|
||||
split.RemoveTile (tileIndex);
|
||||
|
||||
if (split.Tiles.Count == 0)
|
||||
{
|
||||
TileView parent = split.GetParentTileView ();
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = parent.IndexOf (split);
|
||||
|
||||
if (idx == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parent.RemoveTile (idx);
|
||||
}
|
||||
New ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,37 +258,6 @@ public class Notepad : Scenario
|
||||
|
||||
private void Quit () { Application.RequestStop (); }
|
||||
|
||||
private void Split (int offset, Orientation orientation, TabView sender, OpenedFile tab)
|
||||
{
|
||||
var split = (TileView)sender.SuperView.SuperView;
|
||||
int tileIndex = split.IndexOf (sender);
|
||||
|
||||
if (tileIndex == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (orientation != split.Orientation)
|
||||
{
|
||||
split.TrySplitTile (tileIndex, 1, out split);
|
||||
split.Orientation = orientation;
|
||||
tileIndex = 0;
|
||||
}
|
||||
|
||||
Tile newTile = split.InsertTile (tileIndex + offset);
|
||||
TabView newTabView = CreateNewTabView ();
|
||||
tab.CloneTo (newTabView);
|
||||
newTile.ContentView.Add (newTabView);
|
||||
|
||||
newTabView.FocusDeepest (NavigationDirection.Forward, null);
|
||||
newTabView.AdvanceFocus (NavigationDirection.Forward, null);
|
||||
}
|
||||
|
||||
private void SplitDown (TabView sender, OpenedFile tab) { Split (1, Orientation.Horizontal, sender, tab); }
|
||||
private void SplitLeft (TabView sender, OpenedFile tab) { Split (0, Orientation.Vertical, sender, tab); }
|
||||
private void SplitRight (TabView sender, OpenedFile tab) { Split (1, Orientation.Vertical, sender, tab); }
|
||||
private void SplitUp (TabView sender, OpenedFile tab) { Split (0, Orientation.Horizontal, sender, tab); }
|
||||
|
||||
private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
|
||||
{
|
||||
LenShortcut.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
|
||||
@@ -346,12 +287,7 @@ public class Notepad : Scenario
|
||||
items =
|
||||
[
|
||||
new MenuItemv2 ("Save", "", () => Save (_focusedTabView, e.Tab)),
|
||||
new MenuItemv2 ("Close", "", () => Close (tv, e.Tab)),
|
||||
new Line (),
|
||||
new MenuItemv2 ("Split Up", "", () => SplitUp (tv, t)),
|
||||
new MenuItemv2 ("Split Down", "", () => SplitDown (tv, t)),
|
||||
new MenuItemv2 ("Split Right", "", () => SplitRight (tv, t)),
|
||||
new MenuItemv2 ("Split Left", "", () => SplitLeft (tv, t))
|
||||
new MenuItemv2 ("Close", "", () => Close (tv, e.Tab))
|
||||
];
|
||||
|
||||
PopoverMenu? contextMenu = new (items);
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
[ScenarioMetadata ("Tile View Nesting", "Demonstrates recursive nesting of TileViews")]
|
||||
[ScenarioCategory ("Controls")]
|
||||
public class TileViewNesting : Scenario
|
||||
{
|
||||
private CheckBox _cbBorder;
|
||||
private CheckBox _cbHorizontal;
|
||||
private CheckBox _cbTitles;
|
||||
private CheckBox _cbUseLabels;
|
||||
private TextField _textField;
|
||||
private int _viewsCreated;
|
||||
private int _viewsToCreate;
|
||||
private View _workArea;
|
||||
|
||||
/// <summary>Setup the scenario.</summary>
|
||||
public override void Main ()
|
||||
{
|
||||
Application.Init ();
|
||||
// Scenario Windows.
|
||||
var win = new Window
|
||||
{
|
||||
Title = GetName (),
|
||||
Y = 1
|
||||
};
|
||||
|
||||
var lblViews = new Label { Text = "Number Of Views:" };
|
||||
_textField = new() { X = Pos.Right (lblViews), Width = 10, Text = "2" };
|
||||
|
||||
_textField.TextChanged += (s, e) => SetupTileView ();
|
||||
|
||||
_cbHorizontal = new() { X = Pos.Right (_textField) + 1, Text = "Horizontal" };
|
||||
_cbHorizontal.CheckedStateChanged += (s, e) => SetupTileView ();
|
||||
|
||||
_cbBorder = new() { X = Pos.Right (_cbHorizontal) + 1, Text = "Border" };
|
||||
_cbBorder.CheckedStateChanged += (s, e) => SetupTileView ();
|
||||
|
||||
_cbTitles = new() { X = Pos.Right (_cbBorder) + 1, Text = "Titles" };
|
||||
_cbTitles.CheckedStateChanged += (s, e) => SetupTileView ();
|
||||
|
||||
_cbUseLabels = new() { X = Pos.Right (_cbTitles) + 1, Text = "Use Labels" };
|
||||
_cbUseLabels.CheckedStateChanged += (s, e) => SetupTileView ();
|
||||
|
||||
_workArea = new() { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill () };
|
||||
|
||||
var menu = new MenuBar
|
||||
{
|
||||
Menus =
|
||||
[
|
||||
new ("_File", new MenuItem [] { new ("_Quit", "", () => Quit ()) })
|
||||
]
|
||||
};
|
||||
|
||||
win.Add (lblViews);
|
||||
win.Add (_textField);
|
||||
win.Add (_cbHorizontal);
|
||||
win.Add (_cbBorder);
|
||||
win.Add (_cbTitles);
|
||||
win.Add (_cbUseLabels);
|
||||
win.Add (_workArea);
|
||||
|
||||
SetupTileView ();
|
||||
|
||||
var top = new Toplevel ();
|
||||
top.Add (menu);
|
||||
top.Add (win);
|
||||
|
||||
Application.Run (top);
|
||||
top.Dispose ();
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
private void AddMoreViews (TileView to)
|
||||
{
|
||||
if (_viewsCreated == _viewsToCreate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(to.Tiles.ElementAt (0).ContentView is TileView))
|
||||
{
|
||||
Split (to, true);
|
||||
}
|
||||
|
||||
if (!(to.Tiles.ElementAt (1).ContentView is TileView))
|
||||
{
|
||||
Split (to, false);
|
||||
}
|
||||
|
||||
if (to.Tiles.ElementAt (0).ContentView is TileView && to.Tiles.ElementAt (1).ContentView is TileView)
|
||||
{
|
||||
AddMoreViews ((TileView)to.Tiles.ElementAt (0).ContentView);
|
||||
AddMoreViews ((TileView)to.Tiles.ElementAt (1).ContentView);
|
||||
}
|
||||
}
|
||||
|
||||
private View CreateContentControl (int number) { return _cbUseLabels.CheckedState == CheckState.Checked ? CreateLabelView (number) : CreateTextView (number); }
|
||||
|
||||
private View CreateLabelView (int number)
|
||||
{
|
||||
return new Label
|
||||
{
|
||||
Width = Dim.Fill (),
|
||||
Height = 1,
|
||||
|
||||
Text = number.ToString ().Repeat (1000),
|
||||
CanFocus = true
|
||||
};
|
||||
}
|
||||
|
||||
private View CreateTextView (int number)
|
||||
{
|
||||
return new TextView
|
||||
{
|
||||
Width = Dim.Fill (), Height = Dim.Fill (), Text = number.ToString ().Repeat (1000), AllowsTab = false
|
||||
|
||||
//WordWrap = true, // TODO: This is very slow (like 10s to render with 45 views)
|
||||
};
|
||||
}
|
||||
|
||||
private TileView CreateTileView (int titleNumber, Orientation orientation)
|
||||
{
|
||||
var toReturn = new TileView
|
||||
{
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
|
||||
// flip the orientation
|
||||
Orientation = orientation
|
||||
};
|
||||
|
||||
toReturn.Tiles.ElementAt (0).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {titleNumber}" : string.Empty;
|
||||
toReturn.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {titleNumber + 1}" : string.Empty;
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private int GetNumberOfViews ()
|
||||
{
|
||||
if (int.TryParse (_textField.Text, out int views) && views >= 0)
|
||||
{
|
||||
return views;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void Quit () { Application.RequestStop (); }
|
||||
|
||||
private void SetupTileView ()
|
||||
{
|
||||
int numberOfViews = GetNumberOfViews ();
|
||||
|
||||
CheckState titles = _cbTitles.CheckedState;
|
||||
CheckState border = _cbBorder.CheckedState;
|
||||
CheckState startHorizontal = _cbHorizontal.CheckedState;
|
||||
|
||||
foreach (View sub in _workArea.SubViews)
|
||||
{
|
||||
sub.Dispose ();
|
||||
}
|
||||
|
||||
_workArea.RemoveAll ();
|
||||
|
||||
if (numberOfViews <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TileView root = CreateTileView (1, startHorizontal == CheckState.Checked ? Orientation.Horizontal : Orientation.Vertical);
|
||||
|
||||
root.Tiles.ElementAt (0).ContentView.Add (CreateContentControl (1));
|
||||
root.Tiles.ElementAt (0).Title = _cbTitles.CheckedState == CheckState.Checked ? "View 1" : string.Empty;
|
||||
root.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (2));
|
||||
root.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? "View 2" : string.Empty;
|
||||
|
||||
root.LineStyle = border == CheckState.Checked? LineStyle.Rounded : LineStyle.None;
|
||||
|
||||
_workArea.Add (root);
|
||||
|
||||
if (numberOfViews == 1)
|
||||
{
|
||||
root.Tiles.ElementAt (1).ContentView.Visible = false;
|
||||
}
|
||||
|
||||
if (numberOfViews > 2)
|
||||
{
|
||||
_viewsCreated = 2;
|
||||
_viewsToCreate = numberOfViews;
|
||||
AddMoreViews (root);
|
||||
}
|
||||
}
|
||||
|
||||
private void Split (TileView to, bool left)
|
||||
{
|
||||
if (_viewsCreated == _viewsToCreate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TileView newView;
|
||||
|
||||
if (left)
|
||||
{
|
||||
to.TrySplitTile (0, 2, out newView);
|
||||
}
|
||||
else
|
||||
{
|
||||
to.TrySplitTile (1, 2, out newView);
|
||||
}
|
||||
|
||||
_viewsCreated++;
|
||||
|
||||
// During splitting the old Title will have been migrated to View1 so we only need
|
||||
// to set the Title on View2 (the one that gets our new TextView)
|
||||
newView.Tiles.ElementAt (1).Title = _cbTitles.CheckedState == CheckState.Checked ? $"View {_viewsCreated}" : string.Empty;
|
||||
|
||||
// Flip orientation
|
||||
newView.Orientation = to.Orientation == Orientation.Vertical
|
||||
? Orientation.Horizontal
|
||||
: Orientation.Vertical;
|
||||
|
||||
newView.Tiles.ElementAt (1).ContentView.Add (CreateContentControl (_viewsCreated));
|
||||
}
|
||||
}
|
||||
@@ -179,8 +179,6 @@ public static partial class Application
|
||||
// starts running and after Shutdown returns.
|
||||
internal static void ResetState (bool ignoreDisposed = false)
|
||||
{
|
||||
Navigation = new ();
|
||||
|
||||
// Shutdown is the bookend for Init. As such it needs to clean up all resources
|
||||
// Init created. Apps that do any threading will need to code defensively for this.
|
||||
// e.g. see Issue #537
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
<value>Enter Path</value>
|
||||
</data>
|
||||
<data name="fdSearchCaption" xml:space="preserve">
|
||||
<value>Enter Search</value>
|
||||
<value>_Find:</value>
|
||||
</data>
|
||||
<data name="fdSize" xml:space="preserve">
|
||||
<value>Size</value>
|
||||
|
||||
@@ -11,5 +11,41 @@ public partial class View
|
||||
/// See the View Arrangement Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.Gui/docs/arrangement.html"/>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para>
|
||||
/// This example demonstrates how to create a resizable splitter between two views using <see cref="ViewArrangement.LeftResizable"/>:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// // Create left pane that fills remaining space
|
||||
/// View leftPane = new ()
|
||||
/// {
|
||||
/// X = 0,
|
||||
/// Y = 0,
|
||||
/// Width = Dim.Fill (Dim.Func (_ => rightPane.Frame.Width)),
|
||||
/// Height = Dim.Fill (),
|
||||
/// CanFocus = true
|
||||
/// };
|
||||
///
|
||||
/// // Create right pane with resizable left border (acts as splitter)
|
||||
/// View rightPane = new ()
|
||||
/// {
|
||||
/// X = Pos.Right (leftPane) - 1,
|
||||
/// Y = 0,
|
||||
/// Width = Dim.Fill (),
|
||||
/// Height = Dim.Fill (),
|
||||
/// Arrangement = ViewArrangement.LeftResizable,
|
||||
/// BorderStyle = LineStyle.Single,
|
||||
/// SuperViewRendersLineCanvas = true,
|
||||
/// CanFocus = true
|
||||
/// };
|
||||
/// rightPane.Border!.Thickness = new (1, 0, 0, 0); // Only left border
|
||||
///
|
||||
/// container.Add (leftPane, rightPane);
|
||||
/// </code>
|
||||
/// <para>
|
||||
/// The right pane's left border acts as a draggable splitter. The left pane's width automatically adjusts
|
||||
/// to fill the remaining space using <c>Dim.Fill</c> with a function that subtracts the right pane's width.
|
||||
/// </para>
|
||||
/// </example>
|
||||
public ViewArrangement Arrangement { get; set; }
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
|
||||
Logging.Warning ($"{view} has already been Added to {this}.");
|
||||
}
|
||||
|
||||
// TileView likes to add views that were previously added and have HasFocus = true. No bueno.
|
||||
// Ensure views don't have focus when being added
|
||||
view.HasFocus = false;
|
||||
|
||||
// TODO: Make this thread safe
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#nullable enable
|
||||
using System.IO.Abstractions;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>
|
||||
@@ -10,10 +9,11 @@ namespace Terminal.Gui.Views;
|
||||
/// </summary>
|
||||
public class FileDialog : Dialog, IDesignable
|
||||
{
|
||||
private const int alignmentGroupInput = 32;
|
||||
private const int alignmentGroupComplete = 55;
|
||||
private const int ALIGNMENT_GROUP_INPUT = 32;
|
||||
private const int ALIGNMENT_GROUP_COMPLETE = 55;
|
||||
|
||||
/// <summary>Gets the Path separators for the operating system</summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal static char [] Separators =
|
||||
[
|
||||
System.IO.Path.AltDirectorySeparatorChar,
|
||||
@@ -34,12 +34,11 @@ public class FileDialog : Dialog, IDesignable
|
||||
private readonly Button _btnCancel;
|
||||
private readonly Button _btnForward;
|
||||
private readonly Button _btnOk;
|
||||
private readonly Button _btnToggleSplitterCollapse;
|
||||
private readonly Button _btnUp;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IFileSystem? _fileSystem;
|
||||
private readonly FileDialogHistory _history;
|
||||
private readonly SpinnerView _spinnerView;
|
||||
private readonly TileView _splitContainer;
|
||||
private readonly View _tableViewContainer;
|
||||
private readonly TableView _tableView;
|
||||
private readonly TextField _tbFind;
|
||||
private readonly TextField _tbPath;
|
||||
@@ -61,7 +60,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="FileDialog"/> class with a custom <see cref="IFileSystem"/>.</summary>
|
||||
/// <remarks>This overload is mainly useful for testing.</remarks>
|
||||
internal FileDialog (IFileSystem fileSystem)
|
||||
internal FileDialog (IFileSystem? fileSystem)
|
||||
{
|
||||
Height = Dim.Percent (80);
|
||||
Width = Dim.Percent (80);
|
||||
@@ -74,7 +73,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
|
||||
_btnOk = new ()
|
||||
{
|
||||
X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete),
|
||||
X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, ALIGNMENT_GROUP_COMPLETE),
|
||||
Y = Pos.AnchorEnd (),
|
||||
IsDefault = true, Text = Style.OkButtonText
|
||||
};
|
||||
@@ -91,7 +90,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
|
||||
_btnCancel = new ()
|
||||
{
|
||||
X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete),
|
||||
X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, ALIGNMENT_GROUP_COMPLETE),
|
||||
Y = Pos.AnchorEnd (),
|
||||
Text = Strings.btnCancel
|
||||
};
|
||||
@@ -149,21 +148,29 @@ public class FileDialog : Dialog, IDesignable
|
||||
_tbPath.Autocomplete = new AppendAutocomplete (_tbPath);
|
||||
_tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
|
||||
|
||||
_splitContainer = new ()
|
||||
// Create tree view container (left pane)
|
||||
View treeViewContainer = new ()
|
||||
{
|
||||
X = -1,
|
||||
Y = Pos.Bottom (_btnBack),
|
||||
Width = Dim.Fill (Dim.Func (_ => IsInitialized ? _tableViewContainer!.Frame.Width - 1 : 1)),
|
||||
Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1)),
|
||||
CanFocus = true,
|
||||
};
|
||||
|
||||
// Create table view container (right pane)
|
||||
_tableViewContainer = new ()
|
||||
{
|
||||
X = 0,
|
||||
Y = Pos.Bottom (_btnBack),
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1))
|
||||
Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1)),
|
||||
Arrangement = ViewArrangement.LeftResizable,
|
||||
BorderStyle = LineStyle.Dashed,
|
||||
SuperViewRendersLineCanvas = true,
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
Initialized += (s, e) =>
|
||||
{
|
||||
_splitContainer.SetSplitterPos (0, 30);
|
||||
_splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
|
||||
};
|
||||
|
||||
// this.splitContainer.Border.BorderStyle = BorderStyle.None;
|
||||
_tableViewContainer.Border!.Thickness = new (1, 0, 0, 0);
|
||||
|
||||
_tableView = new ()
|
||||
{
|
||||
@@ -202,38 +209,20 @@ public class FileDialog : Dialog, IDesignable
|
||||
|
||||
_treeView.SelectionChanged += TreeView_SelectionChanged;
|
||||
|
||||
_splitContainer.Tiles.ElementAt (0).ContentView.Add (_treeView);
|
||||
_splitContainer.Tiles.ElementAt (1).ContentView.Add (_tableView);
|
||||
|
||||
_btnToggleSplitterCollapse = new ()
|
||||
{
|
||||
X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
|
||||
Y = Pos.AnchorEnd (), Text = GetToggleSplitterText (false)
|
||||
};
|
||||
|
||||
_btnToggleSplitterCollapse.Accepting += (s, e) =>
|
||||
{
|
||||
// Required otherwise the Save button clicks itself
|
||||
e.Handled = true;
|
||||
Tile tile = _splitContainer.Tiles.ElementAt (0);
|
||||
|
||||
bool newState = !tile.ContentView.Visible;
|
||||
tile.ContentView.Visible = newState;
|
||||
_btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
|
||||
SetNeedsLayout ();
|
||||
};
|
||||
treeViewContainer.Add (_treeView);
|
||||
_tableViewContainer.Add (_tableView);
|
||||
|
||||
_tbFind = new ()
|
||||
{
|
||||
X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
|
||||
X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, ALIGNMENT_GROUP_INPUT),
|
||||
CaptionColor = new (Color.Black),
|
||||
Width = 30,
|
||||
Y = Pos.Top (_btnToggleSplitterCollapse),
|
||||
Y = Pos.Top (_btnOk),
|
||||
HotKey = Key.F.WithAlt
|
||||
};
|
||||
|
||||
_spinnerView = new ()
|
||||
{ X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput), Y = Pos.AnchorEnd (1), Visible = false };
|
||||
{ X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, ALIGNMENT_GROUP_INPUT), Y = Pos.AnchorEnd (1), Visible = false };
|
||||
|
||||
_tbFind.TextChanged += (s, o) => RestartSearch ();
|
||||
|
||||
@@ -278,17 +267,17 @@ public class FileDialog : Dialog, IDesignable
|
||||
|
||||
UpdateNavigationVisibility ();
|
||||
|
||||
Add (_tbPath);
|
||||
Add (_btnUp);
|
||||
Add (_btnBack);
|
||||
Add (_btnForward);
|
||||
Add (_splitContainer);
|
||||
Add (_btnToggleSplitterCollapse);
|
||||
Add (_tbFind);
|
||||
Add (_spinnerView);
|
||||
base.Add (_tbPath);
|
||||
base.Add (_btnUp);
|
||||
base.Add (_btnBack);
|
||||
base.Add (_btnForward);
|
||||
base.Add (treeViewContainer);
|
||||
base.Add (_tableViewContainer);
|
||||
base.Add (_tbFind);
|
||||
base.Add (_spinnerView);
|
||||
|
||||
Add (_btnOk);
|
||||
Add (_btnCancel);
|
||||
base.Add (_btnOk);
|
||||
base.Add (_btnCancel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -418,7 +407,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
Move (0, Viewport.Height / 2);
|
||||
|
||||
SetAttribute (new (Color.Red, GetAttributeForRole (VisualRole.Normal).Background));
|
||||
Driver.AddStr (new (' ', feedbackPadLeft));
|
||||
Driver!.AddStr (new (' ', feedbackPadLeft));
|
||||
Driver.AddStr (_feedback);
|
||||
Driver.AddStr (new (' ', feedbackPadRight));
|
||||
}
|
||||
@@ -444,7 +433,6 @@ public class FileDialog : Dialog, IDesignable
|
||||
_btnUp.Text = GetUpButtonText ();
|
||||
_btnBack.Text = GetBackButtonText ();
|
||||
_btnForward.Text = GetForwardButtonText ();
|
||||
_btnToggleSplitterCollapse.Text = GetToggleSplitterText (false);
|
||||
|
||||
_tbPath.Caption = Style.PathCaption;
|
||||
_tbFind.Caption = Style.SearchCaption;
|
||||
@@ -466,7 +454,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
CurrentFilter = AllowedTypes [0];
|
||||
|
||||
// Fiddle factor
|
||||
int width = AllowedTypes.Max (a => a.ToString ().Length) + 6;
|
||||
int width = AllowedTypes.Max (a => a.ToString ()!.Length) + 6;
|
||||
|
||||
_allowedTypeMenu = new (
|
||||
"<placeholder>",
|
||||
@@ -497,7 +485,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
_allowedTypeMenuBar.DrawingContent += (s, e) =>
|
||||
{
|
||||
_allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0);
|
||||
Driver.AddRune (Glyphs.DownArrow);
|
||||
Driver!.AddRune (Glyphs.DownArrow);
|
||||
};
|
||||
|
||||
Add (_allowedTypeMenuBar);
|
||||
@@ -506,7 +494,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
// if no path has been provided
|
||||
if (_tbPath.Text.Length <= 0)
|
||||
{
|
||||
Path = _fileSystem.Directory.GetCurrentDirectory ();
|
||||
Path = _fileSystem!.Directory.GetCurrentDirectory ();
|
||||
}
|
||||
|
||||
// to streamline user experience and allow direct typing of paths
|
||||
@@ -692,12 +680,9 @@ public class FileDialog : Dialog, IDesignable
|
||||
}
|
||||
|
||||
if (!IsCompatibleWithOpenMode (_tbPath.Text, out string reason))
|
||||
{
|
||||
if (reason is { })
|
||||
{
|
||||
_feedback = reason;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -727,19 +712,16 @@ public class FileDialog : Dialog, IDesignable
|
||||
_allowedTypeMenuItems [i].Checked = i == idx;
|
||||
}
|
||||
|
||||
_allowedTypeMenu.Title = allow.ToString ();
|
||||
_allowedTypeMenu.Title = allow.ToString ()!;
|
||||
|
||||
CurrentFilter = allow;
|
||||
|
||||
_tbPath.ClearAllSelection ();
|
||||
_tbPath.Autocomplete.ClearSuggestions ();
|
||||
|
||||
if (State is { })
|
||||
{
|
||||
State.RefreshChildren ();
|
||||
WriteStateToTableView ();
|
||||
}
|
||||
}
|
||||
|
||||
private string AspectGetter (object o)
|
||||
{
|
||||
@@ -780,7 +762,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CellActivate (object sender, CellActivatedEventArgs obj)
|
||||
private void CellActivate (object? sender, CellActivatedEventArgs obj)
|
||||
{
|
||||
if (TryAcceptMulti ())
|
||||
{
|
||||
@@ -835,7 +817,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
{
|
||||
IFileSystemInfo [] toDelete = GetFocusedFiles ();
|
||||
|
||||
if (toDelete is { } && FileOperationsHandler.Delete (toDelete))
|
||||
if (FileOperationsHandler.Delete (toDelete))
|
||||
{
|
||||
RefreshState ();
|
||||
}
|
||||
@@ -911,13 +893,6 @@ public class FileDialog : Dialog, IDesignable
|
||||
return string.Format (Strings.fdCtxSortAsc, _tableView.Table.ColumnNames [clickedCol]);
|
||||
}
|
||||
|
||||
private string GetToggleSplitterText (bool isExpanded)
|
||||
{
|
||||
return isExpanded
|
||||
? new ((char)Glyphs.LeftArrow.Value, 2)
|
||||
: new string ((char)Glyphs.RightArrow.Value, 2);
|
||||
}
|
||||
|
||||
private string GetUpButtonText () { return Style.UseUnicodeCharacters ? "◭" : "▲"; }
|
||||
|
||||
private void HideColumn (int clickedCol)
|
||||
@@ -1020,21 +995,17 @@ public class FileDialog : Dialog, IDesignable
|
||||
{
|
||||
foreach (Point p in _tableView.GetAllSelectedCells ())
|
||||
{
|
||||
FileSystemInfoStats add = State?.Children [p.Y];
|
||||
FileSystemInfoStats add = State?.Children [p.Y]!;
|
||||
|
||||
if (add is { })
|
||||
{
|
||||
toReturn.Add (add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private void New ()
|
||||
{
|
||||
if (State is { })
|
||||
{
|
||||
IFileSystemInfo created = FileOperationsHandler.New (_fileSystem, State.Directory);
|
||||
|
||||
@@ -1046,7 +1017,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTableViewMouseClick (object sender, MouseEventArgs e)
|
||||
private void OnTableViewMouseClick (object? sender, MouseEventArgs e)
|
||||
{
|
||||
Point? clickedCell = _tableView.ScreenToCell (e.Position.X, e.Position.Y, out int? clickedCol);
|
||||
|
||||
@@ -1187,26 +1158,6 @@ public class FileDialog : Dialog, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
// /// <inheritdoc/>
|
||||
// public override bool OnHotKey (KeyEventArgs keyEvent)
|
||||
// {
|
||||
//#if BROKE_IN_2927
|
||||
// // BUGBUG: Ctrl-F is forward in a TextField.
|
||||
// if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
|
||||
// return true;
|
||||
// }
|
||||
//#endif
|
||||
|
||||
// ClearFeedback ();
|
||||
|
||||
// if (allowedTypeMenuBar is { } &&
|
||||
// keyEvent.ConsoleDriverKey == Key.Tab &&
|
||||
// allowedTypeMenuBar.IsMenuOpen) {
|
||||
// allowedTypeMenuBar.CloseMenu (false, false, false);
|
||||
// }
|
||||
|
||||
// return base.OnHotKey (keyEvent);
|
||||
// }
|
||||
private void RestartSearch ()
|
||||
{
|
||||
if (_disposed || State?.Directory is null)
|
||||
@@ -1232,10 +1183,10 @@ public class FileDialog : Dialog, IDesignable
|
||||
return;
|
||||
}
|
||||
|
||||
PushState (new SearchState (State?.Directory, this, _tbFind.Text), true);
|
||||
PushState (new SearchState (State?.Directory!, this, _tbFind.Text), true);
|
||||
}
|
||||
|
||||
private FileSystemInfoStats RowToStats (int rowIndex) { return State?.Children [rowIndex]; }
|
||||
private FileSystemInfoStats RowToStats (int rowIndex) { return State?.Children [rowIndex]!; }
|
||||
|
||||
private void ShowCellContextMenu (Point? clickedCell, MouseEventArgs e)
|
||||
{
|
||||
@@ -1304,10 +1255,10 @@ public class FileDialog : Dialog, IDesignable
|
||||
// really not what most users would expect
|
||||
if (Regex.IsMatch (path, "^\\w:$"))
|
||||
{
|
||||
return _fileSystem.DirectoryInfo.New (path + _fileSystem.Path.DirectorySeparatorChar);
|
||||
return _fileSystem!.DirectoryInfo.New (path + _fileSystem.Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
return _fileSystem.DirectoryInfo.New (path);
|
||||
return _fileSystem!.DirectoryInfo.New (path);
|
||||
}
|
||||
|
||||
private static string StripArrows (string columnName) { return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty); }
|
||||
@@ -1359,7 +1310,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
|
||||
private void TableView_SelectedCellChanged (object? sender, SelectedCellChangedEventArgs obj)
|
||||
{
|
||||
if (!_tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0)
|
||||
{
|
||||
@@ -1373,11 +1324,6 @@ public class FileDialog : Dialog, IDesignable
|
||||
|
||||
FileSystemInfoStats stats = RowToStats (obj.NewRow);
|
||||
|
||||
if (stats is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IFileSystemInfo dest;
|
||||
|
||||
if (stats.IsParent)
|
||||
@@ -1403,7 +1349,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
|
||||
private void TreeView_SelectionChanged (object? sender, SelectionChangedEventArgs<IFileSystemInfo> e)
|
||||
{
|
||||
SetPathToSelectedObject (e.NewValue);
|
||||
}
|
||||
@@ -1436,7 +1382,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
private bool TryAcceptMulti ()
|
||||
{
|
||||
IEnumerable<FileSystemInfoStats> multi = MultiRowToStats ();
|
||||
string reason = null;
|
||||
string? reason = null;
|
||||
|
||||
if (!multi.Any ())
|
||||
{
|
||||
@@ -1473,11 +1419,6 @@ public class FileDialog : Dialog, IDesignable
|
||||
|
||||
private void WriteStateToTableView ()
|
||||
{
|
||||
if (State is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tableView.Table =
|
||||
new FileDialogTableSource (this, State, Style, _currentSortColumn, _currentSortIsAsc);
|
||||
|
||||
@@ -1498,7 +1439,7 @@ public class FileDialog : Dialog, IDesignable
|
||||
public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
|
||||
{
|
||||
parent.SearchMatcher.Initialize (searchTerms);
|
||||
Children = new FileSystemInfoStats [0];
|
||||
Children = [];
|
||||
BeginSearch ();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>Provides data for <see cref="TileView"/> events.</summary>
|
||||
public class SplitterEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Creates a new instance of the <see cref="SplitterEventArgs"/> class.</summary>
|
||||
/// <param name="tileView"><see cref="TileView"/> in which splitter is being moved.</param>
|
||||
/// <param name="idx">Index of the splitter being moved in <see cref="TileView.SplitterDistances"/>.</param>
|
||||
/// <param name="splitterDistance">The new <see cref="Pos"/> of the splitter line.</param>
|
||||
public SplitterEventArgs (TileView tileView, int idx, Pos splitterDistance)
|
||||
{
|
||||
SplitterDistance = splitterDistance;
|
||||
TileView = tileView;
|
||||
Idx = idx;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the splitter that is being moved. This can be used to index
|
||||
/// <see cref="TileView.SplitterDistances"/>
|
||||
/// </summary>
|
||||
public int Idx { get; }
|
||||
|
||||
/// <summary>New position of the splitter line (see <see cref="TileView.SplitterDistances"/>).</summary>
|
||||
public Pos SplitterDistance { get; }
|
||||
|
||||
/// <summary>Container (sender) of the event.</summary>
|
||||
public TileView TileView { get; }
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>
|
||||
/// A single <see cref="ContentView"/> presented in a <see cref="TileView"/>. To create new instances use
|
||||
/// <see cref="TileView.RebuildForTileCount(int)"/> or <see cref="TileView.InsertTile(int)"/>.
|
||||
/// </summary>
|
||||
public class Tile
|
||||
{
|
||||
private string _title = string.Empty;
|
||||
|
||||
/// <summary>Creates a new instance of the <see cref="Tile"/> class.</summary>
|
||||
public Tile ()
|
||||
{
|
||||
ContentView = new View
|
||||
{
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
CanFocus = true
|
||||
};
|
||||
#if DEBUG_IDISPOSABLE
|
||||
ContentView.Data = "Tile.ContentView";
|
||||
#endif
|
||||
Title = string.Empty;
|
||||
MinSize = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ContentView"/> that is contained in this <see cref="TileView"/>. Add new child views to this
|
||||
/// member for multiple <see cref="ContentView"/>s within the <see cref="Tile"/>.
|
||||
/// </summary>
|
||||
public View? ContentView { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the minimum size you to allow when splitter resizing along parent
|
||||
/// <see cref="TileView.Orientation"/> direction.
|
||||
/// </summary>
|
||||
public int MinSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The text that should be displayed above the <see cref="ContentView"/>. This will appear over the splitter line
|
||||
/// or border (above the view client area).
|
||||
/// </summary>
|
||||
/// <remarks>Title are not rendered for root level tiles <see cref="LineStyle"/> is <see cref="LineStyle.None"/>.</remarks>
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set
|
||||
{
|
||||
if (!OnTitleChanging (_title, value))
|
||||
{
|
||||
string old = _title;
|
||||
_title = value;
|
||||
OnTitleChanged (old, _title);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_title = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called when the <see cref="Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.</summary>
|
||||
/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
|
||||
/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
|
||||
public virtual void OnTitleChanged (string oldTitle, string newTitle)
|
||||
{
|
||||
var args = new EventArgs<string> (in newTitle);
|
||||
TitleChanged?.Invoke (this, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before the <see cref="Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be
|
||||
/// cancelled.
|
||||
/// </summary>
|
||||
/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
|
||||
/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
|
||||
/// <returns><c>true</c> if an event handler cancelled the Title change.</returns>
|
||||
public virtual bool OnTitleChanging (string oldTitle, string newTitle)
|
||||
{
|
||||
var args = new CancelEventArgs<string> (ref oldTitle, ref newTitle);
|
||||
TitleChanging?.Invoke (this, args);
|
||||
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
/// <summary>Event fired after the <see cref="Title"/> has been changed.</summary>
|
||||
public event EventHandler? TitleChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the <see cref="Title"/> is changing.
|
||||
/// <see cref="CancelEventArgs.Cancel"/> can be set to <c>true</c> to cancel the change.
|
||||
/// </summary>
|
||||
public event EventHandler<CancelEventArgs<string>>? TitleChanging;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -159,7 +159,7 @@ public class FileDialogFluentTests
|
||||
"/";
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory (Skip = "New splitter design removes expand button.")]
|
||||
[ClassData (typeof (TestDrivers))]
|
||||
public void SaveFileDialog_PressingPopTree_ShouldNotChangeCancel (TestDriver d)
|
||||
{
|
||||
@@ -177,7 +177,7 @@ public class FileDialogFluentTests
|
||||
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory (Skip = "New splitter design removes expand button.")]
|
||||
[ClassData (typeof (TestDrivers))]
|
||||
public void SaveFileDialog_PopTree_AndNavigate (TestDriver d)
|
||||
{
|
||||
@@ -224,8 +224,8 @@ public class FileDialogFluentTests
|
||||
.WaitIteration ()
|
||||
.ScreenShot ("After typing filename 'hello'", _out)
|
||||
.AssertEndsWith ("hello", sd.Path)
|
||||
.LeftClick<Button> (b => b.Text == "►►")
|
||||
.ScreenShot ("After pop tree", _out)
|
||||
//.LeftClick<Button> (b => b.Text == "►►")
|
||||
//.ScreenShot ("After pop tree", _out)
|
||||
.Focus<TreeView<IFileSystemInfo>> (_ => true)
|
||||
.Right ()
|
||||
.ScreenShot ("After expand tree", _out)
|
||||
@@ -267,8 +267,8 @@ public class FileDialogFluentTests
|
||||
.WaitIteration ()
|
||||
.ScreenShot ("After typing filename 'hello'", _out)
|
||||
.AssertEndsWith ("hello", sd.Path)
|
||||
.LeftClick<Button> (b => b.Text == "►►")
|
||||
.ScreenShot ("After pop tree", _out)
|
||||
//.LeftClick<Button> (b => b.Text == "►►")
|
||||
//.ScreenShot ("After pop tree", _out)
|
||||
.Focus<TreeView<IFileSystemInfo>> (_ => true)
|
||||
.Right ()
|
||||
.ScreenShot ("After expand tree", _out)
|
||||
|
||||
@@ -107,7 +107,7 @@ public class FileDialogTests ()
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Assert.Same (tf, dlg.MostFocused);
|
||||
|
||||
Assert.Equal ("Enter Search", tf.Caption);
|
||||
Assert.Equal ("_Find:", tf.Caption);
|
||||
|
||||
// Dialog has not yet been confirmed with a choice
|
||||
Assert.True (dlg.Canceled);
|
||||
@@ -798,8 +798,28 @@ public class FileDialogTests ()
|
||||
|
||||
private TableView GetTableView (FileDialog dlg)
|
||||
{
|
||||
var tile = dlg.SubViews.OfType<TileView> ().Single ();
|
||||
return (TableView)tile.Tiles.ElementAt (1).ContentView.SubViews.ElementAt(0);
|
||||
// The table view is in the _tableViewContainer which is a direct subview of the dialog
|
||||
// We need to search through all subviews recursively
|
||||
TableView FindTableView (View view)
|
||||
{
|
||||
if (view is TableView tv)
|
||||
{
|
||||
return tv;
|
||||
}
|
||||
|
||||
foreach (View subview in view.SubViews)
|
||||
{
|
||||
TableView result = FindTableView (subview);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return FindTableView (dlg);
|
||||
}
|
||||
|
||||
private enum FileDialogPart
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,49 @@ A form of layout where SubViews of a View are visually arranged such that their
|
||||
|
||||
In most use-cases, subviews do not overlap with each other (the exception being when it's done intentionally to create some visual effect). As a result, the default layout for most TUI apps is "tiled", and by default @Terminal.Gui.ViewBase.View.Arrangement is set to @Terminal.Gui.ViewBase.ViewArrangement.Fixed.
|
||||
|
||||
### Creating a Resizable Splitter
|
||||
|
||||
A common pattern in tiled layouts is to create a resizable splitter between two views. This can be achieved using the @Terminal.Gui.ViewBase.ViewArrangement.LeftResizable, @Terminal.Gui.ViewBase.ViewArrangement.RightResizable, @Terminal.Gui.ViewBase.ViewArrangement.TopResizable, or @Terminal.Gui.ViewBase.ViewArrangement.BottomResizable flags.
|
||||
|
||||
Here's an example of creating a horizontal resizable splitter between two views:
|
||||
|
||||
```csharp
|
||||
// Create left pane that fills remaining space
|
||||
View leftPane = new ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (Dim.Func (_ => rightPane.Frame.Width)),
|
||||
Height = Dim.Fill (),
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
// Create right pane with resizable left border (acts as splitter)
|
||||
View rightPane = new ()
|
||||
{
|
||||
X = Pos.Right (leftPane) - 1,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
Arrangement = ViewArrangement.LeftResizable,
|
||||
BorderStyle = LineStyle.Single,
|
||||
SuperViewRendersLineCanvas = true,
|
||||
CanFocus = true
|
||||
};
|
||||
rightPane.Border!.Thickness = new (1, 0, 0, 0); // Only left border
|
||||
|
||||
container.Add (leftPane, rightPane);
|
||||
```
|
||||
|
||||
In this example:
|
||||
- The `rightPane` has `ViewArrangement.LeftResizable` which makes its left border draggable
|
||||
- The left border acts as a splitter that users can drag to resize both panes
|
||||
- The `leftPane` uses `Dim.Fill` with a function that subtracts the `rightPane`'s width to automatically fill the remaining space
|
||||
- The `rightPane` has `SuperViewRendersLineCanvas = true` to ensure the border is rendered properly
|
||||
- Only the left border is shown by setting `Border.Thickness` to `(1, 0, 0, 0)`
|
||||
|
||||
For a vertical splitter (top and bottom panes), use `ViewArrangement.TopResizable` or `ViewArrangement.BottomResizable` instead.
|
||||
|
||||
## Runnable
|
||||
|
||||
Today, Overlapped and Runnable are intrinsically linked. A runnable view is one where `Application.Run(Toplevel)` is called. Each *Runnable* view where (`Modal == false`) has it's own `RunState` and is, effectively, a self-contained "application".
|
||||
|
||||
@@ -628,33 +628,6 @@ TextView provides a fully featured multi-line text
|
||||
It supports word wrap and history for undo.
|
||||
```
|
||||
|
||||
## [TileView](~/api/Terminal.Gui.Views.TileView.yml)
|
||||
|
||||
A [View](~/api/Terminal.Gui.ViewBase.View.yml) consisting of a moveable bar that divides the display area into resizeable [TileView.Tiles](~/api/Terminal.Gui.Views.TileView.Tiles.yml).
|
||||
|
||||
```text
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
```
|
||||
|
||||
## [TimeField](~/api/Terminal.Gui.Views.TimeField.yml)
|
||||
|
||||
Provides time editing functionality with mouse support
|
||||
|
||||
Reference in New Issue
Block a user