From 67cdb5645b0fe9580ad8241aff5f03dfb800f41d Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 13 Feb 2023 02:06:35 +0000 Subject: [PATCH 01/11] Add tests for proper disposing --- UnitTests/Views/TileViewTests.cs | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/UnitTests/Views/TileViewTests.cs b/UnitTests/Views/TileViewTests.cs index 4505694f0d..0f7c045a41 100644 --- a/UnitTests/Views/TileViewTests.cs +++ b/UnitTests/Views/TileViewTests.cs @@ -2094,7 +2094,86 @@ public void TestNestedContainer3RightAnd1Down_TitleTriesToOverspill () TestHelpers.AssertDriverContentsAre (looksLike, output); } + [Fact, AutoInitShutdown] + public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRebuildForTileCount () + { + var 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 + tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); + Assert.Multiple ( + () => Assert.Equal (0, myReusableView.DisposalCount) + , () => { + tv.Dispose (); + Assert.Equal (1, myReusableView.DisposalCount); + }); + } + [Fact, AutoInitShutdown] + public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringInsertTile () + { + var 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 + tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); + Assert.Multiple ( + () => Assert.Equal (0, myReusableView.DisposalCount) + , () => { + tv.Dispose (); + Assert.True (myReusableView.DisposalCount>=1); + }); + } + [Theory, AutoInitShutdown] + [InlineData (0)] + [InlineData (1)] + public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRemoveTile (int idx) + { + var 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 + tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); + Assert.Multiple ( + () => Assert.Equal (0, myReusableView.DisposalCount) + , () => { + tv.Dispose (); + Assert.True (myReusableView.DisposalCount >= 1); + }); + } + private class DisposeCounter : View { + public int DisposalCount; + protected override void Dispose (bool disposing) + { + DisposalCount++; + base.Dispose (disposing); + } + + } /// /// Creates a vertical orientation root container with left pane split into From 14077058d42505223058ef0a41fbe84544c442bd Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 18 Feb 2023 20:39:45 +0000 Subject: [PATCH 02/11] Make Ctrl+F10 toggle split line focusability --- Terminal.Gui/Views/TileView.cs | 35 ++++++++++++++++++++++++++------ UnitTests/Views/TileViewTests.cs | 35 +++++++++++--------------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index cb9ac33736..d1c77d66f3 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -13,6 +13,12 @@ namespace Terminal.Gui { public class TileView : View { TileView parentTileView; + /// + /// The keyboard key that the user can press to toggle resizing + /// of splitter lines. Mouse drag splitting is always enabled. + /// + public Key ToggleResizable { get; set; } = Key.CtrlMask | Key.F10; + /// /// A single presented in a . To create /// new instances use @@ -548,6 +554,27 @@ public bool TrySplitTile (int idx, int numberOfPanels, out TileView result) return true; } + /// + public override bool ProcessHotKey (KeyEvent keyEvent) + { + bool focusMoved = false; + + if(keyEvent.Key == ToggleResizable) { + foreach(var l in splitterLines) { + + l.CanFocus = !l.CanFocus; + + if (l.CanFocus && !focusMoved) { + l.SetFocus (); + focusMoved = true; + } + } + return true; + } + + return base.ProcessHotKey (keyEvent); + } + private bool IsValidNewSplitterPos (int idx, Pos value, int fullSpace) { int newSize = value.Anchor (fullSpace); @@ -862,7 +889,7 @@ private class TileViewLineView : LineView { public TileViewLineView (TileView parent, int idx) { - CanFocus = true; + CanFocus = false; TabStop = true; this.Parent = parent; @@ -927,7 +954,7 @@ public override void Redraw (Rect bounds) public void DrawSplitterSymbol () { - if (CanFocus && HasFocus) { + if (dragPosition != null || CanFocus) { var location = moveRuneRenderLocation ?? new Point (Bounds.Width / 2, Bounds.Height / 2); @@ -937,10 +964,6 @@ public void DrawSplitterSymbol () public override bool MouseEvent (MouseEvent mouseEvent) { - if (!CanFocus) { - return true; - } - if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed)) { // Start a Drag diff --git a/UnitTests/Views/TileViewTests.cs b/UnitTests/Views/TileViewTests.cs index 0f7c045a41..d969ec1123 100644 --- a/UnitTests/Views/TileViewTests.cs +++ b/UnitTests/Views/TileViewTests.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Terminal.Gui; using Terminal.Gui.Graphs; using Xunit; using Xunit.Abstractions; @@ -60,7 +59,7 @@ public void TestTileView_Vertical_WithBorder () public void TestTileView_Vertical_Focused () { var tileView = Get11By3TileView (out var line); - SetInputFocusLine (tileView); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); tileView.Redraw (tileView.Bounds); @@ -100,7 +99,7 @@ public void TestTileView_Vertical_Focused () public void TestTileView_Vertical_Focused_WithBorder () { var tileView = Get11By3TileView (out var line, true); - SetInputFocusLine (tileView); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); tileView.Redraw (tileView.Bounds); @@ -141,9 +140,10 @@ public void TestTileView_Vertical_Focused_WithBorder () public void TestTileView_Vertical_Focused_50PercentSplit () { var tileView = Get11By3TileView (out var line); - SetInputFocusLine (tileView); tileView.SetSplitterPos (0, Pos.Percent (50)); Assert.IsType (tileView.SplitterDistances.ElementAt (0)); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); + tileView.Redraw (tileView.Bounds); string looksLike = @@ -209,7 +209,7 @@ public void TestTileView_Horizontal () public void TestTileView_Vertical_View1MinSize_Absolute () { var tileView = Get11By3TileView (out var line); - SetInputFocusLine (tileView); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); tileView.Tiles.ElementAt (0).MinSize = 6; // distance is too small (below 6) @@ -254,7 +254,7 @@ public void TestTileView_Vertical_View1MinSize_Absolute () public void TestTileView_Vertical_View1MinSize_Absolute_WithBorder () { var tileView = Get11By3TileView (out var line, true); - SetInputFocusLine (tileView); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); tileView.Tiles.ElementAt (0).MinSize = 5; // distance is too small (below 5) @@ -298,7 +298,7 @@ public void TestTileView_Vertical_View1MinSize_Absolute_WithBorder () public void TestTileView_Vertical_View2MinSize_Absolute () { var tileView = Get11By3TileView (out var line); - SetInputFocusLine (tileView); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); tileView.Tiles.ElementAt (1).MinSize = 6; // distance leaves too little space for view2 (less than 6 would remain) @@ -342,7 +342,7 @@ public void TestTileView_Vertical_View2MinSize_Absolute () public void TestTileView_Vertical_View2MinSize_Absolute_WithBorder () { var tileView = Get11By3TileView (out var line, true); - SetInputFocusLine (tileView); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); tileView.Tiles.ElementAt (1).MinSize = 5; // distance leaves too little space for view2 (less than 5 would remain) @@ -386,8 +386,6 @@ public void TestTileView_Vertical_View2MinSize_Absolute_WithBorder () public void TestTileView_InsertPanelAtStart () { var tileView = Get11By3TileView (out var line, true); - SetInputFocusLine (tileView); - tileView.InsertTile (0); tileView.Redraw (tileView.Bounds); @@ -405,8 +403,6 @@ public void TestTileView_InsertPanelAtStart () public void TestTileView_InsertPanelMiddle () { var tileView = Get11By3TileView (out var line, true); - SetInputFocusLine (tileView); - tileView.InsertTile (1); tileView.Redraw (tileView.Bounds); @@ -424,8 +420,6 @@ public void TestTileView_InsertPanelMiddle () public void TestTileView_InsertPanelAtEnd () { var tileView = Get11By3TileView (out var line, true); - SetInputFocusLine (tileView); - tileView.InsertTile (2); tileView.Redraw (tileView.Bounds); @@ -445,7 +439,9 @@ public void TestTileView_Horizontal_Focused () var tileView = Get11By3TileView (out var line); tileView.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal; - SetInputFocusLine (tileView); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); + + Assert.True (line.HasFocus); tileView.Redraw (tileView.Bounds); @@ -485,9 +481,9 @@ public void TestTileView_Horizontal_Focused () public void TestTileView_Horizontal_View1MinSize_Absolute () { var tileView = Get11By3TileView (out var line); + tileView.ProcessHotKey (new KeyEvent (tileView.ToggleResizable, new KeyModifiers ())); tileView.Orientation = Terminal.Gui.Graphs.Orientation.Horizontal; - SetInputFocusLine (tileView); tileView.Tiles.ElementAt (0).MinSize = 1; // 0 should not be allowed because it brings us below minimum size of View1 @@ -2247,13 +2243,6 @@ private LineView GetLine (TileView tileView) return tileView.Subviews.OfType ().Single (); } - private void SetInputFocusLine (TileView tileView) - { - var line = GetLine (tileView); - line.SetFocus (); - Assert.True (line.HasFocus); - } - private TileView Get5x1TilesView (bool border = true) { From c5243ffe667b80fdcd41f3128be999269bc9e446 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 18 Feb 2023 21:41:30 +0000 Subject: [PATCH 03/11] Fix layout bug in first tile when orientation is horizontal --- Terminal.Gui/Views/TileView.cs | 2 +- UnitTests/Views/TileViewTests.cs | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index d1c77d66f3..9585752852 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -775,7 +775,7 @@ private void Setup (Rect bounds) tile.ContentView.Width = GetTileWidthOrHeight (i, Bounds.Width, visibleTiles, visibleSplitterLines); } else { tile.ContentView.X = bounds.X; - tile.ContentView.Y = i == 0 ? 0 : Pos.Bottom (visibleSplitterLines [i - 1]); + tile.ContentView.Y = i == 0 ? bounds.Y : Pos.Bottom (visibleSplitterLines [i - 1]); tile.ContentView.Width = bounds.Width; tile.ContentView.Height = GetTileWidthOrHeight (i, Bounds.Height, visibleTiles, visibleSplitterLines); } diff --git a/UnitTests/Views/TileViewTests.cs b/UnitTests/Views/TileViewTests.cs index d969ec1123..d3e97acec9 100644 --- a/UnitTests/Views/TileViewTests.cs +++ b/UnitTests/Views/TileViewTests.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Linq; using Terminal.Gui.Graphs; using Xunit; @@ -2041,6 +2042,45 @@ public void TestNestedContainer3RightAnd1Down_TileVisibility_WithoutBorder () } + [Fact, AutoInitShutdown] + public void Test_SplitTop_WholeBottom() + { + var tileView = new TileView (2) { + Width = 20, + Height = 10, + Orientation = Orientation.Horizontal, + }; + tileView.Border.BorderStyle = BorderStyle.Single; + + Assert.True (tileView.TrySplitTile (0,2,out TileView top)); + + top.Tiles.ElementAt (0).ContentView.Add (new Label ("bleh")); + top.Tiles.ElementAt (1).ContentView.Add (new Label ("blah")); + + tileView.Tiles.ElementAt (1).ContentView.Add (new Label ("Hello")); + tileView.ColorScheme = new ColorScheme (); + top.ColorScheme = new ColorScheme (); + tileView.LayoutSubviews (); + + tileView.Redraw (tileView.Bounds); + + string looksLike = +@" +┌─────────┬────────┐ +│bleh │blah │ +│ │ │ +│ │ │ +│ │ │ +├─────────┴────────┤ +│Hello │ +│ │ +│ │ +└──────────────────┘"; + + TestHelpers.AssertDriverContentsAre (looksLike, output); + + } + [Fact, AutoInitShutdown] public void TestNestedContainer3RightAnd1Down_TitleDoesNotOverspill() { From 67a9e058d11e6522e28577388c603f073bca7241 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 20 Feb 2023 17:01:05 +0000 Subject: [PATCH 04/11] Switch to GenerateImage --- Terminal.Gui/Views/FrameView.cs | 5 ++++- Terminal.Gui/Views/TileView.cs | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index 027cd26e66..40f7967c0c 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -262,7 +262,10 @@ public override void Redraw (Rect bounds) } Driver.SetAttribute (ColorScheme.Normal); - lc.Draw (this, bounds); + foreach(var p in lc.GenerateImage (bounds)) { + AddRune (p.Key.X, p.Key.Y, p.Value); + } + // Redraw the lines so that focus/drag symbol renders diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 9585752852..d54c08583a 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -465,7 +465,9 @@ public override void Redraw (Rect bounds) } Driver.SetAttribute (ColorScheme.Normal); - lc.Draw (this, bounds); + foreach (var p in lc.GenerateImage (bounds)) { + AddRune (p.Key.X, p.Key.Y, p.Value); + } // Redraw the lines so that focus/drag symbol renders foreach (var line in allLines) { From 3c3a0148147eccf44f9d905030ed8d9ba2ab8d2b Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 24 Feb 2023 19:42:39 +0000 Subject: [PATCH 05/11] Fix not calling base constructor --- Terminal.Gui/Views/FrameView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index 40f7967c0c..8ae9b14122 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -86,7 +86,7 @@ public ContentView () : base () { } /// Title. /// Views. /// The . - public FrameView (Rect frame, ustring title = null, View [] views = null, Border border = null) //: base (frame) + public FrameView (Rect frame, ustring title = null, View [] views = null, Border border = null) : base (frame) { //var cFrame = new Rect (1, 1, Math.Max (frame.Width - 2, 0), Math.Max (frame.Height - 2, 0)); From a068933f192f5edf62520166826f383a690006dd Mon Sep 17 00:00:00 2001 From: Thomas Nind <31306100+tznind@users.noreply.github.com> Date: Fri, 24 Feb 2023 21:57:58 +0000 Subject: [PATCH 06/11] Revert "Merges latest LineCanvas into TileView" --- Terminal.Gui/Core/Border.cs | 23 +--- Terminal.Gui/Core/Graphs/LineCanvas.cs | 2 +- Terminal.Gui/Core/View.cs | 12 +- Terminal.Gui/Views/FrameView.cs | 18 +-- UICatalog/Scenarios/TileViewExperiment.cs | 149 +++++++++------------ UnitTests/Core/LineCanvasTests.cs | 15 ++- docfx/v2specs/View.md | 154 ---------------------- 7 files changed, 93 insertions(+), 280 deletions(-) delete mode 100644 docfx/v2specs/View.md diff --git a/Terminal.Gui/Core/Border.cs b/Terminal.Gui/Core/Border.cs index f3a1c01d47..55d73a65c9 100644 --- a/Terminal.Gui/Core/Border.cs +++ b/Terminal.Gui/Core/Border.cs @@ -1,6 +1,5 @@ using NStack; using System; -using Terminal.Gui.Graphs; namespace Terminal.Gui { /// @@ -708,22 +707,14 @@ private void DrawChildBorder (Rect frame, bool fill = true) // Draw the MarginFrame if (DrawMarginFrame) { - var rect = Child.ViewToScreen (new Rect (-1, -1, Child.Frame.Width + 2, Child.Frame.Height + 2)); + var rect = new Rect () { + X = frame.X - drawMarginFrame, + Y = frame.Y - drawMarginFrame, + Width = frame.Width + (2 * drawMarginFrame), + Height = frame.Height + (2 * drawMarginFrame) + }; if (rect.Width > 0 && rect.Height > 0) { - - var lc = new LineCanvas (); - - lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, BorderStyle); - lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, BorderStyle); - - lc.AddLine (new Point (rect.X, rect.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, BorderStyle); - lc.AddLine (new Point (rect.X + rect.Width, rect.Y), rect.Height, Orientation.Vertical, BorderStyle); - - driver.SetAttribute (new Attribute (Color.Red, Color.BrightYellow)); - - foreach (var p in lc.GenerateImage(rect)) { - AddRuneAt (driver, p.Key.X, p.Key.Y, p.Value); - } + driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this); DrawTitle (Child); } } diff --git a/Terminal.Gui/Core/Graphs/LineCanvas.cs b/Terminal.Gui/Core/Graphs/LineCanvas.cs index 386388adc0..1b1cbe76bf 100644 --- a/Terminal.Gui/Core/Graphs/LineCanvas.cs +++ b/Terminal.Gui/Core/Graphs/LineCanvas.cs @@ -66,7 +66,7 @@ public Dictionary GenerateImage (Rect inArea) for (int x = inArea.X; x < inArea.Width; x++) { var intersects = lines - .Select (l => l.Intersects (inArea.X + x, inArea.Y + y)) + .Select (l => l.Intersects (x, y)) .Where (i => i != null) .ToArray (); diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 38381086ee..a65454d5f4 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -1513,10 +1513,8 @@ public virtual void Redraw (Rect bounds) Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); } - var boundsAdjustedForBorder = Bounds; if (!IgnoreBorderPropertyOnRedraw && Border != null) { Border.DrawContent (this); - boundsAdjustedForBorder = new Rect (bounds.X + 1, bounds.Y + 1, bounds.Width - 2, bounds.Height - 2); } else if (ustring.IsNullOrEmpty (TextFormatter.Text) && (GetType ().IsNestedPublic && !IsOverridden (this, "Redraw") || GetType ().Name == "View") && (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) { @@ -1533,20 +1531,18 @@ public virtual void Redraw (Rect bounds) if (TextFormatter != null) { TextFormatter.NeedsFormat = true; } - TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (), HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), - containerBounds); } // Invoke DrawContentEvent - OnDrawContent (boundsAdjustedForBorder); + OnDrawContent (bounds); if (subviews != null) { foreach (var view in subviews) { if (!view.NeedDisplay.IsEmpty || view.ChildNeedsDisplay || view.LayoutNeeded) { - if (view.Frame.IntersectsWith (clipRect) && (view.Frame.IntersectsWith (boundsAdjustedForBorder) || boundsAdjustedForBorder.X < 0 || bounds.Y < 0)) { + if (view.Frame.IntersectsWith (clipRect) && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { if (view.LayoutNeeded) view.LayoutSubviews (); @@ -1566,7 +1562,7 @@ public virtual void Redraw (Rect bounds) } // Invoke DrawContentCompleteEvent - OnDrawContentComplete (boundsAdjustedForBorder); + OnDrawContentComplete (bounds); ClearLayoutNeeded (); ClearNeedsDisplay (); diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index ef6d7dcfb7..8ae9b14122 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -1,4 +1,4 @@ -// +// // Authors: // Miguel de Icaza (miguel@gnome.org) // @@ -252,27 +252,27 @@ public override void Redraw (Rect bounds) lc.AddLine (new Point (bounds.Width - 1, bounds.Height - 1), -bounds.Height + 1, Orientation.Vertical, Border.BorderStyle); } - //foreach (var subview in contentView.Subviews) { - // lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Width - 1, Orientation.Horizontal, subview.Border.BorderStyle); - // lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Height - 1, Orientation.Vertical, subview.Border.BorderStyle); + foreach (var subview in contentView.Subviews) { + lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Width - 1, Orientation.Horizontal, subview.Border.BorderStyle); + lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Height - 1, Orientation.Vertical, subview.Border.BorderStyle); - // lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Width + 1, Orientation.Horizontal, subview.Border.BorderStyle); - // lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Height + 1, Orientation.Vertical, subview.Border.BorderStyle); + lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Width + 1, Orientation.Horizontal, subview.Border.BorderStyle); + lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Height + 1, Orientation.Vertical, subview.Border.BorderStyle); - //} + } Driver.SetAttribute (ColorScheme.Normal); - foreach(var p in lc.GenerateImage (bounds)) { AddRune (p.Key.X, p.Key.Y, p.Value); } + + // Redraw the lines so that focus/drag symbol renders foreach (var subview in contentView.Subviews) { // line.DrawSplitterSymbol (); } - // Draw Titles over Border foreach (var subview in contentView.Subviews) { // TODO: Use reflection to see if subview has a Title property diff --git a/UICatalog/Scenarios/TileViewExperiment.cs b/UICatalog/Scenarios/TileViewExperiment.cs index 982aef12e8..eeca745352 100644 --- a/UICatalog/Scenarios/TileViewExperiment.cs +++ b/UICatalog/Scenarios/TileViewExperiment.cs @@ -27,104 +27,83 @@ public override void Setup () Application.Top.Add (menu); - var frame1 = new FrameView () { + var frame = new FrameView () { X = 0, Y = 1, - Width = 15, //Dim.Fill (), - Height = 15, //Dim.Fill (), - //IgnoreBorderPropertyOnRedraw = true - + Width = Dim.Fill (), + Height = Dim.Fill (), + IgnoreBorderPropertyOnRedraw = true }; - frame1.Border.BorderStyle = BorderStyle.Double; + frame.Border.BorderStyle = BorderStyle.Double; - var frame2 = new FrameView () { - X = 0, - Y = Pos.Bottom (frame1) + 1, - Width = 15, //Dim.Fill (), - Height = 15, //Dim.Fill (), - //IgnoreBorderPropertyOnRedraw = true + Application.Top.Add (frame); + var view1 = new FrameView () { + Title = "View 1", + Text = "View1 30%/50% Single", + X = -1, + Y = -1, + Width = Dim.Percent (30), + Height = Dim.Percent (50), + ColorScheme = Colors.ColorSchemes ["Dialog"], + Border = new Border () { BorderStyle = BorderStyle.Single } }; - frame2.Border.BorderStyle = BorderStyle.Single; - //ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler; + frame.Add (view1); - Application.Top.Add (frame1); + //var view12splitter = new SplitterEventArgs - var view1 = new TextField () { - //Title = "View 1", - Text = "View1 30%/50% Single", - X = 0, - Y = 0, - Width = 14, //Dim.Percent (30) - 5, - Height = 14, //Dim.Percent (50) - 5, - ColorScheme = Colors.ColorSchemes ["Dialog"], - Border = new Border () { - BorderStyle = BorderStyle.Single, - //BorderThickness = new Thickness (1), - DrawMarginFrame = true, - Padding = new Thickness(1), - BorderBrush = Color.BrightMagenta, - Title = "Border Title" - } + var view2 = new FrameView () { + Title = "View 2", + Text = "View2 right of view1, 30%/70% Single.", + X = Pos.Right (view1) - 1, + Y = -1, + Width = Dim.Percent (30), + Height = Dim.Percent (70), + ColorScheme = Colors.ColorSchemes ["Error"], + Border = new Border () { BorderStyle = BorderStyle.Single } }; - frame1.Add (view1); - frame2.Add (view1); + frame.Add (view2); + + var view3 = new FrameView () { + Title = "View 3", + Text = "View3 right of View2 Fill/Fill Single", + X = Pos.Right (view2) - 1, + Y = -1, + Width = Dim.Fill (-1), + Height = Dim.Fill (-1), + ColorScheme = Colors.ColorSchemes ["Menu"], + Border = new Border () { BorderStyle = BorderStyle.Single } + }; - //var view12splitter = new SplitterEventArgs + frame.Add (view3); + + var view4 = new FrameView () { + Title = "View 4", + Text = "View4 below View2 view2.Width/5 Single", + X = Pos.Left (view2), + Y = Pos.Bottom (view2) - 1, + Width = view2.Width, + Height = 5, + ColorScheme = Colors.ColorSchemes ["TopLevel"], + Border = new Border () { BorderStyle = BorderStyle.Single } + }; + + frame.Add (view4); + + var view5 = new FrameView () { + Title = "View 5", + Text = "View5 below View4 view4.Width/5 Double", + X = Pos.Left (view2), + Y = Pos.Bottom (view4) - 1, + Width = view4.Width, + Height = 5, + ColorScheme = Colors.ColorSchemes ["TopLevel"], + Border = new Border () { BorderStyle = BorderStyle.Double } + }; - //var view2 = new FrameView () { - // Title = "View 2", - // Text = "View2 right of view1, 30%/70% Single.", - // X = Pos.Right (view1) - 1, - // Y = -1, - // Width = Dim.Percent (30), - // Height = Dim.Percent (70), - // ColorScheme = Colors.ColorSchemes ["Error"], - // Border = new Border () { BorderStyle = BorderStyle.Single } - //}; - - //frame.Add (view2); - - //var view3 = new FrameView () { - // Title = "View 3", - // Text = "View3 right of View2 Fill/Fill Single", - // X = Pos.Right (view2) - 1, - // Y = -1, - // Width = Dim.Fill (-1), - // Height = Dim.Fill (-1), - // ColorScheme = Colors.ColorSchemes ["Menu"], - // Border = new Border () { BorderStyle = BorderStyle.Single } - //}; - - //frame.Add (view3); - - //var view4 = new FrameView () { - // Title = "View 4", - // Text = "View4 below View2 view2.Width/5 Single", - // X = Pos.Left (view2), - // Y = Pos.Bottom (view2) - 1, - // Width = view2.Width, - // Height = 5, - // ColorScheme = Colors.ColorSchemes ["TopLevel"], - // Border = new Border () { BorderStyle = BorderStyle.Single } - //}; - - //frame.Add (view4); - - //var view5 = new FrameView () { - // Title = "View 5", - // Text = "View5 below View4 view4.Width/5 Double", - // X = Pos.Left (view2), - // Y = Pos.Bottom (view4) - 1, - // Width = view4.Width, - // Height = 5, - // ColorScheme = Colors.ColorSchemes ["TopLevel"], - // Border = new Border () { BorderStyle = BorderStyle.Double } - //}; - - //frame.Add (view5); + frame.Add (view5); } } } \ No newline at end of file diff --git a/UnitTests/Core/LineCanvasTests.cs b/UnitTests/Core/LineCanvasTests.cs index 969a3562d4..65bc1f3a25 100644 --- a/UnitTests/Core/LineCanvasTests.cs +++ b/UnitTests/Core/LineCanvasTests.cs @@ -331,13 +331,14 @@ private View GetCanvas (out LineCanvas canvas, int offsetX = 0, int offsetY = 0) var canvasCopy = canvas = new LineCanvas (); v.DrawContentComplete += (r) => { - foreach (var p in canvasCopy.GenerateImage (v.Bounds)) { - v.AddRune ( - offsetX + p.Key.X, - offsetY + p.Key.Y, - p.Value); - } - }; + foreach(var p in canvasCopy.GenerateImage(v.Bounds)) + { + v.AddRune( + offsetX + p.Key.X, + offsetY + p.Key.Y, + p.Value); + } + }; return v; } diff --git a/docfx/v2specs/View.md b/docfx/v2specs/View.md deleted file mode 100644 index cbe80699b0..0000000000 --- a/docfx/v2specs/View.md +++ /dev/null @@ -1,154 +0,0 @@ -# V2 Spec for View refactor - -IMPORTANT: I am critical of the existing codebase below. Do not take any of this personally. It is about the code, not the amazing people who wrote the code. - -ALSO IMPORTANT: I've written this to encourage and drive DEBATE. My style is to "Have strong opinions, weakly held." If you read something here you don't understand or don't agree with, SAY SO. Tell me why. Take a stand. - -This covers my thinking on how we will refactor `View` and the classes in the `View` heirarchy(inclidng `Responder`). It does not cover - * Text formatting which will be covered in another spec. - * TrueColor support which will be covered separately. - * ConsoleDriver refactor. - -## Terminal.Gui v2 View-related Lexicon & Taxonomy - - * *View* - The most basic visual element in Terminal.Gui. Implemented in the `View` base-class. - * *SubView* - A View that is contained in antoher view and will be rendered as part of the containing view's *ContentArea*. SubViews are added to another view via the `View.Add` method. A View may only be a SubView of a single View. - * *SuperView* - The View that a *SubView* was added to. - * *Child View* - A view that is held by another view in a parent/child relationshiop, but is NOT a SubView. Examples of this are sub-menus of `MenuBar`. - * *Parent View* - A view that holds a reference to another view in a parent/child relationship, but is NOT a SuperView of the child. - * *Thickness* - Describes how thick a rectangle is on each of the rectangle's four sides. Valid thickness values are >= 0. - * *Margin* - Means the Thickness that separtes a View from other SubViews of the same SUperView. - * *Title* - Means text that is displayed for the View that describes the View to users. For most Views the Title is displayed at the top-left, overlaying the Border. - * *Border* - Means the Thickness where a visual border (drawn using line-drawing glyphs) and the Title are drawn. The Border expands inward; in other words if `Border.Thickness.Top == 2` the border & title will take up the first row and the second row will be filled with spaces. - * *Adornments* - The Thickness between the Margin and Padding. The Adornments property of `View` is a `View`-subclass that hosts SubViews that are not part of the View's content and are rendered within the Adornment Thickness. Examples of Adornments: - * A `TitleBar` renders the View's `Title` and a horizontal line defining the top of the View. Adds thickness to the top of Adornments. - * One or more `LineView`s that render the View's border (NOTE: The magic of `LineCanvas` lets us automatically have the right joins for these and `TitleBar`!). - * A `Vertical Scrollbar` adds thickness to `Adornments.Right` (or `.Left` when right-to-left language support is added). - * A `Horizontal Scrollbar` adds thickness to `Adornments.Bottom` when enabled. - * A `MenuBar` adds thickness to `Adornments.Top` (NOTE: This is a change from v1 where `subview.Y = 1` is required). - * A `StatusBar` adds thickness ot `Adornments.Bottom` and is rendered at the bottom of Padding. - * NOTE: The use of `View.Add` in v1 to add adornments to Views is the cause of much code complexity. Changing the API such that `View.Add` is ONLY for subviews and adding a `View.Adornments.Add` API for menu, statusbar, scroll bar... will enable us to signficantly simplify the codebase. - * *Padding* - Means the Thickness inside of an element that offsets the `Content` from the Border. (NOTE: in v1 `Padding` is OUTSIDE of the `Border`). Padding is `{0, 0, 0, 0}` by default. - * *Frame* - Means the `Rect` that defines the location and size of the `View` including all of the margin, border, padding, and content area. The coordinates are relative to the SuperView of the View (or, in the case of `Application.Top`, `ConsoleDriver.Row == 0; ConsoleDriver.Col == 0`). The Frame's location and size are controlled by either `Absolute` or `Computed` positioning via the `.X`, `.Y`, `.Height`, and `.Width` properties of the View. - * *VisibleArea* - Means the area inside of the Margin + Border (Title) + Padding. `VisibleArea.Location` is always `{0, 0}`. `VisibleArea.Size` is the `View.Frame.Size` shrunk by Margin + Border + Padding. - * *ContentArea* - The `Rect` that describes the location and size of the View's content, relative to `VisibleRect`. If `ContentArea.Location` is negative, anything drawn there will be clipped and any subview positioned in the negative area will cause (optional) scrollbars to appear (making the Thickness of Padding thicker on the appropriate sides). If `ContentArea.Size` is changed such that the dimensions fall outside of `Frame.Size shrunk by Margin + Border + Padding`, drawning will be clipped and (optional) scrollbars will appear. - * *Bounds* - Synomous with *VisibleArea*. (Debate: Do we rename `Bounds` to `VisbleArea` in v2?) - * *ClipArea* - Means the currently vislble portion of the *Content*. This is defined as a`Rect` in coordinates relative to *ContentArea* (NOT *VisibleArea*) (e.g. `ClipArea {X = 0, Y = 0} == ContentArea {X = 0, Y = 0}`). This `Rect` is passed to `View.Redraw` (and should be named "clipArea" not "bounds"). It defines the clip-region the caller desires the `Redraw` implementation to clip itself to (see notes on clipping below). - * *Modal* - The term used when describing a View that was created using the `Application.Run(view)` or `Application.Run` APIs. When a View is running as a modal, user input is restricted to just that View until `Application.Run` exits. - * *TopLevel* - The term used to describe a view that is both Modal and can have a MenuBar and/or StatusBar. - * *Window* - A View that is - - - ### Questions - - * @bdisp - Why does `TopLevel.Activate/Deactivate` exist? Why is this just not `Focus`? - * @bdisp - is the "Mdi" concept, really about "non-modal views that have z-order and can overlap"? "Not Mdi" means "non-modal views that have the same-zorder and are tiled" - - - * `View.MouseEvent` etc... should always use `ContentBounds`-relative coordinates and should constrained by `GetClipRect`. - * -* After many fits and starts we have clipping working well. But the logic is convoluted and full of spaghetti code. We should simplfiy this by being super clear on how clipping works. - * `View.Redraw(clipRect)` specifies a `clipRect` that is outside of `View.GetClipRect` it has no impact (only a `clipRect` where `View.ClipRect.Union(clipRect)` makes the rect smaller does anything). - * Changing `Driver.ClipRect` from within a `Draw` implementation to draw outside of the `ContentBounds` should be disallowed (see non-ContentBounds drawing below). -* Border (margin, padding, frame, title, etc...) is confusing and incorrect. - * The only reason FrameView exists is because the original architecture didn't support offsetting `View.Bounds` - such that a border could be drawn and the interior content would clip correctly. Thus Miguel (or someone) built - FrameView with nested `ContentView` that was at `new Rect(+1, +1, -2, -2)`. - * `Border` was added later, but couldn't be retrofitted into `View` such that if `View.Border ~= null` just worked like `FrameView` - * Thus devs are forced to use the clunky `FrameView` instead of just setting `View.Border`. - * It's not possilbe for a `View` to utilize `Border.BorderBrush`. - * `Border` has a bunch of confusing concepts that don't match other systems (esp the Web/HTML) - * `Margin` on the web means the space between elements - `Border` doesn't have a margin property, but does has the confusing `DrawMarginFrame` property. - * `Border` on the web means the space where a border is drawn. The current implementaiton confuses the term `Frame` and `Border`. `BorderThickness` is provided. In the new world, - but because of the confusion between `Padding` and `Margin` it doesn't work as expectedc. - * `Padding` on the web means the padding inside of an element between the `Border` and `Content`. In the current implementation `Padding` is actually OUTSIDE of the `Border`. This means it's not possible for a view to offset internally by simply changing `Bounds`. - * `Content` on the web means the area inside of the Margin + Border + Padding. `View` does not currently have a concept of this (but `FrameView` and `Window` do via thier private `ContentView`s. - * `Border` has a `Title` property. If `View` had a standard `Title` property could this be simplified (or should views that implement their own Title property just leverage `Border.Title`?). - * It is not possilble for a class drived from View to orverride the drawing of the "Border" (frame, title, padding, etc...). Multiple devs have asked to be able to have the border frame to be drawn with a different color than `View.ColorScheme`. -* API should explicitly enable devs to override the drawing of `Border` independently of the `View.Draw` method. See how `WM_NCDRAW` works in wWindows (Draw non-client). It should be easy to do this from within a `View` sub-class (e.g. override `OnDrawBorder`) and externally (e.g. `DrawBorder += () => ...`. - -* `AutoSize` mostly works, but only because of heroic special-casing logic all over the place by @bdisp. This should be massively simplified. -* `FrameView` is superlufous and should be removed from the heirarchy (instead devs should just be able to manipulate `View.Border` to achieve what `FrameView` provides). The internal `FrameView.ContentView` is a bug-farm and un-needed if `View.Border` worked correctly. -* `TopLevel` is currently built around several concepts that are muddled: - * Views that host a Menu and StatusBar. It is not clear why this is and if it's needed as a concept. - * Views that can be run via `Application.Run`. It is not clear why ANY VIEW can't be run this way - * Views that can be used as a pop-up (modal) (e.g. `Dialog`). As proven by `Wizard`, it is possible to build a View that works well both ways. But it's way too hard to do this today. - * Views that can be moved by the user. - * If `View` class is REALLY required for enabling one more of the above concepts (e.g. `Window`) it should be as thin and simple as possilbe (e.g. should inherit from `FrameView` (or just use `View.Border` effecively assuming `FrameView` is nuked) instead of containing duplicate code). -* The `MdiContainer` stuff is complex, perhaps overly so. It's also mis-named because Terminal.Gui doesn't actually support "documents" nor does it have a full "MDI" system like Windows (did). It seems to represent features useful in overlapping Views, but it is super confusing on how this works, and the naming doesn't help. This all can be refactored to support specific scenarios and thus be simplified. -* There is no facility for users' resizing of Views. @tznind's awesome work on `LineCanvas` and `TileView` combined with @tig's experiments show it could be done in a great way for both modal (overlapping) and tiled Views. -* `DrawFrame` and `DrawTitle` are implemented in `ConsoleDriver` and can be replaced by a combination of `LineCanvas` and `Border`. -* Colors - - * As noted above each of Margin, Border, Padding, and Content should support independent colors. - * Many View sub-classes bastardize the exiting ColorSchemes to get look/feel that works (e.g. `TextView` and `Wizard`). Separately we should revamp ColorSchemes to enable more scenarios. -* `Responder` is supposed to be where all common, non-visual-related, code goes. We should ensure this is the case. -* `View` should have default support for scroll bars. e.g. assume in the new world `View.ContentBounds` is the clip area (defined by `VIew.Frame` minus `Margin` + `Border` + `Padding`) then if any view is added with `View.Add` that has Frame coordinates outside of `ContentBounds` the appropriate scroll bars show up automatgically (optioally of course). Without any code, scrolling just works. -* We have many requests to support non-full-screen apps. We need to ensure the `View` class heirachy suppports this in a simple, understandable way. In a world with non-full-screen (where screen is defined as the visible terminal view) apps, the idea that `Frame` is "screen relative" is broken. Although we COULD just define "screen" as "the area that bounds the Terminal.GUI app.". - * Question related to this: If `View.Border` works correctly (margin, border, padding, content) and if non-full-screen apps are supported, what happens if the margin of `Application.Top` is not zero (e.g. `Border.Margin = new Thickness(1,1)`). It feels more pure that such a margin would make the top-left corner of `Application.Top`'s border be att `ConsoleDriver.Row = 1, Column = 1`). If this is thw path, then "screen" means `Application.Top.Frame`). This is my preference. - -## Thoughts on Built-in Views -* `LineView` can be replaced by `LineCanvas`? -* `Button` and `Label` can be merged. -* `StatusBar` and `Menu` could be combined. If not, then at least made more consistent (e.g. in how hotkeys are specified). - -## Design - -* `Responder`("Responder base class implemented by objects that want to participate on keyboard and mouse input.") remains mostly unchanged, with minor changes: - * Methods that take `View` parametsrs (e.g. `OnEnter`) change to take `Responder` (bad OO design). - * Nuke `IsOverriden` (bad OO design) - * Move `View.Data` to `Responder` (primitive) - * Move `Command` and `KeyBinding` stuff from `View`. - * Move generic mouse and keyboard stuff from `View` (e.g. `WantMousePositionReports`) - - -## Example of creating Adornments -```cs -// ends up looking just like the v1 default Window with a menu & status bar -// and a vertical scrollbar. In v2 the Window class would do all of this automatically. -var top = new TitleBar() { - X = 0, Y = 0, - Width = Dim.Fill(), - Height = 1 - LineStyle = LineStyle.Single -}; -var left = new LineView() { - X = 0, Y = 0, - Width = 1, - Height = Dim.Fill(), - LineStyle = LineStyle.Single -}; -var right = new LineView() { - X = Pos.AnchorEnd(), Y = 0, - Width = 1, - Height = Dim.Fill(), - LineStyle = LineStyle.Single -}; -var bottom = new LineView() { - X = 0, Y = Pos.AnchorEnd(), - Width = Dim.Fill(), - Height = 1, - LineStyle = LineStyle.Single -}; - -var menu = new MenuBar() { - X = Pos.Right(left), Y = Pos.Bottom(top) -}; -var status = new StatusBar () { - X = Pos.Right(left), Y = Pos.Top(bottom) -}; -var vscroll = new ScrollBarView () { - X = Pos.Left(right), - Y = Dim.Fill(2) // for menu & status bar -}; - -Adornments.Add(titleBar); -Adornments.Add(left); -Adornments.Add(right); -Adornments.Add(bottom); -Adornments.Add(vscroll); - -var treeView = new TreeView () { - X = 0, Y = 0, Width = Dim.Fill(), Height = Dim.Fill() -}; -Add (treeView); -``` From ed17727eb1c47ea6d464ec96e981a0bfde0ff518 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 25 Feb 2023 16:09:00 +0000 Subject: [PATCH 07/11] Fix keyboard tab navigation problems --- Terminal.Gui/Views/TileView.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index d54c08583a..1fafb85096 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -173,7 +173,6 @@ public TileView () : this (2) /// public TileView (int tiles) { - CanFocus = true; RebuildForTileCount (tiles); IgnoreBorderPropertyOnRedraw = true; Border = new Border () { @@ -409,15 +408,6 @@ public bool SetSplitterPos (int idx, Pos value) return true; } - /// - public override bool OnEnter (View view) - { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - if (!Tiles.Where (t => t.ContentView.HasFocus).Any ()) { - Tiles.FirstOrDefault ()?.ContentView.SetFocus (); - } - return base.OnEnter (view); - } /// public override void Redraw (Rect bounds) From 7929084591c753b2841bd8ce2a38495c95ea54d0 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 25 Feb 2023 16:36:32 +0000 Subject: [PATCH 08/11] Workaround for changing CanFocus throwing Exceptions sometimes --- Terminal.Gui/Views/TileView.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 1fafb85096..093d105fb9 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -554,8 +554,11 @@ public override bool ProcessHotKey (KeyEvent keyEvent) if(keyEvent.Key == ToggleResizable) { foreach(var l in splitterLines) { + var iniBefore = l.IsInitialized; + l.IsInitialized = false; l.CanFocus = !l.CanFocus; - + l.IsInitialized = iniBefore; + if (l.CanFocus && !focusMoved) { l.SetFocus (); focusMoved = true; From 24470059873a079112150905c5e37f2cf68b71e6 Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 1 Mar 2023 08:40:56 +0000 Subject: [PATCH 09/11] Fix bad merges --- Terminal.Gui/Core/View.cs | 1 + UICatalog/Scenarios/TileViewExperiment.cs | 137 ++++++++++++---------- UnitTests/Views/TileViewTests.cs | 87 -------------- 3 files changed, 76 insertions(+), 149 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index ced07432ff..8d95f959e1 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1521,6 +1521,7 @@ public virtual void Redraw (Rect bounds) Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); } + var boundsAdjustedForBorder = Bounds; if (!IgnoreBorderPropertyOnRedraw && Border != null) { Border.DrawContent (this); boundsAdjustedForBorder = new Rect (bounds.X + 1, bounds.Y + 1, Math.Max (0, bounds.Width - 2), Math.Max(0, bounds.Height - 2)); diff --git a/UICatalog/Scenarios/TileViewExperiment.cs b/UICatalog/Scenarios/TileViewExperiment.cs index 0cef177bbd..6430ce9a66 100644 --- a/UICatalog/Scenarios/TileViewExperiment.cs +++ b/UICatalog/Scenarios/TileViewExperiment.cs @@ -27,16 +27,22 @@ public override void Setup () Application.Top.Add (menu); - var frame = new FrameView () { + var frame1 = new FrameView () { X = 0, Y = 1, - Width = Dim.Fill (), - Height = Dim.Fill (), - IgnoreBorderPropertyOnRedraw = true + Width = 15, //Dim.Fill (), + Height = 15, //Dim.Fill (), + //IgnoreBorderPropertyOnRedraw = true + }; - frame.Border.BorderStyle = BorderStyle.Double; + frame1.Border.BorderStyle = BorderStyle.Double; - Application.Top.Add (frame); + var frame2 = new FrameView () { + X = 0, + Y = Pos.Bottom (frame1) + 1, + Width = 15, //Dim.Fill (), + Height = 15, //Dim.Fill (), + //IgnoreBorderPropertyOnRedraw = true }; frame2.Border.BorderStyle = BorderStyle.Single; @@ -49,69 +55,76 @@ public override void Setup () var view1 = new TextField () { //Title = "View 1", Text = "View1 30%/50% Single", - X = -1, - Y = -1, - Width = Dim.Percent (30), - Height = Dim.Percent (50), + X = 0, + Y = 0, + Width = 14, //Dim.Percent (30) - 5, + Height = 14, //Dim.Percent (50) - 5, ColorScheme = Colors.ColorSchemes ["Dialog"], - Border = new Border () { BorderStyle = BorderStyle.Single } + Border = new Border () { + BorderStyle = BorderStyle.Single, + //BorderThickness = new Thickness (1), + DrawMarginFrame = true, + Padding = new Thickness (1), + BorderBrush = Color.BrightMagenta, + Title = "Border Title" + } }; frame1.Add (view1); //var view12splitter = new SplitterEventArgs - var view2 = new FrameView () { - Title = "View 2", - Text = "View2 right of view1, 30%/70% Single.", - X = Pos.Right (view1) - 1, - Y = -1, - Width = Dim.Percent (30), - Height = Dim.Percent (70), - ColorScheme = Colors.ColorSchemes ["Error"], - Border = new Border () { BorderStyle = BorderStyle.Single } - }; - - frame.Add (view2); - - var view3 = new FrameView () { - Title = "View 3", - Text = "View3 right of View2 Fill/Fill Single", - X = Pos.Right (view2) - 1, - Y = -1, - Width = Dim.Fill (-1), - Height = Dim.Fill (-1), - ColorScheme = Colors.ColorSchemes ["Menu"], - Border = new Border () { BorderStyle = BorderStyle.Single } - }; - - frame.Add (view3); - - var view4 = new FrameView () { - Title = "View 4", - Text = "View4 below View2 view2.Width/5 Single", - X = Pos.Left (view2), - Y = Pos.Bottom (view2) - 1, - Width = view2.Width, - Height = 5, - ColorScheme = Colors.ColorSchemes ["TopLevel"], - Border = new Border () { BorderStyle = BorderStyle.Single } - }; - - frame.Add (view4); - - var view5 = new FrameView () { - Title = "View 5", - Text = "View5 below View4 view4.Width/5 Double", - X = Pos.Left (view2), - Y = Pos.Bottom (view4) - 1, - Width = view4.Width, - Height = 5, - ColorScheme = Colors.ColorSchemes ["TopLevel"], - Border = new Border () { BorderStyle = BorderStyle.Double } - }; - - frame.Add (view5); + //var view2 = new FrameView () { + // Title = "View 2", + // Text = "View2 right of view1, 30%/70% Single.", + // X = Pos.Right (view1) - 1, + // Y = -1, + // Width = Dim.Percent (30), + // Height = Dim.Percent (70), + // ColorScheme = Colors.ColorSchemes ["Error"], + // Border = new Border () { BorderStyle = BorderStyle.Single } + //}; + + //frame.Add (view2); + + //var view3 = new FrameView () { + // Title = "View 3", + // Text = "View3 right of View2 Fill/Fill Single", + // X = Pos.Right (view2) - 1, + // Y = -1, + // Width = Dim.Fill (-1), + // Height = Dim.Fill (-1), + // ColorScheme = Colors.ColorSchemes ["Menu"], + // Border = new Border () { BorderStyle = BorderStyle.Single } + //}; + + //frame.Add (view3); + + //var view4 = new FrameView () { + // Title = "View 4", + // Text = "View4 below View2 view2.Width/5 Single", + // X = Pos.Left (view2), + // Y = Pos.Bottom (view2) - 1, + // Width = view2.Width, + // Height = 5, + // ColorScheme = Colors.ColorSchemes ["TopLevel"], + // Border = new Border () { BorderStyle = BorderStyle.Single } + //}; + + //frame.Add (view4); + + //var view5 = new FrameView () { + // Title = "View 5", + // Text = "View5 below View4 view4.Width/5 Double", + // X = Pos.Left (view2), + // Y = Pos.Bottom (view4) - 1, + // Width = view4.Width, + // Height = 5, + // ColorScheme = Colors.ColorSchemes ["TopLevel"], + // Border = new Border () { BorderStyle = BorderStyle.Double } + //}; + + //frame.Add (view5); } } } \ No newline at end of file diff --git a/UnitTests/Views/TileViewTests.cs b/UnitTests/Views/TileViewTests.cs index a3f76d3d1b..d3e97acec9 100644 --- a/UnitTests/Views/TileViewTests.cs +++ b/UnitTests/Views/TileViewTests.cs @@ -2211,93 +2211,6 @@ protected override void Dispose (bool disposing) } - [Fact,AutoInitShutdown] - public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRebuildForTileCount () - { - var 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 - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - Assert.Multiple ( - ()=>Assert.Equal (0, myReusableView.DisposalCount) - ,()=> { - tv.Dispose (); - Assert.Equal (1, myReusableView.DisposalCount); - }); - } - [Fact, AutoInitShutdown] - public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringInsertTile () - { - var 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 - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - Assert.Multiple ( - () => Assert.Equal (0, myReusableView.DisposalCount) - , () => { - tv.Dispose (); - - // TODO seems to be double disposed ?! - Assert.True (myReusableView.DisposalCount >= 1); - }); - } - [Theory, AutoInitShutdown] - [InlineData(0)] - [InlineData (1)] - public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRemoveTile(int idx) - { - var 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 - tv.Tiles.ElementAt (0).ContentView.Add (myReusableView); - Assert.Multiple ( - () => Assert.Equal (0, myReusableView.DisposalCount) - , () => { - tv.Dispose (); - - // TODO seems to be double disposed ?! - Assert.True (myReusableView.DisposalCount >= 1); - }); - } - - private class DisposeCounter : View - { - public int DisposalCount; - protected override void Dispose (bool disposing) - { - DisposalCount++; - base.Dispose (disposing); - } - - } - /// /// Creates a vertical orientation root container with left pane split into /// two (with horizontal splitter line). From b5b782e17a55e64f694305c11f49ae0771e0069a Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 1 Mar 2023 08:43:46 +0000 Subject: [PATCH 10/11] Fix bad merging --- Terminal.Gui/Core/View.cs | 4 ++-- Terminal.Gui/Views/FrameView.cs | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 8d95f959e1..2b85cfbc56 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1548,7 +1548,7 @@ public virtual void Redraw (Rect bounds) } // Invoke DrawContentEvent - OnDrawContent (bounds); + OnDrawContent (boundsAdjustedForBorder); if (subviews != null) { foreach (var view in subviews) { @@ -1574,7 +1574,7 @@ public virtual void Redraw (Rect bounds) } // Invoke DrawContentCompleteEvent - OnDrawContentComplete (bounds); + OnDrawContentComplete (boundsAdjustedForBorder); ClearLayoutNeeded (); ClearNeedsDisplay (); diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index 80c992a289..f6809d109e 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -297,25 +297,26 @@ public override void Redraw (Rect bounds) lc.AddLine (new Point (bounds.Width - 1, bounds.Height - 1), -bounds.Height + 1, Orientation.Vertical, Border.BorderStyle); } - foreach (var subview in contentView.Subviews) { - lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Width - 1, Orientation.Horizontal, subview.Border.BorderStyle); - lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Height - 1, Orientation.Vertical, subview.Border.BorderStyle); + //foreach (var subview in contentView.Subviews) { + // lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Width - 1, Orientation.Horizontal, subview.Border.BorderStyle); + // lc.AddLine (new Point (subview.Frame.X + 1, subview.Frame.Y + 1), subview.Frame.Height - 1, Orientation.Vertical, subview.Border.BorderStyle); - lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Width + 1, Orientation.Horizontal, subview.Border.BorderStyle); - lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Height + 1, Orientation.Vertical, subview.Border.BorderStyle); + // lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Width + 1, Orientation.Horizontal, subview.Border.BorderStyle); + // lc.AddLine (new Point (subview.Frame.X + subview.Frame.Width, subview.Frame.Y + subview.Frame.Height), -subview.Frame.Height + 1, Orientation.Vertical, subview.Border.BorderStyle); - } + //} Driver.SetAttribute (ColorScheme.Normal); foreach (var p in lc.GenerateImage (bounds)) { this.AddRune (p.Key.X, p.Key.Y, p.Value); } - + // Redraw the lines so that focus/drag symbol renders foreach (var subview in contentView.Subviews) { // line.DrawSplitterSymbol (); } + // Draw Titles over Border foreach (var subview in contentView.Subviews) { // TODO: Use reflection to see if subview has a Title property From cb66c9fc007b9c3a21f7e432b1bc521c2944003d Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 1 Mar 2023 08:53:17 +0000 Subject: [PATCH 11/11] Fix disposing in TileViewNesting scenario --- UICatalog/Scenarios/TileViewNesting.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UICatalog/Scenarios/TileViewNesting.cs b/UICatalog/Scenarios/TileViewNesting.cs index 795de65e67..c14a69c5d8 100644 --- a/UICatalog/Scenarios/TileViewNesting.cs +++ b/UICatalog/Scenarios/TileViewNesting.cs @@ -94,6 +94,9 @@ private void SetupTileView () bool? border = cbBorder.Checked; bool? startHorizontal = cbHorizontal.Checked; + foreach(var sub in workArea.Subviews) { + sub.Dispose (); + } workArea.RemoveAll (); if (numberOfViews <= 0) {