diff --git a/Examples/UICatalog/Scenarios/Notepad.cs b/Examples/UICatalog/Scenarios/Notepad.cs index c827ca59f9..0d9dd5034e 100644 --- a/Examples/UICatalog/Scenarios/Notepad.cs +++ b/Examples/UICatalog/Scenarios/Notepad.cs @@ -59,12 +59,13 @@ public override void Main () _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 @@ private void Close (TabView tv, Tab tabToClose) 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 @@ private void Open (FileInfo fileInfo, string tabName) 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 @@ private void TabView_TabClicked (object sender, TabMouseEventArgs e) 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); diff --git a/Examples/UICatalog/Scenarios/TileViewNesting.cs b/Examples/UICatalog/Scenarios/TileViewNesting.cs deleted file mode 100644 index 2d262bf497..0000000000 --- a/Examples/UICatalog/Scenarios/TileViewNesting.cs +++ /dev/null @@ -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; - - /// Setup the scenario. - 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)); - } -} diff --git a/Terminal.Gui/ViewBase/View.Hierarchy.cs b/Terminal.Gui/ViewBase/View.Hierarchy.cs index d7510c2b07..26d56b4811 100644 --- a/Terminal.Gui/ViewBase/View.Hierarchy.cs +++ b/Terminal.Gui/ViewBase/View.Hierarchy.cs @@ -111,7 +111,7 @@ protected virtual void OnSuperViewChanged (SuperViewChangedEventArgs e) { } 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 diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index 344399b82b..1f0064d748 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -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,29 @@ internal FileDialog (IFileSystem fileSystem) _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 = true, + 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 = true + }; // this.splitContainer.Border.BorderStyle = BorderStyle.None; @@ -202,8 +213,8 @@ internal FileDialog (IFileSystem fileSystem) _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 +226,9 @@ internal FileDialog (IFileSystem fileSystem) { // 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 +292,8 @@ internal FileDialog (IFileSystem fileSystem) Add (_btnUp); Add (_btnBack); Add (_btnForward); - Add (_splitContainer); + Add (_leftPanel); + Add (_rightPanel); Add (_btnToggleSplitterCollapse); Add (_tbFind); Add (_spinnerView); diff --git a/Terminal.Gui/Views/SplitterEventArgs.cs b/Terminal.Gui/Views/SplitterEventArgs.cs deleted file mode 100644 index 7354209e98..0000000000 --- a/Terminal.Gui/Views/SplitterEventArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ - -namespace Terminal.Gui.Views; - -/// Provides data for events. -public class SplitterEventArgs : EventArgs -{ - /// Creates a new instance of the class. - /// in which splitter is being moved. - /// Index of the splitter being moved in . - /// The new of the splitter line. - public SplitterEventArgs (TileView tileView, int idx, Pos splitterDistance) - { - SplitterDistance = splitterDistance; - TileView = tileView; - Idx = idx; - } - - /// - /// Gets the index of the splitter that is being moved. This can be used to index - /// - /// - public int Idx { get; } - - /// New position of the splitter line (see ). - public Pos SplitterDistance { get; } - - /// Container (sender) of the event. - public TileView TileView { get; } -} diff --git a/Terminal.Gui/Views/Tile.cs b/Terminal.Gui/Views/Tile.cs deleted file mode 100644 index 45bcd259bb..0000000000 --- a/Terminal.Gui/Views/Tile.cs +++ /dev/null @@ -1,97 +0,0 @@ -#nullable enable -using System.ComponentModel; - -namespace Terminal.Gui.Views; - -/// -/// A single presented in a . To create new instances use -/// or . -/// -public class Tile -{ - private string _title = string.Empty; - - /// Creates a new instance of the class. - 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; - } - - /// - /// The that is contained in this . Add new child views to this - /// member for multiple s within the . - /// - public View? ContentView { get; internal set; } - - /// - /// Gets or Sets the minimum size you to allow when splitter resizing along parent - /// direction. - /// - public int MinSize { get; set; } - - /// - /// The text that should be displayed above the . This will appear over the splitter line - /// or border (above the view client area). - /// - /// Title are not rendered for root level tiles is . - public string Title - { - get => _title; - set - { - if (!OnTitleChanging (_title, value)) - { - string old = _title; - _title = value; - OnTitleChanged (old, _title); - - return; - } - - _title = value; - } - } - - /// Called when the has been changed. Invokes the event. - /// The that is/has been replaced. - /// The new to be replaced. - public virtual void OnTitleChanged (string oldTitle, string newTitle) - { - var args = new EventArgs (in newTitle); - TitleChanged?.Invoke (this, args); - } - - /// - /// Called before the changes. Invokes the event, which can be - /// cancelled. - /// - /// The that is/has been replaced. - /// The new to be replaced. - /// true if an event handler cancelled the Title change. - public virtual bool OnTitleChanging (string oldTitle, string newTitle) - { - var args = new CancelEventArgs (ref oldTitle, ref newTitle); - TitleChanging?.Invoke (this, args); - - return args.Cancel; - } - - /// Event fired after the has been changed. - public event EventHandler? TitleChanged; - - /// - /// Event fired when the is changing. - /// can be set to true to cancel the change. - /// - public event EventHandler>? TitleChanging; -} diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs deleted file mode 100644 index 0e929ac960..0000000000 --- a/Terminal.Gui/Views/TileView.cs +++ /dev/null @@ -1,1094 +0,0 @@ -#nullable enable - -namespace Terminal.Gui.Views; - -/// -/// A consisting of a moveable bar that divides the display area into resizeable -/// . -/// -public class TileView : View -{ - private Orientation _orientation = Orientation.Vertical; - private List? _splitterDistances; - private List? _splitterLines; - private List? _tiles; - private TileView? _parentTileView; - - /// Creates a new instance of the class with 2 tiles (i.e. left and right). - public TileView () : this (2) { } - - /// Creates a new instance of the class with number of tiles. - /// - public TileView (int tiles) - { - CanFocus = true; - RebuildForTileCount (tiles); - - SubViewLayout += (_, _) => - { - Rectangle viewport = Viewport; - - if (HasBorder ()) - { - viewport = new ( - viewport.X + 1, - viewport.Y + 1, - Math.Max (0, viewport.Width - 2), - Math.Max (0, viewport.Height - 2) - ); - } - - Setup (viewport); - }; - } - - /// The line style to use when drawing the splitter lines. - public LineStyle LineStyle { get; set; } = LineStyle.None; - - /// Orientation of the dividing line (Horizontal or Vertical). - public Orientation Orientation - { - get => _orientation; - set - { - if (_orientation == value) - { - return; - } - - _orientation = value; - - SetNeedsDraw (); - SetNeedsLayout (); - - } - } - - /// The splitter locations. Note that there will be N-1 splitters where N is the number of . - public IReadOnlyCollection SplitterDistances => _splitterDistances!.AsReadOnly (); - - /// The sub sections hosted by the view - public IReadOnlyCollection Tiles => _tiles!.AsReadOnly (); - - // TODO: Update to use Key instead of KeyCode - /// - /// The keyboard key that the user can press to toggle resizing of splitter lines. Mouse drag splitting is always - /// enabled. - /// - public KeyCode ToggleResizable { get; set; } = KeyCode.CtrlMask | KeyCode.F10; - - /// - /// Returns the immediate parent of this. Note that in case of deep nesting this might not - /// be the root . Returns null if this instance is not a nested child (created with - /// ) - /// - /// Use to determine if the returned value is the root. - /// - public TileView? GetParentTileView () { return _parentTileView; } - - /// - /// Returns the index of the first in which contains - /// . - /// - public int IndexOf (View toFind, bool recursive = false) - { - for (var i = 0; i < _tiles!.Count; i++) - { - View v = _tiles [i].ContentView!; - - if (v == toFind) - { - return i; - } - - if (v.SubViews.Contains (toFind)) - { - return i; - } - - if (recursive) - { - if (RecursiveContains (v.SubViews, toFind)) - { - return i; - } - } - } - - return -1; - } - - /// - /// Adds a new to the collection at . This will also add another splitter - /// line - /// - /// - public Tile? InsertTile (int idx) - { - Tile [] oldTiles = Tiles.ToArray (); - RebuildForTileCount (oldTiles.Length + 1); - - Tile? toReturn = null; - - for (var i = 0; i < _tiles?.Count; i++) - { - if (i != idx) - { - Tile oldTile = oldTiles [i > idx ? i - 1 : i]; - - // remove the new empty View - Remove (_tiles [i].ContentView); - _tiles [i].ContentView?.Dispose (); - _tiles [i].ContentView = null; - - // restore old Tile and View - _tiles [i] = oldTile; - _tiles [i].ContentView!.TabStop = TabStop; - Add (_tiles [i].ContentView); - } - else - { - toReturn = _tiles [i]; - } - } - - SetNeedsDraw (); - SetNeedsLayout (); - - return toReturn; - } - - /// - /// - /// if is nested within a parent e.g. via - /// the . if it is a root level . - /// - /// - /// - /// Note that manually adding one to another will not result in a parent/child relationship - /// and both will still be considered 'root' containers. Always use - /// if you want to subdivide a . - /// - /// - public bool IsRootTileView () { return _parentTileView == null; } - - /// Overridden so no Frames get drawn - /// - protected override bool OnDrawingAdornments () { return true; } - - /// - protected override bool OnRenderingLineCanvas () { return false; } - - /// - protected override void OnDrawComplete (DrawContext? context) - { - SetAttributeForRole (Enabled ? VisualRole.Normal : VisualRole.Disabled); - - var lc = new LineCanvas (); - - List allLines = GetAllLineViewsRecursively (this); - List allTitlesToRender = GetAllTitlesToRenderRecursively (this); - - if (IsRootTileView ()) - { - if (HasBorder ()) - { - lc.AddLine (Point.Empty, Viewport.Width, Orientation.Horizontal, LineStyle); - lc.AddLine (Point.Empty, Viewport.Height, Orientation.Vertical, LineStyle); - - lc.AddLine ( - new (Viewport.Width - 1, Viewport.Height - 1), - -Viewport.Width, - Orientation.Horizontal, - LineStyle - ); - - lc.AddLine ( - new (Viewport.Width - 1, Viewport.Height - 1), - -Viewport.Height, - Orientation.Vertical, - LineStyle - ); - } - - foreach (TileViewLineView line in allLines) - { - bool isRoot = _splitterLines!.Contains (line); - - Rectangle screen = line.ViewportToScreen (Rectangle.Empty); - Point origin = ScreenToFrame (screen.Location); - int length = line.Orientation == Orientation.Horizontal ? line.Frame.Width : line.Frame.Height; - - if (!isRoot) - { - if (line.Orientation == Orientation.Horizontal) - { - origin.X -= 1; - } - else - { - origin.Y -= 1; - } - - length += 2; - } - - lc.AddLine (origin, length, line.Orientation, LineStyle); - } - } - - SetAttributeForRole (Enabled ? VisualRole.Normal : VisualRole.Disabled); - - foreach (KeyValuePair p in lc.GetMap (Viewport)) - { - AddRune (p.Key.X, p.Key.Y, p.Value); - } - - // Redraw the lines so that focus/drag symbol renders - foreach (TileViewLineView line in allLines) - { - line.DrawSplitterSymbol (); - } - - // Draw Titles over Border - - foreach (TileTitleToRender titleToRender in allTitlesToRender) - { - Point renderAt = titleToRender.GetLocalCoordinateForTitle (this); - - if (renderAt.Y < 0) - { - // If we have no border then root level tiles - // have nowhere to render their titles. - continue; - } - - // TODO: Render with focus color if focused - - string title = titleToRender.GetTrimmedTitle (); - - for (var i = 0; i < title.Length; i++) - { - AddRune (renderAt.X + i, renderAt.Y, (Rune)title [i]); - } - } - - return; - } - - //// BUGBUG: Why is this not handled by a key binding??? - /// - protected override bool OnKeyDownNotHandled (Key key) - { - var focusMoved = false; - - if (key.KeyCode == ToggleResizable) - { - foreach (TileViewLineView l in _splitterLines!) - { - bool iniBefore = l.IsInitialized; - l.IsInitialized = false; - l.CanFocus = !l.CanFocus; - l.IsInitialized = iniBefore; - - if (l.CanFocus && !focusMoved) - { - l.SetFocus (); - focusMoved = true; - } - } - - return true; - } - - return false; - } - - /// - /// Scraps all and creates new tiles in orientation - /// - /// - /// - public void RebuildForTileCount (int count) - { - _tiles = new (); - _splitterDistances = new (); - - if (_splitterLines is { }) - { - foreach (TileViewLineView sl in _splitterLines) - { - sl.Dispose (); - } - } - - _splitterLines = new (); - - RemoveAll (); - - foreach (Tile tile in _tiles) - { - tile.ContentView?.Dispose (); - tile.ContentView = null; - } - - _tiles.Clear (); - _splitterDistances.Clear (); - - if (count == 0) - { - return; - } - - for (var i = 0; i < count; i++) - { - if (i > 0) - { - Pos currentPos = Pos.Percent (100 / count * i); - _splitterDistances.Add (currentPos); - var line = new TileViewLineView (this, i - 1); - Add (line); - _splitterLines.Add (line); - } - - var tile = new Tile (); - _tiles.Add (tile); - tile.ContentView!.Id = $"Tile.ContentView {i}"; - Add (tile.ContentView); - - // BUGBUG: This should not be needed: - tile.TitleChanged += (s, e) => SetNeedsLayout (); - } - - SetNeedsLayout (); - } - - /// - /// Removes a at the provided from the view. Returns the removed tile - /// or null if already empty. - /// - /// - /// - public Tile? RemoveTile (int idx) - { - Tile [] oldTiles = Tiles.ToArray (); - - if (idx < 0 || idx >= oldTiles.Length) - { - return null; - } - - Tile removed = Tiles.ElementAt (idx); - - RebuildForTileCount (oldTiles.Length - 1); - - for (var i = 0; i < _tiles?.Count; i++) - { - int oldIdx = i >= idx ? i + 1 : i; - Tile oldTile = oldTiles [oldIdx]; - - // remove the new empty View - Remove (_tiles [i].ContentView); - _tiles [i].ContentView?.Dispose (); - _tiles [i].ContentView = null; - - // restore old Tile and View - _tiles [i] = oldTile; - Add (_tiles [i].ContentView); - } - - return removed; - } - - /// - /// - /// Attempts to update the of line at to the new - /// . Returns false if the new position is not allowed because of - /// , location of other splitters etc. - /// - /// - /// Only absolute values (e.g. 10) and percent values (i.e. ) are supported for - /// this property. - /// - /// - public bool SetSplitterPos (int idx, Pos value) - { - if (!(value is PosAbsolute) && !(value is PosPercent)) - { - throw new ArgumentException ( - $"Only Percent and Absolute values are supported. Passed value was {value.GetType ().Name}" - ); - } - - int fullSpace = _orientation == Orientation.Vertical ? Viewport.Width : Viewport.Height; - - if (fullSpace != 0 && !IsValidNewSplitterPos (idx, value, fullSpace)) - { - return false; - } - - if (_splitterDistances is { }) - { - _splitterDistances [idx] = value; - } - - OnSplitterMoved (idx); - SetNeedsDraw (); - SetNeedsLayout (); - - return true; - } - - /// Invoked when any of the is changed. - public event SplitterEventHandler? SplitterMoved; - - /// - /// Converts of element from a regular to a new - /// nested the specified . Returns false if the element already - /// contains a nested view. - /// - /// - /// After successful splitting, the old contents will be moved to the - /// 's first tile. - /// - /// The element of that is to be subdivided. - /// The number of panels that the should be split into - /// The new nested . - /// - /// if a was converted to a new nested . - /// if it was already a nested - /// - public bool TrySplitTile (int idx, int numberOfPanels, out TileView result) - { - // when splitting a view into 2 sub views we will need to migrate - // the title too - Tile tile = _tiles! [idx]; - - string title = tile.Title; - View? toMove = tile.ContentView; - - if (toMove is TileView existing) - { - result = existing; - - return false; - } - - var newContainer = new TileView (numberOfPanels) - { - Width = Dim.Fill (), Height = Dim.Fill (), _parentTileView = this - }; - - // Take everything out of the View we are moving - View [] childViews = toMove!.SubViews.ToArray (); - toMove.RemoveAll (); - - // Remove the view itself and replace it with the new TileView - Remove (toMove); - toMove.Dispose (); - toMove = null; - - Add (newContainer); - - tile.ContentView = newContainer; - - View newTileView1 = newContainer!._tiles? [0].ContentView!; - - // Add the original content into the first view of the new container - foreach (View childView in childViews) - { - newTileView1!.Add (childView); - } - - // Move the title across too - newContainer._tiles! [0].Title = title; - tile.Title = string.Empty; - - result = newContainer; - - return true; - } - - /// - protected override void Dispose (bool disposing) - { - foreach (Tile tile in Tiles) - { - Remove (tile.ContentView); - tile.ContentView?.Dispose (); - } - - base.Dispose (disposing); - } - - /// Raises the event - protected virtual void OnSplitterMoved (int idx) { SplitterMoved?.Invoke (this, new (this, idx, _splitterDistances! [idx])); } - - private List GetAllLineViewsRecursively (View v) - { - List lines = new (); - - foreach (View sub in v.SubViews) - { - if (sub is TileViewLineView s) - { - if (s.Visible && s.Parent.GetRootTileView () == this) - { - lines.Add (s); - } - } - else - { - if (sub.Visible) - { - lines.AddRange (GetAllLineViewsRecursively (sub)); - } - } - } - - return lines; - } - - private List GetAllTitlesToRenderRecursively (TileView? v, int depth = 0) - { - List titles = new (); - - foreach (Tile sub in v!.Tiles) - { - // Don't render titles for invisible stuff! - if (!sub.ContentView!.Visible) - { - continue; - } - - if (sub.ContentView is TileView subTileView) - { - // Panels with sub split tiles in them can never - // have their Titles rendered. Instead we dive in - // and pull up their children as titles - titles.AddRange (GetAllTitlesToRenderRecursively (subTileView, depth + 1)); - } - else - { - if (sub.Title.Length > 0) - { - titles.Add (new (v, sub, depth)); - } - } - } - - return titles; - } - - private TileView GetRootTileView () - { - TileView root = this; - - while (root._parentTileView is { }) - { - root = root._parentTileView; - } - - return root; - } - - private Dim GetTileWidthOrHeight (int i, int space, Tile? [] visibleTiles, TileViewLineView? [] visibleSplitterLines) - { - // last tile - if (i + 1 >= visibleTiles.Length) - { - return Dim.Fill (HasBorder () ? 1 : 0)!; - } - - TileViewLineView? nextSplitter = visibleSplitterLines [i]; - Pos? nextSplitterPos = Orientation == Orientation.Vertical ? nextSplitter!.X : nextSplitter!.Y; - int nextSplitterDistance = nextSplitterPos.GetAnchor (space); - - TileViewLineView? lastSplitter = i >= 1 ? visibleSplitterLines [i - 1] : null; - Pos? lastSplitterPos = Orientation == Orientation.Vertical ? lastSplitter?.X : lastSplitter?.Y; - int lastSplitterDistance = lastSplitterPos?.GetAnchor (space) ?? 0; - - int distance = nextSplitterDistance - lastSplitterDistance; - - if (i > 0) - { - return distance - 1; - } - - return distance - (HasBorder () ? 1 : 0); - } - - private bool HasBorder () { return LineStyle != LineStyle.None; } - - private void HideSplittersBasedOnTileVisibility () - { - if (_splitterLines is { Count: 0 }) - { - return; - } - - foreach (TileViewLineView line in _splitterLines!) - { - line.Visible = true; - } - - for (var i = 0; i < _tiles!.Count; i++) - { - if (!_tiles [i].ContentView!.Visible) - { - // when a tile is not visible, prefer hiding - // the splitter on it's left - TileViewLineView candidate = _splitterLines [Math.Max (0, i - 1)]; - - // unless that splitter is already hidden - // e.g. when hiding panels 0 and 1 of a 3 panel - // container - if (candidate.Visible) - { - candidate.Visible = false; - } - else - { - _splitterLines [Math.Min (i, _splitterLines.Count - 1)].Visible = false; - } - } - } - } - - private bool IsValidNewSplitterPos (int idx, Pos value, int fullSpace) - { - int newSize = value.GetAnchor (fullSpace); - bool isGettingBigger = newSize > _splitterDistances! [idx].GetAnchor (fullSpace); - int lastSplitterOrBorder = HasBorder () ? 1 : 0; - int nextSplitterOrBorder = HasBorder () ? fullSpace - 1 : fullSpace; - - // Cannot move off screen right - if (newSize >= fullSpace - (HasBorder () ? 1 : 0)) - { - if (isGettingBigger) - { - return false; - } - } - - // Cannot move off screen left - if (newSize < (HasBorder () ? 1 : 0)) - { - if (!isGettingBigger) - { - return false; - } - } - - // Do not allow splitter to move left of the one before - if (idx > 0) - { - int posLeft = _splitterDistances [idx - 1].GetAnchor (fullSpace); - - if (newSize <= posLeft) - { - return false; - } - - lastSplitterOrBorder = posLeft; - } - - // Do not allow splitter to move right of the one after - if (idx + 1 < _splitterDistances.Count) - { - int posRight = _splitterDistances [idx + 1].GetAnchor (fullSpace); - - if (newSize >= posRight) - { - return false; - } - - nextSplitterOrBorder = posRight; - } - - if (isGettingBigger) - { - int spaceForNext = nextSplitterOrBorder - newSize; - - // space required for the last line itself - if (idx > 0) - { - spaceForNext--; - } - - // don't grow if it would take us below min size of right panel - if (spaceForNext < _tiles! [idx + 1].MinSize) - { - return false; - } - } - else - { - int spaceForLast = newSize - lastSplitterOrBorder; - - // space required for the line itself - if (idx > 0) - { - spaceForLast--; - } - - // don't shrink if it would take us below min size of left panel - if (spaceForLast < _tiles! [idx].MinSize) - { - return false; - } - } - - return true; - } - - private bool RecursiveContains (IEnumerable haystack, View needle) - { - foreach (View v in haystack) - { - if (v == needle) - { - return true; - } - - if (RecursiveContains (v.SubViews, needle)) - { - return true; - } - } - - return false; - } - - private void Setup (Rectangle viewport) - { - if (viewport.IsEmpty || viewport.Height <= 0 || viewport.Width <= 0) - { - return; - } - - for (var i = 0; i < _splitterLines!.Count; i++) - { - TileViewLineView line = _splitterLines [i]; - - line.Orientation = Orientation; - - line.Width = _orientation == Orientation.Vertical - ? 1 - : Dim.Fill (); - - line.Height = _orientation == Orientation.Vertical - ? Dim.Fill () - : 1; - line.LineRune = _orientation == Orientation.Vertical ? Glyphs.VLine : Glyphs.HLine; - - if (_orientation == Orientation.Vertical) - { - line.X = _splitterDistances! [i]; - line.Y = 0; - } - else - { - line.Y = _splitterDistances! [i]; - line.X = 0; - } - } - - HideSplittersBasedOnTileVisibility (); - - Tile [] visibleTiles = _tiles!.Where (t => t.ContentView!.Visible).ToArray (); - TileViewLineView [] visibleSplitterLines = _splitterLines.Where (l => l.Visible).ToArray (); - - for (var i = 0; i < visibleTiles.Length; i++) - { - Tile tile = visibleTiles [i]; - - if (Orientation == Orientation.Vertical) - { - tile.ContentView!.X = i == 0 ? viewport.X : Pos.Right (visibleSplitterLines [i - 1]); - tile.ContentView.Y = viewport.Y; - tile.ContentView.Height = viewport.Height; - tile.ContentView.Width = GetTileWidthOrHeight (i, Viewport.Width, visibleTiles, visibleSplitterLines); - } - else - { - tile.ContentView!.X = viewport.X; - tile.ContentView.Y = i == 0 ? viewport.Y : Pos.Bottom (visibleSplitterLines [i - 1]); - tile.ContentView.Width = viewport.Width; - tile.ContentView.Height = GetTileWidthOrHeight (i, Viewport.Height, visibleTiles, visibleSplitterLines); - } - - // BUGBUG: This should not be needed. If any of the pos/dim setters above actually changed values, NeedsDisplay should have already been set. - tile.ContentView.SetNeedsDraw (); - } - } - - private class TileTitleToRender - { - public TileTitleToRender (TileView? parent, Tile tile, int depth) - { - Parent = parent; - Tile = tile; - Depth = depth; - } - - public int Depth { get; } - public TileView? Parent { get; } - public Tile? Tile { get; } - - /// - /// Translates the title location from its local coordinate space - /// . - /// - public Point GetLocalCoordinateForTitle (TileView intoCoordinateSpace) - { - Rectangle screen = Tile!.ContentView!.ViewportToScreen (Rectangle.Empty); - - return intoCoordinateSpace.ScreenToFrame (new (screen.X, screen.Y - 1)); - } - - internal string GetTrimmedTitle () - { - Dim? spaceDim = Tile?.ContentView?.Width; - - int spaceAbs = spaceDim!.GetAnchor (Parent!.Viewport.Width); - - var title = $" {Tile!.Title} "; - - if (title.Length > spaceAbs) - { - return title!.Substring (0, spaceAbs); - } - - return title; - } - } - - private class TileViewLineView : LineView - { - public Point? moveRuneRenderLocation; - - private Pos? dragOrignalPos; - private Point? dragPosition; - - public TileViewLineView (TileView parent, int idx) - { - CanFocus = false; - TabStop = TabBehavior.TabStop; - - Parent = parent; - Idx = idx; - AddCommand (Command.Right, () => MoveSplitter (1, 0)); - - AddCommand (Command.Left, () => MoveSplitter (-1, 0)); - - AddCommand (Command.Up, () => MoveSplitter (0, -1)); - - AddCommand (Command.Down, () => MoveSplitter (0, 1)); - - KeyBindings.Add (Key.CursorRight, Command.Right); - KeyBindings.Add (Key.CursorLeft, Command.Left); - KeyBindings.Add (Key.CursorUp, Command.Up); - KeyBindings.Add (Key.CursorDown, Command.Down); - } - - public int Idx { get; } - public TileView Parent { get; } - - public void DrawSplitterSymbol () - { - if (dragPosition is { } || CanFocus) - { - Point location = moveRuneRenderLocation ?? new Point (Viewport.Width / 2, Viewport.Height / 2); - - AddRune (location.X, location.Y, Glyphs.Diamond); - } - } - - protected override bool OnMouseEvent (MouseEventArgs mouseEvent) - { - if (!dragPosition.HasValue && mouseEvent.Flags == MouseFlags.Button1Pressed) - { - // Start a Drag - SetFocus (); - - if (mouseEvent.Flags == MouseFlags.Button1Pressed) - { - dragPosition = mouseEvent.Position; - dragOrignalPos = Orientation == Orientation.Horizontal ? Y : X; - Application.MouseGrabHandler.GrabMouse (this); - - if (Orientation == Orientation.Horizontal) - { } - else - { - moveRuneRenderLocation = new Point ( - 0, - Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Position.Y)) - ); - } - } - - return true; - } - - if ( - dragPosition.HasValue && mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) - { - // Continue Drag - - // how far has user dragged from original location? - if (Orientation == Orientation.Horizontal) - { - int dy = mouseEvent.Position.Y - dragPosition.Value.Y; - Parent.SetSplitterPos (Idx, Offset (Y, dy)); - moveRuneRenderLocation = new Point (mouseEvent.Position.X, 0); - } - else - { - int dx = mouseEvent.Position.X - dragPosition.Value.X; - Parent.SetSplitterPos (Idx, Offset (X, dx)); - moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Position.Y))); - } - - Parent.SetNeedsLayout (); - - return true; - } - - if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) - { - // End Drag - - Application.MouseGrabHandler.UngrabMouse (); - - //Driver.UncookMouse (); - FinalisePosition ( - dragOrignalPos!, - Orientation == Orientation.Horizontal ? Y : X - ); - dragPosition = null; - moveRuneRenderLocation = null; - } - - return false; - } - - /// - protected override bool OnClearingViewport () { return true; } - - protected override bool OnDrawingContent () - { - DrawSplitterSymbol (); - - return true; - } - - public override Point? PositionCursor () - { - base.PositionCursor (); - - Point location = moveRuneRenderLocation ?? new Point (Viewport.Width / 2, Viewport.Height / 2); - Move (location.X, location.Y); - - return null; // Hide cursor - } - - /// - /// - /// Determines the absolute position of and returns a that - /// describes the percentage of that. - /// - /// - /// Effectively turning any into a (as if created with - /// ) - /// - /// - /// The to convert to - /// The Height/Width that lies within - /// - private Pos ConvertToPosPercent (Pos p, int parentLength) - { - // Calculate position in the 'middle' of the cell at p distance along parentLength - float position = p.GetAnchor (parentLength) + 0.5f; - - // Calculate the percentage - var percent = (int)Math.Round (position / parentLength * 100); - - // Return a new PosPercent object - return Pos.Percent (percent); - } - - /// - /// - /// Moves to - /// preserving format (absolute / relative) that - /// had. - /// - /// - /// This ensures that if splitter location was e.g. 50% before and you move it to absolute 5 then you end up - /// with 10% (assuming a parent had 50 width). - /// - /// - /// - /// - private bool FinalisePosition (Pos oldValue, Pos newValue) - { - SetNeedsDraw (); - - SetNeedsLayout (); - - if (oldValue is PosPercent) - { - if (Orientation == Orientation.Horizontal) - { - return Parent.SetSplitterPos (Idx, ConvertToPosPercent (newValue, Parent.Viewport.Height)); - } - - return Parent.SetSplitterPos (Idx, ConvertToPosPercent (newValue, Parent.Viewport.Width)); - } - - return Parent.SetSplitterPos (Idx, newValue); - } - - private bool MoveSplitter (int distanceX, int distanceY) - { - if (Orientation == Orientation.Vertical) - { - // Cannot move in this direction - if (distanceX == 0) - { - return false; - } - - Pos oldX = X; - - return FinalisePosition (oldX, Offset (X, distanceX)); - } - - // Cannot move in this direction - if (distanceY == 0) - { - return false; - } - - Pos oldY = Y; - - return FinalisePosition (oldY, Offset (Y, distanceY)); - } - - private Pos Offset (Pos pos, int delta) - { - int posAbsolute = pos.GetAnchor ( - Orientation == Orientation.Horizontal - ? Parent.Viewport.Height - : Parent.Viewport.Width - ); - - return posAbsolute + delta; - } - } -} - -/// Represents a method that will handle splitter events. -public delegate void SplitterEventHandler (object? sender, SplitterEventArgs e); diff --git a/Tests/UnitTests/FileServices/FileDialogTests.cs b/Tests/UnitTests/FileServices/FileDialogTests.cs index 3aa7830111..11e0972c61 100644 --- a/Tests/UnitTests/FileServices/FileDialogTests.cs +++ b/Tests/UnitTests/FileServices/FileDialogTests.cs @@ -798,8 +798,14 @@ private TextField GetTextField (FileDialog dlg, FileDialogPart part) private TableView GetTableView (FileDialog dlg) { - var tile = dlg.SubViews.OfType ().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 diff --git a/Tests/UnitTests/Views/TileViewTests.cs b/Tests/UnitTests/Views/TileViewTests.cs deleted file mode 100644 index 4cc7ee2ea4..0000000000 --- a/Tests/UnitTests/Views/TileViewTests.cs +++ /dev/null @@ -1,2406 +0,0 @@ -using UnitTests; -using Xunit.Abstractions; - -namespace Terminal.Gui.ViewsTests; - -public class TileViewTests (ITestOutputHelper output) -{ - [Fact] - [AutoInitShutdown] - public void Test_SplitTop_WholeBottom () - { - var tileView = new TileView (2) - { - Width = 20, Height = 10, Orientation = Orientation.Horizontal, LineStyle = LineStyle.Single - }; - - Assert.True (tileView.TrySplitTile (0, 2, out TileView top)); - - top.Tiles.ElementAt (0).ContentView!.Add (new Label { Text = "bleh" }); - top.Tiles.ElementAt (1).ContentView!.Add (new Label { Text = "blah" }); - top.Layout (); - - tileView.Tiles.ElementAt (1).ContentView!.Add (new Label { Text = "Hello" }); - tileView.SetScheme (new ()); - top.SetScheme (new ()); - - top.Layout (); - tileView.Layout (); - tileView.Draw (); - - var looksLike = - @" -┌─────────┬────────┐ -│bleh │blah │ -│ │ │ -│ │ │ -│ │ │ -├─────────┴────────┤ -│Hello │ -│ │ -│ │ -└──────────────────┘"; - - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter1 () - { - TileView tv = Get5x1TilesView (); - - tv.Tiles.ElementAt (0).MinSize = int.MaxValue; - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -┌────┬────┬────┬────┬───┐ -│1111│2222│3333│4444│555│ -│ │ │ │ │ │ -└────┴────┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 0; x <= 5; x++) - { - // All these values would result in tile 0 getting smaller - // so are not allowed (tile[0] has a min size of Int.Max) - Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - for (var x = 6; x < 10; x++) - { - // All these values would result in tile 0 getting bigger - // so are allowed - Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - for (var x = 10; x < 100; x++) - { - // These values would result in the first splitter moving past - // the second splitter so are not allowed - Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────────┬┬────┬────┬───┐ -│11111111││3333│4444│555│ -│ ││ │ │ │ -└────────┴┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter1_NoBorder () - { - TileView tv = Get5x1TilesView (false); - - tv.Tiles.ElementAt (0).MinSize = int.MaxValue; - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -11111│2222│3333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 0; x <= 5; x++) - { - // All these values would result in tile 0 getting smaller - // so are not allowed (tile[0] has a min size of Int.Max) - Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - for (var x = 6; x < 10; x++) - { - // All these values would result in tile 0 getting bigger - // so are allowed - Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - for (var x = 10; x < 100; x++) - { - // These values would result in the first splitter moving past - // the second splitter so are not allowed - Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -111111111││3333│4444│5555 - ││ │ │ - ││ │ │ - ││ │ │ - -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter2 () - { - TileView tv = Get5x1TilesView (); - - tv.Tiles.ElementAt (1).MinSize = 2; - tv.Tiles.ElementAt (2).MinSize = 3; - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -┌────┬────┬────┬────┬───┐ -│1111│2222│3333│4444│555│ -│ │ │ │ │ │ -└────┴────┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x > 7; x--) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 7; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────┬──┬──────┬────┬───┐ -│1111│22│333333│4444│555│ -│ │ │ │ │ │ -└────┴──┴──────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x < 12; x++) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 12; x < 25; x++) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - Application.Top!.Layout (); - tv.Draw (); - - looksLike = - @" -┌────┬─────┬───┬────┬───┐ -│1111│22222│333│4444│555│ -│ │ │ │ │ │ -└────┴─────┴───┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter2_NoBorder () - { - TileView tv = Get5x1TilesView (false); - - tv.Tiles.ElementAt (1).MinSize = 2; - tv.Tiles.ElementAt (2).MinSize = 3; - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -11111│2222│3333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x > 7; x--) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 7; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" - -11111│22│333333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ - -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x < 12; x++) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 12; x < 25; x++) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -11111│22222│333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ - -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter4 () - { - TileView tv = Get5x1TilesView (); - - tv.Tiles.ElementAt (3).MinSize = 2; - tv.Tiles.ElementAt (4).MinSize = 1; - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -┌────┬────┬────┬────┬───┐ -│1111│2222│3333│4444│555│ -│ │ │ │ │ │ -└────┴────┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x > 17; x--) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 17; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────┬────┬────┬──┬─────┐ -│1111│2222│3333│44│55555│ -│ │ │ │ │ │ -└────┴────┴────┴──┴─────┘ - -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x < 23; x++) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 23; x < 100; x++) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────┬────┬────┬──────┬─┐ -│1111│2222│3333│444444│5│ -│ │ │ │ │ │ -└────┴────┴────┴──────┴─┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_MinSizes_VerticalSplitters_ResizeSplitter4_NoBorder () - { - TileView tv = Get5x1TilesView (false); - - tv.Tiles.ElementAt (3).MinSize = 2; - tv.Tiles.ElementAt (4).MinSize = 1; - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -11111│2222│3333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x > 17; x--) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 17; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -11111│2222│3333│44│555555 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x < 24; x++) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 24; x < 100; x++) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -11111│2222│3333│4444444│5 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter1_CannotCrossBorder () - { - TileView tv = Get5x1TilesView (); - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -┌────┬────┬────┬────┬───┐ -│1111│2222│3333│4444│555│ -│ │ │ │ │ │ -└────┴────┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 5; x > 0; x--) - { - Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - Assert.False (tv.SetSplitterPos (0, 0)); - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌┬────────┬────┬────┬───┐ -││22222222│3333│4444│555│ -││ │ │ │ │ -└┴────────┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 6; x < 10; x++) - { - Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - for (var x = 10; x < 100; x++) - { - // These values would result in the first splitter moving past - // the second splitter so are not allowed - Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────────┬┬────┬────┬───┐ -│11111111││3333│4444│555│ -│ ││ │ │ │ -└────────┴┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter1_CannotCrossBorder_NoBorder () - { - TileView tv = Get5x1TilesView (false); - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -11111│2222│3333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 5; x >= 0; x--) - { - Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -│222222222│3333│4444│5555 -│ │ │ │ -│ │ │ │ -│ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 6; x < 10; x++) - { - Assert.True (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - for (var x = 10; x < 100; x++) - { - // These values would result in the first splitter moving past - // the second splitter so are not allowed - Assert.False (tv.SetSplitterPos (0, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -111111111││3333│4444│5555 - ││ │ │ - ││ │ │ - ││ │ │ - -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter2_CannotMoveOverNeighbours () - { - TileView tv = Get5x1TilesView (); - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -┌────┬────┬────┬────┬───┐ -│1111│2222│3333│4444│555│ -│ │ │ │ │ │ -└────┴────┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x > 5; x--) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 5; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────┬┬────────┬────┬───┐ -│1111││33333333│4444│555│ -│ ││ │ │ │ -└────┴┴────────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x < 15; x++) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 15; x < 25; x++) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────┬────────┬┬────┬───┐ -│1111│22222222││4444│555│ -│ │ ││ │ │ -└────┴────────┴┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter2_CannotMoveOverNeighbours_NoBorder () - { - TileView tv = Get5x1TilesView (false); - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -11111│2222│3333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x > 5; x--) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 5; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -11111││33333333│4444│5555 - ││ │ │ - ││ │ │ - ││ │ │ - -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 10; x < 15; x++) - { - Assert.True (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - for (var x = 15; x < 25; x++) - { - Assert.False (tv.SetSplitterPos (1, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -11111│22222222││4444│5555 - │ ││ │ - │ ││ │ - │ ││ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter4_CannotMoveOverNeighbours () - { - TileView tv = Get5x1TilesView (); - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -┌────┬────┬────┬────┬───┐ -│1111│2222│3333│4444│555│ -│ │ │ │ │ │ -└────┴────┴────┴────┴───┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x > 15; x--) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 15; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────┬────┬────┬┬───────┐ -│1111│2222│3333││5555555│ -│ │ │ ││ │ -└────┴────┴────┴┴───────┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x < 24; x++) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 24; x < 100; x++) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -┌────┬────┬────┬───────┬┐ -│1111│2222│3333│4444444││ -│ │ │ │ ││ -└────┴────┴────┴───────┴┘ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - [AutoInitShutdown] - public void Test5Panel_NoMinSizes_VerticalSplitters_ResizeSplitter4_CannotMoveOverNeighbours_NoBorder () - { - TileView tv = Get5x1TilesView (false); - - AutoInitShutdownAttribute.RunIteration (); - - var looksLike = - @" -11111│2222│3333│4444│5555 - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x > 15; x--) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 15; x > 0; x--) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -11111│2222│3333││55555555 - │ │ ││ - │ │ ││ - │ │ ││ - -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - - for (var x = 20; x < 25; x++) - { - Assert.True (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - for (var x = 25; x < 100; x++) - { - Assert.False (tv.SetSplitterPos (3, x), $"Assert failed for x={x}"); - } - - AutoInitShutdownAttribute.RunIteration (); - - looksLike = - @" -11111│2222│3333│44444444│ - │ │ │ │ - │ │ │ │ - │ │ │ │ -"; - DriverAssert.AssertDriverContentsAre (looksLike, output); - } - - [Fact] - public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringInsertTile () - { - TileView tv = GetTileView (20, 10); - - var myReusableView = new DisposeCounter (); - - // I want my view in the first tile - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - Assert.Equal (0, myReusableView.DisposalCount); - - // I've changed my mind, I want 3 tiles now - tv.InsertTile (0); - tv.InsertTile (2); - - // but I still want my view in the first tile - // BUGBUG: Adding a view twice is not legit - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - - Assert.Multiple ( - () => Assert.Equal (0, myReusableView.DisposalCount), - () => - { - tv.Dispose (); - Assert.True (myReusableView.DisposalCount >= 1); - } - ); - } - - [Fact] - public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRebuildForTileCount () - { - TileView tv = GetTileView (20, 10); - - var myReusableView = new DisposeCounter (); - - // I want my view in the first tile - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - Assert.Equal (0, myReusableView.DisposalCount); - - // I've changed my mind, I want 3 tiles now - tv.RebuildForTileCount (3); - - // but I still want my view in the first tile - // BUGBUG: Adding a view twice is not legit - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - - Assert.Multiple ( - () => Assert.Equal (0, myReusableView.DisposalCount), - () => - { - tv.Dispose (); - Assert.Equal (1, myReusableView.DisposalCount); - } - ); - - Assert.NotNull (Application.Top); - Application.Top.Dispose (); - Application.Shutdown (); - } - - [Theory] - [InlineData (0)] - [InlineData (1)] - public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRemoveTile (int idx) - { - TileView tv = GetTileView (20, 10); - - var myReusableView = new DisposeCounter (); - - // I want my view in the first tile - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - Assert.Equal (0, myReusableView.DisposalCount); - - tv.RemoveTile (idx); - - // but I still want my view in the first tile - // BUGBUG: Adding a view twice is not legit - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - - Assert.Multiple ( - () => Assert.Equal (0, myReusableView.DisposalCount), - () => - { - tv.Dispose (); - Assert.True (myReusableView.DisposalCount >= 1); - } - ); - - Assert.NotNull (Application.Top); - Application.Top.Dispose (); - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void TestNestedContainer2LeftAnd1Right_RendersNicely () - { - TileView tileView = GetNestedContainer2Left1Right (false); - - Assert.Equal (20, tileView.Frame.Width); - Assert.Equal (10, tileView.Tiles.ElementAt (0).ContentView.Frame.Width); - Assert.Equal (9, tileView.Tiles.ElementAt (1).ContentView.Frame.Width); - - Assert.IsType (tileView.Tiles.ElementAt (0).ContentView); - var left = (TileView)tileView.Tiles.ElementAt (0).ContentView; - Assert.Same (left.SuperView, tileView); - - Assert.Equal (2, left.Tiles.ElementAt (0).ContentView.SubViews.Count); - Assert.IsType