diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index d54043d8a..81b2e9491 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -238,22 +238,12 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt List groupIds = includedSubviews.Select ( v => { - if (dimension == Dimension.Width) - { - if (v.X.Has (out Pos posAlign)) - { - return ((PosAlign)posAlign).GroupId; - } - } - else - { - if (v.Y.Has (out Pos posAlign)) - { - return ((PosAlign)posAlign).GroupId; - } - } - - return -1; + return dimension switch + { + Dimension.Width when v.X.Has (out PosAlign posAlign) => ((PosAlign)posAlign).GroupId, + Dimension.Height when v.Y.Has (out PosAlign posAlign) => ((PosAlign)posAlign).GroupId, + _ => -1 + }; }) .Distinct () .ToList (); @@ -261,18 +251,7 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt foreach (int groupId in groupIds.Where (g => g != -1)) { // PERF: If this proves a perf issue, consider caching a ref to this list in each item - List posAlignsInGroup = includedSubviews.Where ( - v => - { - return dimension switch - { - Dimension.Width when v.X is PosAlign alignX => alignX.GroupId - == groupId, - Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId - == groupId, - _ => false - }; - }) + List posAlignsInGroup = includedSubviews.Where (v => PosAlign.HasGroupId (v, dimension, groupId)) .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) .ToList (); @@ -350,16 +329,9 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt // BUGBUG: The order may not be correct. May need to call TopologicalSort? // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL). - if (dimension == Dimension.Width) - { - v.SetRelativeLayout (new (maxCalculatedSize, 0)); - } - else - { - v.SetRelativeLayout (new (0, maxCalculatedSize)); - } - - int maxPosView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + int maxPosView = dimension == Dimension.Width + ? v.Frame.X + v.Width!.Calculate (0, maxCalculatedSize, v, dimension) + : v.Frame.Y + v.Height!.Calculate (0, maxCalculatedSize, v, dimension); if (maxPosView > maxCalculatedSize) { @@ -391,16 +363,9 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt // BUGBUG: The order may not be correct. May need to call TopologicalSort? // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL). - if (dimension == Dimension.Width) - { - v.SetRelativeLayout (new (maxCalculatedSize, 0)); - } - else - { - v.SetRelativeLayout (new (0, maxCalculatedSize)); - } - - int maxDimView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + int maxDimView = dimension == Dimension.Width + ? v.Frame.X + v.Width!.Calculate (0, maxCalculatedSize, v, dimension) + : v.Frame.Y + v.Height!.Calculate (0, maxCalculatedSize, v, dimension); if (maxDimView > maxCalculatedSize) { @@ -428,16 +393,9 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt { View v = dimAutoSubViews [i]; - if (dimension == Dimension.Width) - { - v.SetRelativeLayout (new (maxCalculatedSize, 0)); - } - else - { - v.SetRelativeLayout (new (0, maxCalculatedSize)); - } - - int maxDimAuto= dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + int maxDimAuto = dimension == Dimension.Width + ? v.Frame.X + v.Width!.Calculate (0, maxCalculatedSize, v, dimension) + : v.Frame.Y + v.Height!.Calculate (0, maxCalculatedSize, v, dimension); if (maxDimAuto > maxCalculatedSize) { diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index 4691b88bf..9fbcedb1f 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -335,9 +335,9 @@ public abstract record Pos /// /// A reference to this instance. /// - public bool Has (out Pos pos) where T : Pos + public bool Has (out T pos) where T : Pos { - pos = this; + pos = (this as T)!; return this switch { diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs index 9e853ad3f..f54a48c79 100644 --- a/Terminal.Gui/View/Layout/PosAlign.cs +++ b/Terminal.Gui/View/Layout/PosAlign.cs @@ -63,17 +63,7 @@ public record PosAlign : Pos List dimensionsList = new (); // PERF: If this proves a perf issue, consider caching a ref to this list in each item - List viewsInGroup = views.Where ( - v => - { - return dimension switch - { - Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, - Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, - _ => false - }; - }) - .ToList (); + List viewsInGroup = views.Where (v => HasGroupId (v, dimension, groupId)).ToList (); if (viewsInGroup.Count == 0) { @@ -99,6 +89,16 @@ public record PosAlign : Pos return dimensionsList.Sum (); } + internal static bool HasGroupId (View v, Dimension dimension, int groupId) + { + return dimension switch + { + Dimension.Width when v.X.Has (out PosAlign pos) => pos.GroupId == groupId, + Dimension.Height when v.Y.Has (out PosAlign pos) => pos.GroupId == groupId, + _ => false + }; + } + /// /// Gets the identifier of a set of views that should be aligned together. When only a single /// set of views in a SuperView is aligned, setting is not needed because it defaults to 0. @@ -115,12 +115,18 @@ public record PosAlign : Pos return _cachedLocation.Value; } + IList? groupViews; if (us.SuperView is null) { - return 0; + groupViews = new List (); + groupViews.Add (us); + } + else + { + groupViews = us.SuperView!.Subviews; } - AlignAndUpdateGroup (GroupId, us.SuperView.Subviews, dimension, superviewDimension); + AlignAndUpdateGroup (GroupId, groupViews, dimension, superviewDimension); if (_cachedLocation.HasValue) { @@ -145,31 +151,9 @@ public record PosAlign : Pos List dimensionsList = new (); // PERF: If this proves a perf issue, consider caching a ref to this list in each item - List posAligns = views.Select ( - v => - { - switch (dimension) - { - case Dimension.Width when v.X.Has (out Pos pos): - - if (pos is PosAlign posAlignX && posAlignX.GroupId == groupId) - { - return posAlignX; - } - - break; - case Dimension.Height when v.Y.Has (out Pos pos): - if (pos is PosAlign posAlignY && posAlignY.GroupId == groupId) - { - return posAlignY; - } - - break; - } - - return null; - }) - .ToList (); + List posAligns = views.Where (v => PosAlign.HasGroupId (v, dimension, groupId)) + .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) + .ToList (); // PERF: We iterate over viewsInGroup multiple times here. @@ -185,7 +169,9 @@ public record PosAlign : Pos firstInGroup = posAligns [index]!.Aligner; } - dimensionsList.Add (dimension == Dimension.Width ? views [index].Frame.Width : views [index].Frame.Height); + dimensionsList.Add (dimension == Dimension.Width + ? views [index].Width!.Calculate(0, size, views [index], dimension) + : views [index].Height!.Calculate (0, size, views [index], dimension)); } } diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index 4dec98c06..a2f9d5bb1 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -348,7 +348,7 @@ public partial class View // Layout APIs SetLayoutNeeded (); - if (IsAbsoluteLayout()) + if (IsAbsoluteLayout ()) { // Implicit layout is ok here because all Pos/Dim are Absolute values. Layout (); @@ -546,13 +546,24 @@ public partial class View // Layout APIs /// If the view could not be laid out (typically because a dependencies was not ready). public bool Layout (Size contentSize) { + int bailAfter = 100; // Note, SetRelativeLayout calls SetTextFormatterSize - if (SetRelativeLayout (contentSize)) - { - LayoutSubviews (); + //while (NeedsLayout) + //{ + if (SetRelativeLayout (contentSize)) + { + LayoutSubviews (); - return true; - } + Debug.Assert(!NeedsLayout); + return true; + } + + // if (--bailAfter == 0) + // { + // Debug.Write ($"Layout: After {100} tries, SetRelativeLayout was unable to complete."); + // return false; + // } + //} return false; } diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 6f86ee4ad..3deaebd2f 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -137,10 +137,6 @@ public class Shortcut : View, IOrientation, IDesignable DimAutoStyle.Content, Dim.Func (() => { - if (Subviews [0].NeedsLayout) - { - // throw new Exception (); - } return PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width); }), Dim.Func (() => diff --git a/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs index 0e7da8559..64adad09f 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs @@ -45,6 +45,47 @@ public partial class DimAutoTests #endregion PosAbsolute + #region PosAlign + + //[Theory] + //[InlineData (0, 0, 0, 0, 0, 0)] + //[InlineData (0, 19, 0, 9, 19, 9)] + //[InlineData (0, 20, 0, 10, 20, 10)] + //[InlineData (0, 21, 0, 11, 20, 10)] + //[InlineData (1, 21, 1, 11, 20, 10)] + //[InlineData (21, 21, 11, 11, 21, 11)] + //public void With_Subview_Using_PosAlign (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) + //{ + // var view = new View + // { + // Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + // Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) + // }; + + // var subview = new View + // { + // X = Pos.Align (Alignment.Center), + // Y = Pos.Absolute (5), + // Width = 20, + // Height = 10 + // }; + // view.Add (subview); + + // // Assuming the calculation is done after layout + // int calculatedX = view.X.Calculate (100, view.Width, view, Dimension.Width); + // int calculatedY = view.Y.Calculate (100, view.Height, view, Dimension.Height); + // int calculatedWidth = view.Width.Calculate (0, 100, view, Dimension.Width); + // int calculatedHeight = view.Height.Calculate (0, 100, view, Dimension.Height); + + // Assert.Equal (expectedWidth, calculatedWidth); + // Assert.Equal (expectedHeight, calculatedHeight); + + // Assert.Equal (0, calculatedX); + // Assert.Equal (0, calculatedY); + //} + + #endregion PosAlign + #region PosPercent [Theory] diff --git a/UnitTests/View/Layout/Pos.AlignTests.cs b/UnitTests/View/Layout/Pos.AlignTests.cs index d5f70842a..dfc670d18 100644 --- a/UnitTests/View/Layout/Pos.AlignTests.cs +++ b/UnitTests/View/Layout/Pos.AlignTests.cs @@ -106,6 +106,25 @@ public class PosAlignTests Assert.IsType (pos); } + [Fact] + public void PosAlign_Laysout () + { + var view = new View () + { + Id = "view", + X = Pos.Align (Alignment.Center), + Width = 1, + Height = 1 + }; + view.Layout (new (10, 10)); + + Assert.Equal (4, view.Frame.X); + } + + // TODO: Test scenarios where views with matching GroupId's are added/removed from a Superview + + // TODO: Make AlignAndUpdateGroup internal and write low-level unit tests for it + [Fact] public void PosAlign_Set_View_X () { @@ -127,6 +146,18 @@ public class PosAlignTests view.X = posAlign; superView.Layout (); Assert.Equal (4, view.Frame.X); + superView.Remove (view); + + view = new View () + { + Id = "view", + X = posAlign, + Width = 1, + Height = 1 + }; + superView.Add (view); + superView.Layout (); + Assert.Equal (4, view.Frame.X); posAlign = Pos.Align (Alignment.End); view.X = posAlign; diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index 160e41be3..0458526cc 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -21,9 +21,9 @@ public class ShortcutTests { var shortcut = new Shortcut (); shortcut.BeginInit(); - shortcut.EndInit(); - + shortcut.EndInit (); shortcut.Layout (); + Assert.Equal (2, shortcut.Frame.Width); Assert.Equal (1, shortcut.Frame.Height); Assert.Equal (2, shortcut.Viewport.Width); @@ -45,7 +45,9 @@ public class ShortcutTests Key = Key.A, HelpText = "0" }; - shortcut.SetRelativeLayout (new (100, 100)); + shortcut.BeginInit (); + shortcut.EndInit (); + shortcut.Layout (); Assert.Equal (8, shortcut.Frame.Width); Assert.Equal (1, shortcut.Frame.Height); Assert.Equal (8, shortcut.Viewport.Width); @@ -68,7 +70,9 @@ public class ShortcutTests Key = Key.A, HelpText = "0" }; - shortcut.SetRelativeLayout (new (100, 100)); + shortcut.BeginInit (); + shortcut.EndInit (); + shortcut.Layout (); Assert.Equal (9, shortcut.Frame.Width); Assert.Equal (1, shortcut.Frame.Height); Assert.Equal (9, shortcut.Viewport.Width);