Replace TileView with standard View layout in FileDialog and Notepad

Co-authored-by: tig <585482+tig@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-10-02 15:13:11 +00:00
parent 5d4397d170
commit 7fe0249c6b
9 changed files with 47 additions and 3948 deletions

View File

@@ -59,12 +59,13 @@ 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;
top.Add (split);
// Simple container for the tabview
top.Add (_tabView);
_tabView.X = 0;
_tabView.Y = 1;
_tabView.Width = Dim.Fill ();
_tabView.Height = Dim.Fill (1);
LenShortcut = new (Key.Empty, "Len: ", null);
var statusBar = new StatusBar (new [] {
@@ -198,40 +199,6 @@ public class Notepad : Scenario
tv.RemoveTab (tab);
tab.View.Dispose ();
_focusedTabView = tv;
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);
}
}
}
private TabView CreateNewTabView ()
@@ -286,36 +253,7 @@ 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); }
// Split functionality removed - TileView is deprecated in favor of View.Arrangement
private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
{
@@ -346,12 +284,8 @@ 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))
// Split menu items removed - TileView is deprecated in favor of View.Arrangement
];
PopoverMenu? contextMenu = new (items);

View File

@@ -1,228 +0,0 @@
using System.Linq;
namespace UICatalog.Scenarios;
[ScenarioMetadata ("Tile View Nesting", "Demonstrates recursive nesting of TileViews")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("LineView")]
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));
}
}

View File

@@ -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 that were previously added don't have HasFocus = true when re-added
view.HasFocus = false;
// TODO: Make this thread safe

View File

@@ -39,7 +39,8 @@ public class FileDialog : Dialog, IDesignable
private readonly IFileSystem _fileSystem;
private readonly FileDialogHistory _history;
private readonly SpinnerView _spinnerView;
private readonly TileView _splitContainer;
private readonly View _leftPanel;
private readonly View _rightPanel;
private readonly TableView _tableView;
private readonly TextField _tbFind;
private readonly TextField _tbPath;
@@ -149,19 +150,31 @@ public class FileDialog : Dialog, IDesignable
_tbPath.Autocomplete = new AppendAutocomplete (_tbPath);
_tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
_splitContainer = new ()
// Left panel for tree view
_leftPanel = new ()
{
Id = "leftPanel",
X = 0,
Y = Pos.Bottom (_btnBack),
Width = Dim.Fill (),
Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1))
Width = 30,
Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1)),
Visible = false,
CanFocus = false,
TabStop = TabBehavior.NoStop,
Arrangement = ViewArrangement.Resizable
};
Initialized += (s, e) =>
{
_splitContainer.SetSplitterPos (0, 30);
_splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
};
// Right panel for table view
_rightPanel = new ()
{
Id = "rightPanel",
X = Pos.Right (_leftPanel),
Y = Pos.Bottom (_btnBack),
Width = Dim.Fill (),
Height = Dim.Fill (Dim.Func (_ => IsInitialized ? _btnOk.Frame.Height : 1)),
CanFocus = false,
TabStop = TabBehavior.NoStop
};
// this.splitContainer.Border.BorderStyle = BorderStyle.None;
@@ -202,8 +215,8 @@ public class FileDialog : Dialog, IDesignable
_treeView.SelectionChanged += TreeView_SelectionChanged;
_splitContainer.Tiles.ElementAt (0).ContentView.Add (_treeView);
_splitContainer.Tiles.ElementAt (1).ContentView.Add (_tableView);
_leftPanel.Add (_treeView);
_rightPanel.Add (_tableView);
_btnToggleSplitterCollapse = new ()
{
@@ -215,10 +228,9 @@ public class FileDialog : Dialog, IDesignable
{
// 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;
bool newState = !_leftPanel.Visible;
_leftPanel.Visible = newState;
_btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
SetNeedsLayout ();
};
@@ -282,7 +294,8 @@ public class FileDialog : Dialog, IDesignable
Add (_btnUp);
Add (_btnBack);
Add (_btnForward);
Add (_splitContainer);
Add (_leftPanel);
Add (_rightPanel);
Add (_btnToggleSplitterCollapse);
Add (_tbFind);
Add (_spinnerView);

View File

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

View File

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

View File

@@ -798,8 +798,14 @@ 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 TableView is now directly in _rightPanel
var rightPanel = dlg.SubViews.FirstOrDefault(v => v.Id == "rightPanel");
if (rightPanel != null)
{
return (TableView)rightPanel.SubViews.First(s => s is TableView);
}
// Fallback - shouldn't reach here
throw new InvalidOperationException("Could not find rightPanel in FileDialog");
}
private enum FileDialogPart

File diff suppressed because it is too large Load Diff