From b64db9b7a2f89d20d56f6ae3664fe1a460f0f874 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 11 Jul 2024 16:48:58 -0600 Subject: [PATCH 01/96] Fixed gitversion --- .github/workflows/api-docs.yml | 12 ++++++------ GitVersion.yml | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index b2c998fada..d139fe63b6 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -2,7 +2,7 @@ name: Build and publish API docs on: push: - branches: [main, v2_develop] + branches: [v1_release, v2_develop] permissions: id-token: write @@ -17,11 +17,11 @@ jobs: runs-on: windows-latest steps: - name: Checkout - if: github.ref_name == 'main' || github.ref_name == 'develop' + if: github.ref_name == 'v1_release' || github.ref_name == 'v1_develop' uses: actions/checkout@v4 - name: DocFX Build - if: github.ref_name == 'main' || github.ref_name == 'develop' + if: github.ref_name == 'v1_release' || github.ref_name == 'v1_develop' working-directory: docfx run: | dotnet tool install -g docfx @@ -31,17 +31,17 @@ jobs: continue-on-error: false - name: Setup Pages - if: github.ref_name == 'main' || github.ref_name == 'develop' + if: github.ref_name == 'v1_release' || github.ref_name == 'v1_develop' uses: actions/configure-pages@v4 - name: Upload artifact - if: github.ref_name == 'main' || github.ref_name == 'develop' + if: github.ref_name == 'v1_release' || github.ref_name == 'v1_develop' uses: actions/upload-pages-artifact@v3 with: path: docfx/_site - name: Deploy to GitHub Pages - if: github.ref_name == 'main' || github.ref_name == 'develop' + if: github.ref_name == 'v1_release' || github.ref_name == 'v1_develop' id: deployment uses: actions/deploy-pages@v4 with: diff --git a/GitVersion.yml b/GitVersion.yml index ba888b3d58..3a183efa29 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -2,12 +2,12 @@ mode: ContinuousDeployment tag-prefix: '[vV]' continuous-delivery-fallback-tag: dev branches: - develop: + v1_develop: mode: ContinuousDeployment tag: dev - regex: develop + regex: v1_develop source-branches: - - main + - v1_release pre-release-weight: 100 v2_develop: @@ -32,9 +32,8 @@ branches: tag-number-pattern: '[/-](?\d+)' regex: ^(pull|pull\-requests|pr)[/-] source-branches: - - develop - - main - - release + - v1_develop + - v1_release - v2_develop - v2_release - feature From bebf7c6411d21a1bf5d1a2e0c9402a549c6f258d Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 11 Jul 2024 16:51:22 -0600 Subject: [PATCH 02/96] Fixed publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bc4a691968..0182a49de9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,7 +2,7 @@ name: Publish Terminal.Gui on: push: - branches: [ main, develop, v2_release, v2_develop ] + branches: [ v1_release, v1_develop, v2_release, v2_develop ] tags: - v* paths-ignore: From 725855d037998327cad254ef3524a80493b4232c Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 14 Jul 2024 11:32:13 -0600 Subject: [PATCH 03/96] Removed accidental stuff --- UnitTests/View/Layout/Dim.AutoTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index 11b1af70ef..6ad50ed164 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -1325,4 +1325,3 @@ public void DimAutoStyle_Content_UsesContentSize_If_No_Subviews () // Test variations of Frame } -git \ No newline at end of file From 78ab7ecec1cf089dec9011c4a2be6ac72901303b Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 14 Jul 2024 17:58:17 -0600 Subject: [PATCH 04/96] Progress. WIP --- Terminal.Gui/View/Layout/Dim.cs | 28 +- Terminal.Gui/View/Layout/DimAuto.cs | 598 ++++++++++++++++++------- Terminal.Gui/View/Layout/DimCombine.cs | 6 +- Terminal.Gui/View/Layout/ViewLayout.cs | 197 ++++---- Terminal.Gui/Views/Dialog.cs | 9 +- Terminal.Gui/Views/MessageBox.cs | 62 +-- UICatalog/Scenarios/DimAutoDemo.cs | 48 +- UICatalog/UICatalog.cs | 13 +- 8 files changed, 653 insertions(+), 308 deletions(-) diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index e765414cb9..a0ad0deb95 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -175,6 +175,30 @@ public abstract class Dim #endregion static Dim creation methods + /// + /// Indicates whether the specified type is in the hierarchy of this Dim object. + /// + /// + /// + /// + public bool Has (Type type, out Dim dim) + { + dim = this; + if (type == GetType ()) + { + return true; + } + + // If we are a PosCombine, we have to check the left and right + // to see if they are of the type we are looking for. + if (this is DimCombine { } combine && (combine.Left.Has (type, out dim) || combine.Right.Has (type, out dim))) + { + return true; + } + + return false; + } + #region virtual methods /// @@ -224,7 +248,7 @@ internal virtual int Calculate (int location, int superviewContentSize, View us, /// The first to add. /// The second to add. /// The that is the sum of the values of left and right. - public static Dim operator + (Dim? left, Dim? right) + public static Dim operator + (Dim left, Dim right) { if (left is DimAbsolute && right is DimAbsolute) { @@ -249,7 +273,7 @@ internal virtual int Calculate (int location, int superviewContentSize, View us, /// The to subtract from (the minuend). /// The to subtract (the subtrahend). /// The that is the left minus right. - public static Dim operator - (Dim? left, Dim? right) + public static Dim operator - (Dim left, Dim right) { if (left is DimAbsolute && right is DimAbsolute) { diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 5d34a97295..9ea540cd8a 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -59,242 +59,507 @@ public required DimAutoStyle Style internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { var textSize = 0; - var subviewsSize = 0; + var maxCalculatedSize = 0; int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0; - int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? int.MaxValue; + int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? (dimension == Dimension.Width ? Application.Driver.Screen.Width : Application.Driver.Screen.Height); if (Style.FastHasFlags (DimAutoStyle.Text)) { - textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height); + if (dimension == Dimension.Width) + { + //us.TextFormatter.Size = us.GetContentSize (); + textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Width); + } + else + { + us.TextFormatter.Size = us.GetContentSize () with { Height = Application.Driver.Screen.Height }; + textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Height); + } } if (Style.FastHasFlags (DimAutoStyle.Content)) { if (!us.ContentSizeTracksViewport) { - // ContentSize was explicitly set. Ignore subviews. - subviewsSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height; + // ContentSize was explicitly set. Use `us.ContentSize` to determine size. + maxCalculatedSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height; } else { - // ContentSize was NOT explicitly set. Use subviews to determine size. - - // TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451). - subviewsSize = 0; - - List includedSubviews = us.Subviews.ToList ();//.Where (v => !v.ExcludeFromLayout).ToList (); - List subviews; - - #region Not Anchored and Are Not Dependent - // Start with subviews that are not anchored to the end, aligned, or dependent on content size - // [x] PosAnchorEnd - // [x] PosAlign - // [ ] PosCenter - // [ ] PosPercent - // [ ] PosView - // [ ] PosFunc - // [x] DimFill - // [ ] DimPercent - // [ ] DimFunc - // [ ] DimView + maxCalculatedSize = textSize; + // ContentSize was NOT explicitly set. Use `us.Subviews` to determine size. + + List includedSubviews = us.Subviews.ToList (); + + // If [x] it can cause `us.ContentSize` to change. + // If [ ] it doesn't need special processing for us to determine `us.ContentSize`. + + // -------------------- Pos types that are dependent on `us.Subviews` + // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` + // [x] PosView - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize` + // [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize` + + // -------------------- Pos types that are dependent on `us.ContentSize` + // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` + // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` - it can cause a change in `us.ContentSize` + // [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame` + // [ ] PosPercent - Position is dependent `us.ContentSize` + // [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize` + + // -------------------- Pos types that are not dependent on either `us.Subviews` or `us.ContentSize` + // [ ] PosAbsolute - Position is fixed. + // [ ] PosFunc - Position is internally calculated. + + // -------------------- Dim types that are dependent on `us.Subviews` + // [x] DimView - Dimension is dependent on `subview.Target` + // [x] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` - it can cause a change in `us.ContentSize` + + // -------------------- Dim types that are dependent on `us.ContentSize` + // [ ] DimFill - Dimension is dependent on `us.ContentSize` + // [ ] DimPercent - Dimension is dependent on `us.ContentSize` + // [ ] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` + + // -------------------- Dim types that are not dependent on either `us.Subviews` or `us.ContentSize` + // [ ] DimAuto - Dimension is internally calculated + // [ ] DimAbsolute - Dimension is fixed + // [ ] DimFunc - Dimension is internally calculated + + // ====================================================== + // Do the easy stuff first - subviews whose position and size are not dependent on other views or content size + // ====================================================== + // [ ] PosAbsolute - Position is fixed. + // [ ] PosFunc - Position is internally calculated + // [ ] DimAuto - Dimension is internally calculated + // [ ] DimAbsolute - Dimension is fixed + // [ ] DimFunc - Dimension is internally calculated + List notDependentSubViews; if (dimension == Dimension.Width) { - subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd - && v.X is not PosAlign - // && v.X is not PosCenter - && v.Width is not DimAuto - && v.Width is not DimFill).ToList (); + notDependentSubViews = includedSubviews.Where (v => v.Width is { } && + (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) && + !v.X.Has (typeof (PosAnchorEnd), out _) && + !v.X.Has (typeof (PosAlign), out _) && + !v.X.Has (typeof (PosView), out _) && + !v.Width.Has (typeof (DimView), out _) && + !v.X.Has (typeof (PosCenter), out _)).ToList (); } else { - subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd - && v.Y is not PosAlign - // && v.Y is not PosCenter - && v.Height is not DimAuto - && v.Height is not DimFill).ToList (); + notDependentSubViews = includedSubviews.Where (v => v.Height is { } && + (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) && + !v.Y.Has (typeof (PosAnchorEnd), out _) && + !v.Y.Has (typeof (PosAlign), out _) && + !v.Y.Has (typeof (PosView), out _) && + !v.Height.Has (typeof (DimView), out _) && + !v.Y.Has (typeof (PosCenter), out _)).ToList (); } - for (var i = 0; i < subviews.Count; i++) + for (var i = 0; i < notDependentSubViews.Count; i++) { - View v = subviews [i]; + View v = notDependentSubViews [i]; - int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + int size = dimension == Dimension.Width ? v.X.GetAnchor (maxCalculatedSize) + v.Width.GetAnchor (maxCalculatedSize) : v.Y.GetAnchor (maxCalculatedSize) + v.Height.GetAnchor (maxCalculatedSize); - if (size > subviewsSize) + if (size > maxCalculatedSize) { - // BUGBUG: Should we break here? Or choose min/max? - subviewsSize = size; + maxCalculatedSize = size; } } - #endregion Not Anchored and Are Not Dependent - #region Anchored - // Now, handle subviews that are anchored to the end - // [x] PosAnchorEnd + // ************** We now have some idea of `us.ContentSize` *************** + + + // [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame` + List centeredSubViews; if (dimension == Dimension.Width) { - subviews = includedSubviews.Where (v => v.X is PosAnchorEnd).ToList (); + centeredSubViews = us.Subviews.Where (v => v.X.Has (typeof (PosCenter), out _)).ToList (); } else { - subviews = includedSubviews.Where (v => v.Y is PosAnchorEnd).ToList (); + centeredSubViews = us.Subviews.Where (v => v.Y.Has (typeof (PosCenter), out _)).ToList (); } - int maxAnchorEnd = 0; - for (var i = 0; i < subviews.Count; i++) + for (var i = 0; i < centeredSubViews.Count; i++) { - View v = subviews [i]; - maxAnchorEnd = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; - } - - subviewsSize += maxAnchorEnd; - #endregion Anchored + View v = centeredSubViews [i]; - //#region Aligned + int maxCentered = dimension == Dimension.Width ?/* v.X.GetAnchor (maxCalculatedSize) +*/ v.Width.GetAnchor (maxCalculatedSize) : /*v.Y.GetAnchor (maxCalculatedSize) + */v.Height.GetAnchor(maxCalculatedSize); - //// Now, handle subviews that are anchored to the end - //// [x] PosAnchorEnd - //int maxAlign = 0; - //if (dimension == Dimension.Width) - //{ - // // Use Linq to get a list of distinct GroupIds from the subviews - // List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); + if (maxCentered > maxCalculatedSize) + { + maxCalculatedSize = maxCentered; + } + } - // foreach (var groupId in groupIds) - // { - // List dimensionsList = new (); - // // 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 - // }; - // }) - // .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) - // .ToList (); - - // if (posAlignsInGroup.Count == 0) - // { - // continue; - // } + #region Anchored + // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` + List anchoredSubViews; + if (dimension == Dimension.Width) + { + anchoredSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosAnchorEnd), out _)).ToList (); + } + else + { + anchoredSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosAnchorEnd), out _)).ToList (); + } - // maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension); - // } - //} - //else - //{ - // subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); - //} + int maxAnchorEnd = 0; + for (var i = 0; i < anchoredSubViews.Count; i++) + { + View v = anchoredSubViews [i]; - //subviewsSize = int.Max (subviewsSize, maxAlign); - //#endregion Aligned + // Need to set the relative layout for PosAnchorEnd subviews to calculate the size + if (dimension == Dimension.Width) + { + v.SetRelativeLayout (new Size (maxCalculatedSize, 0)); + } + else + { + v.SetRelativeLayout (new Size (0, maxCalculatedSize)); + } + maxAnchorEnd = dimension == Dimension.Width ? v.X.GetAnchor (maxCalculatedSize) + v.Frame.Width : v.Y.GetAnchor (maxCalculatedSize) + v.Frame.Height; + } + maxCalculatedSize = Math.Max (maxCalculatedSize, maxAnchorEnd); + #endregion Anchored - #region Auto + #region PosView + // [x] PosView - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize` + List posViewSubViews; if (dimension == Dimension.Width) { - subviews = includedSubviews.Where (v => v.Width is DimAuto).ToList (); + posViewSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosView), out _)).ToList (); } else { - subviews = includedSubviews.Where (v => v.Height is DimAuto).ToList (); + posViewSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosView), out _)).ToList (); } - int maxAuto = 0; - for (var i = 0; i < subviews.Count; i++) + for (var i = 0; i < posViewSubViews.Count; i++) { - View v = subviews [i]; - - //if (dimension == Dimension.Width) - //{ - // v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0)); - //} - //else - //{ - // v.SetRelativeLayout (new Size (0, autoMax - subviewsSize)); - //} - maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; - - if (maxAuto > subviewsSize) + View v = posViewSubViews [i]; + + // BUGBUG: The order may not be correct. May need to call TopologicalSort? + if (dimension == Dimension.Width) { - // BUGBUG: Should we break here? Or choose min/max? - subviewsSize = maxAuto; + v.SetRelativeLayout (new Size (maxCalculatedSize, 0)); + } + else + { + v.SetRelativeLayout (new Size (0, maxCalculatedSize)); + } + int maxPosView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + + if (maxPosView > maxCalculatedSize) + { + maxCalculatedSize = maxPosView; } } + #endregion PosView + + // [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize` -// subviewsSize += maxAuto; - - #endregion Auto - - //#region Center - //// Now, handle subviews that are Centered - //if (dimension == Dimension.Width) - //{ - // subviews = us.Subviews.Where (v => v.X is PosCenter).ToList (); - //} - //else - //{ - // subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList (); - //} - - //int maxCenter = 0; - //for (var i = 0; i < subviews.Count; i++) - //{ - // View v = subviews [i]; - // maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; - //} - - //subviewsSize += maxCenter; - //#endregion Center - - #region Are Dependent - // Now, go back to those that are dependent on content size - // [x] DimFill - // [ ] DimPercent + #region DimView + // [x] DimView - Dimension is dependent on `subview.Target` - it can cause a change in `us.ContentSize` + List dimViewSubViews; if (dimension == Dimension.Width) { - subviews = includedSubviews.Where (v => v.Width is DimFill).ToList (); + dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (typeof (DimView), out _)).ToList (); } else { - subviews = includedSubviews.Where (v => v.Height is DimFill).ToList (); + dimViewSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (typeof (DimView), out _)).ToList (); } - int maxFill = 0; - for (var i = 0; i < subviews.Count; i++) + for (var i = 0; i < dimViewSubViews.Count; i++) { - View v = subviews [i]; + View v = dimViewSubViews [i]; - if (autoMax == int.MaxValue) - { - autoMax = superviewContentSize; - } + // BUGBUG: The order may not be correct. May need to call TopologicalSort? if (dimension == Dimension.Width) { - v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0)); + v.SetRelativeLayout (new Size (maxCalculatedSize, 0)); } else { - v.SetRelativeLayout (new Size (0, autoMax - subviewsSize)); + v.SetRelativeLayout (new Size (0, maxCalculatedSize)); + } + + int maxDimView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + + if (maxDimView > maxCalculatedSize) + { + maxCalculatedSize = maxDimView; } - maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; } + #endregion DimView + + + // [x] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` - it can cause a change in `us.ContentSize` + + + + + + + + // // ====================================================== + // // Now do PosAlign - It's dependent on other views with `GroupId` AND `us.ContentSize` + // // ====================================================== + // // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` + // #region Aligned + + // int maxAlign = 0; + // if (dimension == Dimension.Width) + // { + // // Use Linq to get a list of distinct GroupIds from the subviews + // List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); + + // foreach (var groupId in groupIds) + // { + // List dimensionsList = new (); + + // // 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 + // }; + // }) + // .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) + // .ToList (); + + // if (posAlignsInGroup.Count == 0) + // { + // continue; + // } + // // BUGBUG: ignores adornments + + // maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension); + // } + // } + // else + // { + + // // BUGBUG: Incompletge + // subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); + // } + + // maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign); + // #endregion Aligned + + // // TODO: This whole body of code is a WIP (forhttps://github.com/gui-cs/Terminal.Gui/issues/3499). + + + // List subviews; + + // #region Not Anchored and Are Not Dependent + // // Start with subviews that are not anchored to the end, aligned, or dependent on content size + // // [x] PosAnchorEnd + // // [x] PosAlign + // // [ ] PosCenter + // // [ ] PosPercent + // // [ ] PosView + // // [ ] PosFunc + // // [x] DimFill + // // [ ] DimPercent + // // [ ] DimFunc + // // [ ] DimView + // if (dimension == Dimension.Width) + // { + // subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd + // && v.X is not PosAlign + // // && v.X is not PosCenter + // && v.Width is not DimAuto + // && v.Width is not DimFill).ToList (); + // } + // else + // { + // subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd + // && v.Y is not PosAlign + // // && v.Y is not PosCenter + // && v.Height is not DimAuto + // && v.Height is not DimFill).ToList (); + // } + + // for (var i = 0; i < subviews.Count; i++) + // { + // View v = subviews [i]; + + // int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + + // if (size > maxCalculatedSize) + // { + // // BUGBUG: Should we break here? Or choose min/max? + // maxCalculatedSize = size; + // } + // } + // #endregion Not Anchored and Are Not Dependent + + + // //#region Aligned + + // //// Now, handle subviews that are aligned + // //// [x] PosAlign + // //int maxAlign = 0; + // //if (dimension == Dimension.Width) + // //{ + // // // Use Linq to get a list of distinct GroupIds from the subviews + // // List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); + + // // foreach (var groupId in groupIds) + // // { + // // List dimensionsList = new (); + + // // // 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 + // // }; + // // }) + // // .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) + // // .ToList (); + + // // if (posAlignsInGroup.Count == 0) + // // { + // // continue; + // // } + // // BUGBUG: ignores adornments + + // // maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension); + // // } + // //} + // //else + // //{ + // // subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); + // //} + + // //subviewsSize = int.Max (subviewsSize, maxAlign); + // //#endregion Aligned + + + // #region Auto + + + + // #endregion Auto + + // //#region Center + // //// Now, handle subviews that are Centered + // //if (dimension == Dimension.Width) + // //{ + // // subviews = us.Subviews.Where (v => v.X is PosCenter).ToList (); + // //} + // //else + // //{ + // // subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList (); + // //} + + // //int maxCenter = 0; + // //for (var i = 0; i < subviews.Count; i++) + // //{ + // // View v = subviews [i]; + // // maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; + // //} + + // //subviewsSize += maxCenter; + // //#endregion Center + + // #region Are Dependent + // // Now, go back to those that are dependent on content size + + + // // Set relative layout for all DimAuto subviews + // List dimAutoSubViews; + // int maxAuto = 0; + // if (dimension == Dimension.Width) + // { + // dimAutoSubViews = includedSubviews.Where (v => v.Width is DimAuto).ToList (); + // } + // else + // { + // dimAutoSubViews = includedSubviews.Where (v => v.Height is DimAuto).ToList (); + // } + // for (var i = 0; i < dimAutoSubViews.Count; i++) + // { + // View v = dimAutoSubViews [i]; + + // if (dimension == Dimension.Width) + // { + // // BUGBUG: ignores adornments - subviewsSize += maxFill; - #endregion Are Dependent + // v.SetRelativeLayout (new Size (autoMax - maxCalculatedSize, 0)); + // } + // else + // { + // // BUGBUG: ignores adornments + + // v.SetRelativeLayout (new Size (0, autoMax - maxCalculatedSize)); + // } + + // maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; + + // if (maxAuto > maxCalculatedSize) + // { + // // BUGBUG: Should we break here? Or choose min/max? + // maxCalculatedSize = maxAuto; + // } + // } + + // // [x] DimFill + // // [ ] DimPercent + // if (dimension == Dimension.Width) + // { + // subviews = includedSubviews.Where (v => v.Width is DimFill).ToList (); + // } + // else + // { + // subviews = includedSubviews.Where (v => v.Height is DimFill).ToList (); + // } + + // int maxFill = 0; + // for (var i = 0; i < subviews.Count; i++) + // { + // View v = subviews [i]; + + // if (autoMax == int.MaxValue) + // { + // autoMax = superviewContentSize; + // } + // if (dimension == Dimension.Width) + // { + // // BUGBUG: ignores adornments + // v.SetRelativeLayout (new Size (autoMax - maxCalculatedSize, 0)); + // } + // else + // { + // // BUGBUG: ignores adornments + // v.SetRelativeLayout (new Size (0, autoMax - maxCalculatedSize)); + // } + // maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; + // } + + // maxCalculatedSize += maxFill; + // #endregion Are Dependent } } // All sizes here are content-relative; ignoring adornments. // We take the largest of text and content. - int max = int.Max (textSize, subviewsSize); + int max = int.Max (textSize, maxCalculatedSize); // And, if min: is set, it wins if larger max = int.Max (max, autoMin); @@ -302,9 +567,24 @@ internal override int Calculate (int location, int superviewContentSize, View us // And, if max: is set, it wins if smaller max = int.Min (max, autoMax); + + // ************** We now definitively know `us.ContentSize` *************** + + foreach (var v in us.Subviews) + { + if (dimension == Dimension.Width) + { + v.SetRelativeLayout (new Size (max, Application.Driver.Screen.Width)); + } + else + { + v.SetRelativeLayout (new Size (Application.Driver.Screen.Height, max)); + } + } + // Factor in adornments Thickness thickness = us.GetAdornmentsThickness (); - max += dimension switch + var adornmentThickness = dimension switch { Dimension.Width => thickness.Horizontal, Dimension.Height => thickness.Vertical, @@ -312,6 +592,8 @@ internal override int Calculate (int location, int superviewContentSize, View us _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null) }; + max += adornmentThickness; + return max; } diff --git a/Terminal.Gui/View/Layout/DimCombine.cs b/Terminal.Gui/View/Layout/DimCombine.cs index 5baf00f639..2e204eadb2 100644 --- a/Terminal.Gui/View/Layout/DimCombine.cs +++ b/Terminal.Gui/View/Layout/DimCombine.cs @@ -13,7 +13,7 @@ namespace Terminal.Gui; /// /// The left dimension. /// The right dimension. -public class DimCombine (AddOrSubtract add, Dim? left, Dim? right) : Dim +public class DimCombine (AddOrSubtract add, Dim left, Dim right) : Dim { /// /// Gets whether the two dimensions are added or subtracted. @@ -23,12 +23,12 @@ public class DimCombine (AddOrSubtract add, Dim? left, Dim? right) : Dim /// /// Gets the left dimension. /// - public Dim? Left { get; } = left; + public Dim Left { get; } = left; /// /// Gets the right dimension. /// - public Dim? Right { get; } = right; + public Dim Right { get; } = right; /// public override string ToString () { return $"Combine({Left}{(Add == AddOrSubtract.Add ? '+' : '-')}{Right})"; } diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 58b0d23ceb..edb64a230c 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -546,15 +546,106 @@ out StatusBar statusBar public event EventHandler LayoutStarted; /// - /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in response to - /// the container view or terminal resizing. + /// Adjusts given the SuperView's ContentSize (nominally the same as + /// this.SuperView.GetContentSize ()) + /// and the position (, ) and dimension (, and + /// ). + /// + /// + /// + /// If , , , or are + /// absolute, they will be updated to reflect the new size and position of the view. Otherwise, they + /// are left unchanged. + /// + /// + /// If any of the view's subviews have a position or dimension dependent on either or other subviews, on + /// will be called for that subview. + /// + /// + /// + /// The size of the SuperView's content (nominally the same as this.SuperView.GetContentSize ()). + /// + internal void SetRelativeLayout (Size superviewContentSize) + { + Debug.Assert (_x is { }); + Debug.Assert (_y is { }); + Debug.Assert (_width is { }); + Debug.Assert (_height is { }); + + CheckDimAuto (); + int newX, newW, newY, newH; + + // Calculate the new X, Y, Width, and Height + // If the Width or Height is Dim.Auto, calculate the Width or Height first. Otherwise, calculate the X or Y first. + if (_width is DimAuto) + { + newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width); + newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width); + } + else + { + newX = _x.Calculate (superviewContentSize.Width, _width, this, Dimension.Width); + newW = _width.Calculate (newX, superviewContentSize.Width, this, Dimension.Width); + } + + if (_height is DimAuto) + { + newH = _height.Calculate (0, superviewContentSize.Height, this, Dimension.Height); + newY = _y.Calculate (superviewContentSize.Height, newH, this, Dimension.Height); + } + else + { + newY = _y.Calculate (superviewContentSize.Height, _height, this, Dimension.Height); + newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); + } + + Rectangle newFrame = new (newX, newY, newW, newH); + + if (Frame != newFrame) + { + // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height + SetFrame (newFrame); + + if (_x is PosAbsolute) + { + _x = Frame.X; + } + + if (_y is PosAbsolute) + { + _y = Frame.Y; + } + + if (_width is DimAbsolute) + { + _width = Frame.Width; + } + + if (_height is DimAbsolute) + { + _height = Frame.Height; + } + + if (!string.IsNullOrEmpty (Title)) + { + SetTitleTextFormatterSize (); + } + + SetNeedsLayout (); + SetNeedsDisplay (); + } + } + + + /// + /// Invoked when the dimensions of the view have changed, for example in response to the container view or terminal resizing. /// /// /// /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, the /// behavior of this method is indeterminate if is . /// - /// Raises the event) before it returns. + /// Raises the event before it returns. /// public virtual void LayoutSubviews () { @@ -689,89 +780,11 @@ internal void SetNeedsLayout () } /// - /// Adjusts given the SuperView's ContentSize (nominally the same as - /// this.SuperView.GetContentSize ()) - /// and the position (, ) and dimension (, and - /// ). + /// Collects all views and their dependencies from a given starting view for layout purposes. Used by to create an ordered list of views to layout. /// - /// - /// - /// If , , , or are - /// absolute, they will be updated to reflect the new size and position of the view. Otherwise, they - /// are left unchanged. - /// - /// - /// - /// The size of the SuperView's content (nominally the same as this.SuperView.GetContentSize ()). - /// - internal void SetRelativeLayout (Size superviewContentSize) - { - Debug.Assert (_x is { }); - Debug.Assert (_y is { }); - Debug.Assert (_width is { }); - Debug.Assert (_height is { }); - - CheckDimAuto (); - int newX, newW, newY, newH; - - if (_width is DimAuto) - { - newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width); - newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width); - } - else - { - newX = _x.Calculate (superviewContentSize.Width, _width, this, Dimension.Width); - newW = _width.Calculate (newX, superviewContentSize.Width, this, Dimension.Width); - } - - if (_height is DimAuto) - { - newH = _height.Calculate (0, superviewContentSize.Height, this, Dimension.Height); - newY = _y.Calculate (superviewContentSize.Height, newH, this, Dimension.Height); - } - else - { - newY = _y.Calculate (superviewContentSize.Height, _height, this, Dimension.Height); - newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); - } - - Rectangle newFrame = new (newX, newY, newW, newH); - - if (Frame != newFrame) - { - // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height - SetFrame (newFrame); - - if (_x is PosAbsolute) - { - _x = Frame.X; - } - - if (_y is PosAbsolute) - { - _y = Frame.Y; - } - - if (_width is DimAbsolute) - { - _width = Frame.Width; - } - - if (_height is DimAbsolute) - { - _height = Frame.Height; - } - - if (!string.IsNullOrEmpty (Title)) - { - SetTitleTextFormatterSize (); - } - - SetNeedsLayout (); - SetNeedsDisplay (); - } - } + /// The starting view from which to collect dependencies. + /// A reference to a set of views representing nodes in the layout graph. + /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views indicating a dependency. internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { @@ -784,6 +797,13 @@ internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View CollectDim (v.Height, v, ref nNodes, ref nEdges); } } + /// + /// Collects dimension (where Width or Height is `DimView`) dependencies for a given view. + /// + /// The dimension (width or height) to collect dependencies for. + /// The view for which to collect dimension dependencies. + /// A reference to a set of views representing nodes in the layout graph. + /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views indicating a dependency. internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { @@ -808,6 +828,13 @@ internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref Has } } + /// + /// Collects position (where X or Y is `PosView`) dependencies for a given view. + /// + /// The position (X or Y) to collect dependencies for. + /// The view for which to collect position dependencies. + /// A reference to a set of views representing nodes in the layout graph. + /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views indicating a dependency. internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { switch (pos) diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index bcdca4fa80..00035cd19b 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -33,14 +33,14 @@ public class Dialog : Window /// . /// [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] - public static int DefaultMinimumWidth { get; set; } = 25; + public static int DefaultMinimumWidth { get; set; } = 0; /// /// Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via /// . /// [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] - public static int DefaultMinimumHeight { get; set; } = 25; + public static int DefaultMinimumHeight { get; set; } = 0; /// @@ -79,8 +79,8 @@ public Dialog () X = Pos.Center (); Y = Pos.Center (); - Width = Dim.Auto (DimAutoStyle.Content, Dim.Percent (DefaultMinimumWidth), Dim.Percent (90)); - Height = Dim.Auto (DimAutoStyle.Content, Dim.Percent (DefaultMinimumHeight), Dim.Percent (90)); + Width = Dim.Auto (DimAutoStyle.Auto, Dim.Percent (DefaultMinimumWidth), Dim.Percent (90)); + Height = Dim.Auto (DimAutoStyle.Auto, Dim.Percent (DefaultMinimumHeight), Dim.Percent (90)); ColorScheme = Colors.ColorSchemes ["Dialog"]; Modal = true; @@ -169,6 +169,7 @@ public void AddButton (Button button) // Use a distinct GroupId so users can use Pos.Align for other views in the Dialog button.X = Pos.Align (ButtonAlignment, ButtonAlignmentModes, GetHashCode ()); button.Y = Pos.AnchorEnd (); + button.Margin.Thickness = button.Margin.Thickness with { Top = 1 }; _buttons.Add (button); Add (button); diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index bfc070de0f..68d78877ae 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.Diagnostics; +using System.Text.Json.Serialization; namespace Terminal.Gui; @@ -368,8 +369,8 @@ params string [] buttons ButtonAlignment = Alignment.Center, ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems, BorderStyle = MessageBox.DefaultBorderStyle, - Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1, Dim.Percent (90)), - Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1, Dim.Percent (90)), + Width = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 1, maximumContentDim: Dim.Percent (90)), + Height = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 2, maximumContentDim: Dim.Percent (90)), }; if (width != 0) @@ -384,36 +385,41 @@ params string [] buttons d.ColorScheme = useErrorColors ? Colors.ColorSchemes ["Error"] : Colors.ColorSchemes ["Dialog"]; - var messageLabel = new Label + d.LayoutComplete += (s, e) => { - HotKeySpecifier = new Rune ('\xFFFF'), - Width = Dim.Auto (DimAutoStyle.Text), - Height = Dim.Auto (DimAutoStyle.Text), - Text = message, - TextAlignment = Alignment.Center, - X = Pos.Center (), - Y = 0, - //ColorScheme = Colors.ColorSchemes ["Error"], - }; + if (wrapMessage) + { + int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; + Debug.Assert (d.TextFormatter.WordWrap); + d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Driver.Screen.Height); + Size textSize = d.TextFormatter.GetAutoSize (); + textSize.Height += buttonHeight; - messageLabel.TextFormatter.WordWrap = wrapMessage; - messageLabel.TextFormatter.MultiLine = !wrapMessage; + if (textSize != d.TextFormatter.Size) + { + //d.TextFormatter.Size = textSize; + //d.SetContentSize (textSize); + d.SetNeedsLayout (); + //d.SetRelativeLayout (Application.Driver.Screen.Size); + } + } + }; - if (wrapMessage) - { - int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; + d.HotKeySpecifier = new Rune ('\xFFFF'); + d.Text = message; + d.TextAlignment = Alignment.Center; + d.VerticalTextAlignment = Alignment.Start; + d.TextFormatter.WordWrap = wrapMessage; + d.TextFormatter.MultiLine = !wrapMessage; - messageLabel.Width = Dim.Fill (); - messageLabel.Height = Dim.Func (() => GetWrapSize ().Height); - Size GetWrapSize () - { - // A bit of a hack to get the height of the wrapped text. - messageLabel.TextFormatter.Size = d.GetContentSize () with { Height = 1000 }; - return messageLabel.TextFormatter.FormatAndGetSize (); - } - } + // Add two lines to push buttons down two rows + // BUGBUG: The " " are here due to a bug in TextFormater.Format that strips trailing newlines when .Wordwrap = true + // d.Text += Environment.NewLine + " " + Environment.NewLine + " "; - d.Add (messageLabel); + d.ColorScheme = new ColorScheme (d.ColorScheme) + { + Focus = d.ColorScheme.Normal + }; // Setup actions Clicked = -1; diff --git a/UICatalog/Scenarios/DimAutoDemo.cs b/UICatalog/Scenarios/DimAutoDemo.cs index 1247297446..83f749199c 100644 --- a/UICatalog/Scenarios/DimAutoDemo.cs +++ b/UICatalog/Scenarios/DimAutoDemo.cs @@ -23,21 +23,21 @@ public override void Main () FrameView dimAutoFrameView = CreateDimAutoContentFrameView (); - FrameView sliderFrameView = CreateSliderFrameView (); - sliderFrameView.X = Pos.Right(dimAutoFrameView) + 1; - sliderFrameView.Width = Dim.Fill (); - sliderFrameView.Height = Dim.Fill (); + //FrameView sliderFrameView = CreateSliderFrameView (); + //sliderFrameView.X = Pos.Right(dimAutoFrameView) + 1; + //sliderFrameView.Width = Dim.Fill (); + //sliderFrameView.Height = Dim.Fill (); - //var dlgButton = new Button - //{ - // Text = "Open Test _Dialog", - // X = Pos.Right (dimAutoFrameView), - // Y = Pos.Top (dimAutoFrameView) - //}; - //dlgButton.Accept += DlgButton_Clicked; + ////var dlgButton = new Button + ////{ + //// Text = "Open Test _Dialog", + //// X = Pos.Right (dimAutoFrameView), + //// Y = Pos.Top (dimAutoFrameView) + ////}; + ////dlgButton.Accept += DlgButton_Clicked; - appWindow.Add (dimAutoFrameView, sliderFrameView /*dlgButton*/); + appWindow.Add (dimAutoFrameView/*, sliderFrameView dlgButton*/); // Run - Start the application. Application.Run (appWindow); @@ -140,23 +140,27 @@ private static FrameView CreateDimAutoContentFrameView () bothAuto.Text = textEdit.Text; }; - var movingButton = new Button - { - Text = "_Click\nTo Move\nDown", - X = Pos.Right (vlabel), - Y = Pos.Bottom (vlabel) - }; - movingButton.Accept += (s, e) => { movingButton.Y = movingButton.Frame.Y + 1; }; - dimAutoFrameView.Add (movingButton); + //var movingButton = new Button + //{ + // Text = "_Click\nTo Move\nDown", + // X = Pos.Right (vlabel), + // Y = Pos.Bottom (vlabel) + //}; + //movingButton.Accept += (s, e) => { movingButton.Y = movingButton.Frame.Y + 1; }; + //dimAutoFrameView.Add (movingButton); var resetButton = new Button { Text = "_Reset Button (AnchorEnd)", X = Pos.AnchorEnd (), - Y = Pos.AnchorEnd (1) + Y = Pos.AnchorEnd () }; - resetButton.Accept += (s, e) => { movingButton.Y = Pos.Bottom (hlabel); }; + resetButton.Accept += (s, e) => + { + //movingButton.Y = Pos.Bottom (hlabel); + //movingButton.X = 0; + }; dimAutoFrameView.Add (resetButton); return dimAutoFrameView; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index ddb4c656f1..ab616f12eb 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -305,12 +305,12 @@ private static void UICatalogMain (Options options) _aboutMessage = new (); _aboutMessage.AppendLine (@"A comprehensive sample library for"); _aboutMessage.AppendLine (@""); - _aboutMessage.AppendLine (@" _______ _ _ _____ _ "); - _aboutMessage.AppendLine (@" |__ __| (_) | | / ____| (_) "); - _aboutMessage.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ "); - _aboutMessage.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | "); - _aboutMessage.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | "); - _aboutMessage.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| "); + _aboutMessage.AppendLine (@" _______ _ _ _____ _ "); + _aboutMessage.AppendLine (@"|__ __| (_) | | / ____| (_)"); + _aboutMessage.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ "); + _aboutMessage.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |"); + _aboutMessage.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | |"); + _aboutMessage.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|"); _aboutMessage.AppendLine (@""); _aboutMessage.AppendLine (@"v2 - Work in Progress"); _aboutMessage.AppendLine (@""); @@ -346,6 +346,7 @@ private static void VerifyObjectsWereDisposed () // 'app' closed cleanly. foreach (Responder? inst in Responder.Instances) { + Debug.Assert (inst.WasDisposed); } From e10564389c2a18e9e9ceb55b9314d4d4ac1bad6f Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 15 Jul 2024 09:27:23 -0600 Subject: [PATCH 05/96] Updated About box --- UICatalog/UICatalog.cs | 102 +++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index ab616f12eb..1218c701f9 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -1,4 +1,4 @@ -global using Attribute = Terminal.Gui.Attribute; +global using Attribute = Terminal.Gui.Attribute; global using CM = Terminal.Gui.ConfigurationManager; using System; using System.Collections.Generic; @@ -295,6 +295,7 @@ private static void UICatalogMain (Options options) _selectedScenario.Main (); _selectedScenario.Dispose (); _selectedScenario = null; + // TODO: Throw if shutdown was not called already Application.Shutdown (); VerifyObjectsWereDisposed (); @@ -302,20 +303,6 @@ private static void UICatalogMain (Options options) return; } - _aboutMessage = new (); - _aboutMessage.AppendLine (@"A comprehensive sample library for"); - _aboutMessage.AppendLine (@""); - _aboutMessage.AppendLine (@" _______ _ _ _____ _ "); - _aboutMessage.AppendLine (@"|__ __| (_) | | / ____| (_)"); - _aboutMessage.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ "); - _aboutMessage.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |"); - _aboutMessage.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | |"); - _aboutMessage.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|"); - _aboutMessage.AppendLine (@""); - _aboutMessage.AppendLine (@"v2 - Work in Progress"); - _aboutMessage.AppendLine (@""); - _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); - while (RunUICatalogTopLevel () is { } scenario) { VerifyObjectsWereDisposed (); @@ -346,7 +333,6 @@ private static void VerifyObjectsWereDisposed () // 'app' closed cleanly. foreach (Responder? inst in Responder.Instances) { - Debug.Assert (inst.WasDisposed); } @@ -375,7 +361,9 @@ public class UICatalogTopLevel : Toplevel public MenuItem? MiIsMenuBorderDisabled; public MenuItem? MiIsMouseDisabled; public MenuItem? MiUseSubMenusSingleFrame; + public Shortcut? ShForce16Colors; + //public Shortcut? ShDiagnostics; public Shortcut? ShVersion; @@ -388,11 +376,27 @@ public class UICatalogTopLevel : Toplevel public UICatalogTopLevel () { - _diagnosticFlags = View.Diagnostics; + _diagnosticFlags = Diagnostics; _themeMenuItems = CreateThemeMenuItems (); _themeMenuBarItem = new ("_Themes", _themeMenuItems); + _aboutMessage = new (); + _aboutMessage.AppendLine (@"A comprehensive sample library for"); + _aboutMessage.AppendLine ( + """ + _______ _ _ _____ _ + |__ __| (_) | | / ____| (_) + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ + | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | + | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | + |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| + """); + _aboutMessage.AppendLine (@""); + _aboutMessage.AppendLine (@"v2 - Pre-Alpha"); + _aboutMessage.AppendLine (@""); + _aboutMessage.Append (@"https://github.com/gui-cs/Terminal.Gui"); + MenuBar = new () { Menus = @@ -436,9 +440,8 @@ public UICatalogTopLevel () () => MessageBox.Query ( "About UI Catalog", _aboutMessage!.ToString (), - 0, - false, - "_Ok" + wrapMessage: false, + buttons: "_Ok" ), null, null, @@ -460,35 +463,34 @@ public UICatalogTopLevel () ShVersion = new () { Title = "Version Info", - CanFocus = false, - + CanFocus = false }; - Shortcut statusBarShortcut = new Shortcut () + var statusBarShortcut = new Shortcut { Key = Key.F10, - Title = "Show/Hide Status Bar", + Title = "Show/Hide Status Bar" }; statusBarShortcut.Accept += (sender, args) => { StatusBar.Visible = !StatusBar.Visible; }; - ShForce16Colors = new Shortcut () + ShForce16Colors = new() { - CommandView = new CheckBox () + CommandView = new CheckBox { Title = "16 color mode", State = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked, - CanFocus = false, + CanFocus = false }, HelpText = "", - Key = Key.F6, + Key = Key.F6 }; ((CheckBox)ShForce16Colors.CommandView).Toggle += (sender, args) => - { - Application.Force16Colors = args.NewValue == CheckState.Checked; - MiForce16Colors!.Checked = Application.Force16Colors; - Application.Refresh (); - }; + { + Application.Force16Colors = args.NewValue == CheckState.Checked; + MiForce16Colors!.Checked = Application.Force16Colors; + Application.Refresh (); + }; //ShDiagnostics = new Shortcut () //{ @@ -503,10 +505,10 @@ public UICatalogTopLevel () //}; StatusBar.Add ( - new Shortcut () + new Shortcut { Title = "Quit", - Key = Application.QuitKey, + Key = Application.QuitKey }, statusBarShortcut, ShForce16Colors, @@ -846,24 +848,24 @@ private MenuItem [] CreateDiagnosticFlagsMenuItems () string GetDiagnosticsTitle (Enum diag) { return Enum.GetName (_diagnosticFlags.GetType (), diag) switch - { - "Off" => OFF, - "Ruler" => RULER, - "Padding" => PADDING, - "MouseEnter" => MOUSEENTER, - _ => "" - }; + { + "Off" => OFF, + "Ruler" => RULER, + "Padding" => PADDING, + "MouseEnter" => MOUSEENTER, + _ => "" + }; } Enum GetDiagnosticsEnumValue (string title) { return title switch - { - RULER => ViewDiagnosticFlags.Ruler, - PADDING => ViewDiagnosticFlags.Padding, - MOUSEENTER => ViewDiagnosticFlags.MouseEnter, - _ => null! - }; + { + RULER => ViewDiagnosticFlags.Ruler, + PADDING => ViewDiagnosticFlags.Padding, + MOUSEENTER => ViewDiagnosticFlags.MouseEnter, + _ => null! + }; } void SetDiagnosticsFlag (Enum diag, bool add) @@ -1007,7 +1009,9 @@ private MenuItem [] CreateForce16ColorItems () MiForce16Colors.Action += () => { MiForce16Colors.Checked = Application.Force16Colors = (bool)!MiForce16Colors.Checked!; - ((CheckBox)ShForce16Colors!.CommandView!).State = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked; + + ((CheckBox)ShForce16Colors!.CommandView!).State = + Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked; Application.Refresh (); }; menuItems.Add (MiForce16Colors); From 58e42715888f2473b3a699aef5e1b4f6d25b9de5 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 15 Jul 2024 10:22:26 -0600 Subject: [PATCH 06/96] MessageBox works. Dialog broken --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 1 + Terminal.Gui/Drawing/Glyphs.cs | 4 +-- Terminal.Gui/View/Adornment/Border.cs | 5 ++-- Terminal.Gui/View/Adornment/Margin.cs | 29 +++++++++++++++----- Terminal.Gui/View/Adornment/ShadowView.cs | 15 ++++++++-- Terminal.Gui/Views/Dialog.cs | 1 - UICatalog/UICatalog.cs | 8 +++--- 7 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index b5ab6fa9c2..2d1a99db3d 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -426,6 +426,7 @@ public bool IsValidLocation (int col, int row) /// Row to move to. public virtual void Move (int col, int row) { + //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0)); Col = col; Row = row; } diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs index 9064008a5d..e98d60b1ec 100644 --- a/Terminal.Gui/Drawing/Glyphs.cs +++ b/Terminal.Gui/Drawing/Glyphs.cs @@ -442,13 +442,13 @@ public class GlyphDefinitions /// Shadow - Vertical Start - Left Half Block - ▌ U+0258c - public Rune ShadowVerticalStart { get; set; } = (Rune)'▌'; // Half: '\u2596' ▖; + public Rune ShadowVerticalStart { get; set; } = (Rune)'▖'; // Half: '\u2596' ▖; /// Shadow - Vertical - Left Half Block - ▌ U+0258c public Rune ShadowVertical { get; set; } = (Rune)'▌'; /// Shadow - Horizontal Start - Upper Half Block - ▀ U+02580 - public Rune ShadowHorizontalStart { get; set; } = (Rune)'▀'; // Half: ▝ U+0259d; + public Rune ShadowHorizontalStart { get; set; } = (Rune)'▝'; // Half: ▝ U+0259d; /// Shadow - Horizontal - Upper Half Block - ▀ U+02580 public Rune ShadowHorizontal { get; set; } = (Rune)'▀'; diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 2931cd9ad2..aa4dd20661 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -149,8 +149,9 @@ public override ColorScheme ColorScheme } } - private Rectangle GetBorderRectangle (Rectangle screenRect) + internal Rectangle GetBorderRectangle () { + Rectangle screenRect = ViewportToScreen (Viewport); return new ( screenRect.X + Math.Max (0, Thickness.Left - 1), screenRect.Y + Math.Max (0, Thickness.Top - 1), @@ -407,7 +408,7 @@ public override void OnDrawContent (Rectangle viewport) // ...thickness extends outward (border/title is always as far in as possible) // PERF: How about a call to Rectangle.Offset? - Rectangle borderBounds = GetBorderRectangle (screenBounds); + Rectangle borderBounds = GetBorderRectangle (); int topTitleLineY = borderBounds.Y; int titleY = borderBounds.Y; var titleBarsLength = 0; // the little vertical thingies diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index 046965e321..9f96a54a5d 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -44,7 +44,7 @@ public override void BeginInit () ShadowStyle = base.ShadowStyle; Add ( - _rightShadow = new() + _rightShadow = new () { X = Pos.AnchorEnd (1), Y = 0, @@ -53,7 +53,7 @@ public override void BeginInit () ShadowStyle = ShadowStyle, Orientation = Orientation.Vertical }, - _bottomShadow = new() + _bottomShadow = new () { X = 0, Y = Pos.AnchorEnd (1), @@ -220,12 +220,27 @@ private void Margin_Highlight (object? sender, CancelEventArgs e private void Margin_LayoutStarted (object? sender, LayoutEventArgs e) { // Adjust the shadow such that it is drawn aligned with the Border - if (ShadowStyle != ShadowStyle.None && _rightShadow is { } && _bottomShadow is { }) + if (_rightShadow is { } && _bottomShadow is { }) { - _rightShadow.Y = Parent.Border.Thickness.Top > 0 - ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.Settings.FastHasFlags (BorderSettings.Title) ? 1 : 0) - : 1; - _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1; + switch (ShadowStyle) + { + case ShadowStyle.Transparent: + // BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner. + _rightShadow.Y = Parent.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0; + break; + + case ShadowStyle.Opaque: + // BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner. + _rightShadow.Y = Parent.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0; + _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).X + 1 : 0; + break; + + case ShadowStyle.None: + default: + _rightShadow.Y = 0; + _bottomShadow.X = 0; + break; + } } } } diff --git a/Terminal.Gui/View/Adornment/ShadowView.cs b/Terminal.Gui/View/Adornment/ShadowView.cs index c5e7a428aa..3215b255b1 100644 --- a/Terminal.Gui/View/Adornment/ShadowView.cs +++ b/Terminal.Gui/View/Adornment/ShadowView.cs @@ -1,4 +1,7 @@ #nullable enable +using Microsoft.VisualBasic; +using System.Diagnostics; + namespace Terminal.Gui; /// @@ -109,7 +112,11 @@ private void DrawHorizontalShadowTransparent (Rectangle viewport) for (int i = screen.X; i < screen.X + screen.Width - 1; i++) { Driver.Move (i, screen.Y); - Driver.AddRune (Driver.Contents [screen.Y, i].Rune); + + if (i < Driver.Contents.GetLength (1) && screen.Y < Driver.Contents.GetLength (0)) + { + Driver.AddRune (Driver.Contents [screen.Y, i].Rune); + } } } @@ -133,7 +140,11 @@ private void DrawVerticalShadowTransparent (Rectangle viewport) for (int i = screen.Y; i < screen.Y + viewport.Height; i++) { Driver.Move (screen.X, i); - Driver.AddRune (Driver.Contents [i, screen.X].Rune); + + if (screen.X < Driver.Contents.GetLength (1) && i < Driver.Contents.GetLength (0)) + { + Driver.AddRune (Driver.Contents [i, screen.X].Rune); + } } } } diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index 00035cd19b..31982ac8a1 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -169,7 +169,6 @@ public void AddButton (Button button) // Use a distinct GroupId so users can use Pos.Align for other views in the Dialog button.X = Pos.Align (ButtonAlignment, ButtonAlignmentModes, GetHashCode ()); button.Y = Pos.AnchorEnd (); - button.Margin.Thickness = button.Margin.Thickness with { Top = 1 }; _buttons.Add (button); Add (button); diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 1218c701f9..b063e15bc4 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -382,7 +382,7 @@ public UICatalogTopLevel () _themeMenuBarItem = new ("_Themes", _themeMenuItems); _aboutMessage = new (); - _aboutMessage.AppendLine (@"A comprehensive sample library for"); + _aboutMessage.AppendLine (@"UI Catalog: A comprehensive sample library for"); _aboutMessage.AppendLine ( """ _______ _ _ _____ _ @@ -395,7 +395,7 @@ _______ _ _ _____ _ _aboutMessage.AppendLine (@""); _aboutMessage.AppendLine (@"v2 - Pre-Alpha"); _aboutMessage.AppendLine (@""); - _aboutMessage.Append (@"https://github.com/gui-cs/Terminal.Gui"); + _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); MenuBar = new () { @@ -438,8 +438,8 @@ _______ _ _ _____ _ "_About...", "About UI Catalog", () => MessageBox.Query ( - "About UI Catalog", - _aboutMessage!.ToString (), + title: "", + message: _aboutMessage!.ToString (), wrapMessage: false, buttons: "_Ok" ), From 8bf0207171c299c37bb51bbbc83cbcb06429f9d5 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 15 Jul 2024 12:00:50 -0600 Subject: [PATCH 07/96] Dialog works --- Terminal.Gui/View/Layout/DimAuto.cs | 161 +++++++++++++++++++--------- Terminal.Gui/Views/Dialog.cs | 2 +- Terminal.Gui/Views/FileDialog.cs | 3 + UICatalog/KeyBindingsDialog.cs | 6 +- UICatalog/Scenarios/Dialogs.cs | 5 +- UICatalog/Scenarios/ListColumns.cs | 2 +- UICatalog/Scenarios/TableEditor.cs | 14 ++- 7 files changed, 130 insertions(+), 63 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 9ea540cd8a..00952abb47 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Diagnostics; using System.Drawing; namespace Terminal.Gui; @@ -159,7 +160,18 @@ internal override int Calculate (int location, int superviewContentSize, View us { View v = notDependentSubViews [i]; - int size = dimension == Dimension.Width ? v.X.GetAnchor (maxCalculatedSize) + v.Width.GetAnchor (maxCalculatedSize) : v.Y.GetAnchor (maxCalculatedSize) + v.Height.GetAnchor (maxCalculatedSize); + int size = 0; + + if (dimension == Dimension.Width) + { + int width = v.Width!.Calculate (0, 0, v, dimension); + size = v.X.GetAnchor (0) + width; + } + else + { + int height = v.Height!.Calculate (0, 0, v, dimension); + size = v.Y.GetAnchor (0) + height; + } if (size > maxCalculatedSize) { @@ -169,7 +181,7 @@ internal override int Calculate (int location, int superviewContentSize, View us // ************** We now have some idea of `us.ContentSize` *************** - + #region Centered // [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame` List centeredSubViews; if (dimension == Dimension.Width) @@ -181,18 +193,111 @@ internal override int Calculate (int location, int superviewContentSize, View us centeredSubViews = us.Subviews.Where (v => v.Y.Has (typeof (PosCenter), out _)).ToList (); } + int maxCentered = 0; + for (var i = 0; i < centeredSubViews.Count; i++) { View v = centeredSubViews [i]; - int maxCentered = dimension == Dimension.Width ?/* v.X.GetAnchor (maxCalculatedSize) +*/ v.Width.GetAnchor (maxCalculatedSize) : /*v.Y.GetAnchor (maxCalculatedSize) + */v.Height.GetAnchor(maxCalculatedSize); + if (dimension == Dimension.Width) + { + int width = v.Width!.Calculate (0, 0, v, dimension); + maxCentered = (v.X.GetAnchor (0) + width) * 2; + } + else + { + int height = v.Height!.Calculate (0, 0, v, dimension); + maxCentered = (v.Y.GetAnchor (0) + height) * 2; + } + } + maxCalculatedSize = int.Max (maxCalculatedSize, maxCentered); + #endregion Centered + + #region Percent + // [ ] DimPercent - Dimension is dependent on `us.ContentSize` + List percentSubViews; + if (dimension == Dimension.Width) + { + percentSubViews = us.Subviews.Where (v => v.Width.Has (typeof (DimPercent), out _)).ToList (); + } + else + { + percentSubViews = us.Subviews.Where (v => v.Height.Has (typeof (DimPercent), out _)).ToList (); + } + + int maxPercent = 0; - if (maxCentered > maxCalculatedSize) + for (var i = 0; i < percentSubViews.Count; i++) + { + View v = percentSubViews [i]; + + if (dimension == Dimension.Width) { - maxCalculatedSize = maxCentered; + int width = v.Width!.Calculate (0, 0, v, dimension); + maxPercent = (v.X.GetAnchor (0) + width); } + else + { + int height = v.Height!.Calculate (0, 0, v, dimension); + maxPercent = (v.Y.GetAnchor (0) + height); + } + } + maxCalculatedSize = int.Max (maxCalculatedSize, maxPercent); + #endregion Percent + + + #region Aligned + // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` + int maxAlign = 0; + // Use Linq to get a list of distinct GroupIds from the subviews + List groupIds = includedSubviews.Select ( + v => + { + if (dimension == Dimension.Width) + { + if (v.X.Has (typeof (PosAlign), out Pos posAlign)) + { + return ((PosAlign)posAlign).GroupId; + } + } + else + { + if (v.Y.Has (typeof (PosAlign), out Pos posAlign)) + { + return ((PosAlign)posAlign).GroupId; + } + } + return -1; + }).Distinct ().ToList (); + + foreach (var 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 + }; + }) + .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) + .ToList (); + + if (posAlignsInGroup.Count == 0) + { + continue; + } + + maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension); } + maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign); + #endregion Aligned + + #region Anchored // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` @@ -226,7 +331,6 @@ internal override int Calculate (int location, int superviewContentSize, View us maxCalculatedSize = Math.Max (maxCalculatedSize, maxAnchorEnd); #endregion Anchored - #region PosView // [x] PosView - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize` List posViewSubViews; @@ -405,51 +509,6 @@ internal override int Calculate (int location, int superviewContentSize, View us // #endregion Not Anchored and Are Not Dependent - // //#region Aligned - - // //// Now, handle subviews that are aligned - // //// [x] PosAlign - // //int maxAlign = 0; - // //if (dimension == Dimension.Width) - // //{ - // // // Use Linq to get a list of distinct GroupIds from the subviews - // // List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); - - // // foreach (var groupId in groupIds) - // // { - // // List dimensionsList = new (); - - // // // 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 - // // }; - // // }) - // // .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) - // // .ToList (); - - // // if (posAlignsInGroup.Count == 0) - // // { - // // continue; - // // } - // // BUGBUG: ignores adornments - - // // maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension); - // // } - // //} - // //else - // //{ - // // subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); - // //} - - // //subviewsSize = int.Max (subviewsSize, maxAlign); - // //#endregion Aligned - // #region Auto diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index 31982ac8a1..f10e831a3c 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -78,9 +78,9 @@ public Dialog () X = Pos.Center (); Y = Pos.Center (); - Width = Dim.Auto (DimAutoStyle.Auto, Dim.Percent (DefaultMinimumWidth), Dim.Percent (90)); Height = Dim.Auto (DimAutoStyle.Auto, Dim.Percent (DefaultMinimumHeight), Dim.Percent (90)); + ColorScheme = Colors.ColorSchemes ["Dialog"]; Modal = true; diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index 3be8ba4efd..fcdd2d6d27 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -60,6 +60,9 @@ public FileDialog () : this (new FileSystem ()) { } /// This overload is mainly useful for testing. internal FileDialog (IFileSystem fileSystem) { + Height = Dim.Percent (80); + Width = Dim.Percent (80); + // Assume canceled Canceled = true; diff --git a/UICatalog/KeyBindingsDialog.cs b/UICatalog/KeyBindingsDialog.cs index d46c9314a0..4fbfad24a2 100644 --- a/UICatalog/KeyBindingsDialog.cs +++ b/UICatalog/KeyBindingsDialog.cs @@ -19,8 +19,8 @@ public KeyBindingsDialog () { Title = "Keybindings"; - //Height = 50; - //Width = 10; + Height = Dim.Percent(80); + Width = Dim.Percent(80); if (ViewTracker.Instance == null) { ViewTracker.Initialize (); @@ -32,7 +32,7 @@ public KeyBindingsDialog () _commandsListView = new ListView { Width = Dim.Percent (50), - Height = Dim.Percent (100) - 1, + Height = Dim.Fill () - 1, Source = new ListWrapper (_commands), SelectedItem = 0 }; diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index f67210417d..c960669f4e 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -312,7 +312,7 @@ Label buttonPressedLabel var addChar = new Button { X = Pos.Center (), - Y = Pos.Center () + 1, + Y = Pos.Center () + 2, Text = $"A_dd a {char.ConvertFromUtf32 (CODE_POINT)} to each button. This text is really long for a reason." }; @@ -325,8 +325,9 @@ Label buttonPressedLabel dialog.LayoutSubviews (); }; - dialog.Closed += (s, e) => { buttonPressedLabel.Text = $"{clicked}"; }; dialog.Add (addChar); + + dialog.Closed += (s, e) => { buttonPressedLabel.Text = $"{clicked}"; }; } catch (FormatException) { diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 0f45b1e5d9..c45fca6a55 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -284,7 +284,7 @@ private void RunListWidthDialog (string prompt, Action setter, F cancel.Accept += (s, e) => { Application.RequestStop (); }; var d = new Dialog { Title = prompt, Buttons = [ok, cancel] }; - var tf = new TextField { Text = getter (_listColView).ToString (), X = 0, Y = 1, Width = Dim.Fill () }; + var tf = new TextField { Text = getter (_listColView).ToString (), X = 0, Y = 0, Width = Dim.Fill () }; d.Add (tf); tf.SetFocus (); diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index df86aceafd..8dd965e4a0 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -1083,13 +1083,16 @@ Func getter }; var cancel = new Button { Text = "Cancel" }; cancel.Accept += (s, e) => { Application.RequestStop (); }; - var d = new Dialog { Title = prompt, Buttons = [ok, cancel] }; + var d = new Dialog + { + Title = prompt, + Buttons = [ok, cancel] + }; ColumnStyle style = _tableView.Style.GetOrCreateColumnStyle (col.Value); - var lbl = new Label { X = 0, Y = 1, Text = _tableView.Table.ColumnNames [col.Value] }; - - var tf = new TextField { Text = getter (style).ToString (), X = 0, Y = 2, Width = Dim.Fill () }; + var lbl = new Label { X = 0, Y = 0, Text = $"{_tableView.Table.ColumnNames [col.Value]}: " }; + var tf = new TextField { Text = getter (style).ToString (), X = Pos.Right (lbl), Y = 0, Width = 20 }; d.Add (lbl, tf); tf.SetFocus (); @@ -1461,7 +1464,8 @@ private void ToggleCheckboxes (bool radio) treeSource, _checkedFileSystemInfos.Contains, CheckOrUncheckFile - ) { UseRadioButtons = radio }; + ) + { UseRadioButtons = radio }; } else { From a3b606fe6ea4aea0a3f17e62d3500135ee5e7bf5 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 15 Jul 2024 16:36:05 -0600 Subject: [PATCH 08/96] New unit tests. Lots of fixes --- Terminal.Gui/Application/Application.cs | 12 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 2 +- Terminal.Gui/Text/TextFormatter.cs | 54 ++-- Terminal.Gui/View/Layout/DimAuto.cs | 17 +- Terminal.Gui/View/Layout/PosCenter.cs | 3 +- Terminal.Gui/View/Layout/ViewLayout.cs | 4 +- Terminal.Gui/View/ViewText.cs | 14 +- Terminal.Gui/Views/Menu/ContextMenu.cs | 2 +- Terminal.Gui/Views/Menu/MenuBar.cs | 2 +- Terminal.Gui/Views/MessageBox.cs | 9 +- UICatalog/Scenarios/MessageBoxes.cs | 1 - UnitTests/View/DrawTests.cs | 10 +- UnitTests/View/Layout/Dim.AutoTests.cs | 298 +++++++++++++++---- UnitTests/View/Layout/Pos.CenterTests.cs | 27 +- UnitTests/View/Layout/Pos.PercentTests.cs | 14 + UnitTests/View/TextTests.cs | 51 +--- UnitTests/View/ViewTests.cs | 4 +- UnitTests/Views/LabelTests.cs | 2 +- UnitTests/Views/TextViewTests.cs | 2 +- 19 files changed, 348 insertions(+), 180 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 4775532637..9833d19cd8 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -68,6 +68,14 @@ internal static List GetSupportedCultures () .ToList (); } + /// + /// Gets the size of the screen. This is the size of the screen as reported by the . + /// + /// + /// If the has not been initialized, this will return a default size of 2048x2048; useful for unit tests. + /// + public static Rectangle Screen => Driver?.Screen ?? new (0, 0, 2048, 2048); + // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`. // This variable is set in `End` in this case so that `Begin` correctly sets `Top`. private static Toplevel _cachedRunStateToplevel; @@ -538,7 +546,7 @@ public static RunState Begin (Toplevel toplevel) MoveCurrent (Current); } - toplevel.SetRelativeLayout (Driver.Screen.Size); + toplevel.SetRelativeLayout (Screen.Size); toplevel.LayoutSubviews (); toplevel.PositionToplevels (); @@ -607,7 +615,7 @@ internal static bool PositionCursor (View view) // If the view is not visible within it's superview, don't position the cursor Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty }); - Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen; + Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Application.Screen; if (!superViewViewport.IntersectsWith (mostFocusedViewport)) { diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 2d1a99db3d..05cbb62172 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -20,7 +20,7 @@ public abstract class ConsoleDriver // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application? /// Gets the location and size of the terminal screen. - public Rectangle Screen => new (0, 0, Cols, Rows); + internal Rectangle Screen => new (0, 0, Cols, Rows); private Rectangle _clip; diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index bee37de67a..d54451139b 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -54,12 +54,12 @@ public bool AutoSize internal Size GetAutoSize () { Size size = CalcRect (0, 0, Text, Direction, TabWidth).Size; + return size with { Width = size.Width - GetHotKeySpecifierLength (), Height = size.Height - GetHotKeySpecifierLength (false) }; - } /// /// Gets the width or height of the characters @@ -112,7 +112,7 @@ public TextDirection Direction } } } - + /// /// Determines if the viewport width will be used or only the text width will be used, /// If all the viewport area will be filled with whitespaces and the same background color @@ -191,7 +191,7 @@ public Size Size { if (AutoSize) { - _size = EnableNeedsFormat (GetAutoSize()); + _size = EnableNeedsFormat (GetAutoSize ()); } else { @@ -451,9 +451,9 @@ public void Draw ( } else { - Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}"); + Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}"); - return; + return; } int colOffset = screen.X < 0 ? Math.Abs (screen.X) : 0; @@ -655,8 +655,20 @@ public Size FormatAndGetSize () return Size.Empty; } - int width = GetLines ().Max (static line => line.GetColumns ()); - int height = GetLines ().Count; + List lines = GetLines (); + + if (lines.Count == 0) + { + return Size.Empty; + } + + int width = lines.Max (static line => line.GetColumns ()); + int height = lines.Count; + + if (IsVerticalDirection (Direction)) + { + return new (height, width); + } return new (width, height); } @@ -1666,13 +1678,13 @@ public static List Format ( private static string PerformCorrectFormatDirection (TextDirection textDirection, string line) { return textDirection switch - { - TextDirection.RightLeft_BottomTop - or TextDirection.RightLeft_TopBottom - or TextDirection.BottomTop_LeftRight - or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), - _ => line - }; + { + TextDirection.RightLeft_BottomTop + or TextDirection.RightLeft_TopBottom + or TextDirection.BottomTop_LeftRight + or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), + _ => line + }; } private static List PerformCorrectFormatDirection (TextDirection textDirection, List runes) @@ -1683,13 +1695,13 @@ private static List PerformCorrectFormatDirection (TextDirection textDirec private static List PerformCorrectFormatDirection (TextDirection textDirection, List lines) { return textDirection switch - { - TextDirection.TopBottom_RightLeft - or TextDirection.LeftRight_BottomTop - or TextDirection.RightLeft_BottomTop - or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), - _ => lines - }; + { + TextDirection.TopBottom_RightLeft + or TextDirection.LeftRight_BottomTop + or TextDirection.RightLeft_BottomTop + or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), + _ => lines + }; } /// Returns the number of lines needed to render the specified text given the width. diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 00952abb47..c8e236d047 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -63,19 +63,25 @@ internal override int Calculate (int location, int superviewContentSize, View us var maxCalculatedSize = 0; int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0; - int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? (dimension == Dimension.Width ? Application.Driver.Screen.Width : Application.Driver.Screen.Height); + int screen = dimension == Dimension.Width ? Application.Screen.Width * 4 : Application.Screen.Height * 4; + int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? screen; if (Style.FastHasFlags (DimAutoStyle.Text)) { if (dimension == Dimension.Width) { - //us.TextFormatter.Size = us.GetContentSize (); + us.TextFormatter.Size = new (autoMax, 2048); textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Width); + us.TextFormatter.Size = new Size (textSize, 2048); } else { - us.TextFormatter.Size = us.GetContentSize () with { Height = Application.Driver.Screen.Height }; + if (us.TextFormatter.Size.Width == 0) + { + us.TextFormatter.Size = us.TextFormatter.GetAutoSize (); + } textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Height); + us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize }; } } @@ -629,15 +635,16 @@ internal override int Calculate (int location, int superviewContentSize, View us // ************** We now definitively know `us.ContentSize` *************** + int oppositeScreen = dimension == Dimension.Width ? Application.Screen.Height * 4 : Application.Screen.Width * 4 ; foreach (var v in us.Subviews) { if (dimension == Dimension.Width) { - v.SetRelativeLayout (new Size (max, Application.Driver.Screen.Width)); + v.SetRelativeLayout (new Size (max, oppositeScreen)); } else { - v.SetRelativeLayout (new Size (Application.Driver.Screen.Height, max)); + v.SetRelativeLayout (new Size (oppositeScreen, max)); } } diff --git a/Terminal.Gui/View/Layout/PosCenter.cs b/Terminal.Gui/View/Layout/PosCenter.cs index 04c7958bbc..b8f7d895ff 100644 --- a/Terminal.Gui/View/Layout/PosCenter.cs +++ b/Terminal.Gui/View/Layout/PosCenter.cs @@ -13,8 +13,9 @@ public class PosCenter : Pos internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension) { + // Protect against negative dimensions int newDimension = Math.Max (dim.Calculate (0, superviewDimension, us, dimension), 0); - return GetAnchor (superviewDimension - newDimension); + return superviewDimension / 2 - newDimension / 2; } } \ No newline at end of file diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index edb64a230c..38463cbba7 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -737,10 +737,10 @@ internal void OnResizeNeeded () // Determine our container's ContentSize - // First try SuperView.Viewport, then Application.Top, then Driver.Viewport. - // Finally, if none of those are valid, use int.MaxValue (for Unit tests). + // Finally, if none of those are valid, use 2048 (for Unit tests). Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.GetContentSize () : Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : - Application.Driver?.Screen.Size ?? new (int.MaxValue, int.MaxValue); + Application.Screen.Size; SetTextFormatterSize (); diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 6b9e0cf94e..a995bc7520 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -1,5 +1,7 @@ #nullable enable +using static Unix.Terminal.Curses; + namespace Terminal.Gui; public partial class View @@ -159,17 +161,6 @@ protected virtual void UpdateTextFormatterText () } } - /// - /// Gets the dimensions required for ignoring a . - /// - /// - internal Size GetSizeNeededForTextWithoutHotKey () - { - return new Size ( - TextFormatter.Size.Width - TextFormatter.GetHotKeySpecifierLength (), - TextFormatter.Size.Height - TextFormatter.GetHotKeySpecifierLength (false)); - } - /// /// Internal API. Sets .Size to the current size, adjusted for /// . @@ -196,6 +187,7 @@ internal void SetTextFormatterSize () if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) || (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) { + // BUGBUG: This ignores wordwrap and other formatting options. size = TextFormatter.GetAutoSize (); if (widthAuto is null || !widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index cc635de003..226af1f917 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/Terminal.Gui/Views/Menu/ContextMenu.cs @@ -144,7 +144,7 @@ public void Show () _container = Application.Current; _container.Closing += Container_Closing; _container.Deactivate += Container_Deactivate; - Rectangle frame = Application.Driver.Screen; + Rectangle frame = Application.Screen; Point position = Position; if (Host is { }) diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index b08b263b51..00247a6131 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -619,7 +619,7 @@ internal Point GetScreenOffset () return Point.Empty; } - Rectangle superViewFrame = SuperView is null ? Driver.Screen : SuperView.Frame; + Rectangle superViewFrame = SuperView is null ? Application.Screen : SuperView.Frame; View sv = SuperView is null ? Application.Current : SuperView; if (sv is null) diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 68d78877ae..f4150d5ab9 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -391,16 +391,13 @@ params string [] buttons { int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; Debug.Assert (d.TextFormatter.WordWrap); - d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Driver.Screen.Height); + d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Screen.Height); Size textSize = d.TextFormatter.GetAutoSize (); textSize.Height += buttonHeight; if (textSize != d.TextFormatter.Size) { - //d.TextFormatter.Size = textSize; - //d.SetContentSize (textSize); d.SetNeedsLayout (); - //d.SetRelativeLayout (Application.Driver.Screen.Size); } } }; @@ -412,10 +409,6 @@ params string [] buttons d.TextFormatter.WordWrap = wrapMessage; d.TextFormatter.MultiLine = !wrapMessage; - // Add two lines to push buttons down two rows - // BUGBUG: The " " are here due to a bug in TextFormater.Format that strips trailing newlines when .Wordwrap = true - // d.Text += Environment.NewLine + " " + Environment.NewLine + " "; - d.ColorScheme = new ColorScheme (d.ColorScheme) { Focus = d.ColorScheme.Normal diff --git a/UICatalog/Scenarios/MessageBoxes.cs b/UICatalog/Scenarios/MessageBoxes.cs index ad206de6de..1cbfc4fdde 100644 --- a/UICatalog/Scenarios/MessageBoxes.cs +++ b/UICatalog/Scenarios/MessageBoxes.cs @@ -47,7 +47,6 @@ public override void Main () { X = 0, Y = Pos.Bottom (label), - Width = Dim.Width (label), Height = 1, TextAlignment = Alignment.End, diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 46c791ab46..272d787802 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -394,7 +394,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport () var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 2, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -419,7 +419,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom () view.Border.Thickness = new Thickness (1, 1, 1, 0); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 2, 1), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -437,7 +437,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left () view.Border.Thickness = new Thickness (0, 1, 1, 1); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 1, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -462,7 +462,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right () view.Border.Thickness = new Thickness (1, 1, 0, 1); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 1, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -488,7 +488,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top () view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 2, 1), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index 6ad50ed164..e133b855c8 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -976,8 +976,9 @@ public void DimAutoStyle_Content_UsesContentSize_WhenSet () public void DimAutoStyle_Content_IgnoresSubviews_When_ContentSize_Is_Set () { var view = new View (); - var subview = new View () { - Frame = new Rectangle (50, 50, 1, 1) + var subview = new View () + { + Frame = new Rectangle (50, 50, 1, 1) }; view.SetContentSize (new (10, 5)); @@ -1045,27 +1046,34 @@ public void With_Subview_Using_DimAbsolute (int subViewOffset, int dimAbsoluteSi [InlineData (1, 50, 51)] [InlineData (0, 25, 25)] [InlineData (-1, 50, 49)] - public void With_Subview_Using_DimFactor (int subViewOffset, int dimFactor, int expectedSize) + public void With_Subview_Using_DimPercent (int subViewOffset, int percent, int expectedSize) { - var view = new View () { Width = 100, Height = 100 }; + var view = new View () + { + Width = 100, Height = 100 + }; var subview = new View () { X = subViewOffset, Y = subViewOffset, - Width = Dim.Percent (dimFactor), - Height = Dim.Percent (dimFactor) + Width = Dim.Percent (percent), + Height = Dim.Percent (percent) }; view.Add (subview); - subview.SetRelativeLayout (new (100, 100)); - - var dim = Dim.Auto (DimAutoStyle.Content); - - int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dim.Calculate (0, 100, view, Dimension.Height); + view.BeginInit (); + view.EndInit (); - Assert.Equal (expectedSize, calculatedWidth); - Assert.Equal (expectedSize, calculatedHeight); + // Assuming the calculation is done after layout + int calculatedX = subview.X.Calculate (100, subview.Width, subview, Dimension.Width); + int calculatedY = subview.Y.Calculate (100, subview.Height, subview, Dimension.Height); + int calculatedWidth = subview.Width.Calculate (0, 100, view, Dimension.Width); + int calculatedHeight = subview.Height.Calculate (0, 100, view, Dimension.Height); + + Assert.Equal (20, calculatedWidth); // subview's width + Assert.Equal (10, calculatedHeight); // subview's height + Assert.Equal (50, calculatedX); // 50% of 100 (Width) + Assert.Equal (50, calculatedY); // 50% of 100 (Height) } [Theory] @@ -1090,7 +1098,7 @@ public void With_Subview_Using_DimFill (int subViewOffset, int dimFillMargin, in }; view.Add (subview); //view.LayoutSubviews (); - view.SetRelativeLayout(new (200,200)); + view.SetRelativeLayout (new (200, 200)); Assert.Equal (expectedSize, view.Frame.Width); } @@ -1136,72 +1144,226 @@ public void With_Subview_Using_DimView () // Testing all Pos combinations - [Fact] - public void With_Subview_At_PosAt () + [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, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (100, 21, 100, 11, 21, 11)] + public void With_Subview_Using_PosAbsolute (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { - var view = new View (); - var subview = new View () { X = Pos.Absolute (10), Y = Pos.Absolute (5), Width = 20, Height = 10 }; + var view = new View () + { + Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) + }; + var subview = new View () + { + X = Pos.Absolute (10), + Y = Pos.Absolute (5), + Width = 20, + Height = 10 + }; view.Add (subview); - var dimWidth = Dim.Auto (); - var dimHeight = Dim.Auto (); + // 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); - int calculatedWidth = dimWidth.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dimHeight.Calculate (0, 100, view, Dimension.Height); + Assert.Equal (expectedWidth, calculatedWidth); + Assert.Equal (expectedHeight, calculatedHeight); - // Expecting the size to include the subview's position and size - Assert.Equal (30, calculatedWidth); // 10 (X position) + 20 (Width) - Assert.Equal (15, calculatedHeight); // 5 (Y position) + 10 (Height) + Assert.Equal (0, calculatedX); + Assert.Equal (0, calculatedY); } - [Fact (Skip = "TextOnly")] - public void With_Subview_At_PosPercent () + [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 (100, 21, 100, 11, 21, 11)] + public void With_Subview_Using_PosPercent (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { - var view = new View () { Width = 100, Height = 100 }; - var subview = new View () { X = Pos.Percent (50), Y = Pos.Percent (50), Width = 20, Height = 10 }; + var view = new View () + { + Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) + }; + var subview = new View () + { + X = Pos.Percent (50), + Y = Pos.Percent (50), + Width = 20, + Height = 10 + }; view.Add (subview); - var dimWidth = Dim.Auto (); - var dimHeight = Dim.Auto (); - // Assuming the calculation is done after layout - int calculatedWidth = dimWidth.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dimHeight.Calculate (0, 100, view, Dimension.Height); - - // Expecting the size to include the subview's position as a percentage of the parent view's size plus the subview's size - Assert.Equal (70, calculatedWidth); // 50% of 100 (Width) + 20 - Assert.Equal (60, calculatedHeight); // 50% of 100 (Height) + 10 + 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); + + view.BeginInit (); + view.EndInit (); + // subview should be at 50% in the parent view + Assert.Equal ((int)(view.Viewport.Width * .50), subview.Frame.X); + Assert.Equal ((int)(view.Viewport.Height * .50), subview.Frame.Y); } - [Fact (Skip = "TextOnly")] - public void With_Subview_At_PosCenter () + [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, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (100, 21, 100, 11, 21, 11)] + public void With_Subview_Using_PosPercent_Combine (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { - var view = new View () { Width = 100, Height = 100 }; - var subview = new View () { X = Pos.Center (), Y = Pos.Center (), Width = 20, Height = 10 }; + var view = new View () + { + Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) + }; + var subview = new View () + { + X = Pos.Percent (50) + 1, + Y = 1 + Pos.Percent (50), + Width = 20, + Height = 10 + }; view.Add (subview); - var dimWidth = Dim.Auto (); - var dimHeight = Dim.Auto (); - // Assuming the calculation is done after layout - int calculatedWidth = dimWidth.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dimHeight.Calculate (0, 100, view, Dimension.Height); + 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); + + view.BeginInit (); + view.EndInit (); + // subview should be at 50% in the parent view + Assert.Equal ((int)(view.Viewport.Width * .50) + 1, subview.Frame.X); + Assert.Equal ((int)(view.Viewport.Height * .50) + 1, subview.Frame.Y); + } + + [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, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (100, 21, 100, 11, 21, 11)] + public void With_Subview_Using_PosCenter (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.Center (), + Y = Pos.Center (), + Width = 20, + Height = 10 + }; + view.Add (subview); - // Expecting the size to include the subview's position at the center of the parent view plus the subview's size - Assert.Equal (70, calculatedWidth); // Centered in 100 (Width) + 20 - Assert.Equal (60, calculatedHeight); // Centered in 100 (Height) + 10 + // 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); + + view.BeginInit (); + view.EndInit (); + // subview should be centered in the parent view + 1 + Assert.Equal (view.Viewport.Width / 2 - subview.Frame.Width / 2, subview.Frame.X); + Assert.Equal (view.Viewport.Height / 2 - subview.Frame.Height / 2, subview.Frame.Y); } - [Fact] - public void With_Subview_At_PosAnchorEnd () + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [InlineData (0, 20, 0, 10, 20, 10)] + [InlineData (0, 21, 0, 11, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (100, 21, 100, 11, 21, 11)] + public void With_Subview_Using_PosCenter_Combine (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { - var dimWidth = Dim.Auto (); - var dimHeight = Dim.Auto (); + var view = new View () + { + Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) + }; + var subview = new View () + { + X = Pos.Center () + 1, + Y = 1 + Pos.Center (), + 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); + + view.BeginInit (); + view.EndInit (); + // subview should be centered in the parent view + 1 + Assert.Equal (view.Viewport.Width / 2 - subview.Frame.Width / 2 + 1, subview.Frame.X); + Assert.Equal (view.Viewport.Height / 2 - subview.Frame.Height / 2 + 1, subview.Frame.Y); + } + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [InlineData (0, 20, 0, 10, 20, 10)] + [InlineData (0, 21, 0, 11, 20, 10)] + [InlineData (1, 21, 1, 11, 20, 10)] + [InlineData (100, 21, 100, 11, 21, 11)] + public void With_Subview_Using_PosAnchorEnd (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) + { var view = new View () { - Width = dimWidth, - Height = dimHeight + Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) }; var subview = new View () @@ -1214,12 +1376,22 @@ public void With_Subview_At_PosAnchorEnd () view.Add (subview); // Assuming the calculation is done after layout - int calculatedWidth = dimWidth.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dimHeight.Calculate (0, 100, view, Dimension.Height); - - // Expecting the size to include the subview's position at the end of the parent view minus the offset plus the subview's size - Assert.Equal (20, calculatedWidth); - Assert.Equal (10, calculatedHeight); + 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); + + view.BeginInit (); + view.EndInit (); + // subview should be at the end of the view + Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); + Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); } [Fact] diff --git a/UnitTests/View/Layout/Pos.CenterTests.cs b/UnitTests/View/Layout/Pos.CenterTests.cs index a17b1132a5..d231df822c 100644 --- a/UnitTests/View/Layout/Pos.CenterTests.cs +++ b/UnitTests/View/Layout/Pos.CenterTests.cs @@ -51,12 +51,31 @@ public void PosCenter_CreatesCorrectInstance () Assert.IsType (pos); } - [Fact] - public void PosCenter_Calculate_ReturnsExpectedValue () + [Theory] + [InlineData (10, 2, 4)] + [InlineData (10, 10, 0)] + [InlineData (10, 11, 0)] + [InlineData (10, 12, -1)] + [InlineData (19, 20, -1)] + public void PosCenter_Calculate_ReturnsExpectedValue (int superviewDimension, int width, int expectedX) { var posCenter = new PosCenter (); - int result = posCenter.Calculate (10, new DimAbsolute (2), null, Dimension.None); - Assert.Equal (4, result); + int result = posCenter.Calculate (superviewDimension, new DimAbsolute (width), null!, Dimension.Width); + Assert.Equal (expectedX, result); + } + + + [Fact] + public void PosCenter_Bigger_Than_SuperView () + { + var superView = new View { Width = 10, Height = 10 }; + var view = new View { X = Center (), Y = Center (), Width = 20, Height = 20 }; + superView.Add (view); + superView.BeginInit(); + superView.EndInit(); + + Assert.Equal (-5, view.Frame.Left); + Assert.Equal (-5, view.Frame.Top); } [Theory] diff --git a/UnitTests/View/Layout/Pos.PercentTests.cs b/UnitTests/View/Layout/Pos.PercentTests.cs index 1614c46ce3..d016c709c8 100644 --- a/UnitTests/View/Layout/Pos.PercentTests.cs +++ b/UnitTests/View/Layout/Pos.PercentTests.cs @@ -7,6 +7,20 @@ public class PosPercentTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; + [Theory] + [InlineData (50, 10, 2, 5)] + [InlineData (50, 10, 10, 5)] + [InlineData (50, 10, 11, 5)] + [InlineData (50, 10, 12, 5)] + [InlineData (50, 19, 20, 9)] + public void PosPercent_Calculate_ReturnsExpectedValue (int percent, int superviewDimension, int width, int expectedX) + { + var posPercent = new PosPercent (percent); + int result = posPercent.Calculate (superviewDimension, new DimAbsolute (width), null!, Dimension.Width); + Assert.Equal (expectedX, result); + } + + // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved // TODO: A new test that calls SetRelativeLayout directly is needed. [Theory] diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index 3bd6cfb230..278ca3ed00 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -778,56 +778,7 @@ string GetContents () Application.End (rs); top.Dispose (); } - - [Fact] - [AutoInitShutdown] - public void GetTextFormatterBoundsSize_GetSizeNeededForText_HotKeySpecifier () - { - var text = "Say Hello 你"; - - // Frame: 0, 0, 12, 1 - var horizontalView = new View - { - Width = Dim.Auto (), Height = Dim.Auto () - }; - horizontalView.TextFormatter.HotKeySpecifier = (Rune)'_'; - horizontalView.Text = text; - - // Frame: 0, 0, 1, 12 - var verticalView = new View - { - Width = Dim.Auto (), Height = Dim.Auto (), TextDirection = TextDirection.TopBottom_LeftRight - }; - verticalView.Text = text; - verticalView.TextFormatter.HotKeySpecifier = (Rune)'_'; - - var top = new Toplevel (); - top.Add (horizontalView, verticalView); - Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (50, 50); - - Assert.Equal (new (0, 0, 12, 1), horizontalView.Frame); - Assert.Equal (new (12, 1), horizontalView.GetSizeNeededForTextWithoutHotKey ()); - Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ()); - - Assert.Equal (new (0, 0, 2, 11), verticalView.Frame); - Assert.Equal (new (2, 11), verticalView.GetSizeNeededForTextWithoutHotKey ()); - Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ()); - - text = "012345678你"; - horizontalView.Text = text; - verticalView.Text = text; - - Assert.Equal (new (0, 0, 11, 1), horizontalView.Frame); - Assert.Equal (new (11, 1), horizontalView.GetSizeNeededForTextWithoutHotKey ()); - Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ()); - - Assert.Equal (new (0, 0, 2, 10), verticalView.Frame); - Assert.Equal (new (2, 10), verticalView.GetSizeNeededForTextWithoutHotKey ()); - Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ()); - top.Dispose (); - } - + [Theory] [AutoInitShutdown] [InlineData (true)] diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 4044507f1a..cc030733e6 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -833,10 +833,10 @@ public void New_Initializes () TextDirection = TextDirection.TopBottom_LeftRight, Width = Dim.Auto (), Height = Dim.Auto () - }; // BUGBUG: AutoSize or Height need be set + }; + r.TextFormatter.WordWrap = false; Assert.NotNull (r); - // BUGBUG: IsInitialized must be true to process calculation r.BeginInit (); r.EndInit (); Assert.False (r.CanFocus); diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index 24a19c77dc..baeab4aa92 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -473,7 +473,7 @@ public void Full_Border () var label = new Label { BorderStyle = LineStyle.Single, Text = "Test" }; label.BeginInit (); label.EndInit (); - label.SetRelativeLayout (Application.Driver.Screen.Size); + label.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 4, 1), label.Viewport); Assert.Equal (new (0, 0, 6, 3), label.Frame); diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs index f260dd162c..da7720ebab 100644 --- a/UnitTests/Views/TextViewTests.cs +++ b/UnitTests/Views/TextViewTests.cs @@ -6900,7 +6900,7 @@ This is the second line. ); ((FakeDriver)Application.Driver).SetBufferSize (6, 25); - tv.SetRelativeLayout (Application.Driver.Screen.Size); + tv.SetRelativeLayout (Application.Screen.Size); tv.Draw (); Assert.Equal (new Point (4, 2), tv.CursorPosition); Assert.Equal (new Point (12, 0), cp); From 0c10d4b121d070b7922c731ebcfbb9dc3ed7a06c Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 15 Jul 2024 16:47:01 -0600 Subject: [PATCH 09/96] More unit tests --- Terminal.Gui/View/Layout/DimAuto.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index c8e236d047..a7cbb82c8f 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -71,7 +71,7 @@ internal override int Calculate (int location, int superviewContentSize, View us if (dimension == Dimension.Width) { us.TextFormatter.Size = new (autoMax, 2048); - textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Width); + textSize = us.TextFormatter.FormatAndGetSize ().Width; us.TextFormatter.Size = new Size (textSize, 2048); } else @@ -80,7 +80,7 @@ internal override int Calculate (int location, int superviewContentSize, View us { us.TextFormatter.Size = us.TextFormatter.GetAutoSize (); } - textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Height); + textSize = us.TextFormatter.FormatAndGetSize ().Height; us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize }; } } From f51e38d25e6946b8d0c50be2e009b99960c7b541 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 09:35:45 -0600 Subject: [PATCH 10/96] Fixed TextFormatter.FormatAndGetSize for wide vertical text --- Terminal.Gui/Text/TextFormatter.cs | 3 +- Terminal.Gui/View/Layout/DimAuto.cs | 2 +- UnitTests/View/TextTests.cs | 71 ++++++----------------------- 3 files changed, 17 insertions(+), 59 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index d54451139b..106f035b49 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -667,7 +667,8 @@ public Size FormatAndGetSize () if (IsVerticalDirection (Direction)) { - return new (height, width); + height = GetColumnsRequiredForVerticalText(lines, 0, lines.Count, TabWidth); + return new (height, lines.Max (static line => line.Length)); } return new (width, height); diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index a7cbb82c8f..473bb2fb50 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -70,7 +70,7 @@ internal override int Calculate (int location, int superviewContentSize, View us { if (dimension == Dimension.Width) { - us.TextFormatter.Size = new (autoMax, 2048); + us.TextFormatter.Size = new (superviewContentSize, 2048); textSize = us.TextFormatter.FormatAndGetSize ().Width; us.TextFormatter.Size = new Size (textSize, 2048); } diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index 278ca3ed00..fafb058356 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -472,8 +472,8 @@ public void AutoSize_True_View_IsEmpty_False_Minimum_Width () } [Fact] - [AutoInitShutdown] - public void AutoSize_True_View_IsEmpty_False_Minimum_Width_Wide_Rune () + [SetupFakeDriver] + public void DimAuto_Vertical_TextDirection_Wide_Rune () { var text = "界View"; @@ -484,69 +484,26 @@ public void AutoSize_True_View_IsEmpty_False_Minimum_Width_Wide_Rune () Width = Dim.Auto (), Height = Dim.Auto () }; - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (view); - var top = new Toplevel (); - top.Add (win); - Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (4, 10); + + view.SetRelativeLayout (new Size (4, 10)); Assert.Equal (5, text.Length); + + // Vertical text - 2 wide, 5 down Assert.Equal (new (0, 0, 2, 5), view.Frame); Assert.Equal (new (2, 5), view.TextFormatter.Size); Assert.Equal (new () { "界View" }, view.TextFormatter.GetLines ()); - Assert.Equal (new (0, 0, 4, 10), win.Frame); - Assert.Equal (new (0, 0, 4, 10), Application.Top.Frame); + + view.Draw (); var expected = @" -┌──┐ -│界│ -│V │ -│i │ -│e │ -│w │ -│ │ -│ │ -│ │ -└──┘ -"; +界 +V +i +e +w "; Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 4, 10), pos); - - text = "0123456789"; - Assert.Equal (10, text.Length); - - //view.Height = Dim.Fill () - text.Length; - Application.Refresh (); - - Assert.Equal (new (0, 0, 2, 5), view.Frame); - Assert.Equal (new (2, 5), view.TextFormatter.Size); - - Exception exception = Record.Exception ( - () => Assert.Equal ( - new () { "界View" }, - view.TextFormatter.GetLines () - ) - ); - Assert.Null (exception); - - expected = @" -┌──┐ -│界│ -│V │ -│i │ -│e │ -│w │ -│ │ -│ │ -│ │ -└──┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (0, 0, 4, 10), pos); - top.Dispose (); } [Fact] @@ -778,7 +735,7 @@ string GetContents () Application.End (rs); top.Dispose (); } - + [Theory] [AutoInitShutdown] [InlineData (true)] From 06339c98fe0426b1bc5c91226fd855af0ce8fd5d Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 09:38:50 -0600 Subject: [PATCH 11/96] Fixed ShadowTests --- UnitTests/View/Adornment/ShadowStyletests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UnitTests/View/Adornment/ShadowStyletests.cs b/UnitTests/View/Adornment/ShadowStyletests.cs index 967720e369..00193ad24b 100644 --- a/UnitTests/View/Adornment/ShadowStyletests.cs +++ b/UnitTests/View/Adornment/ShadowStyletests.cs @@ -99,15 +99,15 @@ public void ShadowStyle_Margin_Thickness (ShadowStyle style, int expectedLeft, i [InlineData ( ShadowStyle.Transparent, """ - 011 - 131 + 031 + 331 111 """)] [InlineData ( ShadowStyle.Opaque, """ - 011 - 121 + 021 + 221 111 """)] [SetupFakeDriver] @@ -182,7 +182,7 @@ public void Style_Changes_Magin_Thickness (ShadowStyle style, int expected) [InlineData ( ShadowStyle.Opaque, """ - 01#$ + 01▖$ AB▌$ !▀▘$ !@#$ From ce5fc1c81ef1c3b9a2d287dc6113ab64a889a408 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 09:43:34 -0600 Subject: [PATCH 12/96] Fixed ShadowTests --- Terminal.Gui/Text/TextFormatter.cs | 13 +++++++++---- UnitTests/View/Adornment/ShadowStyletests.cs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 106f035b49..8b2eb47eba 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -662,13 +662,18 @@ public Size FormatAndGetSize () return Size.Empty; } - int width = lines.Max (static line => line.GetColumns ()); - int height = lines.Count; + int width; + int height; if (IsVerticalDirection (Direction)) { - height = GetColumnsRequiredForVerticalText(lines, 0, lines.Count, TabWidth); - return new (height, lines.Max (static line => line.Length)); + width = GetColumnsRequiredForVerticalText (lines, 0, lines.Count, TabWidth); + height = lines.Max (static line => line.Length); + } + else + { + width = lines.Max (static line => line.GetColumns()); + height = lines.Count; } return new (width, height); diff --git a/UnitTests/View/Adornment/ShadowStyletests.cs b/UnitTests/View/Adornment/ShadowStyletests.cs index 00193ad24b..88ab6df093 100644 --- a/UnitTests/View/Adornment/ShadowStyletests.cs +++ b/UnitTests/View/Adornment/ShadowStyletests.cs @@ -184,7 +184,7 @@ public void Style_Changes_Magin_Thickness (ShadowStyle style, int expected) """ 01▖$ AB▌$ - !▀▘$ + ▝▀▘$ !@#$ """)] [InlineData ( From 3b8319b802e57901daf671daab18f49ed5e26e72 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 11:08:22 -0600 Subject: [PATCH 13/96] Added low-level TextFormatter.FormatAndGetSize tests --- UnitTests/Text/TextFormatterTests.cs | 109 +++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 6746f8ad71..85f3ced33d 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -1,4 +1,5 @@ using System.Text; +using Microsoft.VisualStudio.TestPlatform.Utilities; using Xunit.Abstractions; using static Terminal.Gui.SpinnerStyle; @@ -6045,4 +6046,112 @@ public void Draw_Text_Justification (string text, Alignment horizontalTextAlignm tf.Draw (new Rectangle (0, 0, 7, 7), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } + + #region FormatAndGetSizeTests + + // TODO: Add multi-line examples + // TODO: Add other TextDirection examples + + [Theory] + [SetupFakeDriver] + [InlineData ("界1234", 10, 10, TextDirection.LeftRight_TopBottom, 6, 1, @"界1234")] + [InlineData ("01234", 10, 10, TextDirection.LeftRight_TopBottom, 5, 1, @"01234")] + [InlineData ("界1234", 10, 10, TextDirection.TopBottom_LeftRight, 2, 5, """ + 界 + 1 + 2 + 3 + 4 + """)] + [InlineData ("01234", 10, 10, TextDirection.TopBottom_LeftRight, 1, 5, """ + 0 + 1 + 2 + 3 + 4 + """)] + [InlineData ("界1234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 2, """ + 界1 + 234 + """)] + [InlineData ("01234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 2, """ + 012 + 34 + """)] + [InlineData ("界1234", 3, 3, TextDirection.TopBottom_LeftRight, 3, 3, """ + 界3 + 1 4 + 2 + """)] + [InlineData ("01234", 3, 3, TextDirection.TopBottom_LeftRight, 2, 3, """ + 03 + 14 + 2 + """)] + public void FormatAndGetSize_Returns_Correct_Size (string text, int width, int height, TextDirection direction, int expectedWidth, int expectedHeight, string expectedDraw) + { + TextFormatter tf = new () + { + Direction = direction, + Size = new (width, height), + Text = text + }; + Assert.True (tf.WordWrap); + Size size = tf.FormatAndGetSize (); + Assert.Equal (new (expectedWidth, expectedHeight), size); + + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedDraw, _output); + } + + [Theory] + [SetupFakeDriver] + [InlineData ("界1234", 10, 10, TextDirection.LeftRight_TopBottom, 6, 1, @"界1234")] + [InlineData ("01234", 10, 10, TextDirection.LeftRight_TopBottom, 5, 1, @"01234")] + [InlineData ("界1234", 10, 10, TextDirection.TopBottom_LeftRight, 2, 5, """ + 界 + 1 + 2 + 3 + 4 + """)] + [InlineData ("01234", 10, 10, TextDirection.TopBottom_LeftRight, 1, 5, """ + 0 + 1 + 2 + 3 + 4 + """)] + [InlineData ("界1234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 1, @"界1")] + [InlineData ("01234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 1, @"012")] + [InlineData ("界1234", 3, 3, TextDirection.TopBottom_LeftRight, 2, 3, """ + 界 + 1 + 2 + """)] + [InlineData ("01234", 3, 3, TextDirection.TopBottom_LeftRight, 1, 3, """ + 0 + 1 + 2 + """)] + public void FormatAndGetSize_WordWrap_False_Returns_Correct_Size (string text, int width, int height, TextDirection direction, int expectedWidth, int expectedHeight, string expectedDraw) + { + TextFormatter tf = new () + { + Direction = direction, + Size = new (width, height), + Text = text, + WordWrap = false + }; + Assert.False (tf.WordWrap); + Size size = tf.FormatAndGetSize (); + Assert.Equal (new (expectedWidth, expectedHeight), size); + + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedDraw, _output); + } + + #endregion } From 0e7e1e2c1ed7c5c680d0f72646c73c0dfc1bd678 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 11:10:09 -0600 Subject: [PATCH 14/96] Fixed bogus Title test --- UnitTests/View/TitleTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/View/TitleTests.cs b/UnitTests/View/TitleTests.cs index ca154583e3..9013546090 100644 --- a/UnitTests/View/TitleTests.cs +++ b/UnitTests/View/TitleTests.cs @@ -37,7 +37,7 @@ public void Change_View_Size_Update_Title_Size () Assert.Equal (text, view.Text); // SetupFakeDriver only create a screen with 25 cols and 25 rows - Assert.Equal (new (text.Length, 1), view.GetContentSize ()); + Assert.Equal (new (25, 3), view.GetContentSize ()); top.Dispose (); } From e93eebb7083cacf9d421d58bae33392eca5c4006 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 18:40:51 -0600 Subject: [PATCH 15/96] Fixed little things. --- Terminal.Gui/Text/TextFormatter.cs | 11 +- Terminal.Gui/View/Layout/DimAuto.cs | 370 ++++++------------------- Terminal.Gui/View/Layout/ViewLayout.cs | 5 +- Terminal.Gui/View/ViewText.cs | 28 +- Terminal.Gui/Views/Dialog.cs | 4 +- Terminal.Gui/Views/Shortcut.cs | 4 +- UICatalog/Scenarios/Dialogs.cs | 20 +- UnitTests/Text/TextFormatterTests.cs | 62 +++++ UnitTests/View/Layout/Dim.AutoTests.cs | 328 +++++++++++----------- UnitTests/Views/ShortcutTests.cs | 3 +- 10 files changed, 367 insertions(+), 468 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 8b2eb47eba..84500c5ed6 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -648,14 +648,21 @@ public string Format () /// Gets the size required to hold the formatted text, given the constraints placed by . /// Causes a format, resetting to . /// The size required to hold the formatted text. - public Size FormatAndGetSize () + public Size FormatAndGetSize (Size? constrainSize = null) { - if (string.IsNullOrEmpty (Text) || Size.Height == 0 || Size.Width == 0) + if (constrainSize is null) + { + constrainSize = Size; + } + if (string.IsNullOrEmpty (Text) || constrainSize.Value.Height == 0 || constrainSize.Value.Width == 0) { return Size.Empty; } + Size prevSize = Size; + Size = constrainSize.Value; List lines = GetLines (); + Size = prevSize; if (lines.Count == 0) { diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 473bb2fb50..fedc96188b 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -66,25 +66,30 @@ internal override int Calculate (int location, int superviewContentSize, View us int screen = dimension == Dimension.Width ? Application.Screen.Width * 4 : Application.Screen.Height * 4; int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? screen; + Debug.Assert (autoMin <= autoMax, "MinimumContentDim must be less than or equal to MaximumContentDim."); + if (Style.FastHasFlags (DimAutoStyle.Text)) { if (dimension == Dimension.Width) { - us.TextFormatter.Size = new (superviewContentSize, 2048); + //us.TextFormatter.Size = new (superviewContentSize, 2048); textSize = us.TextFormatter.FormatAndGetSize ().Width; - us.TextFormatter.Size = new Size (textSize, 2048); + //us.TextFormatter.Size = new Size (textSize, 2048); } else { - if (us.TextFormatter.Size.Width == 0) - { - us.TextFormatter.Size = us.TextFormatter.GetAutoSize (); - } + //if (us.TextFormatter.Size.Width == 0) + //{ + // us.TextFormatter.Size = us.TextFormatter.GetAutoSize (); + //} textSize = us.TextFormatter.FormatAndGetSize ().Height; - us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize }; + //us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize }; } } + List viewsNeedingLayout = new List (); + + if (Style.FastHasFlags (DimAutoStyle.Content)) { if (!us.ContentSizeTracksViewport) @@ -105,14 +110,14 @@ internal override int Calculate (int location, int superviewContentSize, View us // -------------------- Pos types that are dependent on `us.Subviews` // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` // [x] PosView - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize` - // [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize` + // [x] PosCombine - Position is dependent if `Pos.Has [one of the above]` - it can cause a change in `us.ContentSize` // -------------------- Pos types that are dependent on `us.ContentSize` // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` - it can cause a change in `us.ContentSize` - // [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame` - // [ ] PosPercent - Position is dependent `us.ContentSize` - // [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize` + // [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame` - + // [ ] PosPercent - Position is dependent `us.ContentSize` - Will always be 0 if there is no other content that makes the superview have a size. + // [x] PosCombine - Position is dependent if `Pos.Has [one of the above]` - it can cause a change in `us.ContentSize` // -------------------- Pos types that are not dependent on either `us.Subviews` or `us.ContentSize` // [ ] PosAbsolute - Position is fixed. @@ -120,12 +125,12 @@ internal override int Calculate (int location, int superviewContentSize, View us // -------------------- Dim types that are dependent on `us.Subviews` // [x] DimView - Dimension is dependent on `subview.Target` - // [x] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` - it can cause a change in `us.ContentSize` + // [x] DimCombine - Dimension is dependent if `Dim.Has [one of the above]` - it can cause a change in `us.ContentSize` // -------------------- Dim types that are dependent on `us.ContentSize` - // [ ] DimFill - Dimension is dependent on `us.ContentSize` - // [ ] DimPercent - Dimension is dependent on `us.ContentSize` - // [ ] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` + // [ ] DimFill - Dimension is dependent on `us.ContentSize` - Will always be 0 if there is no other content that makes the superview have a size. + // [ ] DimPercent - Dimension is dependent on `us.ContentSize` - Will always be 0 if there is no other content that makes the superview have a size. + // [ ] DimCombine - Dimension is dependent if `Dim.Has [one of the above]` // -------------------- Dim types that are not dependent on either `us.Subviews` or `us.ContentSize` // [ ] DimAuto - Dimension is internally calculated @@ -143,23 +148,29 @@ internal override int Calculate (int location, int superviewContentSize, View us List notDependentSubViews; if (dimension == Dimension.Width) { - notDependentSubViews = includedSubviews.Where (v => v.Width is { } && - (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) && - !v.X.Has (typeof (PosAnchorEnd), out _) && - !v.X.Has (typeof (PosAlign), out _) && - !v.X.Has (typeof (PosView), out _) && - !v.Width.Has (typeof (DimView), out _) && - !v.X.Has (typeof (PosCenter), out _)).ToList (); + notDependentSubViews = includedSubviews.Where (v => v.Width is { } + && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) + && !v.X.Has (typeof (PosAnchorEnd), out _) + && !v.X.Has (typeof (PosAlign), out _) + && !v.X.Has (typeof (PosView), out _) + && !v.X.Has (typeof (PosCenter), out _) + && !v.Width.Has (typeof (DimView), out _) + && !v.Width.Has (typeof (DimFill), out _) + && !v.Width.Has (typeof (DimPercent), out _) + ).ToList (); } else { - notDependentSubViews = includedSubviews.Where (v => v.Height is { } && - (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) && - !v.Y.Has (typeof (PosAnchorEnd), out _) && - !v.Y.Has (typeof (PosAlign), out _) && - !v.Y.Has (typeof (PosView), out _) && - !v.Height.Has (typeof (DimView), out _) && - !v.Y.Has (typeof (PosCenter), out _)).ToList (); + notDependentSubViews = includedSubviews.Where (v => v.Height is { } + && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) + && !v.Y.Has (typeof (PosAnchorEnd), out _) + && !v.Y.Has (typeof (PosAlign), out _) + && !v.Y.Has (typeof (PosView), out _) + && !v.Y.Has (typeof (PosCenter), out _) + && !v.Height.Has (typeof (DimView), out _) + && !v.Height.Has (typeof (DimFill), out _) + && !v.Height.Has (typeof (DimPercent), out _) + ).ToList (); } for (var i = 0; i < notDependentSubViews.Count; i++) @@ -170,12 +181,12 @@ internal override int Calculate (int location, int superviewContentSize, View us if (dimension == Dimension.Width) { - int width = v.Width!.Calculate (0, 0, v, dimension); + int width = v.Width!.Calculate (0, superviewContentSize, v, dimension); size = v.X.GetAnchor (0) + width; } else { - int height = v.Height!.Calculate (0, 0, v, dimension); + int height = v.Height!.Calculate (0, superviewContentSize, v, dimension); size = v.Y.GetAnchor (0) + height; } @@ -199,6 +210,8 @@ internal override int Calculate (int location, int superviewContentSize, View us centeredSubViews = us.Subviews.Where (v => v.Y.Has (typeof (PosCenter), out _)).ToList (); } + viewsNeedingLayout.AddRange (centeredSubViews); + int maxCentered = 0; for (var i = 0; i < centeredSubViews.Count; i++) @@ -207,48 +220,51 @@ internal override int Calculate (int location, int superviewContentSize, View us if (dimension == Dimension.Width) { - int width = v.Width!.Calculate (0, 0, v, dimension); - maxCentered = (v.X.GetAnchor (0) + width) * 2; + int width = v.Width!.Calculate (0, screen, v, dimension); + maxCentered = (v.X.GetAnchor (0) + width); } else { - int height = v.Height!.Calculate (0, 0, v, dimension); - maxCentered = (v.Y.GetAnchor (0) + height) * 2; + int height = v.Height!.Calculate (0, screen, v, dimension); + maxCentered = (v.Y.GetAnchor (0) + height); } } maxCalculatedSize = int.Max (maxCalculatedSize, maxCentered); #endregion Centered #region Percent - // [ ] DimPercent - Dimension is dependent on `us.ContentSize` - List percentSubViews; - if (dimension == Dimension.Width) - { - percentSubViews = us.Subviews.Where (v => v.Width.Has (typeof (DimPercent), out _)).ToList (); - } - else - { - percentSubViews = us.Subviews.Where (v => v.Height.Has (typeof (DimPercent), out _)).ToList (); - } - - int maxPercent = 0; - - for (var i = 0; i < percentSubViews.Count; i++) - { - View v = percentSubViews [i]; - - if (dimension == Dimension.Width) - { - int width = v.Width!.Calculate (0, 0, v, dimension); - maxPercent = (v.X.GetAnchor (0) + width); - } - else - { - int height = v.Height!.Calculate (0, 0, v, dimension); - maxPercent = (v.Y.GetAnchor (0) + height); - } - } - maxCalculatedSize = int.Max (maxCalculatedSize, maxPercent); + //// [ ] DimPercent - Dimension is dependent on `us.ContentSize` + //// - DimPercent will always be 0 if there is no other content that makes the superview have a size. + //List dimPercentSubViews; + //if (dimension == Dimension.Width) + //{ + // dimPercentSubViews = us.Subviews.Where (v => v.Width.Has (typeof (DimPercent), out _)).ToList (); + //} + //else + //{ + // dimPercentSubViews = us.Subviews.Where (v => v.Height.Has (typeof (DimPercent), out _)).ToList (); + //} + + //viewsNeedingLayout.AddRange (dimPercentSubViews); + + //int maxDimPercent = 0; + + //for (var i = 0; i < dimPercentSubViews.Count; i++) + //{ + // View v = dimPercentSubViews [i]; + + // if (dimension == Dimension.Width) + // { + // int width = v.Width!.Calculate (0, superviewContentSize, null, dimension); + // maxDimPercent = (v.X.GetAnchor (0) + width); + // } + // else + // { + // int height = v.Height!.Calculate (0, superviewContentSize, null, dimension); + // maxDimPercent = (v.Y.GetAnchor (0) + height); + // } + //} + //maxCalculatedSize = int.Max (maxCalculatedSize, maxDimPercent); #endregion Percent @@ -317,6 +333,8 @@ internal override int Calculate (int location, int superviewContentSize, View us anchoredSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosAnchorEnd), out _)).ToList (); } + viewsNeedingLayout.AddRange (anchoredSubViews); + int maxAnchorEnd = 0; for (var i = 0; i < anchoredSubViews.Count; i++) { @@ -325,13 +343,13 @@ internal override int Calculate (int location, int superviewContentSize, View us // Need to set the relative layout for PosAnchorEnd subviews to calculate the size if (dimension == Dimension.Width) { - v.SetRelativeLayout (new Size (maxCalculatedSize, 0)); + v.SetRelativeLayout (new Size (maxCalculatedSize, screen)); } else { - v.SetRelativeLayout (new Size (0, maxCalculatedSize)); + v.SetRelativeLayout (new Size (screen, maxCalculatedSize)); } - maxAnchorEnd = dimension == Dimension.Width ? v.X.GetAnchor (maxCalculatedSize) + v.Frame.Width : v.Y.GetAnchor (maxCalculatedSize) + v.Frame.Height; + maxAnchorEnd = dimension == Dimension.Width ? v.X.GetAnchor (maxCalculatedSize + v.Frame.Width) : v.Y.GetAnchor (maxCalculatedSize + v.Frame.Height); } maxCalculatedSize = Math.Max (maxCalculatedSize, maxAnchorEnd); @@ -407,218 +425,6 @@ internal override int Calculate (int location, int superviewContentSize, View us } } #endregion DimView - - - // [x] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` - it can cause a change in `us.ContentSize` - - - - - - - - // // ====================================================== - // // Now do PosAlign - It's dependent on other views with `GroupId` AND `us.ContentSize` - // // ====================================================== - // // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` - // #region Aligned - - // int maxAlign = 0; - // if (dimension == Dimension.Width) - // { - // // Use Linq to get a list of distinct GroupIds from the subviews - // List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); - - // foreach (var groupId in groupIds) - // { - // List dimensionsList = new (); - - // // 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 - // }; - // }) - // .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) - // .ToList (); - - // if (posAlignsInGroup.Count == 0) - // { - // continue; - // } - // // BUGBUG: ignores adornments - - // maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension); - // } - // } - // else - // { - - // // BUGBUG: Incompletge - // subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); - // } - - // maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign); - // #endregion Aligned - - // // TODO: This whole body of code is a WIP (forhttps://github.com/gui-cs/Terminal.Gui/issues/3499). - - - // List subviews; - - // #region Not Anchored and Are Not Dependent - // // Start with subviews that are not anchored to the end, aligned, or dependent on content size - // // [x] PosAnchorEnd - // // [x] PosAlign - // // [ ] PosCenter - // // [ ] PosPercent - // // [ ] PosView - // // [ ] PosFunc - // // [x] DimFill - // // [ ] DimPercent - // // [ ] DimFunc - // // [ ] DimView - // if (dimension == Dimension.Width) - // { - // subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd - // && v.X is not PosAlign - // // && v.X is not PosCenter - // && v.Width is not DimAuto - // && v.Width is not DimFill).ToList (); - // } - // else - // { - // subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd - // && v.Y is not PosAlign - // // && v.Y is not PosCenter - // && v.Height is not DimAuto - // && v.Height is not DimFill).ToList (); - // } - - // for (var i = 0; i < subviews.Count; i++) - // { - // View v = subviews [i]; - - // int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; - - // if (size > maxCalculatedSize) - // { - // // BUGBUG: Should we break here? Or choose min/max? - // maxCalculatedSize = size; - // } - // } - // #endregion Not Anchored and Are Not Dependent - - - - // #region Auto - - - - // #endregion Auto - - // //#region Center - // //// Now, handle subviews that are Centered - // //if (dimension == Dimension.Width) - // //{ - // // subviews = us.Subviews.Where (v => v.X is PosCenter).ToList (); - // //} - // //else - // //{ - // // subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList (); - // //} - - // //int maxCenter = 0; - // //for (var i = 0; i < subviews.Count; i++) - // //{ - // // View v = subviews [i]; - // // maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; - // //} - - // //subviewsSize += maxCenter; - // //#endregion Center - - // #region Are Dependent - // // Now, go back to those that are dependent on content size - - - // // Set relative layout for all DimAuto subviews - // List dimAutoSubViews; - // int maxAuto = 0; - // if (dimension == Dimension.Width) - // { - // dimAutoSubViews = includedSubviews.Where (v => v.Width is DimAuto).ToList (); - // } - // else - // { - // dimAutoSubViews = includedSubviews.Where (v => v.Height is DimAuto).ToList (); - // } - // for (var i = 0; i < dimAutoSubViews.Count; i++) - // { - // View v = dimAutoSubViews [i]; - - // if (dimension == Dimension.Width) - // { - // // BUGBUG: ignores adornments - - // v.SetRelativeLayout (new Size (autoMax - maxCalculatedSize, 0)); - // } - // else - // { - // // BUGBUG: ignores adornments - - // v.SetRelativeLayout (new Size (0, autoMax - maxCalculatedSize)); - // } - - // maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; - - // if (maxAuto > maxCalculatedSize) - // { - // // BUGBUG: Should we break here? Or choose min/max? - // maxCalculatedSize = maxAuto; - // } - // } - - // // [x] DimFill - // // [ ] DimPercent - // if (dimension == Dimension.Width) - // { - // subviews = includedSubviews.Where (v => v.Width is DimFill).ToList (); - // } - // else - // { - // subviews = includedSubviews.Where (v => v.Height is DimFill).ToList (); - // } - - // int maxFill = 0; - // for (var i = 0; i < subviews.Count; i++) - // { - // View v = subviews [i]; - - // if (autoMax == int.MaxValue) - // { - // autoMax = superviewContentSize; - // } - // if (dimension == Dimension.Width) - // { - // // BUGBUG: ignores adornments - // v.SetRelativeLayout (new Size (autoMax - maxCalculatedSize, 0)); - // } - // else - // { - // // BUGBUG: ignores adornments - // v.SetRelativeLayout (new Size (0, autoMax - maxCalculatedSize)); - // } - // maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; - // } - - // maxCalculatedSize += maxFill; - // #endregion Are Dependent } } @@ -632,11 +438,11 @@ internal override int Calculate (int location, int superviewContentSize, View us // And, if max: is set, it wins if smaller max = int.Min (max, autoMax); - // ************** We now definitively know `us.ContentSize` *************** - int oppositeScreen = dimension == Dimension.Width ? Application.Screen.Height * 4 : Application.Screen.Width * 4 ; - foreach (var v in us.Subviews) + int oppositeScreen = dimension == Dimension.Width ? Application.Screen.Height * 4 : Application.Screen.Width * 4; + + foreach (var v in viewsNeedingLayout) { if (dimension == Dimension.Width) { diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 38463cbba7..d3d241d20e 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -573,6 +573,8 @@ internal void SetRelativeLayout (Size superviewContentSize) Debug.Assert (_height is { }); CheckDimAuto (); + SetTextFormatterSize (); + int newX, newW, newY, newH; // Calculate the new X, Y, Width, and Height @@ -630,7 +632,6 @@ internal void SetRelativeLayout (Size superviewContentSize) { SetTitleTextFormatterSize (); } - SetNeedsLayout (); SetNeedsDisplay (); } @@ -742,8 +743,6 @@ internal void OnResizeNeeded () Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : Application.Screen.Size; - SetTextFormatterSize (); - SetRelativeLayout (superViewContentSize); if (IsInitialized) diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index a995bc7520..f91b976422 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -187,18 +187,38 @@ internal void SetTextFormatterSize () if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) || (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) { - // BUGBUG: This ignores wordwrap and other formatting options. - size = TextFormatter.GetAutoSize (); + int width = 0; + int height = 0; if (widthAuto is null || !widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) { - size.Width = GetContentSize ().Width; + width = GetContentSize ().Width; } if (heightAuto is null || !heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) { - size.Height = GetContentSize ().Height; + height = GetContentSize ().Height; } + + if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) + { + if (height == 0 && heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) + { + height = Application.Screen.Height; + } + width = TextFormatter.FormatAndGetSize (new (Application.Screen.Width, height)).Width; + } + + if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) + { + if (width == 0 && widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) + { + width = Application.Screen.Height; + } + height = TextFormatter.FormatAndGetSize (new (width, Application.Screen.Height)).Height; + } + + size = new (width, height); } TextFormatter.Size = size; diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index f10e831a3c..7b4da93557 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -33,14 +33,14 @@ public class Dialog : Window /// . /// [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] - public static int DefaultMinimumWidth { get; set; } = 0; + public static int DefaultMinimumWidth { get; set; } = 80; /// /// Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via /// . /// [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] - public static int DefaultMinimumHeight { get; set; } = 0; + public static int DefaultMinimumHeight { get; set; } = 80; /// diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 7ddbe7c5a0..7f1469522d 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -484,7 +484,7 @@ public override string Text get => HelpView?.Text; set { - if (HelpView != null) + if (HelpView is {}) { HelpView.Text = value; ShowHide (); @@ -500,7 +500,7 @@ public string HelpText get => HelpView?.Text; set { - if (HelpView != null) + if (HelpView is {}) { HelpView.Text = value; ShowHide (); diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index c960669f4e..26764b8a88 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -79,7 +79,12 @@ public override void Main () frame.Add (heightEdit); frame.Add ( - new Label { X = Pos.Right (widthEdit) + 2, Y = Pos.Top (widthEdit), Text = "If height & width are both 0," } + new Label + { + X = Pos.Right (widthEdit) + 2, + Y = Pos.Top (widthEdit), + Text = $"If width is 0, the dimension will be {Dialog.DefaultMinimumWidth}%." + } ); frame.Add ( @@ -87,7 +92,7 @@ public override void Main () { X = Pos.Right (heightEdit) + 2, Y = Pos.Top (heightEdit), - Text = "the Dialog will size to 80% of container." + Text = $"If height is 0, the dimension will be {Dialog.DefaultMinimumWidth}%." } ); @@ -263,16 +268,19 @@ Label buttonPressedLabel Buttons = buttons.ToArray () }; - if (height != 0 || width != 0) + if (width != 0) { - dialog.Height = height; dialog.Width = width; } + if (height != 0) + { + dialog.Height = height; + } var add = new Button { X = Pos.Center (), - Y = Pos.Center (), + Y = Pos.Center () - 1, Text = "_Add a button" }; @@ -312,7 +320,7 @@ Label buttonPressedLabel var addChar = new Button { X = Pos.Center (), - Y = Pos.Center () + 2, + Y = Pos.Center () + 1, Text = $"A_dd a {char.ConvertFromUtf32 (CODE_POINT)} to each button. This text is really long for a reason." }; diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 85f3ced33d..2aa04fe4ff 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -6047,6 +6047,68 @@ public void Draw_Text_Justification (string text, Alignment horizontalTextAlignm TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } + + // Test that changing TextFormatter does not impact View dimensions if Dim.Auto is not in play + [Fact] + public void Not_Used_TextFormatter_Does_Not_Change_View_Size () + { + View view = new () + { + Text = "_1234" + }; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.TextFormatter.Text = "ABC"; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.TextFormatter.Alignment = Alignment.Fill; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.TextFormatter.VerticalAlignment = Alignment.Center; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.TextFormatter.HotKeySpecifier = (Rune)'*'; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.TextFormatter.Text = "*ABC"; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + } + + + [Fact] + public void Not_Used_TextSettings_Do_Not_Change_View_Size () + { + View view = new () + { + Text = "_1234" + }; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.TextAlignment = Alignment.Fill; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.VerticalTextAlignment = Alignment.Center; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.HotKeySpecifier = (Rune)'*'; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + + view.Text = "*ABC"; + Assert.False (view.TextFormatter.AutoSize); + Assert.Equal (Size.Empty, view.Frame.Size); + } + + #region FormatAndGetSizeTests // TODO: Add multi-line examples diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index e133b855c8..d2dc5a46f1 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -592,51 +592,6 @@ public void Width_Auto_Min_Honored (int min, int expectedWidth) Assert.Equal (expectedWidth, superView.Frame.Width); } - //// Test Dim.Fill - Fill should not impact width of the DimAuto superview - //[Theory] - //[InlineData (0, 0, 0, 10, 10)] - //[InlineData (0, 1, 0, 10, 10)] - //[InlineData (0, 11, 0, 10, 10)] - //[InlineData (0, 10, 0, 10, 10)] - //[InlineData (0, 5, 0, 10, 10)] - //[InlineData (1, 5, 0, 10, 9)] - //[InlineData (1, 10, 0, 10, 9)] - //[InlineData (0, 0, 1, 10, 9)] - //[InlineData (0, 10, 1, 10, 9)] - //[InlineData (0, 5, 1, 10, 9)] - //[InlineData (1, 5, 1, 10, 8)] - //[InlineData (1, 10, 1, 10, 8)] - //public void Width_Fill_Fills (int subX, int superMinWidth, int fill, int expectedSuperWidth, int expectedSubWidth) - //{ - // var superView = new View - // { - // X = 0, - // Y = 0, - // Width = Dim.Auto (minimumContentDim: superMinWidth), - // Height = 1, - // ValidatePosDim = true - // }; - - // var subView = new View - // { - // X = subX, - // Y = 0, - // Width = Dim.Fill (fill), - // Height = 1, - // ValidatePosDim = true - // }; - - // superView.Add (subView); - - // superView.BeginInit (); - // superView.EndInit (); - // superView.SetRelativeLayout (new (10, 1)); - // Assert.Equal (expectedSuperWidth, superView.Frame.Width); - // superView.LayoutSubviews (); - // Assert.Equal (expectedSubWidth, subView.Frame.Width); - // Assert.Equal (expectedSuperWidth, superView.Frame.Width); - //} - [Theory] [InlineData (0, 1, 1)] [InlineData (1, 1, 1)] @@ -726,6 +681,40 @@ public void Width_Auto_Subviews_Does_Not_Constrain_To_SuperView (int subX, int s Assert.Equal (expectedSubWidth, subView.Frame.Width); } + #region DimAutoStyle.Auto tests + + [Fact] + public void DimAutoStyle_Auto_Text_Size_Is_Used () + { + var view = new View () + { + Text = "0123\n4567", + Width = Auto (), + Height = Auto (), + }; + + view.SetRelativeLayout (new Size (100, 100)); + Assert.Equal (new (4, 2), view.Frame.Size); + + var subView = new View () + { + Text = "ABCD", + Width = Auto (), + Height = Auto (), + }; + view.Add (subView); + + view.SetRelativeLayout (new Size (100, 100)); + Assert.Equal (new (4, 2), view.Frame.Size); + + subView.Text = "ABCDE"; + + view.SetRelativeLayout (new Size (100, 100)); + Assert.Equal (new (5, 2), view.Frame.Size); + } + + #endregion + [Fact] public void DimAutoStyle_Text_Viewport_Stays_Set () { @@ -755,84 +744,21 @@ public void DimAutoStyle_Text_Viewport_Stays_Set () super.Dispose (); } - - // TextFormatter.Size normally tracks ContentSize, but with DimAuto, tracks the text size [Theory] [InlineData ("", 0, 0)] [InlineData (" ", 1, 1)] [InlineData ("01234", 5, 1)] - public void DimAutoStyle_Text_TextFormatter_Size_Ignores_ContentSize (string text, int expectedW, int expectedH) + public void DimAutoStyle_Text_Ignores_ContentSize (string text, int expectedW, int expectedH) { var view = new View (); view.Width = Auto (DimAutoStyle.Text); view.Height = Auto (DimAutoStyle.Text); view.SetContentSize (new (1, 1)); view.Text = text; - Assert.Equal (new (expectedW, expectedH), view.TextFormatter.Size); - } - - - // Test that changing TextFormatter does not impact View dimensions if Dim.Auto is not in play - [Fact] - public void Not_Used_TextFormatter_Does_Not_Change_View_Size () - { - View view = new () - { - Text = "_1234" - }; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.TextFormatter.Text = "ABC"; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.TextFormatter.Alignment = Alignment.Fill; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.TextFormatter.VerticalAlignment = Alignment.Center; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.TextFormatter.HotKeySpecifier = (Rune)'*'; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.TextFormatter.Text = "*ABC"; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); + view.SetRelativeLayout(Application.Screen.Size); + Assert.Equal (new (expectedW, expectedH), view.Frame.Size); } - - [Fact] - public void Not_Used_TextSettings_Do_Not_Change_View_Size () - { - View view = new () - { - Text = "_1234" - }; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.TextAlignment = Alignment.Fill; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.VerticalTextAlignment = Alignment.Center; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.HotKeySpecifier = (Rune)'*'; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - - view.Text = "*ABC"; - Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (Size.Empty, view.Frame.Size); - } - - [Fact] public void TextFormatter_Settings_Change_View_Size () { @@ -841,39 +767,50 @@ public void TextFormatter_Settings_Change_View_Size () Text = "_1234", Width = Dim.Auto () }; - Assert.False (view.TextFormatter.AutoSize); - Assert.NotEqual (Size.Empty, view.Frame.Size); + Assert.Equal (Size.Empty, view.Frame.Size); // Height is 0, so width is 0 regardless of text + + view.Height = 1; + view.SetRelativeLayout (Application.Screen.Size); + Assert.Equal (new Size (4, 1), view.Frame.Size); + Size lastSize = view.Frame.Size; view.TextAlignment = Alignment.Fill; - Assert.False (view.TextFormatter.AutoSize); - Assert.NotEqual (Size.Empty, view.Frame.Size); + Assert.Equal (lastSize, view.Frame.Size); view = new () { Text = "_1234", - Width = Dim.Auto () + Width = Dim.Auto (), + Height = 1 }; + view.SetRelativeLayout (Application.Screen.Size); + + lastSize = view.Frame.Size; view.VerticalTextAlignment = Alignment.Center; - Assert.False (view.TextFormatter.AutoSize); - Assert.NotEqual (Size.Empty, view.Frame.Size); + Assert.Equal (lastSize, view.Frame.Size); view = new () { Text = "_1234", - Width = Dim.Auto () + Width = Dim.Auto (), + Height = 1, }; + view.SetRelativeLayout (Application.Screen.Size); + lastSize = view.Frame.Size; view.HotKeySpecifier = (Rune)'*'; - Assert.False (view.TextFormatter.AutoSize); - Assert.NotEqual (Size.Empty, view.Frame.Size); + view.SetRelativeLayout (Application.Screen.Size); + Assert.NotEqual (lastSize, view.Frame.Size); view = new () { Text = "_1234", - Width = Dim.Auto () + Width = Dim.Auto (), + Height = 1 }; - view.Text = "*ABC"; - Assert.False (view.TextFormatter.AutoSize); - Assert.NotEqual (Size.Empty, view.Frame.Size); + view.SetRelativeLayout (Application.Screen.Size); + lastSize = view.Frame.Size; + view.Text = "*ABCD"; + Assert.NotEqual (lastSize, view.Frame.Size); } // Ensure TextFormatter.AutoSize is never used for View.Text @@ -930,7 +867,6 @@ public void Height_Auto_HotKey_TextFormatter_Size_Correct (string text, int expe Assert.Equal (new (1, expected), view.TextFormatter.Size); } - [SetupFakeDriver] [Fact] public void Change_To_Non_Auto_Resets_ContentSize () @@ -1042,15 +978,16 @@ public void With_Subview_Using_DimAbsolute (int subViewOffset, int dimAbsoluteSi } [Theory] - [InlineData (0, 50, 50)] - [InlineData (1, 50, 51)] - [InlineData (0, 25, 25)] - [InlineData (-1, 50, 49)] - public void With_Subview_Using_DimPercent (int subViewOffset, int percent, int expectedSize) + [InlineData (0, 0, 0, 0, 0, 0, 0, 0)] + [InlineData (0, 50, 0, 0, 0, 0, 0, 0)] + [InlineData (0, 0, 100, 100, 100, 100, 100, 100)] + [InlineData (0, 50, 100, 100, 100, 100, 100, 100)] + public void With_Subview_Using_DimPercent (int subViewOffset, int percent, int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { var view = new View () { - Width = 100, Height = 100 + Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) }; var subview = new View () { @@ -1061,48 +998,105 @@ public void With_Subview_Using_DimPercent (int subViewOffset, int percent, int e }; view.Add (subview); - view.BeginInit (); - view.EndInit (); + // 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); // subview's width + Assert.Equal (expectedHeight, calculatedHeight); // subview's height + Assert.Equal (subViewOffset, calculatedX); + Assert.Equal (subViewOffset, calculatedY); + + view.SetRelativeLayout (new (100, 100)); + view.LayoutSubviews (); + + Assert.Equal (expectedWidth * (percent / 100f), subview.Viewport.Width); + Assert.Equal (expectedHeight * (percent / 100f), subview.Viewport.Height); + } + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 0, 0)] + [InlineData (0, 20, 0, 10, 0, 0)] + [InlineData (0, 21, 0, 11, 0, 0)] + [InlineData (1, 21, 1, 11, 1, 1)] + [InlineData (21, 21, 11, 11, 21, 11)] + public void With_Subview_Using_DimFill (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 = 0, + Y = 0, + Width = Fill (), + Height = Fill () + }; + view.Add (subview); // Assuming the calculation is done after layout - int calculatedX = subview.X.Calculate (100, subview.Width, subview, Dimension.Width); - int calculatedY = subview.Y.Calculate (100, subview.Height, subview, Dimension.Height); - int calculatedWidth = subview.Width.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = subview.Height.Calculate (0, 100, view, Dimension.Height); - - Assert.Equal (20, calculatedWidth); // subview's width - Assert.Equal (10, calculatedHeight); // subview's height - Assert.Equal (50, calculatedX); // 50% of 100 (Width) - Assert.Equal (50, calculatedY); // 50% of 100 (Height) + 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); } [Theory] - [InlineData (0, 0, 100)] - [InlineData (1, 0, 100)] - [InlineData (0, 1, 100)] - [InlineData (1, 1, 100)] - public void With_Subview_Using_DimFill (int subViewOffset, int dimFillMargin, int expectedSize) + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 2, 4)] + [InlineData (0, 20, 0, 10, 2, 4)] + [InlineData (0, 21, 0, 11, 2, 4)] + [InlineData (1, 21, 1, 11, 2, 4)] + [InlineData (21, 21, 11, 11, 21, 11)] + public void With_Subview_Using_DimFill_And_Another_Subview (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { - // BUGBUG: THis test is totally bogus. Dim.Fill isnot working right yet. var view = new View () { - Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 100, maximumContentDim: 100), - Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 100, maximumContentDim: 100), + Width = Dim.Auto (minimumContentDim: minWidth, maximumContentDim: maxWidth), + Height = Dim.Auto (minimumContentDim: minHeight, maximumContentDim: maxHeight) + }; + var absView = new View () + { + X = 1, + Y = 2, + Width = 1, + Height = 2 }; + view.Add (absView); var subview = new View () { - X = subViewOffset, - Y = subViewOffset, - Width = Dim.Fill (dimFillMargin), - Height = Dim.Fill (dimFillMargin) + X = 0, + Y = 0, + Width = Fill (), + Height = Fill () }; view.Add (subview); - //view.LayoutSubviews (); - view.SetRelativeLayout (new (200, 200)); - Assert.Equal (expectedSize, view.Frame.Width); + // 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); } + [Fact] public void With_Subview_Using_DimFunc () { @@ -1150,7 +1144,7 @@ public void With_Subview_Using_DimView () [InlineData (0, 20, 0, 10, 20, 10)] [InlineData (0, 21, 0, 11, 21, 11)] [InlineData (1, 21, 1, 11, 21, 11)] - [InlineData (100, 21, 100, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] public void With_Subview_Using_PosAbsolute (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { var view = new View () @@ -1186,7 +1180,7 @@ public void With_Subview_Using_PosAbsolute (int minWidth, int maxWidth, int minH [InlineData (0, 20, 0, 10, 20, 10)] [InlineData (0, 21, 0, 11, 20, 10)] [InlineData (1, 21, 1, 11, 20, 10)] - [InlineData (100, 21, 100, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] public void With_Subview_Using_PosPercent (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { var view = new View () @@ -1228,7 +1222,7 @@ public void With_Subview_Using_PosPercent (int minWidth, int maxWidth, int minHe [InlineData (0, 20, 0, 10, 20, 10)] [InlineData (0, 21, 0, 11, 21, 11)] [InlineData (1, 21, 1, 11, 21, 11)] - [InlineData (100, 21, 100, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] public void With_Subview_Using_PosPercent_Combine (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { var view = new View () @@ -1268,9 +1262,9 @@ public void With_Subview_Using_PosPercent_Combine (int minWidth, int maxWidth, i [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, 21, 11)] - [InlineData (1, 21, 1, 11, 21, 11)] - [InlineData (100, 21, 100, 11, 21, 11)] + [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_PosCenter (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { var view = new View () @@ -1313,7 +1307,7 @@ public void With_Subview_Using_PosCenter (int minWidth, int maxWidth, int minHei [InlineData (0, 20, 0, 10, 20, 10)] [InlineData (0, 21, 0, 11, 21, 11)] [InlineData (1, 21, 1, 11, 21, 11)] - [InlineData (100, 21, 100, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] public void With_Subview_Using_PosCenter_Combine (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { var view = new View () @@ -1357,7 +1351,7 @@ public void With_Subview_Using_PosCenter_Combine (int minWidth, int maxWidth, in [InlineData (0, 20, 0, 10, 20, 10)] [InlineData (0, 21, 0, 11, 20, 10)] [InlineData (1, 21, 1, 11, 20, 10)] - [InlineData (100, 21, 100, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] public void With_Subview_Using_PosAnchorEnd (int minWidth, int maxWidth, int minHeight, int maxHeight, int expectedWidth, int expectedHeight) { var view = new View () @@ -1469,12 +1463,14 @@ public void DimAutoStyle_Auto_Larger_Wins (string text, int dimension, int expec View view = new () { Width = Auto (), + Height = 1, Text = text }; View subView = new () { Width = dimension, + Height = 1, }; view.Add (subView); diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index 21143b11a4..c434f2dbe1 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -38,7 +38,8 @@ public void NaturalSize (string command, string help, KeyCode key, int expectedW Assert.IsType (shortcut.Width); Assert.IsType (shortcut.Height); - + //shortcut.BeginInit(); + //shortcut.EndInit (); shortcut.LayoutSubviews (); shortcut.SetRelativeLayout (new (100, 100)); From dd6d96a825e21ef5bb527b186447b81905df76b0 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 18:48:38 -0600 Subject: [PATCH 16/96] Undid PosCenter change --- Terminal.Gui/View/Layout/PosCenter.cs | 2 +- UnitTests/View/Layout/Dim.AutoTests.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/View/Layout/PosCenter.cs b/Terminal.Gui/View/Layout/PosCenter.cs index b8f7d895ff..8f3d700b54 100644 --- a/Terminal.Gui/View/Layout/PosCenter.cs +++ b/Terminal.Gui/View/Layout/PosCenter.cs @@ -16,6 +16,6 @@ internal override int Calculate (int superviewDimension, Dim dim, View us, Dimen // Protect against negative dimensions int newDimension = Math.Max (dim.Calculate (0, superviewDimension, us, dimension), 0); - return superviewDimension / 2 - newDimension / 2; + return (superviewDimension - newDimension) / 2; } } \ No newline at end of file diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index d2dc5a46f1..b3f26f0dbe 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -1296,8 +1296,8 @@ public void With_Subview_Using_PosCenter (int minWidth, int maxWidth, int minHei view.BeginInit (); view.EndInit (); // subview should be centered in the parent view + 1 - Assert.Equal (view.Viewport.Width / 2 - subview.Frame.Width / 2, subview.Frame.X); - Assert.Equal (view.Viewport.Height / 2 - subview.Frame.Height / 2, subview.Frame.Y); + Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2, subview.Frame.X); + Assert.Equal ((view.Viewport.Height - subview.Frame.Height) / 2, subview.Frame.Y); } [Theory] @@ -1340,8 +1340,8 @@ public void With_Subview_Using_PosCenter_Combine (int minWidth, int maxWidth, in view.BeginInit (); view.EndInit (); // subview should be centered in the parent view + 1 - Assert.Equal (view.Viewport.Width / 2 - subview.Frame.Width / 2 + 1, subview.Frame.X); - Assert.Equal (view.Viewport.Height / 2 - subview.Frame.Height / 2 + 1, subview.Frame.Y); + Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2 + 1, subview.Frame.X); + Assert.Equal ((view.Viewport.Height - subview.Frame.Height) / 2 + 1, subview.Frame.Y); } [Theory] From 4654d4a85f032b79e4b5a245384154e22c9f74b5 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 18:49:03 -0600 Subject: [PATCH 17/96] Undid PosCenter change --- UnitTests/View/Layout/Pos.CenterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/View/Layout/Pos.CenterTests.cs b/UnitTests/View/Layout/Pos.CenterTests.cs index d231df822c..b46bd34cd0 100644 --- a/UnitTests/View/Layout/Pos.CenterTests.cs +++ b/UnitTests/View/Layout/Pos.CenterTests.cs @@ -56,7 +56,7 @@ public void PosCenter_CreatesCorrectInstance () [InlineData (10, 10, 0)] [InlineData (10, 11, 0)] [InlineData (10, 12, -1)] - [InlineData (19, 20, -1)] + [InlineData (19, 20, 0)] public void PosCenter_Calculate_ReturnsExpectedValue (int superviewDimension, int width, int expectedX) { var posCenter = new PosCenter (); From a30dbe7286ee59b298ce6d4d333354527721def8 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Jul 2024 18:51:24 -0600 Subject: [PATCH 18/96] Fixed broken Window test --- UnitTests/Views/WindowTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UnitTests/Views/WindowTests.cs b/UnitTests/Views/WindowTests.cs index 24ea778763..2a40f15598 100644 --- a/UnitTests/Views/WindowTests.cs +++ b/UnitTests/Views/WindowTests.cs @@ -130,8 +130,8 @@ public void New_Initializes () Assert.Equal ($"Window(){defaultWindow.Frame}", defaultWindow.ToString ()); Assert.True (defaultWindow.CanFocus); Assert.False (defaultWindow.HasFocus); - Assert.Equal (new Rectangle (0, 0, 2147483645, 2147483645), defaultWindow.Viewport); - Assert.Equal (new Rectangle (0, 0, 2147483647, 2147483647), defaultWindow.Frame); + Assert.Equal (new Rectangle (0, 0, Application.Screen.Width - 2, Application.Screen.Height - 2), defaultWindow.Viewport); + Assert.Equal (new Rectangle (0, 0, Application.Screen.Width, Application.Screen.Height), defaultWindow.Frame); Assert.Null (defaultWindow.Focused); Assert.NotNull (defaultWindow.ColorScheme); Assert.Equal (0, defaultWindow.X); From f6064be449315632e581849de53ecaebb1d11b8d Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 08:14:09 -0600 Subject: [PATCH 19/96] Fixed SetTextFormatterSize --- Terminal.Gui/View/ViewText.cs | 8 ++++---- UnitTests/Text/TextFormatterTests.cs | 3 +++ UnitTests/View/TextTests.cs | 6 +++--- UnitTests/View/ViewTests.cs | 11 +++++++---- UnitTests/Views/ShortcutTests.cs | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index f91b976422..8ca567fbd8 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -204,18 +204,18 @@ internal void SetTextFormatterSize () { if (height == 0 && heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) { - height = Application.Screen.Height; + height = Application.Screen.Width * 4; } - width = TextFormatter.FormatAndGetSize (new (Application.Screen.Width, height)).Width; + width = TextFormatter.FormatAndGetSize (new (Application.Screen.Width * 4, height)).Width; } if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) { if (width == 0 && widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) { - width = Application.Screen.Height; + width = Application.Screen.Width * 4; } - height = TextFormatter.FormatAndGetSize (new (width, Application.Screen.Height)).Height; + height = TextFormatter.FormatAndGetSize (new (width, Application.Screen.Height * 4)).Height; } size = new (width, height); diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 2aa04fe4ff..b320bd271c 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -6150,6 +6150,9 @@ 1 4 14 2 """)] + + [InlineData ("01234", 2, 1, TextDirection.LeftRight_TopBottom, 2, 1, @"01")] + public void FormatAndGetSize_Returns_Correct_Size (string text, int width, int height, TextDirection direction, int expectedWidth, int expectedHeight, string expectedDraw) { TextFormatter tf = new () diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index fafb058356..cd5d744f35 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -816,7 +816,7 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); Assert.Equal (expectedSize, lblRight.TextFormatter.Size); - Assert.Equal (expectedSize, lblJust.TextFormatter.Size); + // Assert.Equal (expectedSize, lblJust.TextFormatter.Size); } else { @@ -824,7 +824,7 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); Assert.Equal (expectedSize, lblRight.TextFormatter.Size); - Assert.Equal (expectedSize, lblJust.TextFormatter.Size); + //Assert.Equal (expectedSize, lblJust.TextFormatter.Size); } Assert.Equal (new (0, 0, width + 2, 6), frame.Frame); @@ -943,7 +943,7 @@ public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) Assert.Equal (new (1, 11), lblLeft.TextFormatter.Size); Assert.Equal (new (1, 11), lblCenter.TextFormatter.Size); Assert.Equal (new (1, 11), lblRight.TextFormatter.Size); - Assert.Equal (new (1, 11), lblJust.TextFormatter.Size); + // Assert.Equal (new (1, 11), lblJust.TextFormatter.Size); Assert.Equal (new (0, 0, 9, height + 2), frame.Frame); } else diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index cc030733e6..bb7d3747a4 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -143,9 +143,13 @@ public void Clear_Does_Not_Spillover_Its_Parent (bool label) { var root = new View { Width = 20, Height = 10, ColorScheme = Colors.ColorSchemes ["Base"] }; + string text = new ('c', 100); + View v = label - ? new Label { Text = new ('c', 100) } - : new TextView { Height = 1, Text = new ('c', 100), Width = Dim.Fill () }; + // Label has Width/Height == AutoSize, so Frame.Size will be (100, 1) + ? new Label { Text = text } + // TextView has Width/Height == (Dim.Fill, 1), so Frame.Size will be 20 (width of root), 1 + : new TextView { Width = Dim.Fill (), Height = 1, Text = text }; root.Add (v); @@ -156,8 +160,7 @@ public void Clear_Does_Not_Spillover_Its_Parent (bool label) if (label) { Assert.False (v.CanFocus); - - //Assert.Equal (new Rectangle (0, 0, 20, 1), v.Frame); + Assert.Equal (new (0, 0, text.Length, 1), v.Frame); } else { diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index c434f2dbe1..f3d54ddbf3 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -40,7 +40,7 @@ public void NaturalSize (string command, string help, KeyCode key, int expectedW Assert.IsType (shortcut.Height); //shortcut.BeginInit(); //shortcut.EndInit (); - shortcut.LayoutSubviews (); + // shortcut.LayoutSubviews (); shortcut.SetRelativeLayout (new (100, 100)); // |0123456789 From d8bc5112268bd7457deff98c00b29cbb5bc69f95 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 08:28:38 -0600 Subject: [PATCH 20/96] Fixed Shortcut --- Terminal.Gui/Views/Shortcut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 7f1469522d..e49d5a4e10 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -71,7 +71,7 @@ public Shortcut (Key key, string commandText, Action action, string helpText = n CommandView = new () { Width = Dim.Auto (), - Height = Dim.Auto () + Height = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 1) }; HelpView.Id = "_helpView"; From e566dce521ecab56aaaf2d63e9c1294b0bcf86c5 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 09:07:54 -0600 Subject: [PATCH 21/96] Fixed Horizontal FormatAndGetSize Fill issue; Vertical is still broken --- Terminal.Gui/Text/TextFormatter.cs | 15 ++++++++++++++- UnitTests/View/TextTests.cs | 8 +++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 84500c5ed6..e1cda140a0 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Terminal.Gui; @@ -659,9 +660,21 @@ public Size FormatAndGetSize (Size? constrainSize = null) return Size.Empty; } + // HACK: This is a total hack to work around the fact that TextFormatter.Format does not match TextFormatter.Draw. Size prevSize = Size; Size = constrainSize.Value; + + // HACK: Fill normally will fill the entire constraint size, but we need to know the actual size of the text. + Alignment prevAlignment = Alignment; + if (Alignment == Alignment.Fill) + { + Alignment = Alignment.Start; + } + List lines = GetLines (); + + // Undo hacks + Alignment = prevAlignment; Size = prevSize; if (lines.Count == 0) @@ -679,7 +692,7 @@ public Size FormatAndGetSize (Size? constrainSize = null) } else { - width = lines.Max (static line => line.GetColumns()); + width = lines.Max (static line => line.GetColumns ()); height = lines.Count; } diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index cd5d744f35..a54664d495 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -810,13 +810,15 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) Application.Begin (top); ((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6); + // frame.Width is width + border wide (20 + 2) and 6 high + if (autoSize) { Size expectedSize = new (11, 1); Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); Assert.Equal (expectedSize, lblRight.TextFormatter.Size); - // Assert.Equal (expectedSize, lblJust.TextFormatter.Size); + Assert.Equal (expectedSize, lblJust.TextFormatter.Size); } else { @@ -824,7 +826,7 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); Assert.Equal (expectedSize, lblRight.TextFormatter.Size); - //Assert.Equal (expectedSize, lblJust.TextFormatter.Size); + Assert.Equal (expectedSize, lblJust.TextFormatter.Size); } Assert.Equal (new (0, 0, width + 2, 6), frame.Frame); @@ -943,7 +945,7 @@ public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) Assert.Equal (new (1, 11), lblLeft.TextFormatter.Size); Assert.Equal (new (1, 11), lblCenter.TextFormatter.Size); Assert.Equal (new (1, 11), lblRight.TextFormatter.Size); - // Assert.Equal (new (1, 11), lblJust.TextFormatter.Size); + Assert.Equal (new (1, 11), lblJust.TextFormatter.Size); Assert.Equal (new (0, 0, 9, height + 2), frame.Frame); } else From b528ba8bc70b0e6c31d806963e941787e4855580 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 10:15:09 -0600 Subject: [PATCH 22/96] Fixed Vertical FormatAndGetSize Fill issue --- Terminal.Gui/Text/TextFormatter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index e1cda140a0..79967f5175 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -670,11 +670,17 @@ public Size FormatAndGetSize (Size? constrainSize = null) { Alignment = Alignment.Start; } + Alignment prevVerticalAlignment = VerticalAlignment; + if (VerticalAlignment == Alignment.Fill) + { + VerticalAlignment = Alignment.Start; + } List lines = GetLines (); // Undo hacks Alignment = prevAlignment; + VerticalAlignment = prevVerticalAlignment; Size = prevSize; if (lines.Count == 0) From 37088737b7399687a6d23140787b56ad4e828d2e Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 10:54:38 -0600 Subject: [PATCH 23/96] Removed lame title test --- UnitTests/View/TitleTests.cs | 37 +----------------------------------- 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/UnitTests/View/TitleTests.cs b/UnitTests/View/TitleTests.cs index 9013546090..0c2a6a5b76 100644 --- a/UnitTests/View/TitleTests.cs +++ b/UnitTests/View/TitleTests.cs @@ -5,42 +5,7 @@ namespace Terminal.Gui.ViewTests; public class TitleTests (ITestOutputHelper output) { - [SetupFakeDriver] - [Fact] - public void Change_View_Size_Update_Title_Size () - { - var view = new View - { - Title = "_Hello World", - Width = Dim.Auto (), - Height = Dim.Auto (), - BorderStyle = LineStyle.Single - }; - var top = new Toplevel (); - top.Add (view); - top.BeginInit (); - top.EndInit (); - - Assert.Equal (string.Empty, view.Text); - Assert.Equal (new (2, 2), view.Frame.Size); - top.Draw (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @" -┌┐ -└┘", - output); - - var text = "This text will increment the view size and display the title."; - view.Text = text; - top.Draw (); - Assert.Equal (text, view.Text); - - // SetupFakeDriver only create a screen with 25 cols and 25 rows - Assert.Equal (new (25, 3), view.GetContentSize ()); - - top.Dispose (); - } + // Unit tests that verify look & feel of title are in BorderTests.cs [Fact] public void Set_Title_Fires_TitleChanged () From 51433c0112f506627bc5a09858053701c145c447 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 11:25:45 -0600 Subject: [PATCH 24/96] Refactored DimAuto unit tests --- .../View/Layout/Dim.AutoTests.DimTypes.cs | 226 ++++ UnitTests/View/Layout/Dim.AutoTests.MinMax.cs | 195 +++ .../View/Layout/Dim.AutoTests.PosTypes.cs | 334 +++++ UnitTests/View/Layout/Dim.AutoTests.cs | 1082 ++++------------- 4 files changed, 988 insertions(+), 849 deletions(-) create mode 100644 UnitTests/View/Layout/Dim.AutoTests.DimTypes.cs create mode 100644 UnitTests/View/Layout/Dim.AutoTests.MinMax.cs create mode 100644 UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs diff --git a/UnitTests/View/Layout/Dim.AutoTests.DimTypes.cs b/UnitTests/View/Layout/Dim.AutoTests.DimTypes.cs new file mode 100644 index 0000000000..7a7c5e77bf --- /dev/null +++ b/UnitTests/View/Layout/Dim.AutoTests.DimTypes.cs @@ -0,0 +1,226 @@ +namespace Terminal.Gui.LayoutTests; + +public partial class DimAutoTests +{ + // Tests all the Dim Types in Subview scenarios to ensure DimAutoStyle.Content is calculated correctly + + #region DimAbsolute + + [Theory] + [InlineData (0, 15, 15)] + [InlineData (1, 15, 16)] + [InlineData (-1, 15, 14)] + public void With_Subview_Using_DimAbsolute (int subViewOffset, int dimAbsoluteSize, int expectedSize) + { + var view = new View (); + + var subview = new View + { + X = subViewOffset, + Y = subViewOffset, + Width = Dim.Absolute (dimAbsoluteSize), + Height = Dim.Absolute (dimAbsoluteSize) + }; + view.Add (subview); + + Dim dim = Dim.Auto (DimAutoStyle.Content); + + int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); + int calculatedHeight = dim.Calculate (0, 100, view, Dimension.Height); + + Assert.Equal (expectedSize, calculatedWidth); + Assert.Equal (expectedSize, calculatedHeight); + } + + #endregion DimAbsolute + + #region DimPercent + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0, 0, 0)] + [InlineData (0, 50, 0, 0, 0, 0, 0, 0)] + [InlineData (0, 0, 100, 100, 100, 100, 100, 100)] + [InlineData (0, 50, 100, 100, 100, 100, 100, 100)] + public void With_Subview_Using_DimPercent ( + int subViewOffset, + int percent, + 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 = subViewOffset, + Y = subViewOffset, + Width = Dim.Percent (percent), + Height = Dim.Percent (percent) + }; + 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); // subview's width + Assert.Equal (expectedHeight, calculatedHeight); // subview's height + Assert.Equal (subViewOffset, calculatedX); + Assert.Equal (subViewOffset, calculatedY); + + view.SetRelativeLayout (new (100, 100)); + view.LayoutSubviews (); + + Assert.Equal (expectedWidth * (percent / 100f), subview.Viewport.Width); + Assert.Equal (expectedHeight * (percent / 100f), subview.Viewport.Height); + } + #endregion DimPercent + + #region DimFill + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 0, 0)] + [InlineData (0, 20, 0, 10, 0, 0)] + [InlineData (0, 21, 0, 11, 0, 0)] + [InlineData (1, 21, 1, 11, 1, 1)] + [InlineData (21, 21, 11, 11, 21, 11)] + public void With_Subview_Using_DimFill (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 = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + 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); + } + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 2, 4)] + [InlineData (0, 20, 0, 10, 2, 4)] + [InlineData (0, 21, 0, 11, 2, 4)] + [InlineData (1, 21, 1, 11, 2, 4)] + [InlineData (21, 21, 11, 11, 21, 11)] + public void With_Subview_Using_DimFill_And_Another_Subview (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 absView = new View + { + X = 1, + Y = 2, + Width = 1, + Height = 2 + }; + view.Add (absView); + + var subview = new View + { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + 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 + + #region DimFunc + + [Fact] + public void With_Subview_Using_DimFunc () + { + var view = new View (); + var subview = new View { Width = Dim.Func (() => 20), Height = Dim.Func (() => 25) }; + view.Add (subview); + + subview.SetRelativeLayout (new (100, 100)); + + Dim dim = Dim.Auto (DimAutoStyle.Content); + + int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); + int calculatedHeight = dim.Calculate (0, 100, view, Dimension.Height); + + Assert.Equal (20, calculatedWidth); + Assert.Equal (25, calculatedHeight); + } + + #endregion DimFunc + + #region DimView + + [Fact] + public void With_Subview_Using_DimView () + { + var view = new View (); + var subview = new View { Width = 30, Height = 40 }; + var subSubview = new View { Width = Dim.Width (subview), Height = Dim.Height (subview) }; + view.Add (subview); + view.Add (subSubview); + + subview.SetRelativeLayout (new (100, 100)); + + Dim dim = Dim.Auto (DimAutoStyle.Content); + + int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); + int calculatedHeight = dim.Calculate (0, 100, view, Dimension.Height); + + // Expecting the size to match the subview, which is the largest + Assert.Equal (30, calculatedWidth); + Assert.Equal (40, calculatedHeight); + } + + #endregion DimView + + #region DimCombine + // TODO: Need DimCombine tests + #endregion DimCombine +} diff --git a/UnitTests/View/Layout/Dim.AutoTests.MinMax.cs b/UnitTests/View/Layout/Dim.AutoTests.MinMax.cs new file mode 100644 index 0000000000..f184f5ee9b --- /dev/null +++ b/UnitTests/View/Layout/Dim.AutoTests.MinMax.cs @@ -0,0 +1,195 @@ +namespace Terminal.Gui.LayoutTests; + +public partial class DimAutoTests +{ + #region minimumContentDim Tests + + [Fact] + public void Min_Is_Honored () + { + var superView = new View + { + X = 0, + Y = 0, + Width = Dim.Auto (minimumContentDim: 10), + Height = Dim.Auto (minimumContentDim: 10), + ValidatePosDim = true + }; + + var subView = new View + { + X = 0, + Y = 0, + Width = 5, + Height = 5 + }; + + superView.Add (subView); + superView.BeginInit (); + superView.EndInit (); + + superView.SetRelativeLayout (new (10, 10)); + superView.LayoutSubviews (); // no throw + + Assert.Equal (10, superView.Frame.Width); + Assert.Equal (10, superView.Frame.Height); + } + + [Theory] + [InlineData (0, 2, 4)] + [InlineData (1, 2, 4)] + [InlineData (2, 2, 4)] + [InlineData (3, 2, 5)] + [InlineData (1, 0, 3)] + public void Min_Absolute_Is_Content_Relative (int contentSize, int minAbsolute, int expected) + { + var view = new View + { + X = 0, + Y = 0, + Width = Dim.Auto (minimumContentDim: minAbsolute), + BorderStyle = LineStyle.Single, // a 1 thick adornment + ValidatePosDim = true + }; + + view.SetContentSize (new (contentSize, 0)); + + Assert.Equal (expected, view.Frame.Width); + } + + [Theory] + [InlineData (1, 100, 100)] + [InlineData (1, 50, 50)] + public void Min_Percent (int contentSize, int minPercent, int expected) + { + var view = new View + { + X = 0, + Y = 0, + Width = Dim.Auto (minimumContentDim: Dim.Percent (minPercent)), + ValidatePosDim = true + }; + + view.SetContentSize (new (contentSize, 0)); + view.SetRelativeLayout (new (100, 100)); + + Assert.Equal (expected, view.Frame.Width); + } + + [Theory] + [InlineData (1, 100, 102)] + [InlineData (1, 50, 52)] // 50% of 100 is 50, but the border adds 2 + [InlineData (1, 30, 32)] // 30% of 100 is 30, but the border adds 2 + [InlineData (2, 30, 32)] // 30% of 100 is 30, but the border adds 2 + public void Min_Percent_Is_Content_Relative (int contentSize, int minPercent, int expected) + { + var view = new View + { + X = 0, + Y = 0, + Width = Dim.Auto (minimumContentDim: Dim.Percent (minPercent)), + BorderStyle = LineStyle.Single, // a 1 thick adornment + ValidatePosDim = true + }; + + view.SetContentSize (new (contentSize, 0)); + view.SetRelativeLayout (new (100, 100)); + + Assert.Equal (expected, view.Frame.Width); + } + + [Fact] + public void Min_Resets_If_Subview_Moves_Negative () + { + var superView = new View + { + X = 0, + Y = 0, + Width = Dim.Auto (minimumContentDim: 10), + Height = Dim.Auto (minimumContentDim: 10), + ValidatePosDim = true + }; + + var subView = new View + { + X = 0, + Y = 0, + Width = 5, + Height = 5 + }; + + superView.Add (subView); + superView.BeginInit (); + superView.EndInit (); + + superView.SetRelativeLayout (new (10, 10)); + superView.LayoutSubviews (); // no throw + + Assert.Equal (10, superView.Frame.Width); + Assert.Equal (10, superView.Frame.Height); + + subView.X = -1; + subView.Y = -1; + superView.SetRelativeLayout (new (10, 10)); + superView.LayoutSubviews (); // no throw + + Assert.Equal (5, subView.Frame.Width); + Assert.Equal (5, subView.Frame.Height); + + Assert.Equal (10, superView.Frame.Width); + Assert.Equal (10, superView.Frame.Height); + } + + [Fact] + public void Min_Resets_If_Subview_Shrinks () + { + var superView = new View + { + X = 0, + Y = 0, + Width = Dim.Auto (minimumContentDim: 10), + Height = Dim.Auto (minimumContentDim: 10), + ValidatePosDim = true + }; + + var subView = new View + { + X = 0, + Y = 0, + Width = 5, + Height = 5 + }; + + superView.Add (subView); + superView.BeginInit (); + superView.EndInit (); + + superView.SetRelativeLayout (new (10, 10)); + superView.LayoutSubviews (); // no throw + + Assert.Equal (10, superView.Frame.Width); + Assert.Equal (10, superView.Frame.Height); + + subView.Width = 3; + subView.Height = 3; + superView.SetRelativeLayout (new (10, 10)); + superView.LayoutSubviews (); // no throw + + Assert.Equal (3, subView.Frame.Width); + Assert.Equal (3, subView.Frame.Height); + + Assert.Equal (10, superView.Frame.Width); + Assert.Equal (10, superView.Frame.Height); + } + + #endregion minimumContentDim Tests + + // Test min - ensure that if min is specified in the DimAuto constructor it is honored + + // what happens if DimAuto (min: 10) and the subview moves to a negative coord? + + #region maximumContentDim Tests + // TODO: Add tests + + #endregion maximumContentDim Tests +} diff --git a/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs new file mode 100644 index 0000000000..ccdd69d9d6 --- /dev/null +++ b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs @@ -0,0 +1,334 @@ +namespace Terminal.Gui.LayoutTests; + +public partial class DimAutoTests +{ + // Tests all the Pos Types in Subview scenarios to ensure DimAutoStyle.Content is calculated correctly + + #region PosAbsolute + + [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, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] + public void With_Subview_Using_PosAbsolute (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.Absolute (10), + 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 PosAbsolute + + #region PosPercent + + [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_PosPercent (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.Percent (50), + Y = Pos.Percent (50), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be at 50% in the parent view + Assert.Equal ((int)(view.Viewport.Width * .50), subview.Frame.X); + Assert.Equal ((int)(view.Viewport.Height * .50), subview.Frame.Y); + } + + [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, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] + public void With_Subview_Using_PosPercent_Combine (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.Percent (50) + 1, + Y = 1 + Pos.Percent (50), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be at 50% in the parent view + Assert.Equal ((int)(view.Viewport.Width * .50) + 1, subview.Frame.X); + Assert.Equal ((int)(view.Viewport.Height * .50) + 1, subview.Frame.Y); + } + + #endregion PosPercent + + #region PosCenter + + [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_PosCenter (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.Center (), + Y = Pos.Center (), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be centered in the parent view + 1 + Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2, subview.Frame.X); + Assert.Equal ((view.Viewport.Height - subview.Frame.Height) / 2, subview.Frame.Y); + } + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [InlineData (0, 20, 0, 10, 20, 10)] + [InlineData (0, 21, 0, 11, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] + public void With_Subview_Using_PosCenter_Combine (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.Center () + 1, + Y = 1 + Pos.Center (), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be centered in the parent view + 1 + Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2 + 1, subview.Frame.X); + Assert.Equal ((view.Viewport.Height - subview.Frame.Height) / 2 + 1, subview.Frame.Y); + } + + #endregion PosCenter + + #region PosView + + // TODO: Need PosView tests + + [Fact] + public void With_Subview_Using_PosView () + { + var view = new View () + { + Width = Dim.Auto (), + Height = Dim.Auto (), + }; + var subview1 = new View { X = 1, Y = 2, Width = 1, Height = 2 }; + var subview2 = new View { X = Pos.Top (subview1), Y = Pos.Bottom (subview1), Width = 1, Height = 2 }; + view.Add (subview1, subview2); + + view.SetRelativeLayout (new (100, 100)); + + // subview1.X + subview1.Width + subview2.Width + Assert.Equal (1 + 1 + 1, view.Frame.Width); + // subview1.Y + subview1.Height + subview2.Height + Assert.Equal (2 + 2 + 2, view.Frame.Height); + } + + #endregion PosView + + #region PosAnchorEnd + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [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_PosAnchorEnd (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.AnchorEnd (), + Y = Pos.AnchorEnd (), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be at the end of the view + Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); + Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); + } + + #endregion PosAnchorEnd + + #region PosFunc + + [Fact] + public void With_Subview_Using_PosFunc () + { + var view = new View () + { + Width = Dim.Auto (), + Height = Dim.Auto (), + }; + var subview = new View { X = Pos.Func (() => 20), Y = Pos.Func (() => 25) }; + view.Add (subview); + + view.SetRelativeLayout (new (100, 100)); + + Assert.Equal (20, view.Frame.Width); + Assert.Equal (25, view.Frame.Height); + } + + #endregion PosFunc + + #region PosCombine + + // TODO: Need more PosCombine tests + + #endregion PosCombine +} diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index b3f26f0dbe..6e8ff67285 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -1,11 +1,10 @@ -using System.Globalization; -using System.Text; +using System.Text; using Xunit.Abstractions; using static Terminal.Gui.Dim; namespace Terminal.Gui.LayoutTests; -public class DimAutoTests (ITestOutputHelper output) +public partial class DimAutoTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; @@ -14,8 +13,8 @@ private class DimAutoTestView : View public DimAutoTestView () { ValidatePosDim = true; - Width = Dim.Auto (); - Height = Dim.Auto (); + Width = Auto (); + Height = Auto (); } public DimAutoTestView (Dim width, Dim height) @@ -34,187 +33,6 @@ public DimAutoTestView (string text, Dim width, Dim height) } } - // Test min - ensure that if min is specified in the DimAuto constructor it is honored - [Fact] - public void Min_Is_Honored () - { - var superView = new View - { - X = 0, - Y = 0, - Width = Dim.Auto (minimumContentDim: 10), - Height = Dim.Auto (minimumContentDim: 10), - ValidatePosDim = true - }; - - var subView = new View - { - X = 0, - Y = 0, - Width = 5, - Height = 5 - }; - - superView.Add (subView); - superView.BeginInit (); - superView.EndInit (); - - superView.SetRelativeLayout (new (10, 10)); - superView.LayoutSubviews (); // no throw - - Assert.Equal (10, superView.Frame.Width); - Assert.Equal (10, superView.Frame.Height); - } - - [Theory] - [InlineData (0, 2, 4)] - [InlineData (1, 2, 4)] - [InlineData (2, 2, 4)] - [InlineData (3, 2, 5)] - [InlineData (1, 0, 3)] - public void Min_Absolute_Is_Content_Relative (int contentSize, int minAbsolute, int expected) - { - var view = new View - { - X = 0, - Y = 0, - Width = Dim.Auto (minimumContentDim: minAbsolute), - BorderStyle = LineStyle.Single, // a 1 thick adornment - ValidatePosDim = true - }; - - view.SetContentSize (new (contentSize, 0)); - - Assert.Equal (expected, view.Frame.Width); - } - - - [Theory] - [InlineData (1, 100, 100)] - [InlineData (1, 50, 50)] - public void Min_Percent (int contentSize, int minPercent, int expected) - { - var view = new View - { - X = 0, - Y = 0, - Width = Dim.Auto (minimumContentDim: Dim.Percent (minPercent)), - ValidatePosDim = true - }; - - view.SetContentSize (new (contentSize, 0)); - view.SetRelativeLayout (new (100, 100)); - - Assert.Equal (expected, view.Frame.Width); - } - - [Theory] - [InlineData (1, 100, 102)] - [InlineData (1, 50, 52)] // 50% of 100 is 50, but the border adds 2 - [InlineData (1, 30, 32)] // 30% of 100 is 30, but the border adds 2 - [InlineData (2, 30, 32)] // 30% of 100 is 30, but the border adds 2 - public void Min_Percent_Is_Content_Relative (int contentSize, int minPercent, int expected) - { - var view = new View - { - X = 0, - Y = 0, - Width = Dim.Auto (minimumContentDim: Dim.Percent (minPercent)), - BorderStyle = LineStyle.Single, // a 1 thick adornment - ValidatePosDim = true - }; - - view.SetContentSize (new (contentSize, 0)); - view.SetRelativeLayout (new (100, 100)); - - Assert.Equal (expected, view.Frame.Width); - } - - // what happens if DimAuto (min: 10) and the subview moves to a negative coord? - [Fact] - public void Min_Resets_If_Subview_Moves_Negative () - { - var superView = new View - { - X = 0, - Y = 0, - Width = Dim.Auto (minimumContentDim: 10), - Height = Dim.Auto (minimumContentDim: 10), - ValidatePosDim = true - }; - - var subView = new View - { - X = 0, - Y = 0, - Width = 5, - Height = 5 - }; - - superView.Add (subView); - superView.BeginInit (); - superView.EndInit (); - - superView.SetRelativeLayout (new (10, 10)); - superView.LayoutSubviews (); // no throw - - Assert.Equal (10, superView.Frame.Width); - Assert.Equal (10, superView.Frame.Height); - - subView.X = -1; - subView.Y = -1; - superView.SetRelativeLayout (new (10, 10)); - superView.LayoutSubviews (); // no throw - - Assert.Equal (5, subView.Frame.Width); - Assert.Equal (5, subView.Frame.Height); - - Assert.Equal (10, superView.Frame.Width); - Assert.Equal (10, superView.Frame.Height); - } - - [Fact] - public void Min_Resets_If_Subview_Shrinks () - { - var superView = new View - { - X = 0, - Y = 0, - Width = Dim.Auto (minimumContentDim: 10), - Height = Dim.Auto (minimumContentDim: 10), - ValidatePosDim = true - }; - - var subView = new View - { - X = 0, - Y = 0, - Width = 5, - Height = 5 - }; - - superView.Add (subView); - superView.BeginInit (); - superView.EndInit (); - - superView.SetRelativeLayout (new (10, 10)); - superView.LayoutSubviews (); // no throw - - Assert.Equal (10, superView.Frame.Width); - Assert.Equal (10, superView.Frame.Height); - - subView.Width = 3; - subView.Height = 3; - superView.SetRelativeLayout (new (10, 10)); - superView.LayoutSubviews (); // no throw - - Assert.Equal (3, subView.Frame.Width); - Assert.Equal (3, subView.Frame.Height); - - Assert.Equal (10, superView.Frame.Width); - Assert.Equal (10, superView.Frame.Height); - } - [Theory] [InlineData (0, 0, 0, 0, 0)] [InlineData (0, 0, 5, 0, 0)] @@ -235,7 +53,7 @@ public void Height_Auto_Width_Absolute_NotChanged (int subX, int subY, int subWi X = 0, Y = 0, Width = 10, - Height = Dim.Auto (), + Height = Auto (), ValidatePosDim = true }; @@ -253,7 +71,7 @@ public void Height_Auto_Width_Absolute_NotChanged (int subX, int subY, int subWi superView.BeginInit (); superView.EndInit (); superView.SetRelativeLayout (new (10, 10)); - Assert.Equal (new Rectangle (0, 0, 10, expectedHeight), superView.Frame); + Assert.Equal (new (0, 0, 10, expectedHeight), superView.Frame); } [Fact] @@ -263,18 +81,18 @@ public void NoSubViews_Does_Nothing () { X = 0, Y = 0, - Width = Dim.Auto (), - Height = Dim.Auto (), + Width = Auto (), + Height = Auto (), ValidatePosDim = true }; superView.BeginInit (); superView.EndInit (); superView.SetRelativeLayout (new (10, 10)); - Assert.Equal (new Rectangle (0, 0, 0, 0), superView.Frame); + Assert.Equal (new (0, 0, 0, 0), superView.Frame); superView.SetRelativeLayout (new (10, 10)); - Assert.Equal (new Rectangle (0, 0, 0, 0), superView.Frame); + Assert.Equal (new (0, 0, 0, 0), superView.Frame); } [Fact] @@ -284,8 +102,8 @@ public void NoSubViews_Does_Nothing_Vertical () { X = 0, Y = 0, - Width = Dim.Auto (), - Height = Dim.Auto (), + Width = Auto (), + Height = Auto (), TextDirection = TextDirection.TopBottom_LeftRight, ValidatePosDim = true }; @@ -293,10 +111,10 @@ public void NoSubViews_Does_Nothing_Vertical () superView.BeginInit (); superView.EndInit (); superView.SetRelativeLayout (new (10, 10)); - Assert.Equal (new Rectangle (0, 0, 0, 0), superView.Frame); + Assert.Equal (new (0, 0, 0, 0), superView.Frame); superView.SetRelativeLayout (new (10, 10)); - Assert.Equal (new Rectangle (0, 0, 0, 0), superView.Frame); + Assert.Equal (new (0, 0, 0, 0), superView.Frame); } [Theory] @@ -318,8 +136,8 @@ public void SubView_Changes_SuperView_Size (int subX, int subY, int subWidth, in { X = 0, Y = 0, - Width = Dim.Auto (), - Height = Dim.Auto (), + Width = Auto (), + Height = Auto (), ValidatePosDim = true }; @@ -337,7 +155,7 @@ public void SubView_Changes_SuperView_Size (int subX, int subY, int subWidth, in superView.BeginInit (); superView.EndInit (); superView.SetRelativeLayout (new (10, 10)); - Assert.Equal (new Rectangle (0, 0, expectedWidth, expectedHeight), superView.Frame); + Assert.Equal (new (0, 0, expectedWidth, expectedHeight), superView.Frame); } // Test validation @@ -348,8 +166,8 @@ public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims () { X = 0, Y = 0, - Width = Dim.Auto (), - Height = Dim.Auto (), + Width = Auto (), + Height = Auto (), ValidatePosDim = true }; @@ -357,7 +175,7 @@ public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims () { X = 0, Y = 0, - Width = Dim.Fill (), + Width = Fill (), Height = 10, ValidatePosDim = true }; @@ -371,15 +189,15 @@ public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims () superView.SetRelativeLayout (new (10, 10)); superView.LayoutSubviews (); // no throw - subView.Width = Dim.Fill (); + subView.Width = Fill (); superView.SetRelativeLayout (new (0, 0)); subView.Width = 10; - subView.Height = Dim.Fill (); + subView.Height = Fill (); superView.SetRelativeLayout (new (0, 0)); subView.Height = 10; - subView.Height = Dim.Percent (50); + subView.Height = Percent (50); Assert.Throws (() => superView.SetRelativeLayout (new (0, 0))); subView.Height = 10; @@ -407,8 +225,8 @@ public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims_Combine { X = 0, Y = 0, - Width = Dim.Auto (), - Height = Dim.Auto (), + Width = Auto (), + Height = Auto (), ValidatePosDim = true }; @@ -434,24 +252,24 @@ public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims_Combine superView.SetRelativeLayout (new (0, 0)); superView.LayoutSubviews (); // no throw - subView.Height = Dim.Fill () + 3; + subView.Height = Fill () + 3; superView.SetRelativeLayout (new (0, 0)); subView.Height = 0; - subView.Height = 3 + Dim.Fill (); + subView.Height = 3 + Fill (); superView.SetRelativeLayout (new (0, 0)); subView.Height = 0; - subView.Height = 3 + 5 + Dim.Fill (); + subView.Height = 3 + 5 + Fill (); superView.SetRelativeLayout (new (0, 0)); subView.Height = 0; - subView.Height = 3 + 5 + Dim.Percent (10); + subView.Height = 3 + 5 + Percent (10); Assert.Throws (() => superView.SetRelativeLayout (new (0, 0))); subView.Height = 0; // Tests nested Combine - subView.Height = 5 + new DimCombine (AddOrSubtract.Add, 3, new DimCombine (AddOrSubtract.Add, Dim.Percent (10), 9)); + subView.Height = 5 + new DimCombine (AddOrSubtract.Add, 3, new DimCombine (AddOrSubtract.Add, Percent (10), 9)); Assert.Throws (() => superView.SetRelativeLayout (new (0, 0))); } @@ -462,8 +280,8 @@ public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Pos_Combine ( { X = 0, Y = 0, - Width = Dim.Auto (), - Height = Dim.Auto (), + Width = Auto (), + Height = Auto (), ValidatePosDim = true }; @@ -545,7 +363,7 @@ public void Width_Auto_Height_Absolute_NotChanged (int subX, int subY, int subWi { X = 0, Y = 0, - Width = Dim.Auto (), + Width = Auto (), Height = 10, ValidatePosDim = true }; @@ -564,7 +382,7 @@ public void Width_Auto_Height_Absolute_NotChanged (int subX, int subY, int subWi superView.BeginInit (); superView.EndInit (); superView.SetRelativeLayout (new (10, 10)); - Assert.Equal (new Rectangle (0, 0, expectedWidth, 10), superView.Frame); + Assert.Equal (new (0, 0, expectedWidth, 10), superView.Frame); } // Test that when a view has Width set to DimAuto (min: x) @@ -581,7 +399,7 @@ public void Width_Auto_Min_Honored (int min, int expectedWidth) { X = 0, Y = 0, - Width = Dim.Auto (minimumContentDim: min), + Width = Auto (minimumContentDim: min), Height = 1, ValidatePosDim = true }; @@ -614,10 +432,10 @@ public void Width_Auto_Text_Does_Not_Constrain_To_SuperView (int subX, int textL var subView = new View { - Text = new string ('*', textLen), + Text = new ('*', textLen), X = subX, Y = 0, - Width = Dim.Auto (DimAutoStyle.Text), + Width = Auto (DimAutoStyle.Text), Height = 1, ValidatePosDim = true }; @@ -656,7 +474,7 @@ public void Width_Auto_Subviews_Does_Not_Constrain_To_SuperView (int subX, int s { X = subX, Y = 0, - Width = Dim.Auto (DimAutoStyle.Content), + Width = Auto (DimAutoStyle.Content), Height = 1, ValidatePosDim = true }; @@ -681,54 +499,110 @@ public void Width_Auto_Subviews_Does_Not_Constrain_To_SuperView (int subX, int s Assert.Equal (expectedSubWidth, subView.Frame.Width); } + [SetupFakeDriver] + [Fact] + public void Change_To_Non_Auto_Resets_ContentSize () + { + View view = new () + { + Width = Auto (), + Height = Auto (), + Text = "01234" + }; + + Assert.Equal (new (0, 0, 5, 1), view.Frame); + Assert.Equal (new (5, 1), view.GetContentSize ()); + + // Change text to a longer string + view.Text = "0123456789"; + + Assert.Equal (new (0, 0, 10, 1), view.Frame); + Assert.Equal (new (10, 1), view.GetContentSize ()); + + // If ContentSize was reset, these should cause it to update + view.Width = 5; + view.Height = 1; + + Assert.Equal (new (5, 1), view.GetContentSize ()); + } + #region DimAutoStyle.Auto tests [Fact] public void DimAutoStyle_Auto_Text_Size_Is_Used () { - var view = new View () + var view = new View { Text = "0123\n4567", Width = Auto (), - Height = Auto (), + Height = Auto () }; - view.SetRelativeLayout (new Size (100, 100)); + view.SetRelativeLayout (new (100, 100)); Assert.Equal (new (4, 2), view.Frame.Size); - var subView = new View () + var subView = new View { Text = "ABCD", Width = Auto (), - Height = Auto (), + Height = Auto () }; view.Add (subView); - view.SetRelativeLayout (new Size (100, 100)); + view.SetRelativeLayout (new (100, 100)); Assert.Equal (new (4, 2), view.Frame.Size); subView.Text = "ABCDE"; - view.SetRelativeLayout (new Size (100, 100)); + view.SetRelativeLayout (new (100, 100)); Assert.Equal (new (5, 2), view.Frame.Size); } + [Theory] + [InlineData ("01234", 5, 5)] + [InlineData ("01234", 6, 6)] + [InlineData ("01234", 4, 5)] + [InlineData ("01234", 0, 5)] + [InlineData ("", 5, 5)] + [InlineData ("", 0, 0)] + public void DimAutoStyle_Auto_Larger_Wins (string text, int dimension, int expected) + { + View view = new () + { + Width = Auto (), + Height = 1, + Text = text + }; + + View subView = new () + { + Width = dimension, + Height = 1 + }; + view.Add (subView); + + view.SetRelativeLayout (new (10, 10)); + Assert.Equal (expected, view.Frame.Width); + } + #endregion + #region DimAutoStyle.Text tests + [Fact] public void DimAutoStyle_Text_Viewport_Stays_Set () { - var super = new View () + var super = new View { - Width = Dim.Fill (), - Height = Dim.Fill () + Width = Fill (), + Height = Fill () }; - var view = new View () + var view = new View { Text = "01234567", Width = Auto (DimAutoStyle.Text), - Height = Auto (DimAutoStyle.Text), + Height = Auto (DimAutoStyle.Text) }; super.Add (view); @@ -755,144 +629,40 @@ public void DimAutoStyle_Text_Ignores_ContentSize (string text, int expectedW, i view.Height = Auto (DimAutoStyle.Text); view.SetContentSize (new (1, 1)); view.Text = text; - view.SetRelativeLayout(Application.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (expectedW, expectedH), view.Frame.Size); } [Fact] - public void TextFormatter_Settings_Change_View_Size () + public void DimAutoStyle_Text_Pos_AnchorEnd_Locates_Correctly () { - View view = new () - { - Text = "_1234", - Width = Dim.Auto () - }; - Assert.Equal (Size.Empty, view.Frame.Size); // Height is 0, so width is 0 regardless of text + DimAutoTestView view = new ("01234", Auto (DimAutoStyle.Text), Auto (DimAutoStyle.Text)); - view.Height = 1; - view.SetRelativeLayout (Application.Screen.Size); - Assert.Equal (new Size (4, 1), view.Frame.Size); - Size lastSize = view.Frame.Size; + view.SetRelativeLayout (new (10, 10)); + Assert.Equal (new (5, 1), view.Frame.Size); + Assert.Equal (new (0, 0), view.Frame.Location); - view.TextAlignment = Alignment.Fill; - Assert.Equal (lastSize, view.Frame.Size); + view.X = 0; - view = new () - { - Text = "_1234", - Width = Dim.Auto (), - Height = 1 - }; - view.SetRelativeLayout (Application.Screen.Size); + view.Y = Pos.AnchorEnd (1); + view.SetRelativeLayout (new (10, 10)); + Assert.Equal (new (5, 1), view.Frame.Size); + Assert.Equal (new (0, 9), view.Frame.Location); - lastSize = view.Frame.Size; - view.VerticalTextAlignment = Alignment.Center; - Assert.Equal (lastSize, view.Frame.Size); + view.Y = Pos.AnchorEnd (); + view.SetRelativeLayout (new (10, 10)); + Assert.Equal (new (5, 1), view.Frame.Size); + Assert.Equal (new (0, 9), view.Frame.Location); - view = new () - { - Text = "_1234", - Width = Dim.Auto (), - Height = 1, - }; - view.SetRelativeLayout (Application.Screen.Size); - lastSize = view.Frame.Size; - view.HotKeySpecifier = (Rune)'*'; - view.SetRelativeLayout (Application.Screen.Size); - Assert.NotEqual (lastSize, view.Frame.Size); + view.Y = Pos.AnchorEnd () - 1; + view.SetRelativeLayout (new (10, 10)); + Assert.Equal (new (5, 1), view.Frame.Size); + Assert.Equal (new (0, 8), view.Frame.Location); + } - view = new () - { - Text = "_1234", - Width = Dim.Auto (), - Height = 1 - }; - view.SetRelativeLayout (Application.Screen.Size); - lastSize = view.Frame.Size; - view.Text = "*ABCD"; - Assert.NotEqual (lastSize, view.Frame.Size); - } - - // Ensure TextFormatter.AutoSize is never used for View.Text - [Fact] - public void TextFormatter_Is_Not_Auto () - { - View view = new (); - Assert.False (view.TextFormatter.AutoSize); - view.Width = Dim.Auto (); - Assert.False (view.TextFormatter.AutoSize); - - view = new (); - Assert.False (view.TextFormatter.AutoSize); - view.Height = Dim.Auto (); - Assert.False (view.TextFormatter.AutoSize); - } - - [Theory] - [InlineData ("1234", 4)] - [InlineData ("_1234", 4)] - public void Width_Auto_HotKey_TextFormatter_Size_Correct (string text, int expected) - { - View view = new () - { - Text = text, - Height = 1, - Width = Dim.Auto () - }; - Assert.Equal (new (expected, 1), view.TextFormatter.Size); - } - - [Theory] - [InlineData ("1234", 4)] - [InlineData ("_1234", 4)] - public void Height_Auto_HotKey_TextFormatter_Size_Correct (string text, int expected) - { - View view = new () - { - HotKeySpecifier = (Rune)'_', - Text = text, - Width = Auto (), - Height = 1, - }; - Assert.Equal (new (expected, 1), view.TextFormatter.Size); - - view = new () - { - HotKeySpecifier = (Rune)'_', - TextDirection = TextDirection.TopBottom_LeftRight, - Text = text, - Width = 1, - Height = Auto (), - }; - Assert.Equal (new (1, expected), view.TextFormatter.Size); - } - - [SetupFakeDriver] - [Fact] - public void Change_To_Non_Auto_Resets_ContentSize () - { - View view = new () - { - Width = Auto (), - Height = Auto (), - Text = "01234" - }; - - Assert.Equal (new Rectangle (0, 0, 5, 1), view.Frame); - Assert.Equal (new Size (5, 1), view.GetContentSize ()); - - // Change text to a longer string - view.Text = "0123456789"; + #endregion DimAutoStyle.Text tests - Assert.Equal (new Rectangle (0, 0, 10, 1), view.Frame); - Assert.Equal (new Size (10, 1), view.GetContentSize ()); - - // If ContentSize was reset, these should cause it to update - view.Width = 5; - view.Height = 1; - - Assert.Equal (new Size (5, 1), view.GetContentSize ()); - } + #region DimAutoStyle.Content tests // DimAutoStyle.Content tests [Fact] @@ -901,7 +671,7 @@ public void DimAutoStyle_Content_UsesContentSize_WhenSet () var view = new View (); view.SetContentSize (new (10, 5)); - var dim = Dim.Auto (DimAutoStyle.Content); + Dim dim = Auto (DimAutoStyle.Content); int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); @@ -912,13 +682,14 @@ public void DimAutoStyle_Content_UsesContentSize_WhenSet () public void DimAutoStyle_Content_IgnoresSubviews_When_ContentSize_Is_Set () { var view = new View (); - var subview = new View () + + var subview = new View { - Frame = new Rectangle (50, 50, 1, 1) + Frame = new (50, 50, 1, 1) }; view.SetContentSize (new (10, 5)); - var dim = Dim.Auto (DimAutoStyle.Content); + Dim dim = Auto (DimAutoStyle.Content); int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); @@ -928,8 +699,8 @@ public void DimAutoStyle_Content_IgnoresSubviews_When_ContentSize_Is_Set () [Fact] public void DimAutoStyle_Content_IgnoresText_WhenContentSizeNotSet () { - var view = new View () { Text = "This is a test" }; - var dim = Dim.Auto (DimAutoStyle.Content); + var view = new View { Text = "This is a test" }; + Dim dim = Auto (DimAutoStyle.Content); int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); @@ -940,482 +711,26 @@ public void DimAutoStyle_Content_IgnoresText_WhenContentSizeNotSet () public void DimAutoStyle_Content_UsesLargestSubview_WhenContentSizeNotSet () { var view = new View (); - view.Add (new View () { Frame = new Rectangle (0, 0, 5, 5) }); // Smaller subview - view.Add (new View () { Frame = new Rectangle (0, 0, 10, 10) }); // Larger subview + view.Add (new View { Frame = new (0, 0, 5, 5) }); // Smaller subview + view.Add (new View { Frame = new (0, 0, 10, 10) }); // Larger subview - var dim = Dim.Auto (DimAutoStyle.Content); + Dim dim = Auto (DimAutoStyle.Content); int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); Assert.Equal (10, calculatedWidth); // Expecting the size of the largest subview } - // All the Dim types - - [Theory] - [InlineData (0, 15, 15)] - [InlineData (1, 15, 16)] - [InlineData (-1, 15, 14)] - public void With_Subview_Using_DimAbsolute (int subViewOffset, int dimAbsoluteSize, int expectedSize) - { - var view = new View (); - var subview = new View () - { - X = subViewOffset, - Y = subViewOffset, - Width = Dim.Absolute (dimAbsoluteSize), - Height = Dim.Absolute (dimAbsoluteSize) - }; - view.Add (subview); - - var dim = Dim.Auto (DimAutoStyle.Content); - - int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dim.Calculate (0, 100, view, Dimension.Height); - - Assert.Equal (expectedSize, calculatedWidth); - Assert.Equal (expectedSize, calculatedHeight); - } - - [Theory] - [InlineData (0, 0, 0, 0, 0, 0, 0, 0)] - [InlineData (0, 50, 0, 0, 0, 0, 0, 0)] - [InlineData (0, 0, 100, 100, 100, 100, 100, 100)] - [InlineData (0, 50, 100, 100, 100, 100, 100, 100)] - public void With_Subview_Using_DimPercent (int subViewOffset, int percent, 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 = subViewOffset, - Y = subViewOffset, - Width = Dim.Percent (percent), - Height = Dim.Percent (percent) - }; - 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); // subview's width - Assert.Equal (expectedHeight, calculatedHeight); // subview's height - Assert.Equal (subViewOffset, calculatedX); - Assert.Equal (subViewOffset, calculatedY); - - view.SetRelativeLayout (new (100, 100)); - view.LayoutSubviews (); - - Assert.Equal (expectedWidth * (percent / 100f), subview.Viewport.Width); - Assert.Equal (expectedHeight * (percent / 100f), subview.Viewport.Height); - } - - [Theory] - [InlineData (0, 0, 0, 0, 0, 0)] - [InlineData (0, 19, 0, 9, 0, 0)] - [InlineData (0, 20, 0, 10, 0, 0)] - [InlineData (0, 21, 0, 11, 0, 0)] - [InlineData (1, 21, 1, 11, 1, 1)] - [InlineData (21, 21, 11, 11, 21, 11)] - public void With_Subview_Using_DimFill (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 = 0, - Y = 0, - Width = Fill (), - Height = Fill () - }; - 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); - } - - [Theory] - [InlineData (0, 0, 0, 0, 0, 0)] - [InlineData (0, 19, 0, 9, 2, 4)] - [InlineData (0, 20, 0, 10, 2, 4)] - [InlineData (0, 21, 0, 11, 2, 4)] - [InlineData (1, 21, 1, 11, 2, 4)] - [InlineData (21, 21, 11, 11, 21, 11)] - public void With_Subview_Using_DimFill_And_Another_Subview (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 absView = new View () - { - X = 1, - Y = 2, - Width = 1, - Height = 2 - }; - view.Add (absView); - var subview = new View () - { - X = 0, - Y = 0, - Width = Fill (), - Height = Fill () - }; - 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); - } - - - [Fact] - public void With_Subview_Using_DimFunc () - { - var view = new View (); - var subview = new View () { Width = Dim.Func (() => 20), Height = Dim.Func (() => 25) }; - view.Add (subview); - - subview.SetRelativeLayout (new (100, 100)); - - var dim = Dim.Auto (DimAutoStyle.Content); - - int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dim.Calculate (0, 100, view, Dimension.Height); - - Assert.Equal (20, calculatedWidth); - Assert.Equal (25, calculatedHeight); - } - [Fact] - public void With_Subview_Using_DimView () - { - var view = new View (); - var subview = new View () { Width = 30, Height = 40 }; - var subSubview = new View () { Width = Dim.Width (subview), Height = Dim.Height (subview) }; - view.Add (subview); - view.Add (subSubview); - - subview.SetRelativeLayout (new (100, 100)); - - var dim = Dim.Auto (DimAutoStyle.Content); - - int calculatedWidth = dim.Calculate (0, 100, view, Dimension.Width); - int calculatedHeight = dim.Calculate (0, 100, view, Dimension.Height); - - // Expecting the size to match the subview, which is the largest - Assert.Equal (30, calculatedWidth); - Assert.Equal (40, calculatedHeight); - } - - // Testing all Pos combinations - - [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, 21, 11)] - [InlineData (1, 21, 1, 11, 21, 11)] - [InlineData (21, 21, 11, 11, 21, 11)] - public void With_Subview_Using_PosAbsolute (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.Absolute (10), - 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); - } - - [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_PosPercent (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.Percent (50), - Y = Pos.Percent (50), - 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); - - view.BeginInit (); - view.EndInit (); - // subview should be at 50% in the parent view - Assert.Equal ((int)(view.Viewport.Width * .50), subview.Frame.X); - Assert.Equal ((int)(view.Viewport.Height * .50), subview.Frame.Y); - } - - [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, 21, 11)] - [InlineData (1, 21, 1, 11, 21, 11)] - [InlineData (21, 21, 11, 11, 21, 11)] - public void With_Subview_Using_PosPercent_Combine (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.Percent (50) + 1, - Y = 1 + Pos.Percent (50), - 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); - - view.BeginInit (); - view.EndInit (); - // subview should be at 50% in the parent view - Assert.Equal ((int)(view.Viewport.Width * .50) + 1, subview.Frame.X); - Assert.Equal ((int)(view.Viewport.Height * .50) + 1, subview.Frame.Y); - } - - [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_PosCenter (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.Center (), - Y = Pos.Center (), - 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); - - view.BeginInit (); - view.EndInit (); - // subview should be centered in the parent view + 1 - Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2, subview.Frame.X); - Assert.Equal ((view.Viewport.Height - subview.Frame.Height) / 2, subview.Frame.Y); - } - - [Theory] - [InlineData (0, 0, 0, 0, 0, 0)] - [InlineData (0, 19, 0, 9, 19, 9)] - [InlineData (0, 18, 0, 8, 18, 8)] - [InlineData (0, 20, 0, 10, 20, 10)] - [InlineData (0, 21, 0, 11, 21, 11)] - [InlineData (1, 21, 1, 11, 21, 11)] - [InlineData (21, 21, 11, 11, 21, 11)] - public void With_Subview_Using_PosCenter_Combine (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.Center () + 1, - Y = 1 + Pos.Center (), - 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); - - view.BeginInit (); - view.EndInit (); - // subview should be centered in the parent view + 1 - Assert.Equal ((view.Viewport.Width - subview.Frame.Width) / 2 + 1, subview.Frame.X); - Assert.Equal ((view.Viewport.Height - subview.Frame.Height) / 2 + 1, subview.Frame.Y); - } - - [Theory] - [InlineData (0, 0, 0, 0, 0, 0)] - [InlineData (0, 19, 0, 9, 19, 9)] - [InlineData (0, 18, 0, 8, 18, 8)] - [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_PosAnchorEnd (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.AnchorEnd (), - Y = Pos.AnchorEnd (), - 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); - - view.BeginInit (); - view.EndInit (); - // subview should be at the end of the view - Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); - Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); - } - - [Fact] - public void DimAutoStyle_Text_Pos_AnchorEnd_Locates_Correctly () + public void DimAutoStyle_Content_UsesContentSize_If_No_Subviews () { - DimAutoTestView view = new ("01234", Auto (DimAutoStyle.Text), Auto (DimAutoStyle.Text)); - - view.SetRelativeLayout (new (10, 10)); - Assert.Equal (new (5, 1), view.Frame.Size); - Assert.Equal (new (0, 0), view.Frame.Location); - - view.X = 0; - - view.Y = Pos.AnchorEnd (1); - view.SetRelativeLayout (new (10, 10)); - Assert.Equal (new (5, 1), view.Frame.Size); - Assert.Equal (new (0, 9), view.Frame.Location); - - view.Y = Pos.AnchorEnd (); + DimAutoTestView view = new (Auto (DimAutoStyle.Content), Auto (DimAutoStyle.Content)); + view.SetContentSize (new (5, 5)); view.SetRelativeLayout (new (10, 10)); - Assert.Equal (new (5, 1), view.Frame.Size); - Assert.Equal (new (0, 9), view.Frame.Location); - view.Y = Pos.AnchorEnd () - 1; - view.SetRelativeLayout (new (10, 10)); - Assert.Equal (new (5, 1), view.Frame.Size); - Assert.Equal (new (0, 8), view.Frame.Location); + Assert.Equal (new (5, 5), view.Frame.Size); } - [Fact] public void DimAutoStyle_Content_Pos_AnchorEnd_Locates_Correctly () { @@ -1450,45 +765,114 @@ public void DimAutoStyle_Content_Pos_AnchorEnd_Locates_Correctly () Assert.Equal (new (0, 8), view.Frame.Location); } + #endregion DimAutoStyle.Content tests - [Theory] - [InlineData ("01234", 5, 5)] - [InlineData ("01234", 6, 6)] - [InlineData ("01234", 4, 5)] - [InlineData ("01234", 0, 5)] - [InlineData ("", 5, 5)] - [InlineData ("", 0, 0)] - public void DimAutoStyle_Auto_Larger_Wins (string text, int dimension, int expected) + [Fact] + public void TextFormatter_Settings_Change_View_Size () { View view = new () { - Width = Auto (), - Height = 1, - Text = text + Text = "_1234", + Width = Auto () }; + Assert.Equal (Size.Empty, view.Frame.Size); // Height is 0, so width is 0 regardless of text - View subView = new () + view.Height = 1; + view.SetRelativeLayout (Application.Screen.Size); + Assert.Equal (new (4, 1), view.Frame.Size); + Size lastSize = view.Frame.Size; + + view.TextAlignment = Alignment.Fill; + Assert.Equal (lastSize, view.Frame.Size); + + view = new () { - Width = dimension, - Height = 1, + Text = "_1234", + Width = Auto (), + Height = 1 }; - view.Add (subView); + view.SetRelativeLayout (Application.Screen.Size); - view.SetRelativeLayout (new (10, 10)); - Assert.Equal (expected, view.Frame.Width); + lastSize = view.Frame.Size; + view.VerticalTextAlignment = Alignment.Center; + Assert.Equal (lastSize, view.Frame.Size); + view = new () + { + Text = "_1234", + Width = Auto (), + Height = 1 + }; + view.SetRelativeLayout (Application.Screen.Size); + lastSize = view.Frame.Size; + view.HotKeySpecifier = (Rune)'*'; + view.SetRelativeLayout (Application.Screen.Size); + Assert.NotEqual (lastSize, view.Frame.Size); + + view = new () + { + Text = "_1234", + Width = Auto (), + Height = 1 + }; + view.SetRelativeLayout (Application.Screen.Size); + lastSize = view.Frame.Size; + view.Text = "*ABCD"; + Assert.NotEqual (lastSize, view.Frame.Size); } + // Ensure TextFormatter.AutoSize is never used for View.Text [Fact] - public void DimAutoStyle_Content_UsesContentSize_If_No_Subviews () + public void TextFormatter_Is_Not_Auto () { - DimAutoTestView view = new (Auto (DimAutoStyle.Content), Auto (DimAutoStyle.Content)); - view.SetContentSize (new (5, 5)); - view.SetRelativeLayout (new (10, 10)); + View view = new (); + Assert.False (view.TextFormatter.AutoSize); + view.Width = Auto (); + Assert.False (view.TextFormatter.AutoSize); - Assert.Equal (new (5, 5), view.Frame.Size); + view = new (); + Assert.False (view.TextFormatter.AutoSize); + view.Height = Auto (); + Assert.False (view.TextFormatter.AutoSize); + } + + [Theory] + [InlineData ("1234", 4)] + [InlineData ("_1234", 4)] + public void HotKey_TextFormatter_Width_Correct (string text, int expected) + { + View view = new () + { + Text = text, + Height = 1, + Width = Auto () + }; + Assert.Equal (new (expected, 1), view.TextFormatter.Size); + } + [Theory] + [InlineData ("1234", 4)] + [InlineData ("_1234", 4)] + public void HotKey_TextFormatter_Height_Correct (string text, int expected) + { + View view = new () + { + HotKeySpecifier = (Rune)'_', + Text = text, + Width = Auto (), + Height = 1 + }; + Assert.Equal (new (expected, 1), view.TextFormatter.Size); + view = new () + { + HotKeySpecifier = (Rune)'_', + TextDirection = TextDirection.TopBottom_LeftRight, + Text = text, + Width = 1, + Height = Auto () + }; + Assert.Equal (new (1, expected), view.TextFormatter.Size); } // Test variations of Frame From 2cecc7762a87e8f8da9a43eda7351374913b27ce Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 16:18:58 -0600 Subject: [PATCH 25/96] WIP - Trying to make TextFormatter have indepdentent width/height --- Terminal.Gui/Text/TextFormatter.cs | 118 ++++++++++++++---- Terminal.Gui/View/Layout/DimAuto.cs | 33 ++++- Terminal.Gui/View/Layout/ViewLayout.cs | 5 + Terminal.Gui/View/ViewText.cs | 46 ++----- Terminal.Gui/Views/MessageBox.cs | 36 +++--- UICatalog/Scenarios/ComputedLayout.cs | 55 +++++++- UICatalog/Scenarios/MessageBoxes.cs | 9 +- UnitTests/Text/TextFormatterTests.cs | 7 +- .../View/Layout/Dim.AutoTests.PosTypes.cs | 49 ++++++++ UnitTests/View/Layout/Dim.AutoTests.cs | 59 ++++++++- 10 files changed, 326 insertions(+), 91 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 79967f5175..e6d97ff09a 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -15,7 +16,6 @@ public class TextFormatter private List _lines = new (); private bool _multiLine; private bool _preserveTrailingSpaces; - private Size _size; private int _tabWidth = 4; private string _text; private Alignment _textAlignment = Alignment.Start; @@ -177,6 +177,48 @@ public bool PreserveTrailingSpaces set => _preserveTrailingSpaces = EnableNeedsFormat (value); } + private int? _width; + + public int? Width + { + get => _width; + set + { + if (_width == value) + { + return; + } + _width = value; + if (_width is null || _height is null) + { + return; + } + Size size = EnableNeedsFormat (Size!.Value); + _width = size.Width; + } + } + + private int? _height; + + public int? Height + { + get => _height; + set + { + if (_height == value) + { + return; + } + _height = value; + if (_width is null || _height is null) + { + return; + } + Size size = EnableNeedsFormat (Size!.Value); + _height = size.Height; + } + } + /// Gets or sets the size will be constrained to when formatted. /// /// @@ -185,18 +227,38 @@ public bool PreserveTrailingSpaces /// /// When set, is set to . /// - public Size Size + public Size? Size { - get => _size; + get + { + if (_width is null || _height is null) + { + return null; + } + + return new Size (_width.Value, _height.Value); + } set { if (AutoSize) { - _size = EnableNeedsFormat (GetAutoSize ()); + Size size = EnableNeedsFormat (GetAutoSize ()); + _width = size.Width; + _height = size.Height; } else { - _size = EnableNeedsFormat (value); + if (value is null) + { + _width = null; + _height = null; + } + else + { + Size size = EnableNeedsFormat (value.Value); + _width = size.Width; + _height = size.Height; + } } } } @@ -651,19 +713,20 @@ public string Format () /// The size required to hold the formatted text. public Size FormatAndGetSize (Size? constrainSize = null) { - if (constrainSize is null) - { - constrainSize = Size; - } - if (string.IsNullOrEmpty (Text) || constrainSize.Value.Height == 0 || constrainSize.Value.Width == 0) + if (string.IsNullOrEmpty (Text)) { - return Size.Empty; + return System.Drawing.Size.Empty; } // HACK: This is a total hack to work around the fact that TextFormatter.Format does not match TextFormatter.Draw. - Size prevSize = Size; - Size = constrainSize.Value; - + int? prevWidth = _width; + int? prevHeight = _height; + if (constrainSize is { }) + { + _width = constrainSize?.Width; + _height = constrainSize?.Height; + } + // HACK: Fill normally will fill the entire constraint size, but we need to know the actual size of the text. Alignment prevAlignment = Alignment; if (Alignment == Alignment.Fill) @@ -681,11 +744,16 @@ public Size FormatAndGetSize (Size? constrainSize = null) // Undo hacks Alignment = prevAlignment; VerticalAlignment = prevVerticalAlignment; - Size = prevSize; + + if (constrainSize is { }) + { + _width = prevWidth ?? null; + _height = prevHeight ?? null; + } if (lines.Count == 0) { - return Size.Empty; + return System.Drawing.Size.Empty; } int width; @@ -720,7 +788,7 @@ public Size FormatAndGetSize (Size? constrainSize = null) public List GetLines () { // With this check, we protect against subclasses with overrides of Text - if (string.IsNullOrEmpty (Text) || Size.Height == 0 || Size.Width == 0) + if (string.IsNullOrEmpty (Text) || Size is null || Size?.Height == 0 || Size?.Width == 0) { _lines = new List { string.Empty }; NeedsFormat = false; @@ -745,9 +813,9 @@ public List GetLines () _lines = Format ( text, - Size.Height, + Size!.Value.Height, VerticalAlignment == Alignment.Fill, - Size.Width > colsWidth && WordWrap, + Size!.Value.Width > colsWidth && WordWrap, PreserveTrailingSpaces, TabWidth, Direction, @@ -757,7 +825,7 @@ public List GetLines () if (!AutoSize) { - colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth); + colsWidth = GetMaxColsForWidth (_lines, Size!.Value.Width, TabWidth); if (_lines.Count > colsWidth) { @@ -769,9 +837,9 @@ public List GetLines () { _lines = Format ( text, - Size.Width, + Size!.Value.Width, Alignment == Alignment.Fill, - Size.Height > 1 && WordWrap, + Size!.Value.Height > 1 && WordWrap, PreserveTrailingSpaces, TabWidth, Direction, @@ -779,9 +847,9 @@ public List GetLines () this ); - if (!AutoSize && _lines.Count > Size.Height) + if (!AutoSize && _lines.Count > Size!.Value.Height) { - _lines.RemoveRange (Size.Height, _lines.Count - Size.Height); + _lines.RemoveRange (Size!.Value.Height, _lines.Count - Size!.Value.Height); } } @@ -1952,7 +2020,7 @@ public static Rectangle CalcRect ( { if (string.IsNullOrEmpty (text)) { - return new (new (x, y), Size.Empty); + return new (new (x, y), System.Drawing.Size.Empty); } int w, h; diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index fedc96188b..20569cc98e 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -70,19 +70,40 @@ internal override int Calculate (int location, int superviewContentSize, View us if (Style.FastHasFlags (DimAutoStyle.Text)) { + if (dimension == Dimension.Width) { - //us.TextFormatter.Size = new (superviewContentSize, 2048); - textSize = us.TextFormatter.FormatAndGetSize ().Width; - //us.TextFormatter.Size = new Size (textSize, 2048); + if (us.TextFormatter.Width is null) + { + us.TextFormatter.Size = us.TextFormatter.FormatAndGetSize (new (int.Max (autoMax, superviewContentSize), screen)); + } +// else + { + textSize = us.TextFormatter.Width.Value; + } } else { - //if (us.TextFormatter.Size.Width == 0) + if (us.TextFormatter.Height is null) + { + textSize = us.TextFormatter.FormatAndGetSize (new (us.TextFormatter.Width ?? screen, int.Max (autoMax, superviewContentSize))).Height; + us.TextFormatter.Height = textSize; + } + else + { + textSize = us.TextFormatter.Height.Value; + } + + //if (us.Width.Has (typeof(DimAuto), out var widthDim)) //{ - // us.TextFormatter.Size = us.TextFormatter.GetAutoSize (); + // DimAuto widthDimAuto = (DimAuto)widthDim; + // textSize = us.TextFormatter.FormatAndGetSize (us.GetContentSize ()).Height; //} - textSize = us.TextFormatter.FormatAndGetSize ().Height; + //else + //{ + // textSize = us.TextFormatter.FormatAndGetSize ().Height; + //} + //us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize }; } } diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index d3d241d20e..a569a21658 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -576,6 +576,7 @@ internal void SetRelativeLayout (Size superviewContentSize) SetTextFormatterSize (); int newX, newW, newY, newH; + Rectangle oldFrame = Frame; // Calculate the new X, Y, Width, and Height // If the Width or Height is Dim.Auto, calculate the Width or Height first. Otherwise, calculate the X or Y first. @@ -583,6 +584,7 @@ internal void SetRelativeLayout (Size superviewContentSize) { newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width); newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width); + //SetFrame (oldFrame with { X = newX, Width = newW }); } else { @@ -601,6 +603,8 @@ internal void SetRelativeLayout (Size superviewContentSize) newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); } + SetFrame (oldFrame); + Rectangle newFrame = new (newX, newY, newW, newH); if (Frame != newFrame) @@ -743,6 +747,7 @@ internal void OnResizeNeeded () Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : Application.Screen.Size; + SetTextFormatterSize (); SetRelativeLayout (superViewContentSize); if (IsInitialized) diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 8ca567fbd8..9fdb6598fa 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -180,48 +180,24 @@ internal void SetTextFormatterSize () // Default is to use GetContentSize (). var size = GetContentSize (); - // TODO: This is a hack. Figure out how to move this into DimDimAuto + // TODO: This is a hack. Figure out how to move this logic into DimAuto // Use _width & _height instead of Width & Height to avoid debug spew DimAuto? widthAuto = _width as DimAuto; DimAuto? heightAuto = _height as DimAuto; - if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - || (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) + if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text))) { - int width = 0; - int height = 0; - - if (widthAuto is null || !widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - width = GetContentSize ().Width; - } - - if (heightAuto is null || !heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - height = GetContentSize ().Height; - } - - if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - if (height == 0 && heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - height = Application.Screen.Width * 4; - } - width = TextFormatter.FormatAndGetSize (new (Application.Screen.Width * 4, height)).Width; - } - - if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - if (width == 0 && widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - width = Application.Screen.Width * 4; - } - height = TextFormatter.FormatAndGetSize (new (width, Application.Screen.Height * 4)).Height; - } + TextFormatter.Width = null; + } - size = new (width, height); + if ((heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) + { + TextFormatter.Height = null; } - TextFormatter.Size = size; + if (TextFormatter.Size is { }) + { + TextFormatter.Size = size; + } } private void UpdateTextDirection (TextDirection newDirection) diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index f4150d5ab9..97fcf94527 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -369,8 +369,8 @@ params string [] buttons ButtonAlignment = Alignment.Center, ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems, BorderStyle = MessageBox.DefaultBorderStyle, - Width = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 1, maximumContentDim: Dim.Percent (90)), - Height = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 2, maximumContentDim: Dim.Percent (90)), + Width = Dim.Auto (DimAutoStyle.Auto, /*minimumContentDim: Dim.Percent (DefaultMinimumWidth), */ maximumContentDim: Dim.Percent (90)), + Height = Dim.Auto (DimAutoStyle.Auto, /*minimumContentDim: Dim.Percent (DefaultMinimumHeight),*/ maximumContentDim: Dim.Percent (90)), }; if (width != 0) @@ -385,22 +385,22 @@ params string [] buttons d.ColorScheme = useErrorColors ? Colors.ColorSchemes ["Error"] : Colors.ColorSchemes ["Dialog"]; - d.LayoutComplete += (s, e) => - { - if (wrapMessage) - { - int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; - Debug.Assert (d.TextFormatter.WordWrap); - d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Screen.Height); - Size textSize = d.TextFormatter.GetAutoSize (); - textSize.Height += buttonHeight; - - if (textSize != d.TextFormatter.Size) - { - d.SetNeedsLayout (); - } - } - }; + //d.LayoutComplete += (s, e) => + //{ + // if (wrapMessage) + // { + // int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; + // Debug.Assert (d.TextFormatter.WordWrap); + // d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Screen.Height); + // Size textSize = d.TextFormatter.GetAutoSize (); + // textSize.Height += buttonHeight; + + // if (textSize != d.TextFormatter.Size) + // { + // d.SetNeedsLayout (); + // } + // } + //}; d.HotKeySpecifier = new Rune ('\xFFFF'); d.Text = message; diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index cf0564126d..cb7a721303 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -7,8 +7,7 @@ namespace UICatalog.Scenarios; /// -/// This Scenario demonstrates how to use Termina.gui's Dim and Pos Layout System. [x] - Using Dim.Fill to fill a -/// window [x] - Using Dim.Fill and Dim.Pos to automatically align controls based on an initial control [ ] - ... +/// This Scenario demonstrates how to use Terminal.Gui's Dim and Pos Layout System. /// [ScenarioMetadata ("Computed Layout", "Demonstrates the Computed (Dim and Pos) Layout System.")] [ScenarioCategory ("Layout")] @@ -216,6 +215,58 @@ public override void Main () fv.Title = $"{frameView.GetType ().Name} {{X={fv.X},Y={fv.Y},Width={fv.Width},Height={fv.Height}}}"; }; + + labelList = new List Text = 2, diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index a569a21658..1632b47aba 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -65,8 +65,6 @@ private void SetFrame (in Rectangle frame) // This is the only place where _frame should be set directly. Use Frame = or SetFrame instead. _frame = frame; - SetTextFormatterSize (); - OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); } @@ -235,7 +233,7 @@ public Dim? Height return; } - if (_height is DimAuto) + if (_height is { } && _height.Has (typeof (DimAuto), out _)) { // Reset ContentSize to Viewport _contentSize = null; @@ -243,6 +241,9 @@ public Dim? Height _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); + // Reset TextFormatter - Will be recalculated in SetTextFormatterSize + TextFormatter.Height = null; + OnResizeNeeded (); } } @@ -280,7 +281,7 @@ public Dim? Width return; } - if (_width is DimAuto) + if (_width is { } && _width.Has (typeof (DimAuto), out _)) { // Reset ContentSize to Viewport _contentSize = null; @@ -288,6 +289,9 @@ public Dim? Width _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); + // Reset TextFormatter - Will be recalculated in SetTextFormatterSize + TextFormatter.Width = null; + OnResizeNeeded (); } } @@ -576,7 +580,6 @@ internal void SetRelativeLayout (Size superviewContentSize) SetTextFormatterSize (); int newX, newW, newY, newH; - Rectangle oldFrame = Frame; // Calculate the new X, Y, Width, and Height // If the Width or Height is Dim.Auto, calculate the Width or Height first. Otherwise, calculate the X or Y first. @@ -584,7 +587,6 @@ internal void SetRelativeLayout (Size superviewContentSize) { newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width); newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width); - //SetFrame (oldFrame with { X = newX, Width = newW }); } else { @@ -603,8 +605,6 @@ internal void SetRelativeLayout (Size superviewContentSize) newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); } - SetFrame (oldFrame); - Rectangle newFrame = new (newX, newY, newW, newH); if (Frame != newFrame) @@ -636,9 +636,21 @@ internal void SetRelativeLayout (Size superviewContentSize) { SetTitleTextFormatterSize (); } + SetNeedsLayout (); SetNeedsDisplay (); } + + if (TextFormatter.Width is null) + { + TextFormatter.Width = GetContentSize ().Width; + } + + if (TextFormatter.Height is null) + { + TextFormatter.Height = GetContentSize ().Height; + } + } @@ -671,7 +683,7 @@ public virtual void LayoutSubviews () LayoutAdornments (); - SetTextFormatterSize (); + //SetTextFormatterSize (); // Sort out the dependencies of the X, Y, Width, Height properties HashSet nodes = new (); @@ -747,7 +759,6 @@ internal void OnResizeNeeded () Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : Application.Screen.Size; - SetTextFormatterSize (); SetRelativeLayout (superViewContentSize); if (IsInitialized) diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 9fdb6598fa..a620e6a2bb 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Diagnostics; using static Unix.Terminal.Curses; namespace Terminal.Gui; @@ -88,6 +89,7 @@ public void OnTextChanged () /// public event EventHandler? TextChanged; + // TODO: Make this non-virtual. Nobody overrides it. /// /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will /// redisplay the . @@ -107,6 +109,7 @@ public virtual Alignment TextAlignment } } + // TODO: Make this non-virtual. Nobody overrides it. /// /// Gets or sets the direction of the View's . Changing this property will redisplay the /// . @@ -118,11 +121,7 @@ public virtual Alignment TextAlignment public virtual TextDirection TextDirection { get => TextFormatter.Direction; - set - { - UpdateTextDirection (value); - TextFormatter.Direction = value; - } + set => UpdateTextDirection (value); } /// @@ -130,6 +129,7 @@ public virtual TextDirection TextDirection /// public TextFormatter TextFormatter { get; init; } = new () { }; + // TODO: Make this non-virtual. Nobody overrides it. /// /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will /// redisplay @@ -149,6 +149,7 @@ public virtual Alignment VerticalTextAlignment } } + // TODO: Add a OnUpdateTextFormatterText method that invokes UpdateTextFormatterText so that overrides don't have to call base. /// /// Can be overridden if the has /// different format than the default. @@ -158,6 +159,8 @@ protected virtual void UpdateTextFormatterText () if (TextFormatter is { }) { TextFormatter.Text = _text; + TextFormatter.Width = null; + TextFormatter.Height = null; } } @@ -178,7 +181,7 @@ internal void SetTextFormatterSize () UpdateTextFormatterText (); // Default is to use GetContentSize (). - var size = GetContentSize (); + Size? size = _contentSize; // TODO: This is a hack. Figure out how to move this logic into DimAuto // Use _width & _height instead of Width & Height to avoid debug spew @@ -188,15 +191,24 @@ internal void SetTextFormatterSize () { TextFormatter.Width = null; } + else + { + if (size is { }) + { + TextFormatter.Width = size?.Width; + } + } if ((heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) { TextFormatter.Height = null; } - - if (TextFormatter.Size is { }) + else { - TextFormatter.Size = size; + if (size is { }) + { + TextFormatter.Height = size?.Height; + } } } @@ -209,10 +221,11 @@ private void UpdateTextDirection (TextDirection newDirection) if (directionChanged) { + TextFormatter.Width = null; + TextFormatter.Height = null; OnResizeNeeded (); } - SetTextFormatterSize (); SetNeedsDisplay (); } } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 81d70ba9fc..f489a8bb4e 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -170,6 +170,7 @@ public bool IsDefault /// protected override void UpdateTextFormatterText () { + base.UpdateTextFormatterText(); if (NoDecorations) { TextFormatter.Text = Text; diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index cd1e00ac84..9428ba91f9 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -182,6 +182,7 @@ public CheckState State /// protected override void UpdateTextFormatterText () { + base.UpdateTextFormatterText(); switch (TextAlignment) { case Alignment.Start: diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 0448d5ef45..28f79d58ac 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -623,7 +623,6 @@ private void SetContentOffset (Point offset) { _horizontal.Position = Math.Max (0, -_contentOffset.X); } - SetNeedsDisplay (); } diff --git a/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs index f048e33236..c7f4bc9804 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs @@ -306,7 +306,7 @@ public void With_Subview_Using_PosAnchorEnd (int minWidth, int maxWidth, int min [Theory] [InlineData (0, 10, 0, 10, 10, 2)] [InlineData (0, 5, 0, 5, 5, 3)] // max width of 5 should cause wordwrap at 5 giving a height of 2 + 1 - //[InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 19, 0, 9, 11, 2)] //[InlineData (0, 20, 0, 10, 20, 10)] //[InlineData (0, 21, 0, 11, 21, 11)] //[InlineData (1, 21, 1, 11, 21, 11)] diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index d9be905cff..27ccc197de 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -528,6 +528,25 @@ public void Change_To_Non_Auto_Resets_ContentSize () #region DimAutoStyle.Auto tests + [Theory] + [InlineData ("", 0, 0)] + [InlineData (" ", 1, 1)] + [InlineData ("01234", 5, 1)] + [InlineData ("01234\nABCDE", 5, 2)] + public void DimAutoStyle_Auto_JustText_Sizes_Correctly (string text, int expectedW, int expectedH) + { + var view = new View (); + view.Width = Auto (); + view.Height = Auto (); + + view.Text = text; + + view.SetRelativeLayout (Application.Screen.Size); + + Assert.Equal (new (expectedW, expectedH), view.Frame.Size); + + } + [Fact] public void DimAutoStyle_Auto_Text_Size_Is_Used () { @@ -622,6 +641,7 @@ public void DimAutoStyle_Text_Viewport_Stays_Set () [InlineData ("", 0, 0)] [InlineData (" ", 1, 1)] [InlineData ("01234", 5, 1)] + [InlineData ("01234\nABCDE", 5, 2)] public void DimAutoStyle_Text_Sizes_Correctly (string text, int expectedW, int expectedH) { var view = new View (); @@ -677,7 +697,10 @@ public void DimAutoStyle_Text_Sizes_Correctly_With_Max_Width (string text, int m [InlineData ("", 0, 0)] [InlineData (" ", 1, 1)] [InlineData ("01234", 5, 1)] - public void DimAutoStyle_Text_Ignores_ContentSize (string text, int expectedW, int expectedH) + [InlineData ("01234ABCDE", 10, 1)] + [InlineData ("01234\nABCDE", 5, 2)] + + public void DimAutoStyle_Text_NoMin_Not_Constrained_By_ContentSize (string text, int expectedW, int expectedH) { var view = new View (); view.Width = Auto (DimAutoStyle.Text); @@ -688,6 +711,31 @@ public void DimAutoStyle_Text_Ignores_ContentSize (string text, int expectedW, i Assert.Equal (new (expectedW, expectedH), view.Frame.Size); } + + [Theory] + [InlineData ("", 0, 0)] + [InlineData (" ", 1, 1)] + [InlineData ("01234", 5, 1)] + [InlineData ("01234ABCDE", 10, 1)] + [InlineData ("01234\nABCDE", 5, 2)] + public void DimAutoStyle_Text_NoMin_Not_Constrained_By_SuperView (string text, int expectedW, int expectedH) + { + var superView = new View () + { + Width = 1, Height = 1 + }; + + var view = new View (); + + view.Width = Auto (DimAutoStyle.Text); + view.Height = Auto (DimAutoStyle.Text); + view.Text = text; + superView.Add (view); + + superView.SetRelativeLayout (Application.Screen.Size); + Assert.Equal (new (expectedW, expectedH), view.Frame.Size); + } + [Fact] public void DimAutoStyle_Text_Pos_AnchorEnd_Locates_Correctly () { @@ -902,7 +950,8 @@ public void HotKey_TextFormatter_Width_Correct (string text, int expected) Height = 1, Width = Auto () }; - Assert.Equal (new (expected, 1), view.TextFormatter.Size); + Assert.Equal (expected, view.TextFormatter.Width); + Assert.Equal (1, view.TextFormatter.Height); } [Theory] @@ -917,7 +966,8 @@ public void HotKey_TextFormatter_Height_Correct (string text, int expected) Width = Auto (), Height = 1 }; - Assert.Equal (new (expected, 1), view.TextFormatter.Size); + Assert.Equal (expected, view.TextFormatter.Width); + Assert.Equal (1, view.TextFormatter.Height); view = new () { @@ -927,7 +977,8 @@ public void HotKey_TextFormatter_Height_Correct (string text, int expected) Width = 1, Height = Auto () }; - Assert.Equal (new (1, expected), view.TextFormatter.Size); + Assert.Equal (1, view.TextFormatter.Width); + Assert.Equal (expected, view.TextFormatter.Height); } // Test variations of Frame diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 4ef86d3875..3986bf89fe 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -76,11 +76,9 @@ public void Button_AbsoluteSize_Text (string text, int width, int height, int ex [InlineData (10, 3, 10, 3)] public void Button_AbsoluteSize_DefaultText (int width, int height, int expectedWidth, int expectedHeight) { - var btn1 = new Button - { - Width = width, - Height = height, - }; + var btn1 = new Button (); + btn1.Width = width; + btn1.Height = height; Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.Frame.Size); Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.Viewport.Size); diff --git a/UnitTests/Views/ScrollViewTests.cs b/UnitTests/Views/ScrollViewTests.cs index ceab4e63a0..8176479d30 100644 --- a/UnitTests/Views/ScrollViewTests.cs +++ b/UnitTests/Views/ScrollViewTests.cs @@ -349,7 +349,7 @@ public void Constructors_Defaults () Assert.True (sv.AutoHideScrollBars); Assert.True (sv.KeepContentAlwaysInViewport); - sv = new() { X = 1, Y = 2, Width = 20, Height = 10 }; + sv = new () { X = 1, Y = 2, Width = 20, Height = 10 }; Assert.True (sv.CanFocus); Assert.Equal (new (1, 2, 20, 10), sv.Frame); Assert.Equal (Point.Empty, sv.ContentOffset); @@ -364,7 +364,7 @@ public void ContentBottomRightCorner_Draw () { ((FakeDriver)Application.Driver).SetBufferSize (30, 30); - var top = new View { Width = 30, Height = 30, ColorScheme = new() { Normal = Attribute.Default } }; + var top = new View { Width = 30, Height = 30, ColorScheme = new () { Normal = Attribute.Default } }; Size size = new (20, 10); @@ -374,7 +374,7 @@ public void ContentBottomRightCorner_Draw () Y = 1, Width = 10, Height = 5, - ColorScheme = new() { Normal = new (Color.Red, Color.Green) } + ColorScheme = new () { Normal = new (Color.Red, Color.Green) } }; sv.SetContentSize (size); string text = null; @@ -391,7 +391,7 @@ public void ContentBottomRightCorner_Draw () var view = new View { - ColorScheme = new() { Normal = new (Color.Blue, Color.Yellow) }, + ColorScheme = new () { Normal = new (Color.Blue, Color.Yellow) }, Width = Dim.Auto (DimAutoStyle.Text), Height = Dim.Auto (DimAutoStyle.Text), Text = text @@ -523,7 +523,9 @@ public void DrawTextFormatter_Respects_The_Clip_Bounds () view.Add ( new Label { - Width = Dim.Fill (), Height = 1, Text = rule.Repeat (size.Width / rule.Length) + Width = Dim.Fill (), + Height = 1, + Text = rule.Repeat (size.Width / rule.Length) } ); @@ -856,8 +858,8 @@ public void DrawTextFormatter_Respects_The_Clip_Bounds () └──────────────────┘ "; - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new (1, 1, 21, 14), pos); + TestHelpers.AssertDriverContentsAre (expected, output); + top.Dispose (); } @@ -1090,7 +1092,7 @@ public CustomButton (string fill, string text, int width, int height) Width = width; Height = height; - labelFill = new() { Width = Dim.Fill (), Height = Dim.Fill (), Visible = false }; + labelFill = new () { Width = Dim.Fill (), Height = Dim.Fill (), Visible = false }; labelFill.LayoutComplete += (s, e) => { @@ -1112,7 +1114,7 @@ public CustomButton (string fill, string text, int width, int height) labelFill.Text = fillText.ToString (); }; - labelText = new() { X = Pos.Center (), Y = Pos.Center (), Text = text }; + labelText = new () { X = Pos.Center (), Y = Pos.Center (), Text = text }; Add (labelFill, labelText); CanFocus = true; } From 48eba3bc55ee9f08e2f460202baaababb817418b Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 18 Jul 2024 15:32:51 -0600 Subject: [PATCH 27/96] Code cleanup --- Terminal.Gui/View/ViewText.cs | 89 +++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index a620e6a2bb..9ae02d2db5 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -1,23 +1,17 @@ #nullable enable -using System.Diagnostics; -using static Unix.Terminal.Curses; - namespace Terminal.Gui; public partial class View { + private string _text; + /// - /// Initializes the Text of the View. Called by the constructor. + /// Called when the has changed. Fires the event. /// - private void SetupText () - { - Text = string.Empty; - TextDirection = TextDirection.LeftRight_TopBottom; - } - - private string _text; + public void OnTextChanged () { TextChanged?.Invoke (this, EventArgs.Empty); } + // TODO: Make this non-virtual. Nobody overrides it. /// /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved /// or not when is enabled. @@ -49,11 +43,14 @@ public virtual bool PreserveTrailingSpaces /// to and . /// /// - /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height + /// The text will word-wrap to additional lines if it does not fit horizontally. If + /// 's height /// is 1, the text will be clipped. /// - /// If or are using , - /// the will be adjusted to fit the text. + /// + /// If or are using , + /// the will be adjusted to fit the text. + /// /// When the text changes, the is fired. /// public virtual string Text @@ -76,26 +73,16 @@ public virtual string Text } } - /// - /// Called when the has changed. Fires the event. - /// - public void OnTextChanged () - { - TextChanged?.Invoke (this, EventArgs.Empty); - } - - /// - /// Text changed event, raised when the text has changed. - /// - public event EventHandler? TextChanged; - // TODO: Make this non-virtual. Nobody overrides it. /// /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will /// redisplay the . /// /// - /// or are using , the will be adjusted to fit the text. + /// + /// or are using , the + /// will be adjusted to fit the text. + /// /// /// The text alignment. public virtual Alignment TextAlignment @@ -109,13 +96,21 @@ public virtual Alignment TextAlignment } } + /// + /// Text changed event, raised when the text has changed. + /// + public event EventHandler? TextChanged; + // TODO: Make this non-virtual. Nobody overrides it. /// /// Gets or sets the direction of the View's . Changing this property will redisplay the /// . /// /// - /// or are using , the will be adjusted to fit the text. + /// + /// or are using , the + /// will be adjusted to fit the text. + /// /// /// The text direction. public virtual TextDirection TextDirection @@ -127,7 +122,7 @@ public virtual TextDirection TextDirection /// /// Gets or sets the used to format . /// - public TextFormatter TextFormatter { get; init; } = new () { }; + public TextFormatter TextFormatter { get; init; } = new (); // TODO: Make this non-virtual. Nobody overrides it. /// @@ -136,7 +131,10 @@ public virtual TextDirection TextDirection /// the . /// /// - /// or are using , the will be adjusted to fit the text. + /// + /// or are using , the + /// will be adjusted to fit the text. + /// /// /// The vertical text alignment. public virtual Alignment VerticalTextAlignment @@ -151,9 +149,12 @@ public virtual Alignment VerticalTextAlignment // TODO: Add a OnUpdateTextFormatterText method that invokes UpdateTextFormatterText so that overrides don't have to call base. /// - /// Can be overridden if the has + /// Can be overridden if the has /// different format than the default. /// + /// + /// Overrides must call base.UpdateTextFormatterText before updating . + /// protected virtual void UpdateTextFormatterText () { if (TextFormatter is { }) @@ -165,11 +166,10 @@ protected virtual void UpdateTextFormatterText () } /// - /// Internal API. Sets .Size to the current size, adjusted for - /// . + /// Internal API. Sets .Width/Height. /// /// - /// Use this API to set when the view has changed such that the + /// Use this API to set /Height when the view has changed such that the /// size required to fit the text has changed. /// changes. /// @@ -183,11 +183,11 @@ internal void SetTextFormatterSize () // Default is to use GetContentSize (). Size? size = _contentSize; - // TODO: This is a hack. Figure out how to move this logic into DimAuto // Use _width & _height instead of Width & Height to avoid debug spew - DimAuto? widthAuto = _width as DimAuto; - DimAuto? heightAuto = _height as DimAuto; - if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text))) + var widthAuto = _width as DimAuto; + var heightAuto = _height as DimAuto; + + if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) { TextFormatter.Width = null; } @@ -199,7 +199,7 @@ internal void SetTextFormatterSize () } } - if ((heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) + if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) { TextFormatter.Height = null; } @@ -212,6 +212,15 @@ internal void SetTextFormatterSize () } } + /// + /// Initializes the Text of the View. Called by the constructor. + /// + private void SetupText () + { + Text = string.Empty; + TextDirection = TextDirection.LeftRight_TopBottom; + } + private void UpdateTextDirection (TextDirection newDirection) { bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection); From c4de9fe4f4d956bd679434bb816ee75528eb1e0f Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 18 Jul 2024 15:56:38 -0600 Subject: [PATCH 28/96] Code cleanup --- Terminal.Gui/Text/TextFormatter.cs | 698 +++++++++++++------------ Terminal.Gui/View/Layout/DimAuto.cs | 67 +-- Terminal.Gui/View/Layout/DimCombine.cs | 19 - 3 files changed, 385 insertions(+), 399 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 009b5516a8..14dda57a1a 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1,11 +1,10 @@ #nullable enable using System.Diagnostics; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Terminal.Gui; /// -/// Provides text formatting. Supports s, horizontal alignment, vertical alignment, +/// Provides text formatting. Supports s, horizontal and vertical alignment, text direction, /// multiple lines, and word-based line wrap. /// public class TextFormatter @@ -23,6 +22,10 @@ public class TextFormatter private Alignment _textVerticalAlignment = Alignment.Start; private bool _wordWrap = true; + private int? _width; + + private int? _height; + /// Get or sets the horizontal text alignment. /// The text alignment. public Alignment Alignment @@ -33,11 +36,15 @@ public Alignment Alignment /// Gets or sets whether the should be automatically changed to fit the . /// - /// Used when is using to resize the view's to fit . + /// + /// Used when is using to resize the view's + /// to fit . + /// /// /// AutoSize is ignored if is used. /// /// + [Obsolete ("AutoSize is deprecated, use Width and Height instead.")] public bool AutoSize { get => _autoSize; @@ -57,46 +64,6 @@ public bool AutoSize } } - internal Size GetAutoSize () - { - Size size = CalcRect (0, 0, Text, Direction, TabWidth).Size; - - return size with - { - Width = size.Width - GetHotKeySpecifierLength (), - Height = size.Height - GetHotKeySpecifierLength (false) - }; - } - /// - /// Gets the width or height of the characters - /// in the property. - /// - /// - /// Only the first HotKey specifier found in is supported. - /// - /// - /// If (the default) the width required for the HotKey specifier is returned. Otherwise, the - /// height is returned. - /// - /// - /// The number of characters required for the . If the text - /// direction specified - /// by does not match the parameter, 0 is returned. - /// - public int GetHotKeySpecifierLength (bool isWidth = true) - { - if (isWidth) - { - return TextFormatter.IsHorizontalDirection (Direction) && Text?.Contains ((char)HotKeySpecifier.Value) == true - ? Math.Max (HotKeySpecifier.GetColumns (), 0) - : 0; - } - - return TextFormatter.IsVerticalDirection (Direction) && Text?.Contains ((char)HotKeySpecifier.Value) == true - ? Math.Max (HotKeySpecifier.GetColumns (), 0) - : 0; - } - /// /// Gets the cursor position of the . If the is defined, the cursor will /// be positioned over it. @@ -119,202 +86,6 @@ public TextDirection Direction } } - /// - /// Determines if the viewport width will be used or only the text width will be used, - /// If all the viewport area will be filled with whitespaces and the same background color - /// showing a perfect rectangle. - /// - public bool FillRemaining { get; set; } - - /// Gets or sets the hot key. Fires the event. - public Key HotKey - { - get => _hotKey; - internal set - { - if (_hotKey != value) - { - Key oldKey = _hotKey; - _hotKey = value; - HotKeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, value)); - } - } - } - - /// The position in the text of the hot key. The hot key will be rendered using the hot color. - public int HotKeyPos - { - get => _hotKeyPos; - internal set => _hotKeyPos = value; - } - - /// - /// The specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable hot key support for this View - /// instance. The default is '\xffff'. - /// - public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF; - - /// Gets or sets a value indicating whether multi line is allowed. - /// Multi line is ignored if is . - public bool MultiLine - { - get => _multiLine; - set => _multiLine = EnableNeedsFormat (value); - } - - /// Gets or sets whether the needs to format the text. - /// - /// If when Draw is called, the Draw call will be faster. - /// Used by - /// Set to when any of the properties of are set. - /// Set to when the text is formatted (if is accessed). - /// - public bool NeedsFormat { get; set; } - - /// - /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved or not when - /// is enabled. If trailing spaces at the end of wrapped - /// lines will be removed when is formatted for display. The default is . - /// - public bool PreserveTrailingSpaces - { - get => _preserveTrailingSpaces; - set => _preserveTrailingSpaces = EnableNeedsFormat (value); - } - - private int? _width; - - public int? Width - { - get => _width; - set - { - if (_width == value) - { - return; - } - - if (value < 0) - { - throw new ArgumentOutOfRangeException (nameof (Width), value, @"Must be greater than or equal to 0."); - } - - _width = value; - if (_width is null || _height is null) - { - return; - } - _width = EnableNeedsFormat (value); - } - } - - private int? _height; - - public int? Height - { - get => _height; - set - { - if (_height == value) - { - return; - } - - if (value < 0) - { - throw new ArgumentOutOfRangeException (nameof (Width), value, @"Must be greater than or equal to 0."); - } - - _height = value; - if (_width is null || _height is null) - { - return; - } - _height = EnableNeedsFormat (value); - } - } - - /// Gets or sets the size will be constrained to when formatted. - /// - /// - /// Does not return the size of the formatted text but the size that will be used to constrain the text when - /// formatted. - /// - /// When set, is set to . - /// - public Size? Size - { - get - { - if (_width is null || _height is null) - { - return null; - } - - return new Size (_width.Value, _height.Value); - } - set - { - if (AutoSize) - { - Size size = EnableNeedsFormat (GetAutoSize ()); - _width = size.Width; - _height = size.Height; - } - else - { - if (value is null) - { - _width = null; - _height = null; - } - else - { - Size size = EnableNeedsFormat (value.Value); - _width = size.Width; - _height = size.Height; - } - } - } - } - - /// Gets or sets the number of columns used for a tab. - public int TabWidth - { - get => _tabWidth; - set => _tabWidth = EnableNeedsFormat (value); - } - - /// The text to be formatted. This string is never modified. - public virtual string Text - { - get => _text!; - set - { - _text = EnableNeedsFormat (value); - - if (AutoSize) - { - Size = GetAutoSize (); ; - } - } - } - - /// Gets or sets the vertical text-alignment. - /// The text vertical alignment. - public Alignment VerticalAlignment - { - get => _textVerticalAlignment; - set => _textVerticalAlignment = EnableNeedsFormat (value); - } - - /// Gets or sets whether word wrap will be used to fit to . - public bool WordWrap - { - get => _wordWrap; - set => _wordWrap = EnableNeedsFormat (value); - } - /// Draws the text held by to using the colors specified. /// /// Causes the text to be formatted (references ). Sets to @@ -565,8 +336,12 @@ public void Draw ( break; } - if ((!isVertical && (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset || (idx < runes.Length && runes [idx].GetColumns () > screen.Width))) - || (isVertical && ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y) || (idx < runes.Length && runes [idx].GetColumns () > screen.Width)))) + if ((!isVertical + && (current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset + || (idx < runes.Length && runes [idx].GetColumns () > screen.Width))) + || (isVertical + && ((current > start + size + zeroLengthCount && idx > maxScreen.Top + maxScreen.Height - screen.Y) + || (idx < runes.Length && runes [idx].GetColumns () > screen.Width)))) { break; } @@ -659,7 +434,7 @@ public void Draw ( { if (lastZeroWidthPos is null) { - lastZeroWidthPos = new List (); + lastZeroWidthPos = new (); } int foundIdx = lastZeroWidthPos.IndexOf ( @@ -704,6 +479,13 @@ public void Draw ( } } + /// + /// Determines if the viewport width will be used or only the text width will be used, + /// If all the viewport area will be filled with whitespaces and the same background color + /// showing a perfect rectangle. + /// + public bool FillRemaining { get; set; } + /// Returns the formatted text, constrained to . /// /// If is , causes a format, resetting @@ -725,6 +507,7 @@ public string Format () /// Gets the size required to hold the formatted text, given the constraints placed by . /// Causes a format, resetting to . + /// If provided, will cause the text to be constrained to the provided size instead of and . /// The size required to hold the formatted text. public Size FormatAndGetSize (Size? constrainSize = null) { @@ -736,24 +519,29 @@ public Size FormatAndGetSize (Size? constrainSize = null) // HACK: This is a total hack to work around the fact that TextFormatter.Format does not match TextFormatter.Draw. int? prevWidth = _width; int? prevHeight = _height; + if (constrainSize is { }) { _width = constrainSize?.Width; _height = constrainSize?.Height; } - + // HACK: Fill normally will fill the entire constraint size, but we need to know the actual size of the text. Alignment prevAlignment = Alignment; + if (Alignment == Alignment.Fill) { Alignment = Alignment.Start; } + Alignment prevVerticalAlignment = VerticalAlignment; + if (VerticalAlignment == Alignment.Fill) { VerticalAlignment = Alignment.Start; } + // This calls Format List lines = GetLines (); // Undo hacks @@ -788,6 +576,36 @@ public Size FormatAndGetSize (Size? constrainSize = null) return new (width, height); } + /// + /// Gets the width or height of the characters + /// in the property. + /// + /// + /// Only the first HotKey specifier found in is supported. + /// + /// + /// If (the default) the width required for the HotKey specifier is returned. Otherwise, the + /// height is returned. + /// + /// + /// The number of characters required for the . If the text + /// direction specified + /// by does not match the parameter, 0 is returned. + /// + public int GetHotKeySpecifierLength (bool isWidth = true) + { + if (isWidth) + { + return IsHorizontalDirection (Direction) && Text?.Contains ((char)HotKeySpecifier.Value) == true + ? Math.Max (HotKeySpecifier.GetColumns (), 0) + : 0; + } + + return IsVerticalDirection (Direction) && Text?.Contains ((char)HotKeySpecifier.Value) == true + ? Math.Max (HotKeySpecifier.GetColumns (), 0) + : 0; + } + /// Gets a list of formatted lines, constrained to . /// /// @@ -805,7 +623,7 @@ public List GetLines () // With this check, we protect against subclasses with overrides of Text if (string.IsNullOrEmpty (Text) || Width is null || Width == 0 || Height is null || Height == 0) { - _lines = new List { string.Empty }; + _lines = new() { string.Empty }; NeedsFormat = false; return _lines; @@ -864,7 +682,7 @@ public List GetLines () if (!AutoSize && _lines.Count > Height.Value) { - _lines.RemoveRange (Height.Value, _lines.Count - Height.Value); + _lines.RemoveRange (Height.Value, _lines.Count - Height.Value); } } @@ -874,9 +692,237 @@ public List GetLines () return _lines; } + /// Gets or sets the height will be constrained to when formatted. + /// + /// + /// Does not return the height of the formatted text but the height that will be used to constrain the text when + /// formatted. + /// + /// + /// Use to get the size of the formatted text. + /// + /// When set, is set to . + /// + + public int? Height + { + get => _height; + set + { + if (_height == value) + { + return; + } + + if (value < 0) + { + throw new ArgumentOutOfRangeException (nameof (Width), value, @"Must be greater than or equal to 0."); + } + + _height = value; + + if (_width is null || _height is null) + { + return; + } + + _height = EnableNeedsFormat (value); + } + } + + /// Gets or sets the hot key. Fires the event. + public Key HotKey + { + get => _hotKey; + internal set + { + if (_hotKey != value) + { + Key oldKey = _hotKey; + _hotKey = value; + HotKeyChanged?.Invoke (this, new (oldKey, value)); + } + } + } + /// Event invoked when the is changed. public event EventHandler? HotKeyChanged; + /// The position in the text of the hot key. The hot key will be rendered using the hot color. + public int HotKeyPos + { + get => _hotKeyPos; + internal set => _hotKeyPos = value; + } + + /// + /// The specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable hot key support for this View + /// instance. The default is '\xffff'. + /// + public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF; + + /// Gets or sets a value indicating whether multi line is allowed. + /// Multi line is ignored if is . + public bool MultiLine + { + get => _multiLine; + set => _multiLine = EnableNeedsFormat (value); + } + + /// Gets or sets whether the needs to format the text. + /// + /// If when Draw is called, the Draw call will be faster. + /// Used by + /// Set to when any of the properties of are set. + /// Set to when the text is formatted (if is accessed). + /// + public bool NeedsFormat { get; set; } + + /// + /// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved or not when + /// is enabled. If trailing spaces at the end of wrapped + /// lines will be removed when is formatted for display. The default is . + /// + public bool PreserveTrailingSpaces + { + get => _preserveTrailingSpaces; + set => _preserveTrailingSpaces = EnableNeedsFormat (value); + } + + /// Gets or sets the width and height will be constrained to when formatted. + /// + /// + /// Does not return the size of the formatted text but the size that will be used to constrain the text when + /// formatted. + /// + /// + /// Use to get the size of the formatted text. + /// + /// When set, is set to . + /// + public Size? Size + { + get + { + if (_width is null || _height is null) + { + return null; + } + + return new Size (_width.Value, _height.Value); + } + set + { + if (AutoSize) + { + Size size = EnableNeedsFormat (GetAutoSize ()); + _width = size.Width; + _height = size.Height; + } + else + { + if (value is null) + { + _width = null; + _height = null; + EnableNeedsFormat (true); + } + else + { + Size size = EnableNeedsFormat (value.Value); + _width = size.Width; + _height = size.Height; + } + } + } + } + + /// Gets or sets the number of columns used for a tab. + public int TabWidth + { + get => _tabWidth; + set => _tabWidth = EnableNeedsFormat (value); + } + + /// The text to be formatted. This string is never modified. + public string Text + { + get => _text!; + set + { + _text = EnableNeedsFormat (value); + + if (AutoSize) + { + Size = GetAutoSize (); + ; + } + } + } + + /// Gets or sets the vertical text-alignment. + /// The text vertical alignment. + public Alignment VerticalAlignment + { + get => _textVerticalAlignment; + set => _textVerticalAlignment = EnableNeedsFormat (value); + } + + /// Gets or sets the width will be constrained to when formatted. + /// + /// + /// Does not return the width of the formatted text but the width that will be used to constrain the text when + /// formatted. + /// + /// + /// Use to get the size of the formatted text. + /// + /// When set, is set to . + /// + public int? Width + { + get => _width; + set + { + if (_width == value) + { + return; + } + + if (value < 0) + { + throw new ArgumentOutOfRangeException (nameof (Width), value, @"Must be greater than or equal to 0."); + } + + _width = value; + + if (_width is null || _height is null) + { + return; + } + + _width = EnableNeedsFormat (value); + } + } + + /// Gets or sets whether word wrap will be used to fit to . + public bool WordWrap + { + get => _wordWrap; + set => _wordWrap = EnableNeedsFormat (value); + } + + private Size GetAutoSize () + { + Size size = CalcRect (0, 0, Text, Direction, TabWidth).Size; + + return size with + { + Width = size.Width - GetHotKeySpecifierLength (), + Height = size.Height - GetHotKeySpecifierLength (false) + }; + } + /// Sets to and returns the value. /// /// @@ -894,48 +940,48 @@ private T EnableNeedsFormat (T value) public static bool IsHorizontalDirection (TextDirection textDirection) { return textDirection switch - { - TextDirection.LeftRight_TopBottom => true, - TextDirection.LeftRight_BottomTop => true, - TextDirection.RightLeft_TopBottom => true, - TextDirection.RightLeft_BottomTop => true, - _ => false - }; + { + TextDirection.LeftRight_TopBottom => true, + TextDirection.LeftRight_BottomTop => true, + TextDirection.RightLeft_TopBottom => true, + TextDirection.RightLeft_BottomTop => true, + _ => false + }; } /// Check if it is a vertical direction public static bool IsVerticalDirection (TextDirection textDirection) { return textDirection switch - { - TextDirection.TopBottom_LeftRight => true, - TextDirection.TopBottom_RightLeft => true, - TextDirection.BottomTop_LeftRight => true, - TextDirection.BottomTop_RightLeft => true, - _ => false - }; + { + TextDirection.TopBottom_LeftRight => true, + TextDirection.TopBottom_RightLeft => true, + TextDirection.BottomTop_LeftRight => true, + TextDirection.BottomTop_RightLeft => true, + _ => false + }; } /// Check if it is Left to Right direction public static bool IsLeftToRight (TextDirection textDirection) { return textDirection switch - { - TextDirection.LeftRight_TopBottom => true, - TextDirection.LeftRight_BottomTop => true, - _ => false - }; + { + TextDirection.LeftRight_TopBottom => true, + TextDirection.LeftRight_BottomTop => true, + _ => false + }; } /// Check if it is Top to Bottom direction public static bool IsTopToBottom (TextDirection textDirection) { return textDirection switch - { - TextDirection.TopBottom_LeftRight => true, - TextDirection.TopBottom_RightLeft => true, - _ => false - }; + { + TextDirection.TopBottom_LeftRight => true, + TextDirection.TopBottom_RightLeft => true, + _ => false + }; } // TODO: Move to StringExtensions? @@ -1023,7 +1069,7 @@ private static string ReplaceTABWithSpaces (string str, int tabWidth) return str.Replace ("\t", ""); } - return str.Replace ("\t", new string (' ', tabWidth)); + return str.Replace ("\t", new (' ', tabWidth)); } // TODO: Move to StringExtensions? @@ -1114,7 +1160,7 @@ public static string ClipOrPad (string text, int width) } // value is too wide - return new string (text.TakeWhile (c => (width -= ((Rune)c).GetColumns ()) >= 0).ToArray ()); + return new (text.TakeWhile (c => (width -= ((Rune)c).GetColumns ()) >= 0).ToArray ()); } /// Formats the provided text to fit within the width provided using word wrapping. @@ -1327,21 +1373,21 @@ int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = case ' ': return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); case '\t': - { - length += tabWidth + 1; - - if (length == tabWidth && tabWidth > cWidth) - { - return to + 1; - } + { + length += tabWidth + 1; - if (length > cWidth && tabWidth > cWidth) - { - return to; - } + if (length == tabWidth && tabWidth > cWidth) + { + return to + 1; + } - return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); + if (length > cWidth && tabWidth > cWidth) + { + return to; } + + return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); + } default: to++; @@ -1350,11 +1396,11 @@ int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = } return cLength switch - { - > 0 when to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t' => from, - > 0 when to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t') => from, - _ => to - }; + { + > 0 when to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t' => from, + > 0 when to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t') => from, + _ => to + }; } if (start < text.GetRuneCount ()) @@ -1728,8 +1774,14 @@ public static List Format ( foreach (string line in lines) { - - lineResult.Add (ClipAndJustify (PerformCorrectFormatDirection (textDirection, line), width, justify, textDirection, tabWidth, textFormatter)); + lineResult.Add ( + ClipAndJustify ( + PerformCorrectFormatDirection (textDirection, line), + width, + justify, + textDirection, + tabWidth, + textFormatter)); } return PerformCorrectFormatDirection (textDirection, lineResult); @@ -1793,13 +1845,13 @@ public static List Format ( private static string PerformCorrectFormatDirection (TextDirection textDirection, string line) { return textDirection switch - { - TextDirection.RightLeft_BottomTop - or TextDirection.RightLeft_TopBottom - or TextDirection.BottomTop_LeftRight - or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), - _ => line - }; + { + TextDirection.RightLeft_BottomTop + or TextDirection.RightLeft_TopBottom + or TextDirection.BottomTop_LeftRight + or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), + _ => line + }; } private static List PerformCorrectFormatDirection (TextDirection textDirection, List runes) @@ -1810,25 +1862,13 @@ private static List PerformCorrectFormatDirection (TextDirection textDirec private static List PerformCorrectFormatDirection (TextDirection textDirection, List lines) { return textDirection switch - { - TextDirection.TopBottom_RightLeft - or TextDirection.LeftRight_BottomTop - or TextDirection.RightLeft_BottomTop - or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), - _ => lines - }; - } - - /// Returns the number of lines needed to render the specified text given the width. - /// Calls . - /// Number of lines. - /// Text, may contain newlines. - /// The minimum width for the text. - public static int GetLineCount (string text, int width) - { - List result = Format (text, width, false, true); - - return result.Count; + { + TextDirection.TopBottom_RightLeft + or TextDirection.LeftRight_BottomTop + or TextDirection.RightLeft_BottomTop + or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), + _ => lines + }; } /// @@ -1840,7 +1880,10 @@ public static int GetLineCount (string text, int width) /// /// The lines. /// The line in the list to start with (any lines before will be ignored). - /// The number of lines to process (if less than lines.Count, any lines after will be ignored). + /// + /// The number of lines to process (if less than lines.Count, any lines after will be + /// ignored). + /// /// The number of columns used for a tab. /// The width required. public static int GetColumnsRequiredForVerticalText ( @@ -1857,11 +1900,13 @@ public static int GetColumnsRequiredForVerticalText ( i++) { string runes = lines [i]; + if (runes.Length > 0) { max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth)); } } + return max; } @@ -1961,8 +2006,15 @@ public static int GetLengthThatFits (List runes, int width, int tabWidth = return runeIdx; } - private static int GetRuneWidth (string str, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom) { return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth, textDirection); } - private static int GetRuneWidth (List runes, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom) { return runes.Sum (r => GetRuneWidth (r, tabWidth, textDirection)); } + private static int GetRuneWidth (string str, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom) + { + return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth, textDirection); + } + + private static int GetRuneWidth (List runes, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom) + { + return runes.Sum (r => GetRuneWidth (r, tabWidth, textDirection)); + } private static int GetRuneWidth (Rune rune, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom) { @@ -2025,7 +2077,9 @@ public static int GetMaxColsForWidth (List lines, int width, int tabWidt /// The text direction. /// The number of columns used for a tab. /// - public static Rectangle CalcRect ( + [Obsolete ("CalcRect is deprecated, FormatAndGetSize instead.")] + + internal static Rectangle CalcRect ( int x, int y, string text, @@ -2273,7 +2327,7 @@ public static string ReplaceHotKeyWithTag (string text, int hotPos) if (Rune.IsLetterOrDigit (runes [hotPos])) { - runes [hotPos] = new Rune ((uint)runes [hotPos].Value); + runes [hotPos] = new ((uint)runes [hotPos].Value); } return StringExtensions.ToString (runes); @@ -2312,4 +2366,4 @@ public static string RemoveHotKeySpecifier (string text, int hotPos, Rune hotKey } #endregion // Static Members -} \ No newline at end of file +} diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 6fc715807a..3e1c3e7d83 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -70,22 +70,23 @@ internal override int Calculate (int location, int superviewContentSize, View us if (Style.FastHasFlags (DimAutoStyle.Text)) { - if (dimension == Dimension.Width) { if (us.TextFormatter.Width is null) { + // Set BOTH width and height (by setting Size). We do this because we will be called again, next + // for Dimension.Height. We need to know the width to calculate the height. us.TextFormatter.Size = us.TextFormatter.FormatAndGetSize (new (int.Min (autoMax, screenX4), screenX4)); } -// else - { - textSize = us.TextFormatter.Width.Value; - } + + textSize = us.TextFormatter.Width!.Value; } else { if (us.TextFormatter.Height is null) { + // Set just the height. It is assumed that the width has already been set. + // TODO: There may be cases where the width is not set. We may need to set it here. textSize = us.TextFormatter.FormatAndGetSize (new (us.TextFormatter.Width ?? screenX4, int.Min (autoMax, screenX4))).Height; us.TextFormatter.Height = textSize; } @@ -93,18 +94,6 @@ internal override int Calculate (int location, int superviewContentSize, View us { textSize = us.TextFormatter.Height.Value; } - - //if (us.Width.Has (typeof(DimAuto), out var widthDim)) - //{ - // DimAuto widthDimAuto = (DimAuto)widthDim; - // textSize = us.TextFormatter.FormatAndGetSize (us.GetContentSize ()).Height; - //} - //else - //{ - // textSize = us.TextFormatter.FormatAndGetSize ().Height; - //} - - //us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize }; } } @@ -254,41 +243,10 @@ internal override int Calculate (int location, int superviewContentSize, View us #endregion Centered #region Percent - //// [ ] DimPercent - Dimension is dependent on `us.ContentSize` - //// - DimPercent will always be 0 if there is no other content that makes the superview have a size. - //List dimPercentSubViews; - //if (dimension == Dimension.Width) - //{ - // dimPercentSubViews = us.Subviews.Where (v => v.Width.Has (typeof (DimPercent), out _)).ToList (); - //} - //else - //{ - // dimPercentSubViews = us.Subviews.Where (v => v.Height.Has (typeof (DimPercent), out _)).ToList (); - //} - - //viewsNeedingLayout.AddRange (dimPercentSubViews); - - //int maxDimPercent = 0; - - //for (var i = 0; i < dimPercentSubViews.Count; i++) - //{ - // View v = dimPercentSubViews [i]; - - // if (dimension == Dimension.Width) - // { - // int width = v.Width!.Calculate (0, superviewContentSize, null, dimension); - // maxDimPercent = (v.X.GetAnchor (0) + width); - // } - // else - // { - // int height = v.Height!.Calculate (0, superviewContentSize, null, dimension); - // maxDimPercent = (v.Y.GetAnchor (0) + height); - // } - //} - //maxCalculatedSize = int.Max (maxCalculatedSize, maxDimPercent); + // [ ] DimPercent - Dimension is dependent on `us.ContentSize` + // No need to do anything. #endregion Percent - #region Aligned // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` int maxAlign = 0; @@ -340,8 +298,6 @@ internal override int Calculate (int location, int superviewContentSize, View us maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign); #endregion Aligned - - #region Anchored // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` List anchoredSubViews; @@ -463,6 +419,7 @@ internal override int Calculate (int location, int superviewContentSize, View us int oppositeScreen = dimension == Dimension.Width ? Application.Screen.Height * 4 : Application.Screen.Width * 4; + // TODO: Double-check that we really do need to SetRelativeLayout on these views! foreach (var v in viewsNeedingLayout) { if (dimension == Dimension.Width) @@ -490,12 +447,6 @@ internal override int Calculate (int location, int superviewContentSize, View us return max; } - internal override bool ReferencesOtherViews () - { - // BUGBUG: This is not correct. _contentSize may be null. - return false; //_style.HasFlag (DimAutoStyle.Content); - } - /// public override bool Equals (object? other) { diff --git a/Terminal.Gui/View/Layout/DimCombine.cs b/Terminal.Gui/View/Layout/DimCombine.cs index 2e204eadb2..f8b5d141c4 100644 --- a/Terminal.Gui/View/Layout/DimCombine.cs +++ b/Terminal.Gui/View/Layout/DimCombine.cs @@ -61,23 +61,4 @@ internal override int Calculate (int location, int superviewContentSize, View us return newDimension; } - - /// - /// Diagnostics API to determine if this Dim object references other views. - /// - /// - internal override bool ReferencesOtherViews () - { - if (Left!.ReferencesOtherViews ()) - { - return true; - } - - if (Right!.ReferencesOtherViews ()) - { - return true; - } - - return false; - } } From 6c07532bce386521a756ab21ca4a3e303bd3558d Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 18 Jul 2024 16:01:01 -0600 Subject: [PATCH 29/96] Code cleanup --- Terminal.Gui/View/Layout/DimAuto.cs | 170 ++++---- Terminal.Gui/View/Layout/ViewLayout.cs | 534 +++++++++++++------------ 2 files changed, 375 insertions(+), 329 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 3e1c3e7d83..926ffc0ce9 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -1,6 +1,5 @@ #nullable enable using System.Diagnostics; -using System.Drawing; namespace Terminal.Gui; @@ -16,13 +15,32 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -public class DimAuto () : Dim +public class DimAuto : Dim { private readonly Dim? _maximumContentDim; + private readonly Dim? _minimumContentDim; + + private readonly DimAutoStyle _style; + + /// + public override bool Equals (object? other) + { + if (other is not DimAuto auto) + { + return false; + } + + return auto.MinimumContentDim == MinimumContentDim && auto.MaximumContentDim == MaximumContentDim && auto.Style == Style; + } + + /// + public override int GetHashCode () { return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style); } + /// /// Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. /// + // ReSharper disable once ConvertToAutoProperty public required Dim? MaximumContentDim { @@ -30,11 +48,10 @@ public required Dim? MaximumContentDim init => _maximumContentDim = value; } - private readonly Dim? _minimumContentDim; - /// /// Gets the minimum dimension the View's ContentSize will be constrained to. /// + // ReSharper disable once ConvertToAutoProperty public required Dim? MinimumContentDim { @@ -42,11 +59,10 @@ public required Dim? MinimumContentDim init => _minimumContentDim = value; } - private readonly DimAutoStyle _style; - /// /// Gets the style of the DimAuto. /// + // ReSharper disable once ConvertToAutoProperty public required DimAutoStyle Style { @@ -97,8 +113,7 @@ internal override int Calculate (int location, int superviewContentSize, View us } } - List viewsNeedingLayout = new List (); - + List viewsNeedingLayout = new (); if (Style.FastHasFlags (DimAutoStyle.Content)) { @@ -110,7 +125,8 @@ internal override int Calculate (int location, int superviewContentSize, View us else { maxCalculatedSize = textSize; - // ContentSize was NOT explicitly set. Use `us.Subviews` to determine size. + + // TOOD: All the below is a naive implementation. It may be possible to optimize this. List includedSubviews = us.Subviews.ToList (); @@ -156,9 +172,11 @@ internal override int Calculate (int location, int superviewContentSize, View us // [ ] DimAbsolute - Dimension is fixed // [ ] DimFunc - Dimension is internally calculated List notDependentSubViews; + if (dimension == Dimension.Width) { - notDependentSubViews = includedSubviews.Where (v => v.Width is { } + notDependentSubViews = includedSubviews.Where ( + v => v.Width is { } && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) && !v.X.Has (typeof (PosAnchorEnd), out _) && !v.X.Has (typeof (PosAlign), out _) @@ -167,11 +185,13 @@ internal override int Calculate (int location, int superviewContentSize, View us && !v.Width.Has (typeof (DimView), out _) && !v.Width.Has (typeof (DimFill), out _) && !v.Width.Has (typeof (DimPercent), out _) - ).ToList (); + ) + .ToList (); } else { - notDependentSubViews = includedSubviews.Where (v => v.Height is { } + notDependentSubViews = includedSubviews.Where ( + v => v.Height is { } && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) && !v.Y.Has (typeof (PosAnchorEnd), out _) && !v.Y.Has (typeof (PosAlign), out _) @@ -180,14 +200,15 @@ internal override int Calculate (int location, int superviewContentSize, View us && !v.Height.Has (typeof (DimView), out _) && !v.Height.Has (typeof (DimFill), out _) && !v.Height.Has (typeof (DimPercent), out _) - ).ToList (); + ) + .ToList (); } for (var i = 0; i < notDependentSubViews.Count; i++) { View v = notDependentSubViews [i]; - int size = 0; + var size = 0; if (dimension == Dimension.Width) { @@ -209,8 +230,10 @@ internal override int Calculate (int location, int superviewContentSize, View us // ************** We now have some idea of `us.ContentSize` *************** #region Centered + // [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame` List centeredSubViews; + if (dimension == Dimension.Width) { centeredSubViews = us.Subviews.Where (v => v.X.Has (typeof (PosCenter), out _)).ToList (); @@ -222,7 +245,7 @@ internal override int Calculate (int location, int superviewContentSize, View us viewsNeedingLayout.AddRange (centeredSubViews); - int maxCentered = 0; + var maxCentered = 0; for (var i = 0; i < centeredSubViews.Count; i++) { @@ -231,25 +254,31 @@ internal override int Calculate (int location, int superviewContentSize, View us if (dimension == Dimension.Width) { int width = v.Width!.Calculate (0, screenX4, v, dimension); - maxCentered = (v.X.GetAnchor (0) + width); + maxCentered = v.X.GetAnchor (0) + width; } else { int height = v.Height!.Calculate (0, screenX4, v, dimension); - maxCentered = (v.Y.GetAnchor (0) + height); + maxCentered = v.Y.GetAnchor (0) + height; } } + maxCalculatedSize = int.Max (maxCalculatedSize, maxCentered); + #endregion Centered #region Percent + // [ ] DimPercent - Dimension is dependent on `us.ContentSize` // No need to do anything. + #endregion Percent #region Aligned + // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize` - int maxAlign = 0; + var maxAlign = 0; + // Use Linq to get a list of distinct GroupIds from the subviews List groupIds = includedSubviews.Select ( v => @@ -268,24 +297,29 @@ internal override int Calculate (int location, int superviewContentSize, View us return ((PosAlign)posAlign).GroupId; } } + return -1; - }).Distinct ().ToList (); + }) + .Distinct () + .ToList (); - foreach (var groupId in groupIds.Where (g => g != -1)) + 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 - }; - }) - .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) - .ToList (); + 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 + }; + }) + .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) + .ToList (); if (posAlignsInGroup.Count == 0) { @@ -296,11 +330,14 @@ internal override int Calculate (int location, int superviewContentSize, View us } maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign); + #endregion Aligned #region Anchored + // [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` List anchoredSubViews; + if (dimension == Dimension.Width) { anchoredSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosAnchorEnd), out _)).ToList (); @@ -312,7 +349,8 @@ internal override int Calculate (int location, int superviewContentSize, View us viewsNeedingLayout.AddRange (anchoredSubViews); - int maxAnchorEnd = 0; + var maxAnchorEnd = 0; + for (var i = 0; i < anchoredSubViews.Count; i++) { View v = anchoredSubViews [i]; @@ -320,21 +358,27 @@ internal override int Calculate (int location, int superviewContentSize, View us // Need to set the relative layout for PosAnchorEnd subviews to calculate the size if (dimension == Dimension.Width) { - v.SetRelativeLayout (new Size (maxCalculatedSize, screenX4)); + v.SetRelativeLayout (new (maxCalculatedSize, screenX4)); } else { - v.SetRelativeLayout (new Size (screenX4, maxCalculatedSize)); + v.SetRelativeLayout (new (screenX4, maxCalculatedSize)); } - maxAnchorEnd = dimension == Dimension.Width ? v.X.GetAnchor (maxCalculatedSize + v.Frame.Width) : v.Y.GetAnchor (maxCalculatedSize + v.Frame.Height); + + maxAnchorEnd = dimension == Dimension.Width + ? v.X.GetAnchor (maxCalculatedSize + v.Frame.Width) + : v.Y.GetAnchor (maxCalculatedSize + v.Frame.Height); } maxCalculatedSize = Math.Max (maxCalculatedSize, maxAnchorEnd); + #endregion Anchored #region PosView + // [x] PosView - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize` List posViewSubViews; + if (dimension == Dimension.Width) { posViewSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosView), out _)).ToList (); @@ -351,12 +395,13 @@ internal override int Calculate (int location, int superviewContentSize, View us // BUGBUG: The order may not be correct. May need to call TopologicalSort? if (dimension == Dimension.Width) { - v.SetRelativeLayout (new Size (maxCalculatedSize, 0)); + v.SetRelativeLayout (new (maxCalculatedSize, 0)); } else { - v.SetRelativeLayout (new Size (0, maxCalculatedSize)); + v.SetRelativeLayout (new (0, maxCalculatedSize)); } + int maxPosView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; if (maxPosView > maxCalculatedSize) @@ -364,13 +409,16 @@ internal override int Calculate (int location, int superviewContentSize, View us maxCalculatedSize = maxPosView; } } + #endregion PosView // [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize` #region DimView + // [x] DimView - Dimension is dependent on `subview.Target` - it can cause a change in `us.ContentSize` List dimViewSubViews; + if (dimension == Dimension.Width) { dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (typeof (DimView), out _)).ToList (); @@ -387,11 +435,11 @@ internal override int Calculate (int location, int superviewContentSize, View us // BUGBUG: The order may not be correct. May need to call TopologicalSort? if (dimension == Dimension.Width) { - v.SetRelativeLayout (new Size (maxCalculatedSize, 0)); + v.SetRelativeLayout (new (maxCalculatedSize, 0)); } else { - v.SetRelativeLayout (new Size (0, maxCalculatedSize)); + v.SetRelativeLayout (new (0, maxCalculatedSize)); } int maxDimView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; @@ -401,6 +449,7 @@ internal override int Calculate (int location, int superviewContentSize, View us maxCalculatedSize = maxDimView; } } + #endregion DimView } } @@ -420,50 +469,31 @@ internal override int Calculate (int location, int superviewContentSize, View us int oppositeScreen = dimension == Dimension.Width ? Application.Screen.Height * 4 : Application.Screen.Width * 4; // TODO: Double-check that we really do need to SetRelativeLayout on these views! - foreach (var v in viewsNeedingLayout) + foreach (View v in viewsNeedingLayout) { if (dimension == Dimension.Width) { - v.SetRelativeLayout (new Size (max, oppositeScreen)); + v.SetRelativeLayout (new (max, oppositeScreen)); } else { - v.SetRelativeLayout (new Size (oppositeScreen, max)); + v.SetRelativeLayout (new (oppositeScreen, max)); } } // Factor in adornments Thickness thickness = us.GetAdornmentsThickness (); - var adornmentThickness = dimension switch - { - Dimension.Width => thickness.Horizontal, - Dimension.Height => thickness.Vertical, - Dimension.None => 0, - _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null) - }; + + int adornmentThickness = dimension switch + { + Dimension.Width => thickness.Horizontal, + Dimension.Height => thickness.Vertical, + Dimension.None => 0, + _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null) + }; max += adornmentThickness; return max; } - - /// - public override bool Equals (object? other) - { - if (other is not DimAuto auto) - { - return false; - } - - return auto.MinimumContentDim == MinimumContentDim && - auto.MaximumContentDim == MaximumContentDim && - auto.Style == Style; - } - - /// - public override int GetHashCode () - { - return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style); - } - -} \ No newline at end of file +} diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 1632b47aba..26da0b4b33 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -5,6 +5,240 @@ namespace Terminal.Gui; public partial class View { + /// + /// Indicates whether the specified SuperView-relative coordinates are within the View's . + /// + /// SuperView-relative coordinate + /// if the specified SuperView-relative coordinates are within the View. + public virtual bool Contains (in Point location) { return Frame.Contains (location); } + + /// Finds the first Subview of that is visible at the provided location. + /// + /// + /// Used to determine what view the mouse is over. + /// + /// + /// The view to scope the search by. + /// .SuperView-relative coordinate. + /// + /// The view that was found at the coordinate. + /// if no view was found. + /// + + // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews. + internal static View? FindDeepestView (View? start, in Point location) + { + Point currentLocation = location; + + while (start is { Visible: true } && start.Contains (currentLocation)) + { + Adornment? found = null; + + if (start.Margin.Contains (currentLocation)) + { + found = start.Margin; + } + else if (start.Border.Contains (currentLocation)) + { + found = start.Border; + } + else if (start.Padding.Contains (currentLocation)) + { + found = start.Padding; + } + + Point viewportOffset = start.GetViewportOffsetFromFrame (); + + if (found is { }) + { + start = found; + viewportOffset = found.Parent.Frame.Location; + } + + int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X); + int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y); + + View? subview = null; + + for (int i = start.InternalSubviews.Count - 1; i >= 0; i--) + { + if (start.InternalSubviews [i].Visible + && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y))) + { + subview = start.InternalSubviews [i]; + currentLocation.X = startOffsetX + start.Viewport.X; + currentLocation.Y = startOffsetY + start.Viewport.Y; + + // start is the deepest subview under the mouse; stop searching the subviews + break; + } + } + + if (subview is null) + { + // No subview was found that's under the mouse, so we're done + return start; + } + + // We found a subview of start that's under the mouse, continue... + start = subview; + } + + return null; + } + + /// + /// Gets a new location of the that is within the Viewport of the 's + /// (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates. + /// + /// + /// If does not have a or it's SuperView is not + /// the position will be bound by the and + /// . + /// + /// The View that is to be moved. + /// The target x location. + /// The target y location. + /// The new x location that will ensure will be fully visible. + /// The new y location that will ensure will be fully visible. + /// The new top most statusBar + /// + /// Either (if does not have a Super View) or + /// 's SuperView. This can be used to ensure LayoutSubviews is called on the correct View. + /// + internal static View GetLocationEnsuringFullVisibility ( + View viewToMove, + int targetX, + int targetY, + out int nx, + out int ny, + out StatusBar statusBar + ) + { + int maxDimension; + View superView; + statusBar = null!; + + if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + { + maxDimension = Driver.Cols; + superView = Application.Top; + } + else + { + // Use the SuperView's Viewport, not Frame + maxDimension = viewToMove!.SuperView.Viewport.Width; + superView = viewToMove.SuperView; + } + + if (superView?.Margin is { } && superView == viewToMove!.SuperView) + { + maxDimension -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; + } + + if (viewToMove!.Frame.Width <= maxDimension) + { + nx = Math.Max (targetX, 0); + nx = nx + viewToMove.Frame.Width > maxDimension ? Math.Max (maxDimension - viewToMove.Frame.Width, 0) : nx; + + if (nx > viewToMove.Frame.X + viewToMove.Frame.Width) + { + nx = Math.Max (viewToMove.Frame.Right, 0); + } + } + else + { + nx = targetX; + } + + //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); + var menuVisible = false; + var statusVisible = false; + + if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + { + menuVisible = Application.Top?.MenuBar?.Visible == true; + } + else + { + View t = viewToMove!.SuperView; + + while (t is { } and not Toplevel) + { + t = t.SuperView; + } + + if (t is Toplevel topLevel) + { + menuVisible = topLevel.MenuBar?.Visible == true; + } + } + + if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + { + maxDimension = menuVisible ? 1 : 0; + } + else + { + maxDimension = 0; + } + + ny = Math.Max (targetY, maxDimension); + + if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + { + statusVisible = Application.Top?.StatusBar?.Visible == true; + statusBar = Application.Top?.StatusBar!; + } + else + { + View t = viewToMove!.SuperView; + + while (t is { } and not Toplevel) + { + t = t.SuperView; + } + + if (t is Toplevel topLevel) + { + statusVisible = topLevel.StatusBar?.Visible == true; + statusBar = topLevel.StatusBar!; + } + } + + if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + { + maxDimension = statusVisible ? Driver.Rows - 1 : Driver.Rows; + } + else + { + maxDimension = statusVisible ? viewToMove!.SuperView.Viewport.Height - 1 : viewToMove!.SuperView.Viewport.Height; + } + + if (superView?.Margin is { } && superView == viewToMove?.SuperView) + { + maxDimension -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom; + } + + ny = Math.Min (ny, maxDimension); + + if (viewToMove?.Frame.Height <= maxDimension) + { + ny = ny + viewToMove.Frame.Height > maxDimension + ? Math.Max (maxDimension - viewToMove.Frame.Height, menuVisible ? 1 : 0) + : ny; + + if (ny > viewToMove.Frame.Y + viewToMove.Frame.Height) + { + ny = Math.Max (viewToMove.Frame.Bottom, 0); + } + } + + //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); + + return superView!; + } + #region Frame private Rectangle _frame; @@ -15,7 +249,10 @@ public partial class View /// 's Content, which is bound by . /// /// - /// Frame is relative to the 's Content, which is bound by . + /// + /// Frame is relative to the 's Content, which is bound by + /// . + /// /// /// Setting Frame will set , , , and to the /// values of the corresponding properties of the parameter. @@ -131,7 +368,8 @@ public virtual Point ScreenToFrame (in Point location) /// The object representing the X position. /// /// - /// The position is relative to the 's Content, which is bound by . + /// The position is relative to the 's Content, which is bound by + /// . /// /// /// If set to a relative value (e.g. ) the value is indeterminate until the view has been @@ -169,7 +407,8 @@ public Pos X /// The object representing the Y position. /// /// - /// The position is relative to the 's Content, which is bound by . + /// The position is relative to the 's Content, which is bound by + /// . /// /// /// If set to a relative value (e.g. ) the value is indeterminate until the view has been @@ -206,7 +445,8 @@ public Pos Y /// The object representing the height of the view (the number of rows). /// /// - /// The dimension is relative to the 's Content, which is bound by + /// The dimension is relative to the 's Content, which is bound by + /// /// . /// /// @@ -254,7 +494,8 @@ public Dim? Height /// The object representing the width of the view (the number of columns). /// /// - /// The dimension is relative to the 's Content, which is bound by + /// The dimension is relative to the 's Content, which is bound by + /// /// . /// /// @@ -300,241 +541,6 @@ public Dim? Width #region Layout Engine - #endregion Layout Engine - - /// - /// Indicates whether the specified SuperView-relative coordinates are within the View's . - /// - /// SuperView-relative coordinate - /// if the specified SuperView-relative coordinates are within the View. - public virtual bool Contains (in Point location) { return Frame.Contains (location); } - - /// Finds the first Subview of that is visible at the provided location. - /// - /// - /// Used to determine what view the mouse is over. - /// - /// - /// The view to scope the search by. - /// .SuperView-relative coordinate. - /// - /// The view that was found at the coordinate. - /// if no view was found. - /// - - // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews. - internal static View? FindDeepestView (View? start, in Point location) - { - Point currentLocation = location; - while (start is { Visible: true } && start.Contains (currentLocation)) - { - Adornment? found = null; - - if (start.Margin.Contains (currentLocation)) - { - found = start.Margin; - } - else if (start.Border.Contains (currentLocation)) - { - found = start.Border; - } - else if (start.Padding.Contains (currentLocation)) - { - found = start.Padding; - } - - Point viewportOffset = start.GetViewportOffsetFromFrame (); - - if (found is { }) - { - start = found; - viewportOffset = found.Parent.Frame.Location; - } - - int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X); - int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y); - - View? subview = null; - - for (int i = start.InternalSubviews.Count - 1; i >= 0; i--) - { - if (start.InternalSubviews [i].Visible - && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y))) - { - subview = start.InternalSubviews [i]; - currentLocation.X = startOffsetX + start.Viewport.X; - currentLocation.Y = startOffsetY + start.Viewport.Y; - - // start is the deepest subview under the mouse; stop searching the subviews - break; - } - } - - if (subview is null) - { - // No subview was found that's under the mouse, so we're done - return start; - } - - // We found a subview of start that's under the mouse, continue... - start = subview; - } - - return null; - } - - /// - /// Gets a new location of the that is within the Viewport of the 's - /// (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates. - /// - /// - /// If does not have a or it's SuperView is not - /// the position will be bound by the and - /// . - /// - /// The View that is to be moved. - /// The target x location. - /// The target y location. - /// The new x location that will ensure will be fully visible. - /// The new y location that will ensure will be fully visible. - /// The new top most statusBar - /// - /// Either (if does not have a Super View) or - /// 's SuperView. This can be used to ensure LayoutSubviews is called on the correct View. - /// - internal static View GetLocationEnsuringFullVisibility ( - View viewToMove, - int targetX, - int targetY, - out int nx, - out int ny, - out StatusBar statusBar - ) - { - int maxDimension; - View superView; - statusBar = null!; - - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) - { - maxDimension = Driver.Cols; - superView = Application.Top; - } - else - { - // Use the SuperView's Viewport, not Frame - maxDimension = viewToMove!.SuperView.Viewport.Width; - superView = viewToMove.SuperView; - } - - if (superView?.Margin is { } && superView == viewToMove!.SuperView) - { - maxDimension -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; - } - - if (viewToMove!.Frame.Width <= maxDimension) - { - nx = Math.Max (targetX, 0); - nx = nx + viewToMove.Frame.Width > maxDimension ? Math.Max (maxDimension - viewToMove.Frame.Width, 0) : nx; - - if (nx > viewToMove.Frame.X + viewToMove.Frame.Width) - { - nx = Math.Max (viewToMove.Frame.Right, 0); - } - } - else - { - nx = targetX; - } - - //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); - var menuVisible = false; - var statusVisible = false; - - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) - { - menuVisible = Application.Top?.MenuBar?.Visible == true; - } - else - { - View t = viewToMove!.SuperView; - - while (t is { } and not Toplevel) - { - t = t.SuperView; - } - - if (t is Toplevel topLevel) - { - menuVisible = topLevel.MenuBar?.Visible == true; - } - } - - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) - { - maxDimension = menuVisible ? 1 : 0; - } - else - { - maxDimension = 0; - } - - ny = Math.Max (targetY, maxDimension); - - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) - { - statusVisible = Application.Top?.StatusBar?.Visible == true; - statusBar = Application.Top?.StatusBar!; - } - else - { - View t = viewToMove!.SuperView; - - while (t is { } and not Toplevel) - { - t = t.SuperView; - } - - if (t is Toplevel topLevel) - { - statusVisible = topLevel.StatusBar?.Visible == true; - statusBar = topLevel.StatusBar!; - } - } - - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) - { - maxDimension = statusVisible ? Driver.Rows - 1 : Driver.Rows; - } - else - { - maxDimension = statusVisible ? viewToMove!.SuperView.Viewport.Height - 1 : viewToMove!.SuperView.Viewport.Height; - } - - if (superView?.Margin is { } && superView == viewToMove?.SuperView) - { - maxDimension -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom; - } - - ny = Math.Min (ny, maxDimension); - - if (viewToMove?.Frame.Height <= maxDimension) - { - ny = ny + viewToMove.Frame.Height > maxDimension - ? Math.Max (maxDimension - viewToMove.Frame.Height, menuVisible ? 1 : 0) - : ny; - - if (ny > viewToMove.Frame.Y + viewToMove.Frame.Height) - { - ny = Math.Max (viewToMove.Frame.Bottom, 0); - } - } - - //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); - - return superView!; - } - /// Fired after the View's method has completed. /// /// Subscribe to this event to perform tasks when the has been resized or the layout has @@ -562,7 +568,8 @@ out StatusBar statusBar /// are left unchanged. /// /// - /// If any of the view's subviews have a position or dimension dependent on either or other subviews, on + /// If any of the view's subviews have a position or dimension dependent on either or + /// other subviews, on /// will be called for that subview. /// /// @@ -650,12 +657,11 @@ internal void SetRelativeLayout (Size superviewContentSize) { TextFormatter.Height = GetContentSize ().Height; } - } - /// - /// Invoked when the dimensions of the view have changed, for example in response to the container view or terminal resizing. + /// Invoked when the dimensions of the view have changed, for example in response to the container view or terminal + /// resizing. /// /// /// @@ -678,7 +684,7 @@ public virtual void LayoutSubviews () CheckDimAuto (); - var contentSize = GetContentSize (); + Size contentSize = GetContentSize (); OnLayoutStarted (new (contentSize)); LayoutAdornments (); @@ -756,8 +762,8 @@ internal void OnResizeNeeded () // First try SuperView.Viewport, then Application.Top, then Driver.Viewport. // Finally, if none of those are valid, use 2048 (for Unit tests). Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.GetContentSize () : - Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : - Application.Screen.Size; + Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : + Application.Screen.Size; SetRelativeLayout (superViewContentSize); @@ -795,12 +801,15 @@ internal void SetNeedsLayout () } /// - /// Collects all views and their dependencies from a given starting view for layout purposes. Used by to create an ordered list of views to layout. + /// Collects all views and their dependencies from a given starting view for layout purposes. Used by + /// to create an ordered list of views to layout. /// /// The starting view from which to collect dependencies. /// A reference to a set of views representing nodes in the layout graph. - /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views indicating a dependency. - + /// + /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views + /// indicating a dependency. + /// internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { foreach (View? v in from.InternalSubviews) @@ -812,14 +821,17 @@ internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View CollectDim (v.Height, v, ref nNodes, ref nEdges); } } + /// - /// Collects dimension (where Width or Height is `DimView`) dependencies for a given view. + /// Collects dimension (where Width or Height is `DimView`) dependencies for a given view. /// /// The dimension (width or height) to collect dependencies for. /// The view for which to collect dimension dependencies. /// A reference to a set of views representing nodes in the layout graph. - /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views indicating a dependency. - + /// + /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views + /// indicating a dependency. + /// internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { switch (dim) @@ -844,12 +856,15 @@ internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref Has } /// - /// Collects position (where X or Y is `PosView`) dependencies for a given view. + /// Collects position (where X or Y is `PosView`) dependencies for a given view. /// /// The position (X or Y) to collect dependencies for. /// The view for which to collect position dependencies. /// A reference to a set of views representing nodes in the layout graph. - /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views indicating a dependency. + /// + /// A reference to a set of tuples representing edges in the layout graph, where each tuple consists of a pair of views + /// indicating a dependency. + /// internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { switch (pos) @@ -1008,7 +1023,6 @@ private Pos VerifyIsInitialized (Pos pos, string member) /// public bool ValidatePosDim { get; set; } - // TODO: Move this logic into the Pos/Dim classes /// /// Throws an if any SubViews are using Dim objects that depend on this @@ -1022,8 +1036,8 @@ private void CheckDimAuto () return; } - DimAuto? widthAuto = Width as DimAuto; - DimAuto? heightAuto = Height as DimAuto; + var widthAuto = Width as DimAuto; + var heightAuto = Height as DimAuto; // Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions. foreach (View view in Subviews) @@ -1088,8 +1102,10 @@ void ThrowInvalid (View view, object? checkPosDim, string name) throw new InvalidOperationException ( $"{view.GetType ().Name}.{name} = {bad.GetType ().Name} " + $"which depends on the SuperView's dimensions and the SuperView uses Dim.Auto." - ); + ); } } } + + #endregion Layout Engine } From e4e2b59a60645d138972e69541ed454199d9a22d Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 18 Jul 2024 16:03:32 -0600 Subject: [PATCH 30/96] Code cleanup --- Terminal.Gui/View/Layout/ViewLayout.cs | 4 +--- Terminal.Gui/View/ViewText.cs | 9 +++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 26da0b4b33..c41c5e12cf 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -689,8 +689,6 @@ public virtual void LayoutSubviews () LayoutAdornments (); - //SetTextFormatterSize (); - // Sort out the dependencies of the X, Y, Width, Height properties HashSet nodes = new (); HashSet<(View, View)> edges = new (); @@ -719,7 +717,7 @@ public virtual void LayoutSubviews () private void LayoutSubview (View v, Size contentSize) { - // BUGBUG: Calling SetRelativeLayout before LayoutSubviews is problematic. Need to resolve. + // Note, SetRelativeLayout calls SetTextFormatterSize v.SetRelativeLayout (contentSize); v.LayoutSubviews (); v.LayoutNeeded = false; diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 9ae02d2db5..0e43dba948 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -157,12 +157,9 @@ public virtual Alignment VerticalTextAlignment /// protected virtual void UpdateTextFormatterText () { - if (TextFormatter is { }) - { - TextFormatter.Text = _text; - TextFormatter.Width = null; - TextFormatter.Height = null; - } + TextFormatter.Text = _text; + TextFormatter.Width = null; + TextFormatter.Height = null; } /// From 7733a69e8ae7efc010747265b8b990422b360839 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 08:08:52 -0600 Subject: [PATCH 31/96] Code cleanup --- Terminal.Gui/Text/TextFormatter.cs | 1 - Terminal.Gui/View/Layout/DimAuto.cs | 21 +++------------------ Terminal.Gui/View/Layout/ViewLayout.cs | 1 - Terminal.Gui/Views/Bar.cs | 5 ----- 4 files changed, 3 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 14dda57a1a..ea0c359240 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -516,7 +516,6 @@ public Size FormatAndGetSize (Size? constrainSize = null) return System.Drawing.Size.Empty; } - // HACK: This is a total hack to work around the fact that TextFormatter.Format does not match TextFormatter.Draw. int? prevWidth = _width; int? prevHeight = _height; diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 926ffc0ce9..c649616fdf 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -356,6 +356,7 @@ internal override int Calculate (int location, int superviewContentSize, View us View v = anchoredSubViews [i]; // Need to set the relative layout for PosAnchorEnd subviews to calculate the size + // TODO: Figure out a way to not have Calculate change the state of subviews (calling SRL). if (dimension == Dimension.Width) { v.SetRelativeLayout (new (maxCalculatedSize, screenX4)); @@ -393,6 +394,7 @@ internal override int Calculate (int location, int superviewContentSize, View us View v = posViewSubViews [i]; // 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)); @@ -433,6 +435,7 @@ internal override int Calculate (int location, int superviewContentSize, View us View v = dimViewSubViews [i]; // 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)); @@ -464,24 +467,6 @@ internal override int Calculate (int location, int superviewContentSize, View us // And, if max: is set, it wins if smaller max = int.Min (max, autoMax); - // ************** We now definitively know `us.ContentSize` *************** - - int oppositeScreen = dimension == Dimension.Width ? Application.Screen.Height * 4 : Application.Screen.Width * 4; - - // TODO: Double-check that we really do need to SetRelativeLayout on these views! - foreach (View v in viewsNeedingLayout) - { - if (dimension == Dimension.Width) - { - v.SetRelativeLayout (new (max, oppositeScreen)); - } - else - { - v.SetRelativeLayout (new (oppositeScreen, max)); - } - } - - // Factor in adornments Thickness thickness = us.GetAdornmentsThickness (); int adornmentThickness = dimension switch diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index c41c5e12cf..6a4cbcf02d 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -282,7 +282,6 @@ public Rectangle Frame _width = _frame.Width; _height = _frame.Height; - // TODO: Figure out if the below can be optimized. if (IsInitialized) { OnResizeNeeded (); diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 07447fba81..92262371b8 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -153,9 +153,6 @@ internal override void OnLayoutStarted (LayoutEventArgs args) barItem.ColorScheme = ColorScheme; barItem.X = Pos.Align (Alignment.Start, AlignmentModes); barItem.Y = 0; //Pos.Center (); - - // HACK: This should not be needed - barItem.SetRelativeLayout (GetContentSize ()); } break; @@ -190,8 +187,6 @@ internal override void OnLayoutStarted (LayoutEventArgs args) if (barItem is Shortcut scBarItem) { scBarItem.MinimumKeyTextSize = minKeyWidth; - // HACK: This should not be needed - scBarItem.SetRelativeLayout (GetContentSize ()); maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width); } From c119fc59012375cd2550d8e6e2f3d9aabf72636f Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 15:23:28 -0600 Subject: [PATCH 32/96] Updated DimAuto.md --- docfx/docs/dimauto.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/docfx/docs/dimauto.md b/docfx/docs/dimauto.md index e138348d58..42c6c2a68d 100644 --- a/docfx/docs/dimauto.md +++ b/docfx/docs/dimauto.md @@ -2,7 +2,7 @@ The `Dim.Auto` type is a type of `Dim` that automatically sizes the view based on its content. This is useful when you want to size a view based on the content it contains. That content can either be the `Text`, the `SubViews`, or something else defined by the view. -Like all `Dim` types, `Dim.Auto` is used to set the `Width` or `Height` of a view. +Like all `Dim` types, `Dim.Auto` is used to set the `Width` or `Height` of a view and can be combined with other `Dim` types using addition or subtraction (see. `DimCombine`). The `DimAutoStyle` enum defines the different ways that `Dim.Auto` can be used to size a view. The `DimAutoStyle` enum has the following values: @@ -77,7 +77,7 @@ int GetDynamicMinSize () ### Specifying a maximum size -> NOT YET IMPLEMENTED +It is common to want to constrain how large a View can be sized. The `maximumContentDim` parameter to the `Dim.Auto ()` method enables this. Like `minimumContentDim` it is of type `Dim` and thus can represent a dynamic value. For example, by default `Dialog` specifies `maximumContentDim` as `Dim.Percent (90)` to ensure a Dialog box is never larger than 90% of the screen. ## Limitations @@ -85,11 +85,28 @@ int GetDynamicMinSize () `Dim.Auto` is also not always the most efficient way to size a view. If you know the size of the content ahead of time, you can set the `Width` and `Height` properties to `Dim.Absolute (n)` instead of using `Dim.Auto`. -> TODO: Verify accuracy of the following paragraphs +## Behavior of other Pos/Dim Types when used within a Dim.Auto-sized View + +The table below descibes the behavior of the various Pos/Dim types when used by subviews of a View that uses `Dim.Auto` for it's `Width` or `Height`: + +| Type | Impacts Dimension | Limitations | +|-------------|-------------------|---------------------------------------------------------------------------------------------------------| +| PosAlign | Yes | The subviews with the same `GroupId` will be aligned at the maximimum dimension to enable them to not be clipped. This dimension plus the group's position will determine the minimum `Dim.Auto` dimension. | +| PosView | Yes | The position plus the dimension of `subview.Target` will determine the minimum `Dim.Auto` dimension. | +| PosCombine | Yes | | +| PosAnchorEnd| Yes | The `Dim.Auto` dimension will be increased by the dimension of the subview. | +| PosCenter | No | | +| PosPercent | No | | +| PosAbsolute | Yes | | +| PosFunc | Yes | | +| DimView | Yes | The position plus the dimension of `subview.Target` will determine the minimum `Dim.Auto` dimension. | +| DimCombine | Yes | | +| DimFill | No | | +| DimPercent | No | | +| DimAuto | Yes | | +| DimAbsolute | Yes | | +| DimFunc | Yes | | -Some `Pos` and `Dim` types are not compatible with `Dim.Auto`. For example, you cannot use `Dim.Auto (DimAutoStyle.Content)` with Subviews that use `Dim.Fill ()` or `Dim.Percent (n)` because the size of the Subview is not known until the layout is computed. - -`Pos` types that are relative to the size of the view, such as `Pos.Percent (n)` are not compatible with `Dim.Auto` because the size of the view is not known until the layout is computed. However, `Pos.Center ()` and `Pos.AnchorEnd ()` are compatible with `Dim.Auto` because they are relative to the size of the view's Superview. ## Building Dim.Auto friendly View From ee57f41b9fb52775d74977df75795ffe1207e37d Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 15:42:04 -0600 Subject: [PATCH 33/96] Fixed @bdisp noted error --- Terminal.Gui/Text/TextFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index ea0c359240..710ed52bdd 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -715,7 +715,7 @@ public int? Height if (value < 0) { - throw new ArgumentOutOfRangeException (nameof (Width), value, @"Must be greater than or equal to 0."); + throw new ArgumentOutOfRangeException (nameof (Height), value, @"Must be greater than or equal to 0."); } _height = value; From 153f1015d79437ae7e542cb45b29b28d5deb1a19 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 15:59:06 -0600 Subject: [PATCH 34/96] Fixed warnings --- Terminal.Gui/Text/TextFormatter.cs | 10 +++++----- Terminal.Gui/Views/CheckBox.cs | 11 +++++++++++ Terminal.Gui/Views/CheckState.cs | 23 +++++++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 Terminal.Gui/Views/CheckState.cs diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 710ed52bdd..0ffbd70705 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -309,7 +309,7 @@ public void Draw ( int start = isVertical ? screen.Top : screen.Left; int size = isVertical ? screen.Height : screen.Width; int current = start + colOffset; - List lastZeroWidthPos = null; + List lastZeroWidthPos = null!; Rune rune = default; int zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0; @@ -378,12 +378,12 @@ public void Draw ( { lastZeroWidthPos [foundIdx] = new Point ( - lastZeroWidthPos [foundIdx].Value.X + 1, + lastZeroWidthPos [foundIdx]!.Value.X + 1, current ); driver?.Move ( - lastZeroWidthPos [foundIdx].Value.X, + lastZeroWidthPos [foundIdx]!.Value.X, current ); } @@ -1758,7 +1758,7 @@ public static List Format ( return lineResult; } - string [] lines = null; + string []? lines = null; if (text.Contains ("\r\n")) { @@ -1967,7 +1967,7 @@ public static int GetSumMaxCharWidth (string text, int startIndex = -1, int leng /// The index of the text that fit the width. public static int GetLengthThatFits (string text, int width, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom) { - return GetLengthThatFits (text?.ToRuneList (), width, tabWidth, textDirection); + return GetLengthThatFits (text.ToRuneList (), width, tabWidth, textDirection); } /// Gets the number of the Runes in a list of Runes that will fit in . diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index 9428ba91f9..8d1b6a57b2 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -6,8 +6,19 @@ namespace Terminal.Gui; /// public enum CheckState { + /// + /// Neither checked nor unchecked. + /// None, + + /// + /// Checked. + /// Checked, + + /// + /// Not checked. + /// UnChecked } diff --git a/Terminal.Gui/Views/CheckState.cs b/Terminal.Gui/Views/CheckState.cs new file mode 100644 index 0000000000..54f818bc79 --- /dev/null +++ b/Terminal.Gui/Views/CheckState.cs @@ -0,0 +1,23 @@ +#nullable enable +namespace Terminal.Gui; + +/// +/// Represents the state of a . +/// +public enum CheckState +{ + /// + /// Neither checked nor unchecked. + /// + None, + + /// + /// Checked. + /// + Checked, + + /// + /// Not checked. + /// + UnChecked +} From c6a9021f2ed44fbf0ce0712759089eb2b5103531 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 16:07:14 -0600 Subject: [PATCH 35/96] Fixed error in Textformatter --- Terminal.Gui/Text/TextFormatter.cs | 2 +- Terminal.Gui/Views/CheckBox.cs | 21 --------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 0ffbd70705..e3458afe95 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1967,7 +1967,7 @@ public static int GetSumMaxCharWidth (string text, int startIndex = -1, int leng /// The index of the text that fit the width. public static int GetLengthThatFits (string text, int width, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom) { - return GetLengthThatFits (text.ToRuneList (), width, tabWidth, textDirection); + return GetLengthThatFits (text?.ToRuneList () ?? [], width, tabWidth, textDirection); } /// Gets the number of the Runes in a list of Runes that will fit in . diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index 8d1b6a57b2..c43000953c 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -1,27 +1,6 @@ #nullable enable namespace Terminal.Gui; -/// -/// Represents the state of a . -/// -public enum CheckState -{ - /// - /// Neither checked nor unchecked. - /// - None, - - /// - /// Checked. - /// - Checked, - - /// - /// Not checked. - /// - UnChecked -} - /// Shows a check box that can be toggled. public class CheckBox : View { From c537c347eef439c67d085f01d78a25c9b71a9b7a Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 16:10:39 -0600 Subject: [PATCH 36/96] doc tweak --- docfx/docs/dimauto.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docfx/docs/dimauto.md b/docfx/docs/dimauto.md index 42c6c2a68d..f831c3ac67 100644 --- a/docfx/docs/dimauto.md +++ b/docfx/docs/dimauto.md @@ -89,7 +89,7 @@ It is common to want to constrain how large a View can be sized. The `maximumCon The table below descibes the behavior of the various Pos/Dim types when used by subviews of a View that uses `Dim.Auto` for it's `Width` or `Height`: -| Type | Impacts Dimension | Limitations | +| Type | Impacts Dimension | Notes | |-------------|-------------------|---------------------------------------------------------------------------------------------------------| | PosAlign | Yes | The subviews with the same `GroupId` will be aligned at the maximimum dimension to enable them to not be clipped. This dimension plus the group's position will determine the minimum `Dim.Auto` dimension. | | PosView | Yes | The position plus the dimension of `subview.Target` will determine the minimum `Dim.Auto` dimension. | From 23737decec34618e7bfa407a2901cbbee0c4ee36 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 17:02:14 -0600 Subject: [PATCH 37/96] rebased against v2_develop --- Terminal.Gui/Application/Application.cs | 24 +++++++-------- Terminal.Gui/Clipboard/IClipboard.cs | 8 ++--- .../Configuration/ConfigurationManager.cs | 2 +- .../ConfigurationManagerEventArgs.cs | 2 +- .../SerializableConfigurationProperty.cs | 2 +- Terminal.Gui/Configuration/ThemeManager.cs | 2 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 10 +++---- .../ConsoleDrivers/ConsoleKeyMapping.cs | 2 +- .../CursesDriver/ClipboardImpl.cs | 2 +- .../CursesDriver/CursesDriver.cs | 4 +-- .../CursesDriver/UnmanagedLibrary.cs | 2 +- .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 2 +- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 8 ++--- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 4 +-- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2 +- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 14 ++++----- Terminal.Gui/Drawing/ColorScheme.cs | 2 +- Terminal.Gui/Drawing/Glyphs.cs | 2 +- Terminal.Gui/Drawing/LineCanvas.cs | 2 +- Terminal.Gui/Drawing/LineStyle.cs | 2 +- Terminal.Gui/FileServices/AllowedType.cs | 4 +-- Terminal.Gui/FileServices/FileDialogState.cs | 2 +- Terminal.Gui/FileServices/FileDialogStyle.cs | 8 ++--- Terminal.Gui/Input/Command.cs | 8 ++--- Terminal.Gui/Input/Key.cs | 2 +- Terminal.Gui/Input/MouseEventEventArgs.cs | 2 +- Terminal.Gui/Resources/config.json | 2 +- Terminal.Gui/StackExtensions.cs | 2 +- .../Text/Autocomplete/AppendAutocomplete.cs | 2 +- .../Text/Autocomplete/PopupAutocomplete.cs | 6 ++-- Terminal.Gui/Text/TextFormatter.cs | 12 ++++---- Terminal.Gui/View/Adornment/Border.cs | 2 +- Terminal.Gui/View/Layout/ViewLayout.cs | 3 +- Terminal.Gui/View/View.cs | 2 +- Terminal.Gui/View/ViewEventArgs.cs | 2 +- Terminal.Gui/View/ViewKeyboard.cs | 13 ++++---- Terminal.Gui/Views/ComboBox.cs | 18 +++++------ Terminal.Gui/Views/DateField.cs | 10 +++---- Terminal.Gui/Views/FileDialog.cs | 4 +-- Terminal.Gui/Views/GraphView/Axis.cs | 12 ++++---- Terminal.Gui/Views/GraphView/Series.cs | 8 ++--- Terminal.Gui/Views/HexView.cs | 6 ++-- Terminal.Gui/Views/Menu/ContextMenu.cs | 2 +- Terminal.Gui/Views/Menu/Menu.cs | 2 +- Terminal.Gui/Views/Menu/MenuBar.cs | 2 +- Terminal.Gui/Views/MessageBox.cs | 2 +- Terminal.Gui/Views/SelectedItemChangedArgs.cs | 2 +- Terminal.Gui/Views/Slider.cs | 8 ++--- Terminal.Gui/Views/SliderOption.cs | 2 +- Terminal.Gui/Views/Tab.cs | 4 +-- Terminal.Gui/Views/TabView.cs | 2 +- .../Views/TableView/EnumerableTableSource.cs | 6 ++-- Terminal.Gui/Views/TableView/TableView.cs | 10 +++---- .../Views/TableView/TreeTableSource.cs | 8 ++--- Terminal.Gui/Views/TextField.cs | 2 +- Terminal.Gui/Views/TextView.cs | 30 +++++++++---------- Terminal.Gui/Views/TileView.cs | 2 +- Terminal.Gui/Views/TimeField.cs | 8 ++--- Terminal.Gui/Views/TreeView/Branch.cs | 14 ++++----- .../TreeView/DrawTreeViewLineEventArgs.cs | 2 +- Terminal.Gui/Views/TreeView/TreeNode.cs | 6 ++-- Terminal.Gui/Views/TreeView/TreeStyle.cs | 2 +- Terminal.Gui/Views/TreeView/TreeView.cs | 20 ++++++------- Terminal.Gui/Views/Wizard/Wizard.cs | 8 ++--- UnitTests/Views/TabTests.cs | 2 +- 65 files changed, 187 insertions(+), 187 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 9833d19cd8..752b8b90db 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -165,7 +165,7 @@ internal static void ResetState (bool ignoreDisposed = false) Colors.Reset (); // Reset synchronization context to allow the user to run async/await, - // as the main loop has been ended, the synchronization context from + // as the main loop has been ended, the synchronization context from // gui.cs does no longer process any callbacks. See #1084 for more details: // (https://github.com/gui-cs/Terminal.Gui/issues/1084). SynchronizationContext.SetSynchronizationContext (null); @@ -211,11 +211,11 @@ internal static void ResetState (bool ignoreDisposed = false) // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. // // Called from: - // + // // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset. // Run() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run() to be called without calling Init first. // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset. - // + // // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] @@ -250,7 +250,7 @@ internal static void InternalInit ( // Start the process of configuration management. // Note that we end up calling LoadConfigurationFromAllSources // multiple times. We need to do this because some settings are only - // valid after a Driver is loaded. In this cases we need just + // valid after a Driver is loaded. In this case we need just // `Settings` so we can determine which driver to use. // Don't reset, so we can inherit the theme from the previous run. Load (); @@ -391,7 +391,7 @@ public static void Shutdown () /// public static event EventHandler NotifyNewRunState; - /// Notify that a existent is stopping ( was called). + /// Notify that an existent is stopping ( was called). /// /// If is callers to /// must also subscribe to and manually dispose of the token @@ -471,7 +471,7 @@ public static RunState Begin (Toplevel toplevel) Top.OnLeave (toplevel); } - // BUGBUG: We should not depend on `Id` internally. + // BUGBUG: We should not depend on `Id` internally. // BUGBUG: It is super unclear what this code does anyway. if (string.IsNullOrEmpty (toplevel.Id)) { @@ -859,7 +859,7 @@ public static void Invoke (Action action) } // TODO: Determine if this is really needed. The only code that calls WakeUp I can find - // is ProgressBarStyles and it's not clear it needs to. + // is ProgressBarStyles, and it's not clear it needs to. /// Wakes up the running application that might be waiting on input. public static void Wakeup () { MainLoop?.Wakeup (); } @@ -1168,13 +1168,13 @@ public static void End (RunState runState) runState.Toplevel.OnUnloaded (); } - // End the RunState.Toplevel + // End the RunState.Toplevel // First, take it off the Toplevel Stack if (_topLevels.Count > 0) { if (_topLevels.Peek () != runState.Toplevel) { - // If there the top of the stack is not the RunState.Toplevel then + // If the top of the stack is not the RunState.Toplevel then // this call to End is not balanced with the call to Begin that started the RunState throw new ArgumentException ("End must be balanced with calls to Begin"); } @@ -1185,8 +1185,8 @@ public static void End (RunState runState) // Notify that it is closing runState.Toplevel?.OnClosed (runState.Toplevel); - // If there is a OverlappedTop that is not the RunState.Toplevel then runstate.TopLevel - // is a child of MidTop and we should notify the OverlappedTop that it is closing + // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel + // is a child of MidTop, and we should notify the OverlappedTop that it is closing if (OverlappedTop is { } && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop) { OverlappedTop.OnChildClosed (runState.Toplevel); @@ -1242,7 +1242,7 @@ public static void End (RunState runState) /// Holds the stack of TopLevel views. - // BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What + // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What // about TopLevels that are just a SubView of another View? internal static readonly Stack _topLevels = new (); diff --git a/Terminal.Gui/Clipboard/IClipboard.cs b/Terminal.Gui/Clipboard/IClipboard.cs index 1dd497bf1c..789b30aeb6 100644 --- a/Terminal.Gui/Clipboard/IClipboard.cs +++ b/Terminal.Gui/Clipboard/IClipboard.cs @@ -6,21 +6,21 @@ public interface IClipboard /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. bool IsSupported { get; } - /// Get the operation system clipboard. + /// Get the operating system clipboard. /// Thrown if it was not possible to read the clipboard contents. string GetClipboardData (); - /// Sets the operation system clipboard. + /// Sets the operating system clipboard. /// /// Thrown if it was not possible to set the clipboard contents. void SetClipboardData (string text); - /// Gets the operation system clipboard if possible. + /// Gets the operating system clipboard if possible. /// Clipboard contents read /// true if it was possible to read the OS clipboard. bool TryGetClipboardData (out string result); - /// Sets the operation system clipboard if possible. + /// Sets the operating system clipboard if possible. /// /// True if the clipboard content was set successfully. bool TrySetClipboardData (string text); diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs index 68c8c184c6..e234b6ee7f 100644 --- a/Terminal.Gui/Configuration/ConfigurationManager.cs +++ b/Terminal.Gui/Configuration/ConfigurationManager.cs @@ -125,7 +125,7 @@ public enum ConfigLocations /// private static SettingsScope? _settings; - /// Name of the running application. By default this property is set to the application's assembly name. + /// Name of the running application. By default, this property is set to the application's assembly name. public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!; /// Application-specific configuration settings scope. diff --git a/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs b/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs index 9d70923211..0cc42f6a1c 100644 --- a/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs +++ b/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs @@ -15,6 +15,6 @@ public class ThemeManagerEventArgs : EventArgs /// Initializes a new instance of public ThemeManagerEventArgs (string newTheme) { NewTheme = newTheme; } - /// The name of the new active theme.. + /// The name of the new active theme. public string NewTheme { get; set; } = string.Empty; } diff --git a/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs b/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs index 4741814e67..1e0e2545e2 100644 --- a/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs +++ b/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; -/// An attribute that can be applied to a property to indicate that it should included in the configuration file. +/// An attribute that can be applied to a property to indicate that it should be included in the configuration file. /// /// [SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter /// (typeof (JsonStringEnumConverter))] public static LineStyle DefaultBorderStyle { ... diff --git a/Terminal.Gui/Configuration/ThemeManager.cs b/Terminal.Gui/Configuration/ThemeManager.cs index e6efe32e08..daf2edd9d5 100644 --- a/Terminal.Gui/Configuration/ThemeManager.cs +++ b/Terminal.Gui/Configuration/ThemeManager.cs @@ -9,7 +9,7 @@ namespace Terminal.Gui; /// Contains a dictionary of the s for a Terminal.Gui application. /// /// A Theme is a collection of settings that are named. The default theme is named "Default". -/// The property is used to detemrine the currently active theme. +/// The property is used to determine the currently active theme. /// /// /// is a singleton class. It is created when the first property diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 05cbb62172..e5ac4ad076 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -568,7 +568,7 @@ public virtual Attribute MakeColor (in Color foreground, in Color background) /// Called when a key is released. Fires the event. /// - /// Drivers that do not support key release events will calls this method after processing + /// Drivers that do not support key release events will call this method after processing /// is complete. /// /// @@ -604,7 +604,7 @@ public enum CursorVisibility /// Cursor caret has default /// /// Works under Xterm-like terminal otherwise this is equivalent to . This default directly - /// depends of the XTerm user configuration settings so it could be Block, I-Beam, Underline with possible blinking. + /// depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking. /// Default = 0x00010119, @@ -682,7 +682,7 @@ public enum KeyCode : uint /// /// If the is set, then the value is that of the special mask, otherwise, the value is - /// in the the lower bits (as extracted by ). + /// in the lower bits (as extracted by ). /// SpecialMask = 0x_fff0_0000, @@ -839,9 +839,9 @@ public enum KeyCode : uint //Delete = 127, // --- Special keys --- - // The values below are common non-alphanum keys. Their values are + // The values below are common non-alphanum keys. Their values are // based on the .NET ConsoleKey values, which, in-turn are based on the - // VK_ values from the Windows API. + // VK_ values from the Windows API. // We add MaxCodePoint to avoid conflicts with the Unicode values. /// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys. diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index 8043c65eb6..c3497f3acd 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -1435,7 +1435,7 @@ public enum VK : ushort (VK)'2', ConsoleModifiers.Shift, '\"' - ), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (") + ), // BUGBUG: This is true for Portuguese keyboard, but not ENG (@) or DEU (") new ScanCodeMapping ( 3, (VK)'2', diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs index 99938dcc0b..0ec8efc059 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs @@ -89,7 +89,7 @@ private bool CheckSupport () /// /// A clipboard implementation for MacOSX. This implementation uses the Mac clipboard API (via P/Invoke) to -/// copy/paste. The existance of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported. +/// copy/paste. The existence of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported. /// internal class MacOSXClipboard : ClipboardBase { diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 14d71c6371..378e4b6081 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -317,7 +317,7 @@ internal override MainLoop Init () Curses.doupdate (); // - // We are setting Invisible as default so we could ignore XTerm DECSUSR setting + // We are setting Invisible as default, so we could ignore XTerm DECSUSR setting // switch (Curses.curs_set (0)) { @@ -977,7 +977,7 @@ internal static class Platform private static int _suspendSignal; /// Suspends the process by sending SIGTSTP to itself - /// The suspend. + /// True if the suspension was successful. public static bool Suspend () { int signal = GetSuspendSignal (); diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs index 7400214ecc..be3fd1ac36 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs @@ -291,7 +291,7 @@ private static class MacOSX } /// - /// On Linux systems, using using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if + /// On Linux systems, using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if /// libc6-dev is not installed. As a workaround, we load symbols for dlopen and dlsym from the current process as on /// Linux Mono sure is linked against these symbols. /// diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index b62ef17a40..29ef5afa79 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -22,7 +22,7 @@ public EscSeqReqStatus (string terminator, int numReq) /// Gets the number of requests. public int NumRequests { get; } - /// Gets the Escape Sequence Termintor (e.g. ESC[8t ... t is the terminator). + /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). public string Terminator { get; } } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index f9c547af6b..1eb63e34aa 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -159,10 +159,10 @@ public enum ClearScreenOptions /// Decodes an ANSI escape sequence. /// /// The which may contain a request. - /// The which may changes. - /// The which may changes. + /// The which may change. + /// The which may change. /// The array. - /// The which may changes. + /// The which may change. /// The control returned by the method. /// The code returned by the method. /// The values returned by the method. @@ -426,7 +426,7 @@ public static ConsoleModifiers GetConsoleModifiers (string? value) #nullable restore /// - /// Gets all the needed information about a escape sequence. + /// Gets all the needed information about an escape sequence. /// /// The array with all chars. /// diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 74328a8189..6ec810c8a0 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -198,7 +198,7 @@ public override void UpdateScreen () FakeConsole.CursorTop = 0; FakeConsole.CursorLeft = 0; - //SetCursorVisibility (savedVisibitity); + //SetCursorVisibility (savedVisibility); void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { @@ -437,7 +437,7 @@ public virtual void ResizeScreen () { if (FakeConsole.WindowHeight > 0) { - // Can raise an exception while is still resizing. + // Can raise an exception while it is still resizing. try { FakeConsole.CursorTop = 0; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 86ef134664..fcd7316e22 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1669,7 +1669,7 @@ private KeyCode MapKey (ConsoleKeyInfo keyInfo) /// /// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is -/// cross platform but lacks things like file descriptor monitoring. +/// cross-platform but lacks things like file descriptor monitoring. /// /// This implementation is used for NetDriver. internal class NetMainLoop : IMainLoopDriver diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 89c82dd58a..8ff127a4ba 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -659,7 +659,7 @@ public struct ExtendedCharInfo { public char Char { get; set; } public Attribute Attribute { get; set; } - public bool Empty { get; set; } // TODO: Temp hack until virutal terminal sequences + public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences public ExtendedCharInfo (char character, Attribute attribute) { @@ -901,7 +901,7 @@ public InputRecord [] ReadConsoleInput () } } -#if false // Not needed on the constructor. Perhaps could be used on resizing. To study. +#if false // Not needed on the constructor. Perhaps could be used on resizing. To study. [DllImport ("kernel32.dll", ExactSpelling = true)] static extern IntPtr GetConsoleWindow (); @@ -1397,7 +1397,7 @@ internal override MainLoop Init () { if (WinConsole is { }) { - // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. + // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos); Cols = winSize.Width; @@ -1642,7 +1642,7 @@ private KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) if (keyInfo.Modifiers != 0) { - // These Oem keys have well defined chars. We ensure the representative char is used. + // These Oem keys have well-defined chars. We ensure the representative char is used. // If we don't do this, then on some keyboard layouts the wrong char is // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important // for key persistence ("Ctrl++" vs. "Ctrl+="). @@ -1656,7 +1656,7 @@ private KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) }; } - // Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down + // Return the mappedChar with modifiers. Because mappedChar is un-shifted, if Shift was down // we should keep it return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar); } @@ -1901,8 +1901,8 @@ private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) // The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button. // This will tell when a mouse button is pressed. When the button is released this event will - // be fired with it's bit set to 0. So when the button is up ButtonState will be 0. - // To map to the correct driver events we save the last pressed mouse button so we can + // be fired with its bit set to 0. So when the button is up ButtonState will be 0. + // To map to the correct driver events we save the last pressed mouse button, so we can // map to the correct clicked event. if ((_lastMouseButtonPressed is { } || _isButtonReleased) && mouseEvent.ButtonState != 0) { diff --git a/Terminal.Gui/Drawing/ColorScheme.cs b/Terminal.Gui/Drawing/ColorScheme.cs index d2acdab5ca..603694eec8 100644 --- a/Terminal.Gui/Drawing/ColorScheme.cs +++ b/Terminal.Gui/Drawing/ColorScheme.cs @@ -77,7 +77,7 @@ public Attribute Focus init => _focus = value; } - /// The foreground and background color for for text in a focused view that indicates a . + /// The foreground and background color for text in a focused view that indicates a . public Attribute HotFocus { get => _hotFocus; diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs index e98d60b1ec..f64285f0f9 100644 --- a/Terminal.Gui/Drawing/Glyphs.cs +++ b/Terminal.Gui/Drawing/Glyphs.cs @@ -348,7 +348,7 @@ public class GlyphDefinitions /// Box Drawings Left Tee - Heavy Vertical and Heavy Horizontal (U+2527) - ┣ public Rune LeftTeeHvDblH { get; set; } = (Rune)'┣'; - /// Box Drawings Righ Tee - Single Vertical and Single Horizontal (U+2524) - ┤ + /// Box Drawings Right Tee - Single Vertical and Single Horizontal (U+2524) - ┤ public Rune RightTee { get; set; } = (Rune)'┤'; /// Box Drawings Right Tee - Single Vertical and Double Horizontal (U+2561) - ╡ diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 2bda9e5dd4..4b5119a82a 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -199,7 +199,7 @@ public void Clear () } // TODO: Unless there's an obvious use case for this API we should delete it in favor of the - // simpler version that doensn't take an area. + // simpler version that doesn't take an area. /// /// Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their /// locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate diff --git a/Terminal.Gui/Drawing/LineStyle.cs b/Terminal.Gui/Drawing/LineStyle.cs index 6bb06212ec..47e37a97aa 100644 --- a/Terminal.Gui/Drawing/LineStyle.cs +++ b/Terminal.Gui/Drawing/LineStyle.cs @@ -41,7 +41,7 @@ public enum LineStyle RoundedDotted // TODO: Support Ruler - ///// + ///// ///// The border is drawn as a diagnostic ruler ("|123456789..."). ///// //Ruler diff --git a/Terminal.Gui/FileServices/AllowedType.cs b/Terminal.Gui/FileServices/AllowedType.cs index 06978cc86f..8d840edeca 100644 --- a/Terminal.Gui/FileServices/AllowedType.cs +++ b/Terminal.Gui/FileServices/AllowedType.cs @@ -33,7 +33,7 @@ public class AllowedTypeAny : IAllowedType public class AllowedType : IAllowedType { /// Initializes a new instance of the class. - /// The human readable text to display. + /// The human-readable text to display. /// Extension(s) to match e.g. .csv. public AllowedType (string description, params string [] extensions) { @@ -46,7 +46,7 @@ public AllowedType (string description, params string [] extensions) Extensions = extensions; } - /// Gets or Sets the human readable description for the file type e.g. "Comma Separated Values". + /// Gets or Sets the human-readable description for the file type e.g. "Comma Separated Values". public string Description { get; set; } /// Gets or Sets the permitted file extension(s) (e.g. ".csv"). diff --git a/Terminal.Gui/FileServices/FileDialogState.cs b/Terminal.Gui/FileServices/FileDialogState.cs index ece6d24286..042b5a75d7 100644 --- a/Terminal.Gui/FileServices/FileDialogState.cs +++ b/Terminal.Gui/FileServices/FileDialogState.cs @@ -54,7 +54,7 @@ protected virtual IEnumerable GetChildren (IDirectoryInfo d .ToList (); } - // if theres a UI filter in place too + // if there's a UI filter in place too if (Parent.CurrentFilter is { }) { children = children.Where (MatchesApiFilter).ToList (); diff --git a/Terminal.Gui/FileServices/FileDialogStyle.cs b/Terminal.Gui/FileServices/FileDialogStyle.cs index 9cc6557f15..aac806de81 100644 --- a/Terminal.Gui/FileServices/FileDialogStyle.cs +++ b/Terminal.Gui/FileServices/FileDialogStyle.cs @@ -24,7 +24,7 @@ public FileDialogStyle (IFileSystem fileSystem) public string CancelButtonText { get; set; } = Strings.btnCancel; /// - /// Gets or sets the class thatis responsible for determining which color to use to represent files and + /// Gets or sets the class that is responsible for determining which color to use to represent files and /// directories when is . /// public FileSystemColorProvider ColorProvider { get; set; } = new (); @@ -100,7 +100,7 @@ public FileDialogStyle (IFileSystem fileSystem) /// Gets or sets the header text displayed in the Modified column of the files table. public string ModifiedColumnName { get; set; } = Strings.fdModified; - /// Gets or sets the text on the 'Ok' button. Typically you may want to change this to "Open" or "Save" etc. + /// Gets or sets the text on the 'Ok' button. Typically, you may want to change this to "Open" or "Save" etc. public string OkButtonText { get; set; } = Strings.btnOk; /// Gets or sets the text displayed in the 'Path' text box when user has not supplied any input yet. @@ -163,7 +163,7 @@ private Dictionary DefaultTreeRootGetter () } catch (Exception) { - // Cannot get the system disks thats fine + // Cannot get the system disks, that's fine } try @@ -195,7 +195,7 @@ private Dictionary DefaultTreeRootGetter () } catch (Exception) { - // Cannot get the special files for this OS oh well + // Cannot get the special files for this OS, oh well } return roots; diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index 24ebdb407d..566cc9336c 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -113,7 +113,7 @@ public enum Command /// Move one page down. PageDown, - /// Move one page page extending the selection to cover revealed objects/characters. + /// Move one page down extending the selection to cover revealed objects/characters. PageDownExtend, /// Move one page up. @@ -137,7 +137,7 @@ public enum Command /// Open the selected item. OpenSelectedItem, - /// Toggles the Expanded or collapsed state of a a list or item (with subitems). + /// Toggles the Expanded or collapsed state of a list or item (with subitems). ToggleExpandCollapse, /// Expands a list or item (with subitems). @@ -224,13 +224,13 @@ public enum Command /// Quit a . QuitToplevel, - /// Suspend a application (Only implemented in ). + /// Suspend an application (Only implemented in ). Suspend, /// Moves focus to the next view. NextView, - /// Moves focuss to the previous view. + /// Moves focus to the previous view. PreviousView, /// Moves focus to the next view or Toplevel (case of Overlapped). diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index 04777de66d..d2972bc04b 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -487,7 +487,7 @@ private static string GetKeyString (KeyCode key) /// Formats a as a string. /// The key to format. - /// The character to use as a separator between modifier keys and and the key itself. + /// The character to use as a separator between modifier keys and the key itself. /// /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key /// name will be returned. diff --git a/Terminal.Gui/Input/MouseEventEventArgs.cs b/Terminal.Gui/Input/MouseEventEventArgs.cs index 5e0990206e..89fc168a7e 100644 --- a/Terminal.Gui/Input/MouseEventEventArgs.cs +++ b/Terminal.Gui/Input/MouseEventEventArgs.cs @@ -13,7 +13,7 @@ public class MouseEventEventArgs : EventArgs /// /// Indicates if the current mouse event has already been processed and the driver should stop notifying any other - /// event subscriber. Its important to set this value to true specially when updating any View's layout from inside the + /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the /// subscriber method. /// /// diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index 52256c4181..89d0a6dea8 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -3,7 +3,7 @@ // ConfigurationManager. It is automatically loaded, and applied, each time Application.Init // is run (via the ConfiguraitonManager.Reset method). // - // In otherwords, initial values set in the the codebase are always overwritten by the contents of this + // In other words, initial values set in the the codebase are always overwritten by the contents of this // resource embedded in the Terminal.Gui.dll assembly. // // The Unit Test method "ConfigurationManagerTests.SaveDefaults" can be used to re-create the base of this file, but diff --git a/Terminal.Gui/StackExtensions.cs b/Terminal.Gui/StackExtensions.cs index bde15a404e..b3e7c01d8a 100644 --- a/Terminal.Gui/StackExtensions.cs +++ b/Terminal.Gui/StackExtensions.cs @@ -160,7 +160,7 @@ public static void MoveTo ( } } - /// Replaces an stack object values that match with the value to replace. + /// Replaces a stack object values that match with the value to replace. /// The stack object type. /// The stack object. /// Value to replace. diff --git a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs index 76d74f512d..f5f7190e62 100644 --- a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs @@ -105,7 +105,7 @@ public override void RenderOverlay (Point renderAt) return; } - // draw it like its selected even though its not + // draw it like it's selected, even though it's not Application.Driver.SetAttribute ( new Attribute ( ColorScheme.Normal.Foreground, diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs index b4264d74b7..93e32b12e8 100644 --- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs @@ -322,7 +322,7 @@ public override void RenderOverlay (Point renderAt) if (PopupInsideContainer) { - // don't overspill horizontally, let's see if can be displayed on the left + // don't overspill horizontally, let's see if it can be displayed on the left if (width > HostControl.Viewport.Width - renderAt.X) { // Verifies that the left limit available is greater than the right limit @@ -339,7 +339,7 @@ public override void RenderOverlay (Point renderAt) } else { - // don't overspill horizontally, let's see if can be displayed on the left + // don't overspill horizontally, let's see if it can be displayed on the left if (width > top.Viewport.Width - (renderAt.X + HostControl.Frame.X)) { // Verifies that the left limit available is greater than the right limit @@ -408,7 +408,7 @@ protected void Close () /// /// Called when the user confirms a selection at the current cursor location in the . The - /// string is the full autocomplete word to be inserted. Typically a host will have to + /// string is the full autocomplete word to be inserted. Typically, a host will have to /// remove some characters such that the string completes the word instead of simply being /// appended. /// diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index e3458afe95..994eeb007e 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1914,7 +1914,7 @@ public static int GetColumnsRequiredForVerticalText ( /// (uses ). if it contains newlines. /// /// - /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// Text, may contain newlines. @@ -1932,7 +1932,7 @@ public static int GetWidestLineLength (string text, int tabWidth = 0) /// . /// /// - /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The text. @@ -1957,7 +1957,7 @@ public static int GetSumMaxCharWidth (string text, int startIndex = -1, int leng /// Gets the number of the Runes in the text that will fit in . /// - /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The text. @@ -1972,7 +1972,7 @@ public static int GetLengthThatFits (string text, int width, int tabWidth = 0, T /// Gets the number of the Runes in a list of Runes that will fit in . /// - /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The list of runes. @@ -2034,7 +2034,7 @@ private static int GetRuneWidth (Rune rune, int tabWidth, TextDirection textDire /// Gets the index position from the list based on the . /// - /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The lines. @@ -2067,7 +2067,7 @@ public static int GetMaxColsForWidth (List lines, int width, int tabWidt /// Calculates the rectangle required to hold text, assuming no word wrapping or alignment. /// - /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The x location of the rectangle diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index aa4dd20661..076ec74cb5 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -74,7 +74,7 @@ public Border (View parent) : base (parent) public override void BeginInit () { #if HOVER - // TOOD: Hack - make Arragnement overidable + // TOOD: Hack - make Arrangement overridable if ((Parent?.Arrangement & ViewArrangement.Movable) != 0) { HighlightStyle |= HighlightStyle.Hover; diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 6a4cbcf02d..60e405e843 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -668,6 +668,7 @@ internal void SetRelativeLayout (Size superviewContentSize) /// behavior of this method is indeterminate if is . /// /// Raises the event before it returns. + /// Raises the event before it returns. /// public virtual void LayoutSubviews () { @@ -755,7 +756,7 @@ internal void OnResizeNeeded () // TODO: Identify a real-world use-case where this API should be virtual. // TODO: Until then leave it `internal` and non-virtual - // Determine our container's ContentSize - + // Determine our container's ContentSize - // First try SuperView.Viewport, then Application.Top, then Driver.Viewport. // Finally, if none of those are valid, use 2048 (for Unit tests). Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.GetContentSize () : diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 1e016e8ae0..68eb1853f3 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -268,7 +268,7 @@ public virtual void EndInit () EndInitAdornments (); - // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate. + // TODO: Move these into ViewText.cs as EndInit_Text() to consolidate. // TODO: Verify UpdateTextDirection really needs to be called here. // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called. UpdateTextDirection (TextDirection); diff --git a/Terminal.Gui/View/ViewEventArgs.cs b/Terminal.Gui/View/ViewEventArgs.cs index 4646746d6f..b17b98afe4 100644 --- a/Terminal.Gui/View/ViewEventArgs.cs +++ b/Terminal.Gui/View/ViewEventArgs.cs @@ -67,7 +67,7 @@ public FocusEventArgs (View leaving, View entering) { /// /// Indicates if the current focus event has already been processed and the driver should stop notifying any other - /// event subscriber. Its important to set this value to true specially when updating any View's layout from inside the + /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the /// subscriber method. /// public bool Handled { get; set; } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 127c3fac9e..d103d894d8 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -57,8 +57,7 @@ private void DisposeKeyboard () /// Gets or sets the hot key defined for this view. Pressing the hot key on the keyboard while this view has focus will /// invoke the and commands. /// causes the view to be focused and does nothing. By default, the HotKey is - /// automatically set to the first character of that is prefixed with with - /// . + /// automatically set to the first character of that is prefixed with . /// /// A HotKey is a keypress that selects a visible UI item. For selecting items across `s (e.g.a /// in a ) the keypress must include the @@ -741,11 +740,11 @@ private bool ProcessSubViewKeyBindings (Key keyEvent, KeyBindingScope scope, ref return false; } - // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful. - // TODO: A better approach would be have Application hold a list of bound Hotkeys, similar to + // TODO: This is a "prototype" debug check. It may be too annoying vs. useful. + // TODO: A better approach would be to have Application hold a list of bound Hotkeys, similar to // TODO: how Application holds a list of Application Scoped key bindings and then check that list. /// - /// Returns true if Key is bound in this view heirarchy. For debugging + /// Returns true if Key is bound in this view hierarchy. For debugging /// /// The key to test. /// Returns the view the key is bound to. @@ -808,8 +807,8 @@ public bool IsHotKeyKeyBound (Key key, out View boundView) Debug.WriteLine ($"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.{commandBinding.Commands [0]}: {boundView}."); } - // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful. - // Scour the bindings up our View heirarchy + // TODO: This is a "prototype" debug check. It may be too annoying vs. useful. + // Scour the bindings up our View hierarchy // to ensure that the key is not already bound to a different set of commands. if (SuperView?.IsHotKeyKeyBound (key, out View previouslyBoundView) ?? false) { diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index b98cceb7ea..e4e71c97df 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -118,10 +118,10 @@ public bool HideDropdownListOnClick set => _hideDropdownListOnClick = _listview.HideDropdownListOnClick = value; } - /// Gets the drop down list state, expanded or collapsed. + /// Gets the drop-down list state, expanded or collapsed. public bool IsShow { get; private set; } - /// If set to true its not allow any changes in the text. + /// If set to true, no changes to the text will be allowed. public bool ReadOnly { get => _search.ReadOnly; @@ -200,7 +200,7 @@ public IListDataSource Source } /// - /// Collapses the drop down list. Returns true if the state chagned or false if it was already collapsed and no + /// Collapses the drop-down list. Returns true if the state changed or false if it was already collapsed and no /// action was taken /// public virtual bool Collapse () @@ -220,7 +220,7 @@ public virtual bool Collapse () public event EventHandler Collapsed; /// - /// Expands the drop down list. Returns true if the state chagned or false if it was already expanded and no + /// Expands the drop-down list. Returns true if the state changed or false if it was already expanded and no /// action was taken /// public virtual bool Expand () @@ -398,7 +398,7 @@ private bool ActivateSelected () /// Internal height of dynamic search list /// - private int CalculatetHeight () + private int CalculateHeight () { if (!IsInitialized || Viewport.Height == 0) { @@ -617,7 +617,7 @@ private void ProcessLayout () || (_autoHide && Viewport.Width > 0 && _search.Frame.Width != Viewport.Width - 1)) { _search.Width = _listview.Width = _autoHide ? Viewport.Width - 1 : Viewport.Width; - _listview.Height = CalculatetHeight (); + _listview.Height = CalculateHeight (); _search.SetRelativeLayout (GetContentSize ()); _listview.SetRelativeLayout (GetContentSize ()); } @@ -634,7 +634,7 @@ private void Reset (bool keepSearchText = false) ResetSearchSet (); _listview.SetSource (_searchSet); - _listview.Height = CalculatetHeight (); + _listview.Height = CalculateHeight (); if (Subviews.Count > 0 && HasFocus) { @@ -769,7 +769,7 @@ private void SetSearchText (string value) private void SetValue (object text, bool isFromSelectedItem = false) { - // TOOD: THe fact we have to suspend events to change the text makes this feel very hacky. + // TOOD: The fact we have to suspend events to change the text makes this feel very hacky. _search.TextChanged -= Search_Changed; // Note we set _text, to avoid set_Text from setting _search.Text again _text = _search.Text = text.ToString (); @@ -792,7 +792,7 @@ private void ShowList () _listview.ResumeSuspendCollectionChangedEvent (); _listview.Clear (); - _listview.Height = CalculatetHeight (); + _listview.Height = CalculateHeight (); SuperView?.BringSubviewToFront (this); } diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 7677ba8631..9627b25589 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -201,10 +201,10 @@ private void DateField_Changing (object sender, CancelEventArgs e) } spaces += FormatLength; - string trimedText = e.NewValue [..spaces]; + string trimmedText = e.NewValue [..spaces]; spaces -= FormatLength; - trimedText = trimedText.Replace (new string (' ', spaces), " "); - var date = Convert.ToDateTime (trimedText).ToString (_format.Trim ()); + trimmedText = trimmedText.Replace (new string (' ', spaces), " "); + var date = Convert.ToDateTime (trimmedText).ToString (_format.Trim ()); if ($" {date}" != e.NewValue) { @@ -542,8 +542,8 @@ private bool SetText (string text) return true; } - // Converts various date formats to a uniform 10-character format. - // This aids in simplifying the handling of single-digit months and days, + // Converts various date formats to a uniform 10-character format. + // This aids in simplifying the handling of single-digit months and days, // and reduces the number of distinct date formats to maintain. private static string StandardizeDateFormat (string format) { diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index fcdd2d6d27..ad478f6ddc 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -590,7 +590,7 @@ internal void ApplySort () { FileSystemInfoStats [] stats = State?.Children ?? new FileSystemInfoStats[0]; - // This portion is never reordered (aways .. at top then folders) + // This portion is never reordered (always .. at top then folders) IOrderedEnumerable forcedOrder = stats .OrderByDescending (f => f.IsParent) .ThenBy (f => f.IsDir ? -1 : 100); @@ -670,7 +670,7 @@ internal void SortColumn (int col, bool isAsc) return; } - // Don't include ".." (IsParent) in multiselections + // Don't include ".." (IsParent) in multi-selections MultiSelected = toMultiAccept .Where (s => !s.IsParent) .Select (s => s.FileSystemInfo.FullName) diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs index 7e7561b76d..efff79ce97 100644 --- a/Terminal.Gui/Views/GraphView/Axis.cs +++ b/Terminal.Gui/Views/GraphView/Axis.cs @@ -80,7 +80,7 @@ public virtual void Reset () private string DefaultLabelGetter (AxisIncrementToRender toRender) { return toRender.Value.ToString ("N0"); } } -/// The horizontal (x axis) of a +/// The horizontal (x-axis) of a public class HorizontalAxis : Axis { /// @@ -132,7 +132,7 @@ public override void DrawAxisLabel (GraphView graph, int screenPosition, string } } - /// Draws the horizontal x axis labels and ticks + /// Draws the horizontal x-axis labels and ticks public override void DrawAxisLabels (GraphView graph) { if (!Visible || Increment == 0) @@ -180,10 +180,10 @@ public override void DrawAxisLine (GraphView graph) int y = GetAxisYPosition (graph); - // start the x axis at left of screen (either 0 or margin) + // start the x-axis at left of screen (either 0 or margin) var xStart = (int)graph.MarginLeft; - // but if the x axis has a minmum (minimum is in graph space units) + // but if the x-axis has a minimum (minimum is in graph space units) if (Minimum.HasValue) { // start at the screen location of the minimum @@ -257,7 +257,7 @@ private IEnumerable GetLabels (GraphView graph, Rectangle // Not every increment has to have a label if (ShowLabelsEvery != 0) { - // if this increment does also needs a label + // if this increment also needs a label if (labels++ % ShowLabelsEvery == 0) { toRender.Text = LabelGetter (toRender); @@ -403,7 +403,7 @@ private int GetAxisYEnd (GraphView graph) // draw down the screen (0 is top of screen) // end at the bottom of the screen - //unless there is a minimum + //unless there is a minimum if (Minimum.HasValue) { return graph.GraphSpaceToScreen (new PointF (0, Minimum.Value)).Y; diff --git a/Terminal.Gui/Views/GraphView/Series.cs b/Terminal.Gui/Views/GraphView/Series.cs index c117bd0a24..f0974556c6 100644 --- a/Terminal.Gui/Views/GraphView/Series.cs +++ b/Terminal.Gui/Views/GraphView/Series.cs @@ -51,7 +51,7 @@ public class MultiBarSeries : ISeries /// Creates a new series of clustered bars. /// Each category has this many bars - /// How far appart to put each category (in graph space) + /// How far apart to put each category (in graph space) /// /// How much spacing between bars in a category (should be less than / /// ) @@ -97,7 +97,7 @@ public MultiBarSeries (int numberOfBarsPerCategory, float barsEvery, float spaci /// /// Sub collections. Each series contains the bars for a different category. Thus SubSeries[0].Bars[0] is the - /// first bar on the axis and SubSeries[1].Bars[0] is the second etc + /// first bar on the axis and SubSeries[1].Bars[0] is the second etc. /// public IReadOnlyCollection SubSeries => new ReadOnlyCollection (subSeries); @@ -191,7 +191,7 @@ public virtual void DrawSeries (GraphView graph, Rectangle drawBounds, Rectangle { screenStart.X = graph.AxisY.GetAxisXPosition (graph); - // dont draw bar off the right of the control + // don't draw bar off the right of the control screenEnd.X = Math.Min (graph.Viewport.Width - 1, screenEnd.X); // if bar is off the screen @@ -205,7 +205,7 @@ public virtual void DrawSeries (GraphView graph, Rectangle drawBounds, Rectangle // Start the axis screenStart.Y = graph.AxisX.GetAxisYPosition (graph); - // dont draw bar up above top of control + // don't draw bar up above top of control screenEnd.Y = Math.Max (0, screenEnd.Y); // if bar is off the screen diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index 40e4e90304..e96eceb17d 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -130,7 +130,7 @@ public Point CursorPosition } /// - /// Sets or gets the offset into the that will displayed at the top of the + /// Sets or gets the offset into the that will be displayed at the top of the /// /// /// The display start. @@ -557,9 +557,9 @@ internal void SetDisplayStart (long value) } // - // This is used to support editing of the buffer on a peer List<>, + // This is used to support editing of the buffer on a peer List<>, // the offset corresponds to an offset relative to DisplayStart, and - // the buffer contains the contents of a screenful of data, so the + // the buffer contains the contents of a screenful of data, so the // offset is relative to the buffer. // // diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index 226af1f917..e2b2127ace 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/Terminal.Gui/Views/Menu/ContextMenu.cs @@ -18,7 +18,7 @@ /// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling /// . /// -/// ContextMenus are located using screen using screen coordinates and appear above all other Views. +/// ContextMenus are located using screen coordinates and appear above all other Views. /// public sealed class ContextMenu : IDisposable { diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 2da69bc01b..34e8dab30d 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -282,7 +282,7 @@ private bool ExpandCollapse (MenuItem menuItem) return true; } - // TODO: Determine if there's a cleaner way to handle this + // TODO: Determine if there's a cleaner way to handle this. // This supports the case where the menu bar is a context menu return _host.OnInvokingKeyBindings (keyEvent, scope); } diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 00247a6131..530b2ba9b8 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -317,7 +317,7 @@ public override void OnDrawContent (Rectangle viewport) /// Virtual method that will invoke the . /// The current menu to be closed. - /// Whether the current menu will be reopen. + /// Whether the current menu will be reopened. /// Whether is a sub-menu or not. public virtual MenuClosingEventArgs OnMenuClosing (MenuBarItem currentMenu, bool reopen, bool isSubMenu) { diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 97fcf94527..5d583250b0 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -434,7 +434,7 @@ params string [] buttons } } - // Run the modal; do not shutdown the mainloop driver when done + // Run the modal; do not shut down the mainloop driver when done Application.Run (d); d.Dispose (); diff --git a/Terminal.Gui/Views/SelectedItemChangedArgs.cs b/Terminal.Gui/Views/SelectedItemChangedArgs.cs index 10c1a9ca37..a2f5eb47cb 100644 --- a/Terminal.Gui/Views/SelectedItemChangedArgs.cs +++ b/Terminal.Gui/Views/SelectedItemChangedArgs.cs @@ -1,6 +1,6 @@ namespace Terminal.Gui; -/// Event arguments for the SelectedItemChagned event. +/// Event arguments for the SelectedItemChanged event. public class SelectedItemChangedArgs : EventArgs { /// Initializes a new class. diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 1b96bda0db..61ac401d6e 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -396,7 +396,7 @@ public virtual bool OnOptionFocused (int newFocusedOption, SliderEventArgs ar /// Causes the specified option to be set and be focused. public bool SetOption (int optionIndex) { - // TODO: Handle range type. + // TODO: Handle range type. // Note: Maybe return false only when optionIndex doesn't exist, otherwise true. if (!_setOptions.Contains (optionIndex) && optionIndex >= 0 && optionIndex < _options.Count) @@ -1285,9 +1285,9 @@ private void DrawLegends () protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { // Note(jmperricone): Maybe we click to focus the cursor, and on next click we set the option. - // That will makes OptionFocused Event more relevant. + // That will make OptionFocused Event more relevant. // (tig: I don't think so. Maybe an option if someone really wants it, but for now that - // adss to much friction to UI. + // adds too much friction to UI. // TODO(jmperricone): Make Range Type work with mouse. if (!(mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked) @@ -1325,7 +1325,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) var success = false; var option = 0; - // how far has user dragged from original location? + // how far has user dragged from original location? if (Orientation == Orientation.Horizontal) { success = TryGetOptionByPosition (mouseEvent.Position.X, 0, Math.Max (0, _config._cachedInnerSpacing / 2), out option); diff --git a/Terminal.Gui/Views/SliderOption.cs b/Terminal.Gui/Views/SliderOption.cs index 1cfcc1f07f..a3d10781dd 100644 --- a/Terminal.Gui/Views/SliderOption.cs +++ b/Terminal.Gui/Views/SliderOption.cs @@ -15,7 +15,7 @@ public SliderOption (string legend, Rune legendAbbr, T data) Data = data; } - /// Event fired when the an option has changed. + /// Event fired when an option has changed. public event EventHandler Changed; /// Custom data of the option. diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs index a5288f70a1..3fe2d0a680 100644 --- a/Terminal.Gui/Views/Tab.cs +++ b/Terminal.Gui/Views/Tab.cs @@ -5,7 +5,7 @@ public class Tab : View { private string _displayText; - /// Creates a new unamed tab with no controls inside. + /// Creates a new unnamed tab with no controls inside. public Tab () { BorderStyle = LineStyle.Rounded; @@ -16,7 +16,7 @@ public Tab () /// public string DisplayText { - get => _displayText ?? "Unamed"; + get => _displayText ?? "Unnamed"; set => _displayText = value; } diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index 095b735826..04f1773c10 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -1296,7 +1296,7 @@ private void RenderTabLine () { if (_host.Focused == this) { - // if focus is the tab bar ourself then show that they can switch tabs + // if focus is the tab bar itself then show that they can switch tabs prevAttr = ColorScheme.HotFocus; } else diff --git a/Terminal.Gui/Views/TableView/EnumerableTableSource.cs b/Terminal.Gui/Views/TableView/EnumerableTableSource.cs index 251a5afebf..327cdaf5cf 100644 --- a/Terminal.Gui/Views/TableView/EnumerableTableSource.cs +++ b/Terminal.Gui/Views/TableView/EnumerableTableSource.cs @@ -5,7 +5,7 @@ public class EnumerableTableSource : IEnumerableTableSource { private readonly T [] data; - private readonly Dictionary> lamdas; + private readonly Dictionary> lambdas; /// Creates a new instance of the class that presents collection as a table. /// @@ -29,14 +29,14 @@ public EnumerableTableSource (IEnumerable data, DictionaryGets the object collection hosted by this wrapper. public IReadOnlyCollection Data => data.AsReadOnly (); /// - public object this [int row, int col] => lamdas [ColumnNames [col]] (data [row]); + public object this [int row, int col] => lambdas [ColumnNames [col]] (data [row]); /// public int Rows => data.Length; diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 7081106a45..0807991d50 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -1438,13 +1438,13 @@ private IEnumerable CalculateViewport (Rectangle bounds, int pad // is there enough space to meet the MinAcceptableWidth availableHorizontalSpace - usedSpace >= colStyle.MinAcceptableWidth) { - // show column and use use whatever space is + // show column and use whatever space is // left for rendering it showColumn = true; colWidth = availableHorizontalSpace - usedSpace; } - // If its the only column we are able to render then + // If it's the only column we are able to render then // accept it anyway (that must be one massively wide column!) if (first) { @@ -1674,7 +1674,7 @@ private void RenderBottomLine (int row, int availableWidth, ColumnToRender [] co } else if (Style.ExpandLastColumn == false && columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - // if the next console column is the lastcolumns end + // if the next console column is the last column's end rune = Glyphs.BottomTee; } } @@ -1748,7 +1748,7 @@ private void RenderHeaderOverline (int row, int availableWidth, ColumnToRender [ rune = Glyphs.URCorner; } - // if the next console column is the lastcolumns end + // if the next console column is the last column's end else if (Style.ExpandLastColumn == false && columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { rune = Glyphs.TopTee; @@ -1841,7 +1841,7 @@ private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender } } - // if the next console column is the lastcolumns end + // if the next console column is the last column's end else if (Style.ExpandLastColumn == false && columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { rune = Style.ShowVerticalCellLines ? Glyphs.Cross : Glyphs.BottomTee; diff --git a/Terminal.Gui/Views/TableView/TreeTableSource.cs b/Terminal.Gui/Views/TableView/TreeTableSource.cs index 5256949c9a..f2cab65092 100644 --- a/Terminal.Gui/Views/TableView/TreeTableSource.cs +++ b/Terminal.Gui/Views/TableView/TreeTableSource.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui; /// public class TreeTableSource : IEnumerableTableSource, IDisposable where T : class { - private readonly Dictionary> _lamdas; + private readonly Dictionary> _lambdas; private readonly TableView _tableView; private readonly TreeView _tree; @@ -47,7 +47,7 @@ Dictionary> subsequentColumns ColumnNames = colList.ToArray (); - _lamdas = subsequentColumns; + _lambdas = subsequentColumns; } /// @@ -60,13 +60,13 @@ public void Dispose () /// public object this [int row, int col] => - col == 0 ? GetColumnZeroRepresentationFromTree (row) : _lamdas [ColumnNames [col]] (RowToObject (row)); + col == 0 ? GetColumnZeroRepresentationFromTree (row) : _lambdas [ColumnNames [col]] (RowToObject (row)); /// public int Rows => _tree.BuildLineMap ().Count; /// - public int Columns => _lamdas.Count + 1; + public int Columns => _lambdas.Count + 1; /// public string [] ColumnNames { get; } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index a4e220966b..bdcb92dd5b 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1105,7 +1105,7 @@ public override bool OnProcessKeyDown (Key a) } /// Virtual method that invoke the event if it's defined. - /// The event arguments.. + /// The event arguments. /// if the event was cancelled. public bool OnTextChanging (CancelEventArgs args) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 62cce0b878..bea620aeeb 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -334,7 +334,7 @@ void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) { lastValidCol = nCol; - if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknow) + if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknown) { runeType = GetRuneType (nRune); } @@ -1063,7 +1063,7 @@ private RuneType GetRuneType (Rune rune) return RuneType.IsPunctuation; } - return RuneType.IsUnknow; + return RuneType.IsUnknown; } private bool IsSameRuneType (Rune newRune, RuneType runeType) @@ -1255,7 +1255,7 @@ private enum RuneType IsWhiteSpace, IsLetterOrDigit, IsPunctuation, - IsUnknow + IsUnknown } } @@ -1772,8 +1772,8 @@ public TextModel WrapModel ( nCol = 0; nStartRow = 0; nStartCol = 0; - bool isRowAndColSetted = row == 0 && col == 0; - bool isStartRowAndColSetted = startRow == 0 && startCol == 0; + bool isRowAndColSet = row == 0 && col == 0; + bool isStartRowAndColSet = startRow == 0 && startCol == 0; List wModelLines = new (); for (var i = 0; i < Model.Count; i++) @@ -1796,7 +1796,7 @@ public TextModel WrapModel ( { List wrapLine = wrappedLines [j]; - if (!isRowAndColSetted && modelRow == i) + if (!isRowAndColSet && modelRow == i) { if (nCol + wrapLine.Count <= modelCol) { @@ -1806,12 +1806,12 @@ public TextModel WrapModel ( if (nCol == modelCol) { nCol = wrapLine.Count; - isRowAndColSetted = true; + isRowAndColSet = true; } else if (j == wrappedLines.Count - 1) { nCol = wrapLine.Count - j + modelCol - nCol; - isRowAndColSetted = true; + isRowAndColSet = true; } } else @@ -1819,11 +1819,11 @@ public TextModel WrapModel ( int offset = nCol + wrapLine.Count - modelCol; nCol = wrapLine.Count - offset; nRow = lines; - isRowAndColSetted = true; + isRowAndColSet = true; } } - if (!isStartRowAndColSetted && modelStartRow == i) + if (!isStartRowAndColSet && modelStartRow == i) { if (nStartCol + wrapLine.Count <= modelStartCol) { @@ -1833,12 +1833,12 @@ public TextModel WrapModel ( if (nStartCol == modelStartCol) { nStartCol = wrapLine.Count; - isStartRowAndColSetted = true; + isStartRowAndColSet = true; } else if (j == wrappedLines.Count - 1) { nStartCol = wrapLine.Count - j + modelStartCol - nStartCol; - isStartRowAndColSetted = true; + isStartRowAndColSet = true; } } else @@ -1846,7 +1846,7 @@ public TextModel WrapModel ( int offset = nStartCol + wrapLine.Count - modelStartCol; nStartCol = wrapLine.Count - offset; nStartRow = lines; - isStartRowAndColSetted = true; + isStartRowAndColSet = true; } } @@ -2547,14 +2547,14 @@ public bool AllowsReturn if (_allowsReturn && !_multiline) { - // BUGBUG: Seting properties should not have side-effects like this. Multiline and AllowsReturn should be independent. + // BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsReturn should be independent. Multiline = true; } if (!_allowsReturn && _multiline) { Multiline = false; - // BUGBUG: Seting properties should not have side-effects like this. Multiline and AlowsTab should be independent. + // BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsTab should be independent. AllowsTab = false; } diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 78ffd9b5ff..54ec8a4d2a 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -935,7 +935,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { // Continue Drag - // how far has user dragged from original location? + // how far has user dragged from original location? if (Orientation == Orientation.Horizontal) { int dy = mouseEvent.Position.Y - dragPosition.Value.Y; diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index b7aad9fec9..5f303cc98b 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -449,13 +449,13 @@ private void TextField_TextChanging (object sender, CancelEventArgs e) } spaces += FieldLength; - string trimedText = e.NewValue [..spaces]; + string trimmedText = e.NewValue [..spaces]; spaces -= FieldLength; - trimedText = trimedText.Replace (new string (' ', spaces), " "); + trimmedText = trimmedText.Replace (new string (' ', spaces), " "); - if (trimedText != e.NewValue) + if (trimmedText != e.NewValue) { - e.NewValue = trimedText; + e.NewValue = trimmedText; } if (!TimeSpan.TryParseExact ( diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index 7619a815fd..0b37314cdd 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -93,7 +93,7 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, tree.Move (0, y); - // if we have scrolled to the right then bits of the prefix will have dispeared off the screen + // if we have scrolled to the right then bits of the prefix will have disappeared off the screen int toSkip = tree.ScrollOffsetHorizontal; Attribute attr = symbolColor; @@ -329,7 +329,7 @@ public void Refresh (bool startAtTop) Parent?.Refresh (true); } - // we don't want to loose the state of our children so lets be selective about how we refresh + // we don't want to lose the state of our children so lets be selective about how we refresh //if we don't know about any children yet just use the normal method if (ChildBranches is null) { @@ -347,7 +347,7 @@ public void Refresh (bool startAtTop) { ChildBranches.Remove (toRemove); - //also if the user has this node selected (its disapearing) so lets change selection to us (the parent object) to be helpful + //also if the user has this node selected (its disappearing) so lets change selection to us (the parent object) to be helpful if (Equals (tree.SelectedObject, toRemove)) { tree.SelectedObject = Model; @@ -357,14 +357,14 @@ public void Refresh (bool startAtTop) // New children need to be added foreach (T newChild in newChildren) { - // If we don't know about the child yet we need a new branch + // If we don't know about the child, yet we need a new branch if (!ChildBranches.ContainsKey (newChild)) { ChildBranches.Add (newChild, new Branch (tree, this, newChild)); } else { - //we already have this object but update the reference anyway incase Equality match but the references are new + //we already have this object but update the reference anyway in case Equality match but the references are new ChildBranches [newChild].Model = newChild; } } @@ -486,7 +486,7 @@ internal void Rebuild () { if (IsExpanded) { - //if we are expanded we need to updatethe visible children + // if we are expanded we need to update the visible children foreach (KeyValuePair> child in ChildBranches) { child.Value.Rebuild (); @@ -515,7 +515,7 @@ private IEnumerable> GetParentBranches () } /// - /// Returns true if this branch has parents and it is the last node of it's parents branches (or last root of the + /// Returns true if this branch has parents, and it is the last node of its parents branches (or last root of the /// tree). /// /// diff --git a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs index f0a9e5e7e5..1454abff7e 100644 --- a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs +++ b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs @@ -19,7 +19,7 @@ public class DrawTreeViewLineEventArgs where T : class /// /// The notional index in which contains the first character of the - /// text (i.e. after all branch lines and expansion/collapse sybmols). + /// text (i.e. after all branch lines and expansion/collapse symbols). /// /// May be negative or outside of bounds of if the view has been scrolled horizontally. public int IndexOfModelText { get; init; } diff --git a/Terminal.Gui/Views/TreeView/TreeNode.cs b/Terminal.Gui/Views/TreeView/TreeNode.cs index e996beb507..351e616208 100644 --- a/Terminal.Gui/Views/TreeView/TreeNode.cs +++ b/Terminal.Gui/Views/TreeView/TreeNode.cs @@ -1,7 +1,7 @@ namespace Terminal.Gui; /// -/// Interface to implement when you want the regular (non generic) to automatically +/// Interface to implement when you want the regular (non-generic) to automatically /// determine children for your class (without having to specify an ) /// public interface ITreeNode @@ -17,7 +17,7 @@ public interface ITreeNode string Text { get; set; } } -/// Simple class for representing nodes, use with regular (non generic) . +/// Simple class for representing nodes, use with regular (non-generic) . public class TreeNode : ITreeNode { /// Initialises a new instance with no @@ -39,5 +39,5 @@ public TreeNode () { } /// returns /// - public override string ToString () { return Text ?? "Unamed Node"; } + public override string ToString () { return Text ?? "Unnamed Node"; } } diff --git a/Terminal.Gui/Views/TreeView/TreeStyle.cs b/Terminal.Gui/Views/TreeView/TreeStyle.cs index df8772dbd3..0b560f2a9e 100644 --- a/Terminal.Gui/Views/TreeView/TreeStyle.cs +++ b/Terminal.Gui/Views/TreeView/TreeStyle.cs @@ -29,7 +29,7 @@ public class TreeStyle public bool InvertExpandSymbolColors { get; set; } /// - /// to leave the last row of the control free for overwritting (e.g. by a scrollbar) When + /// to leave the last row of the control free for overwriting (e.g. by a scrollbar) When /// scrolling will be triggered on the second last row of the control rather than. the last. /// /// diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index d2e3d02938..f2039d9c6e 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -35,7 +35,7 @@ public class TreeView : TreeView public TreeView () { TreeBuilder = new TreeNodeBuilder (); - AspectGetter = o => o is null ? "Null" : o.Text ?? o?.ToString () ?? "Unamed Node"; + AspectGetter = o => o is null ? "Null" : o.Text ?? o?.ToString () ?? "Unnamed Node"; } } @@ -72,7 +72,7 @@ public class TreeView : View, ITreeView where T : class private T selectedObject; /// - /// Creates a new tree view with absolute positioning. Use to set set + /// Creates a new tree view with absolute positioning. Use to set /// root objects for the tree. Children will not be rendered until you set . /// public TreeView () @@ -299,7 +299,7 @@ public TreeView () /// /// Initialises .Creates a new tree view with absolute positioning. Use - /// to set set root objects for the tree. + /// to set root objects for the tree. /// public TreeView (ITreeBuilder builder) : this () { TreeBuilder = builder; } @@ -315,7 +315,7 @@ public TreeView () public AspectGetterDelegate AspectGetter { get; set; } = o => o.ToString () ?? ""; /// - /// Delegate for multi colored tree views. Return the to use for each passed object or + /// Delegate for multi-colored tree views. Return the to use for each passed object or /// null to use the default. /// public Func ColorGetter { get; set; } @@ -374,7 +374,7 @@ public int ScrollOffsetHorizontal /// The amount of tree view that has been scrolled off the top of the screen (by the user scrolling down). /// - /// Setting a value of less than 0 will result in a offset of 0. To see changes in the UI call + /// Setting a value of less than 0 will result in an offset of 0. To see changes in the UI call /// . /// public int ScrollOffsetVertical @@ -402,7 +402,7 @@ public T SelectedObject } } - /// Determines how sub branches of the tree are dynamically built at runtime as the user expands root nodes. + /// Determines how sub-branches of the tree are dynamically built at runtime as the user expands root nodes. /// public ITreeBuilder TreeBuilder { get; set; } @@ -500,7 +500,7 @@ public void AddObjects (IEnumerable collection) /// public void AdjustSelection (int offset, bool expandSelection = false) { - // if it is not a shift click or we don't allow multi select + // if it is not a shift click, or we don't allow multi select if (!expandSelection || !MultiSelect) { multiSelectedRegions.Clear (); @@ -518,7 +518,7 @@ public void AdjustSelection (int offset, bool expandSelection = false) if (idx == -1) { - // The current selection has disapeared! + // The current selection has disappeared! SelectedObject = roots.Keys.FirstOrDefault (); } else @@ -1090,7 +1090,7 @@ protected internal override bool OnMouseEvent (MouseEvent me) SelectedObject = clickedBranch.Model; SetNeedsDisplay (); - // trigger activation event + // trigger activation event OnObjectActivated (new ObjectActivatedEventArgs (this, clickedBranch.Model)); // mouse event is handled. @@ -1324,7 +1324,7 @@ public void SelectAll () /// /// Implementation of and . Performs operation and updates - /// selection if disapeared. + /// selection if disappeared. /// /// /// diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index 75377c8b4b..70aeeeb5f7 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -195,7 +195,7 @@ public void AddStep (WizardStep newStep) } /// - /// Raised when the user has cancelled the by pressin the Esc key. To prevent a modal ( + /// Raised when the user has cancelled the by pressing the Esc key. To prevent a modal ( /// is true) Wizard from closing, cancel the event by setting /// to true before returning from the event handler. /// @@ -302,7 +302,7 @@ public WizardStep GetPreviousStep () } /// - /// Causes the wizad to move to the previous enabled step (or first step if is not set). + /// Causes the wizard to move to the previous enabled step (or first step if is not set). /// If there is no previous step, does nothing. /// public void GoBack () @@ -316,7 +316,7 @@ public void GoBack () } /// - /// Causes the wizad to move to the next enabled step (or last step if is not set). If + /// Causes the wizard to move to the next enabled step (or last step if is not set). If /// there is no previous step, does nothing. /// public void GoNext () @@ -388,7 +388,7 @@ public bool GoToStep (WizardStep newStep) /// is derived from and Dialog causes Esc to call /// , closing the Dialog. Wizard overrides /// to instead fire the event when Wizard is being used as a - /// non-modal (see . + /// non-modal (see ). /// /// /// diff --git a/UnitTests/Views/TabTests.cs b/UnitTests/Views/TabTests.cs index 1e7ff7bab4..a4de58bb0c 100644 --- a/UnitTests/Views/TabTests.cs +++ b/UnitTests/Views/TabTests.cs @@ -6,7 +6,7 @@ public class TabTests public void Constructor_Defaults () { var tab = new Tab (); - Assert.Equal ("Unamed", tab.DisplayText); + Assert.Equal ("Unnamed", tab.DisplayText); Assert.Null (tab.View); Assert.Equal (LineStyle.Rounded, tab.BorderStyle); Assert.True (tab.CanFocus); From 6b5bcced8d9964fc20e6b99e507ad7e7e7ef0e20 Mon Sep 17 00:00:00 2001 From: Cam Sinclair Date: Fri, 19 Jul 2024 13:34:08 +1000 Subject: [PATCH 38/96] Fix typo in config.json --- Terminal.Gui/Resources/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index 89d0a6dea8..e3c1ac3dd8 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -1,7 +1,7 @@ { // Specifies the "source of truth" for default values for all Terminal.Gui settings managed by // ConfigurationManager. It is automatically loaded, and applied, each time Application.Init - // is run (via the ConfiguraitonManager.Reset method). + // is run (via the ConfigurationManager.Reset method). // // In other words, initial values set in the the codebase are always overwritten by the contents of this // resource embedded in the Terminal.Gui.dll assembly. From 24e5fbd196f8552c0e35af8694160771214566cb Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 17:32:43 -0600 Subject: [PATCH 39/96] Revert "Fix typo in config.json" This reverts commit 6b5bcced8d9964fc20e6b99e507ad7e7e7ef0e20. --- Terminal.Gui/Resources/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index e3c1ac3dd8..89d0a6dea8 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -1,7 +1,7 @@ { // Specifies the "source of truth" for default values for all Terminal.Gui settings managed by // ConfigurationManager. It is automatically loaded, and applied, each time Application.Init - // is run (via the ConfigurationManager.Reset method). + // is run (via the ConfiguraitonManager.Reset method). // // In other words, initial values set in the the codebase are always overwritten by the contents of this // resource embedded in the Terminal.Gui.dll assembly. From fa847f435661a561fe98279710509a8eaaa60a19 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 19 Jul 2024 17:32:53 -0600 Subject: [PATCH 40/96] Revert "rebased against v2_develop" This reverts commit 23737decec34618e7bfa407a2901cbbee0c4ee36. --- Terminal.Gui/Application/Application.cs | 24 +++++++-------- Terminal.Gui/Clipboard/IClipboard.cs | 8 ++--- .../Configuration/ConfigurationManager.cs | 2 +- .../ConfigurationManagerEventArgs.cs | 2 +- .../SerializableConfigurationProperty.cs | 2 +- Terminal.Gui/Configuration/ThemeManager.cs | 2 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 10 +++---- .../ConsoleDrivers/ConsoleKeyMapping.cs | 2 +- .../CursesDriver/ClipboardImpl.cs | 2 +- .../CursesDriver/CursesDriver.cs | 4 +-- .../CursesDriver/UnmanagedLibrary.cs | 2 +- .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 2 +- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 8 ++--- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 4 +-- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2 +- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 14 ++++----- Terminal.Gui/Drawing/ColorScheme.cs | 2 +- Terminal.Gui/Drawing/Glyphs.cs | 2 +- Terminal.Gui/Drawing/LineCanvas.cs | 2 +- Terminal.Gui/Drawing/LineStyle.cs | 2 +- Terminal.Gui/FileServices/AllowedType.cs | 4 +-- Terminal.Gui/FileServices/FileDialogState.cs | 2 +- Terminal.Gui/FileServices/FileDialogStyle.cs | 8 ++--- Terminal.Gui/Input/Command.cs | 8 ++--- Terminal.Gui/Input/Key.cs | 2 +- Terminal.Gui/Input/MouseEventEventArgs.cs | 2 +- Terminal.Gui/Resources/config.json | 2 +- Terminal.Gui/StackExtensions.cs | 2 +- .../Text/Autocomplete/AppendAutocomplete.cs | 2 +- .../Text/Autocomplete/PopupAutocomplete.cs | 6 ++-- Terminal.Gui/Text/TextFormatter.cs | 12 ++++---- Terminal.Gui/View/Adornment/Border.cs | 2 +- Terminal.Gui/View/Layout/ViewLayout.cs | 3 +- Terminal.Gui/View/View.cs | 2 +- Terminal.Gui/View/ViewEventArgs.cs | 2 +- Terminal.Gui/View/ViewKeyboard.cs | 13 ++++---- Terminal.Gui/Views/ComboBox.cs | 18 +++++------ Terminal.Gui/Views/DateField.cs | 10 +++---- Terminal.Gui/Views/FileDialog.cs | 4 +-- Terminal.Gui/Views/GraphView/Axis.cs | 12 ++++---- Terminal.Gui/Views/GraphView/Series.cs | 8 ++--- Terminal.Gui/Views/HexView.cs | 6 ++-- Terminal.Gui/Views/Menu/ContextMenu.cs | 2 +- Terminal.Gui/Views/Menu/Menu.cs | 2 +- Terminal.Gui/Views/Menu/MenuBar.cs | 2 +- Terminal.Gui/Views/MessageBox.cs | 2 +- Terminal.Gui/Views/SelectedItemChangedArgs.cs | 2 +- Terminal.Gui/Views/Slider.cs | 8 ++--- Terminal.Gui/Views/SliderOption.cs | 2 +- Terminal.Gui/Views/Tab.cs | 4 +-- Terminal.Gui/Views/TabView.cs | 2 +- .../Views/TableView/EnumerableTableSource.cs | 6 ++-- Terminal.Gui/Views/TableView/TableView.cs | 10 +++---- .../Views/TableView/TreeTableSource.cs | 8 ++--- Terminal.Gui/Views/TextField.cs | 2 +- Terminal.Gui/Views/TextView.cs | 30 +++++++++---------- Terminal.Gui/Views/TileView.cs | 2 +- Terminal.Gui/Views/TimeField.cs | 8 ++--- Terminal.Gui/Views/TreeView/Branch.cs | 14 ++++----- .../TreeView/DrawTreeViewLineEventArgs.cs | 2 +- Terminal.Gui/Views/TreeView/TreeNode.cs | 6 ++-- Terminal.Gui/Views/TreeView/TreeStyle.cs | 2 +- Terminal.Gui/Views/TreeView/TreeView.cs | 20 ++++++------- Terminal.Gui/Views/Wizard/Wizard.cs | 8 ++--- UnitTests/Views/TabTests.cs | 2 +- 65 files changed, 187 insertions(+), 187 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 752b8b90db..9833d19cd8 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -165,7 +165,7 @@ internal static void ResetState (bool ignoreDisposed = false) Colors.Reset (); // Reset synchronization context to allow the user to run async/await, - // as the main loop has been ended, the synchronization context from + // as the main loop has been ended, the synchronization context from // gui.cs does no longer process any callbacks. See #1084 for more details: // (https://github.com/gui-cs/Terminal.Gui/issues/1084). SynchronizationContext.SetSynchronizationContext (null); @@ -211,11 +211,11 @@ internal static void ResetState (bool ignoreDisposed = false) // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. // // Called from: - // + // // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset. // Run() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run() to be called without calling Init first. // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset. - // + // // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] @@ -250,7 +250,7 @@ internal static void InternalInit ( // Start the process of configuration management. // Note that we end up calling LoadConfigurationFromAllSources // multiple times. We need to do this because some settings are only - // valid after a Driver is loaded. In this case we need just + // valid after a Driver is loaded. In this cases we need just // `Settings` so we can determine which driver to use. // Don't reset, so we can inherit the theme from the previous run. Load (); @@ -391,7 +391,7 @@ public static void Shutdown () /// public static event EventHandler NotifyNewRunState; - /// Notify that an existent is stopping ( was called). + /// Notify that a existent is stopping ( was called). /// /// If is callers to /// must also subscribe to and manually dispose of the token @@ -471,7 +471,7 @@ public static RunState Begin (Toplevel toplevel) Top.OnLeave (toplevel); } - // BUGBUG: We should not depend on `Id` internally. + // BUGBUG: We should not depend on `Id` internally. // BUGBUG: It is super unclear what this code does anyway. if (string.IsNullOrEmpty (toplevel.Id)) { @@ -859,7 +859,7 @@ public static void Invoke (Action action) } // TODO: Determine if this is really needed. The only code that calls WakeUp I can find - // is ProgressBarStyles, and it's not clear it needs to. + // is ProgressBarStyles and it's not clear it needs to. /// Wakes up the running application that might be waiting on input. public static void Wakeup () { MainLoop?.Wakeup (); } @@ -1168,13 +1168,13 @@ public static void End (RunState runState) runState.Toplevel.OnUnloaded (); } - // End the RunState.Toplevel + // End the RunState.Toplevel // First, take it off the Toplevel Stack if (_topLevels.Count > 0) { if (_topLevels.Peek () != runState.Toplevel) { - // If the top of the stack is not the RunState.Toplevel then + // If there the top of the stack is not the RunState.Toplevel then // this call to End is not balanced with the call to Begin that started the RunState throw new ArgumentException ("End must be balanced with calls to Begin"); } @@ -1185,8 +1185,8 @@ public static void End (RunState runState) // Notify that it is closing runState.Toplevel?.OnClosed (runState.Toplevel); - // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel - // is a child of MidTop, and we should notify the OverlappedTop that it is closing + // If there is a OverlappedTop that is not the RunState.Toplevel then runstate.TopLevel + // is a child of MidTop and we should notify the OverlappedTop that it is closing if (OverlappedTop is { } && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop) { OverlappedTop.OnChildClosed (runState.Toplevel); @@ -1242,7 +1242,7 @@ public static void End (RunState runState) /// Holds the stack of TopLevel views. - // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What + // BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What // about TopLevels that are just a SubView of another View? internal static readonly Stack _topLevels = new (); diff --git a/Terminal.Gui/Clipboard/IClipboard.cs b/Terminal.Gui/Clipboard/IClipboard.cs index 789b30aeb6..1dd497bf1c 100644 --- a/Terminal.Gui/Clipboard/IClipboard.cs +++ b/Terminal.Gui/Clipboard/IClipboard.cs @@ -6,21 +6,21 @@ public interface IClipboard /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. bool IsSupported { get; } - /// Get the operating system clipboard. + /// Get the operation system clipboard. /// Thrown if it was not possible to read the clipboard contents. string GetClipboardData (); - /// Sets the operating system clipboard. + /// Sets the operation system clipboard. /// /// Thrown if it was not possible to set the clipboard contents. void SetClipboardData (string text); - /// Gets the operating system clipboard if possible. + /// Gets the operation system clipboard if possible. /// Clipboard contents read /// true if it was possible to read the OS clipboard. bool TryGetClipboardData (out string result); - /// Sets the operating system clipboard if possible. + /// Sets the operation system clipboard if possible. /// /// True if the clipboard content was set successfully. bool TrySetClipboardData (string text); diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs index e234b6ee7f..68c8c184c6 100644 --- a/Terminal.Gui/Configuration/ConfigurationManager.cs +++ b/Terminal.Gui/Configuration/ConfigurationManager.cs @@ -125,7 +125,7 @@ public enum ConfigLocations /// private static SettingsScope? _settings; - /// Name of the running application. By default, this property is set to the application's assembly name. + /// Name of the running application. By default this property is set to the application's assembly name. public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!; /// Application-specific configuration settings scope. diff --git a/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs b/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs index 0cc42f6a1c..9d70923211 100644 --- a/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs +++ b/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs @@ -15,6 +15,6 @@ public class ThemeManagerEventArgs : EventArgs /// Initializes a new instance of public ThemeManagerEventArgs (string newTheme) { NewTheme = newTheme; } - /// The name of the new active theme. + /// The name of the new active theme.. public string NewTheme { get; set; } = string.Empty; } diff --git a/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs b/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs index 1e0e2545e2..4741814e67 100644 --- a/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs +++ b/Terminal.Gui/Configuration/SerializableConfigurationProperty.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; -/// An attribute that can be applied to a property to indicate that it should be included in the configuration file. +/// An attribute that can be applied to a property to indicate that it should included in the configuration file. /// /// [SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter /// (typeof (JsonStringEnumConverter))] public static LineStyle DefaultBorderStyle { ... diff --git a/Terminal.Gui/Configuration/ThemeManager.cs b/Terminal.Gui/Configuration/ThemeManager.cs index daf2edd9d5..e6efe32e08 100644 --- a/Terminal.Gui/Configuration/ThemeManager.cs +++ b/Terminal.Gui/Configuration/ThemeManager.cs @@ -9,7 +9,7 @@ namespace Terminal.Gui; /// Contains a dictionary of the s for a Terminal.Gui application. /// /// A Theme is a collection of settings that are named. The default theme is named "Default". -/// The property is used to determine the currently active theme. +/// The property is used to detemrine the currently active theme. /// /// /// is a singleton class. It is created when the first property diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index e5ac4ad076..05cbb62172 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -568,7 +568,7 @@ public virtual Attribute MakeColor (in Color foreground, in Color background) /// Called when a key is released. Fires the event. /// - /// Drivers that do not support key release events will call this method after processing + /// Drivers that do not support key release events will calls this method after processing /// is complete. /// /// @@ -604,7 +604,7 @@ public enum CursorVisibility /// Cursor caret has default /// /// Works under Xterm-like terminal otherwise this is equivalent to . This default directly - /// depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking. + /// depends of the XTerm user configuration settings so it could be Block, I-Beam, Underline with possible blinking. /// Default = 0x00010119, @@ -682,7 +682,7 @@ public enum KeyCode : uint /// /// If the is set, then the value is that of the special mask, otherwise, the value is - /// in the lower bits (as extracted by ). + /// in the the lower bits (as extracted by ). /// SpecialMask = 0x_fff0_0000, @@ -839,9 +839,9 @@ public enum KeyCode : uint //Delete = 127, // --- Special keys --- - // The values below are common non-alphanum keys. Their values are + // The values below are common non-alphanum keys. Their values are // based on the .NET ConsoleKey values, which, in-turn are based on the - // VK_ values from the Windows API. + // VK_ values from the Windows API. // We add MaxCodePoint to avoid conflicts with the Unicode values. /// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys. diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index c3497f3acd..8043c65eb6 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -1435,7 +1435,7 @@ public enum VK : ushort (VK)'2', ConsoleModifiers.Shift, '\"' - ), // BUGBUG: This is true for Portuguese keyboard, but not ENG (@) or DEU (") + ), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (") new ScanCodeMapping ( 3, (VK)'2', diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs index 0ec8efc059..99938dcc0b 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs @@ -89,7 +89,7 @@ private bool CheckSupport () /// /// A clipboard implementation for MacOSX. This implementation uses the Mac clipboard API (via P/Invoke) to -/// copy/paste. The existence of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported. +/// copy/paste. The existance of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported. /// internal class MacOSXClipboard : ClipboardBase { diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 378e4b6081..14d71c6371 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -317,7 +317,7 @@ internal override MainLoop Init () Curses.doupdate (); // - // We are setting Invisible as default, so we could ignore XTerm DECSUSR setting + // We are setting Invisible as default so we could ignore XTerm DECSUSR setting // switch (Curses.curs_set (0)) { @@ -977,7 +977,7 @@ internal static class Platform private static int _suspendSignal; /// Suspends the process by sending SIGTSTP to itself - /// True if the suspension was successful. + /// The suspend. public static bool Suspend () { int signal = GetSuspendSignal (); diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs index be3fd1ac36..7400214ecc 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs @@ -291,7 +291,7 @@ private static class MacOSX } /// - /// On Linux systems, using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if + /// On Linux systems, using using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if /// libc6-dev is not installed. As a workaround, we load symbols for dlopen and dlsym from the current process as on /// Linux Mono sure is linked against these symbols. /// diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs index 29ef5afa79..b62ef17a40 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -22,7 +22,7 @@ public EscSeqReqStatus (string terminator, int numReq) /// Gets the number of requests. public int NumRequests { get; } - /// Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator). + /// Gets the Escape Sequence Termintor (e.g. ESC[8t ... t is the terminator). public string Terminator { get; } } diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 1eb63e34aa..f9c547af6b 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -159,10 +159,10 @@ public enum ClearScreenOptions /// Decodes an ANSI escape sequence. /// /// The which may contain a request. - /// The which may change. - /// The which may change. + /// The which may changes. + /// The which may changes. /// The array. - /// The which may change. + /// The which may changes. /// The control returned by the method. /// The code returned by the method. /// The values returned by the method. @@ -426,7 +426,7 @@ public static ConsoleModifiers GetConsoleModifiers (string? value) #nullable restore /// - /// Gets all the needed information about an escape sequence. + /// Gets all the needed information about a escape sequence. /// /// The array with all chars. /// diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 6ec810c8a0..74328a8189 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -198,7 +198,7 @@ public override void UpdateScreen () FakeConsole.CursorTop = 0; FakeConsole.CursorLeft = 0; - //SetCursorVisibility (savedVisibility); + //SetCursorVisibility (savedVisibitity); void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { @@ -437,7 +437,7 @@ public virtual void ResizeScreen () { if (FakeConsole.WindowHeight > 0) { - // Can raise an exception while it is still resizing. + // Can raise an exception while is still resizing. try { FakeConsole.CursorTop = 0; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index fcd7316e22..86ef134664 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1669,7 +1669,7 @@ private KeyCode MapKey (ConsoleKeyInfo keyInfo) /// /// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is -/// cross-platform but lacks things like file descriptor monitoring. +/// cross platform but lacks things like file descriptor monitoring. /// /// This implementation is used for NetDriver. internal class NetMainLoop : IMainLoopDriver diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 8ff127a4ba..89c82dd58a 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -659,7 +659,7 @@ public struct ExtendedCharInfo { public char Char { get; set; } public Attribute Attribute { get; set; } - public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences + public bool Empty { get; set; } // TODO: Temp hack until virutal terminal sequences public ExtendedCharInfo (char character, Attribute attribute) { @@ -901,7 +901,7 @@ public InputRecord [] ReadConsoleInput () } } -#if false // Not needed on the constructor. Perhaps could be used on resizing. To study. +#if false // Not needed on the constructor. Perhaps could be used on resizing. To study. [DllImport ("kernel32.dll", ExactSpelling = true)] static extern IntPtr GetConsoleWindow (); @@ -1397,7 +1397,7 @@ internal override MainLoop Init () { if (WinConsole is { }) { - // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. + // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos); Cols = winSize.Width; @@ -1642,7 +1642,7 @@ private KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) if (keyInfo.Modifiers != 0) { - // These Oem keys have well-defined chars. We ensure the representative char is used. + // These Oem keys have well defined chars. We ensure the representative char is used. // If we don't do this, then on some keyboard layouts the wrong char is // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important // for key persistence ("Ctrl++" vs. "Ctrl+="). @@ -1656,7 +1656,7 @@ private KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) }; } - // Return the mappedChar with modifiers. Because mappedChar is un-shifted, if Shift was down + // Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down // we should keep it return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar); } @@ -1901,8 +1901,8 @@ private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) // The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button. // This will tell when a mouse button is pressed. When the button is released this event will - // be fired with its bit set to 0. So when the button is up ButtonState will be 0. - // To map to the correct driver events we save the last pressed mouse button, so we can + // be fired with it's bit set to 0. So when the button is up ButtonState will be 0. + // To map to the correct driver events we save the last pressed mouse button so we can // map to the correct clicked event. if ((_lastMouseButtonPressed is { } || _isButtonReleased) && mouseEvent.ButtonState != 0) { diff --git a/Terminal.Gui/Drawing/ColorScheme.cs b/Terminal.Gui/Drawing/ColorScheme.cs index 603694eec8..d2acdab5ca 100644 --- a/Terminal.Gui/Drawing/ColorScheme.cs +++ b/Terminal.Gui/Drawing/ColorScheme.cs @@ -77,7 +77,7 @@ public Attribute Focus init => _focus = value; } - /// The foreground and background color for text in a focused view that indicates a . + /// The foreground and background color for for text in a focused view that indicates a . public Attribute HotFocus { get => _hotFocus; diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs index f64285f0f9..e98d60b1ec 100644 --- a/Terminal.Gui/Drawing/Glyphs.cs +++ b/Terminal.Gui/Drawing/Glyphs.cs @@ -348,7 +348,7 @@ public class GlyphDefinitions /// Box Drawings Left Tee - Heavy Vertical and Heavy Horizontal (U+2527) - ┣ public Rune LeftTeeHvDblH { get; set; } = (Rune)'┣'; - /// Box Drawings Right Tee - Single Vertical and Single Horizontal (U+2524) - ┤ + /// Box Drawings Righ Tee - Single Vertical and Single Horizontal (U+2524) - ┤ public Rune RightTee { get; set; } = (Rune)'┤'; /// Box Drawings Right Tee - Single Vertical and Double Horizontal (U+2561) - ╡ diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 4b5119a82a..2bda9e5dd4 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -199,7 +199,7 @@ public void Clear () } // TODO: Unless there's an obvious use case for this API we should delete it in favor of the - // simpler version that doesn't take an area. + // simpler version that doensn't take an area. /// /// Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their /// locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate diff --git a/Terminal.Gui/Drawing/LineStyle.cs b/Terminal.Gui/Drawing/LineStyle.cs index 47e37a97aa..6bb06212ec 100644 --- a/Terminal.Gui/Drawing/LineStyle.cs +++ b/Terminal.Gui/Drawing/LineStyle.cs @@ -41,7 +41,7 @@ public enum LineStyle RoundedDotted // TODO: Support Ruler - ///// + ///// ///// The border is drawn as a diagnostic ruler ("|123456789..."). ///// //Ruler diff --git a/Terminal.Gui/FileServices/AllowedType.cs b/Terminal.Gui/FileServices/AllowedType.cs index 8d840edeca..06978cc86f 100644 --- a/Terminal.Gui/FileServices/AllowedType.cs +++ b/Terminal.Gui/FileServices/AllowedType.cs @@ -33,7 +33,7 @@ public class AllowedTypeAny : IAllowedType public class AllowedType : IAllowedType { /// Initializes a new instance of the class. - /// The human-readable text to display. + /// The human readable text to display. /// Extension(s) to match e.g. .csv. public AllowedType (string description, params string [] extensions) { @@ -46,7 +46,7 @@ public AllowedType (string description, params string [] extensions) Extensions = extensions; } - /// Gets or Sets the human-readable description for the file type e.g. "Comma Separated Values". + /// Gets or Sets the human readable description for the file type e.g. "Comma Separated Values". public string Description { get; set; } /// Gets or Sets the permitted file extension(s) (e.g. ".csv"). diff --git a/Terminal.Gui/FileServices/FileDialogState.cs b/Terminal.Gui/FileServices/FileDialogState.cs index 042b5a75d7..ece6d24286 100644 --- a/Terminal.Gui/FileServices/FileDialogState.cs +++ b/Terminal.Gui/FileServices/FileDialogState.cs @@ -54,7 +54,7 @@ protected virtual IEnumerable GetChildren (IDirectoryInfo d .ToList (); } - // if there's a UI filter in place too + // if theres a UI filter in place too if (Parent.CurrentFilter is { }) { children = children.Where (MatchesApiFilter).ToList (); diff --git a/Terminal.Gui/FileServices/FileDialogStyle.cs b/Terminal.Gui/FileServices/FileDialogStyle.cs index aac806de81..9cc6557f15 100644 --- a/Terminal.Gui/FileServices/FileDialogStyle.cs +++ b/Terminal.Gui/FileServices/FileDialogStyle.cs @@ -24,7 +24,7 @@ public FileDialogStyle (IFileSystem fileSystem) public string CancelButtonText { get; set; } = Strings.btnCancel; /// - /// Gets or sets the class that is responsible for determining which color to use to represent files and + /// Gets or sets the class thatis responsible for determining which color to use to represent files and /// directories when is . /// public FileSystemColorProvider ColorProvider { get; set; } = new (); @@ -100,7 +100,7 @@ public FileDialogStyle (IFileSystem fileSystem) /// Gets or sets the header text displayed in the Modified column of the files table. public string ModifiedColumnName { get; set; } = Strings.fdModified; - /// Gets or sets the text on the 'Ok' button. Typically, you may want to change this to "Open" or "Save" etc. + /// Gets or sets the text on the 'Ok' button. Typically you may want to change this to "Open" or "Save" etc. public string OkButtonText { get; set; } = Strings.btnOk; /// Gets or sets the text displayed in the 'Path' text box when user has not supplied any input yet. @@ -163,7 +163,7 @@ private Dictionary DefaultTreeRootGetter () } catch (Exception) { - // Cannot get the system disks, that's fine + // Cannot get the system disks thats fine } try @@ -195,7 +195,7 @@ private Dictionary DefaultTreeRootGetter () } catch (Exception) { - // Cannot get the special files for this OS, oh well + // Cannot get the special files for this OS oh well } return roots; diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index 566cc9336c..24ebdb407d 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -113,7 +113,7 @@ public enum Command /// Move one page down. PageDown, - /// Move one page down extending the selection to cover revealed objects/characters. + /// Move one page page extending the selection to cover revealed objects/characters. PageDownExtend, /// Move one page up. @@ -137,7 +137,7 @@ public enum Command /// Open the selected item. OpenSelectedItem, - /// Toggles the Expanded or collapsed state of a list or item (with subitems). + /// Toggles the Expanded or collapsed state of a a list or item (with subitems). ToggleExpandCollapse, /// Expands a list or item (with subitems). @@ -224,13 +224,13 @@ public enum Command /// Quit a . QuitToplevel, - /// Suspend an application (Only implemented in ). + /// Suspend a application (Only implemented in ). Suspend, /// Moves focus to the next view. NextView, - /// Moves focus to the previous view. + /// Moves focuss to the previous view. PreviousView, /// Moves focus to the next view or Toplevel (case of Overlapped). diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index d2972bc04b..04777de66d 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -487,7 +487,7 @@ private static string GetKeyString (KeyCode key) /// Formats a as a string. /// The key to format. - /// The character to use as a separator between modifier keys and the key itself. + /// The character to use as a separator between modifier keys and and the key itself. /// /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key /// name will be returned. diff --git a/Terminal.Gui/Input/MouseEventEventArgs.cs b/Terminal.Gui/Input/MouseEventEventArgs.cs index 89fc168a7e..5e0990206e 100644 --- a/Terminal.Gui/Input/MouseEventEventArgs.cs +++ b/Terminal.Gui/Input/MouseEventEventArgs.cs @@ -13,7 +13,7 @@ public class MouseEventEventArgs : EventArgs /// /// Indicates if the current mouse event has already been processed and the driver should stop notifying any other - /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the + /// event subscriber. Its important to set this value to true specially when updating any View's layout from inside the /// subscriber method. /// /// diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index 89d0a6dea8..52256c4181 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -3,7 +3,7 @@ // ConfigurationManager. It is automatically loaded, and applied, each time Application.Init // is run (via the ConfiguraitonManager.Reset method). // - // In other words, initial values set in the the codebase are always overwritten by the contents of this + // In otherwords, initial values set in the the codebase are always overwritten by the contents of this // resource embedded in the Terminal.Gui.dll assembly. // // The Unit Test method "ConfigurationManagerTests.SaveDefaults" can be used to re-create the base of this file, but diff --git a/Terminal.Gui/StackExtensions.cs b/Terminal.Gui/StackExtensions.cs index b3e7c01d8a..bde15a404e 100644 --- a/Terminal.Gui/StackExtensions.cs +++ b/Terminal.Gui/StackExtensions.cs @@ -160,7 +160,7 @@ public static void MoveTo ( } } - /// Replaces a stack object values that match with the value to replace. + /// Replaces an stack object values that match with the value to replace. /// The stack object type. /// The stack object. /// Value to replace. diff --git a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs index f5f7190e62..76d74f512d 100644 --- a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs @@ -105,7 +105,7 @@ public override void RenderOverlay (Point renderAt) return; } - // draw it like it's selected, even though it's not + // draw it like its selected even though its not Application.Driver.SetAttribute ( new Attribute ( ColorScheme.Normal.Foreground, diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs index 93e32b12e8..b4264d74b7 100644 --- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs @@ -322,7 +322,7 @@ public override void RenderOverlay (Point renderAt) if (PopupInsideContainer) { - // don't overspill horizontally, let's see if it can be displayed on the left + // don't overspill horizontally, let's see if can be displayed on the left if (width > HostControl.Viewport.Width - renderAt.X) { // Verifies that the left limit available is greater than the right limit @@ -339,7 +339,7 @@ public override void RenderOverlay (Point renderAt) } else { - // don't overspill horizontally, let's see if it can be displayed on the left + // don't overspill horizontally, let's see if can be displayed on the left if (width > top.Viewport.Width - (renderAt.X + HostControl.Frame.X)) { // Verifies that the left limit available is greater than the right limit @@ -408,7 +408,7 @@ protected void Close () /// /// Called when the user confirms a selection at the current cursor location in the . The - /// string is the full autocomplete word to be inserted. Typically, a host will have to + /// string is the full autocomplete word to be inserted. Typically a host will have to /// remove some characters such that the string completes the word instead of simply being /// appended. /// diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 994eeb007e..e3458afe95 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1914,7 +1914,7 @@ public static int GetColumnsRequiredForVerticalText ( /// (uses ). if it contains newlines. /// /// - /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// Text, may contain newlines. @@ -1932,7 +1932,7 @@ public static int GetWidestLineLength (string text, int tabWidth = 0) /// . /// /// - /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The text. @@ -1957,7 +1957,7 @@ public static int GetSumMaxCharWidth (string text, int startIndex = -1, int leng /// Gets the number of the Runes in the text that will fit in . /// - /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The text. @@ -1972,7 +1972,7 @@ public static int GetLengthThatFits (string text, int width, int tabWidth = 0, T /// Gets the number of the Runes in a list of Runes that will fit in . /// - /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The list of runes. @@ -2034,7 +2034,7 @@ private static int GetRuneWidth (Rune rune, int tabWidth, TextDirection textDire /// Gets the index position from the list based on the . /// - /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The lines. @@ -2067,7 +2067,7 @@ public static int GetMaxColsForWidth (List lines, int width, int tabWidt /// Calculates the rectangle required to hold text, assuming no word wrapping or alignment. /// - /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding + /// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding /// glyphs (e.g. Arabic). /// /// The x location of the rectangle diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 076ec74cb5..aa4dd20661 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -74,7 +74,7 @@ public Border (View parent) : base (parent) public override void BeginInit () { #if HOVER - // TOOD: Hack - make Arrangement overridable + // TOOD: Hack - make Arragnement overidable if ((Parent?.Arrangement & ViewArrangement.Movable) != 0) { HighlightStyle |= HighlightStyle.Hover; diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 60e405e843..6a4cbcf02d 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -668,7 +668,6 @@ internal void SetRelativeLayout (Size superviewContentSize) /// behavior of this method is indeterminate if is . /// /// Raises the event before it returns. - /// Raises the event before it returns. /// public virtual void LayoutSubviews () { @@ -756,7 +755,7 @@ internal void OnResizeNeeded () // TODO: Identify a real-world use-case where this API should be virtual. // TODO: Until then leave it `internal` and non-virtual - // Determine our container's ContentSize - + // Determine our container's ContentSize - // First try SuperView.Viewport, then Application.Top, then Driver.Viewport. // Finally, if none of those are valid, use 2048 (for Unit tests). Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.GetContentSize () : diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 68eb1853f3..1e016e8ae0 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -268,7 +268,7 @@ public virtual void EndInit () EndInitAdornments (); - // TODO: Move these into ViewText.cs as EndInit_Text() to consolidate. + // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate. // TODO: Verify UpdateTextDirection really needs to be called here. // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called. UpdateTextDirection (TextDirection); diff --git a/Terminal.Gui/View/ViewEventArgs.cs b/Terminal.Gui/View/ViewEventArgs.cs index b17b98afe4..4646746d6f 100644 --- a/Terminal.Gui/View/ViewEventArgs.cs +++ b/Terminal.Gui/View/ViewEventArgs.cs @@ -67,7 +67,7 @@ public FocusEventArgs (View leaving, View entering) { /// /// Indicates if the current focus event has already been processed and the driver should stop notifying any other - /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the + /// event subscriber. Its important to set this value to true specially when updating any View's layout from inside the /// subscriber method. /// public bool Handled { get; set; } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index d103d894d8..127c3fac9e 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -57,7 +57,8 @@ private void DisposeKeyboard () /// Gets or sets the hot key defined for this view. Pressing the hot key on the keyboard while this view has focus will /// invoke the and commands. /// causes the view to be focused and does nothing. By default, the HotKey is - /// automatically set to the first character of that is prefixed with . + /// automatically set to the first character of that is prefixed with with + /// . /// /// A HotKey is a keypress that selects a visible UI item. For selecting items across `s (e.g.a /// in a ) the keypress must include the @@ -740,11 +741,11 @@ private bool ProcessSubViewKeyBindings (Key keyEvent, KeyBindingScope scope, ref return false; } - // TODO: This is a "prototype" debug check. It may be too annoying vs. useful. - // TODO: A better approach would be to have Application hold a list of bound Hotkeys, similar to + // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful. + // TODO: A better approach would be have Application hold a list of bound Hotkeys, similar to // TODO: how Application holds a list of Application Scoped key bindings and then check that list. /// - /// Returns true if Key is bound in this view hierarchy. For debugging + /// Returns true if Key is bound in this view heirarchy. For debugging /// /// The key to test. /// Returns the view the key is bound to. @@ -807,8 +808,8 @@ public bool IsHotKeyKeyBound (Key key, out View boundView) Debug.WriteLine ($"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.{commandBinding.Commands [0]}: {boundView}."); } - // TODO: This is a "prototype" debug check. It may be too annoying vs. useful. - // Scour the bindings up our View hierarchy + // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful. + // Scour the bindings up our View heirarchy // to ensure that the key is not already bound to a different set of commands. if (SuperView?.IsHotKeyKeyBound (key, out View previouslyBoundView) ?? false) { diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index e4e71c97df..b98cceb7ea 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -118,10 +118,10 @@ public bool HideDropdownListOnClick set => _hideDropdownListOnClick = _listview.HideDropdownListOnClick = value; } - /// Gets the drop-down list state, expanded or collapsed. + /// Gets the drop down list state, expanded or collapsed. public bool IsShow { get; private set; } - /// If set to true, no changes to the text will be allowed. + /// If set to true its not allow any changes in the text. public bool ReadOnly { get => _search.ReadOnly; @@ -200,7 +200,7 @@ public IListDataSource Source } /// - /// Collapses the drop-down list. Returns true if the state changed or false if it was already collapsed and no + /// Collapses the drop down list. Returns true if the state chagned or false if it was already collapsed and no /// action was taken /// public virtual bool Collapse () @@ -220,7 +220,7 @@ public virtual bool Collapse () public event EventHandler Collapsed; /// - /// Expands the drop-down list. Returns true if the state changed or false if it was already expanded and no + /// Expands the drop down list. Returns true if the state chagned or false if it was already expanded and no /// action was taken /// public virtual bool Expand () @@ -398,7 +398,7 @@ private bool ActivateSelected () /// Internal height of dynamic search list /// - private int CalculateHeight () + private int CalculatetHeight () { if (!IsInitialized || Viewport.Height == 0) { @@ -617,7 +617,7 @@ private void ProcessLayout () || (_autoHide && Viewport.Width > 0 && _search.Frame.Width != Viewport.Width - 1)) { _search.Width = _listview.Width = _autoHide ? Viewport.Width - 1 : Viewport.Width; - _listview.Height = CalculateHeight (); + _listview.Height = CalculatetHeight (); _search.SetRelativeLayout (GetContentSize ()); _listview.SetRelativeLayout (GetContentSize ()); } @@ -634,7 +634,7 @@ private void Reset (bool keepSearchText = false) ResetSearchSet (); _listview.SetSource (_searchSet); - _listview.Height = CalculateHeight (); + _listview.Height = CalculatetHeight (); if (Subviews.Count > 0 && HasFocus) { @@ -769,7 +769,7 @@ private void SetSearchText (string value) private void SetValue (object text, bool isFromSelectedItem = false) { - // TOOD: The fact we have to suspend events to change the text makes this feel very hacky. + // TOOD: THe fact we have to suspend events to change the text makes this feel very hacky. _search.TextChanged -= Search_Changed; // Note we set _text, to avoid set_Text from setting _search.Text again _text = _search.Text = text.ToString (); @@ -792,7 +792,7 @@ private void ShowList () _listview.ResumeSuspendCollectionChangedEvent (); _listview.Clear (); - _listview.Height = CalculateHeight (); + _listview.Height = CalculatetHeight (); SuperView?.BringSubviewToFront (this); } diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 9627b25589..7677ba8631 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -201,10 +201,10 @@ private void DateField_Changing (object sender, CancelEventArgs e) } spaces += FormatLength; - string trimmedText = e.NewValue [..spaces]; + string trimedText = e.NewValue [..spaces]; spaces -= FormatLength; - trimmedText = trimmedText.Replace (new string (' ', spaces), " "); - var date = Convert.ToDateTime (trimmedText).ToString (_format.Trim ()); + trimedText = trimedText.Replace (new string (' ', spaces), " "); + var date = Convert.ToDateTime (trimedText).ToString (_format.Trim ()); if ($" {date}" != e.NewValue) { @@ -542,8 +542,8 @@ private bool SetText (string text) return true; } - // Converts various date formats to a uniform 10-character format. - // This aids in simplifying the handling of single-digit months and days, + // Converts various date formats to a uniform 10-character format. + // This aids in simplifying the handling of single-digit months and days, // and reduces the number of distinct date formats to maintain. private static string StandardizeDateFormat (string format) { diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index ad478f6ddc..fcdd2d6d27 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -590,7 +590,7 @@ internal void ApplySort () { FileSystemInfoStats [] stats = State?.Children ?? new FileSystemInfoStats[0]; - // This portion is never reordered (always .. at top then folders) + // This portion is never reordered (aways .. at top then folders) IOrderedEnumerable forcedOrder = stats .OrderByDescending (f => f.IsParent) .ThenBy (f => f.IsDir ? -1 : 100); @@ -670,7 +670,7 @@ internal void SortColumn (int col, bool isAsc) return; } - // Don't include ".." (IsParent) in multi-selections + // Don't include ".." (IsParent) in multiselections MultiSelected = toMultiAccept .Where (s => !s.IsParent) .Select (s => s.FileSystemInfo.FullName) diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs index efff79ce97..7e7561b76d 100644 --- a/Terminal.Gui/Views/GraphView/Axis.cs +++ b/Terminal.Gui/Views/GraphView/Axis.cs @@ -80,7 +80,7 @@ public virtual void Reset () private string DefaultLabelGetter (AxisIncrementToRender toRender) { return toRender.Value.ToString ("N0"); } } -/// The horizontal (x-axis) of a +/// The horizontal (x axis) of a public class HorizontalAxis : Axis { /// @@ -132,7 +132,7 @@ public override void DrawAxisLabel (GraphView graph, int screenPosition, string } } - /// Draws the horizontal x-axis labels and ticks + /// Draws the horizontal x axis labels and ticks public override void DrawAxisLabels (GraphView graph) { if (!Visible || Increment == 0) @@ -180,10 +180,10 @@ public override void DrawAxisLine (GraphView graph) int y = GetAxisYPosition (graph); - // start the x-axis at left of screen (either 0 or margin) + // start the x axis at left of screen (either 0 or margin) var xStart = (int)graph.MarginLeft; - // but if the x-axis has a minimum (minimum is in graph space units) + // but if the x axis has a minmum (minimum is in graph space units) if (Minimum.HasValue) { // start at the screen location of the minimum @@ -257,7 +257,7 @@ private IEnumerable GetLabels (GraphView graph, Rectangle // Not every increment has to have a label if (ShowLabelsEvery != 0) { - // if this increment also needs a label + // if this increment does also needs a label if (labels++ % ShowLabelsEvery == 0) { toRender.Text = LabelGetter (toRender); @@ -403,7 +403,7 @@ private int GetAxisYEnd (GraphView graph) // draw down the screen (0 is top of screen) // end at the bottom of the screen - //unless there is a minimum + //unless there is a minimum if (Minimum.HasValue) { return graph.GraphSpaceToScreen (new PointF (0, Minimum.Value)).Y; diff --git a/Terminal.Gui/Views/GraphView/Series.cs b/Terminal.Gui/Views/GraphView/Series.cs index f0974556c6..c117bd0a24 100644 --- a/Terminal.Gui/Views/GraphView/Series.cs +++ b/Terminal.Gui/Views/GraphView/Series.cs @@ -51,7 +51,7 @@ public class MultiBarSeries : ISeries /// Creates a new series of clustered bars. /// Each category has this many bars - /// How far apart to put each category (in graph space) + /// How far appart to put each category (in graph space) /// /// How much spacing between bars in a category (should be less than / /// ) @@ -97,7 +97,7 @@ public MultiBarSeries (int numberOfBarsPerCategory, float barsEvery, float spaci /// /// Sub collections. Each series contains the bars for a different category. Thus SubSeries[0].Bars[0] is the - /// first bar on the axis and SubSeries[1].Bars[0] is the second etc. + /// first bar on the axis and SubSeries[1].Bars[0] is the second etc /// public IReadOnlyCollection SubSeries => new ReadOnlyCollection (subSeries); @@ -191,7 +191,7 @@ public virtual void DrawSeries (GraphView graph, Rectangle drawBounds, Rectangle { screenStart.X = graph.AxisY.GetAxisXPosition (graph); - // don't draw bar off the right of the control + // dont draw bar off the right of the control screenEnd.X = Math.Min (graph.Viewport.Width - 1, screenEnd.X); // if bar is off the screen @@ -205,7 +205,7 @@ public virtual void DrawSeries (GraphView graph, Rectangle drawBounds, Rectangle // Start the axis screenStart.Y = graph.AxisX.GetAxisYPosition (graph); - // don't draw bar up above top of control + // dont draw bar up above top of control screenEnd.Y = Math.Max (0, screenEnd.Y); // if bar is off the screen diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index e96eceb17d..40e4e90304 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -130,7 +130,7 @@ public Point CursorPosition } /// - /// Sets or gets the offset into the that will be displayed at the top of the + /// Sets or gets the offset into the that will displayed at the top of the /// /// /// The display start. @@ -557,9 +557,9 @@ internal void SetDisplayStart (long value) } // - // This is used to support editing of the buffer on a peer List<>, + // This is used to support editing of the buffer on a peer List<>, // the offset corresponds to an offset relative to DisplayStart, and - // the buffer contains the contents of a screenful of data, so the + // the buffer contains the contents of a screenful of data, so the // offset is relative to the buffer. // // diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index e2b2127ace..226af1f917 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/Terminal.Gui/Views/Menu/ContextMenu.cs @@ -18,7 +18,7 @@ /// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling /// . /// -/// ContextMenus are located using screen coordinates and appear above all other Views. +/// ContextMenus are located using screen using screen coordinates and appear above all other Views. /// public sealed class ContextMenu : IDisposable { diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 34e8dab30d..2da69bc01b 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -282,7 +282,7 @@ private bool ExpandCollapse (MenuItem menuItem) return true; } - // TODO: Determine if there's a cleaner way to handle this. + // TODO: Determine if there's a cleaner way to handle this // This supports the case where the menu bar is a context menu return _host.OnInvokingKeyBindings (keyEvent, scope); } diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 530b2ba9b8..00247a6131 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -317,7 +317,7 @@ public override void OnDrawContent (Rectangle viewport) /// Virtual method that will invoke the . /// The current menu to be closed. - /// Whether the current menu will be reopened. + /// Whether the current menu will be reopen. /// Whether is a sub-menu or not. public virtual MenuClosingEventArgs OnMenuClosing (MenuBarItem currentMenu, bool reopen, bool isSubMenu) { diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 5d583250b0..97fcf94527 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -434,7 +434,7 @@ params string [] buttons } } - // Run the modal; do not shut down the mainloop driver when done + // Run the modal; do not shutdown the mainloop driver when done Application.Run (d); d.Dispose (); diff --git a/Terminal.Gui/Views/SelectedItemChangedArgs.cs b/Terminal.Gui/Views/SelectedItemChangedArgs.cs index a2f5eb47cb..10c1a9ca37 100644 --- a/Terminal.Gui/Views/SelectedItemChangedArgs.cs +++ b/Terminal.Gui/Views/SelectedItemChangedArgs.cs @@ -1,6 +1,6 @@ namespace Terminal.Gui; -/// Event arguments for the SelectedItemChanged event. +/// Event arguments for the SelectedItemChagned event. public class SelectedItemChangedArgs : EventArgs { /// Initializes a new class. diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 61ac401d6e..1b96bda0db 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -396,7 +396,7 @@ public virtual bool OnOptionFocused (int newFocusedOption, SliderEventArgs ar /// Causes the specified option to be set and be focused. public bool SetOption (int optionIndex) { - // TODO: Handle range type. + // TODO: Handle range type. // Note: Maybe return false only when optionIndex doesn't exist, otherwise true. if (!_setOptions.Contains (optionIndex) && optionIndex >= 0 && optionIndex < _options.Count) @@ -1285,9 +1285,9 @@ private void DrawLegends () protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { // Note(jmperricone): Maybe we click to focus the cursor, and on next click we set the option. - // That will make OptionFocused Event more relevant. + // That will makes OptionFocused Event more relevant. // (tig: I don't think so. Maybe an option if someone really wants it, but for now that - // adds too much friction to UI. + // adss to much friction to UI. // TODO(jmperricone): Make Range Type work with mouse. if (!(mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked) @@ -1325,7 +1325,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) var success = false; var option = 0; - // how far has user dragged from original location? + // how far has user dragged from original location? if (Orientation == Orientation.Horizontal) { success = TryGetOptionByPosition (mouseEvent.Position.X, 0, Math.Max (0, _config._cachedInnerSpacing / 2), out option); diff --git a/Terminal.Gui/Views/SliderOption.cs b/Terminal.Gui/Views/SliderOption.cs index a3d10781dd..1cfcc1f07f 100644 --- a/Terminal.Gui/Views/SliderOption.cs +++ b/Terminal.Gui/Views/SliderOption.cs @@ -15,7 +15,7 @@ public SliderOption (string legend, Rune legendAbbr, T data) Data = data; } - /// Event fired when an option has changed. + /// Event fired when the an option has changed. public event EventHandler Changed; /// Custom data of the option. diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs index 3fe2d0a680..a5288f70a1 100644 --- a/Terminal.Gui/Views/Tab.cs +++ b/Terminal.Gui/Views/Tab.cs @@ -5,7 +5,7 @@ public class Tab : View { private string _displayText; - /// Creates a new unnamed tab with no controls inside. + /// Creates a new unamed tab with no controls inside. public Tab () { BorderStyle = LineStyle.Rounded; @@ -16,7 +16,7 @@ public Tab () /// public string DisplayText { - get => _displayText ?? "Unnamed"; + get => _displayText ?? "Unamed"; set => _displayText = value; } diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index 04f1773c10..095b735826 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -1296,7 +1296,7 @@ private void RenderTabLine () { if (_host.Focused == this) { - // if focus is the tab bar itself then show that they can switch tabs + // if focus is the tab bar ourself then show that they can switch tabs prevAttr = ColorScheme.HotFocus; } else diff --git a/Terminal.Gui/Views/TableView/EnumerableTableSource.cs b/Terminal.Gui/Views/TableView/EnumerableTableSource.cs index 327cdaf5cf..251a5afebf 100644 --- a/Terminal.Gui/Views/TableView/EnumerableTableSource.cs +++ b/Terminal.Gui/Views/TableView/EnumerableTableSource.cs @@ -5,7 +5,7 @@ public class EnumerableTableSource : IEnumerableTableSource { private readonly T [] data; - private readonly Dictionary> lambdas; + private readonly Dictionary> lamdas; /// Creates a new instance of the class that presents collection as a table. /// @@ -29,14 +29,14 @@ public EnumerableTableSource (IEnumerable data, DictionaryGets the object collection hosted by this wrapper. public IReadOnlyCollection Data => data.AsReadOnly (); /// - public object this [int row, int col] => lambdas [ColumnNames [col]] (data [row]); + public object this [int row, int col] => lamdas [ColumnNames [col]] (data [row]); /// public int Rows => data.Length; diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 0807991d50..7081106a45 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -1438,13 +1438,13 @@ private IEnumerable CalculateViewport (Rectangle bounds, int pad // is there enough space to meet the MinAcceptableWidth availableHorizontalSpace - usedSpace >= colStyle.MinAcceptableWidth) { - // show column and use whatever space is + // show column and use use whatever space is // left for rendering it showColumn = true; colWidth = availableHorizontalSpace - usedSpace; } - // If it's the only column we are able to render then + // If its the only column we are able to render then // accept it anyway (that must be one massively wide column!) if (first) { @@ -1674,7 +1674,7 @@ private void RenderBottomLine (int row, int availableWidth, ColumnToRender [] co } else if (Style.ExpandLastColumn == false && columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - // if the next console column is the last column's end + // if the next console column is the lastcolumns end rune = Glyphs.BottomTee; } } @@ -1748,7 +1748,7 @@ private void RenderHeaderOverline (int row, int availableWidth, ColumnToRender [ rune = Glyphs.URCorner; } - // if the next console column is the last column's end + // if the next console column is the lastcolumns end else if (Style.ExpandLastColumn == false && columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { rune = Glyphs.TopTee; @@ -1841,7 +1841,7 @@ private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender } } - // if the next console column is the last column's end + // if the next console column is the lastcolumns end else if (Style.ExpandLastColumn == false && columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { rune = Style.ShowVerticalCellLines ? Glyphs.Cross : Glyphs.BottomTee; diff --git a/Terminal.Gui/Views/TableView/TreeTableSource.cs b/Terminal.Gui/Views/TableView/TreeTableSource.cs index f2cab65092..5256949c9a 100644 --- a/Terminal.Gui/Views/TableView/TreeTableSource.cs +++ b/Terminal.Gui/Views/TableView/TreeTableSource.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui; /// public class TreeTableSource : IEnumerableTableSource, IDisposable where T : class { - private readonly Dictionary> _lambdas; + private readonly Dictionary> _lamdas; private readonly TableView _tableView; private readonly TreeView _tree; @@ -47,7 +47,7 @@ Dictionary> subsequentColumns ColumnNames = colList.ToArray (); - _lambdas = subsequentColumns; + _lamdas = subsequentColumns; } /// @@ -60,13 +60,13 @@ public void Dispose () /// public object this [int row, int col] => - col == 0 ? GetColumnZeroRepresentationFromTree (row) : _lambdas [ColumnNames [col]] (RowToObject (row)); + col == 0 ? GetColumnZeroRepresentationFromTree (row) : _lamdas [ColumnNames [col]] (RowToObject (row)); /// public int Rows => _tree.BuildLineMap ().Count; /// - public int Columns => _lambdas.Count + 1; + public int Columns => _lamdas.Count + 1; /// public string [] ColumnNames { get; } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index bdcb92dd5b..a4e220966b 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1105,7 +1105,7 @@ public override bool OnProcessKeyDown (Key a) } /// Virtual method that invoke the event if it's defined. - /// The event arguments. + /// The event arguments.. /// if the event was cancelled. public bool OnTextChanging (CancelEventArgs args) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index bea620aeeb..62cce0b878 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -334,7 +334,7 @@ void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) { lastValidCol = nCol; - if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknown) + if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknow) { runeType = GetRuneType (nRune); } @@ -1063,7 +1063,7 @@ private RuneType GetRuneType (Rune rune) return RuneType.IsPunctuation; } - return RuneType.IsUnknown; + return RuneType.IsUnknow; } private bool IsSameRuneType (Rune newRune, RuneType runeType) @@ -1255,7 +1255,7 @@ private enum RuneType IsWhiteSpace, IsLetterOrDigit, IsPunctuation, - IsUnknown + IsUnknow } } @@ -1772,8 +1772,8 @@ public TextModel WrapModel ( nCol = 0; nStartRow = 0; nStartCol = 0; - bool isRowAndColSet = row == 0 && col == 0; - bool isStartRowAndColSet = startRow == 0 && startCol == 0; + bool isRowAndColSetted = row == 0 && col == 0; + bool isStartRowAndColSetted = startRow == 0 && startCol == 0; List wModelLines = new (); for (var i = 0; i < Model.Count; i++) @@ -1796,7 +1796,7 @@ public TextModel WrapModel ( { List wrapLine = wrappedLines [j]; - if (!isRowAndColSet && modelRow == i) + if (!isRowAndColSetted && modelRow == i) { if (nCol + wrapLine.Count <= modelCol) { @@ -1806,12 +1806,12 @@ public TextModel WrapModel ( if (nCol == modelCol) { nCol = wrapLine.Count; - isRowAndColSet = true; + isRowAndColSetted = true; } else if (j == wrappedLines.Count - 1) { nCol = wrapLine.Count - j + modelCol - nCol; - isRowAndColSet = true; + isRowAndColSetted = true; } } else @@ -1819,11 +1819,11 @@ public TextModel WrapModel ( int offset = nCol + wrapLine.Count - modelCol; nCol = wrapLine.Count - offset; nRow = lines; - isRowAndColSet = true; + isRowAndColSetted = true; } } - if (!isStartRowAndColSet && modelStartRow == i) + if (!isStartRowAndColSetted && modelStartRow == i) { if (nStartCol + wrapLine.Count <= modelStartCol) { @@ -1833,12 +1833,12 @@ public TextModel WrapModel ( if (nStartCol == modelStartCol) { nStartCol = wrapLine.Count; - isStartRowAndColSet = true; + isStartRowAndColSetted = true; } else if (j == wrappedLines.Count - 1) { nStartCol = wrapLine.Count - j + modelStartCol - nStartCol; - isStartRowAndColSet = true; + isStartRowAndColSetted = true; } } else @@ -1846,7 +1846,7 @@ public TextModel WrapModel ( int offset = nStartCol + wrapLine.Count - modelStartCol; nStartCol = wrapLine.Count - offset; nStartRow = lines; - isStartRowAndColSet = true; + isStartRowAndColSetted = true; } } @@ -2547,14 +2547,14 @@ public bool AllowsReturn if (_allowsReturn && !_multiline) { - // BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsReturn should be independent. + // BUGBUG: Seting properties should not have side-effects like this. Multiline and AllowsReturn should be independent. Multiline = true; } if (!_allowsReturn && _multiline) { Multiline = false; - // BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsTab should be independent. + // BUGBUG: Seting properties should not have side-effects like this. Multiline and AlowsTab should be independent. AllowsTab = false; } diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 54ec8a4d2a..78ffd9b5ff 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -935,7 +935,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { // Continue Drag - // how far has user dragged from original location? + // how far has user dragged from original location? if (Orientation == Orientation.Horizontal) { int dy = mouseEvent.Position.Y - dragPosition.Value.Y; diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index 5f303cc98b..b7aad9fec9 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -449,13 +449,13 @@ private void TextField_TextChanging (object sender, CancelEventArgs e) } spaces += FieldLength; - string trimmedText = e.NewValue [..spaces]; + string trimedText = e.NewValue [..spaces]; spaces -= FieldLength; - trimmedText = trimmedText.Replace (new string (' ', spaces), " "); + trimedText = trimedText.Replace (new string (' ', spaces), " "); - if (trimmedText != e.NewValue) + if (trimedText != e.NewValue) { - e.NewValue = trimmedText; + e.NewValue = trimedText; } if (!TimeSpan.TryParseExact ( diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index 0b37314cdd..7619a815fd 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -93,7 +93,7 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, tree.Move (0, y); - // if we have scrolled to the right then bits of the prefix will have disappeared off the screen + // if we have scrolled to the right then bits of the prefix will have dispeared off the screen int toSkip = tree.ScrollOffsetHorizontal; Attribute attr = symbolColor; @@ -329,7 +329,7 @@ public void Refresh (bool startAtTop) Parent?.Refresh (true); } - // we don't want to lose the state of our children so lets be selective about how we refresh + // we don't want to loose the state of our children so lets be selective about how we refresh //if we don't know about any children yet just use the normal method if (ChildBranches is null) { @@ -347,7 +347,7 @@ public void Refresh (bool startAtTop) { ChildBranches.Remove (toRemove); - //also if the user has this node selected (its disappearing) so lets change selection to us (the parent object) to be helpful + //also if the user has this node selected (its disapearing) so lets change selection to us (the parent object) to be helpful if (Equals (tree.SelectedObject, toRemove)) { tree.SelectedObject = Model; @@ -357,14 +357,14 @@ public void Refresh (bool startAtTop) // New children need to be added foreach (T newChild in newChildren) { - // If we don't know about the child, yet we need a new branch + // If we don't know about the child yet we need a new branch if (!ChildBranches.ContainsKey (newChild)) { ChildBranches.Add (newChild, new Branch (tree, this, newChild)); } else { - //we already have this object but update the reference anyway in case Equality match but the references are new + //we already have this object but update the reference anyway incase Equality match but the references are new ChildBranches [newChild].Model = newChild; } } @@ -486,7 +486,7 @@ internal void Rebuild () { if (IsExpanded) { - // if we are expanded we need to update the visible children + //if we are expanded we need to updatethe visible children foreach (KeyValuePair> child in ChildBranches) { child.Value.Rebuild (); @@ -515,7 +515,7 @@ private IEnumerable> GetParentBranches () } /// - /// Returns true if this branch has parents, and it is the last node of its parents branches (or last root of the + /// Returns true if this branch has parents and it is the last node of it's parents branches (or last root of the /// tree). /// /// diff --git a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs index 1454abff7e..f0a9e5e7e5 100644 --- a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs +++ b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs @@ -19,7 +19,7 @@ public class DrawTreeViewLineEventArgs where T : class /// /// The notional index in which contains the first character of the - /// text (i.e. after all branch lines and expansion/collapse symbols). + /// text (i.e. after all branch lines and expansion/collapse sybmols). /// /// May be negative or outside of bounds of if the view has been scrolled horizontally. public int IndexOfModelText { get; init; } diff --git a/Terminal.Gui/Views/TreeView/TreeNode.cs b/Terminal.Gui/Views/TreeView/TreeNode.cs index 351e616208..e996beb507 100644 --- a/Terminal.Gui/Views/TreeView/TreeNode.cs +++ b/Terminal.Gui/Views/TreeView/TreeNode.cs @@ -1,7 +1,7 @@ namespace Terminal.Gui; /// -/// Interface to implement when you want the regular (non-generic) to automatically +/// Interface to implement when you want the regular (non generic) to automatically /// determine children for your class (without having to specify an ) /// public interface ITreeNode @@ -17,7 +17,7 @@ public interface ITreeNode string Text { get; set; } } -/// Simple class for representing nodes, use with regular (non-generic) . +/// Simple class for representing nodes, use with regular (non generic) . public class TreeNode : ITreeNode { /// Initialises a new instance with no @@ -39,5 +39,5 @@ public TreeNode () { } /// returns /// - public override string ToString () { return Text ?? "Unnamed Node"; } + public override string ToString () { return Text ?? "Unamed Node"; } } diff --git a/Terminal.Gui/Views/TreeView/TreeStyle.cs b/Terminal.Gui/Views/TreeView/TreeStyle.cs index 0b560f2a9e..df8772dbd3 100644 --- a/Terminal.Gui/Views/TreeView/TreeStyle.cs +++ b/Terminal.Gui/Views/TreeView/TreeStyle.cs @@ -29,7 +29,7 @@ public class TreeStyle public bool InvertExpandSymbolColors { get; set; } /// - /// to leave the last row of the control free for overwriting (e.g. by a scrollbar) When + /// to leave the last row of the control free for overwritting (e.g. by a scrollbar) When /// scrolling will be triggered on the second last row of the control rather than. the last. /// /// diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index f2039d9c6e..d2e3d02938 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -35,7 +35,7 @@ public class TreeView : TreeView public TreeView () { TreeBuilder = new TreeNodeBuilder (); - AspectGetter = o => o is null ? "Null" : o.Text ?? o?.ToString () ?? "Unnamed Node"; + AspectGetter = o => o is null ? "Null" : o.Text ?? o?.ToString () ?? "Unamed Node"; } } @@ -72,7 +72,7 @@ public class TreeView : View, ITreeView where T : class private T selectedObject; /// - /// Creates a new tree view with absolute positioning. Use to set + /// Creates a new tree view with absolute positioning. Use to set set /// root objects for the tree. Children will not be rendered until you set . /// public TreeView () @@ -299,7 +299,7 @@ public TreeView () /// /// Initialises .Creates a new tree view with absolute positioning. Use - /// to set root objects for the tree. + /// to set set root objects for the tree. /// public TreeView (ITreeBuilder builder) : this () { TreeBuilder = builder; } @@ -315,7 +315,7 @@ public TreeView () public AspectGetterDelegate AspectGetter { get; set; } = o => o.ToString () ?? ""; /// - /// Delegate for multi-colored tree views. Return the to use for each passed object or + /// Delegate for multi colored tree views. Return the to use for each passed object or /// null to use the default. /// public Func ColorGetter { get; set; } @@ -374,7 +374,7 @@ public int ScrollOffsetHorizontal /// The amount of tree view that has been scrolled off the top of the screen (by the user scrolling down). /// - /// Setting a value of less than 0 will result in an offset of 0. To see changes in the UI call + /// Setting a value of less than 0 will result in a offset of 0. To see changes in the UI call /// . /// public int ScrollOffsetVertical @@ -402,7 +402,7 @@ public T SelectedObject } } - /// Determines how sub-branches of the tree are dynamically built at runtime as the user expands root nodes. + /// Determines how sub branches of the tree are dynamically built at runtime as the user expands root nodes. /// public ITreeBuilder TreeBuilder { get; set; } @@ -500,7 +500,7 @@ public void AddObjects (IEnumerable collection) /// public void AdjustSelection (int offset, bool expandSelection = false) { - // if it is not a shift click, or we don't allow multi select + // if it is not a shift click or we don't allow multi select if (!expandSelection || !MultiSelect) { multiSelectedRegions.Clear (); @@ -518,7 +518,7 @@ public void AdjustSelection (int offset, bool expandSelection = false) if (idx == -1) { - // The current selection has disappeared! + // The current selection has disapeared! SelectedObject = roots.Keys.FirstOrDefault (); } else @@ -1090,7 +1090,7 @@ protected internal override bool OnMouseEvent (MouseEvent me) SelectedObject = clickedBranch.Model; SetNeedsDisplay (); - // trigger activation event + // trigger activation event OnObjectActivated (new ObjectActivatedEventArgs (this, clickedBranch.Model)); // mouse event is handled. @@ -1324,7 +1324,7 @@ public void SelectAll () /// /// Implementation of and . Performs operation and updates - /// selection if disappeared. + /// selection if disapeared. /// /// /// diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index 70aeeeb5f7..75377c8b4b 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -195,7 +195,7 @@ public void AddStep (WizardStep newStep) } /// - /// Raised when the user has cancelled the by pressing the Esc key. To prevent a modal ( + /// Raised when the user has cancelled the by pressin the Esc key. To prevent a modal ( /// is true) Wizard from closing, cancel the event by setting /// to true before returning from the event handler. /// @@ -302,7 +302,7 @@ public WizardStep GetPreviousStep () } /// - /// Causes the wizard to move to the previous enabled step (or first step if is not set). + /// Causes the wizad to move to the previous enabled step (or first step if is not set). /// If there is no previous step, does nothing. /// public void GoBack () @@ -316,7 +316,7 @@ public void GoBack () } /// - /// Causes the wizard to move to the next enabled step (or last step if is not set). If + /// Causes the wizad to move to the next enabled step (or last step if is not set). If /// there is no previous step, does nothing. /// public void GoNext () @@ -388,7 +388,7 @@ public bool GoToStep (WizardStep newStep) /// is derived from and Dialog causes Esc to call /// , closing the Dialog. Wizard overrides /// to instead fire the event when Wizard is being used as a - /// non-modal (see ). + /// non-modal (see . /// /// /// diff --git a/UnitTests/Views/TabTests.cs b/UnitTests/Views/TabTests.cs index a4de58bb0c..1e7ff7bab4 100644 --- a/UnitTests/Views/TabTests.cs +++ b/UnitTests/Views/TabTests.cs @@ -6,7 +6,7 @@ public class TabTests public void Constructor_Defaults () { var tab = new Tab (); - Assert.Equal ("Unnamed", tab.DisplayText); + Assert.Equal ("Unamed", tab.DisplayText); Assert.Null (tab.View); Assert.Equal (LineStyle.Rounded, tab.BorderStyle); Assert.True (tab.CanFocus); From 3ed30767a02b1b022eddd1967a3ec918517db14b Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 10:00:54 -0600 Subject: [PATCH 41/96] Addressed review feedback from @dodexahedron. --- Terminal.Gui/Text/TextFormatter.cs | 255 ++++++++++---------- Terminal.Gui/View/Layout/Dim.cs | 5 +- Terminal.Gui/View/Layout/Pos.cs | 10 +- UnitTests/View/Layout/Dim.PercentTests.cs | 4 +- UnitTests/View/Layout/Pos.AnchorEndTests.cs | 2 +- UnitTests/View/Layout/Pos.PercentTests.cs | 2 +- 6 files changed, 130 insertions(+), 148 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 994eeb007e..5e29ac0676 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -54,7 +54,10 @@ public bool AutoSize if (_autoSize) { - Size = GetAutoSize (); + Size size = CalcRect (0, 0, Text, Direction, TabWidth).Size; + + Width = size.Width - GetHotKeySpecifierLength (); + Height = size.Height - GetHotKeySpecifierLength (false); } else { @@ -619,75 +622,80 @@ public int GetHotKeySpecifierLength (bool isWidth = true) /// public List GetLines () { + int width = _width.GetValueOrDefault (); + int height = _height.GetValueOrDefault (); + // With this check, we protect against subclasses with overrides of Text - if (string.IsNullOrEmpty (Text) || Width is null || Width == 0 || Height is null || Height == 0) + if (string.IsNullOrEmpty (Text) || width == 0 || height == 0) { - _lines = new() { string.Empty }; + _lines = [string.Empty]; NeedsFormat = false; return _lines; } - if (NeedsFormat) + if (!NeedsFormat) { - string text = _text!; + return _lines; + } - if (FindHotKey (_text!, HotKeySpecifier, out _hotKeyPos, out Key newHotKey)) - { - HotKey = newHotKey; - text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier); - text = ReplaceHotKeyWithTag (text, _hotKeyPos); - } + string text = _text!; - if (IsVerticalDirection (Direction)) + if (FindHotKey (_text!, HotKeySpecifier, out _hotKeyPos, out Key newHotKey)) + { + HotKey = newHotKey; + text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier); + text = ReplaceHotKeyWithTag (text, _hotKeyPos); + } + + if (IsVerticalDirection (Direction)) + { + int colsWidth = GetSumMaxCharWidth (text, 0, 1, TabWidth); + + _lines = Format ( + text, + height, + VerticalAlignment == Alignment.Fill, + width > colsWidth && WordWrap, + PreserveTrailingSpaces, + TabWidth, + Direction, + MultiLine, + this + ); + + if (!AutoSize) { - int colsWidth = GetSumMaxCharWidth (text, 0, 1, TabWidth); - - _lines = Format ( - text, - Height.Value, - VerticalAlignment == Alignment.Fill, - Width.Value > colsWidth && WordWrap, - PreserveTrailingSpaces, - TabWidth, - Direction, - MultiLine, - this - ); - - if (!AutoSize) - { - colsWidth = GetMaxColsForWidth (_lines, Width.Value, TabWidth); + colsWidth = GetMaxColsForWidth (_lines, width, TabWidth); - if (_lines.Count > colsWidth) - { - _lines.RemoveRange (colsWidth, _lines.Count - colsWidth); - } + if (_lines.Count > colsWidth) + { + _lines.RemoveRange (colsWidth, _lines.Count - colsWidth); } } - else + } + else + { + _lines = Format ( + text, + width, + Alignment == Alignment.Fill, + height > 1 && WordWrap, + PreserveTrailingSpaces, + TabWidth, + Direction, + MultiLine, + this + ); + + if (!AutoSize && _lines.Count > height) { - _lines = Format ( - text, - Width.Value, - Alignment == Alignment.Fill, - Height.Value > 1 && WordWrap, - PreserveTrailingSpaces, - TabWidth, - Direction, - MultiLine, - this - ); - - if (!AutoSize && _lines.Count > Height.Value) - { - _lines.RemoveRange (Height.Value, _lines.Count - Height.Value); - } + _lines.RemoveRange (height, _lines.Count - height); } - - NeedsFormat = false; } + NeedsFormat = false; + return _lines; } @@ -713,10 +721,7 @@ public int? Height return; } - if (value < 0) - { - throw new ArgumentOutOfRangeException (nameof (Height), value, @"Must be greater than or equal to 0."); - } + ArgumentOutOfRangeException.ThrowIfNegative (value.GetValueOrDefault (), nameof (Height)); _height = value; @@ -854,7 +859,6 @@ public string Text if (AutoSize) { Size = GetAutoSize (); - ; } } } @@ -888,10 +892,7 @@ public int? Width return; } - if (value < 0) - { - throw new ArgumentOutOfRangeException (nameof (Width), value, @"Must be greater than or equal to 0."); - } + ArgumentOutOfRangeException.ThrowIfNegative (value.GetValueOrDefault (), nameof (Width)); _width = value; @@ -939,48 +940,48 @@ private T EnableNeedsFormat (T value) public static bool IsHorizontalDirection (TextDirection textDirection) { return textDirection switch - { - TextDirection.LeftRight_TopBottom => true, - TextDirection.LeftRight_BottomTop => true, - TextDirection.RightLeft_TopBottom => true, - TextDirection.RightLeft_BottomTop => true, - _ => false - }; + { + TextDirection.LeftRight_TopBottom => true, + TextDirection.LeftRight_BottomTop => true, + TextDirection.RightLeft_TopBottom => true, + TextDirection.RightLeft_BottomTop => true, + _ => false + }; } /// Check if it is a vertical direction public static bool IsVerticalDirection (TextDirection textDirection) { return textDirection switch - { - TextDirection.TopBottom_LeftRight => true, - TextDirection.TopBottom_RightLeft => true, - TextDirection.BottomTop_LeftRight => true, - TextDirection.BottomTop_RightLeft => true, - _ => false - }; + { + TextDirection.TopBottom_LeftRight => true, + TextDirection.TopBottom_RightLeft => true, + TextDirection.BottomTop_LeftRight => true, + TextDirection.BottomTop_RightLeft => true, + _ => false + }; } /// Check if it is Left to Right direction public static bool IsLeftToRight (TextDirection textDirection) { return textDirection switch - { - TextDirection.LeftRight_TopBottom => true, - TextDirection.LeftRight_BottomTop => true, - _ => false - }; + { + TextDirection.LeftRight_TopBottom => true, + TextDirection.LeftRight_BottomTop => true, + _ => false + }; } /// Check if it is Top to Bottom direction public static bool IsTopToBottom (TextDirection textDirection) { return textDirection switch - { - TextDirection.TopBottom_LeftRight => true, - TextDirection.TopBottom_RightLeft => true, - _ => false - }; + { + TextDirection.TopBottom_LeftRight => true, + TextDirection.TopBottom_RightLeft => true, + _ => false + }; } // TODO: Move to StringExtensions? @@ -1191,10 +1192,7 @@ public static List WordWrapText ( TextFormatter? textFormatter = null ) { - if (width < 0) - { - throw new ArgumentOutOfRangeException ($"{nameof (width)} cannot be negative."); - } + ArgumentOutOfRangeException.ThrowIfNegative (width, nameof (width)); List lines = new (); @@ -1372,21 +1370,21 @@ int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = case ' ': return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); case '\t': - { - length += tabWidth + 1; - - if (length == tabWidth && tabWidth > cWidth) { - return to + 1; - } + length += tabWidth + 1; - if (length > cWidth && tabWidth > cWidth) - { - return to; - } + if (length == tabWidth && tabWidth > cWidth) + { + return to + 1; + } - return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); - } + if (length > cWidth && tabWidth > cWidth) + { + return to; + } + + return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); + } default: to++; @@ -1395,11 +1393,11 @@ int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = } return cLength switch - { - > 0 when to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t' => from, - > 0 when to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t') => from, - _ => to - }; + { + > 0 when to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t' => from, + > 0 when to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t') => from, + _ => to + }; } if (start < text.GetRuneCount ()) @@ -1461,10 +1459,7 @@ public static string ClipAndJustify ( TextFormatter? textFormatter = null ) { - if (width < 0) - { - throw new ArgumentOutOfRangeException ($"{nameof (width)} cannot be negative."); - } + ArgumentOutOfRangeException.ThrowIfNegative (width, nameof (width)); if (string.IsNullOrEmpty (text)) { @@ -1584,10 +1579,8 @@ public static string Justify ( int tabWidth = 0 ) { - if (width < 0) - { - throw new ArgumentOutOfRangeException ($"{nameof (width)} cannot be negative."); - } + ArgumentOutOfRangeException.ThrowIfNegative (width, nameof (width)); + if (string.IsNullOrEmpty (text)) { @@ -1730,10 +1723,8 @@ public static List Format ( TextFormatter? textFormatter = null ) { - if (width < 0) - { - throw new ArgumentOutOfRangeException ($"{nameof (width)} cannot be negative."); - } + ArgumentOutOfRangeException.ThrowIfNegative (width, nameof (width)); + List lineResult = new (); @@ -1844,13 +1835,13 @@ public static List Format ( private static string PerformCorrectFormatDirection (TextDirection textDirection, string line) { return textDirection switch - { - TextDirection.RightLeft_BottomTop - or TextDirection.RightLeft_TopBottom - or TextDirection.BottomTop_LeftRight - or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), - _ => line - }; + { + TextDirection.RightLeft_BottomTop + or TextDirection.RightLeft_TopBottom + or TextDirection.BottomTop_LeftRight + or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), + _ => line + }; } private static List PerformCorrectFormatDirection (TextDirection textDirection, List runes) @@ -1861,13 +1852,13 @@ private static List PerformCorrectFormatDirection (TextDirection textDirec private static List PerformCorrectFormatDirection (TextDirection textDirection, List lines) { return textDirection switch - { - TextDirection.TopBottom_RightLeft - or TextDirection.LeftRight_BottomTop - or TextDirection.RightLeft_BottomTop - or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), - _ => lines - }; + { + TextDirection.TopBottom_RightLeft + or TextDirection.LeftRight_BottomTop + or TextDirection.RightLeft_BottomTop + or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), + _ => lines + }; } /// diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index a0ad0deb95..26adde4f39 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -160,10 +160,7 @@ public abstract class Dim /// public static Dim? Percent (int percent, DimPercentMode mode = DimPercentMode.ContentSize) { - if (percent is < 0 /*or > 100*/) - { - throw new ArgumentException ("Percent value must be positive."); - } + ArgumentOutOfRangeException.ThrowIfNegative (percent, nameof (percent)); return new DimPercent (percent, mode); } diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index 2213524aee..99c5229024 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -197,10 +197,7 @@ public static Pos Align (Alignment alignment, AlignmentModes modes = AlignmentMo /// public static Pos AnchorEnd (int offset) { - if (offset < 0) - { - throw new ArgumentException (@"Must be positive", nameof (offset)); - } + ArgumentOutOfRangeException.ThrowIfNegative (offset, nameof (offset)); return new PosAnchorEnd (offset); } @@ -246,10 +243,7 @@ public static Pos AnchorEnd (int offset) /// public static Pos Percent (int percent) { - if (percent is < 0) - { - throw new ArgumentException ("Percent value must be positive."); - } + ArgumentOutOfRangeException.ThrowIfNegative (percent, nameof (percent)); return new PosPercent (percent); } diff --git a/UnitTests/View/Layout/Dim.PercentTests.cs b/UnitTests/View/Layout/Dim.PercentTests.cs index c5bcb25d35..adf23b46cd 100644 --- a/UnitTests/View/Layout/Dim.PercentTests.cs +++ b/UnitTests/View/Layout/Dim.PercentTests.cs @@ -67,9 +67,9 @@ public void DimPercent_Equals () public void DimPercent_Invalid_Throws () { Dim dim = Dim.Percent (0); - Assert.Throws (() => dim = Dim.Percent (-1)); + Assert.Throws (() => dim = Dim.Percent (-1)); //Assert.Throws (() => dim = Dim.Percent (101)); - Assert.Throws (() => dim = Dim.Percent (-1000001)); + Assert.Throws (() => dim = Dim.Percent (-1000001)); //Assert.Throws (() => dim = Dim.Percent (1000001)); } diff --git a/UnitTests/View/Layout/Pos.AnchorEndTests.cs b/UnitTests/View/Layout/Pos.AnchorEndTests.cs index 4309ee858c..2d3fca6027 100644 --- a/UnitTests/View/Layout/Pos.AnchorEndTests.cs +++ b/UnitTests/View/Layout/Pos.AnchorEndTests.cs @@ -67,7 +67,7 @@ public void PosAnchorEnd_Negative_Throws () { Pos pos; int n = -1; - Assert.Throws (() => pos = Pos.AnchorEnd (n)); + Assert.Throws (() => pos = Pos.AnchorEnd (n)); } [Theory] diff --git a/UnitTests/View/Layout/Pos.PercentTests.cs b/UnitTests/View/Layout/Pos.PercentTests.cs index d016c709c8..a325fe514e 100644 --- a/UnitTests/View/Layout/Pos.PercentTests.cs +++ b/UnitTests/View/Layout/Pos.PercentTests.cs @@ -78,7 +78,7 @@ public void PosPercent_SetsValue () public void PosPercent_ThrowsOnIvalid () { Pos pos = Percent (0); - Assert.Throws (() => pos = Percent (-1)); + Assert.Throws (() => pos = Percent (-1)); //Assert.Throws (() => pos = Pos.Percent (101)); //Assert.Throws (() => pos = Pos.Percent (1000001)); From 6550cde7b50128353ed5a6f501c1941c44ff4fbc Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 11:17:46 -0600 Subject: [PATCH 42/96] Nuked Textformatter.AutoSize. Removed references to AutoSize throughout. Updated unit tests. --- Terminal.Gui/Drawing/Thickness.cs | 6 +- Terminal.Gui/Text/TextFormatter.cs | 96 +- Terminal.Gui/Views/Bar.cs | 2 +- Terminal.Gui/Views/Menu/Menu.cs | 3 +- Terminal.Gui/Views/MessageBox.cs | 17 - Terminal.Gui/Views/ProgressBar.cs | 2 +- .../Scenarios/TextAlignmentAndDirection.cs | 36 +- UICatalog/Scenarios/ViewExperiments.cs | 7 +- UnitTests/Text/TextFormatterTests.cs | 2923 +++++++++++------ UnitTests/View/DrawTests.cs | 1 - UnitTests/View/Layout/Dim.AutoTests.cs | 14 - UnitTests/View/Layout/Pos.Tests.cs | 2 - UnitTests/View/TextTests.cs | 80 +- UnitTests/View/ViewTests.cs | 1 - UnitTests/Views/LabelTests.cs | 110 +- 15 files changed, 2046 insertions(+), 1254 deletions(-) diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 532c0af8a3..b787738ab5 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -190,12 +190,14 @@ rect with if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.Padding)) { // Draw the diagnostics label on the bottom + string text = label is null ? string.Empty : $"{label} {this}"; var tf = new TextFormatter { - Text = label is null ? string.Empty : $"{label} {this}", + Text = text, Alignment = Alignment.Center, VerticalAlignment = Alignment.End, - AutoSize = true + Width = text.GetColumns (), + Height = 1 }; tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect); } diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 5e29ac0676..fc5e65d9c7 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -9,7 +9,6 @@ namespace Terminal.Gui; /// public class TextFormatter { - private bool _autoSize; private Key _hotKey = new (); private int _hotKeyPos = -1; private List _lines = new (); @@ -34,39 +33,6 @@ public Alignment Alignment set => _textAlignment = EnableNeedsFormat (value); } - /// Gets or sets whether the should be automatically changed to fit the . - /// - /// - /// Used when is using to resize the view's - /// to fit . - /// - /// - /// AutoSize is ignored if is used. - /// - /// - [Obsolete ("AutoSize is deprecated, use Width and Height instead.")] - public bool AutoSize - { - get => _autoSize; - set - { - _autoSize = EnableNeedsFormat (value); - - if (_autoSize) - { - Size size = CalcRect (0, 0, Text, Direction, TabWidth).Size; - - Width = size.Width - GetHotKeySpecifierLength (); - Height = size.Height - GetHotKeySpecifierLength (false); - } - else - { - Height = null; - Width = null; - } - } - } - /// /// Gets the cursor position of the . If the is defined, the cursor will /// be positioned over it. @@ -81,11 +47,6 @@ public TextDirection Direction set { _textDirection = EnableNeedsFormat (value); - - if (AutoSize) - { - Size = GetAutoSize (); - } } } @@ -664,14 +625,11 @@ public List GetLines () this ); - if (!AutoSize) - { - colsWidth = GetMaxColsForWidth (_lines, width, TabWidth); + colsWidth = GetMaxColsForWidth (_lines, width, TabWidth); - if (_lines.Count > colsWidth) - { - _lines.RemoveRange (colsWidth, _lines.Count - colsWidth); - } + if (_lines.Count > colsWidth) + { + _lines.RemoveRange (colsWidth, _lines.Count - colsWidth); } } else @@ -688,7 +646,7 @@ public List GetLines () this ); - if (!AutoSize && _lines.Count > height) + if (_lines.Count > height) { _lines.RemoveRange (height, _lines.Count - height); } @@ -817,26 +775,17 @@ public Size? Size } set { - if (AutoSize) + if (value is null) { - Size size = EnableNeedsFormat (GetAutoSize ()); - _width = size.Width; - _height = size.Height; + _width = null; + _height = null; + EnableNeedsFormat (true); } else { - if (value is null) - { - _width = null; - _height = null; - EnableNeedsFormat (true); - } - else - { - Size size = EnableNeedsFormat (value.Value); - _width = size.Width; - _height = size.Height; - } + Size size = EnableNeedsFormat (value.Value); + _width = size.Width; + _height = size.Height; } } } @@ -852,15 +801,7 @@ public int TabWidth public string Text { get => _text!; - set - { - _text = EnableNeedsFormat (value); - - if (AutoSize) - { - Size = GetAutoSize (); - } - } + set => _text = EnableNeedsFormat (value); } /// Gets or sets the vertical text-alignment. @@ -912,17 +853,6 @@ public bool WordWrap set => _wordWrap = EnableNeedsFormat (value); } - private Size GetAutoSize () - { - Size size = CalcRect (0, 0, Text, Direction, TabWidth).Size; - - return size with - { - Width = size.Width - GetHotKeySpecifierLength (), - Height = size.Height - GetHotKeySpecifierLength (false) - }; - } - /// Sets to and returns the value. /// /// diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 92262371b8..d9c7946912 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -165,7 +165,7 @@ internal override void OnLayoutStarted (LayoutEventArgs args) List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); foreach (Shortcut shortcut in shortcuts) { - // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView + // Let DimAuto do its thing to get the minimum width of each CommandView and HelpView //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); } diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 34e8dab30d..7ae998ee00 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -489,7 +489,8 @@ public override void OnDrawContent (Rectangle viewport) { var tf = new TextFormatter { - AutoSize = true, + Width = Frame.Width - 3, + Height = 1, Alignment = Alignment.Center, HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw }; diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 5d583250b0..a9d0b542ac 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -385,23 +385,6 @@ params string [] buttons d.ColorScheme = useErrorColors ? Colors.ColorSchemes ["Error"] : Colors.ColorSchemes ["Dialog"]; - //d.LayoutComplete += (s, e) => - //{ - // if (wrapMessage) - // { - // int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; - // Debug.Assert (d.TextFormatter.WordWrap); - // d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Screen.Height); - // Size textSize = d.TextFormatter.GetAutoSize (); - // textSize.Height += buttonHeight; - - // if (textSize != d.TextFormatter.Size) - // { - // d.SetNeedsLayout (); - // } - // } - //}; - d.HotKeySpecifier = new Rune ('\xFFFF'); d.Text = message; d.TextAlignment = Alignment.Center; diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index b7afd2459a..a205bb8305 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -175,7 +175,7 @@ public override void OnDrawContent (Rectangle viewport) if (ProgressBarFormat != ProgressBarFormat.Simple && !_isActivity) { - var tf = new TextFormatter { Alignment = Alignment.Center, Text = Text, AutoSize = true }; + var tf = new TextFormatter { Alignment = Alignment.Center, Text = Text }; var attr = new Attribute (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Background); if (_fraction > .5) diff --git a/UICatalog/Scenarios/TextAlignmentAndDirection.cs b/UICatalog/Scenarios/TextAlignmentAndDirection.cs index 117d641204..6861aed8d5 100644 --- a/UICatalog/Scenarios/TextAlignmentAndDirection.cs +++ b/UICatalog/Scenarios/TextAlignmentAndDirection.cs @@ -522,46 +522,12 @@ public override void Main () app.Add (wrapCheckbox); - // AUTOSIZE CHECKBOX - - var autoSizeCheckbox = new CheckBox - { - X = Pos.Right (container) + 1, - Y = Pos.Y (wrapCheckbox) + 1, - Width = Dim.Fill (10), - Height = 1, - Text = "AutoSize" - }; - autoSizeCheckbox.State = autoSizeCheckbox.TextFormatter.AutoSize ? CheckState.Checked : CheckState.UnChecked; - - autoSizeCheckbox.Toggle += (s, e) => - { - if (e.CurrentValue == CheckState.Checked) - { - foreach (Label t in multiLineLabels) - { - t.TextFormatter.AutoSize = false; - } - } - else - { - foreach (Label t in multiLineLabels) - { - t.TextFormatter.AutoSize = true; - } - } - }; - - app.Add (autoSizeCheckbox); - - // Direction Options - List directionsEnum = Enum.GetValues (typeof (TextDirection)).Cast ().ToList (); var directionOptions = new RadioGroup { X = Pos.Right (container) + 1, - Y = Pos.Bottom (autoSizeCheckbox) + 1, + Y = Pos.Bottom (wrapCheckbox) + 1, Width = Dim.Fill (10), Height = Dim.Fill (1), HotKeySpecifier = (Rune)'\xffff', diff --git a/UICatalog/Scenarios/ViewExperiments.cs b/UICatalog/Scenarios/ViewExperiments.cs index 5d08f0aa6c..adb9ab12b0 100644 --- a/UICatalog/Scenarios/ViewExperiments.cs +++ b/UICatalog/Scenarios/ViewExperiments.cs @@ -151,13 +151,10 @@ public override void Main () view.Add (view5); - var label = new Label { Text = "AutoSize true; 1;1:", X = 1, Y = 1 }; - view.Add (label); - var edit = new TextField { - Text = "Right (label)", - X = Pos.Right (label), + Text = "Right (view5)", + X = Pos.Right (view5), Y = 1, Width = 15, Height = 1 diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 31f4d2b646..3ae1e655c5 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -1,7 +1,5 @@ using System.Text; -using Microsoft.VisualStudio.TestPlatform.Utilities; using Xunit.Abstractions; -using static Terminal.Gui.SpinnerStyle; // Alias Console to MockConsole so we don't accidentally use Console @@ -41,55 +39,6 @@ public class TextFormatterTests } }; - [Fact] - public void Basic_Usage_With_AutoSize_True () - { - var testText = "test"; - var testBounds = new Rectangle (0, 0, 100, 1); - var tf = new TextFormatter (); - - // Manually set AutoSize to true - tf.AutoSize = true; - - tf.Text = testText; - Size expectedSize = new (testText.Length, 1); - Assert.Equal (testText, tf.Text); - Assert.Equal (Alignment.Start, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.GetLines ()); - - tf.Alignment = Alignment.End; - expectedSize = new (testText.Length, 1); - Assert.Equal (testText, tf.Text); - Assert.Equal (Alignment.End, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.GetLines ()); - - tf.Alignment = Alignment.End; - expectedSize = new (testText.Length, 1); - tf.Size = expectedSize; - Assert.Equal (testText, tf.Text); - Assert.Equal (Alignment.End, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.GetLines ()); - - tf.Alignment = Alignment.Center; - expectedSize = new (testText.Length, 1); - tf.Size = expectedSize; - Assert.Equal (testText, tf.Text); - Assert.Equal (Alignment.Center, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.GetLines ()); - } - [Theory] [InlineData (null)] [InlineData ("")] @@ -169,7 +118,7 @@ public void CalcRect_With_Combining_Runes (int width, int height, TextDirection public void CalcRect_Horizontal_Width_Correct (string text, TextDirection textDirection) { // The width is the number of columns in the text - Assert.Equal (new Size (text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text, textDirection).Size); + Assert.Equal (new (text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text, textDirection).Size); } [Theory] @@ -182,7 +131,7 @@ public void CalcRect_Horizontal_Width_Correct (string text, TextDirection textDi public void CalcRect_Vertical_Height_Correct (string text, TextDirection textDirection) { // The height is based both the number of lines and the number of wide chars - Assert.Equal (new Size (1 + text.GetColumns () - text.Length, text.Length), TextFormatter.CalcRect (0, 0, text, textDirection).Size); + Assert.Equal (new (1 + text.GetColumns () - text.Length, text.Length), TextFormatter.CalcRect (0, 0, text, textDirection).Size); } [Theory] @@ -438,8 +387,8 @@ public void Draw_With_Combining_Runes (int width, int height, TextDirection text tf.Draw ( new (0, 0, width, height), - new Attribute (ColorName.White, ColorName.Black), - new Attribute (ColorName.Blue, ColorName.Black), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), default (Rectangle), driver ); @@ -456,8 +405,8 @@ public void FillRemaining_True_False () Attribute [] attrs = { - Attribute.Default, new Attribute (ColorName.Green, ColorName.BrightMagenta), - new Attribute (ColorName.Blue, ColorName.Cyan) + Attribute.Default, new (ColorName.Green, ColorName.BrightMagenta), + new (ColorName.Blue, ColorName.Cyan) }; var tf = new TextFormatter { Size = new (14, 3), Text = "Test\nTest long\nTest long long\n", MultiLine = true }; @@ -786,7 +735,7 @@ string justifiedText for (int i = text.GetRuneCount (); i < maxWidth; i++) { - fmtText = TextFormatter.Format (text, i, Alignment.Fill, false, true) [0]; + fmtText = TextFormatter.Format (text, i, Alignment.Fill, true) [0]; Assert.Equal (i, fmtText.GetRuneCount ()); char c = fmtText [^1]; Assert.True (text.EndsWith (c)); @@ -984,7 +933,6 @@ public void GetMaxColsForWidth_With_Combining_Runes () Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1)); } - [Theory] [InlineData (new [] { "0123456789" }, 1)] [InlineData (new [] { "Hello World" }, 1)] @@ -993,7 +941,6 @@ public void GetMaxColsForWidth_With_Combining_Runes () public void GetColumnsRequiredForVerticalText_List_GetsWidth (IEnumerable text, int expectedWidth) { Assert.Equal (expectedWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList ())); - } [Theory] @@ -1220,7 +1167,6 @@ IEnumerable resultLines Text = text, Size = new (maxWidth, maxHeight), WordWrap = false, MultiLine = multiLine }; - Assert.False (tf.AutoSize); Assert.False (tf.WordWrap); Assert.True (tf.MultiLine == multiLine); Assert.Equal (TextDirection.LeftRight_TopBottom, tf.Direction); @@ -1307,7 +1253,6 @@ IEnumerable resultLines Direction = TextDirection.TopBottom_LeftRight }; - Assert.False (tf.AutoSize); Assert.False (tf.WordWrap); Assert.True (tf.MultiLine == multiLine); Assert.Equal (TextDirection.TopBottom_LeftRight, tf.Direction); @@ -1329,7 +1274,7 @@ public void NeedsFormat_Sets () Assert.NotEmpty (tf.GetLines ()); Assert.False (tf.NeedsFormat); // get_Lines causes a Format Assert.Equal (testText, tf.Text); - tf.Draw (testBounds, new Attribute (), new Attribute ()); + tf.Draw (testBounds, new (), new ()); Assert.False (tf.NeedsFormat); tf.Size = new (1, 1); @@ -2059,7 +2004,7 @@ string expected if (char.IsHighSurrogate (text [index])) { // Rune array length isn't equal to string array - Assert.Equal (expected, new string (new [] { text [index], text [index + 1] })); + Assert.Equal (expected, new (new [] { text [index], text [index + 1] })); } else { @@ -2086,19 +2031,19 @@ string expected var text = "This is a \tTab"; var tf = new TextFormatter (); - tf.AutoSize = true; tf.Direction = textDirection; tf.TabWidth = tabWidth; tf.Text = text; + tf.Width = 20; + tf.Height = 20; Assert.True (tf.WordWrap); Assert.False (tf.PreserveTrailingSpaces); - Assert.Equal (new (width, height), tf.Size); tf.Draw ( new (0, 0, width, height), - new Attribute (ColorName.White, ColorName.Black), - new Attribute (ColorName.Blue, ColorName.Black), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), default (Rectangle), driver ); @@ -2125,20 +2070,20 @@ string expected var text = "This is a \tTab"; var tf = new TextFormatter (); - tf.AutoSize = true; tf.Direction = textDirection; tf.TabWidth = tabWidth; tf.PreserveTrailingSpaces = true; tf.Text = text; + tf.Width = 20; + tf.Height = 20; Assert.True (tf.WordWrap); - Assert.Equal (new (width, height), tf.Size); tf.Draw ( new (0, 0, width, height), - new Attribute (ColorName.White, ColorName.Black), - new Attribute (ColorName.Blue, ColorName.Black), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), default (Rectangle), driver ); @@ -2165,20 +2110,20 @@ string expected var text = "This is a \tTab"; var tf = new TextFormatter (); - tf.AutoSize = true; tf.Direction = textDirection; tf.TabWidth = tabWidth; tf.WordWrap = true; tf.Text = text; + tf.Width = 20; + tf.Height = 20; Assert.False (tf.PreserveTrailingSpaces); - Assert.Equal (new (width, height), tf.Size); tf.Draw ( new (0, 0, width, height), - new Attribute (ColorName.White, ColorName.Black), - new Attribute (ColorName.Blue, ColorName.Black), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), default (Rectangle), driver ); @@ -2206,42 +2151,17 @@ public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedTe } [Theory] + [InlineData ("你", TextDirection.LeftRight_TopBottom, 2, 1)] + [InlineData ("你", TextDirection.TopBottom_LeftRight, 2, 1)] [InlineData ("你你", TextDirection.LeftRight_TopBottom, 4, 1)] - [InlineData ("AB", TextDirection.LeftRight_TopBottom, 2, 1)] [InlineData ("你你", TextDirection.TopBottom_LeftRight, 2, 2)] - [InlineData ("AB", TextDirection.TopBottom_LeftRight, 1, 2)] - public void AutoSize_True_TextDirection_Correct_Size (string text, TextDirection textDirection, int expectedWidth, int expectedHeight) + public void Text_Set_SizeIsCorrect (string text, TextDirection textDirection, int expectedWidth, int expectedHeight) { var tf = new TextFormatter { Direction = textDirection, Text = text }; - Assert.False (tf.AutoSize); - - // If autosize is false, no auto sizing! - Assert.Null (tf.Size); + tf.Width = 10; + tf.Height = 10; - tf.Size = new (1, 1); // This should have no impact (autosize overrides) - tf.AutoSize = true; - - Assert.Equal (new Size (expectedWidth, expectedHeight), tf.Size); - } - [Theory] - [InlineData ("你", TextDirection.LeftRight_TopBottom, false, 0, 0)] - [InlineData ("你", TextDirection.LeftRight_TopBottom, true, 2, 1)] - [InlineData ("你", TextDirection.TopBottom_LeftRight, false, 0, 0)] - [InlineData ("你", TextDirection.TopBottom_LeftRight, true, 2, 1)] - - [InlineData ("你你", TextDirection.LeftRight_TopBottom, false, 0, 0)] - [InlineData ("你你", TextDirection.LeftRight_TopBottom, true, 4, 1)] - [InlineData ("你你", TextDirection.TopBottom_LeftRight, false, 0, 0)] - [InlineData ("你你", TextDirection.TopBottom_LeftRight, true, 2, 2)] - public void Text_Set_SizeIsCorrect (string text, TextDirection textDirection, bool autoSize, int expectedWidth, int expectedHeight) - { - var tf = new TextFormatter { Direction = textDirection, Text = text, AutoSize = autoSize }; - - if (!autoSize) - { - tf.Size = Size.Empty; - } - Assert.Equal (new Size (expectedWidth, expectedHeight), tf.Size); + Assert.Equal (new (expectedWidth, expectedHeight), tf.FormatAndGetSize ()); } [Fact] @@ -3140,181 +3060,166 @@ public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, I ); Assert.Equal (resultLines, wrappedLines); } + [SetupFakeDriver] [Theory] - [InlineData ("A", 0, false, "")] - [InlineData ("A", 1, false, "A")] - [InlineData ("A", 2, false, "A")] - [InlineData ("AB", 1, false, "A")] - [InlineData ("AB", 2, false, "AB")] - [InlineData ("ABC", 3, false, "ABC")] - [InlineData ("ABC", 4, false, "ABC")] - [InlineData ("ABC", 6, false, "ABC")] - - [InlineData ("A", 0, true, "")] - [InlineData ("A", 1, true, "A")] - [InlineData ("A", 2, true, "A")] - [InlineData ("AB", 1, true, "A")] - [InlineData ("AB", 2, true, "AB")] - [InlineData ("ABC", 3, true, "ABC")] - [InlineData ("ABC", 4, true, "ABC")] - [InlineData ("ABC", 6, true, "ABC")] - public void Draw_Horizontal_Left (string text, int width, bool autoSize, string expectedText) + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, "A")] + [InlineData ("AB", 1, "A")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, "ABC")] + [InlineData ("ABC", 6, "ABC")] + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, "A")] + [InlineData ("AB", 1, "A")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, "ABC")] + [InlineData ("ABC", 6, "ABC")] + public void Draw_Horizontal_Left (string text, int width, string expectedText) { TextFormatter tf = new () { Text = text, - Alignment = Alignment.Start, - AutoSize = autoSize, + Alignment = Alignment.Start }; - if (!autoSize) - { - tf.Size = new Size (width, 1); - } - tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = 1; + tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 0, false, "")] - [InlineData ("A", 1, false, "A")] - [InlineData ("A", 2, false, " A")] - [InlineData ("AB", 1, false, "B")] - [InlineData ("AB", 2, false, "AB")] - [InlineData ("ABC", 3, false, "ABC")] - [InlineData ("ABC", 4, false, " ABC")] - [InlineData ("ABC", 6, false, " ABC")] - - [InlineData ("A", 0, true, "")] - [InlineData ("A", 1, true, "A")] - [InlineData ("A", 2, true, " A")] - [InlineData ("AB", 1, true, "B")] - [InlineData ("AB", 2, true, "AB")] - [InlineData ("ABC", 3, true, "ABC")] - [InlineData ("ABC", 4, true, " ABC")] - [InlineData ("ABC", 6, true, " ABC")] - public void Draw_Horizontal_Right (string text, int width, bool autoSize, string expectedText) + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, " A")] + [InlineData ("AB", 1, "B")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, " ABC")] + [InlineData ("ABC", 6, " ABC")] + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, " A")] + [InlineData ("AB", 1, "B")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, " ABC")] + [InlineData ("ABC", 6, " ABC")] + public void Draw_Horizontal_Right (string text, int width, string expectedText) { TextFormatter tf = new () { Text = text, - Alignment = Alignment.End, - AutoSize = autoSize, + Alignment = Alignment.End }; - if (!autoSize) - { - tf.Size = new Size (width, 1); - } + tf.Width = width; + tf.Height = 1; - tf.Draw (new Rectangle (Point.Empty, new (width, 1)), Attribute.Default, Attribute.Default); + tf.Draw (new (Point.Empty, new (width, 1)), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 0, false, "")] - [InlineData ("A", 1, false, "A")] - [InlineData ("A", 2, false, "A")] - [InlineData ("A", 3, false, " A")] - [InlineData ("AB", 1, false, "A")] - [InlineData ("AB", 2, false, "AB")] - [InlineData ("ABC", 3, false, "ABC")] - [InlineData ("ABC", 4, false, "ABC")] - [InlineData ("ABC", 5, false, " ABC")] - [InlineData ("ABC", 6, false, " ABC")] - [InlineData ("ABC", 9, false, " ABC")] - - [InlineData ("A", 0, true, "")] - [InlineData ("A", 1, true, "A")] - [InlineData ("A", 2, true, "A")] - [InlineData ("A", 3, true, " A")] - [InlineData ("AB", 1, true, "A")] - [InlineData ("AB", 2, true, "AB")] - [InlineData ("ABC", 3, true, "ABC")] - [InlineData ("ABC", 4, true, "ABC")] - [InlineData ("ABC", 5, true, " ABC")] - [InlineData ("ABC", 6, true, " ABC")] - [InlineData ("ABC", 9, true, " ABC")] - public void Draw_Horizontal_Centered (string text, int width, bool autoSize, string expectedText) + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, "A")] + [InlineData ("A", 3, " A")] + [InlineData ("AB", 1, "A")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, "ABC")] + [InlineData ("ABC", 5, " ABC")] + [InlineData ("ABC", 6, " ABC")] + [InlineData ("ABC", 9, " ABC")] + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, "A")] + [InlineData ("A", 3, " A")] + [InlineData ("AB", 1, "A")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, "ABC")] + [InlineData ("ABC", 5, " ABC")] + [InlineData ("ABC", 6, " ABC")] + [InlineData ("ABC", 9, " ABC")] + public void Draw_Horizontal_Centered (string text, int width, string expectedText) { TextFormatter tf = new () { Text = text, - Alignment = Alignment.Center, - AutoSize = autoSize, + Alignment = Alignment.Center }; - if (!autoSize) - { - tf.Size = new Size (width, 1); - } - tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = 1; + tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 0, false, "")] - [InlineData ("A", 1, false, "A")] - [InlineData ("A", 2, false, "A")] - [InlineData ("A B", 3, false, "A B")] - [InlineData ("A B", 1, false, "A")] - [InlineData ("A B", 2, false, "A")] - [InlineData ("A B", 4, false, "A B")] - [InlineData ("A B", 5, false, "A B")] - [InlineData ("A B", 6, false, "A B")] - [InlineData ("A B", 10, false, "A B")] - [InlineData ("ABC ABC", 10, false, "ABC ABC")] - - [InlineData ("A", 0, true, "")] - [InlineData ("A", 1, true, "A")] - [InlineData ("A", 2, true, "A")] - [InlineData ("A B", 1, true, "A")] - [InlineData ("A B", 2, true, "A")] - [InlineData ("A B", 3, true, "A B")] - [InlineData ("A B", 4, true, "A B")] - [InlineData ("A B", 5, true, "A B")] - [InlineData ("A B", 6, true, "A B")] - [InlineData ("A B", 10, true, "A B")] - [InlineData ("ABC ABC", 10, true, "ABC ABC")] - public void Draw_Horizontal_Justified (string text, int width, bool autoSize, string expectedText) + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, "A")] + [InlineData ("A B", 3, "A B")] + [InlineData ("A B", 1, "A")] + [InlineData ("A B", 2, "A")] + [InlineData ("A B", 4, "A B")] + [InlineData ("A B", 5, "A B")] + [InlineData ("A B", 6, "A B")] + [InlineData ("A B", 10, "A B")] + [InlineData ("ABC ABC", 10, "ABC ABC")] + public void Draw_Horizontal_Justified (string text, int width, string expectedText) { TextFormatter tf = new () { Text = text, - Alignment = Alignment.Fill, - AutoSize = autoSize, + Alignment = Alignment.Fill }; - if (!autoSize) - { - tf.Size = new Size (width, 1); - } - tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = 1; + tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 5, 5, false, "A")] - [InlineData ("AB12", 5, 5, false, @" + [InlineData ("A", 5, 5, "A")] + [InlineData ( + "AB12", + 5, + 5, + @" A B 1 2")] - [InlineData ("AB\n12", 5, 5, false, @" + [InlineData ( + "AB\n12", + 5, + 5, + @" A1 B2")] - [InlineData ("", 5, 1, false, "")] - - [InlineData ("Hello Worlds", 1, 12, true, @" + [InlineData ("", 5, 1, "")] + [InlineData ( + "Hello Worlds", + 1, + 12, + @" H e l @@ -3327,8 +3232,11 @@ public void Draw_Horizontal_Justified (string text, int width, bool autoSize, st l d s")] - - [InlineData ("Hello Worlds", 1, 12, false, @" + [InlineData ( + "Hello Worlds", + 1, + 12, + @" H e l @@ -3341,36 +3249,38 @@ public void Draw_Horizontal_Justified (string text, int width, bool autoSize, st l d s")] - - [InlineData ("Hello Worlds", 12, 1, false, @"HelloWorlds")] - - public void Draw_Vertical_TopBottom_LeftRight (string text, int width, int height, bool autoSize, string expectedText) + [InlineData ("Hello Worlds", 12, 1, @"HelloWorlds")] + public void Draw_Vertical_TopBottom_LeftRight (string text, int width, int height, string expectedText) { TextFormatter tf = new () { Text = text, - AutoSize = autoSize, - Direction = TextDirection.TopBottom_LeftRight, + Direction = TextDirection.TopBottom_LeftRight }; - if (!autoSize) - { - tf.Size = new Size (width, height); - } - tf.Draw (new Rectangle (0, 0, 20, 20), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = height; + tf.Draw (new (0, 0, 20, 20), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } - [SetupFakeDriver] [Theory] [InlineData ("Hello World", 15, 1, "Hello World")] - [InlineData ("Well Done\nNice Work", 15, 2, @" + [InlineData ( + "Well Done\nNice Work", + 15, + 2, + @" Well Done Nice Work")] [InlineData ("你好 世界", 15, 1, "你好 世界")] - [InlineData ("做 得好\n幹 得好", 15, 2, @" + [InlineData ( + "做 得好\n幹 得好", + 15, + 2, + @" 做 得好 幹 得好")] public void Justify_Horizontal (string text, int width, int height, string expectedText) @@ -3383,7 +3293,7 @@ public void Justify_Horizontal (string text, int width, int height, string expec MultiLine = true }; - tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default); + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } @@ -3391,7 +3301,11 @@ public void Justify_Horizontal (string text, int width, int height, string expec [SetupFakeDriver] [Theory] [InlineData ("Hello World", 1, 15, "H\ne\nl\nl\no\n \n \n \n \n \nW\no\nr\nl\nd")] - [InlineData ("Well Done\nNice Work", 2, 15, @" + [InlineData ( + "Well Done\nNice Work", + 2, + 15, + @" WN ei lc @@ -3408,7 +3322,11 @@ public void Justify_Horizontal (string text, int width, int height, string expec nr ek")] [InlineData ("你好 世界", 2, 15, "你\n好\n \n \n \n \n \n \n \n \n \n \n \n世\n界")] - [InlineData ("做 得好\n幹 得好", 4, 15, @" + [InlineData ( + "做 得好\n幹 得好", + 4, + 15, + @" 做幹 @@ -3435,138 +3353,105 @@ public void Justify_Vertical (string text, int width, int height, string expecte MultiLine = true }; - tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default); + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 0, 1, false, "", 0)] - [InlineData ("A", 1, 1, false, "A", 0)] - [InlineData ("A", 2, 2, false, " A", 1)] - [InlineData ("AB", 1, 1, false, "B", 0)] - [InlineData ("AB", 2, 2, false, " A\n B", 0)] - [InlineData ("ABC", 3, 2, false, " B\n C", 0)] - [InlineData ("ABC", 4, 2, false, " B\n C", 0)] - [InlineData ("ABC", 6, 2, false, " B\n C", 0)] - [InlineData ("こんにちは", 0, 1, false, "", 0)] - [InlineData ("こんにちは", 1, 0, false, "", 0)] - [InlineData ("こんにちは", 1, 1, false, "", 0)] - [InlineData ("こんにちは", 2, 1, false, "は", 0)] - [InlineData ("こんにちは", 2, 2, false, "ち\nは", 0)] - [InlineData ("こんにちは", 2, 3, false, "に\nち\nは", 0)] - [InlineData ("こんにちは", 2, 4, false, "ん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 2, 5, false, "こ\nん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 2, 6, false, "こ\nん\nに\nち\nは", 1)] - [InlineData ("ABCD\nこんにちは", 4, 7, false, " こ\n Aん\n Bに\n Cち\n Dは", 2)] - [InlineData ("こんにちは\nABCD", 3, 7, false, "こ \nんA\nにB\nちC\nはD", 2)] - - [InlineData ("A", 0, 1, true, "", 0)] - [InlineData ("A", 1, 1, true, "A", 0)] - [InlineData ("A", 2, 2, true, " A", 1)] - [InlineData ("AB", 1, 1, true, "B", 0)] - [InlineData ("AB", 2, 2, true, " A\n B", 0)] - [InlineData ("ABC", 3, 2, true, " B\n C", 0)] - [InlineData ("ABC", 4, 2, true, " B\n C", 0)] - [InlineData ("ABC", 6, 2, true, " B\n C", 0)] - [InlineData ("こんにちは", 0, 1, true, "", 0)] - [InlineData ("こんにちは", 1, 0, true, "", 0)] - [InlineData ("こんにちは", 1, 1, true, "", 0)] - [InlineData ("こんにちは", 2, 1, true, "は", 0)] - [InlineData ("こんにちは", 2, 2, true, "ち\nは", 0)] - [InlineData ("こんにちは", 2, 3, true, "に\nち\nは", 0)] - [InlineData ("こんにちは", 2, 4, true, "ん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 2, 5, true, "こ\nん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 2, 6, true, "こ\nん\nに\nち\nは", 1)] - [InlineData ("ABCD\nこんにちは", 4, 7, true, " こ\n Aん\n Bに\n Cち\n Dは", 2)] - [InlineData ("こんにちは\nABCD", 3, 7, true, "こ \nんA\nにB\nちC\nはD", 2)] - public void Draw_Vertical_Bottom_Horizontal_Right (string text, int width, int height, bool autoSize, string expectedText, int expectedY) + [InlineData ("A", 0, 1, "", 0)] + [InlineData ("A", 1, 1, "A", 0)] + [InlineData ("A", 2, 2, " A", 1)] + [InlineData ("AB", 1, 1, "B", 0)] + [InlineData ("AB", 2, 2, " A\n B", 0)] + [InlineData ("ABC", 3, 2, " B\n C", 0)] + [InlineData ("ABC", 4, 2, " B\n C", 0)] + [InlineData ("ABC", 6, 2, " B\n C", 0)] + [InlineData ("こんにちは", 0, 1, "", 0)] + [InlineData ("こんにちは", 1, 0, "", 0)] + [InlineData ("こんにちは", 1, 1, "", 0)] + [InlineData ("こんにちは", 2, 1, "は", 0)] + [InlineData ("こんにちは", 2, 2, "ち\nは", 0)] + [InlineData ("こんにちは", 2, 3, "に\nち\nは", 0)] + [InlineData ("こんにちは", 2, 4, "ん\nに\nち\nは", 0)] + [InlineData ("こんにちは", 2, 5, "こ\nん\nに\nち\nは", 0)] + [InlineData ("こんにちは", 2, 6, "こ\nん\nに\nち\nは", 1)] + [InlineData ("ABCD\nこんにちは", 4, 7, " こ\n Aん\n Bに\n Cち\n Dは", 2)] + [InlineData ("こんにちは\nABCD", 3, 7, "こ \nんA\nにB\nちC\nはD", 2)] + public void Draw_Vertical_Bottom_Horizontal_Right (string text, int width, int height, string expectedText, int expectedY) { TextFormatter tf = new () { Text = text, Alignment = Alignment.End, Direction = TextDirection.TopBottom_LeftRight, - VerticalAlignment = Alignment.End, - AutoSize = autoSize, + VerticalAlignment = Alignment.End }; - if (!autoSize) - { - tf.Size = new Size (width, height); - } + tf.Width = width; + tf.Height = height; - tf.Draw (new Rectangle (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default); + tf.Draw (new (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default); Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); Assert.Equal (expectedY, rect.Y); } [SetupFakeDriver] [Theory] - [InlineData ("A", 5, false, "A")] - [InlineData ("AB12", 5, false, @" + [InlineData ("A", 5, "A")] + [InlineData ( + "AB12", + 5, + @" A B 1 2")] - [InlineData ("AB\n12", 5, false, @" + [InlineData ( + "AB\n12", + 5, + @" A1 B2")] - [InlineData ("", 1, false, "")] - [InlineData ("AB1 2", 2, false, @" + [InlineData ("", 1, "")] + [InlineData ( + "AB1 2", + 2, + @" A12 B ")] - [InlineData ("こんにちは", 1, false, @" + [InlineData ( + "こんにちは", + 1, + @" こん")] - [InlineData ("こんにちは", 2, false, @" + [InlineData ( + "こんにちは", + 2, + @" こに んち")] - [InlineData ("こんにちは", 5, false, @" -こ -ん -に -ち -は")] - - [InlineData ("A", 5, true, "A")] - [InlineData ("AB12", 5, true, @" -A -B -1 -2")] - [InlineData ("AB\n12", 5, true, @" -A1 -B2")] - [InlineData ("", 1, true, "")] - [InlineData ("AB1 2", 2, true, @" -A -B")] - [InlineData ("こんにちは", 1, true, @" -こ")] - [InlineData ("こんにちは", 2, true, @" -こ -ん")] - [InlineData ("こんにちは", 5, true, @" + [InlineData ( + "こんにちは", + 5, + @" こ ん に ち は")] - public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, bool autoSize, string expectedText) + public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, string expectedText) { TextFormatter tf = new () { Text = text, - AutoSize = autoSize, - Direction = TextDirection.TopBottom_LeftRight, + Direction = TextDirection.TopBottom_LeftRight }; - if (!autoSize) - { - tf.Size = new Size (5, height); - } - tf.Draw (new Rectangle (0, 0, 5, height), Attribute.Default, Attribute.Default); + tf.Width = 5; + tf.Height = height; + tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } @@ -3575,273 +3460,160 @@ public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, bool [Theory] // The expectedY param is to probe that the expectedText param start at that Y coordinate - - [InlineData ("A", 0, false, "", 0)] - [InlineData ("A", 1, false, "A", 0)] - [InlineData ("A", 2, false, "A", 0)] - [InlineData ("A", 3, false, "A", 1)] - [InlineData ("AB", 1, false, "A", 0)] - [InlineData ("AB", 2, false, "A\nB", 0)] - [InlineData ("ABC", 2, false, "A\nB", 0)] - [InlineData ("ABC", 3, false, "A\nB\nC", 0)] - [InlineData ("ABC", 4, false, "A\nB\nC", 0)] - [InlineData ("ABC", 5, false, "A\nB\nC", 1)] - [InlineData ("ABC", 6, false, "A\nB\nC", 1)] - [InlineData ("ABC", 9, false, "A\nB\nC", 3)] - [InlineData ("ABCD", 2, false, "B\nC", 0)] - [InlineData ("こんにちは", 0, false, "", 0)] - [InlineData ("こんにちは", 1, false, "に", 0)] - [InlineData ("こんにちは", 2, false, "ん\nに", 0)] - [InlineData ("こんにちは", 3, false, "ん\nに\nち", 0)] - [InlineData ("こんにちは", 4, false, "こ\nん\nに\nち", 0)] - [InlineData ("こんにちは", 5, false, "こ\nん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 6, false, "こ\nん\nに\nち\nは", 0)] - [InlineData ("ABCD\nこんにちは", 7, false, "Aこ\nBん\nCに\nDち\n は", 1)] - [InlineData ("こんにちは\nABCD", 7, false, "こA\nんB\nにC\nちD\nは ", 1)] - - [InlineData ("A", 0, true, "", 0)] - [InlineData ("A", 1, true, "A", 0)] - [InlineData ("A", 2, true, "A", 0)] - [InlineData ("A", 3, true, "A", 1)] - [InlineData ("AB", 1, true, "A", 0)] - [InlineData ("AB", 2, true, "A\nB", 0)] - [InlineData ("ABC", 2, true, "A\nB", 0)] - [InlineData ("ABC", 3, true, "A\nB\nC", 0)] - [InlineData ("ABC", 4, true, "A\nB\nC", 0)] - [InlineData ("ABC", 5, true, "A\nB\nC", 1)] - [InlineData ("ABC", 6, true, "A\nB\nC", 1)] - [InlineData ("ABC", 9, true, "A\nB\nC", 3)] - [InlineData ("ABCD", 2, true, "B\nC", 0)] - [InlineData ("こんにちは", 0, true, "", 0)] - [InlineData ("こんにちは", 1, true, "に", 0)] - [InlineData ("こんにちは", 2, true, "ん\nに", 0)] - [InlineData ("こんにちは", 3, true, "ん\nに\nち", 0)] - [InlineData ("こんにちは", 4, true, "こ\nん\nに\nち", 0)] - [InlineData ("こんにちは", 5, true, "こ\nん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 6, true, "こ\nん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 7, true, "こ\nん\nに\nち\nは", 1)] - [InlineData ("ABCD\nこんにちは", 7, true, "Aこ\nBん\nCに\nDち\n は", 1)] - [InlineData ("こんにちは\nABCD", 7, true, "こA\nんB\nにC\nちD\nは ", 1)] - public void Draw_Vertical_TopBottom_LeftRight_Middle (string text, int height, bool autoSize, string expectedText, int expectedY) + [InlineData ("A", 0, "", 0)] + [InlineData ("A", 1, "A", 0)] + [InlineData ("A", 2, "A", 0)] + [InlineData ("A", 3, "A", 1)] + [InlineData ("AB", 1, "A", 0)] + [InlineData ("AB", 2, "A\nB", 0)] + [InlineData ("ABC", 2, "A\nB", 0)] + [InlineData ("ABC", 3, "A\nB\nC", 0)] + [InlineData ("ABC", 4, "A\nB\nC", 0)] + [InlineData ("ABC", 5, "A\nB\nC", 1)] + [InlineData ("ABC", 6, "A\nB\nC", 1)] + [InlineData ("ABC", 9, "A\nB\nC", 3)] + [InlineData ("ABCD", 2, "B\nC", 0)] + [InlineData ("こんにちは", 0, "", 0)] + [InlineData ("こんにちは", 1, "に", 0)] + [InlineData ("こんにちは", 2, "ん\nに", 0)] + [InlineData ("こんにちは", 3, "ん\nに\nち", 0)] + [InlineData ("こんにちは", 4, "こ\nん\nに\nち", 0)] + [InlineData ("こんにちは", 5, "こ\nん\nに\nち\nは", 0)] + [InlineData ("こんにちは", 6, "こ\nん\nに\nち\nは", 0)] + [InlineData ("ABCD\nこんにちは", 7, "Aこ\nBん\nCに\nDち\n は", 1)] + [InlineData ("こんにちは\nABCD", 7, "こA\nんB\nにC\nちD\nは ", 1)] + public void Draw_Vertical_TopBottom_LeftRight_Middle (string text, int height, string expectedText, int expectedY) { TextFormatter tf = new () { Text = text, Direction = TextDirection.TopBottom_LeftRight, - VerticalAlignment = Alignment.Center, - AutoSize = autoSize, + VerticalAlignment = Alignment.Center }; - if (!autoSize) - { - int width = text.ToRunes ().Max (r => r.GetColumns ()); - - if (text.Contains ("\n")) - { - width++; - } + int width = text.ToRunes ().Max (r => r.GetColumns ()); - tf.Size = new Size (width, height); + if (text.Contains ("\n")) + { + width++; } - tf.Draw (new Rectangle (0, 0, 5, height), Attribute.Default, Attribute.Default); + + tf.Width = width; + tf.Height = height; + tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); Assert.Equal (expectedY, rect.Y); } - [Theory] - [InlineData ("1234", 4)] - [InlineData ("_1234", 4)] - public void AutoSize_HotKey_Size_Correct (string text, int expected) - { - // Horizontal - TextFormatter tf = new () - { - AutoSize = true, - HotKeySpecifier = (Rune)'_', - Text = text, - }; - Assert.Equal (new (expected, 1), tf.Size); - - // Vertical - tf = new () - { - HotKeySpecifier = (Rune)'_', - Direction = TextDirection.TopBottom_LeftRight, - Text = text, - AutoSize = true, - }; - Assert.Equal (new (1, expected), tf.Size); - } - [SetupFakeDriver] [Theory] - [InlineData ("A", 1, 0, false, "")] - [InlineData ("A", 0, 1, false, "")] - [InlineData ("AB1 2", 2, 1, false, "2")] - [InlineData ("AB12", 5, 1, false, "21BA")] - [InlineData ("AB\n12", 5, 2, false, "BA\n21")] - [InlineData ("ABC 123 456", 7, 2, false, "654 321\nCBA ")] - [InlineData ("こんにちは", 1, 1, false, "")] - [InlineData ("こんにちは", 2, 1, false, "は")] - [InlineData ("こんにちは", 5, 1, false, "はち")] - [InlineData ("こんにちは", 10, 1, false, "はちにんこ")] - [InlineData ("こんにちは\nAB\n12", 10, 3, false, "はちにんこ\nBA \n21 ")] - - [InlineData ("A", 1, 0, true, "")] - [InlineData ("A", 0, 1, true, "")] - [InlineData ("AB1 2", 2, 1, true, "2")] - [InlineData ("AB12", 5, 1, true, "21BA")] - [InlineData ("AB\n12", 5, 2, true, "BA\n21")] - [InlineData ("ABC 123 456", 7, 2, true, "654 321")] - [InlineData ("こんにちは", 1, 1, true, "")] - [InlineData ("こんにちは", 2, 1, true, "は")] - [InlineData ("こんにちは", 5, 1, true, "はち")] - [InlineData ("こんにちは", 10, 1, true, "はちにんこ")] - [InlineData ("こんにちは\nAB\n12", 10, 3, true, "はちにんこ\nBA \n21 ")] - public void Draw_Horizontal_RightLeft_TopBottom (string text, int width, int height, bool autoSize, string expectedText) + [InlineData ("A", 1, 0, "")] + [InlineData ("A", 0, 1, "")] + [InlineData ("AB1 2", 2, 1, "2")] + [InlineData ("AB12", 5, 1, "21BA")] + [InlineData ("AB\n12", 5, 2, "BA\n21")] + [InlineData ("ABC 123 456", 7, 2, "654 321\nCBA ")] + [InlineData ("こんにちは", 1, 1, "")] + [InlineData ("こんにちは", 2, 1, "は")] + [InlineData ("こんにちは", 5, 1, "はち")] + [InlineData ("こんにちは", 10, 1, "はちにんこ")] + [InlineData ("こんにちは\nAB\n12", 10, 3, "はちにんこ\nBA \n21 ")] + public void Draw_Horizontal_RightLeft_TopBottom (string text, int width, int height, string expectedText) { TextFormatter tf = new () { Text = text, - Direction = TextDirection.RightLeft_TopBottom, - AutoSize = autoSize, + Direction = TextDirection.RightLeft_TopBottom }; - if (!autoSize) - { - tf.Size = new Size (width, height); - } - tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = height; + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 1, 0, false, "")] - [InlineData ("A", 0, 1, false, "")] - [InlineData ("AB1 2", 2, 1, false, "2")] - [InlineData ("AB12", 5, 1, false, "21BA")] - [InlineData ("AB\n12", 5, 2, false, "21\nBA")] - [InlineData ("ABC 123 456", 7, 2, false, "CBA \n654 321")] - [InlineData ("こんにちは", 1, 1, false, "")] - [InlineData ("こんにちは", 2, 1, false, "は")] - [InlineData ("こんにちは", 5, 1, false, "はち")] - [InlineData ("こんにちは", 10, 1, false, "はちにんこ")] - [InlineData ("こんにちは\nAB\n12", 10, 3, false, "21 \nBA \nはちにんこ")] - - [InlineData ("A", 1, 0, true, "")] - [InlineData ("A", 0, 1, true, "")] - [InlineData ("AB1 2", 2, 1, true, "2")] - [InlineData ("AB12", 5, 1, true, "21BA")] - [InlineData ("AB\n12", 5, 2, true, "21\nBA")] - [InlineData ("ABC 123 456", 7, 2, true, "654 321")] - [InlineData ("こんにちは", 1, 1, true, "")] - [InlineData ("こんにちは", 2, 1, true, "は")] - [InlineData ("こんにちは", 5, 1, true, "はち")] - [InlineData ("こんにちは", 10, 1, true, "はちにんこ")] - [InlineData ("こんにちは\nAB\n12", 10, 3, true, "21 \nBA \nはちにんこ")] - public void Draw_Horizontal_RightLeft_BottomTop (string text, int width, int height, bool autoSize, string expectedText) + [InlineData ("A", 1, 0, "")] + [InlineData ("A", 0, 1, "")] + [InlineData ("AB1 2", 2, 1, "2")] + [InlineData ("AB12", 5, 1, "21BA")] + [InlineData ("AB\n12", 5, 2, "21\nBA")] + [InlineData ("ABC 123 456", 7, 2, "CBA \n654 321")] + [InlineData ("こんにちは", 1, 1, "")] + [InlineData ("こんにちは", 2, 1, "は")] + [InlineData ("こんにちは", 5, 1, "はち")] + [InlineData ("こんにちは", 10, 1, "はちにんこ")] + [InlineData ("こんにちは\nAB\n12", 10, 3, "21 \nBA \nはちにんこ")] + public void Draw_Horizontal_RightLeft_BottomTop (string text, int width, int height, string expectedText) { TextFormatter tf = new () { Text = text, - Direction = TextDirection.RightLeft_BottomTop, - AutoSize = autoSize, + Direction = TextDirection.RightLeft_BottomTop }; - if (!autoSize) - { - tf.Size = new Size (width, height); - } - tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = height; + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 1, 0, false, "")] - [InlineData ("A", 0, 1, false, "")] - [InlineData ("AB1 2", 1, 2, false, "2")] - [InlineData ("AB12", 1, 5, false, "2\n1\nB\nA")] - [InlineData ("AB\n12", 2, 5, false, "B2\nA1")] - [InlineData ("ABC 123 456", 2, 7, false, "6C\n5B\n4A\n \n3 \n2 \n1 ")] - [InlineData ("こんにちは", 1, 1, false, "")] - [InlineData ("こんにちは", 2, 1, false, "は")] - [InlineData ("こんにちは", 2, 5, false, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは", 2, 10, false, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは\nAB\n12", 4, 10, false, "はB2\nちA1\nに \nん \nこ ")] - - [InlineData ("A", 1, 0, true, "")] - [InlineData ("A", 0, 1, true, "")] - [InlineData ("AB1 2", 1, 2, true, "2")] - [InlineData ("AB12", 1, 5, true, "2\n1\nB\nA")] - [InlineData ("AB\n12", 2, 5, true, "B2\nA1")] - [InlineData ("ABC 123 456", 2, 7, true, "6\n5\n4\n \n3\n2\n1")] - [InlineData ("こんにちは", 1, 1, true, "")] - [InlineData ("こんにちは", 2, 1, true, "は")] - [InlineData ("こんにちは", 2, 5, true, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは", 2, 10, true, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは\nAB\n12", 4, 10, true, "はB2\nちA1\nに \nん \nこ ")] - public void Draw_Vertical_BottomTop_LeftRight (string text, int width, int height, bool autoSize, string expectedText) + [InlineData ("A", 1, 0, "")] + [InlineData ("A", 0, 1, "")] + [InlineData ("AB1 2", 1, 2, "2")] + [InlineData ("AB12", 1, 5, "2\n1\nB\nA")] + [InlineData ("AB\n12", 2, 5, "B2\nA1")] + [InlineData ("ABC 123 456", 2, 7, "6C\n5B\n4A\n \n3 \n2 \n1 ")] + [InlineData ("こんにちは", 1, 1, "")] + [InlineData ("こんにちは", 2, 1, "は")] + [InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは\nAB\n12", 4, 10, "はB2\nちA1\nに \nん \nこ ")] + public void Draw_Vertical_BottomTop_LeftRight (string text, int width, int height, string expectedText) { TextFormatter tf = new () { Text = text, - Direction = TextDirection.BottomTop_LeftRight, - AutoSize = autoSize, + Direction = TextDirection.BottomTop_LeftRight }; - if (!autoSize) - { - tf.Size = new Size (width, height); - } - tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = height; + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("A", 1, 0, false, "")] - [InlineData ("A", 0, 1, false, "")] - [InlineData ("AB1 2", 1, 2, false, "2")] - [InlineData ("AB12", 1, 5, false, "2\n1\nB\nA")] - [InlineData ("AB\n12", 2, 5, false, "2B\n1A")] - [InlineData ("ABC 123 456", 2, 7, false, "C6\nB5\nA4\n \n 3\n 2\n 1")] - [InlineData ("こんにちは", 1, 1, false, "")] - [InlineData ("こんにちは", 2, 1, false, "は")] - [InlineData ("こんにちは", 2, 5, false, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは", 2, 10, false, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは\nAB\n12", 4, 10, false, "2Bは\n1Aち\n に\n ん\n こ")] - - [InlineData ("A", 1, 0, true, "")] - [InlineData ("A", 0, 1, true, "")] - [InlineData ("AB1 2", 1, 2, true, "2")] - [InlineData ("AB12", 1, 5, true, "2\n1\nB\nA")] - [InlineData ("AB\n12", 2, 5, true, "2B\n1A")] - [InlineData ("ABC 123 456", 2, 7, true, "6\n5\n4\n \n3\n2\n1")] - [InlineData ("こんにちは", 1, 1, true, "")] - [InlineData ("こんにちは", 2, 1, true, "は")] - [InlineData ("こんにちは", 2, 5, true, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは", 2, 10, true, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは\nAB\n12", 4, 10, true, "2Bは\n1Aち\n に\n ん\n こ")] - public void Draw_Vertical_BottomTop_RightLeft (string text, int width, int height, bool autoSize, string expectedText) + [InlineData ("A", 1, 0, "")] + [InlineData ("A", 0, 1, "")] + [InlineData ("AB1 2", 1, 2, "2")] + [InlineData ("AB12", 1, 5, "2\n1\nB\nA")] + [InlineData ("AB\n12", 2, 5, "2B\n1A")] + [InlineData ("ABC 123 456", 2, 7, "C6\nB5\nA4\n \n 3\n 2\n 1")] + [InlineData ("こんにちは", 1, 1, "")] + [InlineData ("こんにちは", 2, 1, "は")] + [InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは\nAB\n12", 4, 10, "2Bは\n1Aち\n に\n ん\n こ")] + public void Draw_Vertical_BottomTop_RightLeft (string text, int width, int height, string expectedText) { TextFormatter tf = new () { Text = text, - Direction = TextDirection.BottomTop_RightLeft, - AutoSize = autoSize, + Direction = TextDirection.BottomTop_RightLeft }; - if (!autoSize) - { - tf.Size = new Size (width, height); - } - tf.Draw (new Rectangle (0, 0, width, height), Attribute.Default, Attribute.Default); + tf.Width = width; + tf.Height = height; + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } @@ -3860,11 +3632,11 @@ public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds ( top.Add (view); Application.Iteration += (s, a) => - { - Assert.Equal (-2, view.Y); + { + Assert.Equal (-2, view.Y); - Application.RequestStop (); - }; + Application.RequestStop (); + }; try { @@ -3877,6 +3649,7 @@ public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds ( } top.Dispose (); + // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); } @@ -3886,7 +3659,12 @@ public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds ( // Horizontal with Alignment.Start // LeftRight_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" 0 2 4** ******* ******* @@ -3894,7 +3672,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" **0 2 4 ******* ******* @@ -3902,7 +3685,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" *0 2 4* ******* ******* @@ -3910,7 +3698,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" 0 2 4 ******* ******* @@ -3918,8 +3711,12 @@ 0 2 4 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" 0 你 4* ******* ******* @@ -3927,7 +3724,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" *0 你 4 ******* ******* @@ -3935,7 +3737,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" 0 你 4* ******* ******* @@ -3943,7 +3750,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.LeftRight_TopBottom, + @" 0 你 4 ******* ******* @@ -3953,7 +3765,12 @@ 0 你 4 *******")] // LeftRight_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" 0 2 4** ******* ******* @@ -3961,7 +3778,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" **0 2 4 ******* ******* @@ -3969,7 +3791,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" *0 2 4* ******* ******* @@ -3977,7 +3804,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" 0 2 4 ******* ******* @@ -3985,8 +3817,12 @@ 0 2 4 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" 0 你 4* ******* ******* @@ -3994,7 +3830,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" *0 你 4 ******* ******* @@ -4002,7 +3843,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" 0 你 4* ******* ******* @@ -4010,7 +3856,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.LeftRight_BottomTop, + @" 0 你 4 ******* ******* @@ -4020,7 +3871,12 @@ 0 你 4 *******")] // RightLeft_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" 4 2 0** ******* ******* @@ -4028,7 +3884,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" **4 2 0 ******* ******* @@ -4036,7 +3897,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" *4 2 0* ******* ******* @@ -4044,7 +3910,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" 4 2 0 ******* ******* @@ -4052,8 +3923,12 @@ 4 2 0 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" 4 你 0* ******* ******* @@ -4061,7 +3936,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" *4 你 0 ******* ******* @@ -4069,7 +3949,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" 4 你 0* ******* ******* @@ -4077,7 +3962,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.RightLeft_TopBottom, + @" 4 你 0 ******* ******* @@ -4087,7 +3977,12 @@ 4 你 0 *******")] // RightLeft_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" 4 2 0** ******* ******* @@ -4095,7 +3990,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" **4 2 0 ******* ******* @@ -4103,7 +4003,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" *4 2 0* ******* ******* @@ -4111,7 +4016,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" 4 2 0 ******* ******* @@ -4119,8 +4029,12 @@ 4 2 0 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" 4 你 0* ******* ******* @@ -4128,7 +4042,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" *4 你 0 ******* ******* @@ -4136,15 +4055,25 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.RightLeft_BottomTop, @" -4 你 0* -******* + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" +4 你 0* +******* ******* ******* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.RightLeft_BottomTop, + @" 4 你 0 ******* ******* @@ -4155,7 +4084,12 @@ 4 你 0 // Horizontal with Alignment.End // LeftRight_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4163,7 +4097,12 @@ 4 你 0 ******* ******* 0 2 4**")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4171,7 +4110,12 @@ 4 你 0 ******* ******* **0 2 4")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4179,7 +4123,12 @@ 4 你 0 ******* ******* *0 2 4*")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4187,8 +4136,12 @@ 4 你 0 ******* ******* 0 2 4")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4196,7 +4149,12 @@ 4 你 0 ******* ******* 0 你 4*")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4204,7 +4162,12 @@ 4 你 0 ******* ******* *0 你 4")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4212,7 +4175,12 @@ 4 你 0 ******* ******* 0 你 4*")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4222,7 +4190,12 @@ 4 你 0 0 你 4")] // LeftRight_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4230,7 +4203,12 @@ 4 你 0 ******* ******* 0 2 4**")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4238,7 +4216,12 @@ 4 你 0 ******* ******* **0 2 4")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4246,7 +4229,12 @@ 4 你 0 ******* ******* *0 2 4*")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4254,8 +4242,12 @@ 4 你 0 ******* ******* 0 2 4")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4263,7 +4255,12 @@ 4 你 0 ******* ******* 0 你 4*")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4271,7 +4268,12 @@ 4 你 0 ******* ******* *0 你 4")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4279,7 +4281,12 @@ 4 你 0 ******* ******* 0 你 4*")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4289,7 +4296,12 @@ 4 你 0 0 你 4")] // RightLeft_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4297,7 +4309,12 @@ 4 你 0 ******* ******* 4 2 0**")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4305,7 +4322,12 @@ 4 你 0 ******* ******* **4 2 0")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4313,7 +4335,12 @@ 4 你 0 ******* ******* *4 2 0*")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4321,8 +4348,12 @@ 4 你 0 ******* ******* 4 2 0")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4330,7 +4361,12 @@ 4 你 0 ******* ******* 4 你 0*")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4338,7 +4374,12 @@ 4 你 0 ******* ******* *4 你 0")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4346,7 +4387,12 @@ 4 你 0 ******* ******* 4 你 0*")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4356,7 +4402,12 @@ 4 你 0 4 你 0")] // RightLeft_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4364,7 +4415,12 @@ 4 你 0 ******* ******* 4 2 0**")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4372,7 +4428,12 @@ 4 你 0 ******* ******* **4 2 0")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4380,7 +4441,12 @@ 4 你 0 ******* ******* *4 2 0*")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4388,8 +4454,12 @@ 4 你 0 ******* ******* 4 2 0")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4397,7 +4467,12 @@ 4 你 0 ******* ******* 4 你 0*")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4405,7 +4480,12 @@ 4 你 0 ******* ******* *4 你 0")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4413,7 +4493,12 @@ 4 你 0 ******* ******* 4 你 0*")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4424,7 +4509,12 @@ 4 你 0 // Horizontal with alignment.Centered // LeftRight_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4432,7 +4522,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4440,7 +4535,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4448,7 +4548,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4456,8 +4561,12 @@ 0 2 4 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4465,7 +4574,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4473,7 +4587,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4481,7 +4600,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.LeftRight_TopBottom, + @" ******* ******* ******* @@ -4491,7 +4615,12 @@ 0 你 4 *******")] // LeftRight_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4499,7 +4628,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4507,7 +4641,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4515,7 +4654,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4523,8 +4667,12 @@ 0 2 4 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4532,7 +4680,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4540,7 +4693,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4548,7 +4706,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.LeftRight_BottomTop, + @" ******* ******* ******* @@ -4558,7 +4721,12 @@ 0 你 4 *******")] // RightLeft_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4566,7 +4734,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4574,7 +4747,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4582,7 +4760,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4590,8 +4773,12 @@ 4 2 0 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4599,7 +4786,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4607,7 +4799,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4615,7 +4812,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.RightLeft_TopBottom, + @" ******* ******* ******* @@ -4625,7 +4827,12 @@ 4 你 0 *******")] // RightLeft_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4633,7 +4840,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4641,7 +4853,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4649,7 +4866,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4657,8 +4879,12 @@ 4 2 0 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4666,7 +4892,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4674,7 +4905,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4682,7 +4918,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.RightLeft_BottomTop, + @" ******* ******* ******* @@ -4693,7 +4934,12 @@ 4 你 0 // Horizontal with alignment.Justified // LeftRight_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" 0 2 4** ******* ******* @@ -4701,7 +4947,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" **0 2 4 ******* ******* @@ -4709,7 +4960,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" *0 2 4* ******* ******* @@ -4717,7 +4973,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" 0 2 4 ******* ******* @@ -4725,8 +4986,12 @@ 0 2 4 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" 0 你 4* ******* ******* @@ -4734,7 +4999,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" *0 你 4 ******* ******* @@ -4742,7 +5012,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" 0 你 4* ******* ******* @@ -4750,7 +5025,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.LeftRight_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.LeftRight_TopBottom, + @" 0 你 4 ******* ******* @@ -4760,7 +5040,12 @@ 0 你 4 *******")] // LeftRight_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" 0 2 4** ******* ******* @@ -4768,7 +5053,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" **0 2 4 ******* ******* @@ -4776,7 +5066,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" *0 2 4* ******* ******* @@ -4784,7 +5079,12 @@ 0 2 4** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" 0 2 4 ******* ******* @@ -4792,8 +5092,12 @@ 0 2 4 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" 0 你 4* ******* ******* @@ -4801,7 +5105,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" *0 你 4 ******* ******* @@ -4809,7 +5118,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" 0 你 4* ******* ******* @@ -4817,7 +5131,12 @@ 0 你 4* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.LeftRight_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.LeftRight_BottomTop, + @" 0 你 4 ******* ******* @@ -4827,7 +5146,12 @@ 0 你 4 *******")] // RightLeft_TopBottom - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" 4 2 0** ******* ******* @@ -4835,7 +5159,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" **4 2 0 ******* ******* @@ -4843,7 +5172,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" *4 2 0* ******* ******* @@ -4851,7 +5185,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" 4 2 0 ******* ******* @@ -4859,8 +5198,12 @@ 4 2 0 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" 4 你 0* ******* ******* @@ -4868,7 +5211,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" *4 你 0 ******* ******* @@ -4876,7 +5224,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" 4 你 0* ******* ******* @@ -4884,7 +5237,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.RightLeft_TopBottom, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.RightLeft_TopBottom, + @" 4 你 0 ******* ******* @@ -4894,7 +5252,12 @@ 4 你 0 *******")] // RightLeft_BottomTop - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" 4 2 0** ******* ******* @@ -4902,7 +5265,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" **4 2 0 ******* ******* @@ -4910,7 +5278,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" *4 2 0* ******* ******* @@ -4918,7 +5291,12 @@ 4 2 0** ******* ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" 4 2 0 ******* ******* @@ -4926,8 +5304,12 @@ 4 2 0 ******* ******* *******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" 4 你 0* ******* ******* @@ -4935,7 +5317,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" *4 你 0 ******* ******* @@ -4943,7 +5330,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" 4 你 0* ******* ******* @@ -4951,7 +5343,12 @@ 4 你 0* ******* ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.RightLeft_BottomTop, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.RightLeft_BottomTop, + @" 4 你 0 ******* ******* @@ -4962,7 +5359,12 @@ 4 你 0 // Vertical with alignment.Left // TopBottom_LeftRight - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" 0****** ****** 2****** @@ -4970,7 +5372,12 @@ 4 你 0 4****** ******* *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* 0****** @@ -4978,7 +5385,12 @@ 4 你 0 2****** ****** 4******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* 0****** ****** @@ -4986,7 +5398,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" 0****** ****** ****** @@ -4994,16 +5411,25 @@ 4 你 0 ****** ****** 4******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.TopBottom_LeftRight, @" -0****** - ****** + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" +0****** + ****** 你***** ****** 4****** ******* *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* 0****** @@ -5011,7 +5437,12 @@ 4 你 0 你***** ****** 4******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* 0****** ****** @@ -5019,7 +5450,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" 0****** ****** ****** @@ -5029,7 +5465,12 @@ 4 你 0 4******")] // TopBottom_RightLeft - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** 2****** @@ -5037,7 +5478,12 @@ 4 你 0 4****** ******* *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* 0****** @@ -5045,7 +5491,12 @@ 4 你 0 2****** ****** 4******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* 0****** ****** @@ -5053,7 +5504,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** ****** @@ -5061,8 +5517,12 @@ 4 你 0 ****** ****** 4******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** 你***** @@ -5070,7 +5530,12 @@ 4 你 0 4****** ******* *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* 0****** @@ -5078,7 +5543,12 @@ 4 你 0 你***** ****** 4******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* 0****** ****** @@ -5086,7 +5556,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** ****** @@ -5096,7 +5571,12 @@ 4 你 0 4******")] // BottomTop_LeftRight - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** 2****** @@ -5104,7 +5584,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* 4****** @@ -5112,7 +5597,12 @@ 4 你 0 2****** ****** 0******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* 4****** ****** @@ -5120,7 +5610,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** ****** @@ -5128,8 +5623,12 @@ 4 你 0 ****** ****** 0******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** 你***** @@ -5137,7 +5636,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* 4****** @@ -5145,7 +5649,12 @@ 4 你 0 你***** ****** 0******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* 4****** ****** @@ -5153,7 +5662,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** ****** @@ -5163,7 +5677,12 @@ 4 你 0 0******")] // BottomTop_RightLeft - [InlineData ("0 2 4", Alignment.Start, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** 2****** @@ -5171,7 +5690,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* 4****** @@ -5179,7 +5703,12 @@ 4 你 0 2****** ****** 0******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* 4****** ****** @@ -5187,7 +5716,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 2 4", Alignment.Start, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Start, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** ****** @@ -5195,8 +5729,12 @@ 4 你 0 ****** ****** 0******")] - - [InlineData ("0 你 4", Alignment.Start, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** 你***** @@ -5204,7 +5742,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* 4****** @@ -5212,7 +5755,12 @@ 4 你 0 你***** ****** 0******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* 4****** ****** @@ -5220,7 +5768,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 你 4", Alignment.Start, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Start, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** ****** @@ -5231,7 +5784,12 @@ 4 你 0 // Vertical with alignment.Right // TopBottom_LeftRight - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" ******0 ****** ******2 @@ -5239,7 +5797,12 @@ 4 你 0 ******4 ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* ******0 @@ -5247,7 +5810,12 @@ 4 你 0 ******2 ****** ******4")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* ******0 ****** @@ -5255,7 +5823,12 @@ 4 你 0 ****** ******4 *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" ******0 ****** ****** @@ -5263,8 +5836,12 @@ 4 你 0 ****** ****** ******4")] - - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" *****0* ***** * *****你 @@ -5272,7 +5849,12 @@ 4 你 0 *****4* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* *****0* @@ -5280,7 +5862,12 @@ 4 你 0 *****你 ***** * *****4*")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* *****0* ***** * @@ -5288,7 +5875,12 @@ 4 你 0 ***** * *****4* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" *****0* ***** * ***** * @@ -5298,7 +5890,12 @@ 4 你 0 *****4*")] // TopBottom_RightLeft - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" ******0 ****** ******2 @@ -5306,7 +5903,12 @@ 4 你 0 ******4 ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* ******0 @@ -5314,7 +5916,12 @@ 4 你 0 ******2 ****** ******4")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* ******0 ****** @@ -5322,7 +5929,12 @@ 4 你 0 ****** ******4 *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" ******0 ****** ****** @@ -5330,8 +5942,12 @@ 4 你 0 ****** ****** ******4")] - - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" *****0* ***** * *****你 @@ -5339,7 +5955,12 @@ 4 你 0 *****4* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* *****0* @@ -5347,7 +5968,12 @@ 4 你 0 *****你 ***** * *****4*")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* *****0* ***** * @@ -5355,7 +5981,12 @@ 4 你 0 ***** * *****4* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" *****0* ***** * ***** * @@ -5365,7 +5996,12 @@ 4 你 0 *****4*")] // BottomTop_LeftRight - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" ******4 ****** ******2 @@ -5373,7 +6009,12 @@ 4 你 0 ******0 ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* ******4 @@ -5381,7 +6022,12 @@ 4 你 0 ******2 ****** ******0")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* ******4 ****** @@ -5389,7 +6035,12 @@ 4 你 0 ****** ******0 *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" ******4 ****** ****** @@ -5397,8 +6048,12 @@ 4 你 0 ****** ****** ******0")] - - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" *****4* ***** * *****你 @@ -5406,7 +6061,12 @@ 4 你 0 *****0* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* *****4* @@ -5414,7 +6074,12 @@ 4 你 0 *****你 ***** * *****0*")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* *****4* ***** * @@ -5422,7 +6087,12 @@ 4 你 0 ***** * *****0* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" *****4* ***** * ***** * @@ -5432,7 +6102,12 @@ 4 你 0 *****0*")] // BottomTop_RightLeft - [InlineData ("0 2 4", Alignment.End, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" ******4 ****** ******2 @@ -5440,7 +6115,12 @@ 4 你 0 ******0 ******* *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* ******4 @@ -5448,7 +6128,12 @@ 4 你 0 ******2 ****** ******0")] - [InlineData ("0 2 4", Alignment.End, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* ******4 ****** @@ -5456,7 +6141,12 @@ 4 你 0 ****** ******0 *******")] - [InlineData ("0 2 4", Alignment.End, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.End, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" ******4 ****** ****** @@ -5464,8 +6154,12 @@ 4 你 0 ****** ****** ******0")] - - [InlineData ("0 你 4", Alignment.End, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" *****4* ***** * *****你 @@ -5473,7 +6167,12 @@ 4 你 0 *****0* ******* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* *****4* @@ -5481,7 +6180,12 @@ 4 你 0 *****你 ***** * *****0*")] - [InlineData ("0 你 4", Alignment.End, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* *****4* ***** * @@ -5489,7 +6193,12 @@ 4 你 0 ***** * *****0* *******")] - [InlineData ("0 你 4", Alignment.End, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.End, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" *****4* ***** * ***** * @@ -5500,7 +6209,12 @@ 4 你 0 // Vertical with alignment.Centered // TopBottom_LeftRight - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" ***0*** *** *** ***2*** @@ -5508,7 +6222,12 @@ 4 你 0 ***4*** ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* ***0*** @@ -5516,7 +6235,12 @@ 4 你 0 ***2*** *** *** ***4***")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* ***0*** *** *** @@ -5524,7 +6248,12 @@ 4 你 0 *** *** ***4*** *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" ***0*** *** *** *** *** @@ -5532,8 +6261,12 @@ 4 你 0 *** *** *** *** ***4***")] - - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" **0**** ** **** **你*** @@ -5541,7 +6274,12 @@ 4 你 0 **4**** ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* **0**** @@ -5549,7 +6287,12 @@ 4 你 0 **你*** ** **** **4****")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* **0**** ** **** @@ -5557,7 +6300,12 @@ 4 你 0 ** **** **4**** *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" **0**** ** **** ** **** @@ -5567,7 +6315,12 @@ 4 你 0 **4****")] // TopBottom_RightLeft - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" ***0*** *** *** ***2*** @@ -5575,7 +6328,12 @@ 4 你 0 ***4*** ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* ***0*** @@ -5583,7 +6341,12 @@ 4 你 0 ***2*** *** *** ***4***")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* ***0*** *** *** @@ -5591,7 +6354,12 @@ 4 你 0 *** *** ***4*** *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" ***0*** *** *** *** *** @@ -5599,8 +6367,12 @@ 4 你 0 *** *** *** *** ***4***")] - - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" **0**** ** **** **你*** @@ -5608,7 +6380,12 @@ 4 你 0 **4**** ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* **0**** @@ -5616,7 +6393,12 @@ 4 你 0 **你*** ** **** **4****")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* **0**** ** **** @@ -5624,7 +6406,12 @@ 4 你 0 ** **** **4**** *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" **0**** ** **** ** **** @@ -5634,7 +6421,12 @@ 4 你 0 **4****")] // BottomTop_LeftRight - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" ***4*** *** *** ***2*** @@ -5642,7 +6434,12 @@ 4 你 0 ***0*** ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* ***4*** @@ -5650,7 +6447,12 @@ 4 你 0 ***2*** *** *** ***0***")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* ***4*** *** *** @@ -5658,7 +6460,12 @@ 4 你 0 *** *** ***0*** *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" ***4*** *** *** *** *** @@ -5666,8 +6473,12 @@ 4 你 0 *** *** *** *** ***0***")] - - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" **4**** ** **** **你*** @@ -5675,7 +6486,12 @@ 4 你 0 **0**** ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* **4**** @@ -5683,7 +6499,12 @@ 4 你 0 **你*** ** **** **0****")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* **4**** ** **** @@ -5691,7 +6512,12 @@ 4 你 0 ** **** **0**** *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" **4**** ** **** ** **** @@ -5701,7 +6527,12 @@ 4 你 0 **0****")] // BottomTop_RightLeft - [InlineData ("0 2 4", Alignment.Center, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" ***4*** *** *** ***2*** @@ -5709,7 +6540,12 @@ 4 你 0 ***0*** ******* *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* ***4*** @@ -5717,7 +6553,12 @@ 4 你 0 ***2*** *** *** ***0***")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* ***4*** *** *** @@ -5725,7 +6566,12 @@ 4 你 0 *** *** ***0*** *******")] - [InlineData ("0 2 4", Alignment.Center, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Center, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" ***4*** *** *** *** *** @@ -5733,8 +6579,12 @@ 4 你 0 *** *** *** *** ***0***")] - - [InlineData ("0 你 4", Alignment.Center, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" **4**** ** **** **你*** @@ -5742,7 +6592,12 @@ 4 你 0 **0**** ******* *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* **4**** @@ -5750,7 +6605,12 @@ 4 你 0 **你*** ** **** **0****")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* **4**** ** **** @@ -5758,7 +6618,12 @@ 4 你 0 ** **** **0**** *******")] - [InlineData ("0 你 4", Alignment.Center, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Center, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" **4**** ** **** ** **** @@ -5769,7 +6634,12 @@ 4 你 0 // Vertical with alignment.Justified // TopBottom_LeftRight - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" 0****** ****** 2****** @@ -5777,7 +6647,12 @@ 4 你 0 4****** ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* 0****** @@ -5785,7 +6660,12 @@ 4 你 0 2****** ****** 4******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* 0****** ****** @@ -5793,7 +6673,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" 0****** ****** ****** @@ -5801,8 +6686,12 @@ 4 你 0 ****** ****** 4******")] - - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.TopBottom_LeftRight, + @" 0****** ****** 你***** @@ -5810,7 +6699,12 @@ 4 你 0 4****** ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.TopBottom_LeftRight, + @" ******* ******* 0****** @@ -5818,7 +6712,12 @@ 4 你 0 你***** ****** 4******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.TopBottom_LeftRight, + @" ******* 0****** ****** @@ -5826,7 +6725,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.TopBottom_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.TopBottom_LeftRight, + @" 0****** ****** ****** @@ -5836,7 +6740,12 @@ 4 你 0 4******")] // TopBottom_RightLeft - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** 2****** @@ -5844,7 +6753,12 @@ 4 你 0 4****** ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* 0****** @@ -5852,7 +6766,12 @@ 4 你 0 2****** ****** 4******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* 0****** ****** @@ -5860,7 +6779,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** ****** @@ -5868,8 +6792,12 @@ 4 你 0 ****** ****** 4******")] - - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** 你***** @@ -5877,7 +6805,12 @@ 4 你 0 4****** ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.TopBottom_RightLeft, + @" ******* ******* 0****** @@ -5885,7 +6818,12 @@ 4 你 0 你***** ****** 4******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.TopBottom_RightLeft, + @" ******* 0****** ****** @@ -5893,7 +6831,12 @@ 4 你 0 ****** 4****** *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.TopBottom_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.TopBottom_RightLeft, + @" 0****** ****** ****** @@ -5903,7 +6846,12 @@ 4 你 0 4******")] // BottomTop_LeftRight - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** 2****** @@ -5911,7 +6859,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* 4****** @@ -5919,7 +6872,12 @@ 4 你 0 2****** ****** 0******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* 4****** ****** @@ -5927,7 +6885,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** ****** @@ -5935,8 +6898,12 @@ 4 你 0 ****** ****** 0******")] - - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** 你***** @@ -5944,7 +6911,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.BottomTop_LeftRight, + @" ******* ******* 4****** @@ -5952,7 +6924,12 @@ 4 你 0 你***** ****** 0******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.BottomTop_LeftRight, + @" ******* 4****** ****** @@ -5960,7 +6937,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.BottomTop_LeftRight, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.BottomTop_LeftRight, + @" 4****** ****** ****** @@ -5970,7 +6952,12 @@ 4 你 0 0******")] // BottomTop_RightLeft - [InlineData ("0 2 4", Alignment.Fill, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** 2****** @@ -5978,7 +6965,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* 4****** @@ -5986,7 +6978,12 @@ 4 你 0 2****** ****** 0******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* 4****** ****** @@ -5994,7 +6991,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 2 4", Alignment.Fill, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 2 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** ****** @@ -6002,8 +7004,12 @@ 4 你 0 ****** ****** 0******")] - - [InlineData ("0 你 4", Alignment.Fill, Alignment.Start, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Start, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** 你***** @@ -6011,7 +7017,12 @@ 4 你 0 0****** ******* *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.End, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.End, + TextDirection.BottomTop_RightLeft, + @" ******* ******* 4****** @@ -6019,7 +7030,12 @@ 4 你 0 你***** ****** 0******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Center, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Center, + TextDirection.BottomTop_RightLeft, + @" ******* 4****** ****** @@ -6027,7 +7043,12 @@ 4 你 0 ****** 0****** *******")] - [InlineData ("0 你 4", Alignment.Fill, Alignment.Fill, TextDirection.BottomTop_RightLeft, @" + [InlineData ( + "0 你 4", + Alignment.Fill, + Alignment.Fill, + TextDirection.BottomTop_RightLeft, + @" 4****** ****** ****** @@ -6035,7 +7056,6 @@ 4 你 0 ****** ****** 0******")] - public void Draw_Text_Justification (string text, Alignment horizontalTextAlignment, Alignment alignment, TextDirection textDirection, string expectedText) { TextFormatter tf = new () @@ -6047,12 +7067,11 @@ public void Draw_Text_Justification (string text, Alignment horizontalTextAlignm Text = text }; - Application.Driver.FillRect (new Rectangle (0, 0, 7, 7), (Rune)'*'); - tf.Draw (new Rectangle (0, 0, 7, 7), Attribute.Default, Attribute.Default); + Application.Driver.FillRect (new (0, 0, 7, 7), (Rune)'*'); + tf.Draw (new (0, 0, 7, 7), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } - // Test that changing TextFormatter does not impact View dimensions if Dim.Auto is not in play [Fact] public void Not_Used_TextFormatter_Does_Not_Change_View_Size () @@ -6061,31 +7080,24 @@ public void Not_Used_TextFormatter_Does_Not_Change_View_Size () { Text = "_1234" }; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.TextFormatter.Text = "ABC"; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.TextFormatter.Alignment = Alignment.Fill; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.TextFormatter.VerticalAlignment = Alignment.Center; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.TextFormatter.HotKeySpecifier = (Rune)'*'; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.TextFormatter.Text = "*ABC"; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); } - [Fact] public void Not_Used_TextSettings_Do_Not_Change_View_Size () { @@ -6093,27 +7105,21 @@ public void Not_Used_TextSettings_Do_Not_Change_View_Size () { Text = "_1234" }; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.TextAlignment = Alignment.Fill; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.VerticalTextAlignment = Alignment.Center; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.HotKeySpecifier = (Rune)'*'; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); view.Text = "*ABC"; - Assert.False (view.TextFormatter.AutoSize); Assert.Equal (Size.Empty, view.Frame.Size); } - #region FormatAndGetSizeTests // TODO: Add multi-line examples @@ -6123,47 +7129,96 @@ public void Not_Used_TextSettings_Do_Not_Change_View_Size () [SetupFakeDriver] [InlineData ("界1234", 10, 10, TextDirection.LeftRight_TopBottom, 6, 1, @"界1234")] [InlineData ("01234", 10, 10, TextDirection.LeftRight_TopBottom, 5, 1, @"01234")] - [InlineData ("界1234", 10, 10, TextDirection.TopBottom_LeftRight, 2, 5, """ - 界 - 1 - 2 - 3 - 4 - """)] - [InlineData ("01234", 10, 10, TextDirection.TopBottom_LeftRight, 1, 5, """ - 0 - 1 - 2 - 3 - 4 - """)] - [InlineData ("界1234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 2, """ - 界1 - 234 - """)] - [InlineData ("01234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 2, """ - 012 - 34 - """)] - [InlineData ("界1234", 3, 3, TextDirection.TopBottom_LeftRight, 3, 3, """ - 界3 - 1 4 - 2 - """)] - [InlineData ("01234", 3, 3, TextDirection.TopBottom_LeftRight, 2, 3, """ - 03 - 14 - 2 - """)] - + [InlineData ( + "界1234", + 10, + 10, + TextDirection.TopBottom_LeftRight, + 2, + 5, + """ + 界 + 1 + 2 + 3 + 4 + """)] + [InlineData ( + "01234", + 10, + 10, + TextDirection.TopBottom_LeftRight, + 1, + 5, + """ + 0 + 1 + 2 + 3 + 4 + """)] + [InlineData ( + "界1234", + 3, + 3, + TextDirection.LeftRight_TopBottom, + 3, + 2, + """ + 界1 + 234 + """)] + [InlineData ( + "01234", + 3, + 3, + TextDirection.LeftRight_TopBottom, + 3, + 2, + """ + 012 + 34 + """)] + [InlineData ( + "界1234", + 3, + 3, + TextDirection.TopBottom_LeftRight, + 3, + 3, + """ + 界3 + 1 4 + 2 + """)] + [InlineData ( + "01234", + 3, + 3, + TextDirection.TopBottom_LeftRight, + 2, + 3, + """ + 03 + 14 + 2 + """)] [InlineData ("01234", 2, 1, TextDirection.LeftRight_TopBottom, 2, 1, @"01")] - - public void FormatAndGetSize_Returns_Correct_Size (string text, int width, int height, TextDirection direction, int expectedWidth, int expectedHeight, string expectedDraw) + public void FormatAndGetSize_Returns_Correct_Size ( + string text, + int width, + int height, + TextDirection direction, + int expectedWidth, + int expectedHeight, + string expectedDraw + ) { TextFormatter tf = new () { Direction = direction, - Size = new (width, height), + Width = width, + Height = height, Text = text }; Assert.True (tf.WordWrap); @@ -6179,33 +7234,69 @@ public void FormatAndGetSize_Returns_Correct_Size (string text, int width, int h [SetupFakeDriver] [InlineData ("界1234", 10, 10, TextDirection.LeftRight_TopBottom, 6, 1, @"界1234")] [InlineData ("01234", 10, 10, TextDirection.LeftRight_TopBottom, 5, 1, @"01234")] - [InlineData ("界1234", 10, 10, TextDirection.TopBottom_LeftRight, 2, 5, """ - 界 - 1 - 2 - 3 - 4 - """)] - [InlineData ("01234", 10, 10, TextDirection.TopBottom_LeftRight, 1, 5, """ - 0 - 1 - 2 - 3 - 4 - """)] + [InlineData ( + "界1234", + 10, + 10, + TextDirection.TopBottom_LeftRight, + 2, + 5, + """ + 界 + 1 + 2 + 3 + 4 + """)] + [InlineData ( + "01234", + 10, + 10, + TextDirection.TopBottom_LeftRight, + 1, + 5, + """ + 0 + 1 + 2 + 3 + 4 + """)] [InlineData ("界1234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 1, @"界1")] [InlineData ("01234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 1, @"012")] - [InlineData ("界1234", 3, 3, TextDirection.TopBottom_LeftRight, 2, 3, """ - 界 - 1 - 2 - """)] - [InlineData ("01234", 3, 3, TextDirection.TopBottom_LeftRight, 1, 3, """ - 0 - 1 - 2 - """)] - public void FormatAndGetSize_WordWrap_False_Returns_Correct_Size (string text, int width, int height, TextDirection direction, int expectedWidth, int expectedHeight, string expectedDraw) + [InlineData ( + "界1234", + 3, + 3, + TextDirection.TopBottom_LeftRight, + 2, + 3, + """ + 界 + 1 + 2 + """)] + [InlineData ( + "01234", + 3, + 3, + TextDirection.TopBottom_LeftRight, + 1, + 3, + """ + 0 + 1 + 2 + """)] + public void FormatAndGetSize_WordWrap_False_Returns_Correct_Size ( + string text, + int width, + int height, + TextDirection direction, + int expectedWidth, + int expectedHeight, + string expectedDraw + ) { TextFormatter tf = new () { diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 272d787802..9308764827 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -293,7 +293,6 @@ public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_Wi var top = new Toplevel (); top.Add (win); - // Don't use Label. It sets AutoSize = true which is not what we're testing here. var view = new View { Text = "ワイドルーン。", Height = Dim.Fill (), Width = Dim.Fill () }; // Don't have unit tests use things that aren't absolutely critical for the test, like Dialog diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index 27ccc197de..431b673a5f 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -924,20 +924,6 @@ public void TextFormatter_Settings_Change_View_Size () Assert.NotEqual (lastSize, view.Frame.Size); } - // Ensure TextFormatter.AutoSize is never used for View.Text - [Fact] - public void TextFormatter_Is_Not_Auto () - { - View view = new (); - Assert.False (view.TextFormatter.AutoSize); - view.Width = Auto (); - Assert.False (view.TextFormatter.AutoSize); - - view = new (); - Assert.False (view.TextFormatter.AutoSize); - view.Height = Auto (); - Assert.False (view.TextFormatter.AutoSize); - } [Theory] [InlineData ("1234", 4)] diff --git a/UnitTests/View/Layout/Pos.Tests.cs b/UnitTests/View/Layout/Pos.Tests.cs index 9a98cf8ff7..c3e6bb9a20 100644 --- a/UnitTests/View/Layout/Pos.Tests.cs +++ b/UnitTests/View/Layout/Pos.Tests.cs @@ -6,8 +6,6 @@ namespace Terminal.Gui.LayoutTests; public class PosTests () { - // Was named AutoSize_Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () - // but doesn't actually have anything to do with AutoSize. [Fact] public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index a54664d495..201336d347 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -5,8 +5,7 @@ namespace Terminal.Gui.ViewTests; /// -/// Tests of the and properties (independent of -/// AutoSize). +/// Tests of the and properties. /// public class TextTests (ITestOutputHelper output) { @@ -398,7 +397,7 @@ public void TextDirection_Toggle () [Fact] [AutoInitShutdown] - public void AutoSize_True_View_IsEmpty_False_Minimum_Width () + public void View_IsEmpty_False_Minimum_Width () { var text = "Views"; @@ -508,7 +507,7 @@ public void DimAuto_Vertical_TextDirection_Wide_Rune () [Fact] [AutoInitShutdown] - public void AutoSize_True_Width_Height_SetMinWidthHeight_Narrow_Wide_Runes () + public void Width_Height_SetMinWidthHeight_Narrow_Wide_Runes () { var text = $"0123456789{Environment.NewLine}01234567891"; @@ -605,7 +604,7 @@ public void AutoSize_True_Width_Height_SetMinWidthHeight_Narrow_Wide_Runes () [Fact] [AutoInitShutdown] - public void AutoSize_True_Width_Height_Stay_True_If_TextFormatter_Size_Fit () + public void Width_Height_Stay_True_If_TextFormatter_Size_Fit () { var text = "Finish 終"; @@ -736,11 +735,9 @@ string GetContents () top.Dispose (); } - [Theory] + [Fact] [AutoInitShutdown] - [InlineData (true)] - [InlineData (false)] - public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) + public void View_Draw_Horizontal_Simple_TextAlignments () { var text = "Hello World"; var width = 20; @@ -752,12 +749,6 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) Height = 1 }; - if (autoSize) - { - lblLeft.Width = Dim.Auto (); - lblLeft.Height = Dim.Auto (); - } - var lblCenter = new View { Text = text, @@ -767,12 +758,6 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) TextAlignment = Alignment.Center }; - if (autoSize) - { - lblCenter.Width = Dim.Auto (); - lblCenter.Height = Dim.Auto (); - } - var lblRight = new View { Text = text, @@ -782,12 +767,6 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) TextAlignment = Alignment.End }; - if (autoSize) - { - lblRight.Width = Dim.Auto (); - lblRight.Height = Dim.Auto (); - } - var lblJust = new View { Text = text, @@ -797,12 +776,6 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) TextAlignment = Alignment.Fill }; - if (autoSize) - { - lblJust.Width = Dim.Auto (); - lblJust.Height = Dim.Auto (); - } - var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () }; frame.Add (lblLeft, lblCenter, lblRight, lblJust); var top = new Toplevel (); @@ -811,41 +784,16 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) ((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6); // frame.Width is width + border wide (20 + 2) and 6 high - - if (autoSize) - { - Size expectedSize = new (11, 1); - Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); - Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); - Assert.Equal (expectedSize, lblRight.TextFormatter.Size); - Assert.Equal (expectedSize, lblJust.TextFormatter.Size); - } - else - { - Size expectedSize = new (width, 1); - Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); - Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); - Assert.Equal (expectedSize, lblRight.TextFormatter.Size); - Assert.Equal (expectedSize, lblJust.TextFormatter.Size); - } + Size expectedSize = new (width, 1); + Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); + Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); + Assert.Equal (expectedSize, lblRight.TextFormatter.Size); + Assert.Equal (expectedSize, lblJust.TextFormatter.Size); Assert.Equal (new (0, 0, width + 2, 6), frame.Frame); string expected; - if (autoSize) - { - expected = @" -┌────────────────────┐ -│Hello World │ -│Hello World │ -│Hello World │ -│Hello World │ -└────────────────────┘ -"; - } - else - { expected = @" ┌────────────────────┐ │Hello World │ @@ -854,7 +802,6 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) │Hello World│ └────────────────────┘ "; - } Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); Assert.Equal (new (0, 0, width + 2, 6), pos); @@ -1300,9 +1247,4 @@ public void Narrow_Wide_Runes () pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); } - - // Test behavior of AutoSize property. - // - Default is false - // - Setting to true invalidates Height/Width - // - Setting to false invalidates Height/Width } diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index bb7d3747a4..877a72292a 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -1006,7 +1006,6 @@ public void Visible_Clear_The_View_Output () { var view = new View { Text = "Testing visibility." }; // use View, not Label to avoid AutoSize == true - // BUGBUG: AutoSize is false and size wasn't provided so it's 0,0 Assert.Equal (0, view.Frame.Width); Assert.Equal (0, view.Height); var win = new Window (); diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index baeab4aa92..9bfce36f79 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -86,7 +86,7 @@ public void HotKey_Command_Does_Not_Accept () [Fact] [AutoInitShutdown] - public void AutoSize_Stays_True_AnchorEnd () + public void Text_Set_With_AnchorEnd_Works () { var label = new Label { Y = Pos.Center (), Text = "Say Hello 你" }; label.X = Pos.AnchorEnd (0) - Pos.Func (() => label.TextFormatter.Text.GetColumns ()); @@ -127,7 +127,7 @@ public void AutoSize_Stays_True_AnchorEnd () [Fact] [AutoInitShutdown] - public void AutoSize_Stays_True_Center () + public void Set_Text_With_Center () { var label = new Label { X = Pos.Center (), Y = Pos.Center (), Text = "Say Hello 你" }; @@ -164,35 +164,7 @@ public void AutoSize_Stays_True_Center () TestHelpers.AssertDriverContentsWithFrameAre (expected, output); top.Dispose (); } - - [Fact] - [AutoInitShutdown] - public void AutoSize_Stays_True_With_EmptyText () - { - var label = new Label { X = Pos.Center (), Y = Pos.Center () }; - - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - win.Add (label); - var top = new Toplevel (); - top.Add (win); - - label.Text = "Say Hello 你"; - - Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); - - var expected = @" -┌────────────────────────────┐ -│ │ -│ Say Hello 你 │ -│ │ -└────────────────────────────┘ -"; - - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - top.Dispose (); - } - + [Fact] public void Constructors_Defaults () { @@ -206,7 +178,7 @@ public void Constructors_Defaults () [Fact] [AutoInitShutdown] - public void Label_Draw_Fill_Remaining_AutoSize_False () + public void Label_Draw_Fill_Remaining () { var tfSize = new Size (80, 1); @@ -222,9 +194,6 @@ public void Label_Draw_Fill_Remaining_AutoSize_False () top.Add (label); Application.Begin (top); - Assert.False (label.TextFormatter.AutoSize); - Assert.False (tf1.AutoSize); - Assert.False (tf2.AutoSize); Assert.False (label.TextFormatter.FillRemaining); Assert.False (tf1.FillRemaining); Assert.True (tf2.FillRemaining); @@ -914,7 +883,6 @@ public void Bottom_Equal_Inside_Window () { var win = new Window (); - // Label is AutoSize == true var label = new Label { Text = "This should be the last line.", @@ -955,73 +923,6 @@ public void Bottom_Equal_Inside_Window () top.Dispose (); } -#if V2_STATUSBAR - // TODO: This is a Label test. Move to label tests if there's not already a test for this. - - [Fact] - [AutoInitShutdown] - public void Bottom_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel () - { - var win = new Window (); - - // Label is AutoSize == true - var label = new Label - { - Text = "This should be the last line.", - ColorScheme = Colors.ColorSchemes ["Menu"], - - //Width = Dim.Fill (), - X = 0, - Y = Pos.Bottom (win) - 4 // two lines top and bottom borders more two lines above border - }; - - win.Add (label); - - var menu = new MenuBar { Menus = new MenuBarItem [] { new ("Menu", "", null) } }; - var status = new StatusBar (new StatusItem [] { new (KeyCode.F1, "~F1~ Help", null) }); - Toplevel top = new (); - top.Add (win, menu, status); - RunState rs = Application.Begin (top); - - Assert.Equal (new (0, 0, 80, 25), top.Frame); - Assert.Equal (new (0, 0, 80, 1), menu.Frame); - Assert.Equal (new (0, 24, 80, 1), status.Frame); - Assert.Equal (new (0, 1, 80, 23), win.Frame); - Assert.Equal (new (0, 20, 29, 1), label.Frame); - - var expected = @" - Menu -┌──────────────────────────────────────────────────────────────────────────────┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│This should be the last line. │ -└──────────────────────────────────────────────────────────────────────────────┘ - F1 Help -"; - - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } -#endif // TODO: This is a Dim test. Move to Dim tests. [Fact] @@ -1041,7 +942,6 @@ public void Dim_Subtract_Operator_With_Text () var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 }; var count = 20; - // Label is AutoSize == true List public bool FillRemaining { get; set; } - /// Returns the formatted text, constrained to . + /// Returns the formatted text, constrained to . /// /// If is , causes a format, resetting /// to . @@ -469,9 +462,12 @@ public string Format () return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ()); } - /// Gets the size required to hold the formatted text, given the constraints placed by . + /// Gets the size required to hold the formatted text, given the constraints placed by . /// Causes a format, resetting to . - /// If provided, will cause the text to be constrained to the provided size instead of and . + /// + /// If provided, will cause the text to be constrained to the provided size instead of and + /// . + /// /// The size required to hold the formatted text. public Size FormatAndGetSize (Size? constrainSize = null) { @@ -480,13 +476,13 @@ public Size FormatAndGetSize (Size? constrainSize = null) return System.Drawing.Size.Empty; } - int? prevWidth = _width; - int? prevHeight = _height; + int? prevWidth = _constrainToWidth; + int? prevHeight = _constrainToHeight; if (constrainSize is { }) { - _width = constrainSize?.Width; - _height = constrainSize?.Height; + _constrainToWidth = constrainSize?.Width; + _constrainToHeight = constrainSize?.Height; } // HACK: Fill normally will fill the entire constraint size, but we need to know the actual size of the text. @@ -513,8 +509,8 @@ public Size FormatAndGetSize (Size? constrainSize = null) if (constrainSize is { }) { - _width = prevWidth ?? null; - _height = prevHeight ?? null; + _constrainToWidth = prevWidth ?? null; + _constrainToHeight = prevHeight ?? null; } if (lines.Count == 0) @@ -569,7 +565,7 @@ public int GetHotKeySpecifierLength (bool isWidth = true) : 0; } - /// Gets a list of formatted lines, constrained to . + /// Gets a list of formatted lines, constrained to . /// /// /// If the text needs to be formatted (if is ) @@ -577,14 +573,14 @@ public int GetHotKeySpecifierLength (bool isWidth = true) /// will be . /// /// - /// If either of the dimensions of are zero, the text will not be formatted and no lines will + /// If either of the dimensions of are zero, the text will not be formatted and no lines will /// be returned. /// /// public List GetLines () { - int width = _width.GetValueOrDefault (); - int height = _height.GetValueOrDefault (); + int width = _constrainToWidth.GetValueOrDefault (); + int height = _constrainToHeight.GetValueOrDefault (); // With this check, we protect against subclasses with overrides of Text if (string.IsNullOrEmpty (Text) || width == 0 || height == 0) @@ -657,6 +653,40 @@ public List GetLines () return _lines; } + private int? _constrainToWidth; + + /// Gets or sets the width will be constrained to when formatted. + /// + /// + /// Does not return the width of the formatted text but the width that will be used to constrain the text when + /// formatted. + /// + /// + /// If the height will be unconstrained. if both and are the text will be formatted to the size of the text. + /// + /// + /// Use to get the size of the formatted text. + /// + /// When set, is set to . + /// + public int? ConstrainToWidth + { + get => _constrainToWidth; + set + { + if (_constrainToWidth == value) + { + return; + } + + ArgumentOutOfRangeException.ThrowIfNegative (value.GetValueOrDefault (), nameof (ConstrainToWidth)); + + _constrainToWidth = EnableNeedsFormat (value); + } + } + + private int? _constrainToHeight; + /// Gets or sets the height will be constrained to when formatted. /// /// @@ -664,31 +694,65 @@ public List GetLines () /// formatted. /// /// + /// If the height will be unconstrained. if both and are the text will be formatted to the size of the text. + /// + /// /// Use to get the size of the formatted text. /// /// When set, is set to . /// - public int? Height + public int? ConstrainToHeight { - get => _height; + get => _constrainToHeight; set { - if (_height == value) + if (_constrainToHeight == value) { return; } - ArgumentOutOfRangeException.ThrowIfNegative (value.GetValueOrDefault (), nameof (Height)); + ArgumentOutOfRangeException.ThrowIfNegative (value.GetValueOrDefault (), nameof (ConstrainToHeight)); - _height = value; + _constrainToHeight = EnableNeedsFormat (value); + } + } - if (_width is null || _height is null) + /// Gets or sets the width and height will be constrained to when formatted. + /// + /// + /// Does not return the size of the formatted text but the size that will be used to constrain the text when + /// formatted. + /// + /// + /// Use to get the size of the formatted text. + /// + /// When set, is set to . + /// + public Size? ConstrainToSize + { + get + { + if (_constrainToWidth is null || _constrainToHeight is null) { - return; + return null; } - _height = EnableNeedsFormat (value); + return new Size (_constrainToWidth.Value, _constrainToHeight.Value); + } + set + { + if (value is null) + { + _constrainToWidth = null; + _constrainToHeight = null; + EnableNeedsFormat (true); + } + else + { + _constrainToWidth = EnableNeedsFormat (value.Value.Width); + _constrainToHeight = EnableNeedsFormat (value.Value.Height); + } } } @@ -751,45 +815,6 @@ public bool PreserveTrailingSpaces set => _preserveTrailingSpaces = EnableNeedsFormat (value); } - /// Gets or sets the width and height will be constrained to when formatted. - /// - /// - /// Does not return the size of the formatted text but the size that will be used to constrain the text when - /// formatted. - /// - /// - /// Use to get the size of the formatted text. - /// - /// When set, is set to . - /// - public Size? Size - { - get - { - if (_width is null || _height is null) - { - return null; - } - - return new Size (_width.Value, _height.Value); - } - set - { - if (value is null) - { - _width = null; - _height = null; - EnableNeedsFormat (true); - } - else - { - Size size = EnableNeedsFormat (value.Value); - _width = size.Width; - _height = size.Height; - } - } - } - /// Gets or sets the number of columns used for a tab. public int TabWidth { @@ -812,41 +837,7 @@ public Alignment VerticalAlignment set => _textVerticalAlignment = EnableNeedsFormat (value); } - /// Gets or sets the width will be constrained to when formatted. - /// - /// - /// Does not return the width of the formatted text but the width that will be used to constrain the text when - /// formatted. - /// - /// - /// Use to get the size of the formatted text. - /// - /// When set, is set to . - /// - public int? Width - { - get => _width; - set - { - if (_width == value) - { - return; - } - - ArgumentOutOfRangeException.ThrowIfNegative (value.GetValueOrDefault (), nameof (Width)); - - _width = value; - - if (_width is null || _height is null) - { - return; - } - - _width = EnableNeedsFormat (value); - } - } - - /// Gets or sets whether word wrap will be used to fit to . + /// Gets or sets whether word wrap will be used to fit to . public bool WordWrap { get => _wordWrap; @@ -870,48 +861,48 @@ private T EnableNeedsFormat (T value) public static bool IsHorizontalDirection (TextDirection textDirection) { return textDirection switch - { - TextDirection.LeftRight_TopBottom => true, - TextDirection.LeftRight_BottomTop => true, - TextDirection.RightLeft_TopBottom => true, - TextDirection.RightLeft_BottomTop => true, - _ => false - }; + { + TextDirection.LeftRight_TopBottom => true, + TextDirection.LeftRight_BottomTop => true, + TextDirection.RightLeft_TopBottom => true, + TextDirection.RightLeft_BottomTop => true, + _ => false + }; } /// Check if it is a vertical direction public static bool IsVerticalDirection (TextDirection textDirection) { return textDirection switch - { - TextDirection.TopBottom_LeftRight => true, - TextDirection.TopBottom_RightLeft => true, - TextDirection.BottomTop_LeftRight => true, - TextDirection.BottomTop_RightLeft => true, - _ => false - }; + { + TextDirection.TopBottom_LeftRight => true, + TextDirection.TopBottom_RightLeft => true, + TextDirection.BottomTop_LeftRight => true, + TextDirection.BottomTop_RightLeft => true, + _ => false + }; } /// Check if it is Left to Right direction public static bool IsLeftToRight (TextDirection textDirection) { return textDirection switch - { - TextDirection.LeftRight_TopBottom => true, - TextDirection.LeftRight_BottomTop => true, - _ => false - }; + { + TextDirection.LeftRight_TopBottom => true, + TextDirection.LeftRight_BottomTop => true, + _ => false + }; } /// Check if it is Top to Bottom direction public static bool IsTopToBottom (TextDirection textDirection) { return textDirection switch - { - TextDirection.TopBottom_LeftRight => true, - TextDirection.TopBottom_RightLeft => true, - _ => false - }; + { + TextDirection.TopBottom_LeftRight => true, + TextDirection.TopBottom_RightLeft => true, + _ => false + }; } // TODO: Move to StringExtensions? @@ -1300,21 +1291,21 @@ int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = case ' ': return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); case '\t': - { - length += tabWidth + 1; - - if (length == tabWidth && tabWidth > cWidth) - { - return to + 1; - } + { + length += tabWidth + 1; - if (length > cWidth && tabWidth > cWidth) - { - return to; - } + if (length == tabWidth && tabWidth > cWidth) + { + return to + 1; + } - return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); + if (length > cWidth && tabWidth > cWidth) + { + return to; } + + return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length); + } default: to++; @@ -1323,11 +1314,11 @@ int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = } return cLength switch - { - > 0 when to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t' => from, - > 0 when to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t') => from, - _ => to - }; + { + > 0 when to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t' => from, + > 0 when to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t') => from, + _ => to + }; } if (start < text.GetRuneCount ()) @@ -1511,7 +1502,6 @@ public static string Justify ( { ArgumentOutOfRangeException.ThrowIfNegative (width, nameof (width)); - if (string.IsNullOrEmpty (text)) { return text; @@ -1655,7 +1645,6 @@ public static List Format ( { ArgumentOutOfRangeException.ThrowIfNegative (width, nameof (width)); - List lineResult = new (); if (string.IsNullOrEmpty (text) || width == 0) @@ -1765,13 +1754,13 @@ public static List Format ( private static string PerformCorrectFormatDirection (TextDirection textDirection, string line) { return textDirection switch - { - TextDirection.RightLeft_BottomTop - or TextDirection.RightLeft_TopBottom - or TextDirection.BottomTop_LeftRight - or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), - _ => line - }; + { + TextDirection.RightLeft_BottomTop + or TextDirection.RightLeft_TopBottom + or TextDirection.BottomTop_LeftRight + or TextDirection.BottomTop_RightLeft => StringExtensions.ToString (line.EnumerateRunes ().Reverse ()), + _ => line + }; } private static List PerformCorrectFormatDirection (TextDirection textDirection, List runes) @@ -1782,13 +1771,13 @@ private static List PerformCorrectFormatDirection (TextDirection textDirec private static List PerformCorrectFormatDirection (TextDirection textDirection, List lines) { return textDirection switch - { - TextDirection.TopBottom_RightLeft - or TextDirection.LeftRight_BottomTop - or TextDirection.RightLeft_BottomTop - or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), - _ => lines - }; + { + TextDirection.TopBottom_RightLeft + or TextDirection.LeftRight_BottomTop + or TextDirection.RightLeft_BottomTop + or TextDirection.BottomTop_RightLeft => lines.ToArray ().Reverse ().ToList (), + _ => lines + }; } /// @@ -1998,7 +1987,6 @@ public static int GetMaxColsForWidth (List lines, int width, int tabWidt /// The number of columns used for a tab. /// [Obsolete ("CalcRect is deprecated, FormatAndGetSize instead.")] - internal static Rectangle CalcRect ( int x, int y, diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs index a1db317cbb..e7cff5e8b4 100644 --- a/Terminal.Gui/View/Adornment/Adornment.cs +++ b/Terminal.Gui/View/Adornment/Adornment.cs @@ -165,7 +165,7 @@ public override void OnDrawContent (Rectangle viewport) { if (TextFormatter is { }) { - TextFormatter.Size = Frame.Size; + TextFormatter.ConstrainToSize = Frame.Size; TextFormatter.NeedsFormat = true; } } diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 076ec74cb5..02daea2a87 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -421,7 +421,7 @@ public override void OnDrawContent (Rectangle viewport) ) ); - Parent.TitleTextFormatter.Size = new (maxTitleWidth, 1); + Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1); int sideLineLength = borderBounds.Height; bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 }; diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index c649616fdf..9a86125424 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -88,27 +88,27 @@ internal override int Calculate (int location, int superviewContentSize, View us { if (dimension == Dimension.Width) { - if (us.TextFormatter.Width is null) + if (us.TextFormatter.ConstrainToWidth is null) { // Set BOTH width and height (by setting Size). We do this because we will be called again, next // for Dimension.Height. We need to know the width to calculate the height. - us.TextFormatter.Size = us.TextFormatter.FormatAndGetSize (new (int.Min (autoMax, screenX4), screenX4)); + us.TextFormatter.ConstrainToSize = us.TextFormatter.FormatAndGetSize (new (int.Min (autoMax, screenX4), screenX4)); } - textSize = us.TextFormatter.Width!.Value; + textSize = us.TextFormatter.ConstrainToWidth!.Value; } else { - if (us.TextFormatter.Height is null) + if (us.TextFormatter.ConstrainToHeight is null) { // Set just the height. It is assumed that the width has already been set. // TODO: There may be cases where the width is not set. We may need to set it here. - textSize = us.TextFormatter.FormatAndGetSize (new (us.TextFormatter.Width ?? screenX4, int.Min (autoMax, screenX4))).Height; - us.TextFormatter.Height = textSize; + textSize = us.TextFormatter.FormatAndGetSize (new (us.TextFormatter.ConstrainToWidth ?? screenX4, int.Min (autoMax, screenX4))).Height; + us.TextFormatter.ConstrainToHeight = textSize; } else { - textSize = us.TextFormatter.Height.Value; + textSize = us.TextFormatter.ConstrainToHeight.Value; } } } diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 421894c5e7..0a6c7a7fd5 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -481,7 +481,7 @@ public Dim? Height _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); // Reset TextFormatter - Will be recalculated in SetTextFormatterSize - TextFormatter.Height = null; + TextFormatter.ConstrainToHeight = null; OnResizeNeeded (); } @@ -530,7 +530,7 @@ public Dim? Width _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); // Reset TextFormatter - Will be recalculated in SetTextFormatterSize - TextFormatter.Width = null; + TextFormatter.ConstrainToWidth = null; OnResizeNeeded (); } @@ -647,14 +647,14 @@ internal void SetRelativeLayout (Size superviewContentSize) SetNeedsDisplay (); } - if (TextFormatter.Width is null) + if (TextFormatter.ConstrainToWidth is null) { - TextFormatter.Width = GetContentSize ().Width; + TextFormatter.ConstrainToWidth = GetContentSize ().Width; } - if (TextFormatter.Height is null) + if (TextFormatter.ConstrainToHeight is null) { - TextFormatter.Height = GetContentSize ().Height; + TextFormatter.ConstrainToHeight = GetContentSize ().Height; } } diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 68eb1853f3..df782c294f 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -479,7 +479,7 @@ public string Title private void SetTitleTextFormatterSize () { - TitleTextFormatter.Size = new ( + TitleTextFormatter.ConstrainToSize = new ( TextFormatter.GetWidestLineLength (TitleTextFormatter.Text) - (TitleTextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true ? Math.Max (HotKeySpecifier.GetColumns (), 0) diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 0e43dba948..40a669961c 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -158,15 +158,15 @@ public virtual Alignment VerticalTextAlignment protected virtual void UpdateTextFormatterText () { TextFormatter.Text = _text; - TextFormatter.Width = null; - TextFormatter.Height = null; + TextFormatter.ConstrainToWidth = null; + TextFormatter.ConstrainToHeight = null; } /// /// Internal API. Sets .Width/Height. /// /// - /// Use this API to set /Height when the view has changed such that the + /// Use this API to set /Height when the view has changed such that the /// size required to fit the text has changed. /// changes. /// @@ -186,25 +186,25 @@ internal void SetTextFormatterSize () if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) { - TextFormatter.Width = null; + TextFormatter.ConstrainToWidth = null; } else { if (size is { }) { - TextFormatter.Width = size?.Width; + TextFormatter.ConstrainToWidth = size?.Width; } } if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) { - TextFormatter.Height = null; + TextFormatter.ConstrainToHeight = null; } else { if (size is { }) { - TextFormatter.Height = size?.Height; + TextFormatter.ConstrainToHeight = size?.Height; } } } @@ -227,8 +227,8 @@ private void UpdateTextDirection (TextDirection newDirection) if (directionChanged) { - TextFormatter.Width = null; - TextFormatter.Height = null; + TextFormatter.ConstrainToWidth = null; + TextFormatter.ConstrainToHeight = null; OnResizeNeeded (); } diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 7ae998ee00..8c457136a7 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -489,8 +489,8 @@ public override void OnDrawContent (Rectangle viewport) { var tf = new TextFormatter { - Width = Frame.Width - 3, - Height = 1, + ConstrainToWidth = Frame.Width - 3, + ConstrainToHeight = 1, Alignment = Alignment.Center, HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw }; diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 3ae1e655c5..a177b9daec 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -383,7 +383,7 @@ public void Draw_With_Combining_Runes (int width, int height, TextDirection text Assert.True (tf.WordWrap); - tf.Size = new (width, height); + tf.ConstrainToSize = new (width, height); tf.Draw ( new (0, 0, width, height), @@ -408,7 +408,7 @@ public void FillRemaining_True_False () Attribute.Default, new (ColorName.Green, ColorName.BrightMagenta), new (ColorName.Blue, ColorName.Cyan) }; - var tf = new TextFormatter { Size = new (14, 3), Text = "Test\nTest long\nTest long long\n", MultiLine = true }; + var tf = new TextFormatter { ConstrainToSize = new (14, 3), Text = "Test\nTest long\nTest long long\n", MultiLine = true }; tf.Draw ( new (1, 1, 19, 3), @@ -1164,7 +1164,7 @@ IEnumerable resultLines { var tf = new TextFormatter { - Text = text, Size = new (maxWidth, maxHeight), WordWrap = false, MultiLine = multiLine + Text = text, ConstrainToSize = new (maxWidth, maxHeight), WordWrap = false, MultiLine = multiLine }; Assert.False (tf.WordWrap); @@ -1247,7 +1247,7 @@ IEnumerable resultLines var tf = new TextFormatter { Text = text, - Size = new (maxWidth, maxHeight), + ConstrainToSize = new (maxWidth, maxHeight), WordWrap = false, MultiLine = multiLine, Direction = TextDirection.TopBottom_LeftRight @@ -1277,7 +1277,7 @@ public void NeedsFormat_Sets () tf.Draw (testBounds, new (), new ()); Assert.False (tf.NeedsFormat); - tf.Size = new (1, 1); + tf.ConstrainToSize = new (1, 1); Assert.True (tf.NeedsFormat); Assert.NotEmpty (tf.GetLines ()); Assert.False (tf.NeedsFormat); // get_Lines causes a Format @@ -2034,8 +2034,8 @@ string expected tf.Direction = textDirection; tf.TabWidth = tabWidth; tf.Text = text; - tf.Width = 20; - tf.Height = 20; + tf.ConstrainToWidth = 20; + tf.ConstrainToHeight = 20; Assert.True (tf.WordWrap); Assert.False (tf.PreserveTrailingSpaces); @@ -2075,8 +2075,8 @@ string expected tf.TabWidth = tabWidth; tf.PreserveTrailingSpaces = true; tf.Text = text; - tf.Width = 20; - tf.Height = 20; + tf.ConstrainToWidth = 20; + tf.ConstrainToHeight = 20; Assert.True (tf.WordWrap); @@ -2115,8 +2115,8 @@ string expected tf.TabWidth = tabWidth; tf.WordWrap = true; tf.Text = text; - tf.Width = 20; - tf.Height = 20; + tf.ConstrainToWidth = 20; + tf.ConstrainToHeight = 20; Assert.False (tf.PreserveTrailingSpaces); @@ -2158,8 +2158,8 @@ public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedTe public void Text_Set_SizeIsCorrect (string text, TextDirection textDirection, int expectedWidth, int expectedHeight) { var tf = new TextFormatter { Direction = textDirection, Text = text }; - tf.Width = 10; - tf.Height = 10; + tf.ConstrainToWidth = 10; + tf.ConstrainToHeight = 10; Assert.Equal (new (expectedWidth, expectedHeight), tf.FormatAndGetSize ()); } @@ -3071,14 +3071,6 @@ public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, I [InlineData ("ABC", 3, "ABC")] [InlineData ("ABC", 4, "ABC")] [InlineData ("ABC", 6, "ABC")] - [InlineData ("A", 0, "")] - [InlineData ("A", 1, "A")] - [InlineData ("A", 2, "A")] - [InlineData ("AB", 1, "A")] - [InlineData ("AB", 2, "AB")] - [InlineData ("ABC", 3, "ABC")] - [InlineData ("ABC", 4, "ABC")] - [InlineData ("ABC", 6, "ABC")] public void Draw_Horizontal_Left (string text, int width, string expectedText) { @@ -3088,8 +3080,8 @@ public void Draw_Horizontal_Left (string text, int width, string expectedText) Alignment = Alignment.Start }; - tf.Width = width; - tf.Height = 1; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = 1; tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3105,14 +3097,6 @@ public void Draw_Horizontal_Left (string text, int width, string expectedText) [InlineData ("ABC", 3, "ABC")] [InlineData ("ABC", 4, " ABC")] [InlineData ("ABC", 6, " ABC")] - [InlineData ("A", 0, "")] - [InlineData ("A", 1, "A")] - [InlineData ("A", 2, " A")] - [InlineData ("AB", 1, "B")] - [InlineData ("AB", 2, "AB")] - [InlineData ("ABC", 3, "ABC")] - [InlineData ("ABC", 4, " ABC")] - [InlineData ("ABC", 6, " ABC")] public void Draw_Horizontal_Right (string text, int width, string expectedText) { TextFormatter tf = new () @@ -3121,8 +3105,8 @@ public void Draw_Horizontal_Right (string text, int width, string expectedText) Alignment = Alignment.End }; - tf.Width = width; - tf.Height = 1; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = 1; tf.Draw (new (Point.Empty, new (width, 1)), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3141,17 +3125,6 @@ public void Draw_Horizontal_Right (string text, int width, string expectedText) [InlineData ("ABC", 5, " ABC")] [InlineData ("ABC", 6, " ABC")] [InlineData ("ABC", 9, " ABC")] - [InlineData ("A", 0, "")] - [InlineData ("A", 1, "A")] - [InlineData ("A", 2, "A")] - [InlineData ("A", 3, " A")] - [InlineData ("AB", 1, "A")] - [InlineData ("AB", 2, "AB")] - [InlineData ("ABC", 3, "ABC")] - [InlineData ("ABC", 4, "ABC")] - [InlineData ("ABC", 5, " ABC")] - [InlineData ("ABC", 6, " ABC")] - [InlineData ("ABC", 9, " ABC")] public void Draw_Horizontal_Centered (string text, int width, string expectedText) { TextFormatter tf = new () @@ -3160,8 +3133,8 @@ public void Draw_Horizontal_Centered (string text, int width, string expectedTex Alignment = Alignment.Center }; - tf.Width = width; - tf.Height = 1; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = 1; tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3188,8 +3161,8 @@ public void Draw_Horizontal_Justified (string text, int width, string expectedTe Alignment = Alignment.Fill }; - tf.Width = width; - tf.Height = 1; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = 1; tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3258,8 +3231,8 @@ public void Draw_Vertical_TopBottom_LeftRight (string text, int width, int heigh Direction = TextDirection.TopBottom_LeftRight }; - tf.Width = width; - tf.Height = height; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; tf.Draw (new (0, 0, 20, 20), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3289,7 +3262,7 @@ public void Justify_Horizontal (string text, int width, int height, string expec { Text = text, Alignment = Alignment.Fill, - Size = new Size (width, height), + ConstrainToSize = new Size (width, height), MultiLine = true }; @@ -3349,7 +3322,7 @@ public void Justify_Vertical (string text, int width, int height, string expecte Text = text, Direction = TextDirection.TopBottom_LeftRight, VerticalAlignment = Alignment.Fill, - Size = new Size (width, height), + ConstrainToSize = new Size (width, height), MultiLine = true }; @@ -3389,8 +3362,8 @@ public void Draw_Vertical_Bottom_Horizontal_Right (string text, int width, int h VerticalAlignment = Alignment.End }; - tf.Width = width; - tf.Height = height; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; tf.Draw (new (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default); Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3449,8 +3422,8 @@ public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, stri Direction = TextDirection.TopBottom_LeftRight }; - tf.Width = 5; - tf.Height = height; + tf.ConstrainToWidth = 5; + tf.ConstrainToHeight = height; tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3498,8 +3471,8 @@ public void Draw_Vertical_TopBottom_LeftRight_Middle (string text, int height, s width++; } - tf.Width = width; - tf.Height = height; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3527,8 +3500,8 @@ public void Draw_Horizontal_RightLeft_TopBottom (string text, int width, int hei Direction = TextDirection.RightLeft_TopBottom }; - tf.Width = width; - tf.Height = height; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3555,8 +3528,8 @@ public void Draw_Horizontal_RightLeft_BottomTop (string text, int width, int hei Direction = TextDirection.RightLeft_BottomTop }; - tf.Width = width; - tf.Height = height; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3583,8 +3556,8 @@ public void Draw_Vertical_BottomTop_LeftRight (string text, int width, int heigh Direction = TextDirection.BottomTop_LeftRight }; - tf.Width = width; - tf.Height = height; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -3611,8 +3584,8 @@ public void Draw_Vertical_BottomTop_RightLeft (string text, int width, int heigh Direction = TextDirection.BottomTop_RightLeft }; - tf.Width = width; - tf.Height = height; + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); @@ -7063,7 +7036,7 @@ public void Draw_Text_Justification (string text, Alignment horizontalTextAlignm Alignment = horizontalTextAlignment, VerticalAlignment = alignment, Direction = textDirection, - Size = new (7, 7), + ConstrainToSize = new (7, 7), Text = text }; @@ -7217,8 +7190,8 @@ string expectedDraw TextFormatter tf = new () { Direction = direction, - Width = width, - Height = height, + ConstrainToWidth = width, + ConstrainToHeight = height, Text = text }; Assert.True (tf.WordWrap); @@ -7301,7 +7274,7 @@ string expectedDraw TextFormatter tf = new () { Direction = direction, - Size = new (width, height), + ConstrainToSize = new (width, height), Text = text, WordWrap = false }; diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index 431b673a5f..6aca5a9746 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -936,8 +936,8 @@ public void HotKey_TextFormatter_Width_Correct (string text, int expected) Height = 1, Width = Auto () }; - Assert.Equal (expected, view.TextFormatter.Width); - Assert.Equal (1, view.TextFormatter.Height); + Assert.Equal (expected, view.TextFormatter.ConstrainToWidth); + Assert.Equal (1, view.TextFormatter.ConstrainToHeight); } [Theory] @@ -952,8 +952,8 @@ public void HotKey_TextFormatter_Height_Correct (string text, int expected) Width = Auto (), Height = 1 }; - Assert.Equal (expected, view.TextFormatter.Width); - Assert.Equal (1, view.TextFormatter.Height); + Assert.Equal (expected, view.TextFormatter.ConstrainToWidth); + Assert.Equal (1, view.TextFormatter.ConstrainToHeight); view = new () { @@ -963,8 +963,8 @@ public void HotKey_TextFormatter_Height_Correct (string text, int expected) Width = 1, Height = Auto () }; - Assert.Equal (1, view.TextFormatter.Width); - Assert.Equal (expected, view.TextFormatter.Height); + Assert.Equal (1, view.TextFormatter.ConstrainToWidth); + Assert.Equal (expected, view.TextFormatter.ConstrainToHeight); } // Test variations of Frame diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index 201336d347..3925cf1139 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -18,7 +18,7 @@ public void TextFormatter_Size_Default (string text, int expectedW, int expected { var view = new View (); view.Text = text; - Assert.Equal (new (expectedW, expectedH), view.TextFormatter.Size); + Assert.Equal (new (expectedW, expectedH), view.TextFormatter.ConstrainToSize); } // TextFormatter.Size should track ContentSize (without DimAuto) @@ -31,7 +31,7 @@ public void TextFormatter_Size_Tracks_ContentSize (string text, int expectedW, i var view = new View (); view.SetContentSize (new (1, 1)); view.Text = text; - Assert.Equal (new (expectedW, expectedH), view.TextFormatter.Size); + Assert.Equal (new (expectedW, expectedH), view.TextFormatter.ConstrainToSize); } [Fact] @@ -276,9 +276,9 @@ public void TextDirection_Toggle () view.Width = 12; view.Height = 1; - view.TextFormatter.Size = new (12, 1); + view.TextFormatter.ConstrainToSize = new (12, 1); win.LayoutSubviews (); - Assert.Equal (new (12, 1), view.TextFormatter.Size); + Assert.Equal (new (12, 1), view.TextFormatter.ConstrainToSize); Assert.Equal (new (0, 0, 12, 1), view.Frame); top.Clear (); view.Draw (); @@ -420,7 +420,7 @@ public void View_IsEmpty_False_Minimum_Width () Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 1, 5), view.Frame); - Assert.Equal (new (1, 5), view.TextFormatter.Size); + Assert.Equal (new (1, 5), view.TextFormatter.ConstrainToSize); Assert.Equal (new () { "Views" }, view.TextFormatter.GetLines ()); Assert.Equal (new (0, 0, 4, 10), win.Frame); Assert.Equal (new (0, 0, 4, 10), Application.Top.Frame); @@ -448,7 +448,7 @@ public void View_IsEmpty_False_Minimum_Width () Application.Refresh (); Assert.Equal (new (0, 0, 1, 5), view.Frame); - Assert.Equal (new (1, 5), view.TextFormatter.Size); + Assert.Equal (new (1, 5), view.TextFormatter.ConstrainToSize); Exception exception = Record.Exception (() => Assert.Single (view.TextFormatter.GetLines ())); Assert.Null (exception); @@ -490,7 +490,7 @@ public void DimAuto_Vertical_TextDirection_Wide_Rune () // Vertical text - 2 wide, 5 down Assert.Equal (new (0, 0, 2, 5), view.Frame); - Assert.Equal (new (2, 5), view.TextFormatter.Size); + Assert.Equal (new (2, 5), view.TextFormatter.ConstrainToSize); Assert.Equal (new () { "界View" }, view.TextFormatter.GetLines ()); view.Draw (); @@ -630,8 +630,8 @@ public void Width_Height_Stay_True_If_TextFormatter_Size_Fit () RunState rs = Application.Begin (top); ((FakeDriver)Application.Driver).SetBufferSize (22, 22); - Assert.Equal (new (text.GetColumns (), 1), horizontalView.TextFormatter.Size); - Assert.Equal (new (2, 8), verticalView.TextFormatter.Size); + Assert.Equal (new (text.GetColumns (), 1), horizontalView.TextFormatter.ConstrainToSize); + Assert.Equal (new (2, 8), verticalView.TextFormatter.ConstrainToSize); //Assert.Equal (new (0, 0, 10, 1), horizontalView.Frame); //Assert.Equal (new (0, 3, 10, 9), verticalView.Frame); @@ -785,10 +785,10 @@ public void View_Draw_Horizontal_Simple_TextAlignments () // frame.Width is width + border wide (20 + 2) and 6 high Size expectedSize = new (width, 1); - Assert.Equal (expectedSize, lblLeft.TextFormatter.Size); - Assert.Equal (expectedSize, lblCenter.TextFormatter.Size); - Assert.Equal (expectedSize, lblRight.TextFormatter.Size); - Assert.Equal (expectedSize, lblJust.TextFormatter.Size); + Assert.Equal (expectedSize, lblLeft.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblCenter.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblRight.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblJust.TextFormatter.ConstrainToSize); Assert.Equal (new (0, 0, width + 2, 6), frame.Frame); @@ -889,18 +889,18 @@ public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) if (autoSize) { - Assert.Equal (new (1, 11), lblLeft.TextFormatter.Size); - Assert.Equal (new (1, 11), lblCenter.TextFormatter.Size); - Assert.Equal (new (1, 11), lblRight.TextFormatter.Size); - Assert.Equal (new (1, 11), lblJust.TextFormatter.Size); + Assert.Equal (new (1, 11), lblLeft.TextFormatter.ConstrainToSize); + Assert.Equal (new (1, 11), lblCenter.TextFormatter.ConstrainToSize); + Assert.Equal (new (1, 11), lblRight.TextFormatter.ConstrainToSize); + Assert.Equal (new (1, 11), lblJust.TextFormatter.ConstrainToSize); Assert.Equal (new (0, 0, 9, height + 2), frame.Frame); } else { - Assert.Equal (new (1, height), lblLeft.TextFormatter.Size); - Assert.Equal (new (1, height), lblCenter.TextFormatter.Size); - Assert.Equal (new (1, height), lblRight.TextFormatter.Size); - Assert.Equal (new (1, height), lblJust.TextFormatter.Size); + Assert.Equal (new (1, height), lblLeft.TextFormatter.ConstrainToSize); + Assert.Equal (new (1, height), lblCenter.TextFormatter.ConstrainToSize); + Assert.Equal (new (1, height), lblRight.TextFormatter.ConstrainToSize); + Assert.Equal (new (1, height), lblJust.TextFormatter.ConstrainToSize); Assert.Equal (new (0, 0, 9, height + 2), frame.Frame); } @@ -974,7 +974,7 @@ public void PreserveTrailingSpaces_Removes_Trailing_Spaces () Assert.Equal ("Hello World ", view.TextFormatter.Text); view.TextFormatter.WordWrap = true; - view.TextFormatter.Size = new (5, 3); + view.TextFormatter.ConstrainToSize = new (5, 3); view.PreserveTrailingSpaces = false; Assert.Equal ($"Hello{Environment.NewLine}World", view.TextFormatter.Format ()); @@ -1101,7 +1101,7 @@ public void TextDirection_Horizontal_Dims_Correct_WidthAbsolute () Assert.Equal (new (0, 0, 10, 1), view.Frame); Assert.Equal (new (0, 0, 10, 1), view.Viewport); - Assert.Equal (new (10, 1), view.TextFormatter.Size); + Assert.Equal (new (10, 1), view.TextFormatter.ConstrainToSize); } [Fact] @@ -1136,7 +1136,7 @@ public void Narrow_Wide_Runes () var horizontalView = new View { Width = 20, Height = 1, Text = text }; // Autosize is off, so we have to explicitly set TextFormatter.Size - horizontalView.TextFormatter.Size = new (20, 1); + horizontalView.TextFormatter.ConstrainToSize = new (20, 1); var verticalView = new View { @@ -1148,7 +1148,7 @@ public void Narrow_Wide_Runes () }; // Autosize is off, so we have to explicitly set TextFormatter.Size - verticalView.TextFormatter.Size = new (1, 20); + verticalView.TextFormatter.ConstrainToSize = new (1, 20); var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill (), Text = "Window" }; frame.Add (horizontalView, verticalView); @@ -1204,7 +1204,7 @@ public void Narrow_Wide_Runes () // Autosize is off, so we have to explicitly set TextFormatter.Size // We know these glpyhs are 2 cols wide, so we need to widen the view verticalView.Width = 2; - verticalView.TextFormatter.Size = new (2, 20); + verticalView.TextFormatter.ConstrainToSize = new (2, 20); Assert.True (verticalView.TextFormatter.NeedsFormat); top.Draw (); diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 3986bf89fe..b344a994ec 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -62,7 +62,7 @@ public void Button_AbsoluteSize_Text (string text, int width, int height, int ex Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.Frame.Size); Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.Viewport.Size); Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.GetContentSize ()); - Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.TextFormatter.Size); + Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.TextFormatter.ConstrainToSize); btn1.Dispose (); } @@ -82,7 +82,7 @@ public void Button_AbsoluteSize_DefaultText (int width, int height, int expected Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.Frame.Size); Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.Viewport.Size); - Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.TextFormatter.Size); + Assert.Equal (new Size (expectedWidth, expectedHeight), btn1.TextFormatter.ConstrainToSize); btn1.Dispose (); } @@ -176,7 +176,7 @@ public void Constructors_Defaults () btn.Dispose (); btn = new () { Text = "_Test", IsDefault = true }; - Assert.Equal (new (10, 1), btn.TextFormatter.Size); + Assert.Equal (new (10, 1), btn.TextFormatter.ConstrainToSize); @@ -199,7 +199,7 @@ public void Constructors_Defaults () // [* Test *] Assert.Equal ('_', btn.HotKeySpecifier.Value); Assert.Equal (10, btn.TextFormatter.Format ().Length); - Assert.Equal (new (10, 1), btn.TextFormatter.Size); + Assert.Equal (new (10, 1), btn.TextFormatter.ConstrainToSize); Assert.Equal (new (10, 1), btn.GetContentSize ()); Assert.Equal (new (0, 0, 10, 1), btn.Viewport); Assert.Equal (new (0, 0, 10, 1), btn.Frame); diff --git a/UnitTests/Views/CheckBoxTests.cs b/UnitTests/Views/CheckBoxTests.cs index c7739272df..1860905d06 100644 --- a/UnitTests/Views/CheckBoxTests.cs +++ b/UnitTests/Views/CheckBoxTests.cs @@ -38,7 +38,7 @@ public void CheckBox_AbsoluteSize_Text (string text, int width, int height, int Assert.Equal (new (expectedWidth, expectedHeight), checkBox.Frame.Size); Assert.Equal (new (expectedWidth, expectedHeight), checkBox.Viewport.Size); - Assert.Equal (new (expectedWidth, expectedHeight), checkBox.TextFormatter.Size); + Assert.Equal (new (expectedWidth, expectedHeight), checkBox.TextFormatter.ConstrainToSize); checkBox.Dispose (); } @@ -62,7 +62,7 @@ public void CheckBox_AbsoluteSize_DefaultText (int width, int height, int expect Assert.Equal (new (expectedWidth, expectedHeight), checkBox.Frame.Size); Assert.Equal (new (expectedWidth, expectedHeight), checkBox.Viewport.Size); - Assert.Equal (new (expectedWidth, expectedHeight), checkBox.TextFormatter.Size); + Assert.Equal (new (expectedWidth, expectedHeight), checkBox.TextFormatter.ConstrainToSize); checkBox.Dispose (); } @@ -258,7 +258,7 @@ public void TextAlignment_Centered () Assert.Equal (Alignment.Center, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (_size25x1, checkBox.TextFormatter.Size); + Assert.Equal (_size25x1, checkBox.TextFormatter.ConstrainToSize); var expected = @$" ┌┤Test Demo 你├──────────────┐ @@ -335,10 +335,10 @@ public void TextAlignment_Justified () checkBox1.State = CheckState.Checked; Assert.Equal (new (1, 1, 25, 1), checkBox1.Frame); - Assert.Equal (_size25x1, checkBox1.TextFormatter.Size); + Assert.Equal (_size25x1, checkBox1.TextFormatter.ConstrainToSize); checkBox2.State = CheckState.Checked; Assert.Equal (new (1, 2, 25, 1), checkBox2.Frame); - Assert.Equal (_size25x1, checkBox2.TextFormatter.Size); + Assert.Equal (_size25x1, checkBox2.TextFormatter.ConstrainToSize); Application.Refresh (); expected = @$" @@ -376,7 +376,7 @@ public void TextAlignment_Left () Assert.Equal (Alignment.Start, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (_size25x1, checkBox.TextFormatter.Size); + Assert.Equal (_size25x1, checkBox.TextFormatter.ConstrainToSize); var expected = @$" ┌┤Test Demo 你├──────────────┐ @@ -427,7 +427,7 @@ public void TextAlignment_Right () Assert.Equal (Alignment.End, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); - Assert.Equal (_size25x1, checkBox.TextFormatter.Size); + Assert.Equal (_size25x1, checkBox.TextFormatter.ConstrainToSize); var expected = @$" ┌┤Test Demo 你├──────────────┐ diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index 9bfce36f79..58a52c8d89 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -184,10 +184,10 @@ public void Label_Draw_Fill_Remaining () var label = new Label { Text = "This label needs to be cleared before rewritten.", Width = tfSize.Width, Height = tfSize.Height }; - var tf1 = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Size = tfSize }; + var tf1 = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, ConstrainToSize = tfSize }; tf1.Text = "This TextFormatter (tf1) without fill will not be cleared on rewritten."; - var tf2 = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Size = tfSize, FillRemaining = true }; + var tf2 = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, ConstrainToSize = tfSize, FillRemaining = true }; tf2.Text = "This TextFormatter (tf2) with fill will be cleared on rewritten."; var top = new Toplevel (); @@ -1201,7 +1201,7 @@ public void Label_IsEmpty_False_Minimum_Height () Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 5, 1), label.Frame); - Assert.Equal (new (5, 1), label.TextFormatter.Size); + Assert.Equal (new (5, 1), label.TextFormatter.ConstrainToSize); Assert.Equal (["Label"], label.TextFormatter.GetLines ()); Assert.Equal (new (0, 0, 10, 4), win.Frame); Assert.Equal (new (0, 0, 10, 4), Application.Top.Frame); @@ -1223,7 +1223,7 @@ public void Label_IsEmpty_False_Minimum_Height () Application.Refresh (); Assert.Equal (new (0, 0, 5, 1), label.Frame); - Assert.Equal (new (5, 1), label.TextFormatter.Size); + Assert.Equal (new (5, 1), label.TextFormatter.ConstrainToSize); Exception exception = Record.Exception (() => Assert.Single (label.TextFormatter.GetLines ())); Assert.Null (exception); @@ -1260,7 +1260,7 @@ public void Label_IsEmpty_False_Never_Return_Null_Lines () Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 5, 1), label.Frame); - Assert.Equal (new (5, 1), label.TextFormatter.Size); + Assert.Equal (new (5, 1), label.TextFormatter.ConstrainToSize); Assert.Equal (["Label"], label.TextFormatter.GetLines ()); Assert.Equal (new (0, 0, 10, 4), win.Frame); Assert.Equal (new (0, 0, 10, 4), Application.Top.Frame); @@ -1282,7 +1282,7 @@ public void Label_IsEmpty_False_Never_Return_Null_Lines () Application.Refresh (); Assert.Equal (new (0, 0, 5, 1), label.Frame); - Assert.Equal (new (5, 1), label.TextFormatter.Size); + Assert.Equal (new (5, 1), label.TextFormatter.ConstrainToSize); Assert.Single (label.TextFormatter.GetLines ()); expected = @" From 71e14c8ad169cc67c0af0b59ad498f4c688ea839 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 11:40:18 -0600 Subject: [PATCH 44/96] API doc improvements --- Terminal.Gui/Text/TextFormatter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 838ad4f2d3..7436823ffc 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -725,6 +725,12 @@ public int? ConstrainToHeight /// formatted. /// /// + /// If both the width and height will be unconstrained and text will be formatted to the size of the text. + /// + /// + /// Setting this property is the same as setting and separately. + /// + /// /// Use to get the size of the formatted text. /// /// When set, is set to . From 4bdaef7ad6831bf6c7e480d639d6137ca05a0c28 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 11:45:55 -0600 Subject: [PATCH 45/96] Nuked TextFormatter.CalcRect and unit tests --- Terminal.Gui/Text/TextFormatter.cs | 142 --------------------------- UnitTests/Text/TextFormatterTests.cs | 95 ------------------ 2 files changed, 237 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 7436823ffc..a46b73df7f 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1981,148 +1981,6 @@ public static int GetMaxColsForWidth (List lines, int width, int tabWidt return lineIdx; } - /// Calculates the rectangle required to hold text, assuming no word wrapping or alignment. - /// - /// This API will return incorrect results if the text includes glyphs whose width is dependent on surrounding - /// glyphs (e.g. Arabic). - /// - /// The x location of the rectangle - /// The y location of the rectangle - /// The text to measure - /// The text direction. - /// The number of columns used for a tab. - /// - [Obsolete ("CalcRect is deprecated, FormatAndGetSize instead.")] - internal static Rectangle CalcRect ( - int x, - int y, - string text, - TextDirection direction = TextDirection.LeftRight_TopBottom, - int tabWidth = 0 - ) - { - if (string.IsNullOrEmpty (text)) - { - return new (new (x, y), System.Drawing.Size.Empty); - } - - int w, h; - - if (IsHorizontalDirection (direction)) - { - var mw = 0; - var ml = 1; - - var cols = 0; - - foreach (Rune rune in text.EnumerateRunes ()) - { - if (rune.Value == '\n') - { - ml++; - - if (cols > mw) - { - mw = cols; - } - - cols = 0; - } - else if (rune.Value != '\r') - { - cols++; - var rw = 0; - - if (rune.Value == '\t') - { - rw += tabWidth - 1; - } - else - { - rw = rune.GetColumns (); - - if (rw > 0) - { - rw--; - } - else if (rw == 0) - { - cols--; - } - } - - cols += rw; - } - } - - if (cols > mw) - { - mw = cols; - } - - w = mw; - h = ml; - } - else - { - int vw = 1, cw = 1; - var vh = 0; - - var rows = 0; - - foreach (Rune rune in text.EnumerateRunes ()) - { - if (rune.Value == '\n') - { - vw++; - - if (rows > vh) - { - vh = rows; - } - - rows = 0; - cw = 1; - } - else if (rune.Value != '\r') - { - rows++; - var rw = 0; - - if (rune.Value == '\t') - { - rw += tabWidth - 1; - rows += rw; - } - else - { - rw = rune.GetColumns (); - - if (rw == 0) - { - rows--; - } - else if (cw < rw) - { - cw = rw; - vw++; - } - } - } - } - - if (rows > vh) - { - vh = rows; - } - - w = vw; - h = vh; - } - - return new (x, y, w, h); - } - /// Finds the HotKey and its location in text. /// The text to look in. /// The HotKey specifier (e.g. '_') to look for. diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index a177b9daec..a83b7205ce 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -39,101 +39,6 @@ public class TextFormatterTests } }; - [Theory] - [InlineData (null)] - [InlineData ("")] - public void CalcRect_Invalid_Returns_Empty (string text) - { - Assert.Equal (Rectangle.Empty, TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new (new (1, 2), Size.Empty), TextFormatter.CalcRect (1, 2, text)); - Assert.Equal (new (new (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text)); - } - - [Theory] - [InlineData ("line1\nline2", 5, 2)] - [InlineData ("\nline2", 5, 2)] - [InlineData ("\n\n", 0, 3)] - [InlineData ("\n\n\n", 0, 4)] - [InlineData ("line1\nline2\nline3long!", 10, 3)] - [InlineData ("line1\nline2\n\n", 5, 4)] - [InlineData ("line1\r\nline2", 5, 2)] - [InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)] - [InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)] - [InlineData (" ~  s  gui.cs   master\n↑10", 27, 2)] - public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines) - { - Assert.Equal (new (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); - string [] lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n"); - int maxWidth = lines.Max (s => s.GetColumns ()); - var lineWider = 0; - - for (var i = 0; i < lines.Length; i++) - { - int w = lines [i].GetColumns (); - - if (w == maxWidth) - { - lineWider = i; - } - } - - Assert.Equal (new (0, 0, maxWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); - - Assert.Equal ( - new ( - 0, - 0, - lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), - expectedLines - ), - TextFormatter.CalcRect (0, 0, text) - ); - } - - [Theory] - [InlineData ("test")] - [InlineData (" ~  s  gui.cs   master ↑10")] - public void CalcRect_SingleLine_Returns_1High (string text) - { - Assert.Equal (new (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new (0, 0, text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text)); - } - - [Theory] - [InlineData (14, 1, TextDirection.LeftRight_TopBottom)] - [InlineData (1, 14, TextDirection.TopBottom_LeftRight)] - public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection) - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (new (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection)); - } - - [Theory] - [InlineData ("test", TextDirection.LeftRight_TopBottom)] - [InlineData (" ~  s  gui.cs   master ↑10", TextDirection.LeftRight_TopBottom)] - [InlineData ("Say Hello view4 你", TextDirection.LeftRight_TopBottom)] - [InlineData ("Say Hello view4 你", TextDirection.RightLeft_TopBottom)] - [InlineData ("Say Hello view4 你", TextDirection.LeftRight_BottomTop)] - [InlineData ("Say Hello view4 你", TextDirection.RightLeft_BottomTop)] - public void CalcRect_Horizontal_Width_Correct (string text, TextDirection textDirection) - { - // The width is the number of columns in the text - Assert.Equal (new (text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text, textDirection).Size); - } - - [Theory] - [InlineData ("test", TextDirection.TopBottom_LeftRight)] - [InlineData (" ~  s  gui.cs   master ↑10", TextDirection.TopBottom_LeftRight)] - [InlineData ("Say Hello view4 你", TextDirection.TopBottom_LeftRight)] - [InlineData ("Say Hello view4 你", TextDirection.TopBottom_RightLeft)] - [InlineData ("Say Hello view4 你", TextDirection.BottomTop_LeftRight)] - [InlineData ("Say Hello view4 你", TextDirection.BottomTop_RightLeft)] - public void CalcRect_Vertical_Height_Correct (string text, TextDirection textDirection) - { - // The height is based both the number of lines and the number of wide chars - Assert.Equal (new (1 + text.GetColumns () - text.Length, text.Length), TextFormatter.CalcRect (0, 0, text, textDirection).Size); - } - [Theory] [InlineData ("")] [InlineData (null)] From 31ef0fc15284f7c81e18280b83b1714f71e60c5a Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 13:06:33 -0600 Subject: [PATCH 46/96] Revamped Messagebox tests --- Terminal.Gui/View/Layout/ViewLayout.cs | 2 + Terminal.Gui/Views/MessageBox.cs | 19 +- UICatalog/Scenarios/Dialogs.cs | 4 +- UICatalog/Scenarios/MessageBoxes.cs | 6 +- UnitTests/Dialogs/MessageBoxTests.cs | 636 ++++--------------------- 5 files changed, 125 insertions(+), 542 deletions(-) diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 0a6c7a7fd5..10bbac7fcd 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -87,6 +87,8 @@ public partial class View return null; } + // BUGBUG: This method interferes with Dialog/MessageBox default min/max size. + /// /// Gets a new location of the that is within the Viewport of the 's /// (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates. diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index a9d0b542ac..03339a9573 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -36,18 +36,18 @@ public static class MessageBox public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; /// - /// Defines the default minimum MessageBox width, as a percentage of the container width. Can be configured via + /// Defines the default minimum MessageBox width, as a percentage of the screen width. Can be configured via /// . /// [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] - public static int DefaultMinimumWidth { get; set; } = 60; + public static int DefaultMinimumWidth { get; set; } = 0; /// - /// Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via + /// Defines the default minimum Dialog height, as a percentage of the screen width. Can be configured via /// . /// [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] - public static int DefaultMinimumHeight { get; set; } = 5; + public static int DefaultMinimumHeight { get; set; } = 0; /// /// The index of the selected button, or -1 if the user pressed to close the MessageBox. This is useful for web /// based console where there is no SynchronizationContext or TaskScheduler. @@ -369,10 +369,17 @@ params string [] buttons ButtonAlignment = Alignment.Center, ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems, BorderStyle = MessageBox.DefaultBorderStyle, - Width = Dim.Auto (DimAutoStyle.Auto, /*minimumContentDim: Dim.Percent (DefaultMinimumWidth), */ maximumContentDim: Dim.Percent (90)), - Height = Dim.Auto (DimAutoStyle.Auto, /*minimumContentDim: Dim.Percent (DefaultMinimumHeight),*/ maximumContentDim: Dim.Percent (90)), }; + d.Width = Dim.Auto (DimAutoStyle.Auto, + minimumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * (DefaultMinimumWidth / 100f) )), + maximumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * 0.9f))); + + d.Height = Dim.Auto (DimAutoStyle.Auto, + minimumContentDim: Dim.Func (() => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * (DefaultMinimumHeight / 100f))), + maximumContentDim: Dim.Func (() => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * 0.9f))); + + if (width != 0) { d.Width = width; diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index 26764b8a88..096cc1ec46 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -83,7 +83,7 @@ public override void Main () { X = Pos.Right (widthEdit) + 2, Y = Pos.Top (widthEdit), - Text = $"If width is 0, the dimension will be {Dialog.DefaultMinimumWidth}%." + Text = $"If width is 0, the dimension will be greater than {Dialog.DefaultMinimumWidth}%." } ); @@ -92,7 +92,7 @@ public override void Main () { X = Pos.Right (heightEdit) + 2, Y = Pos.Top (heightEdit), - Text = $"If height is 0, the dimension will be {Dialog.DefaultMinimumWidth}%." + Text = $"If height is 0, the dimension will be greater {Dialog.DefaultMinimumHeight}%." } ); diff --git a/UICatalog/Scenarios/MessageBoxes.cs b/UICatalog/Scenarios/MessageBoxes.cs index b1e16a97b8..e2e8f941fb 100644 --- a/UICatalog/Scenarios/MessageBoxes.cs +++ b/UICatalog/Scenarios/MessageBoxes.cs @@ -15,7 +15,7 @@ public override void Main () Window app = new () { - Title = GetQuitKeyAndName () + Title = GetQuitKeyAndName (), }; var frame = new FrameView @@ -69,7 +69,7 @@ public override void Main () { X = Pos.Right (widthEdit) + 2, Y = Pos.Top (widthEdit), - Text = $"If width is 0, the dimension will be {MessageBox.DefaultMinimumWidth}%." + Text = $"If width is 0, the dimension will be greater than {MessageBox.DefaultMinimumWidth}%." } ); @@ -78,7 +78,7 @@ public override void Main () { X = Pos.Right (heightEdit) + 2, Y = Pos.Top (heightEdit), - Text = $"If height is 0, the dimension will be {MessageBox.DefaultMinimumWidth}%." + Text = $"If height is 0, the dimension will be greater than {MessageBox.DefaultMinimumHeight}%." } ); diff --git a/UnitTests/Dialogs/MessageBoxTests.cs b/UnitTests/Dialogs/MessageBoxTests.cs index f32f7074ac..8638fae696 100644 --- a/UnitTests/Dialogs/MessageBoxTests.cs +++ b/UnitTests/Dialogs/MessageBoxTests.cs @@ -1,4 +1,5 @@ using System.Text; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Xunit.Abstractions; namespace Terminal.Gui.DialogTests; @@ -120,199 +121,48 @@ public void KeyBindings_Space_Causes_Focused_Button_Click () Assert.Equal (1, result); } - [Fact (Skip = "Pos.Align WIP")] + [Theory] + [InlineData (@"", false, false, 6, 6, 2, 2)] + [InlineData (@"", false, true, 3, 6, 9, 3)] + [InlineData (@"01234\n-----\n01234", false, false, 1, 6, 13, 3)] + [InlineData (@"01234\n-----\n01234", true, false, 1, 5, 13, 4)] + [InlineData (@"0123456789", false, false, 1, 6, 12, 3)] + [InlineData (@"0123456789", false, true, 1, 5, 12, 4)] + [InlineData (@"01234567890123456789", false, true, 1, 5, 13, 4)] + [InlineData (@"01234567890123456789", true, true, 1, 5, 13, 5)] + [InlineData (@"01234567890123456789\n01234567890123456789", false, true, 1, 5, 13, 4)] + [InlineData (@"01234567890123456789\n01234567890123456789", true, true, 1, 4, 13, 7)] [AutoInitShutdown] - public void Location_Default () + public void Location_And_Size_Correct (string message, bool wrapMessage, bool hasButton, int expectedX, int expectedY, int expectedW, int expectedH) { int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query (string.Empty, string.Empty, null); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - Assert.IsType (Application.Current); - // Default location is centered, so - // X = (100 / 2) - (60 / 2) = 20 - // Y = (100 / 2) - (5 / 2) = 47 - Assert.Equal (new Point (20, 47), (Point)Application.Current.Frame.Location); + ((FakeDriver)Application.Driver).SetBufferSize (15, 15); // 15 x 15 gives us enough room for a button with one char (9x1) - Application.RequestStop (); - } - }; - - Application.Run ().Dispose (); - } - - [Theory (Skip = "Pos.Align WIP")] - [AutoInitShutdown] - [InlineData (" ", true, 1)] - [InlineData (" ", false, 1)] - [InlineData ("", true, 1)] - [InlineData ("", false, 1)] - [InlineData ("\n", true, 1)] - [InlineData ("\n", false, 1)] - [InlineData (" \n", true, 1)] - [InlineData (" \n", false, 2)] - public void Message_Empty_Or_A_NewLline_WrapMessagge_True_Or_False ( - string message, - bool wrapMessage, - int linesLength - ) - { - int iterations = -1; + Rectangle mbFrame = Rectangle.Empty; Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query (string.Empty, message, 0, wrapMessage, "ok"); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - if (linesLength == 1) - { - TestHelpers.AssertDriverContentsWithFrameAre ( - @$" - ┌──────────────────────────────────────────────┐ - │ │ - │ │ - │ { - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } ok { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - } │ - └──────────────────────────────────────────────┘", - _output - ); - } - else - { - TestHelpers.AssertDriverContentsWithFrameAre ( - @$" - ┌──────────────────────────────────────────────┐ - │ │ - │ │ - │ │ - │ { - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } ok { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - } │ - └──────────────────────────────────────────────┘", - _output - ); - } - - Application.RequestStop (); - } - }; + { + iterations++; + + if (iterations == 0) + { + MessageBox.Query (string.Empty, message, 0, wrapMessage, hasButton ? ["0"] : []); + Application.RequestStop (); + } + else if (iterations == 1) + { + mbFrame = Application.Current.Frame; + Application.RequestStop (); + } + }; Application.Run ().Dispose (); - } - - [Fact (Skip = "Pos.Align WIP")] - [AutoInitShutdown] - public void Message_Long_Without_Spaces_WrapMessage_True () - { - int iterations = -1; - var top = new Toplevel (); - top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); - - var btn = - $"{ - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } btn { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - }"; - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - // 50 characters should make the height of the wrapped text 7 - MessageBox.Query (string.Empty, new string ('f', 50), 0, true, "btn"); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @$" -┌──────────────────┐ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ ffffffffffffff │ -│ │ -│ {btn} │ -└──────────────────┘", - _output - ); - Application.RequestStop (); - - // Really long text - MessageBox.Query (string.Empty, new string ('f', 500), 0, true, "btn"); - } - else if (iterations == 2) - { - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @$" -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ {btn} │", - _output - ); - Application.RequestStop (); - } - }; - Application.Run (top); + Assert.Equal (new (expectedX, expectedY, expectedW, expectedH), mbFrame); } - - [Fact (Skip = "Pos.Align WIP")] + + [Fact] [AutoInitShutdown] public void Message_With_Spaces_WrapMessage_False () { @@ -322,15 +172,7 @@ public void Message_With_Spaces_WrapMessage_False () ((FakeDriver)Application.Driver).SetBufferSize (20, 10); var btn = - $"{ - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } btn { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - }"; + $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} btn {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; Application.Iteration += (s, a) => { @@ -353,15 +195,12 @@ public void Message_With_Spaces_WrapMessage_False () { Application.Refresh (); - TestHelpers.AssertDriverContentsWithFrameAre ( - @" -──────────────────── -ff ff ff ff ff ff ff - - ⟦► btn ◄⟧ -──────────────────── -", - _output + TestHelpers.AssertDriverContentsWithFrameAre (@" + ╔════════════════╗ + ║ ff ff ff ff ff ║ + ║ ⟦► btn ◄⟧║ + ╚════════════════╝", + _output ); Application.RequestStop (); @@ -374,12 +213,10 @@ ff ff ff ff ff ff ff TestHelpers.AssertDriverContentsWithFrameAre ( @" -──────────────────── -ffffffffffffffffffff - - ⟦► btn ◄⟧ -──────────────────── -", + ╔════════════════╗ + ║ffffffffffffffff║ + ║ ⟦► btn ◄⟧║ + ╚════════════════╝", _output ); Application.RequestStop (); @@ -389,25 +226,17 @@ ff ff ff ff ff ff ff Application.Run (top); } - [Fact (Skip = "Pos.Align WIP")] + [Fact] [AutoInitShutdown] public void Message_With_Spaces_WrapMessage_True () { int iterations = -1; - var top = new Toplevel(); + var top = new Toplevel (); top.BorderStyle = LineStyle.None; ((FakeDriver)Application.Driver).SetBufferSize (20, 10); var btn = - $"{ - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } btn { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - }"; + $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} btn {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; Application.Iteration += (s, a) => { @@ -430,14 +259,14 @@ public void Message_With_Spaces_WrapMessage_True () { Application.Refresh (); - TestHelpers.AssertDriverContentsWithFrameAre (@$" -┌─────────────────┐ -│ff ff ff ff ff ff│ -│ff ff ff ff ff ff│ -│ ff ff ff ff ff │ -│ │ -│ {btn} │ -└─────────────────┘", + TestHelpers.AssertDriverContentsWithFrameAre (@" + ╔══════════════╗ + ║ff ff ff ff ff║ + ║ff ff ff ff ff║ + ║ff ff ff ff ff║ + ║ ff ff ║ + ║ ⟦► btn ◄⟧║ + ╚══════════════╝", _output ); Application.RequestStop (); @@ -450,16 +279,15 @@ public void Message_With_Spaces_WrapMessage_True () Application.Refresh (); TestHelpers.AssertDriverContentsWithFrameAre (@$" -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ffffffffffffffffff│ -│ {btn} │", + ╔════════════════╗ + ║ffffffffffffffff║ + ║ffffffffffffffff║ + ║ffffffffffffffff║ + ║ffffffffffffffff║ + ║ffffffffffffffff║ + ║ffffffffffffffff║ + ║fffffff⟦► btn ◄⟧║ + ╚════════════════╝", _output ); Application.RequestStop (); @@ -469,261 +297,7 @@ public void Message_With_Spaces_WrapMessage_True () Application.Run (top); top.Dispose (); } - - [Fact (Skip = "Pos.Align WIP")] - [AutoInitShutdown] - public void Message_Without_Spaces_WrapMessage_False () - { - int iterations = -1; - var top = new Toplevel(); - top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); - - var btn = - $"{ - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } btn { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - }"; - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query (string.Empty, new string ('f', 50), 0, false, "btn"); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @" -──────────────────── -ffffffffffffffffffff - - ⟦► btn ◄⟧ -──────────────────── -", - _output - ); - - Application.RequestStop (); - - // Really long text - MessageBox.Query (string.Empty, new string ('f', 500), 0, false, "btn"); - } - else if (iterations == 2) - { - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @" -──────────────────── -ffffffffffffffffffff - - ⟦► btn ◄⟧ -──────────────────── -", - _output - ); - - Application.RequestStop (); - } - }; - - Application.Run (top); - } - - [Fact (Skip = "Pos.Align WIP")] - [AutoInitShutdown] - public void Size_Default () - { - int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query (string.Empty, string.Empty, null); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - Assert.IsType (Application.Current); - - // Default size is Percent(60) - Assert.Equal (new ((int)(100 * .60), 5), Application.Current.Frame.Size); - - Application.RequestStop (); - } - }; - - Application.Run ().Dispose (); - } - - [Fact (Skip = "Pos.Align WIP")] - [AutoInitShutdown] - public void Size_JustBigEnough_Fixed_Size () - { - int iterations = -1; - - var btn = - $"{ - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } Ok { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - }"; - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query (11, 5, string.Empty, "Message", "_Ok"); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @$" - ┌─────────┐ - │ Message │ - │ │ - │{ - btn - } │ - └─────────┘ -", - _output - ); - - Application.RequestStop (); - } - }; - - Application.Run ().Dispose (); - } - - [Fact (Skip = "Pos.Align WIP")] - [AutoInitShutdown] - public void Size_No_With_Button () - { - var top = new Toplevel (); - top.BorderStyle = LineStyle.None; - int iterations = -1; - - var aboutMessage = new StringBuilder (); - aboutMessage.AppendLine (@"0123456789012345678901234567890123456789"); - aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); - var message = aboutMessage.ToString (); - - var btn = - $"{ - CM.Glyphs.LeftBracket - }{ - CM.Glyphs.LeftDefaultIndicator - } Ok { - CM.Glyphs.RightDefaultIndicator - }{ - CM.Glyphs.RightBracket - }"; - - ((FakeDriver)Application.Driver).SetBufferSize (40 + 4, 8); - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query (string.Empty, message, "_Ok"); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @$" - ┌────────────────────────────────────────┐ - │0123456789012345678901234567890123456789│ - │ https://github.com/gui-cs/Terminal.Gui │ - │ │ - │ {btn} │ - └────────────────────────────────────────┘ -", - _output - ); - - Application.RequestStop (); - } - }; - - Application.Run (top); - top.Dispose (); - } - - [Fact (Skip = "Pos.Align WIP")] - [AutoInitShutdown] - public void Size_None_No_Buttons () - { - int iterations = -1; - - Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query ("Title", "Message"); - - Application.RequestStop (); - } - else if (iterations == 1) - { - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @" - ┌┤Title├───────────────────────────────────────┐ - │ Message │ - │ │ - │ │ - └──────────────────────────────────────────────┘ -", - _output - ); - - Application.RequestStop (); - } - }; - - Application.Run ().Dispose (); - } - + [Theory] [InlineData (0, 0, "1")] [InlineData (1, 1, "1")] @@ -832,45 +406,45 @@ public void Size_Not_Default_No_Message (int height, int width) } // TODO: Reimplement once messagebox ues Dim.Auto -// [Fact] -// [AutoInitShutdown] -// public void Size_Tiny_Fixed_Size () -// { -// int iterations = -1; - -// Application.Iteration += (s, a) => -// { -// iterations++; - -// if (iterations == 0) -// { -// MessageBox.Query (7, 5, string.Empty, "Message", "_Ok"); - -// Application.RequestStop (); -// } -// else if (iterations == 1) -// { -// Application.Refresh (); - -// Assert.Equal (new (7, 5), Application.Current.Frame.Size); - -// TestHelpers.AssertDriverContentsWithFrameAre ( -// @$" -// ┌─────┐ -// │Messa│ -// │ ge │ -// │ Ok { -// CM.Glyphs.RightDefaultIndicator -// }│ -// └─────┘ -//", -// _output -// ); - -// Application.RequestStop (); -// } -// }; - -// Application.Run ().Dispose (); -// } + // [Fact] + // [AutoInitShutdown] + // public void Size_Tiny_Fixed_Size () + // { + // int iterations = -1; + + // Application.Iteration += (s, a) => + // { + // iterations++; + + // if (iterations == 0) + // { + // MessageBox.Query (7, 5, string.Empty, "Message", "_Ok"); + + // Application.RequestStop (); + // } + // else if (iterations == 1) + // { + // Application.Refresh (); + + // Assert.Equal (new (7, 5), Application.Current.Frame.Size); + + // TestHelpers.AssertDriverContentsWithFrameAre ( + // @$" + // ┌─────┐ + // │Messa│ + // │ ge │ + // │ Ok { + // CM.Glyphs.RightDefaultIndicator + // }│ + // └─────┘ + //", + // _output + // ); + + // Application.RequestStop (); + // } + // }; + + // Application.Run ().Dispose (); + // } } From c7ae9867894174c3faf29787a6ae56e798a433b2 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 13:12:31 -0600 Subject: [PATCH 47/96] Review feedback --- Terminal.Gui/Text/TextFormatter.cs | 3 +- UnitTests/View/Layout/Pos.PercentTests.cs | 2 +- UnitTests/View/TextTests.cs | 67 ++++++++++++++++++++--- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index a46b73df7f..a9d73ebb68 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -579,6 +579,7 @@ public int GetHotKeySpecifierLength (bool isWidth = true) /// public List GetLines () { + string text = _text!; int width = _constrainToWidth.GetValueOrDefault (); int height = _constrainToHeight.GetValueOrDefault (); @@ -596,8 +597,6 @@ public List GetLines () return _lines; } - string text = _text!; - if (FindHotKey (_text!, HotKeySpecifier, out _hotKeyPos, out Key newHotKey)) { HotKey = newHotKey; diff --git a/UnitTests/View/Layout/Pos.PercentTests.cs b/UnitTests/View/Layout/Pos.PercentTests.cs index a325fe514e..2303e64033 100644 --- a/UnitTests/View/Layout/Pos.PercentTests.cs +++ b/UnitTests/View/Layout/Pos.PercentTests.cs @@ -75,7 +75,7 @@ public void PosPercent_SetsValue () } [Fact] - public void PosPercent_ThrowsOnIvalid () + public void PosPercent_ThrowsOnInvalid () { Pos pos = Percent (0); Assert.Throws (() => pos = Percent (-1)); diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index 3925cf1139..9df6e2d514 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -734,10 +734,11 @@ string GetContents () Application.End (rs); top.Dispose (); } - - [Fact] + [Theory] [AutoInitShutdown] - public void View_Draw_Horizontal_Simple_TextAlignments () + [InlineData (true)] + [InlineData (false)] + public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) { var text = "Hello World"; var width = 20; @@ -749,6 +750,12 @@ public void View_Draw_Horizontal_Simple_TextAlignments () Height = 1 }; + if (autoSize) + { + lblLeft.Width = Dim.Auto (); + lblLeft.Height = Dim.Auto (); + } + var lblCenter = new View { Text = text, @@ -758,6 +765,12 @@ public void View_Draw_Horizontal_Simple_TextAlignments () TextAlignment = Alignment.Center }; + if (autoSize) + { + lblCenter.Width = Dim.Auto (); + lblCenter.Height = Dim.Auto (); + } + var lblRight = new View { Text = text, @@ -767,6 +780,12 @@ public void View_Draw_Horizontal_Simple_TextAlignments () TextAlignment = Alignment.End }; + if (autoSize) + { + lblRight.Width = Dim.Auto (); + lblRight.Height = Dim.Auto (); + } + var lblJust = new View { Text = text, @@ -776,6 +795,12 @@ public void View_Draw_Horizontal_Simple_TextAlignments () TextAlignment = Alignment.Fill }; + if (autoSize) + { + lblJust.Width = Dim.Auto (); + lblJust.Height = Dim.Auto (); + } + var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () }; frame.Add (lblLeft, lblCenter, lblRight, lblJust); var top = new Toplevel (); @@ -784,16 +809,41 @@ public void View_Draw_Horizontal_Simple_TextAlignments () ((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6); // frame.Width is width + border wide (20 + 2) and 6 high - Size expectedSize = new (width, 1); - Assert.Equal (expectedSize, lblLeft.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblCenter.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblRight.TextFormatter.ConstrainToSize); - Assert.Equal (expectedSize, lblJust.TextFormatter.ConstrainToSize); + + if (autoSize) + { + Size expectedSize = new (11, 1); + Assert.Equal (expectedSize, lblLeft.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblCenter.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblRight.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblJust.TextFormatter.ConstrainToSize); + } + else + { + Size expectedSize = new (width, 1); + Assert.Equal (expectedSize, lblLeft.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblCenter.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblRight.TextFormatter.ConstrainToSize); + Assert.Equal (expectedSize, lblJust.TextFormatter.ConstrainToSize); + } Assert.Equal (new (0, 0, width + 2, 6), frame.Frame); string expected; + if (autoSize) + { + expected = @" +┌────────────────────┐ +│Hello World │ +│Hello World │ +│Hello World │ +│Hello World │ +└────────────────────┘ +"; + } + else + { expected = @" ┌────────────────────┐ │Hello World │ @@ -802,6 +852,7 @@ public void View_Draw_Horizontal_Simple_TextAlignments () │Hello World│ └────────────────────┘ "; + } Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); Assert.Equal (new (0, 0, width + 2, 6), pos); From 8a8666c7248318a4a9723c09400d3b0d832bf190 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 13:19:49 -0600 Subject: [PATCH 48/96] Removed old comments --- Terminal.Gui/View/Layout/ViewLayout.cs | 3 --- Terminal.Gui/Views/Slider.cs | 5 ----- 2 files changed, 8 deletions(-) diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 10bbac7fcd..3686c660b2 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -733,9 +733,6 @@ private void LayoutSubview (View v, Size contentSize) /// internal virtual void OnLayoutComplete (LayoutEventArgs args) { LayoutComplete?.Invoke (this, args); } - // BUGBUG: We need an API/event that is called from SetRelativeLayout instead of/in addition to - // BUGBUG: OnLayoutStarted which is called from LayoutSubviews. - /// /// Raises the event. Called from before any subviews /// have been laid out. diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 61ac401d6e..a233b18733 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -50,11 +50,6 @@ private void SetInitialProperties ( SetDefaultStyle (); SetCommands (); - SetContentSize (); - - // BUGBUG: This should not be needed - Need to ensure SetRelativeLayout gets called during EndInit - Initialized += (s, e) => { SetContentSize (); }; - LayoutStarted += (s, e) => { SetContentSize (); }; } From 07d7bbb1f42eb27998f563205b2099f4cd53abd3 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 13:23:17 -0600 Subject: [PATCH 49/96] reverted bad change --- Terminal.Gui/Views/Slider.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index a233b18733..61ac401d6e 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -50,6 +50,11 @@ private void SetInitialProperties ( SetDefaultStyle (); SetCommands (); + SetContentSize (); + + // BUGBUG: This should not be needed - Need to ensure SetRelativeLayout gets called during EndInit + Initialized += (s, e) => { SetContentSize (); }; + LayoutStarted += (s, e) => { SetContentSize (); }; } From 6c6244a04d38ec7b4734a4436fabf791bf87040d Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 13:26:45 -0600 Subject: [PATCH 50/96] Updated DatePicker BUGBUG --- Terminal.Gui/Views/DatePicker.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs index dcb154bf57..21f6f5f206 100644 --- a/Terminal.Gui/Views/DatePicker.cs +++ b/Terminal.Gui/Views/DatePicker.cs @@ -276,9 +276,6 @@ private void SetInitialProperties (DateTime date) Width = Dim.Auto (DimAutoStyle.Content); Height = Dim.Auto (DimAutoStyle.Content); - // BUGBUG: Remove when Dim.Auto(subviews) fully works - SetContentSize (new (_calendar.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 7, _calendar.Frame.Height + 1)); - _dateField.DateChanged += DateField_DateChanged; Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton); From ffc4136a425efb5a0705c52d0e56b965f08b392c Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 13:34:35 -0600 Subject: [PATCH 51/96] Fixed ExpanderButton --- UICatalog/Scenarios/AdornmentsEditor.cs | 4 ---- UICatalog/Scenarios/Bars.cs | 6 ------ UICatalog/Scenarios/BorderEditor.cs | 3 +-- UICatalog/Scenarios/ExpanderButton.cs | 7 ++----- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/UICatalog/Scenarios/AdornmentsEditor.cs b/UICatalog/Scenarios/AdornmentsEditor.cs index bfdbec5ebf..b2098e92c0 100644 --- a/UICatalog/Scenarios/AdornmentsEditor.cs +++ b/UICatalog/Scenarios/AdornmentsEditor.cs @@ -124,10 +124,6 @@ private void AdornmentsEditor_Initialized (object sender, EventArgs e) Add (_diagRulerCheckBox); _diagRulerCheckBox.Y = Pos.Bottom (_diagPaddingCheckBox); - - // BUGBUG: This should not be needed. There's some bug in the layout system that doesn't update the layout. - SuperView.LayoutSubviews(); - } private void Application_MouseEvent (object sender, MouseEvent e) diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index c2027d8eca..d494a79c45 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -341,12 +341,6 @@ private void App_Loaded (object sender, EventArgs e) // Application.MouseEvent -= Application_MouseEvent; //} - private void Menu_Initialized (object sender, EventArgs e) - { - // BUGBUG: this should not be needed - - ((View)(sender)).LayoutSubviews (); - } private void ConfigMenuBar (Bar bar) { diff --git a/UICatalog/Scenarios/BorderEditor.cs b/UICatalog/Scenarios/BorderEditor.cs index 5e0a6e77a6..c2bf93570c 100644 --- a/UICatalog/Scenarios/BorderEditor.cs +++ b/UICatalog/Scenarios/BorderEditor.cs @@ -33,9 +33,8 @@ private void BorderEditor_Initialized (object sender, EventArgs e) { X = 0, - // BUGBUG: Hack until dimauto is working properly Y = Pos.Bottom (Subviews [^1]), - Width = Dim.Width (Subviews [^2]) + Dim.Width (Subviews [^1]) - 1, + Width = Dim.Fill (), SelectedItem = (int)(((Border)AdornmentToEdit)?.LineStyle ?? LineStyle.None), BorderStyle = LineStyle.Single, Title = "Border St_yle", diff --git a/UICatalog/Scenarios/ExpanderButton.cs b/UICatalog/Scenarios/ExpanderButton.cs index 2959d338ab..3cbe02d7ff 100644 --- a/UICatalog/Scenarios/ExpanderButton.cs +++ b/UICatalog/Scenarios/ExpanderButton.cs @@ -96,8 +96,6 @@ protected virtual bool OnOrientationChanging (Orientation newOrientation) ExpandedGlyph = new ('\u21d2'); // ⇒ } - Text = $"{(Collapsed ? CollapsedGlyph : ExpandedGlyph)}"; - ExpandOrCollapse (Collapsed); } @@ -157,9 +155,6 @@ protected virtual bool OnCollapsedChanging (bool newValue) subview.Visible = !Collapsed; subview.Enabled = !Collapsed; } - - // BUGBUG: This should not be needed. There's some bug in the layout system that doesn't update the layout. - superView.SuperView?.LayoutSubviews (); } return args.Cancel; @@ -186,6 +181,8 @@ protected virtual bool OnCollapsedChanging (bool newValue) private void ExpandOrCollapse (bool collapse) { + Text = $"{(Collapsed ? CollapsedGlyph : ExpandedGlyph)}"; + View superView = SuperView; if (superView is Adornment adornment) { From c59f59a8ca0f7d0151ef10be01fc2db6769ab12d Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 13:39:54 -0600 Subject: [PATCH 52/96] Identified Dim.Auto bug with AnchorEnd --- UICatalog/Scenarios/MarginEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/MarginEditor.cs b/UICatalog/Scenarios/MarginEditor.cs index 536b81be5e..5705a01f8e 100644 --- a/UICatalog/Scenarios/MarginEditor.cs +++ b/UICatalog/Scenarios/MarginEditor.cs @@ -28,8 +28,8 @@ private void MarginEditor_Initialized (object sender, EventArgs e) _rgShadow = new RadioGroup { X = 0, - //Y = Pos.AnchorEnd(), - // BUGBUG: Hack until dimauto is working properly + //Y = Pos.AnchorEnd (), + // BUGBUG: Hack until Dim.Auto and Pos.AnchorEnd where this view's Dim is also Dim.Auto Y = Pos.Bottom (Subviews [^1]), SuperViewRendersLineCanvas = true, From 149bedf19bec9103cce9f4492b0577e71f07d76c Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 14:22:04 -0600 Subject: [PATCH 53/96] Added more AnchorEnd unit tests. Found bugs with Pos.View and Dim.View. Fixed. --- Terminal.Gui/View/Layout/DimAuto.cs | 32 ++- UICatalog/Scenarios/DimAutoDemo.cs | 10 + UICatalog/Scenarios/MarginEditor.cs | 4 +- .../View/Layout/Dim.AutoTests.PosTypes.cs | 233 ++++++++++++++++++ 4 files changed, 258 insertions(+), 21 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 9a86125424..1ea820ee03 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -180,9 +180,7 @@ internal override int Calculate (int location, int superviewContentSize, View us && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) && !v.X.Has (typeof (PosAnchorEnd), out _) && !v.X.Has (typeof (PosAlign), out _) - && !v.X.Has (typeof (PosView), out _) && !v.X.Has (typeof (PosCenter), out _) - && !v.Width.Has (typeof (DimView), out _) && !v.Width.Has (typeof (DimFill), out _) && !v.Width.Has (typeof (DimPercent), out _) ) @@ -195,9 +193,7 @@ internal override int Calculate (int location, int superviewContentSize, View us && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) && !v.Y.Has (typeof (PosAnchorEnd), out _) && !v.Y.Has (typeof (PosAlign), out _) - && !v.Y.Has (typeof (PosView), out _) && !v.Y.Has (typeof (PosCenter), out _) - && !v.Height.Has (typeof (DimView), out _) && !v.Height.Has (typeof (DimFill), out _) && !v.Height.Has (typeof (DimPercent), out _) ) @@ -218,7 +214,7 @@ internal override int Calculate (int location, int superviewContentSize, View us else { int height = v.Height!.Calculate (0, superviewContentSize, v, dimension); - size = v.Y.GetAnchor (0) + height; + size = v.Y!.GetAnchor (0) + height; } if (size > maxCalculatedSize) @@ -310,13 +306,13 @@ internal override int Calculate (int location, int superviewContentSize, View us 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 - }; + { + Dimension.Width when v.X is PosAlign alignX => alignX.GroupId + == groupId, + Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId + == groupId, + _ => false + }; }) .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) .ToList (); @@ -470,12 +466,12 @@ internal override int Calculate (int location, int superviewContentSize, View us Thickness thickness = us.GetAdornmentsThickness (); int adornmentThickness = dimension switch - { - Dimension.Width => thickness.Horizontal, - Dimension.Height => thickness.Vertical, - Dimension.None => 0, - _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null) - }; + { + Dimension.Width => thickness.Horizontal, + Dimension.Height => thickness.Vertical, + Dimension.None => 0, + _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null) + }; max += adornmentThickness; diff --git a/UICatalog/Scenarios/DimAutoDemo.cs b/UICatalog/Scenarios/DimAutoDemo.cs index 83f749199c..9b8efbc48d 100644 --- a/UICatalog/Scenarios/DimAutoDemo.cs +++ b/UICatalog/Scenarios/DimAutoDemo.cs @@ -163,6 +163,16 @@ private static FrameView CreateDimAutoContentFrameView () }; dimAutoFrameView.Add (resetButton); + + var radioGroup = new RadioGroup () + { + RadioLabels = ["One", "Two", "Three"], + X = 0, + Y = Pos.AnchorEnd (), + Title = "Radios", + BorderStyle = LineStyle.Dotted + }; + dimAutoFrameView.Add (radioGroup); return dimAutoFrameView; } diff --git a/UICatalog/Scenarios/MarginEditor.cs b/UICatalog/Scenarios/MarginEditor.cs index 5705a01f8e..d764eff1d1 100644 --- a/UICatalog/Scenarios/MarginEditor.cs +++ b/UICatalog/Scenarios/MarginEditor.cs @@ -28,9 +28,7 @@ private void MarginEditor_Initialized (object sender, EventArgs e) _rgShadow = new RadioGroup { X = 0, - //Y = Pos.AnchorEnd (), - // BUGBUG: Hack until Dim.Auto and Pos.AnchorEnd where this view's Dim is also Dim.Auto - Y = Pos.Bottom (Subviews [^1]), + Y = Pos.AnchorEnd (), SuperViewRendersLineCanvas = true, Title = "_Shadow", diff --git a/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs index c7f4bc9804..0e7da85599 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.PosTypes.cs @@ -303,6 +303,239 @@ public void With_Subview_Using_PosAnchorEnd (int minWidth, int maxWidth, int min Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); } + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [InlineData (0, 20, 0, 10, 20, 10)] + [InlineData (0, 21, 0, 11, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] + [InlineData (0, 30, 0, 20, 25, 15)] + public void With_Subview_And_Subview_Using_PosAnchorEnd (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 otherView = new View + { + Width = 5, + Height = 5 + }; + view.Add (otherView); + + var subview = new View + { + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be at the end of the view + Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); + Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); + } + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [InlineData (0, 20, 0, 10, 20, 10)] + [InlineData (0, 21, 0, 11, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] + [InlineData (0, 30, 0, 20, 25, 15)] + public void With_DimAutoSubview_And_Subview_Using_PosAnchorEnd (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 otherView = new View + { + Text = "01234\n01234\n01234\n01234\n01234", + Width = Dim.Auto(), + Height = Dim.Auto () + }; + view.Add (otherView); + + var subview = new View + { + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be at the end of the view + Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); + Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); + } + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [InlineData (0, 20, 0, 10, 20, 10)] + [InlineData (0, 21, 0, 11, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] + [InlineData (0, 30, 0, 20, 26, 16)] + public void With_PosViewSubview_And_Subview_Using_PosAnchorEnd (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 otherView = new View + { + Width = 1, + Height = 1, + }; + view.Add (otherView); + + var posViewView = new View + { + X = Pos.Bottom(otherView), + Y = Pos.Right(otherView), + Width = 5, + Height = 5, + }; + view.Add (posViewView); + + var subview = new View + { + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be at the end of the view + Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); + Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); + } + + + [Theory] + [InlineData (0, 0, 0, 0, 0, 0)] + [InlineData (0, 19, 0, 9, 19, 9)] + [InlineData (0, 18, 0, 8, 18, 8)] + [InlineData (0, 20, 0, 10, 20, 10)] + [InlineData (0, 21, 0, 11, 21, 11)] + [InlineData (1, 21, 1, 11, 21, 11)] + [InlineData (21, 21, 11, 11, 21, 11)] + [InlineData (0, 30, 0, 20, 22, 12)] + public void With_DimViewSubview_And_Subview_Using_PosAnchorEnd (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 otherView = new View + { + Width = 1, + Height = 1, + }; + view.Add (otherView); + + var dimViewView = new View + { + Id = "dimViewView", + X = 1, + Y = 1, + Width = Dim.Width (otherView), + Height = Dim.Height (otherView), + }; + view.Add (dimViewView); + + var subview = new View + { + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + 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); + + view.BeginInit (); + view.EndInit (); + + // subview should be at the end of the view + Assert.Equal (view.Viewport.Width - subview.Frame.Width, subview.Frame.X); + Assert.Equal (view.Viewport.Height - subview.Frame.Height, subview.Frame.Y); + } [Theory] [InlineData (0, 10, 0, 10, 10, 2)] [InlineData (0, 5, 0, 5, 5, 3)] // max width of 5 should cause wordwrap at 5 giving a height of 2 + 1 From bf852fff7471f8e0fbb4cdd96aaf235e38e5e537 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 14:23:36 -0600 Subject: [PATCH 54/96] Added BUGBUG --- Terminal.Gui/View/Layout/DimAuto.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 1ea820ee03..6bd5f4d0f6 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -177,7 +177,7 @@ internal override int Calculate (int location, int superviewContentSize, View us { notDependentSubViews = includedSubviews.Where ( v => v.Width is { } - && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) + && (v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.X.Has and v.Width.Has? && !v.X.Has (typeof (PosAnchorEnd), out _) && !v.X.Has (typeof (PosAlign), out _) && !v.X.Has (typeof (PosCenter), out _) @@ -190,7 +190,7 @@ internal override int Calculate (int location, int superviewContentSize, View us { notDependentSubViews = includedSubviews.Where ( v => v.Height is { } - && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) + && (v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) // BUGBUG: We should use v.Y.Has and v.Height.Has? && !v.Y.Has (typeof (PosAnchorEnd), out _) && !v.Y.Has (typeof (PosAlign), out _) && !v.Y.Has (typeof (PosCenter), out _) From 87c5a43b4ab0b0297f4315d9786a9a5821ab8d3b Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 14:30:19 -0600 Subject: [PATCH 55/96] Undid previous change that broke StatusBar --- Terminal.Gui/Views/Bar.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index d9c7946912..720baab734 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -153,8 +153,9 @@ internal override void OnLayoutStarted (LayoutEventArgs args) barItem.ColorScheme = ColorScheme; barItem.X = Pos.Align (Alignment.Start, AlignmentModes); barItem.Y = 0; //Pos.Center (); + // HACK: This should not be needed + barItem.SetRelativeLayout (GetContentSize ()); } - break; case Orientation.Vertical: @@ -187,6 +188,8 @@ internal override void OnLayoutStarted (LayoutEventArgs args) if (barItem is Shortcut scBarItem) { scBarItem.MinimumKeyTextSize = minKeyWidth; + // HACK: This should not be needed + scBarItem.SetRelativeLayout (GetContentSize ()); maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width); } From 6f9fb1a68d7fbae612fd1d9da7de0203fb247a21 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 15:16:56 -0600 Subject: [PATCH 56/96] Trying to debug @bdisp's About box issue --- Terminal.Gui/Text/TextFormatter.cs | 7 ++++--- UICatalog/UICatalog.cs | 20 +++++++++---------- UnitTests/Text/TextFormatterTests.cs | 29 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index a9d73ebb68..d29b855b20 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -580,11 +580,9 @@ public int GetHotKeySpecifierLength (bool isWidth = true) public List GetLines () { string text = _text!; - int width = _constrainToWidth.GetValueOrDefault (); - int height = _constrainToHeight.GetValueOrDefault (); // With this check, we protect against subclasses with overrides of Text - if (string.IsNullOrEmpty (Text) || width == 0 || height == 0) + if (string.IsNullOrEmpty (Text) || ConstrainToWidth is 0 || ConstrainToHeight is 0) { _lines = [string.Empty]; NeedsFormat = false; @@ -597,6 +595,9 @@ public List GetLines () return _lines; } + int width = ConstrainToWidth ?? int.MaxValue; + int height = ConstrainToHeight ?? int.MaxValue; + if (FindHotKey (_text!, HotKeySpecifier, out _hotKeyPos, out Key newHotKey)) { HotKey = newHotKey; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index b063e15bc4..0cf9a5c903 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -382,17 +382,15 @@ public UICatalogTopLevel () _themeMenuBarItem = new ("_Themes", _themeMenuItems); _aboutMessage = new (); - _aboutMessage.AppendLine (@"UI Catalog: A comprehensive sample library for"); - _aboutMessage.AppendLine ( - """ - _______ _ _ _____ _ - |__ __| (_) | | / ____| (_) - | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ - | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | - | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | - |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| - """); - _aboutMessage.AppendLine (@""); + _aboutMessage.AppendLine ($"UI Catalog: A comprehensive sample library for"); + _aboutMessage.AppendLine (@" + _______ _ _ _____ _ +|__ __| (_) | | / ____| (_) + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ + | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | + | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | + |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| +"); _aboutMessage.AppendLine (@"v2 - Pre-Alpha"); _aboutMessage.AppendLine (@""); _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index a83b7205ce..0ec438820b 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -7193,4 +7193,33 @@ string expectedDraw } #endregion + + [Fact] + public void UICatalog_AboutBox_Text () + { + StringBuilder _aboutMessage = new (); + _aboutMessage.AppendLine ($"UI Catalog: A comprehensive sample library for"); + _aboutMessage.AppendLine (@" + _______ _ _ _____ _ +|__ __| (_) | | / ____| (_) + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ + | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | + | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | + |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| +"); + _aboutMessage.AppendLine (@"v2 - Pre-Alpha"); + _aboutMessage.AppendLine (@""); + _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); + + TextFormatter tf = new () + { + Text = _aboutMessage.ToString(), + WordWrap = false, + MultiLine = true, + HotKeySpecifier = (Rune)0xFFFF + }; + + Size tfSize = tf.FormatAndGetSize (); + Assert.Equal (new Size (58, 13), tfSize); + } } From 6da8dab64007376268ae3ac05ca52ececc990067 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 16:33:23 -0600 Subject: [PATCH 57/96] Trying to figure out aboutbox issue --- UICatalog/UICatalog.cs | 71 ++++++++++++++++------------ UnitTests/TestHelpers.cs | 7 +++ UnitTests/Text/TextFormatterTests.cs | 43 +++++++++++------ 3 files changed, 75 insertions(+), 46 deletions(-) diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 0cf9a5c903..495c7b93dc 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -45,9 +45,8 @@ namespace UICatalog; /// (https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog/README.md). /// /// -internal class UICatalogApp +public class UICatalogApp { - private static StringBuilder? _aboutMessage; private static int _cachedCategoryIndex; // When a scenario is run, the main app is killed. These items @@ -74,6 +73,30 @@ internal class UICatalogApp [JsonPropertyName ("UICatalog.StatusBar")] public static bool ShowStatusBar { get; set; } = true; + /// + /// Gets the message displayed in the About Box. `public` so it can be used from Unit tests. + /// + /// + public static string GetAboutBoxMessage () + { + StringBuilder msg = new (); + msg.AppendLine ($"UI Catalog: A comprehensive sample library for"); + msg.AppendLine (); + msg.AppendLine ( +@" _______ _ _ _____ _ +|__ __| (_) | | / ____| (_) + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ + | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | + | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | + |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|"); + msg.AppendLine (); + msg.AppendLine (@"v2 - Pre-Alpha"); + msg.AppendLine (); + msg.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); + + return msg.ToString (); + } + private static void ConfigFileChanged (object sender, FileSystemEventArgs e) { if (Application.Top == null) @@ -381,20 +404,6 @@ public UICatalogTopLevel () _themeMenuItems = CreateThemeMenuItems (); _themeMenuBarItem = new ("_Themes", _themeMenuItems); - _aboutMessage = new (); - _aboutMessage.AppendLine ($"UI Catalog: A comprehensive sample library for"); - _aboutMessage.AppendLine (@" - _______ _ _ _____ _ -|__ __| (_) | | / ____| (_) - | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ - | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | - | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | - |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| -"); - _aboutMessage.AppendLine (@"v2 - Pre-Alpha"); - _aboutMessage.AppendLine (@""); - _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); - MenuBar = new () { Menus = @@ -437,7 +446,7 @@ _______ _ _ _____ _ "About UI Catalog", () => MessageBox.Query ( title: "", - message: _aboutMessage!.ToString (), + message: GetAboutBoxMessage (), wrapMessage: false, buttons: "_Ok" ), @@ -471,7 +480,7 @@ _______ _ _ _____ _ }; statusBarShortcut.Accept += (sender, args) => { StatusBar.Visible = !StatusBar.Visible; }; - ShForce16Colors = new() + ShForce16Colors = new () { CommandView = new CheckBox { @@ -846,24 +855,24 @@ private MenuItem [] CreateDiagnosticFlagsMenuItems () string GetDiagnosticsTitle (Enum diag) { return Enum.GetName (_diagnosticFlags.GetType (), diag) switch - { - "Off" => OFF, - "Ruler" => RULER, - "Padding" => PADDING, - "MouseEnter" => MOUSEENTER, - _ => "" - }; + { + "Off" => OFF, + "Ruler" => RULER, + "Padding" => PADDING, + "MouseEnter" => MOUSEENTER, + _ => "" + }; } Enum GetDiagnosticsEnumValue (string title) { return title switch - { - RULER => ViewDiagnosticFlags.Ruler, - PADDING => ViewDiagnosticFlags.Padding, - MOUSEENTER => ViewDiagnosticFlags.MouseEnter, - _ => null! - }; + { + RULER => ViewDiagnosticFlags.Ruler, + PADDING => ViewDiagnosticFlags.Padding, + MOUSEENTER => ViewDiagnosticFlags.MouseEnter, + _ => null! + }; } void SetDiagnosticsFlag (Enum diag, bool add) diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 899a359864..4604b4eca6 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -448,6 +448,13 @@ public static Rectangle AssertDriverContentsWithFrameAre ( expectedLook = expectedLook [..^Environment.NewLine.Length]; } + // If test is about to fail show user what things looked like + if (!string.Equals (expectedLook, actualLook)) + { + output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook); + output?.WriteLine (" But Was:" + Environment.NewLine + actualLook); + } + Assert.Equal (expectedLook, actualLook); return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 0ec438820b..3e55df4a75 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -7195,25 +7195,14 @@ string expectedDraw #endregion [Fact] + [SetupFakeDriver] public void UICatalog_AboutBox_Text () { - StringBuilder _aboutMessage = new (); - _aboutMessage.AppendLine ($"UI Catalog: A comprehensive sample library for"); - _aboutMessage.AppendLine (@" - _______ _ _ _____ _ -|__ __| (_) | | / ____| (_) - | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ - | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | - | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | - |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| -"); - _aboutMessage.AppendLine (@"v2 - Pre-Alpha"); - _aboutMessage.AppendLine (@""); - _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); - TextFormatter tf = new () { - Text = _aboutMessage.ToString(), + Text = UICatalog.UICatalogApp.GetAboutBoxMessage (), + Alignment = Alignment.Center, + VerticalAlignment = Alignment.Start, WordWrap = false, MultiLine = true, HotKeySpecifier = (Rune)0xFFFF @@ -7221,5 +7210,29 @@ _______ _ _ _____ _ Size tfSize = tf.FormatAndGetSize (); Assert.Equal (new Size (58, 13), tfSize); + + ((FakeDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height); + + Application.Driver.FillRect (Application.Screen, (Rune)'*'); + tf.Draw (Application.Screen, Attribute.Default, Attribute.Default); + + string expectedText = """ + ******UI Catalog: A comprehensive sample library for****** + ********************************************************** + _______ _ _ _____ _* + |__ __| (_) | | / ____| (_) + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _* + | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | + | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | + |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| + ********************************************************** + **********************v2 - Pre-Alpha********************** + ********************************************************** + **********https://github.com/gui-cs/Terminal.Gui********** + ********************************************************** + """; + + TestHelpers.AssertDriverContentsAre (expectedText, _output); + } } From 00731564157bc83c89dd21dc5815d1f1af86b0e9 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 16:43:47 -0600 Subject: [PATCH 58/96] added message box unit test for UI Catalog about box --- UnitTests/Dialogs/MessageBoxTests.cs | 143 +++++++++++++++------------ 1 file changed, 80 insertions(+), 63 deletions(-) diff --git a/UnitTests/Dialogs/MessageBoxTests.cs b/UnitTests/Dialogs/MessageBoxTests.cs index 8638fae696..986894efa0 100644 --- a/UnitTests/Dialogs/MessageBoxTests.cs +++ b/UnitTests/Dialogs/MessageBoxTests.cs @@ -137,31 +137,31 @@ public void Location_And_Size_Correct (string message, bool wrapMessage, bool ha { int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (15, 15); // 15 x 15 gives us enough room for a button with one char (9x1) + ((FakeDriver)Application.Driver).SetBufferSize (15, 15); // 15 x 15 gives us enough room for a button with one char (9x1) Rectangle mbFrame = Rectangle.Empty; Application.Iteration += (s, a) => - { - iterations++; - - if (iterations == 0) - { - MessageBox.Query (string.Empty, message, 0, wrapMessage, hasButton ? ["0"] : []); - Application.RequestStop (); - } - else if (iterations == 1) - { - mbFrame = Application.Current.Frame; - Application.RequestStop (); - } - }; + { + iterations++; + + if (iterations == 0) + { + MessageBox.Query (string.Empty, message, 0, wrapMessage, hasButton ? ["0"] : []); + Application.RequestStop (); + } + else if (iterations == 1) + { + mbFrame = Application.Current.Frame; + Application.RequestStop (); + } + }; Application.Run ().Dispose (); Assert.Equal (new (expectedX, expectedY, expectedW, expectedH), mbFrame); } - + [Fact] [AutoInitShutdown] public void Message_With_Spaces_WrapMessage_False () @@ -195,12 +195,13 @@ public void Message_With_Spaces_WrapMessage_False () { Application.Refresh (); - TestHelpers.AssertDriverContentsWithFrameAre (@" + TestHelpers.AssertDriverContentsWithFrameAre ( + @" ╔════════════════╗ ║ ff ff ff ff ff ║ ║ ⟦► btn ◄⟧║ ╚════════════════╝", - _output + _output ); Application.RequestStop (); @@ -259,7 +260,8 @@ public void Message_With_Spaces_WrapMessage_True () { Application.Refresh (); - TestHelpers.AssertDriverContentsWithFrameAre (@" + TestHelpers.AssertDriverContentsWithFrameAre ( + @" ╔══════════════╗ ║ff ff ff ff ff║ ║ff ff ff ff ff║ @@ -278,7 +280,8 @@ public void Message_With_Spaces_WrapMessage_True () { Application.Refresh (); - TestHelpers.AssertDriverContentsWithFrameAre (@$" + TestHelpers.AssertDriverContentsWithFrameAre ( + @$" ╔════════════════╗ ║ffffffffffffffff║ ║ffffffffffffffff║ @@ -297,7 +300,7 @@ public void Message_With_Spaces_WrapMessage_True () Application.Run (top); top.Dispose (); } - + [Theory] [InlineData (0, 0, "1")] [InlineData (1, 1, "1")] @@ -405,46 +408,60 @@ public void Size_Not_Default_No_Message (int height, int width) }; } - // TODO: Reimplement once messagebox ues Dim.Auto - // [Fact] - // [AutoInitShutdown] - // public void Size_Tiny_Fixed_Size () - // { - // int iterations = -1; - - // Application.Iteration += (s, a) => - // { - // iterations++; - - // if (iterations == 0) - // { - // MessageBox.Query (7, 5, string.Empty, "Message", "_Ok"); - - // Application.RequestStop (); - // } - // else if (iterations == 1) - // { - // Application.Refresh (); - - // Assert.Equal (new (7, 5), Application.Current.Frame.Size); - - // TestHelpers.AssertDriverContentsWithFrameAre ( - // @$" - // ┌─────┐ - // │Messa│ - // │ ge │ - // │ Ok { - // CM.Glyphs.RightDefaultIndicator - // }│ - // └─────┘ - //", - // _output - // ); - - // Application.RequestStop (); - // } - // }; - - // Application.Run ().Dispose (); - // } + [Fact] + [AutoInitShutdown] + public void UICatalog_AboutBox () + { + int iterations = -1; + ((FakeDriver)Application.Driver).SetBufferSize (70, 15); + + Application.Iteration += (s, a) => + { + iterations++; + + if (iterations == 0) + { + MessageBox.Query ( + title: "", + message: UICatalog.UICatalogApp.GetAboutBoxMessage (), + wrapMessage: false, + buttons: "_Ok" + ); + + Application.RequestStop (); + } + else if (iterations == 1) + { + Application.Refresh (); + + string expectedText = """ + ┌────────────────────────────────────────────────────────────────────┐ + │ ╔══════════════════════════════════════════════════════════╗ │ + │ ║ UI Catalog: A comprehensive sample library for ║ │ + │ ║ ║ │ + │ ║ _______ _ _ _____ _ ║ │ + │ ║|__ __| (_) | | / ____| (_)║ │ + │ ║ | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ ║ │ + │ ║ | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |║ │ + │ ║ | | __/ | | | | | | | | | | | (_| | || |__| | |_| | |║ │ + │ ║ |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|║ │ + │ ║ ║ │ + │ ║ v2 - Pre-Alpha ║ │ + │ ║ ⟦► Ok ◄⟧║ │ + │ ╚══════════════════════════════════════════════════════════╝ │ + └────────────────────────────────────────────────────────────────────┘ + """; + + TestHelpers.AssertDriverContentsAre (expectedText, _output); + + Application.RequestStop (); + } + }; + + var top = new Toplevel (); + top.BorderStyle = LineStyle.Single; + Application.Run (top); + + } } + From d003d00ae059059a7de77029c6ebd3292c5f2cb9 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 17:48:38 -0600 Subject: [PATCH 59/96] Removed verbatim strings --- UICatalog/UICatalog.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 495c7b93dc..5433cad302 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -79,16 +79,17 @@ public class UICatalogApp /// public static string GetAboutBoxMessage () { + // NOTE: Do not use verbatim string here. + // WSL gets all confused. StringBuilder msg = new (); - msg.AppendLine ($"UI Catalog: A comprehensive sample library for"); + msg.AppendLine ("UI Catalog: A comprehensive sample library for"); msg.AppendLine (); - msg.AppendLine ( -@" _______ _ _ _____ _ -|__ __| (_) | | / ____| (_) - | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ - | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | - | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | - |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|"); + msg.AppendLine (@" _______ _ _ _____ _"); + msg.AppendLine (@"|__ __| (_) | | / ____| (_)"); + msg.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _"); + msg.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |"); + msg.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | |"); + msg.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|"); msg.AppendLine (); msg.AppendLine (@"v2 - Pre-Alpha"); msg.AppendLine (); From b2cd779c3cd055110b1c9a56fa1428215be5e113 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 20 Jul 2024 18:09:25 -0600 Subject: [PATCH 60/96] Use .ReplaceLineEndings() in TextFormatter --- Terminal.Gui/Text/TextFormatter.cs | 2 +- UICatalog/UICatalog.cs | 21 ++++++++++++--------- UnitTests/Text/TextFormatterTests.cs | 6 +++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index d29b855b20..5748ae0fbb 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -579,7 +579,7 @@ public int GetHotKeySpecifierLength (bool isWidth = true) /// public List GetLines () { - string text = _text!; + string text = _text!.ReplaceLineEndings (); // With this check, we protect against subclasses with overrides of Text if (string.IsNullOrEmpty (Text) || ConstrainToWidth is 0 || ConstrainToHeight is 0) diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 5433cad302..1cfeba1fbc 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -79,21 +79,24 @@ public class UICatalogApp /// public static string GetAboutBoxMessage () { - // NOTE: Do not use verbatim string here. + // NOTE: Do not use multiline verbatim strings here. // WSL gets all confused. StringBuilder msg = new (); msg.AppendLine ("UI Catalog: A comprehensive sample library for"); msg.AppendLine (); - msg.AppendLine (@" _______ _ _ _____ _"); - msg.AppendLine (@"|__ __| (_) | | / ____| (_)"); - msg.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _"); - msg.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |"); - msg.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | |"); - msg.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|"); + + msg.AppendLine (""" + _______ _ _ _____ _ + |__ __| (_) | | / ____| (_) + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ + | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | + | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | + |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| + """); msg.AppendLine (); - msg.AppendLine (@"v2 - Pre-Alpha"); + msg.AppendLine ("v2 - Pre-Alpha"); msg.AppendLine (); - msg.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); + msg.AppendLine ("https://github.com/gui-cs/Terminal.Gui"); return msg.ToString (); } diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 3e55df4a75..c3073ba6e2 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -7219,9 +7219,9 @@ public void UICatalog_AboutBox_Text () string expectedText = """ ******UI Catalog: A comprehensive sample library for****** ********************************************************** - _______ _ _ _____ _* + _______ _ _ _____ _ |__ __| (_) | | / ____| (_) - | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _* + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| @@ -7232,7 +7232,7 @@ _______ _ _ _____ _* ********************************************************** """; - TestHelpers.AssertDriverContentsAre (expectedText, _output); + TestHelpers.AssertDriverContentsAre (expectedText.ReplaceLineEndings (), _output); } } From 8ca972bc74c43e0f97a97ffe67fd0190dc85bf4d Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 21 Jul 2024 16:18:18 -0600 Subject: [PATCH 61/96] Learning how to use [CombinatorialData] --- UnitTests/View/Layout/Dim.AutoTests.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index 6aca5a9746..bd0e2eaf7c 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -926,9 +926,8 @@ public void TextFormatter_Settings_Change_View_Size () [Theory] - [InlineData ("1234", 4)] - [InlineData ("_1234", 4)] - public void HotKey_TextFormatter_Width_Correct (string text, int expected) + [CombinatorialData] + public void HotKey_TextFormatter_Width_Correct ([CombinatorialValues ("1234", "_1234", "1_234", "____")] string text) { View view = new () { @@ -936,14 +935,13 @@ public void HotKey_TextFormatter_Width_Correct (string text, int expected) Height = 1, Width = Auto () }; - Assert.Equal (expected, view.TextFormatter.ConstrainToWidth); + Assert.Equal (4, view.TextFormatter.ConstrainToWidth); Assert.Equal (1, view.TextFormatter.ConstrainToHeight); } [Theory] - [InlineData ("1234", 4)] - [InlineData ("_1234", 4)] - public void HotKey_TextFormatter_Height_Correct (string text, int expected) + [CombinatorialData] + public void HotKey_TextFormatter_Height_Correct ([CombinatorialValues ("1234", "_1234", "1_234", "____")] string text) { View view = new () { @@ -952,7 +950,7 @@ public void HotKey_TextFormatter_Height_Correct (string text, int expected) Width = Auto (), Height = 1 }; - Assert.Equal (expected, view.TextFormatter.ConstrainToWidth); + Assert.Equal (4, view.TextFormatter.ConstrainToWidth); Assert.Equal (1, view.TextFormatter.ConstrainToHeight); view = new () @@ -964,7 +962,7 @@ public void HotKey_TextFormatter_Height_Correct (string text, int expected) Height = Auto () }; Assert.Equal (1, view.TextFormatter.ConstrainToWidth); - Assert.Equal (expected, view.TextFormatter.ConstrainToHeight); + Assert.Equal (4, view.TextFormatter.ConstrainToHeight); } // Test variations of Frame From f0d4c48b50bb8fbd7dc44454b4b5aa96e9fb6055 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 21 Jul 2024 16:42:11 -0600 Subject: [PATCH 62/96] Learning how to use [CombinatorialData] moar --- UnitTests/View/Layout/ViewportTests.cs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/UnitTests/View/Layout/ViewportTests.cs b/UnitTests/View/Layout/ViewportTests.cs index f5bc9212d7..5c0aaa6c7e 100644 --- a/UnitTests/View/Layout/ViewportTests.cs +++ b/UnitTests/View/Layout/ViewportTests.cs @@ -12,11 +12,8 @@ public class ViewportTests (ITestOutputHelper output) private readonly ITestOutputHelper _output = output; [Theory] - [InlineData (0, 10)] - [InlineData (1, 10)] - [InlineData (-1, 10)] - [InlineData (11, 10)] - public void Get_Viewport_NoSuperView_WithoutAdornments (int x, int expectedW) + [CombinatorialData] + public void Get_Viewport_NoSuperView_WithoutAdornments ([CombinatorialRange (-11, 11)] int x) { // We test with only X because Y is equivalent. Height/Width are irrelevant. // Arrange @@ -31,7 +28,7 @@ public void Get_Viewport_NoSuperView_WithoutAdornments (int x, int expectedW) var bounds = view.Viewport; // Assert - Assert.Equal (expectedW, bounds.Width); + Assert.Equal (10, bounds.Width); } [Theory] @@ -250,11 +247,8 @@ public void Set_Viewport_ValidValue_UpdatesViewport (int viewWidth, int viewHeig } [Theory] - [InlineData (0, 0, 10, 10, 10, 10)] - [InlineData (10, 0, 10, 10, 10, 10)] - [InlineData (0, 10, 10, 10, 10, 10)] - [InlineData (10, 10, 10, 10, 10, 10)] - public void Set_Viewport_ValidValue_UpdatesViewport_AllowLocationGreaterThanContentSize (int viewWidth, int viewHeight, int viewportX, int viewportY, int expectedX, int expectedY) + [CombinatorialData] + public void Set_Viewport_ValidValue_UpdatesViewport_AllowLocationGreaterThanContentSize ([CombinatorialRange (0, 5)] int viewWidth, [CombinatorialRange (0, 5)] int viewHeight) { // Arrange var view = new View () @@ -263,13 +257,13 @@ public void Set_Viewport_ValidValue_UpdatesViewport_AllowLocationGreaterThanCont Height = viewHeight, ViewportSettings = ViewportSettings.AllowLocationGreaterThanContentSize }; - var newViewport = new Rectangle (viewportX, viewportY, viewWidth, viewHeight); + var newViewport = new Rectangle (10, 10, viewWidth, viewHeight); // Act view.Viewport = newViewport; // Assert - Assert.Equal (new Rectangle (expectedX, expectedY, viewWidth, viewHeight), view.Viewport); + Assert.Equal (new Rectangle (10, 10, viewWidth, viewHeight), view.Viewport); } [Fact] From f328f0c93fcd40b04f1cbddbd32d57a286349f69 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 21 Jul 2024 16:52:18 -0600 Subject: [PATCH 63/96] Initial commit. --- UnitTests/Views/OverlappedTests.cs | 187 ++++++++++++++++++++++++++++ UnitTests/Views/ToplevelTests.cs | 189 +---------------------------- 2 files changed, 188 insertions(+), 188 deletions(-) diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index b4437b89db..9c7202d684 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -1015,4 +1015,191 @@ private class Overlapped : Toplevel { public Overlapped () { IsOverlappedContainer = true; } } + + [Fact] + [AutoInitShutdown] + public void KeyBindings_Command_With_OverlappedTop () + { + Toplevel top = new (); + Assert.Null (Application.OverlappedTop); + top.IsOverlappedContainer = true; + Application.Begin (top); + Assert.Equal (Application.Top, Application.OverlappedTop); + + var isRunning = true; + + var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; + var lblTf1W1 = new Label { Text = "Enter text in TextField on Win1:" }; + var tf1W1 = new TextField { X = Pos.Right (lblTf1W1) + 1, Width = Dim.Fill (), Text = "Text1 on Win1" }; + var lblTvW1 = new Label { Y = Pos.Bottom (lblTf1W1) + 1, Text = "Enter text in TextView on Win1:" }; + + var tvW1 = new TextView + { + X = Pos.Left (tf1W1), Width = Dim.Fill (), Height = 2, Text = "First line Win1\nSecond line Win1" + }; + var lblTf2W1 = new Label { Y = Pos.Bottom (lblTvW1) + 1, Text = "Enter text in TextField on Win1:" }; + var tf2W1 = new TextField { X = Pos.Left (tf1W1), Width = Dim.Fill (), Text = "Text2 on Win1" }; + win1.Add (lblTf1W1, tf1W1, lblTvW1, tvW1, lblTf2W1, tf2W1); + + var win2 = new Window { Id = "win2", Width = Dim.Percent (50), Height = Dim.Fill () }; + var lblTf1W2 = new Label { Text = "Enter text in TextField on Win2:" }; + var tf1W2 = new TextField { X = Pos.Right (lblTf1W2) + 1, Width = Dim.Fill (), Text = "Text1 on Win2" }; + var lblTvW2 = new Label { Y = Pos.Bottom (lblTf1W2) + 1, Text = "Enter text in TextView on Win2:" }; + + var tvW2 = new TextView + { + X = Pos.Left (tf1W2), Width = Dim.Fill (), Height = 2, Text = "First line Win1\nSecond line Win2" + }; + var lblTf2W2 = new Label { Y = Pos.Bottom (lblTvW2) + 1, Text = "Enter text in TextField on Win2:" }; + var tf2W2 = new TextField { X = Pos.Left (tf1W2), Width = Dim.Fill (), Text = "Text2 on Win2" }; + win2.Add (lblTf1W2, tf1W2, lblTvW2, tvW2, lblTf2W2, tf2W2); + + win1.Closing += (s, e) => isRunning = false; + Assert.Null (top.Focused); + Assert.Equal (top, Application.Current); + Assert.True (top.IsCurrentTop); + Assert.Equal (top, Application.OverlappedTop); + Application.Begin (win1); + Assert.Equal (new (0, 0, 40, 25), win1.Frame); + Assert.NotEqual (top, Application.Current); + Assert.False (top.IsCurrentTop); + Assert.Equal (win1, Application.Current); + Assert.True (win1.IsCurrentTop); + Assert.True (win1.IsOverlapped); + Assert.Null (top.Focused); + Assert.Null (top.MostFocused); + Assert.Equal (tf1W1, win1.MostFocused); + Assert.True (win1.IsOverlapped); + Assert.Single (Application.OverlappedChildren); + Application.Begin (win2); + Assert.Equal (new (0, 0, 40, 25), win2.Frame); + Assert.NotEqual (top, Application.Current); + Assert.False (top.IsCurrentTop); + Assert.Equal (win2, Application.Current); + Assert.True (win2.IsCurrentTop); + Assert.True (win2.IsOverlapped); + Assert.Null (top.Focused); + Assert.Null (top.MostFocused); + Assert.Equal (tf1W2, win2.MostFocused); + Assert.Equal (2, Application.OverlappedChildren.Count); + + Application.MoveToOverlappedChild (win1); + Assert.Equal (win1, Application.Current); + Assert.Equal (win1, Application.OverlappedChildren [0]); + win1.Running = true; + Assert.True (Application.OnKeyDown (Application.QuitKey)); + Assert.False (isRunning); + Assert.False (win1.Running); + Assert.Equal (win1, Application.OverlappedChildren [0]); + + Assert.True ( + Application.OnKeyDown (Key.Z.WithCtrl) + ); + + Assert.True (Application.OnKeyDown (Key.F5)); // refresh + + Assert.True (Application.OnKeyDown (Key.Tab)); + Assert.True (win1.IsCurrentTop); + Assert.Equal (tvW1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.Tab)); + Assert.Equal ($"\tFirst line Win1{Environment.NewLine}Second line Win1", tvW1.Text); + + Assert.True ( + Application.OnKeyDown (Key.Tab.WithShift) + ); + Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text); + + Assert.True ( + Application.OnKeyDown (Key.Tab.WithCtrl) + ); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf2W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.Tab)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf1W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorRight)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf1W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorDown)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tvW1, win1.MostFocused); +#if UNIX_KEY_BINDINGS + Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.I.WithCtrl))); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf2W1, win1.MostFocused); +#endif + Assert.True ( + Application.OverlappedChildren [0] + .NewKeyDownEvent (Key.Tab.WithShift) + ); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tvW1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorLeft)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf1W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorUp)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf2W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.Tab)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf1W1, win1.MostFocused); + + Assert.True ( + Application.OverlappedChildren [0] + .NewKeyDownEvent (Key.Tab.WithCtrl) + ); + Assert.Equal (win2, Application.OverlappedChildren [0]); + Assert.Equal (tf1W2, win2.MostFocused); + tf2W2.SetFocus (); + Assert.True (tf2W2.HasFocus); + + Assert.True ( + Application.OverlappedChildren [0] + .NewKeyDownEvent (Key.Tab.WithCtrl.WithShift) + ); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf1W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Application.AlternateForwardKey)); + Assert.Equal (win2, Application.OverlappedChildren [0]); + Assert.Equal (tf2W2, win2.MostFocused); + Assert.True (Application.OnKeyDown (Application.AlternateBackwardKey)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf1W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorDown)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tvW1, win1.MostFocused); +#if UNIX_KEY_BINDINGS + Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.B.WithCtrl))); +#else + Assert.True (Application.OnKeyDown (Key.CursorLeft)); +#endif + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf1W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorDown)); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (Point.Empty, tvW1.CursorPosition); + + Assert.True ( + Application.OverlappedChildren [0] + .NewKeyDownEvent (Key.End.WithCtrl) + ); + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (new (16, 1), tvW1.CursorPosition); +#if UNIX_KEY_BINDINGS + Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.F.WithCtrl))); +#else + Assert.True (Application.OnKeyDown (Key.CursorRight)); +#endif + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf2W1, win1.MostFocused); + +#if UNIX_KEY_BINDINGS + Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.L.WithCtrl))); +#endif + win2.Dispose (); + win1.Dispose (); + top.Dispose (); + } } diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index ed3482a50c..abbfdeb59c 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui.ViewsTests; -public class ToplevelTests (ITestOutputHelper output) +public partial class ToplevelTests (ITestOutputHelper output) { [Fact] public void Constructor_Default () @@ -556,193 +556,6 @@ public void KeyBindings_Command () top.Dispose (); } - [Fact] - [AutoInitShutdown] - public void KeyBindings_Command_With_OverlappedTop () - { - Toplevel top = new (); - Assert.Null (Application.OverlappedTop); - top.IsOverlappedContainer = true; - Application.Begin (top); - Assert.Equal (Application.Top, Application.OverlappedTop); - - var isRunning = true; - - var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; - var lblTf1W1 = new Label { Text = "Enter text in TextField on Win1:" }; - var tf1W1 = new TextField { X = Pos.Right (lblTf1W1) + 1, Width = Dim.Fill (), Text = "Text1 on Win1" }; - var lblTvW1 = new Label { Y = Pos.Bottom (lblTf1W1) + 1, Text = "Enter text in TextView on Win1:" }; - - var tvW1 = new TextView - { - X = Pos.Left (tf1W1), Width = Dim.Fill (), Height = 2, Text = "First line Win1\nSecond line Win1" - }; - var lblTf2W1 = new Label { Y = Pos.Bottom (lblTvW1) + 1, Text = "Enter text in TextField on Win1:" }; - var tf2W1 = new TextField { X = Pos.Left (tf1W1), Width = Dim.Fill (), Text = "Text2 on Win1" }; - win1.Add (lblTf1W1, tf1W1, lblTvW1, tvW1, lblTf2W1, tf2W1); - - var win2 = new Window { Id = "win2", Width = Dim.Percent (50), Height = Dim.Fill () }; - var lblTf1W2 = new Label { Text = "Enter text in TextField on Win2:" }; - var tf1W2 = new TextField { X = Pos.Right (lblTf1W2) + 1, Width = Dim.Fill (), Text = "Text1 on Win2" }; - var lblTvW2 = new Label { Y = Pos.Bottom (lblTf1W2) + 1, Text = "Enter text in TextView on Win2:" }; - - var tvW2 = new TextView - { - X = Pos.Left (tf1W2), Width = Dim.Fill (), Height = 2, Text = "First line Win1\nSecond line Win2" - }; - var lblTf2W2 = new Label { Y = Pos.Bottom (lblTvW2) + 1, Text = "Enter text in TextField on Win2:" }; - var tf2W2 = new TextField { X = Pos.Left (tf1W2), Width = Dim.Fill (), Text = "Text2 on Win2" }; - win2.Add (lblTf1W2, tf1W2, lblTvW2, tvW2, lblTf2W2, tf2W2); - - win1.Closing += (s, e) => isRunning = false; - Assert.Null (top.Focused); - Assert.Equal (top, Application.Current); - Assert.True (top.IsCurrentTop); - Assert.Equal (top, Application.OverlappedTop); - Application.Begin (win1); - Assert.Equal (new (0, 0, 40, 25), win1.Frame); - Assert.NotEqual (top, Application.Current); - Assert.False (top.IsCurrentTop); - Assert.Equal (win1, Application.Current); - Assert.True (win1.IsCurrentTop); - Assert.True (win1.IsOverlapped); - Assert.Null (top.Focused); - Assert.Null (top.MostFocused); - Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (win1.IsOverlapped); - Assert.Single (Application.OverlappedChildren); - Application.Begin (win2); - Assert.Equal (new (0, 0, 40, 25), win2.Frame); - Assert.NotEqual (top, Application.Current); - Assert.False (top.IsCurrentTop); - Assert.Equal (win2, Application.Current); - Assert.True (win2.IsCurrentTop); - Assert.True (win2.IsOverlapped); - Assert.Null (top.Focused); - Assert.Null (top.MostFocused); - Assert.Equal (tf1W2, win2.MostFocused); - Assert.Equal (2, Application.OverlappedChildren.Count); - - Application.MoveToOverlappedChild (win1); - Assert.Equal (win1, Application.Current); - Assert.Equal (win1, Application.OverlappedChildren [0]); - win1.Running = true; - Assert.True (Application.OnKeyDown (Application.QuitKey)); - Assert.False (isRunning); - Assert.False (win1.Running); - Assert.Equal (win1, Application.OverlappedChildren [0]); - - Assert.True ( - Application.OnKeyDown (Key.Z.WithCtrl) - ); - - Assert.True (Application.OnKeyDown (Key.F5)); // refresh - - Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.True (win1.IsCurrentTop); - Assert.Equal (tvW1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.Equal ($"\tFirst line Win1{Environment.NewLine}Second line Win1", tvW1.Text); - - Assert.True ( - Application.OnKeyDown (Key.Tab.WithShift) - ); - Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text); - - Assert.True ( - Application.OnKeyDown (Key.Tab.WithCtrl) - ); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorRight)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorDown)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); -#if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.I.WithCtrl))); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); -#endif - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithShift) - ); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorLeft)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorUp)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); - - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithCtrl) - ); - Assert.Equal (win2, Application.OverlappedChildren [0]); - Assert.Equal (tf1W2, win2.MostFocused); - tf2W2.SetFocus (); - Assert.True (tf2W2.HasFocus); - - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithCtrl.WithShift) - ); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Application.AlternateForwardKey)); - Assert.Equal (win2, Application.OverlappedChildren [0]); - Assert.Equal (tf2W2, win2.MostFocused); - Assert.True (Application.OnKeyDown (Application.AlternateBackwardKey)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorDown)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); -#if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.B.WithCtrl))); -#else - Assert.True (Application.OnKeyDown (Key.CursorLeft)); -#endif - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorDown)); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); - Assert.Equal (Point.Empty, tvW1.CursorPosition); - - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.End.WithCtrl) - ); - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); - Assert.Equal (new (16, 1), tvW1.CursorPosition); -#if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.F.WithCtrl))); -#else - Assert.True (Application.OnKeyDown (Key.CursorRight)); -#endif - Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); - -#if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.L.WithCtrl))); -#endif - win2.Dispose (); - win1.Dispose (); - top.Dispose (); - } - [Fact] public void Added_Event_Should_Not_Be_Used_To_Initialize_Toplevel_Events () { From 44ce74a5c0e2debadb3771e24ca6452018850771 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 22 Jul 2024 16:52:02 -0600 Subject: [PATCH 64/96] Refactored Application into smaller files. Made Application #nullable enable --- .../Application/Application.Driver.cs | 29 + .../Application/Application.Initialization.cs | 209 +++ ...ionKeyboard.cs => Application.Keyboard.cs} | 2 +- ...plicationMouse.cs => Application.Mouse.cs} | 4 +- Terminal.Gui/Application/Application.Run.cs | 863 +++++++++++++ Terminal.Gui/Application/Application.cs | 1117 +---------------- Terminal.Gui/Clipboard/Clipboard.cs | 33 +- Terminal.Gui/Drawing/LineCanvas.cs | 6 +- Terminal.Gui/Drawing/Ruler.cs | 8 +- Terminal.Gui/Drawing/Thickness.cs | 14 +- .../Text/Autocomplete/AppendAutocomplete.cs | 4 +- .../Text/Autocomplete/PopupAutocomplete.cs | 6 +- Terminal.Gui/View/Layout/Dim.cs | 4 +- Terminal.Gui/View/Layout/DimView.cs | 4 +- Terminal.Gui/View/Layout/Pos.cs | 12 +- Terminal.Gui/View/Layout/PosView.cs | 4 +- Terminal.Gui/View/Layout/ViewLayout.cs | 4 +- Terminal.Gui/View/ViewDrawing.cs | 8 +- Terminal.Gui/Views/GraphView/Annotations.cs | 6 +- Terminal.Gui/Views/GraphView/Axis.cs | 14 +- Terminal.Gui/Views/GraphView/Series.cs | 4 +- Terminal.Gui/Views/Menu/ContextMenu.cs | 2 +- Terminal.Gui/Views/RadioGroup.cs | 12 +- UICatalog/Scenarios/CombiningMarks.cs | 28 +- UICatalog/Scenarios/Images.cs | 4 +- UICatalog/Scenarios/SendKeys.cs | 2 +- UICatalog/Scenarios/TextEffectsScenario.cs | 2 +- UICatalog/Scenarios/TrueColors.cs | 4 +- UICatalog/Scenarios/VkeyPacketSimulator.cs | 2 +- UICatalog/UICatalog.cs | 4 +- UnitTests/Application/ApplicationTests.cs | 14 +- UnitTests/Application/CursorTests.cs | 7 +- UnitTests/Clipboard/ClipboardTests.cs | 4 +- UnitTests/ConsoleDrivers/ClipRegionTests.cs | 8 +- .../ConsoleDrivers/ConsoleDriverTests.cs | 2 +- .../ConsoleDrivers/ConsoleKeyMappingTests.cs | 2 +- UnitTests/Dialogs/MessageBoxTests.cs | 20 +- UnitTests/Drawing/RulerTests.cs | 10 +- UnitTests/Drawing/ThicknessTests.cs | 20 +- UnitTests/FileServices/FileDialogTests.cs | 4 +- UnitTests/Text/TextFormatterTests.cs | 4 +- UnitTests/View/Adornment/BorderTests.cs | 16 +- UnitTests/View/Adornment/MarginTests.cs | 2 +- UnitTests/View/Adornment/PaddingTests.cs | 2 +- UnitTests/View/DrawTests.cs | 46 +- UnitTests/View/Layout/Dim.FillTests.cs | 2 +- UnitTests/View/Layout/Pos.AnchorEndTests.cs | 2 +- UnitTests/View/Layout/Pos.CenterTests.cs | 4 +- UnitTests/View/Layout/ViewportTests.cs | 2 +- UnitTests/View/NavigationTests.cs | 14 +- UnitTests/View/TextTests.cs | 20 +- UnitTests/View/ViewTests.cs | 30 +- UnitTests/Views/AppendAutocompleteTests.cs | 36 +- UnitTests/Views/ButtonTests.cs | 6 +- UnitTests/Views/CheckBoxTests.cs | 8 +- UnitTests/Views/ContextMenuTests.cs | 18 +- UnitTests/Views/FrameViewTests.cs | 2 +- UnitTests/Views/LabelTests.cs | 26 +- UnitTests/Views/ListViewTests.cs | 4 +- UnitTests/Views/MenuBarTests.cs | 28 +- UnitTests/Views/OverlappedTests.cs | 2 +- UnitTests/Views/RadioGroupTests.cs | 2 +- UnitTests/Views/ScrollBarViewTests.cs | 12 +- UnitTests/Views/ScrollViewTests.cs | 2 +- UnitTests/Views/TableViewTests.cs | 2 +- UnitTests/Views/TextFieldTests.cs | 14 +- UnitTests/Views/TextViewTests.cs | 18 +- UnitTests/Views/ToplevelTests.cs | 48 +- UnitTests/Views/TreeTableSourceTests.cs | 4 +- UnitTests/Views/TreeViewTests.cs | 2 +- UnitTests/Views/WindowTests.cs | 6 +- 71 files changed, 1449 insertions(+), 1441 deletions(-) create mode 100644 Terminal.Gui/Application/Application.Driver.cs create mode 100644 Terminal.Gui/Application/Application.Initialization.cs rename Terminal.Gui/Application/{ApplicationKeyboard.cs => Application.Keyboard.cs} (99%) rename Terminal.Gui/Application/{ApplicationMouse.cs => Application.Mouse.cs} (98%) create mode 100644 Terminal.Gui/Application/Application.Run.cs diff --git a/Terminal.Gui/Application/Application.Driver.cs b/Terminal.Gui/Application/Application.Driver.cs new file mode 100644 index 0000000000..f15bd80539 --- /dev/null +++ b/Terminal.Gui/Application/Application.Driver.cs @@ -0,0 +1,29 @@ +#nullable enable +namespace Terminal.Gui; + +public static partial class Application // Driver abstractions +{ + internal static bool _forceFakeConsole; + + /// Gets the that has been selected. See also . + public static ConsoleDriver? Driver { get; internal set; } + + /// + /// Gets or sets whether will be forced to output only the 16 colors defined in + /// . The default is , meaning 24-bit (TrueColor) colors will be output + /// as long as the selected supports TrueColor. + /// + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static bool Force16Colors { get; set; } + + /// + /// Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If not + /// specified, the driver is selected based on the platform. + /// + /// + /// Note, will override this configuration setting if called + /// with either `driver` or `driverName` specified. + /// + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static string ForceDriver { get; set; } = string.Empty; +} diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs new file mode 100644 index 0000000000..a971850e39 --- /dev/null +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -0,0 +1,209 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace Terminal.Gui; + +public static partial class Application // Initialization (Init/Shutdown) +{ + /// Initializes a new instance of Application. + /// Call this method once per instance (or after has been called). + /// + /// This function loads the right for the platform, Creates a . and + /// assigns it to + /// + /// + /// must be called when the application is closing (typically after + /// has returned) to ensure resources are cleaned up and + /// terminal settings + /// restored. + /// + /// + /// The function combines + /// and + /// into a single + /// call. An application cam use without explicitly calling + /// . + /// + /// + /// The to use. If neither or + /// are specified the default driver for the platform will be used. + /// + /// + /// The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the + /// to use. If neither or are + /// specified the default driver for the platform will be used. + /// + [RequiresUnreferencedCode ("AOT")] + [RequiresDynamicCode ("AOT")] + public static void Init (ConsoleDriver driver = null, string driverName = null) { InternalInit (driver, driverName); } + + internal static bool _initialized; + internal static int _mainThreadId = -1; + + + // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. + // + // Called from: + // + // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset. + // Run() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run() to be called without calling Init first. + // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset. + // + // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. + [RequiresUnreferencedCode ("AOT")] + [RequiresDynamicCode ("AOT")] + internal static void InternalInit ( + ConsoleDriver driver = null, + string driverName = null, + bool calledViaRunT = false + ) + { + if (_initialized && driver is null) + { + return; + } + + if (_initialized) + { + throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); + } + + if (!calledViaRunT) + { + // Reset all class variables (Application is a singleton). + ResetState (); + } + + // For UnitTests + if (driver is { }) + { + Driver = driver; + } + + // Start the process of configuration management. + // Note that we end up calling LoadConfigurationFromAllSources + // multiple times. We need to do this because some settings are only + // valid after a Driver is loaded. In this case we need just + // `Settings` so we can determine which driver to use. + // Don't reset, so we can inherit the theme from the previous run. + Load (); + Apply (); + + // Ignore Configuration for ForceDriver if driverName is specified + if (!string.IsNullOrEmpty (driverName)) + { + ForceDriver = driverName; + } + + if (Driver is null) + { + PlatformID p = Environment.OSVersion.Platform; + + if (string.IsNullOrEmpty (ForceDriver)) + { + if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) + { + Driver = new WindowsDriver (); + } + else + { + Driver = new CursesDriver (); + } + } + else + { + List drivers = GetDriverTypes (); + Type driverType = drivers.FirstOrDefault (t => t.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase)); + + if (driverType is { }) + { + Driver = (ConsoleDriver)Activator.CreateInstance (driverType); + } + else + { + throw new ArgumentException ( + $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}" + ); + } + } + } + + try + { + MainLoop = Driver.Init (); + } + catch (InvalidOperationException ex) + { + // This is a case where the driver is unable to initialize the console. + // This can happen if the console is already in use by another process or + // if running in unit tests. + // In this case, we want to throw a more specific exception. + throw new InvalidOperationException ( + "Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", + ex + ); + } + + Driver.SizeChanged += (s, args) => OnSizeChanging (args); + Driver.KeyDown += (s, args) => OnKeyDown (args); + Driver.KeyUp += (s, args) => OnKeyUp (args); + Driver.MouseEvent += (s, args) => OnMouseEvent (args); + + SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); + + SupportedCultures = GetSupportedCultures (); + _mainThreadId = Thread.CurrentThread.ManagedThreadId; + _initialized = true; + InitializedChanged?.Invoke (null, new (in _initialized)); + } + + private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); } + private static void Driver_KeyDown (object sender, Key e) { OnKeyDown (e); } + private static void Driver_KeyUp (object sender, Key e) { OnKeyUp (e); } + private static void Driver_MouseEvent (object sender, MouseEvent e) { OnMouseEvent (e); } + + /// Gets of list of types that are available. + /// + [RequiresUnreferencedCode ("AOT")] + public static List GetDriverTypes () + { + // use reflection to get the list of drivers + List driverTypes = new (); + + foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ()) + { + foreach (Type type in asm.GetTypes ()) + { + if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) + { + driverTypes.Add (type); + } + } + } + + return driverTypes; + } + + /// Shutdown an application initialized with . + /// + /// Shutdown must be called for every call to or + /// to ensure all resources are cleaned + /// up (Disposed) + /// and terminal settings are restored. + /// + public static void Shutdown () + { + // TODO: Throw an exception if Init hasn't been called. + ResetState (); + PrintJsonErrors (); + InitializedChanged?.Invoke (null, new (in _initialized)); + } + + /// + /// This event is raised after the and methods have been called. + /// + /// + /// Intended to support unit tests that need to know when the application has been initialized. + /// + public static event EventHandler> InitializedChanged; +} diff --git a/Terminal.Gui/Application/ApplicationKeyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs similarity index 99% rename from Terminal.Gui/Application/ApplicationKeyboard.cs rename to Terminal.Gui/Application/Application.Keyboard.cs index be737968f0..e8d6982862 100644 --- a/Terminal.Gui/Application/ApplicationKeyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; -partial class Application +public static partial class Application // Keyboard handling { private static Key _alternateForwardKey = Key.Empty; // Defined in config.json diff --git a/Terminal.Gui/Application/ApplicationMouse.cs b/Terminal.Gui/Application/Application.Mouse.cs similarity index 98% rename from Terminal.Gui/Application/ApplicationMouse.cs rename to Terminal.Gui/Application/Application.Mouse.cs index 9f2a953390..4d3fb61298 100644 --- a/Terminal.Gui/Application/ApplicationMouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -1,6 +1,6 @@ namespace Terminal.Gui; -partial class Application +public static partial class Application // Mouse handling { #region Mouse handling @@ -272,7 +272,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (view is Adornment adornmentView) { - view = adornmentView.Parent.SuperView; + view = adornmentView.Parent!.SuperView; } else { diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs new file mode 100644 index 0000000000..34189d9fc9 --- /dev/null +++ b/Terminal.Gui/Application/Application.Run.cs @@ -0,0 +1,863 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Terminal.Gui; + +public static partial class Application // Run (Begin, Run, End, Stop) +{ + private static Toplevel _cachedRunStateToplevel; + + /// + /// Notify that a new was created ( was called). The token is + /// created in and this event will be fired before that function exits. + /// + /// + /// If is callers to + /// must also subscribe to and manually dispose of the token + /// when the application is done. + /// + public static event EventHandler NotifyNewRunState; + + /// Notify that an existent is stopping ( was called). + /// + /// If is callers to + /// must also subscribe to and manually dispose of the token + /// when the application is done. + /// + public static event EventHandler NotifyStopRunState; + + /// Building block API: Prepares the provided for execution. + /// + /// The handle that needs to be passed to the method upon + /// completion. + /// + /// The to prepare execution for. + /// + /// This method prepares the provided for running with the focus, it adds this to the list + /// of s, lays out the Subviews, focuses the first element, and draws the + /// in the screen. This is usually followed by executing the method, and then the + /// method upon termination which will undo these changes. + /// + public static RunState Begin (Toplevel toplevel) + { + ArgumentNullException.ThrowIfNull (toplevel); + +#if DEBUG_IDISPOSABLE + Debug.Assert (!toplevel.WasDisposed); + + if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel) + { + Debug.Assert (_cachedRunStateToplevel.WasDisposed); + } +#endif + + if (toplevel.IsOverlappedContainer && OverlappedTop != toplevel && OverlappedTop is { }) + { + throw new InvalidOperationException ("Only one Overlapped Container is allowed."); + } + + // Ensure the mouse is ungrabbed. + MouseGrabView = null; + + var rs = new RunState (toplevel); + + // View implements ISupportInitializeNotification which is derived from ISupportInitialize + if (!toplevel.IsInitialized) + { + toplevel.BeginInit (); + toplevel.EndInit (); + } + +#if DEBUG_IDISPOSABLE + if (Top is { } && toplevel != Top && !_topLevels.Contains (Top)) + { + // This assertion confirm if the Top was already disposed + Debug.Assert (Top.WasDisposed); + Debug.Assert (Top == _cachedRunStateToplevel); + } +#endif + + lock (_topLevels) + { + if (Top is { } && toplevel != Top && !_topLevels.Contains (Top)) + { + // If Top was already disposed and isn't on the Toplevels Stack, + // clean it up here if is the same as _cachedRunStateToplevel + if (Top == _cachedRunStateToplevel) + { + Top = null; + } + else + { + // Probably this will never hit + throw new ObjectDisposedException (Top.GetType ().FullName); + } + } + else if (OverlappedTop is { } && toplevel != Top && _topLevels.Contains (Top)) + { + Top.OnLeave (toplevel); + } + + // BUGBUG: We should not depend on `Id` internally. + // BUGBUG: It is super unclear what this code does anyway. + if (string.IsNullOrEmpty (toplevel.Id)) + { + var count = 1; + var id = (_topLevels.Count + count).ToString (); + + while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) is { }) + { + count++; + id = (_topLevels.Count + count).ToString (); + } + + toplevel.Id = (_topLevels.Count + count).ToString (); + + _topLevels.Push (toplevel); + } + else + { + Toplevel dup = _topLevels.FirstOrDefault (x => x.Id == toplevel.Id); + + if (dup is null) + { + _topLevels.Push (toplevel); + } + } + + if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) + { + throw new ArgumentException ("There are duplicates Toplevel IDs"); + } + } + + if (Top is null || toplevel.IsOverlappedContainer) + { + Top = toplevel; + } + + var refreshDriver = true; + + if (OverlappedTop is null + || toplevel.IsOverlappedContainer + || (Current?.Modal == false && toplevel.Modal) + || (Current?.Modal == false && !toplevel.Modal) + || (Current?.Modal == true && toplevel.Modal)) + { + if (toplevel.Visible) + { + Current?.OnDeactivate (toplevel); + Toplevel previousCurrent = Current; + Current = toplevel; + Current.OnActivate (previousCurrent); + + SetCurrentOverlappedAsTop (); + } + else + { + refreshDriver = false; + } + } + else if ((OverlappedTop != null + && toplevel != OverlappedTop + && Current?.Modal == true + && !_topLevels.Peek ().Modal) + || (OverlappedTop is { } && toplevel != OverlappedTop && Current?.Running == false)) + { + refreshDriver = false; + MoveCurrent (toplevel); + } + else + { + refreshDriver = false; + MoveCurrent (Current); + } + + toplevel.SetRelativeLayout (Driver.Screen.Size); + + toplevel.LayoutSubviews (); + toplevel.PositionToplevels (); + toplevel.FocusFirst (); + BringOverlappedTopToFront (); + + if (refreshDriver) + { + OverlappedTop?.OnChildLoaded (toplevel); + toplevel.OnLoaded (); + toplevel.SetNeedsDisplay (); + toplevel.Draw (); + Driver.UpdateScreen (); + + if (PositionCursor (toplevel)) + { + Driver.UpdateCursor (); + } + } + + NotifyNewRunState?.Invoke (toplevel, new (rs)); + + return rs; + } + + /// + /// Calls on the most focused view in the view starting with . + /// + /// + /// Does nothing if is or if the most focused view is not visible or + /// enabled. + /// + /// If the most focused view is not visible within it's superview, the cursor will be hidden. + /// + /// + /// if a view positioned the cursor and the position is visible. + internal static bool PositionCursor (View view) + { + // Find the most focused view and position the cursor there. + View mostFocused = view?.MostFocused; + + if (mostFocused is null) + { + if (view is { HasFocus: true }) + { + mostFocused = view; + } + else + { + return false; + } + } + + // If the view is not visible or enabled, don't position the cursor + if (!mostFocused.Visible || !mostFocused.Enabled) + { + Driver.GetCursorVisibility (out CursorVisibility current); + + if (current != CursorVisibility.Invisible) + { + Driver.SetCursorVisibility (CursorVisibility.Invisible); + } + + return false; + } + + // If the view is not visible within it's superview, don't position the cursor + Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty }); + Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen; + + if (!superViewViewport.IntersectsWith (mostFocusedViewport)) + { + return false; + } + + Point? cursor = mostFocused.PositionCursor (); + + Driver.GetCursorVisibility (out CursorVisibility currentCursorVisibility); + + if (cursor is { }) + { + // Convert cursor to screen coords + cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location; + + // If the cursor is not in a visible location in the SuperView, hide it + if (!superViewViewport.Contains (cursor.Value)) + { + if (currentCursorVisibility != CursorVisibility.Invisible) + { + Driver.SetCursorVisibility (CursorVisibility.Invisible); + } + + return false; + } + + // Show it + if (currentCursorVisibility == CursorVisibility.Invisible) + { + Driver.SetCursorVisibility (mostFocused.CursorVisibility); + } + + return true; + } + + if (currentCursorVisibility != CursorVisibility.Invisible) + { + Driver.SetCursorVisibility (CursorVisibility.Invisible); + } + + return false; + } + + /// + /// Runs the application by creating a object and calling + /// . + /// + /// + /// Calling first is not needed as this function will initialize the application. + /// + /// must be called when the application is closing (typically after Run> has returned) to + /// ensure resources are cleaned up and terminal settings restored. + /// + /// + /// The caller is responsible for disposing the object returned by this method. + /// + /// + /// The created object. The caller is responsible for disposing this object. + [RequiresUnreferencedCode ("AOT")] + [RequiresDynamicCode ("AOT")] + public static Toplevel Run (Func errorHandler = null, ConsoleDriver driver = null) { return Run (errorHandler, driver); } + + /// + /// Runs the application by creating a -derived object of type T and calling + /// . + /// + /// + /// Calling first is not needed as this function will initialize the application. + /// + /// must be called when the application is closing (typically after Run> has returned) to + /// ensure resources are cleaned up and terminal settings restored. + /// + /// + /// The caller is responsible for disposing the object returned by this method. + /// + /// + /// + /// + /// The to use. If not specified the default driver for the platform will + /// be used ( , , or ). Must be + /// if has already been called. + /// + /// The created T object. The caller is responsible for disposing this object. + [RequiresUnreferencedCode ("AOT")] + [RequiresDynamicCode ("AOT")] + public static T Run (Func errorHandler = null, ConsoleDriver driver = null) + where T : Toplevel, new () + { + if (!_initialized) + { + // Init() has NOT been called. + InternalInit (driver, null, true); + } + + var top = new T (); + + Run (top, errorHandler); + + return top; + } + + /// Runs the Application using the provided view. + /// + /// + /// This method is used to start processing events for the main application, but it is also used to run other + /// modal s such as boxes. + /// + /// + /// To make a stop execution, call + /// . + /// + /// + /// Calling is equivalent to calling + /// , followed by , and then calling + /// . + /// + /// + /// Alternatively, to have a program control the main loop and process events manually, call + /// to set things up manually and then repeatedly call + /// with the wait parameter set to false. By doing this the + /// method will only process any pending events, timers, idle handlers and then + /// return control immediately. + /// + /// When using or + /// + /// will be called automatically. + /// + /// + /// RELEASE builds only: When is any exceptions will be + /// rethrown. Otherwise, if will be called. If + /// returns the will resume; otherwise this method will + /// exit. + /// + /// + /// The to run as a modal. + /// + /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, + /// rethrows when null). + /// + public static void Run (Toplevel view, Func errorHandler = null) + { + ArgumentNullException.ThrowIfNull (view); + + if (_initialized) + { + if (Driver is null) + { + // Disposing before throwing + view.Dispose (); + + // This code path should be impossible because Init(null, null) will select the platform default driver + throw new InvalidOperationException ( + "Init() completed without a driver being set (this should be impossible); Run() cannot be called." + ); + } + } + else + { + // Init() has NOT been called. + throw new InvalidOperationException ( + "Init() has not been called. Only Run() or Run() can be used without calling Init()." + ); + } + + var resume = true; + + while (resume) + { +#if !DEBUG + try + { +#endif + resume = false; + RunState runState = Begin (view); + + // If EndAfterFirstIteration is true then the user must dispose of the runToken + // by using NotifyStopRunState event. + RunLoop (runState); + + if (runState.Toplevel is null) + { +#if DEBUG_IDISPOSABLE + Debug.Assert (_topLevels.Count == 0); +#endif + runState.Dispose (); + + return; + } + + if (!EndAfterFirstIteration) + { + End (runState); + } +#if !DEBUG + } + catch (Exception error) + { + if (errorHandler is null) + { + throw; + } + + resume = errorHandler (error); + } +#endif + } + } + + /// Adds a timeout to the application. + /// + /// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be + /// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a + /// token that can be used to stop the timeout by calling . + /// + public static object AddTimeout (TimeSpan time, Func callback) { return MainLoop?.AddTimeout (time, callback); } + + /// Removes a previously scheduled timeout + /// The token parameter is the value returned by . + /// Returns + /// true + /// if the timeout is successfully removed; otherwise, + /// false + /// . + /// This method also returns + /// false + /// if the timeout is not found. + public static bool RemoveTimeout (object token) { return MainLoop?.RemoveTimeout (token) ?? false; } + + /// Runs on the thread that is processing events + /// the action to be invoked on the main processing thread. + public static void Invoke (Action action) + { + MainLoop?.AddIdle ( + () => + { + action (); + + return false; + } + ); + } + + /// Wakes up the running application that might be waiting on input. + public static void Wakeup () { MainLoop?.Wakeup (); } + + /// Triggers a refresh of the entire display. + public static void Refresh () + { + // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear + Driver.ClearContents (); + View last = null; + + foreach (Toplevel v in _topLevels.Reverse ()) + { + if (v.Visible) + { + v.SetNeedsDisplay (); + v.SetSubViewNeedsDisplay (); + v.Draw (); + } + + last = v; + } + + Driver.Refresh (); + } + + /// This event is raised on each iteration of the main loop. + /// See also + public static event EventHandler Iteration; + + /// The driver for the application + /// The main loop. + internal static MainLoop MainLoop { get; private set; } + + /// + /// Set to true to cause to be called after the first iteration. Set to false (the default) to + /// cause the application to continue running until Application.RequestStop () is called. + /// + public static bool EndAfterFirstIteration { get; set; } + + /// Building block API: Runs the main loop for the created . + /// The state returned by the method. + public static void RunLoop (RunState state) + { + ArgumentNullException.ThrowIfNull (state); + ObjectDisposedException.ThrowIf (state.Toplevel is null, "state"); + + var firstIteration = true; + + for (state.Toplevel.Running = true; state.Toplevel?.Running == true;) + { + MainLoop.Running = true; + + if (EndAfterFirstIteration && !firstIteration) + { + return; + } + + RunIteration (ref state, ref firstIteration); + } + + MainLoop.Running = false; + + // Run one last iteration to consume any outstanding input events from Driver + // This is important for remaining OnKeyUp events. + RunIteration (ref state, ref firstIteration); + } + + /// Run one application iteration. + /// The state returned by . + /// + /// Set to if this is the first run loop iteration. Upon return, it + /// will be set to if at least one iteration happened. + /// + public static void RunIteration (ref RunState state, ref bool firstIteration) + { + if (MainLoop.Running && MainLoop.EventsPending ()) + { + // Notify Toplevel it's ready + if (firstIteration) + { + state.Toplevel.OnReady (); + } + + MainLoop.RunIteration (); + Iteration?.Invoke (null, new ()); + EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); + + if (state.Toplevel != Current) + { + OverlappedTop?.OnDeactivate (state.Toplevel); + state.Toplevel = Current; + OverlappedTop?.OnActivate (state.Toplevel); + Top.SetSubViewNeedsDisplay (); + Refresh (); + } + } + + firstIteration = false; + + if (Current == null) + { + return; + } + + if (state.Toplevel != Top && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) + { + state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame); + Top.Draw (); + + foreach (Toplevel top in _topLevels.Reverse ()) + { + if (top != Top && top != state.Toplevel) + { + top.SetNeedsDisplay (); + top.SetSubViewNeedsDisplay (); + top.Draw (); + } + } + } + + if (_topLevels.Count == 1 + && state.Toplevel == Top + && (Driver.Cols != state.Toplevel.Frame.Width + || Driver.Rows != state.Toplevel.Frame.Height) + && (state.Toplevel.NeedsDisplay + || state.Toplevel.SubViewNeedsDisplay + || state.Toplevel.LayoutNeeded)) + { + Driver.ClearContents (); + } + + if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ()) + { + state.Toplevel.SetNeedsDisplay (); + state.Toplevel.Draw (); + Driver.UpdateScreen (); + + //Driver.UpdateCursor (); + } + + if (PositionCursor (state.Toplevel)) + { + Driver.UpdateCursor (); + } + + // else + { + //if (PositionCursor (state.Toplevel)) + //{ + // Driver.Refresh (); + //} + //Driver.UpdateCursor (); + } + + if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) + { + Top.Draw (); + } + } + + /// Stops the provided , causing or the if provided. + /// The to stop. + /// + /// This will cause to return. + /// + /// Calling is equivalent to setting the + /// property on the currently running to false. + /// + /// + public static void RequestStop (Toplevel top = null) + { + if (OverlappedTop is null || top is null || (OverlappedTop is null && top is { })) + { + top = Current; + } + + if (OverlappedTop != null + && top.IsOverlappedContainer + && top?.Running == true + && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) + { + OverlappedTop.RequestStop (); + } + else if (OverlappedTop != null + && top != Current + && Current?.Running == true + && Current?.Modal == true + && top.Modal + && top.Running) + { + var ev = new ToplevelClosingEventArgs (Current); + Current.OnClosing (ev); + + if (ev.Cancel) + { + return; + } + + ev = new (top); + top.OnClosing (ev); + + if (ev.Cancel) + { + return; + } + + Current.Running = false; + OnNotifyStopRunState (Current); + top.Running = false; + OnNotifyStopRunState (top); + } + else if ((OverlappedTop != null + && top != OverlappedTop + && top != Current + && Current?.Modal == false + && Current?.Running == true + && !top.Running) + || (OverlappedTop != null + && top != OverlappedTop + && top != Current + && Current?.Modal == false + && Current?.Running == false + && !top.Running + && _topLevels.ToArray () [1].Running)) + { + MoveCurrent (top); + } + else if (OverlappedTop != null + && Current != top + && Current?.Running == true + && !top.Running + && Current?.Modal == true + && top.Modal) + { + // The Current and the top are both modal so needed to set the Current.Running to false too. + Current.Running = false; + OnNotifyStopRunState (Current); + } + else if (OverlappedTop != null + && Current == top + && OverlappedTop?.Running == true + && Current?.Running == true + && top.Running + && Current?.Modal == true + && top.Modal) + { + // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top, + // both are the same, so needed to set the Current.Running to false too. + Current.Running = false; + OnNotifyStopRunState (Current); + } + else + { + Toplevel currentTop; + + if (top == Current || (Current?.Modal == true && !top.Modal)) + { + currentTop = Current; + } + else + { + currentTop = top; + } + + if (!currentTop.Running) + { + return; + } + + var ev = new ToplevelClosingEventArgs (currentTop); + currentTop.OnClosing (ev); + + if (ev.Cancel) + { + return; + } + + currentTop.Running = false; + OnNotifyStopRunState (currentTop); + } + } + + private static void OnNotifyStopRunState (Toplevel top) + { + if (EndAfterFirstIteration) + { + NotifyStopRunState?.Invoke (top, new (top)); + } + } + + /// + /// Building block API: completes the execution of a that was started with + /// . + /// + /// The returned by the method. + public static void End (RunState runState) + { + ArgumentNullException.ThrowIfNull (runState); + + if (OverlappedTop is { }) + { + OverlappedTop.OnChildUnloaded (runState.Toplevel); + } + else + { + runState.Toplevel.OnUnloaded (); + } + + // End the RunState.Toplevel + // First, take it off the Toplevel Stack + if (_topLevels.Count > 0) + { + if (_topLevels.Peek () != runState.Toplevel) + { + // If the top of the stack is not the RunState.Toplevel then + // this call to End is not balanced with the call to Begin that started the RunState + throw new ArgumentException ("End must be balanced with calls to Begin"); + } + + _topLevels.Pop (); + } + + // Notify that it is closing + runState.Toplevel?.OnClosed (runState.Toplevel); + + // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel + // is a child of MidTop, and we should notify the OverlappedTop that it is closing + if (OverlappedTop is { } && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop) + { + OverlappedTop.OnChildClosed (runState.Toplevel); + } + + // Set Current and Top to the next TopLevel on the stack + if (_topLevels.Count == 0) + { + Current = null; + } + else + { + if (_topLevels.Count > 1 && _topLevels.Peek () == OverlappedTop && OverlappedChildren.Any (t => t.Visible) is { }) + { + OverlappedMoveNext (); + } + + Current = _topLevels.Peek (); + + if (_topLevels.Count == 1 && Current == OverlappedTop) + { + OverlappedTop.OnAllChildClosed (); + } + else + { + SetCurrentOverlappedAsTop (); + runState.Toplevel.OnLeave (Current); + Current.OnEnter (runState.Toplevel); + } + + Refresh (); + } + + // Don't dispose runState.Toplevel. It's up to caller dispose it + // If it's not the same as the current in the RunIteration, + // it will be fixed later in the next RunIteration. + if (OverlappedTop is { } && !_topLevels.Contains (OverlappedTop)) + { + _cachedRunStateToplevel = OverlappedTop; + } + else + { + _cachedRunStateToplevel = runState.Toplevel; + } + + runState.Toplevel = null; + runState.Dispose (); + } +} diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 9c981b743d..1561413e9a 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -1,5 +1,5 @@ +#nullable enable using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; @@ -18,31 +18,6 @@ namespace Terminal.Gui; /// TODO: Flush this out. public static partial class Application { - // For Unit testing - ignores UseSystemConsole - internal static bool _forceFakeConsole; - - /// Gets the that has been selected. See also . - public static ConsoleDriver Driver { get; internal set; } - - /// - /// Gets or sets whether will be forced to output only the 16 colors defined in - /// . The default is , meaning 24-bit (TrueColor) colors will be output - /// as long as the selected supports TrueColor. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool Force16Colors { get; set; } - - /// - /// Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If not - /// specified, the driver is selected based on the platform. - /// - /// - /// Note, will override this configuration setting if called - /// with either `driver` or `driverName` specified. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static string ForceDriver { get; set; } = string.Empty; - /// Gets all cultures supported by the application without the invariant language. public static List SupportedCultures { get; private set; } @@ -68,10 +43,6 @@ internal static List GetSupportedCultures () .ToList (); } - // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`. - // This variable is set in `End` in this case so that `Begin` correctly sets `Top`. - private static Toplevel _cachedRunStateToplevel; - // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test. // Encapsulate all setting of initial state for Application; Having // this in a function like this ensures we don't make mistakes in @@ -82,9 +53,9 @@ internal static void ResetState (bool ignoreDisposed = false) // Shutdown is the bookend for Init. As such it needs to clean up all resources // Init created. Apps that do any threading will need to code defensively for this. // e.g. see Issue #537 - foreach (Toplevel t in _topLevels) + foreach (Toplevel? t in _topLevels) { - t.Running = false; + t!.Running = false; } _topLevels.Clear (); @@ -163,1072 +134,11 @@ internal static void ResetState (bool ignoreDisposed = false) SynchronizationContext.SetSynchronizationContext (null); } - #region Initialization (Init/Shutdown) - - /// Initializes a new instance of Application. - /// Call this method once per instance (or after has been called). - /// - /// This function loads the right for the platform, Creates a . and - /// assigns it to - /// - /// - /// must be called when the application is closing (typically after - /// has returned) to ensure resources are cleaned up and - /// terminal settings - /// restored. - /// - /// - /// The function combines - /// and - /// into a single - /// call. An application cam use without explicitly calling - /// . - /// - /// - /// The to use. If neither or - /// are specified the default driver for the platform will be used. - /// - /// - /// The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the - /// to use. If neither or are - /// specified the default driver for the platform will be used. - /// - [RequiresUnreferencedCode ("AOT")] - [RequiresDynamicCode ("AOT")] - public static void Init (ConsoleDriver driver = null, string driverName = null) { InternalInit (driver, driverName); } - - internal static bool _initialized; - internal static int _mainThreadId = -1; - - // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. - // - // Called from: - // - // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset. - // Run() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run() to be called without calling Init first. - // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset. - // - // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. - [RequiresUnreferencedCode ("AOT")] - [RequiresDynamicCode ("AOT")] - internal static void InternalInit ( - ConsoleDriver driver = null, - string driverName = null, - bool calledViaRunT = false - ) - { - if (_initialized && driver is null) - { - return; - } - - if (_initialized) - { - throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); - } - - if (!calledViaRunT) - { - // Reset all class variables (Application is a singleton). - ResetState (); - } - - // For UnitTests - if (driver is { }) - { - Driver = driver; - } - - // Start the process of configuration management. - // Note that we end up calling LoadConfigurationFromAllSources - // multiple times. We need to do this because some settings are only - // valid after a Driver is loaded. In this case we need just - // `Settings` so we can determine which driver to use. - // Don't reset, so we can inherit the theme from the previous run. - Load (); - Apply (); - - // Ignore Configuration for ForceDriver if driverName is specified - if (!string.IsNullOrEmpty (driverName)) - { - ForceDriver = driverName; - } - - if (Driver is null) - { - PlatformID p = Environment.OSVersion.Platform; - - if (string.IsNullOrEmpty (ForceDriver)) - { - if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) - { - Driver = new WindowsDriver (); - } - else - { - Driver = new CursesDriver (); - } - } - else - { - List drivers = GetDriverTypes (); - Type driverType = drivers.FirstOrDefault (t => t.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase)); - - if (driverType is { }) - { - Driver = (ConsoleDriver)Activator.CreateInstance (driverType); - } - else - { - throw new ArgumentException ( - $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}" - ); - } - } - } - - try - { - MainLoop = Driver.Init (); - } - catch (InvalidOperationException ex) - { - // This is a case where the driver is unable to initialize the console. - // This can happen if the console is already in use by another process or - // if running in unit tests. - // In this case, we want to throw a more specific exception. - throw new InvalidOperationException ( - "Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", - ex - ); - } - - Driver.SizeChanged += (s, args) => OnSizeChanging (args); - Driver.KeyDown += (s, args) => OnKeyDown (args); - Driver.KeyUp += (s, args) => OnKeyUp (args); - Driver.MouseEvent += (s, args) => OnMouseEvent (args); - - SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); - - SupportedCultures = GetSupportedCultures (); - _mainThreadId = Thread.CurrentThread.ManagedThreadId; - _initialized = true; - InitializedChanged?.Invoke (null, new (in _initialized)); - } - - private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); } - private static void Driver_KeyDown (object sender, Key e) { OnKeyDown (e); } - private static void Driver_KeyUp (object sender, Key e) { OnKeyUp (e); } - private static void Driver_MouseEvent (object sender, MouseEvent e) { OnMouseEvent (e); } - - /// Gets of list of types that are available. - /// - [RequiresUnreferencedCode ("AOT")] - public static List GetDriverTypes () - { - // use reflection to get the list of drivers - List driverTypes = new (); - - foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ()) - { - foreach (Type type in asm.GetTypes ()) - { - if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) - { - driverTypes.Add (type); - } - } - } - - return driverTypes; - } - - /// Shutdown an application initialized with . - /// - /// Shutdown must be called for every call to or - /// to ensure all resources are cleaned - /// up (Disposed) - /// and terminal settings are restored. - /// - public static void Shutdown () - { - // TODO: Throw an exception if Init hasn't been called. - ResetState (); - PrintJsonErrors (); - InitializedChanged?.Invoke (null, new (in _initialized)); - } - -#nullable enable - /// - /// This event is raised after the and methods have been called. - /// - /// - /// Intended to support unit tests that need to know when the application has been initialized. - /// - public static event EventHandler>? InitializedChanged; -#nullable restore - - #endregion Initialization (Init/Shutdown) - - #region Run (Begin, Run, End, Stop) - - /// - /// Notify that a new was created ( was called). The token is - /// created in and this event will be fired before that function exits. - /// - /// - /// If is callers to - /// must also subscribe to and manually dispose of the token - /// when the application is done. - /// - public static event EventHandler NotifyNewRunState; - - /// Notify that an existent is stopping ( was called). - /// - /// If is callers to - /// must also subscribe to and manually dispose of the token - /// when the application is done. - /// - public static event EventHandler NotifyStopRunState; - - /// Building block API: Prepares the provided for execution. - /// - /// The handle that needs to be passed to the method upon - /// completion. - /// - /// The to prepare execution for. - /// - /// This method prepares the provided for running with the focus, it adds this to the list - /// of s, lays out the Subviews, focuses the first element, and draws the - /// in the screen. This is usually followed by executing the method, and then the - /// method upon termination which will undo these changes. - /// - public static RunState Begin (Toplevel toplevel) - { - ArgumentNullException.ThrowIfNull (toplevel); - -#if DEBUG_IDISPOSABLE - Debug.Assert (!toplevel.WasDisposed); - - if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel) - { - Debug.Assert (_cachedRunStateToplevel.WasDisposed); - } -#endif - - if (toplevel.IsOverlappedContainer && OverlappedTop != toplevel && OverlappedTop is { }) - { - throw new InvalidOperationException ("Only one Overlapped Container is allowed."); - } - - // Ensure the mouse is ungrabbed. - MouseGrabView = null; - - var rs = new RunState (toplevel); - - // View implements ISupportInitializeNotification which is derived from ISupportInitialize - if (!toplevel.IsInitialized) - { - toplevel.BeginInit (); - toplevel.EndInit (); - } - -#if DEBUG_IDISPOSABLE - if (Top is { } && toplevel != Top && !_topLevels.Contains (Top)) - { - // This assertion confirm if the Top was already disposed - Debug.Assert (Top.WasDisposed); - Debug.Assert (Top == _cachedRunStateToplevel); - } -#endif - - lock (_topLevels) - { - if (Top is { } && toplevel != Top && !_topLevels.Contains (Top)) - { - // If Top was already disposed and isn't on the Toplevels Stack, - // clean it up here if is the same as _cachedRunStateToplevel - if (Top == _cachedRunStateToplevel) - { - Top = null; - } - else - { - // Probably this will never hit - throw new ObjectDisposedException (Top.GetType ().FullName); - } - } - else if (OverlappedTop is { } && toplevel != Top && _topLevels.Contains (Top)) - { - Top.OnLeave (toplevel); - } - - // BUGBUG: We should not depend on `Id` internally. - // BUGBUG: It is super unclear what this code does anyway. - if (string.IsNullOrEmpty (toplevel.Id)) - { - var count = 1; - var id = (_topLevels.Count + count).ToString (); - - while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) is { }) - { - count++; - id = (_topLevels.Count + count).ToString (); - } - - toplevel.Id = (_topLevels.Count + count).ToString (); - - _topLevels.Push (toplevel); - } - else - { - Toplevel dup = _topLevels.FirstOrDefault (x => x.Id == toplevel.Id); - - if (dup is null) - { - _topLevels.Push (toplevel); - } - } - - if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) - { - throw new ArgumentException ("There are duplicates Toplevel IDs"); - } - } - - if (Top is null || toplevel.IsOverlappedContainer) - { - Top = toplevel; - } - - var refreshDriver = true; - - if (OverlappedTop is null - || toplevel.IsOverlappedContainer - || (Current?.Modal == false && toplevel.Modal) - || (Current?.Modal == false && !toplevel.Modal) - || (Current?.Modal == true && toplevel.Modal)) - { - if (toplevel.Visible) - { - Current?.OnDeactivate (toplevel); - Toplevel previousCurrent = Current; - Current = toplevel; - Current.OnActivate (previousCurrent); - - SetCurrentOverlappedAsTop (); - } - else - { - refreshDriver = false; - } - } - else if ((OverlappedTop != null - && toplevel != OverlappedTop - && Current?.Modal == true - && !_topLevels.Peek ().Modal) - || (OverlappedTop is { } && toplevel != OverlappedTop && Current?.Running == false)) - { - refreshDriver = false; - MoveCurrent (toplevel); - } - else - { - refreshDriver = false; - MoveCurrent (Current); - } - - toplevel.SetRelativeLayout (Driver.Screen.Size); - - toplevel.LayoutSubviews (); - toplevel.PositionToplevels (); - toplevel.FocusFirst (); - BringOverlappedTopToFront (); - - if (refreshDriver) - { - OverlappedTop?.OnChildLoaded (toplevel); - toplevel.OnLoaded (); - toplevel.SetNeedsDisplay (); - toplevel.Draw (); - Driver.UpdateScreen (); - - if (PositionCursor (toplevel)) - { - Driver.UpdateCursor (); - } - } - - NotifyNewRunState?.Invoke (toplevel, new (rs)); - - return rs; - } - - /// - /// Calls on the most focused view in the view starting with . - /// - /// - /// Does nothing if is or if the most focused view is not visible or - /// enabled. - /// - /// If the most focused view is not visible within it's superview, the cursor will be hidden. - /// - /// - /// if a view positioned the cursor and the position is visible. - internal static bool PositionCursor (View view) - { - // Find the most focused view and position the cursor there. - View mostFocused = view?.MostFocused; - - if (mostFocused is null) - { - if (view is { HasFocus: true }) - { - mostFocused = view; - } - else - { - return false; - } - } - - // If the view is not visible or enabled, don't position the cursor - if (!mostFocused.Visible || !mostFocused.Enabled) - { - Driver.GetCursorVisibility (out CursorVisibility current); - - if (current != CursorVisibility.Invisible) - { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - } - - return false; - } - - // If the view is not visible within it's superview, don't position the cursor - Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty }); - Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen; - - if (!superViewViewport.IntersectsWith (mostFocusedViewport)) - { - return false; - } - - Point? cursor = mostFocused.PositionCursor (); - - Driver.GetCursorVisibility (out CursorVisibility currentCursorVisibility); - - if (cursor is { }) - { - // Convert cursor to screen coords - cursor = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = cursor.Value }).Location; - - // If the cursor is not in a visible location in the SuperView, hide it - if (!superViewViewport.Contains (cursor.Value)) - { - if (currentCursorVisibility != CursorVisibility.Invisible) - { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - } - - return false; - } - - // Show it - if (currentCursorVisibility == CursorVisibility.Invisible) - { - Driver.SetCursorVisibility (mostFocused.CursorVisibility); - } - - return true; - } - - if (currentCursorVisibility != CursorVisibility.Invisible) - { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - } - - return false; - } - - /// - /// Runs the application by creating a object and calling - /// . - /// - /// - /// Calling first is not needed as this function will initialize the application. - /// - /// must be called when the application is closing (typically after Run> has returned) to - /// ensure resources are cleaned up and terminal settings restored. - /// - /// - /// The caller is responsible for disposing the object returned by this method. - /// - /// - /// The created object. The caller is responsible for disposing this object. - [RequiresUnreferencedCode ("AOT")] - [RequiresDynamicCode ("AOT")] - public static Toplevel Run (Func errorHandler = null, ConsoleDriver driver = null) { return Run (errorHandler, driver); } - - /// - /// Runs the application by creating a -derived object of type T and calling - /// . - /// - /// - /// Calling first is not needed as this function will initialize the application. - /// - /// must be called when the application is closing (typically after Run> has returned) to - /// ensure resources are cleaned up and terminal settings restored. - /// - /// - /// The caller is responsible for disposing the object returned by this method. - /// - /// - /// - /// - /// The to use. If not specified the default driver for the platform will - /// be used ( , , or ). Must be - /// if has already been called. - /// - /// The created T object. The caller is responsible for disposing this object. - [RequiresUnreferencedCode ("AOT")] - [RequiresDynamicCode ("AOT")] - public static T Run (Func errorHandler = null, ConsoleDriver driver = null) - where T : Toplevel, new() - { - if (!_initialized) - { - // Init() has NOT been called. - InternalInit (driver, null, true); - } - - var top = new T (); - - Run (top, errorHandler); - - return top; - } - - /// Runs the Application using the provided view. - /// - /// - /// This method is used to start processing events for the main application, but it is also used to run other - /// modal s such as boxes. - /// - /// - /// To make a stop execution, call - /// . - /// - /// - /// Calling is equivalent to calling - /// , followed by , and then calling - /// . - /// - /// - /// Alternatively, to have a program control the main loop and process events manually, call - /// to set things up manually and then repeatedly call - /// with the wait parameter set to false. By doing this the - /// method will only process any pending events, timers, idle handlers and then - /// return control immediately. - /// - /// When using or - /// - /// will be called automatically. - /// - /// - /// RELEASE builds only: When is any exceptions will be - /// rethrown. Otherwise, if will be called. If - /// returns the will resume; otherwise this method will - /// exit. - /// - /// - /// The to run as a modal. - /// - /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, - /// rethrows when null). - /// - public static void Run (Toplevel view, Func errorHandler = null) - { - ArgumentNullException.ThrowIfNull (view); - - if (_initialized) - { - if (Driver is null) - { - // Disposing before throwing - view.Dispose (); - - // This code path should be impossible because Init(null, null) will select the platform default driver - throw new InvalidOperationException ( - "Init() completed without a driver being set (this should be impossible); Run() cannot be called." - ); - } - } - else - { - // Init() has NOT been called. - throw new InvalidOperationException ( - "Init() has not been called. Only Run() or Run() can be used without calling Init()." - ); - } - - var resume = true; - - while (resume) - { -#if !DEBUG - try - { -#endif - resume = false; - RunState runState = Begin (view); - - // If EndAfterFirstIteration is true then the user must dispose of the runToken - // by using NotifyStopRunState event. - RunLoop (runState); - - if (runState.Toplevel is null) - { -#if DEBUG_IDISPOSABLE - Debug.Assert (_topLevels.Count == 0); -#endif - runState.Dispose (); - - return; - } - - if (!EndAfterFirstIteration) - { - End (runState); - } -#if !DEBUG - } - catch (Exception error) - { - if (errorHandler is null) - { - throw; - } - - resume = errorHandler (error); - } -#endif - } - } - - /// Adds a timeout to the application. - /// - /// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be - /// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a - /// token that can be used to stop the timeout by calling . - /// - public static object AddTimeout (TimeSpan time, Func callback) { return MainLoop?.AddTimeout (time, callback); } - - /// Removes a previously scheduled timeout - /// The token parameter is the value returned by . - /// Returns - /// true - /// if the timeout is successfully removed; otherwise, - /// false - /// . - /// This method also returns - /// false - /// if the timeout is not found. - public static bool RemoveTimeout (object token) { return MainLoop?.RemoveTimeout (token) ?? false; } - - /// Runs on the thread that is processing events - /// the action to be invoked on the main processing thread. - public static void Invoke (Action action) - { - MainLoop?.AddIdle ( - () => - { - action (); - - return false; - } - ); - } + // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`. + // This field is set in `End` in this case so that `Begin` correctly sets `Top`. // TODO: Determine if this is really needed. The only code that calls WakeUp I can find // is ProgressBarStyles, and it's not clear it needs to. - /// Wakes up the running application that might be waiting on input. - public static void Wakeup () { MainLoop?.Wakeup (); } - - /// Triggers a refresh of the entire display. - public static void Refresh () - { - // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear - Driver.ClearContents (); - View last = null; - - foreach (Toplevel v in _topLevels.Reverse ()) - { - if (v.Visible) - { - v.SetNeedsDisplay (); - v.SetSubViewNeedsDisplay (); - v.Draw (); - } - - last = v; - } - - Driver.Refresh (); - } - - /// This event is raised on each iteration of the main loop. - /// See also - public static event EventHandler Iteration; - - /// The driver for the application - /// The main loop. - internal static MainLoop MainLoop { get; private set; } - - /// - /// Set to true to cause to be called after the first iteration. Set to false (the default) to - /// cause the application to continue running until Application.RequestStop () is called. - /// - public static bool EndAfterFirstIteration { get; set; } - - /// Building block API: Runs the main loop for the created . - /// The state returned by the method. - public static void RunLoop (RunState state) - { - ArgumentNullException.ThrowIfNull (state); - ObjectDisposedException.ThrowIf (state.Toplevel is null, "state"); - - var firstIteration = true; - - for (state.Toplevel.Running = true; state.Toplevel?.Running == true;) - { - MainLoop.Running = true; - - if (EndAfterFirstIteration && !firstIteration) - { - return; - } - - RunIteration (ref state, ref firstIteration); - } - - MainLoop.Running = false; - - // Run one last iteration to consume any outstanding input events from Driver - // This is important for remaining OnKeyUp events. - RunIteration (ref state, ref firstIteration); - } - - /// Run one application iteration. - /// The state returned by . - /// - /// Set to if this is the first run loop iteration. Upon return, it - /// will be set to if at least one iteration happened. - /// - public static void RunIteration (ref RunState state, ref bool firstIteration) - { - if (MainLoop.Running && MainLoop.EventsPending ()) - { - // Notify Toplevel it's ready - if (firstIteration) - { - state.Toplevel.OnReady (); - } - - MainLoop.RunIteration (); - Iteration?.Invoke (null, new ()); - EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); - - if (state.Toplevel != Current) - { - OverlappedTop?.OnDeactivate (state.Toplevel); - state.Toplevel = Current; - OverlappedTop?.OnActivate (state.Toplevel); - Top.SetSubViewNeedsDisplay (); - Refresh (); - } - } - - firstIteration = false; - - if (Current == null) - { - return; - } - - if (state.Toplevel != Top && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) - { - state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame); - Top.Draw (); - - foreach (Toplevel top in _topLevels.Reverse ()) - { - if (top != Top && top != state.Toplevel) - { - top.SetNeedsDisplay (); - top.SetSubViewNeedsDisplay (); - top.Draw (); - } - } - } - - if (_topLevels.Count == 1 - && state.Toplevel == Top - && (Driver.Cols != state.Toplevel.Frame.Width - || Driver.Rows != state.Toplevel.Frame.Height) - && (state.Toplevel.NeedsDisplay - || state.Toplevel.SubViewNeedsDisplay - || state.Toplevel.LayoutNeeded)) - { - Driver.ClearContents (); - } - - if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ()) - { - state.Toplevel.SetNeedsDisplay (); - state.Toplevel.Draw (); - Driver.UpdateScreen (); - - //Driver.UpdateCursor (); - } - - if (PositionCursor (state.Toplevel)) - { - Driver.UpdateCursor (); - } - - // else - { - //if (PositionCursor (state.Toplevel)) - //{ - // Driver.Refresh (); - //} - //Driver.UpdateCursor (); - } - - if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) - { - Top.Draw (); - } - } - - /// Stops the provided , causing or the if provided. - /// The to stop. - /// - /// This will cause to return. - /// - /// Calling is equivalent to setting the - /// property on the currently running to false. - /// - /// - public static void RequestStop (Toplevel top = null) - { - if (OverlappedTop is null || top is null || (OverlappedTop is null && top is { })) - { - top = Current; - } - - if (OverlappedTop != null - && top.IsOverlappedContainer - && top?.Running == true - && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) - { - OverlappedTop.RequestStop (); - } - else if (OverlappedTop != null - && top != Current - && Current?.Running == true - && Current?.Modal == true - && top.Modal - && top.Running) - { - var ev = new ToplevelClosingEventArgs (Current); - Current.OnClosing (ev); - - if (ev.Cancel) - { - return; - } - - ev = new (top); - top.OnClosing (ev); - - if (ev.Cancel) - { - return; - } - - Current.Running = false; - OnNotifyStopRunState (Current); - top.Running = false; - OnNotifyStopRunState (top); - } - else if ((OverlappedTop != null - && top != OverlappedTop - && top != Current - && Current?.Modal == false - && Current?.Running == true - && !top.Running) - || (OverlappedTop != null - && top != OverlappedTop - && top != Current - && Current?.Modal == false - && Current?.Running == false - && !top.Running - && _topLevels.ToArray () [1].Running)) - { - MoveCurrent (top); - } - else if (OverlappedTop != null - && Current != top - && Current?.Running == true - && !top.Running - && Current?.Modal == true - && top.Modal) - { - // The Current and the top are both modal so needed to set the Current.Running to false too. - Current.Running = false; - OnNotifyStopRunState (Current); - } - else if (OverlappedTop != null - && Current == top - && OverlappedTop?.Running == true - && Current?.Running == true - && top.Running - && Current?.Modal == true - && top.Modal) - { - // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top, - // both are the same, so needed to set the Current.Running to false too. - Current.Running = false; - OnNotifyStopRunState (Current); - } - else - { - Toplevel currentTop; - - if (top == Current || (Current?.Modal == true && !top.Modal)) - { - currentTop = Current; - } - else - { - currentTop = top; - } - - if (!currentTop.Running) - { - return; - } - - var ev = new ToplevelClosingEventArgs (currentTop); - currentTop.OnClosing (ev); - - if (ev.Cancel) - { - return; - } - - currentTop.Running = false; - OnNotifyStopRunState (currentTop); - } - } - - private static void OnNotifyStopRunState (Toplevel top) - { - if (EndAfterFirstIteration) - { - NotifyStopRunState?.Invoke (top, new (top)); - } - } - - /// - /// Building block API: completes the execution of a that was started with - /// . - /// - /// The returned by the method. - public static void End (RunState runState) - { - ArgumentNullException.ThrowIfNull (runState); - - if (OverlappedTop is { }) - { - OverlappedTop.OnChildUnloaded (runState.Toplevel); - } - else - { - runState.Toplevel.OnUnloaded (); - } - - // End the RunState.Toplevel - // First, take it off the Toplevel Stack - if (_topLevels.Count > 0) - { - if (_topLevels.Peek () != runState.Toplevel) - { - // If the top of the stack is not the RunState.Toplevel then - // this call to End is not balanced with the call to Begin that started the RunState - throw new ArgumentException ("End must be balanced with calls to Begin"); - } - - _topLevels.Pop (); - } - - // Notify that it is closing - runState.Toplevel?.OnClosed (runState.Toplevel); - - // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel - // is a child of MidTop, and we should notify the OverlappedTop that it is closing - if (OverlappedTop is { } && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop) - { - OverlappedTop.OnChildClosed (runState.Toplevel); - } - - // Set Current and Top to the next TopLevel on the stack - if (_topLevels.Count == 0) - { - Current = null; - } - else - { - if (_topLevels.Count > 1 && _topLevels.Peek () == OverlappedTop && OverlappedChildren.Any (t => t.Visible) is { }) - { - OverlappedMoveNext (); - } - - Current = _topLevels.Peek (); - - if (_topLevels.Count == 1 && Current == OverlappedTop) - { - OverlappedTop.OnAllChildClosed (); - } - else - { - SetCurrentOverlappedAsTop (); - runState.Toplevel.OnLeave (Current); - Current.OnEnter (runState.Toplevel); - } - - Refresh (); - } - - // Don't dispose runState.Toplevel. It's up to caller dispose it - // If it's not the same as the current in the RunIteration, - // it will be fixed later in the next RunIteration. - if (OverlappedTop is { } && !_topLevels.Contains (OverlappedTop)) - { - _cachedRunStateToplevel = OverlappedTop; - } - else - { - _cachedRunStateToplevel = runState.Toplevel; - } - - runState.Toplevel = null; - runState.Dispose (); - } - - #endregion Run (Begin, Run, End) #region Toplevel handling @@ -1240,7 +150,7 @@ public static void End (RunState runState) /// The object used for the application on startup () /// The top. - public static Toplevel Top { get; private set; } + public static Toplevel? Top { get; private set; } /// /// The current object. This is updated in enters and leaves to @@ -1251,7 +161,7 @@ public static void End (RunState runState) /// Only relevant in scenarios where is . /// /// The current. - public static Toplevel Current { get; private set; } + public static Toplevel? Current { get; private set; } private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) { @@ -1263,7 +173,7 @@ private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) return; } - foreach (Toplevel top in _topLevels.Reverse ()) + foreach (Toplevel? top in _topLevels.Reverse ()) { if (top.Modal && top != Current) { @@ -1292,7 +202,7 @@ private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) int rx = location.X - start.Frame.X; int ry = location.Y - start.Frame.Y; - foreach (Toplevel t in _topLevels) + foreach (Toplevel? t in _topLevels) { if (t != Current) { @@ -1327,7 +237,7 @@ private static View FindTopFromView (View view) #nullable enable // Only return true if the Current has changed. - private static bool MoveCurrent (Toplevel? top) + private static bool MoveCurrent (Toplevel top) { // The Current is modal and the top is not modal Toplevel then // the Current must be moved above the first not modal Toplevel. @@ -1343,9 +253,9 @@ private static bool MoveCurrent (Toplevel? top) } var index = 0; - Toplevel [] savedToplevels = _topLevels.ToArray (); + Toplevel? [] savedToplevels = _topLevels.ToArray (); - foreach (Toplevel t in savedToplevels) + foreach (Toplevel? t in savedToplevels) { if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) { @@ -1376,7 +286,7 @@ private static bool MoveCurrent (Toplevel? top) var index = 0; - foreach (Toplevel t in _topLevels.ToArray ()) + foreach (Toplevel? t in _topLevels.ToArray ()) { if (!t.Running && t != Current && index > 0) { @@ -1505,6 +415,7 @@ public static string ToString (ConsoleDriver driver) sb.AppendLine (); } + return sb.ToString (); } } diff --git a/Terminal.Gui/Clipboard/Clipboard.cs b/Terminal.Gui/Clipboard/Clipboard.cs index 63c1cc40ab..5dccea0a41 100644 --- a/Terminal.Gui/Clipboard/Clipboard.cs +++ b/Terminal.Gui/Clipboard/Clipboard.cs @@ -31,11 +31,11 @@ public static string Contents { if (IsSupported) { - string clipData = Application.Driver.Clipboard.GetClipboardData (); + string clipData = Application.Driver?.Clipboard.GetClipboardData (); if (clipData is null) { - // throw new InvalidOperationException ($"{Application.Driver.GetType ().Name}.GetClipboardData returned null instead of string.Empty"); + // throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty"); clipData = string.Empty; } @@ -60,7 +60,7 @@ public static string Contents value = string.Empty; } - Application.Driver.Clipboard.SetClipboardData (value); + Application.Driver?.Clipboard.SetClipboardData (value); } _contents = value; @@ -74,19 +74,16 @@ public static string Contents /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. /// - public static bool IsSupported => Application.Driver.Clipboard.IsSupported; + public static bool IsSupported => Application.Driver?.Clipboard.IsSupported ?? false; /// Copies the _contents of the OS clipboard to if possible. /// The _contents of the OS clipboard if successful, if not. /// the OS clipboard was retrieved, otherwise. public static bool TryGetClipboardData (out string result) { - if (IsSupported && Application.Driver.Clipboard.TryGetClipboardData (out result)) + if (IsSupported && Application.Driver!.Clipboard.TryGetClipboardData (out result)) { - if (_contents != result) - { - _contents = result; - } + _contents = result; return true; } @@ -101,7 +98,7 @@ public static bool TryGetClipboardData (out string result) /// the OS clipboard was set, otherwise. public static bool TrySetClipboardData (string text) { - if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text)) + if (IsSupported && Application.Driver!.Clipboard.TrySetClipboardData (text)) { _contents = text; @@ -155,7 +152,7 @@ public static (int exitCode, string result) Process ( using (var process = new Process { - StartInfo = new ProcessStartInfo + StartInfo = new() { FileName = cmd, Arguments = arguments, @@ -191,17 +188,9 @@ public static (int exitCode, string result) Process ( if (process.ExitCode > 0) { - output = $@"Process failed to run. Command line: { - cmd - } { - arguments - }. - Output: { - output - } - Error: { - process.StandardError.ReadToEnd () - }"; + output = $@"Process failed to run. Command line: {cmd} {arguments}. + Output: {output} + Error: {process.StandardError.ReadToEnd ()}"; } return (process.ExitCode, output); diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 4b5119a82a..9a7365f264 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -336,7 +336,7 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : intersects [0]!.Line.Attribute; } - private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects) + private Cell? GetCellForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects) { if (!intersects.Any ()) { @@ -356,7 +356,7 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE return cell; } - private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects) + private Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects) { if (!intersects.Any ()) { @@ -679,7 +679,7 @@ private abstract class IntersectionRuneResolver internal Rune _thickV; public IntersectionRuneResolver () { SetGlyphs (); } - public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects) + public Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects) { bool useRounded = intersects.Any ( i => i?.Line.Length != 0 diff --git a/Terminal.Gui/Drawing/Ruler.cs b/Terminal.Gui/Drawing/Ruler.cs index 348036c840..d2551101d0 100644 --- a/Terminal.Gui/Drawing/Ruler.cs +++ b/Terminal.Gui/Drawing/Ruler.cs @@ -39,8 +39,8 @@ public void Draw (Point location, int start = 0) _hTemplate.Repeat ((int)Math.Ceiling (Length + 2 / (double)_hTemplate.Length)) [start..(Length + start)]; // Top - Application.Driver.Move (location.X, location.Y); - Application.Driver.AddStr (hrule); + Application.Driver?.Move (location.X, location.Y); + Application.Driver?.AddStr (hrule); } else { @@ -50,8 +50,8 @@ public void Draw (Point location, int start = 0) for (int r = location.Y; r < location.Y + Length; r++) { - Application.Driver.Move (location.X, r); - Application.Driver.AddRune ((Rune)vrule [r - location.Y]); + Application.Driver?.Move (location.X, r); + Application.Driver?.AddRune ((Rune)vrule [r - location.Y]); } } } diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 532c0af8a3..ac6cc6cd6d 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -119,20 +119,20 @@ public Rectangle Draw (Rectangle rect, string label = null) // Draw the Top side if (Top > 0) { - Application.Driver.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar); + Application.Driver?.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar); } // Draw the Left side // Draw the Left side if (Left > 0) { - Application.Driver.FillRect (rect with { Width = Math.Min (rect.Width, Left) }, leftChar); + Application.Driver?.FillRect (rect with { Width = Math.Min (rect.Width, Left) }, leftChar); } // Draw the Right side if (Right > 0) { - Application.Driver.FillRect ( + Application.Driver?.FillRect ( rect with { X = Math.Max (0, rect.X + rect.Width - Right), @@ -145,7 +145,7 @@ rect with // Draw the Bottom side if (Bottom > 0) { - Application.Driver.FillRect ( + Application.Driver?.FillRect ( rect with { Y = rect.Y + Math.Max (0, rect.Height - Bottom), @@ -197,7 +197,11 @@ rect with VerticalAlignment = Alignment.End, AutoSize = true }; - tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect); + + if (Application.Driver?.CurrentAttribute is { }) + { + tf.Draw (rect, Application.Driver!.CurrentAttribute, Application.Driver!.CurrentAttribute, rect); + } } return GetInside (rect); diff --git a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs index f5f7190e62..2fa920e708 100644 --- a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs @@ -106,7 +106,7 @@ public override void RenderOverlay (Point renderAt) } // draw it like it's selected, even though it's not - Application.Driver.SetAttribute ( + Application.Driver?.SetAttribute ( new Attribute ( ColorScheme.Normal.Foreground, textField.ColorScheme.Focus.Background @@ -128,7 +128,7 @@ public override void RenderOverlay (Point renderAt) ); } - Application.Driver.AddStr (fragment); + Application.Driver?.AddStr (fragment); } /// diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs index 93e32b12e8..4dfeb8a951 100644 --- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs @@ -376,18 +376,18 @@ public override void RenderOverlay (Point renderAt) { if (i == SelectedIdx - ScrollOffset) { - Application.Driver.SetAttribute (ColorScheme.Focus); + Application.Driver?.SetAttribute (ColorScheme.Focus); } else { - Application.Driver.SetAttribute (ColorScheme.Normal); + Application.Driver?.SetAttribute (ColorScheme.Normal); } popup.Move (0, i); string text = TextFormatter.ClipOrPad (toRender [i].Title, width); - Application.Driver.AddStr (text); + Application.Driver?.AddStr (text); } } diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index e765414cb9..7dfc6eb2e3 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -139,7 +139,7 @@ public abstract class Dim /// Creates a object that tracks the Height of the specified . /// The height of the other . /// The view that will be tracked. - public static Dim Height (View view) { return new DimView (view, Dimension.Height); } + public static Dim Height (View? view) { return new DimView (view, Dimension.Height); } /// Creates a percentage object that is a percentage of the width or height of the SuperView. /// The percent object. @@ -171,7 +171,7 @@ public abstract class Dim /// Creates a object that tracks the Width of the specified . /// The width of the other . /// The view that will be tracked. - public static Dim Width (View view) { return new DimView (view, Dimension.Width); } + public static Dim Width (View? view) { return new DimView (view, Dimension.Width); } #endregion static Dim creation methods diff --git a/Terminal.Gui/View/Layout/DimView.cs b/Terminal.Gui/View/Layout/DimView.cs index 22c0d1f709..09ea96800c 100644 --- a/Terminal.Gui/View/Layout/DimView.cs +++ b/Terminal.Gui/View/Layout/DimView.cs @@ -15,7 +15,7 @@ public class DimView : Dim /// /// The view the dimension is anchored to. /// Indicates which dimension is tracked. - public DimView (View view, Dimension dimension) + public DimView (View? view, Dimension dimension) { Target = view; Dimension = dimension; @@ -35,7 +35,7 @@ public DimView (View view, Dimension dimension) /// /// Gets the View the dimension is anchored to. /// - public View Target { get; init; } + public View? Target { get; init; } /// public override string ToString () diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index 2213524aee..853bfa0abb 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -257,22 +257,22 @@ public static Pos Percent (int percent) /// Creates a object that tracks the Top (Y) position of the specified . /// The that depends on the other view. /// The that will be tracked. - public static Pos Top (View view) { return new PosView (view, Side.Top); } + public static Pos Top (View? view) { return new PosView (view, Side.Top); } /// Creates a object that tracks the Top (Y) position of the specified . /// The that depends on the other view. /// The that will be tracked. - public static Pos Y (View view) { return new PosView (view, Side.Top); } + public static Pos Y (View? view) { return new PosView (view, Side.Top); } /// Creates a object that tracks the Left (X) position of the specified . /// The that depends on the other view. /// The that will be tracked. - public static Pos Left (View view) { return new PosView (view, Side.Left); } + public static Pos Left (View? view) { return new PosView (view, Side.Left); } /// Creates a object that tracks the Left (X) position of the specified . /// The that depends on the other view. /// The that will be tracked. - public static Pos X (View view) { return new PosView (view, Side.Left); } + public static Pos X (View? view) { return new PosView (view, Side.Left); } /// /// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified @@ -280,7 +280,7 @@ public static Pos Percent (int percent) /// /// The that depends on the other view. /// The that will be tracked. - public static Pos Bottom (View view) { return new PosView (view, Side.Bottom); } + public static Pos Bottom (View? view) { return new PosView (view, Side.Bottom); } /// /// Creates a object that tracks the Right (X+Width) coordinate of the specified @@ -288,7 +288,7 @@ public static Pos Percent (int percent) /// /// The that depends on the other view. /// The that will be tracked. - public static Pos Right (View view) { return new PosView (view, Side.Right); } + public static Pos Right (View? view) { return new PosView (view, Side.Right); } #endregion static Pos creation methods diff --git a/Terminal.Gui/View/Layout/PosView.cs b/Terminal.Gui/View/Layout/PosView.cs index b48613307c..fdf5bf784e 100644 --- a/Terminal.Gui/View/Layout/PosView.cs +++ b/Terminal.Gui/View/Layout/PosView.cs @@ -12,12 +12,12 @@ namespace Terminal.Gui; /// /// The View the position is anchored to. /// The side of the View the position is anchored to. -public class PosView (View view, Side side) : Pos +public class PosView (View? view, Side side) : Pos { /// /// Gets the View the position is anchored to. /// - public View Target { get; } = view; + public View? Target { get; } = view; /// /// Gets the side of the View the position is anchored to. diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index fa0983993e..637d1f0ca4 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -398,7 +398,7 @@ public Dim? Width /// Either (if does not have a Super View) or /// 's SuperView. This can be used to ensure LayoutSubviews is called on the correct View. /// - internal static View GetLocationEnsuringFullVisibility ( + internal static View? GetLocationEnsuringFullVisibility ( View viewToMove, int targetX, int targetY, @@ -408,7 +408,7 @@ out StatusBar statusBar ) { int maxDimension; - View superView; + View? superView; statusBar = null!; if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 67778536f2..73fa5d550f 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -288,19 +288,19 @@ public void Draw () public void DrawHotString (string text, Attribute hotColor, Attribute normalColor) { Rune hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; - Application.Driver.SetAttribute (normalColor); + Application.Driver?.SetAttribute (normalColor); foreach (Rune rune in text.EnumerateRunes ()) { if (rune == new Rune (hotkeySpec.Value)) { - Application.Driver.SetAttribute (hotColor); + Application.Driver?.SetAttribute (hotColor); continue; } - Application.Driver.AddRune (rune); - Application.Driver.SetAttribute (normalColor); + Application.Driver?.AddRune (rune); + Application.Driver?.SetAttribute (normalColor); } } diff --git a/Terminal.Gui/Views/GraphView/Annotations.cs b/Terminal.Gui/Views/GraphView/Annotations.cs index 30247d4a69..7dbc8836e8 100644 --- a/Terminal.Gui/Views/GraphView/Annotations.cs +++ b/Terminal.Gui/Views/GraphView/Annotations.cs @@ -133,7 +133,7 @@ public void Render (GraphView graph) { if (!IsInitialized) { - ColorScheme = new ColorScheme { Normal = Application.Driver.GetAttribute () }; + ColorScheme = new ColorScheme { Normal = Application.Driver?.GetAttribute () ?? Attribute.Default}; graph.Add (this); } @@ -149,7 +149,7 @@ public void Render (GraphView graph) { if (entry.Item1.Color.HasValue) { - Application.Driver.SetAttribute (entry.Item1.Color.Value); + Application.Driver?.SetAttribute (entry.Item1.Color.Value); } else { @@ -166,7 +166,7 @@ public void Render (GraphView graph) Move (1, linesDrawn); string str = TextFormatter.ClipOrPad (entry.Item2, Viewport.Width - 1); - Application.Driver.AddStr (str); + Application.Driver?.AddStr (str); linesDrawn++; diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs index efff79ce97..c469388904 100644 --- a/Terminal.Gui/Views/GraphView/Axis.cs +++ b/Terminal.Gui/Views/GraphView/Axis.cs @@ -103,7 +103,7 @@ public override void DrawAxisLabel (GraphView graph, int screenPosition, string graph.Move (screenPosition, y); // draw the tick on the axis - Application.Driver.AddRune (Glyphs.TopTee); + Application.Driver?.AddRune (Glyphs.TopTee); // and the label text if (!string.IsNullOrWhiteSpace (text)) @@ -161,7 +161,7 @@ public override void DrawAxisLabels (GraphView graph) } graph.Move (graph.Viewport.Width / 2 - toRender.Length / 2, graph.Viewport.Height - 1); - Application.Driver.AddStr (toRender); + Application.Driver?.AddStr (toRender); } } @@ -222,7 +222,7 @@ public int GetAxisYPosition (GraphView graph) protected override void DrawAxisLine (GraphView graph, int x, int y) { graph.Move (x, y); - Application.Driver.AddRune (Glyphs.HLine); + Application.Driver?.AddRune (Glyphs.HLine); } private IEnumerable GetLabels (GraphView graph, Rectangle viewport) @@ -298,13 +298,13 @@ public override void DrawAxisLabel (GraphView graph, int screenPosition, string graph.Move (x, screenPosition); // draw the tick on the axis - Application.Driver.AddRune (Glyphs.RightTee); + Application.Driver?.AddRune (Glyphs.RightTee); // and the label text if (!string.IsNullOrWhiteSpace (text)) { graph.Move (Math.Max (0, x - labelThickness), screenPosition); - Application.Driver.AddStr (text); + Application.Driver?.AddStr (text); } } @@ -342,7 +342,7 @@ public override void DrawAxisLabels (GraphView graph) for (var i = 0; i < toRender.Length; i++) { graph.Move (0, startDrawingAtY + i); - Application.Driver.AddRune ((Rune)toRender [i]); + Application.Driver?.AddRune ((Rune)toRender [i]); } } } @@ -395,7 +395,7 @@ public int GetAxisXPosition (GraphView graph) protected override void DrawAxisLine (GraphView graph, int x, int y) { graph.Move (x, y); - Application.Driver.AddRune (Glyphs.VLine); + Application.Driver?.AddRune (Glyphs.VLine); } private int GetAxisYEnd (GraphView graph) diff --git a/Terminal.Gui/Views/GraphView/Series.cs b/Terminal.Gui/Views/GraphView/Series.cs index f0974556c6..f7c02e1749 100644 --- a/Terminal.Gui/Views/GraphView/Series.cs +++ b/Terminal.Gui/Views/GraphView/Series.cs @@ -33,7 +33,7 @@ public void DrawSeries (GraphView graph, Rectangle drawBounds, RectangleF graphB { if (Fill.Color.HasValue) { - Application.Driver.SetAttribute (Fill.Color.Value); + Application.Driver?.SetAttribute (Fill.Color.Value); } foreach (PointF p in Points.Where (p => graphBounds.Contains (p))) @@ -261,7 +261,7 @@ protected virtual void DrawBarLine (GraphView graph, Point start, Point end, Bar if (adjusted.Color.HasValue) { - Application.Driver.SetAttribute (adjusted.Color.Value); + Application.Driver?.SetAttribute (adjusted.Color.Value); } graph.DrawLine (start, end, adjusted.Rune); diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index 8bcbc076ad..f35cbdf34f 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/Terminal.Gui/Views/Menu/ContextMenu.cs @@ -144,7 +144,7 @@ public void Show () _container = Application.Current; _container.Closing += Container_Closing; _container.Deactivate += Container_Deactivate; - Rectangle frame = Application.Driver.Screen; + Rectangle frame = Application.Driver?.Screen ?? Rectangle.Empty; Point position = Position; if (Host is { }) diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index f0dc5174be..bb2996ba42 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -278,7 +278,7 @@ public override void OnDrawContent (Rectangle viewport) if (j == hotPos && i == _cursor) { - Application.Driver.SetAttribute ( + Application.Driver?.SetAttribute ( HasFocus ? ColorScheme.HotFocus : GetHotNormalColor () @@ -286,11 +286,11 @@ public override void OnDrawContent (Rectangle viewport) } else if (j == hotPos && i != _cursor) { - Application.Driver.SetAttribute (GetHotNormalColor ()); + Application.Driver?.SetAttribute (GetHotNormalColor ()); } else if (HasFocus && i == _cursor) { - Application.Driver.SetAttribute (ColorScheme.Focus); + Application.Driver?.SetAttribute (ColorScheme.Focus); } if (rune == HotKeySpecifier && j + 1 < rlRunes.Length) @@ -300,7 +300,7 @@ public override void OnDrawContent (Rectangle viewport) if (i == _cursor) { - Application.Driver.SetAttribute ( + Application.Driver?.SetAttribute ( HasFocus ? ColorScheme.HotFocus : GetHotNormalColor () @@ -308,11 +308,11 @@ public override void OnDrawContent (Rectangle viewport) } else if (i != _cursor) { - Application.Driver.SetAttribute (GetHotNormalColor ()); + Application.Driver?.SetAttribute (GetHotNormalColor ()); } } - Application.Driver.AddRune (rune); + Application.Driver?.AddRune (rune); Driver.SetAttribute (GetNormalColor ()); } } diff --git a/UICatalog/Scenarios/CombiningMarks.cs b/UICatalog/Scenarios/CombiningMarks.cs index b61ebb09f1..78455eecaf 100644 --- a/UICatalog/Scenarios/CombiningMarks.cs +++ b/UICatalog/Scenarios/CombiningMarks.cs @@ -15,20 +15,20 @@ public override void Main () top.DrawContentComplete += (s, e) => { - Application.Driver.Move (0, 0); - Application.Driver.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616."); - Application.Driver.Move (0, 2); - Application.Driver.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr."); - Application.Driver.Move (0, 3); - Application.Driver.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr."); - Application.Driver.Move (0, 4); - Application.Driver.AddRune ('['); - Application.Driver.AddRune ('a'); - Application.Driver.AddRune ('\u0301'); - Application.Driver.AddRune ('\u0301'); - Application.Driver.AddRune ('\u0328'); - Application.Driver.AddRune (']'); - Application.Driver.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each."); + Application.Driver?.Move (0, 0); + Application.Driver?.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616."); + Application.Driver?.Move (0, 2); + Application.Driver?.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr."); + Application.Driver?.Move (0, 3); + Application.Driver?.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr."); + Application.Driver?.Move (0, 4); + Application.Driver?.AddRune ('['); + Application.Driver?.AddRune ('a'); + Application.Driver?.AddRune ('\u0301'); + Application.Driver?.AddRune ('\u0301'); + Application.Driver?.AddRune ('\u0328'); + Application.Driver?.AddRune (']'); + Application.Driver?.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each."); }; Application.Run (top); diff --git a/UICatalog/Scenarios/Images.cs b/UICatalog/Scenarios/Images.cs index d31eae9cc7..f17246742a 100644 --- a/UICatalog/Scenarios/Images.cs +++ b/UICatalog/Scenarios/Images.cs @@ -20,9 +20,9 @@ public override void Main () Application.Init (); var win = new Window { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName()}" }; - bool canTrueColor = Application.Driver.SupportsTrueColor; + bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false; - var lblDriverName = new Label { X = 0, Y = 0, Text = $"Driver is {Application.Driver.GetType ().Name}" }; + var lblDriverName = new Label { X = 0, Y = 0, Text = $"Driver is {Application.Driver?.GetType ().Name}" }; win.Add (lblDriverName); var cbSupportsTrueColor = new CheckBox diff --git a/UICatalog/Scenarios/SendKeys.cs b/UICatalog/Scenarios/SendKeys.cs index a27a80232f..6dc4a3bdfc 100644 --- a/UICatalog/Scenarios/SendKeys.cs +++ b/UICatalog/Scenarios/SendKeys.cs @@ -86,7 +86,7 @@ void ProcessInput () ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r; - Application.Driver.SendKeys ( + Application.Driver?.SendKeys ( r, ck, ckbShift.State == CheckState.Checked, diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 17f6a6e5c1..7d5d0e1569 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -260,5 +260,5 @@ private void DrawTopLineGradient (Rectangle viewport) } } - private static void SetColor (Color color) { Application.Driver.SetAttribute (new (color, color)); } + private static void SetColor (Color color) { Application.Driver?.SetAttribute (new (color, color)); } } diff --git a/UICatalog/Scenarios/TrueColors.cs b/UICatalog/Scenarios/TrueColors.cs index 19e00187d8..d08d9685af 100644 --- a/UICatalog/Scenarios/TrueColors.cs +++ b/UICatalog/Scenarios/TrueColors.cs @@ -19,11 +19,11 @@ public override void Main () var x = 2; var y = 1; - bool canTrueColor = Application.Driver.SupportsTrueColor; + bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false; var lblDriverName = new Label { - X = x, Y = y++, Text = $"Current driver is {Application.Driver.GetType ().Name}" + X = x, Y = y++, Text = $"Current driver is {Application.Driver?.GetType ().Name}" }; app.Add (lblDriverName); y++; diff --git a/UICatalog/Scenarios/VkeyPacketSimulator.cs b/UICatalog/Scenarios/VkeyPacketSimulator.cs index 50ce09b71c..975775f454 100644 --- a/UICatalog/Scenarios/VkeyPacketSimulator.cs +++ b/UICatalog/Scenarios/VkeyPacketSimulator.cs @@ -198,7 +198,7 @@ public override void Main () char keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo); - Application.Driver.SendKeys ( + Application.Driver?.SendKeys ( keyChar, ConsoleKey.Packet, consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift), diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index ddb4c656f1..dd4184cb4a 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -369,7 +369,7 @@ private static void VerifyObjectsWereDisposed () /// public class UICatalogTopLevel : Toplevel { - public ListView CategoryList; + public ListView? CategoryList; public MenuItem? MiForce16Colors; public MenuItem? MiIsMenuBorderDisabled; public MenuItem? MiIsMouseDisabled; @@ -999,7 +999,7 @@ private MenuItem [] CreateForce16ColorItems () Title = "Force _16 Colors", Shortcut = (KeyCode)Key.F6, Checked = Application.Force16Colors, - CanExecute = () => Application.Driver.SupportsTrueColor + CanExecute = () => Application.Driver?.SupportsTrueColor ?? false }; MiForce16Colors.CheckType |= MenuItemCheckStyle.Checked; diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index a7c1fc64ab..6363233a98 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -44,7 +44,7 @@ public void Begin_Sets_Application_Top_To_Console_Size () Toplevel top = new (); Application.Begin (top); Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame); - ((FakeDriver)Application.Driver).SetBufferSize (5, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); Assert.Equal (new (0, 0, 5, 5), Application.Top.Frame); top.Dispose (); } @@ -134,7 +134,7 @@ public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType) Application.Init (driverName: driverType.Name); Assert.NotNull (Application.Driver); Assert.NotEqual (driver, Application.Driver); - Assert.Equal (driverType, Application.Driver.GetType ()); + Assert.Equal (driverType, Application.Driver?.GetType ()); Shutdown (); } @@ -565,8 +565,8 @@ private void Post_Init_State () Assert.NotNull (Application.MainLoop); // FakeDriver is always 80x25 - Assert.Equal (80, Application.Driver.Cols); - Assert.Equal (25, Application.Driver.Rows); + Assert.Equal (80, Application.Driver!.Cols); + Assert.Equal (25, Application.Driver!.Rows); } private void Pre_Init_State () @@ -695,7 +695,7 @@ public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow () Application.ForceDriver = "FakeDriver"; Application.Init (); - Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ()); + Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ()); Application.Iteration += (s, a) => { Application.RequestStop (); }; @@ -737,7 +737,7 @@ public void Run_T_NoInit_DoesNotThrow () Application.Iteration += (s, a) => { Application.RequestStop (); }; Application.Run (); - Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ()); + Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ()); Application.Top.Dispose (); Shutdown (); @@ -888,7 +888,7 @@ public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving () Width = 5, Height = 5, Arrangement = ViewArrangement.Movable }; - ((FakeDriver)Application.Driver).SetBufferSize (10, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); RunState rs = Application.Begin (w); // Don't use visuals to test as style of border can change over time. diff --git a/UnitTests/Application/CursorTests.cs b/UnitTests/Application/CursorTests.cs index 337003b0f9..87999a9d2d 100644 --- a/UnitTests/Application/CursorTests.cs +++ b/UnitTests/Application/CursorTests.cs @@ -141,7 +141,10 @@ public void PositionCursor_Defaults_Invisible () Assert.True (view.HasFocus); Assert.False (Application.PositionCursor (view)); - Application.Driver.GetCursorVisibility (out CursorVisibility cursor); - Assert.Equal (CursorVisibility.Invisible, cursor); + + if (Application.Driver?.GetCursorVisibility (out CursorVisibility cursor) ?? false) + { + Assert.Equal (CursorVisibility.Invisible, cursor); + } } } diff --git a/UnitTests/Clipboard/ClipboardTests.cs b/UnitTests/Clipboard/ClipboardTests.cs index 65c2e7707b..e2c0ac11ff 100644 --- a/UnitTests/Clipboard/ClipboardTests.cs +++ b/UnitTests/Clipboard/ClipboardTests.cs @@ -9,14 +9,14 @@ public class ClipboardTests [Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)] public void IClipboard_GetClipBoardData_Throws_NotSupportedException () { - var iclip = Application.Driver.Clipboard; + var iclip = Application.Driver?.Clipboard; Assert.Throws (() => iclip.GetClipboardData ()); } [Fact, AutoInitShutdown (useFakeClipboard: true, fakeClipboardAlwaysThrowsNotSupportedException: true)] public void IClipboard_SetClipBoardData_Throws_NotSupportedException () { - var iclip = Application.Driver.Clipboard; + var iclip = Application.Driver?.Clipboard; Assert.Throws (() => iclip.SetClipboardData ("foo")); } diff --git a/UnitTests/ConsoleDrivers/ClipRegionTests.cs b/UnitTests/ConsoleDrivers/ClipRegionTests.cs index 0d27f91c1e..8a90f2e4de 100644 --- a/UnitTests/ConsoleDrivers/ClipRegionTests.cs +++ b/UnitTests/ConsoleDrivers/ClipRegionTests.cs @@ -26,8 +26,8 @@ public void AddRune_Is_Clipped (Type driverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); - Application.Driver.Rows = 25; - Application.Driver.Cols = 80; + Application.Driver!.Rows = 25; + Application.Driver!.Cols = 80; driver.Move (0, 0); driver.AddRune ('x'); @@ -94,8 +94,8 @@ public void IsValidLocation (Type driverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); Application.Init (driver); - Application.Driver.Rows = 10; - Application.Driver.Cols = 10; + Application.Driver!.Rows = 10; + Application.Driver!.Cols = 10; // positive Assert.True (driver.IsValidLocation (0, 0)); diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs index afbf20d96e..8ecc978076 100644 --- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs @@ -234,7 +234,7 @@ public void TerminalResized_Simulation (Type driverType) // { // var win = new Window (); // Application.Begin (win); - // ((FakeDriver)Application.Driver).SetBufferSize (20, 8); + // ((FakeDriver)Application.Driver!).SetBufferSize (20, 8); // System.Threading.Tasks.Task.Run (() => { // System.Threading.Tasks.Task.Delay (500).Wait (); diff --git a/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs b/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs index de05b868f4..1ea984b3dc 100644 --- a/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs @@ -123,7 +123,7 @@ uint expectedScanCode if (iterations == 0) { var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo); - Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, shift, alt, control); + Application.Driver?.SendKeys (keyChar, ConsoleKey.Packet, shift, alt, control); } }; Application.Run (); diff --git a/UnitTests/Dialogs/MessageBoxTests.cs b/UnitTests/Dialogs/MessageBoxTests.cs index f32f7074ac..8715eea2c3 100644 --- a/UnitTests/Dialogs/MessageBoxTests.cs +++ b/UnitTests/Dialogs/MessageBoxTests.cs @@ -125,7 +125,7 @@ public void KeyBindings_Space_Causes_Focused_Button_Click () public void Location_Default () { int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); Application.Iteration += (s, a) => { @@ -243,7 +243,7 @@ public void Message_Long_Without_Spaces_WrapMessage_True () int iterations = -1; var top = new Toplevel (); top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); var btn = $"{ @@ -319,7 +319,7 @@ public void Message_With_Spaces_WrapMessage_False () int iterations = -1; var top = new Toplevel (); top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); var btn = $"{ @@ -396,7 +396,7 @@ public void Message_With_Spaces_WrapMessage_True () int iterations = -1; var top = new Toplevel(); top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); var btn = $"{ @@ -477,7 +477,7 @@ public void Message_Without_Spaces_WrapMessage_False () int iterations = -1; var top = new Toplevel(); top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); var btn = $"{ @@ -547,7 +547,7 @@ public void Message_Without_Spaces_WrapMessage_False () public void Size_Default () { int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); Application.Iteration += (s, a) => { @@ -650,7 +650,7 @@ public void Size_No_With_Button () CM.Glyphs.RightBracket }"; - ((FakeDriver)Application.Driver).SetBufferSize (40 + 4, 8); + ((FakeDriver)Application.Driver!).SetBufferSize (40 + 4, 8); Application.Iteration += (s, a) => { @@ -737,7 +737,7 @@ public void Size_None_No_Buttons () public void Size_Not_Default_Message (int height, int width, string message) { int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); Application.Iteration += (s, a) => { @@ -774,7 +774,7 @@ public void Size_Not_Default_Message (int height, int width, string message) public void Size_Not_Default_Message_Button (int height, int width, string message) { int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); Application.Iteration += (s, a) => { @@ -807,7 +807,7 @@ public void Size_Not_Default_Message_Button (int height, int width, string messa public void Size_Not_Default_No_Message (int height, int width) { int iterations = -1; - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); Application.Iteration += (s, a) => { diff --git a/UnitTests/Drawing/RulerTests.cs b/UnitTests/Drawing/RulerTests.cs index 0fdbfd7e28..d43ea327f5 100644 --- a/UnitTests/Drawing/RulerTests.cs +++ b/UnitTests/Drawing/RulerTests.cs @@ -29,7 +29,7 @@ public void Constructor_Defaults () [AutoInitShutdown] public void Draw_Default () { - ((FakeDriver)Application.Driver).SetBufferSize (25, 25); + ((FakeDriver)Application.Driver!).SetBufferSize (25, 25); var r = new Ruler (); r.Draw (Point.Empty); @@ -47,7 +47,7 @@ public void Draw_Horizontal () var top = new Toplevel (); top.Add (f); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (len + 5, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (len + 5, 5); Assert.Equal (new (0, 0, len + 5, 5), f.Frame); var r = new Ruler (); @@ -121,7 +121,7 @@ public void Draw_Horizontal_Start () var top = new Toplevel (); top.Add (f); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (len + 5, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (len + 5, 5); Assert.Equal (new (0, 0, len + 5, 5), f.Frame); var r = new Ruler (); @@ -168,7 +168,7 @@ public void Draw_Vertical () var top = new Toplevel (); top.Add (f); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (5, len + 5); + ((FakeDriver)Application.Driver!).SetBufferSize (5, len + 5); Assert.Equal (new (0, 0, 5, len + 5), f.Frame); var r = new Ruler (); @@ -302,7 +302,7 @@ public void Draw_Vertical_Start () var top = new Toplevel (); top.Add (f); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (5, len + 5); + ((FakeDriver)Application.Driver!).SetBufferSize (5, len + 5); Assert.Equal (new (0, 0, 5, len + 5), f.Frame); var r = new Ruler (); diff --git a/UnitTests/Drawing/ThicknessTests.cs b/UnitTests/Drawing/ThicknessTests.cs index 8215f8148d..c711357224 100644 --- a/UnitTests/Drawing/ThicknessTests.cs +++ b/UnitTests/Drawing/ThicknessTests.cs @@ -51,13 +51,13 @@ public void Constructor_Width () [AutoInitShutdown] public void DrawTests () { - ((FakeDriver)Application.Driver).SetBufferSize (60, 60); + ((FakeDriver)Application.Driver!).SetBufferSize (60, 60); var t = new Thickness (0, 0, 0, 0); var r = new Rectangle (5, 5, 40, 15); View.Diagnostics |= ViewDiagnosticFlags.Padding; - Application.Driver.FillRect ( - new Rectangle (0, 0, Application.Driver.Cols, Application.Driver.Rows), + Application.Driver?.FillRect ( + new Rectangle (0, 0, Application.Driver!.Cols, Application.Driver!.Rows), (Rune)' ' ); t.Draw (r, "Test"); @@ -73,8 +73,8 @@ public void DrawTests () r = new Rectangle (5, 5, 40, 15); View.Diagnostics |= ViewDiagnosticFlags.Padding; - Application.Driver.FillRect ( - new Rectangle (0, 0, Application.Driver.Cols, Application.Driver.Rows), + Application.Driver?.FillRect ( + new Rectangle (0, 0, Application.Driver!.Cols, Application.Driver!.Rows), (Rune)' ' ); t.Draw (r, "Test"); @@ -104,8 +104,8 @@ T T r = new Rectangle (5, 5, 40, 15); View.Diagnostics |= ViewDiagnosticFlags.Padding; - Application.Driver.FillRect ( - new Rectangle (0, 0, Application.Driver.Cols, Application.Driver.Rows), + Application.Driver?.FillRect ( + new Rectangle (0, 0, Application.Driver!.Cols, Application.Driver!.Rows), (Rune)' ' ); t.Draw (r, "Test"); @@ -135,8 +135,8 @@ T TTT r = new Rectangle (5, 5, 40, 15); View.Diagnostics |= ViewDiagnosticFlags.Padding; - Application.Driver.FillRect ( - new Rectangle (0, 0, Application.Driver.Cols, Application.Driver.Rows), + Application.Driver?.FillRect ( + new Rectangle (0, 0, Application.Driver!.Cols, Application.Driver!.Rows), (Rune)' ' ); t.Draw (r, "Test"); @@ -174,7 +174,7 @@ public void DrawTests_Ruler () top.Add (f); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (45, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (45, 20); var t = new Thickness (0, 0, 0, 0); var r = new Rectangle (2, 2, 40, 15); Application.Refresh (); diff --git a/UnitTests/FileServices/FileDialogTests.cs b/UnitTests/FileServices/FileDialogTests.cs index 1395543eeb..1488a75f04 100644 --- a/UnitTests/FileServices/FileDialogTests.cs +++ b/UnitTests/FileServices/FileDialogTests.cs @@ -701,14 +701,14 @@ public void Autocomplete_AcceptSuggstion () private void Send (char ch, ConsoleKey ck, bool shift = false, bool alt = false, bool control = false) { - Application.Driver.SendKeys (ch, ck, shift, alt, control); + Application.Driver?.SendKeys (ch, ck, shift, alt, control); } private void Send (string chars) { foreach (char ch in chars) { - Application.Driver.SendKeys (ch, ConsoleKey.NoName, false, false, false); + Application.Driver?.SendKeys (ch, ConsoleKey.NoName, false, false, false); } } diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 6746f8ad71..f821d504b6 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -451,7 +451,7 @@ public void Draw_With_Combining_Runes (int width, int height, TextDirection text [SetupFakeDriver] public void FillRemaining_True_False () { - ((FakeDriver)Application.Driver).SetBufferSize (22, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (22, 5); Attribute [] attrs = { @@ -6041,7 +6041,7 @@ public void Draw_Text_Justification (string text, Alignment horizontalTextAlignm Text = text }; - Application.Driver.FillRect (new Rectangle (0, 0, 7, 7), (Rune)'*'); + Application.Driver?.FillRect (new Rectangle (0, 0, 7, 7), (Rune)'*'); tf.Draw (new Rectangle (0, 0, 7, 7), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } diff --git a/UnitTests/View/Adornment/BorderTests.cs b/UnitTests/View/Adornment/BorderTests.cs index 387844dbe6..cae90f7082 100644 --- a/UnitTests/View/Adornment/BorderTests.cs +++ b/UnitTests/View/Adornment/BorderTests.cs @@ -95,7 +95,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Four_Size_Width (int w RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (width, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (width, 5); Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; @@ -229,7 +229,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Three_Size_Width (int RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (width, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (width, 4); Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; @@ -363,7 +363,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Two_Size_Width (int wi RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (width, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (width, 4); Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; @@ -486,7 +486,7 @@ public void Border_With_Title_Size_Height (int height) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (20, height); + ((FakeDriver)Application.Driver!).SetBufferSize (20, height); Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; @@ -548,7 +548,7 @@ public void Border_With_Title_Size_Width (int width) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (width, 3); + ((FakeDriver)Application.Driver!).SetBufferSize (width, 3); Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; @@ -728,7 +728,7 @@ public void HasSuperView () RunState rs = Application.Begin (top); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (5, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); Application.RunIteration (ref rs, ref firstIteration); var expected = @" @@ -756,7 +756,7 @@ public void HasSuperView_Title () RunState rs = Application.Begin (top); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); Application.RunIteration (ref rs, ref firstIteration); var expected = @" @@ -779,7 +779,7 @@ public void NoSuperView () RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (3, 3); + ((FakeDriver)Application.Driver!).SetBufferSize (3, 3); Application.RunIteration (ref rs, ref firstIteration); var expected = @" diff --git a/UnitTests/View/Adornment/MarginTests.cs b/UnitTests/View/Adornment/MarginTests.cs index 1cfe6f0d10..736a720b17 100644 --- a/UnitTests/View/Adornment/MarginTests.cs +++ b/UnitTests/View/Adornment/MarginTests.cs @@ -8,7 +8,7 @@ public class MarginTests (ITestOutputHelper output) [SetupFakeDriver] public void Margin_Uses_SuperView_ColorScheme () { - ((FakeDriver)Application.Driver).SetBufferSize (5, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Margin.Thickness = new (1); diff --git a/UnitTests/View/Adornment/PaddingTests.cs b/UnitTests/View/Adornment/PaddingTests.cs index 4f7bffb208..2c917572ff 100644 --- a/UnitTests/View/Adornment/PaddingTests.cs +++ b/UnitTests/View/Adornment/PaddingTests.cs @@ -8,7 +8,7 @@ public class PaddingTests (ITestOutputHelper output) [SetupFakeDriver] public void Padding_Uses_Parent_ColorScheme () { - ((FakeDriver)Application.Driver).SetBufferSize (5, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Padding.Thickness = new (1); diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 46c791ab46..52b4659f83 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -22,13 +22,13 @@ public void Move_Is_Constrained_To_Viewport () // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen) view.Move (0, 0); - Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row)); + Assert.Equal (new Point (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); view.Move (-1, -1); - Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row)); + Assert.Equal (new Point (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); view.Move (1, 1); - Assert.Equal (new Point (2, 2), new Point (Application.Driver.Col, Application.Driver.Row)); + Assert.Equal (new Point (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row)); } [Fact] @@ -48,16 +48,16 @@ public void AddRune_Is_Constrained_To_Viewport () view.Draw (); // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen) - Assert.Equal ((Rune)' ', Application.Driver.Contents [2, 2].Rune); + Assert.Equal ((Rune)' ', Application.Driver?.Contents [2, 2].Rune); view.AddRune (0, 0, Rune.ReplacementChar); - Assert.Equal (Rune.ReplacementChar, Application.Driver.Contents [2, 2].Rune); + Assert.Equal (Rune.ReplacementChar, Application.Driver?.Contents [2, 2].Rune); view.AddRune (-1, -1, Rune.ReplacementChar); - Assert.Equal ((Rune)'M', Application.Driver.Contents [1, 1].Rune); + Assert.Equal ((Rune)'M', Application.Driver?.Contents [1, 1].Rune); view.AddRune (1, 1, Rune.ReplacementChar); - Assert.Equal ((Rune)'M', Application.Driver.Contents [3, 3].Rune); + Assert.Equal ((Rune)'M', Application.Driver?.Contents [3, 3].Rune); View.Diagnostics = ViewDiagnosticFlags.Off; } @@ -250,7 +250,7 @@ public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); const string expectedOutput = """ @@ -301,7 +301,7 @@ public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_Wi dg.Add (view); RunState rsTop = Application.Begin (top); RunState rsDiag = Application.Begin (dg); - ((FakeDriver)Application.Driver).SetBufferSize (30, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 10); const string expectedOutput = """ @@ -354,7 +354,7 @@ public void Colors_On_TextAlignment_Right_And_Bottom () top.Add (viewRight, viewBottom); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (7, 7); + ((FakeDriver)Application.Driver!).SetBufferSize (7, 7); TestHelpers.AssertDriverContentsWithFrameAre ( """ @@ -394,7 +394,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport () var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Driver!.Screen.Size); Assert.Equal (new (0, 0, 2, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -419,7 +419,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom () view.Border.Thickness = new Thickness (1, 1, 1, 0); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Driver!.Screen.Size); Assert.Equal (new (0, 0, 2, 1), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -437,7 +437,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left () view.Border.Thickness = new Thickness (0, 1, 1, 1); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Driver!.Screen.Size); Assert.Equal (new (0, 0, 1, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -462,7 +462,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right () view.Border.Thickness = new Thickness (1, 1, 0, 1); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Driver!.Screen.Size); Assert.Equal (new (0, 0, 1, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -488,7 +488,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top () view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application.Driver.Screen.Size); + view.SetRelativeLayout (Application.Driver!.Screen.Size); Assert.Equal (new (0, 0, 2, 1), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -561,7 +561,7 @@ public void Draw_Negative_Viewport_Horizontal_With_New_Lines () container.Add (content); Toplevel top = new (); top.Add (container); - Application.Driver.Clip = container.Frame; + Application.Driver!.Clip = container.Frame; Application.Begin (top); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -727,7 +727,7 @@ public void Draw_Negative_Viewport_Horizontal_Without_New_Lines () return; - void Top_LayoutComplete (object? sender, LayoutEventArgs e) { Application.Driver.Clip = container.Frame; } + void Top_LayoutComplete (object? sender, LayoutEventArgs e) { Application.Driver!.Clip = container.Frame; } } [Fact] @@ -767,7 +767,7 @@ public void Draw_Negative_Viewport_Vertical () container.Add (content); Toplevel top = new (); top.Add (container); - Application.Driver.Clip = container.Frame; + Application.Driver!.Clip = container.Frame; Application.Begin (top); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -889,7 +889,7 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); var expected = """ @@ -928,13 +928,13 @@ public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped () view.Border.Thickness = new Thickness (1); view.BeginInit (); view.EndInit (); - Assert.Equal (view.Frame, Application.Driver.Clip); + Assert.Equal (view.Frame, Application.Driver?.Clip); // Act view.SetClip (); // Assert - Assert.Equal (expectedClip, Application.Driver.Clip); + Assert.Equal (expectedClip, Application.Driver?.Clip); view.Dispose (); } @@ -960,14 +960,14 @@ public void SetClip_Default_ClipsToViewport () view.Border.Thickness = new Thickness (1); view.BeginInit (); view.EndInit (); - Assert.Equal (view.Frame, Application.Driver.Clip); + Assert.Equal (view.Frame, Application.Driver?.Clip); view.Viewport = view.Viewport with { X = 1, Y = 1 }; // Act view.SetClip (); // Assert - Assert.Equal (expectedClip, Application.Driver.Clip); + Assert.Equal (expectedClip, Application.Driver?.Clip); view.Dispose (); } diff --git a/UnitTests/View/Layout/Dim.FillTests.cs b/UnitTests/View/Layout/Dim.FillTests.cs index c4b4ebacfe..cdda3088d5 100644 --- a/UnitTests/View/Layout/Dim.FillTests.cs +++ b/UnitTests/View/Layout/Dim.FillTests.cs @@ -14,7 +14,7 @@ public void DimFill_SizedCorrectly () var top = new Toplevel (); top.Add (view); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (32, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (32, 5); //view.SetNeedsLayout (); top.LayoutSubviews (); diff --git a/UnitTests/View/Layout/Pos.AnchorEndTests.cs b/UnitTests/View/Layout/Pos.AnchorEndTests.cs index 4309ee858c..ddd3c62af5 100644 --- a/UnitTests/View/Layout/Pos.AnchorEndTests.cs +++ b/UnitTests/View/Layout/Pos.AnchorEndTests.cs @@ -184,7 +184,7 @@ public void PosAnchorEnd_UseDimForOffset_DimPercent_PositionsViewOffsetByDim (i [SetupFakeDriver] public void PosAnchorEnd_View_And_Button () { - ((FakeDriver)Application.Driver).SetBufferSize (20, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 5); var b = $"{CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}"; diff --git a/UnitTests/View/Layout/Pos.CenterTests.cs b/UnitTests/View/Layout/Pos.CenterTests.cs index a17b1132a5..e713c07b63 100644 --- a/UnitTests/View/Layout/Pos.CenterTests.cs +++ b/UnitTests/View/Layout/Pos.CenterTests.cs @@ -85,7 +85,7 @@ public void PosCenter_SubView_85_Percent_Height (int height) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (20, height); + ((FakeDriver)Application.Driver!).SetBufferSize (20, height); Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; @@ -232,7 +232,7 @@ public void PosCenter_SubView_85_Percent_Width (int width) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver).SetBufferSize (width, 7); + ((FakeDriver)Application.Driver!).SetBufferSize (width, 7); Application.RunIteration (ref rs, ref firstIteration); var expected = string.Empty; diff --git a/UnitTests/View/Layout/ViewportTests.cs b/UnitTests/View/Layout/ViewportTests.cs index f5bc9212d7..f0d30f1186 100644 --- a/UnitTests/View/Layout/ViewportTests.cs +++ b/UnitTests/View/Layout/ViewportTests.cs @@ -472,7 +472,7 @@ public void ContentSize_Ignores_ViewportSize_If_ContentSizeTracksViewport_Is_Fal //[InlineData (5, 5, false)] //public void IsVisibleInSuperView_With_Driver (int x, int y, bool expected) //{ - // ((FakeDriver)Application.Driver).SetBufferSize (10, 10); + // ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); // var view = new View { X = 1, Y = 1, Width = 5, Height = 5 }; // var top = new Toplevel (); diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/NavigationTests.cs index 05dc30a1f9..5a7019c191 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/NavigationTests.cs @@ -669,7 +669,7 @@ public void FocusNext_Does_Not_Throws_If_A_View_Was_Removed_From_The_Collection // Assert.False (tfQuiting); // Assert.False (topQuiting); -// Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); +// Application.Driver?.SendKeys ('Q', ConsoleKey.Q, false, false, true); // Assert.False (sbQuiting); // Assert.True (tfQuiting); // Assert.False (topQuiting); @@ -677,7 +677,7 @@ public void FocusNext_Does_Not_Throws_If_A_View_Was_Removed_From_The_Collection //#if BROKE_WITH_2927 // tf.KeyPressed -= Tf_KeyPress; // tfQuiting = false; -// Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); +// Application.Driver?.SendKeys ('q', ConsoleKey.Q, false, false, true); // Application.MainLoop.RunIteration (); // Assert.True (sbQuiting); // Assert.False (tfQuiting); @@ -685,7 +685,7 @@ public void FocusNext_Does_Not_Throws_If_A_View_Was_Removed_From_The_Collection // sb.RemoveItem (0); // sbQuiting = false; -// Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); +// Application.Driver?.SendKeys ('q', ConsoleKey.Q, false, false, true); // Application.MainLoop.RunIteration (); // Assert.False (sbQuiting); // Assert.False (tfQuiting); @@ -733,13 +733,13 @@ public void FocusNext_Does_Not_Throws_If_A_View_Was_Removed_From_The_Collection // Assert.False (sbQuiting); // Assert.False (tfQuiting); -// Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); +// Application.Driver?.SendKeys ('Q', ConsoleKey.Q, false, false, true); // Assert.False (sbQuiting); // Assert.True (tfQuiting); // tf.KeyDown -= Tf_KeyPressed; // tfQuiting = false; -// Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); +// Application.Driver?.SendKeys ('Q', ConsoleKey.Q, false, false, true); // Application.MainLoop.RunIteration (); //#if BROKE_WITH_2927 // Assert.True (sbQuiting); @@ -834,7 +834,7 @@ public void ScreenToView_ViewToScreen_FindDeepestView_Full_Top () Assert.Equal (new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows), top.Frame); Assert.Equal (new Rectangle (0, 0, 80, 25), top.Frame); - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); Assert.Equal (new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows), top.Frame); Assert.Equal (new Rectangle (0, 0, 20, 10), top.Frame); @@ -984,7 +984,7 @@ public void ScreenToView_ViewToScreen_FindDeepestView_Smaller_Top () Assert.NotEqual (new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows), top.Frame); Assert.Equal (new Rectangle (3, 2, 20, 10), top.Frame); - ((FakeDriver)Application.Driver).SetBufferSize (30, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 20); Assert.Equal (new Rectangle (0, 0, 30, 20), new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows)); Assert.NotEqual (new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows), top.Frame); Assert.Equal (new Rectangle (3, 2, 20, 10), top.Frame); diff --git a/UnitTests/View/TextTests.cs b/UnitTests/View/TextTests.cs index 3bd6cfb230..f0cb43091b 100644 --- a/UnitTests/View/TextTests.cs +++ b/UnitTests/View/TextTests.cs @@ -148,7 +148,7 @@ public void TextDirection_Toggle () top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (15, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); Assert.Equal (new (0, 0, 15, 15), win.Frame); Assert.Equal (new (0, 0, 15, 15), win.Margin.Frame); @@ -416,7 +416,7 @@ public void AutoSize_True_View_IsEmpty_False_Minimum_Width () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (4, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (4, 10); Assert.Equal (5, text.Length); @@ -489,7 +489,7 @@ public void AutoSize_True_View_IsEmpty_False_Minimum_Width_Wide_Rune () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (4, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (4, 10); Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 2, 5), view.Frame); @@ -584,7 +584,7 @@ public void AutoSize_True_Width_Height_SetMinWidthHeight_Narrow_Wide_Runes () var top = new Toplevel (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 20); Assert.Equal (new (0, 0, 11, 2), horizontalView.Frame); Assert.Equal (new (0, 3, 2, 11), verticalView.Frame); @@ -672,7 +672,7 @@ public void AutoSize_True_Width_Height_Stay_True_If_TextFormatter_Size_Fit () var top = new Toplevel (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (22, 22); + ((FakeDriver)Application.Driver!).SetBufferSize (22, 22); Assert.Equal (new (text.GetColumns (), 1), horizontalView.TextFormatter.Size); Assert.Equal (new (2, 8), verticalView.TextFormatter.Size); @@ -769,7 +769,7 @@ string GetContents () for (var i = 0; i < 4; i++) { - text += Application.Driver.Contents [0, i].Rune; + text += Application.Driver?.Contents [0, i].Rune; } return text; @@ -804,7 +804,7 @@ public void GetTextFormatterBoundsSize_GetSizeNeededForText_HotKeySpecifier () var top = new Toplevel (); top.Add (horizontalView, verticalView); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (50, 50); + ((FakeDriver)Application.Driver!).SetBufferSize (50, 50); Assert.Equal (new (0, 0, 12, 1), horizontalView.Frame); Assert.Equal (new (12, 1), horizontalView.GetSizeNeededForTextWithoutHotKey ()); @@ -900,7 +900,7 @@ public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize) var top = new Toplevel (); top.Add (frame); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6); + ((FakeDriver)Application.Driver!).SetBufferSize (width + 2, 6); if (autoSize) { @@ -1028,7 +1028,7 @@ public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) var top = new Toplevel (); top.Add (frame); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (9, height + 2); + ((FakeDriver)Application.Driver!).SetBufferSize (9, height + 2); if (autoSize) { @@ -1272,7 +1272,7 @@ public void TextDirection_Vertical_Dims_Correct () [SetupFakeDriver] public void Narrow_Wide_Runes () { - ((FakeDriver)Application.Driver).SetBufferSize (32, 32); + ((FakeDriver)Application.Driver!).SetBufferSize (32, 32); var top = new View { Width = 32, Height = 32 }; var text = $"First line{Environment.NewLine}Second line"; diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 4044507f1a..d545cdc1dd 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -14,26 +14,26 @@ public void Clear_Viewport_Can_Use_Driver_AddRune_Or_AddStr_Methods () view.DrawContent += (s, e) => { - Rectangle savedClip = Application.Driver.Clip; - Application.Driver.Clip = new (1, 1, view.Viewport.Width, view.Viewport.Height); + Rectangle savedClip = Application.Driver!.Clip; + Application.Driver!.Clip = new (1, 1, view.Viewport.Width, view.Viewport.Height); for (var row = 0; row < view.Viewport.Height; row++) { - Application.Driver.Move (1, row + 1); + Application.Driver?.Move (1, row + 1); for (var col = 0; col < view.Viewport.Width; col++) { - Application.Driver.AddStr ($"{col}"); + Application.Driver?.AddStr ($"{col}"); } } - Application.Driver.Clip = savedClip; + Application.Driver!.Clip = savedClip; e.Cancel = true; }; var top = new Toplevel (); top.Add (view); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); var expected = @" ┌──────────────────┐ @@ -78,26 +78,26 @@ public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods () view.DrawContent += (s, e) => { - Rectangle savedClip = Application.Driver.Clip; - Application.Driver.Clip = new (1, 1, view.Viewport.Width, view.Viewport.Height); + Rectangle savedClip = Application.Driver!.Clip; + Application.Driver!.Clip = new (1, 1, view.Viewport.Width, view.Viewport.Height); for (var row = 0; row < view.Viewport.Height; row++) { - Application.Driver.Move (1, row + 1); + Application.Driver?.Move (1, row + 1); for (var col = 0; col < view.Viewport.Width; col++) { - Application.Driver.AddStr ($"{col}"); + Application.Driver?.AddStr ($"{col}"); } } - Application.Driver.Clip = savedClip; + Application.Driver!.Clip = savedClip; e.Cancel = true; }; var top = new Toplevel (); top.Add (view); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); var expected = @" ┌──────────────────┐ @@ -1016,7 +1016,7 @@ public void Visible_Clear_The_View_Output () view.Height = Dim.Auto (); Assert.Equal ("Testing visibility.".Length, view.Frame.Width); Assert.True (view.Visible); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -1107,9 +1107,9 @@ int RunesCount () Cell [,] contents = ((FakeDriver)Application.Driver).Contents; var runesCount = 0; - for (var i = 0; i < Application.Driver.Rows; i++) + for (var i = 0; i < Application.Driver!.Rows; i++) { - for (var j = 0; j < Application.Driver.Cols; j++) + for (var j = 0; j < Application.Driver!.Cols; j++) { if (contents [i, j].Rune != (Rune)' ') { diff --git a/UnitTests/Views/AppendAutocompleteTests.cs b/UnitTests/Views/AppendAutocompleteTests.cs index fab9ca7509..eaabc43a69 100644 --- a/UnitTests/Views/AppendAutocompleteTests.cs +++ b/UnitTests/Views/AppendAutocompleteTests.cs @@ -11,14 +11,14 @@ public void TestAutoAppend_AfterCloseKey_NoAutocomplete () TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" - Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false); + Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // When cancelling autocomplete - Application.Driver.SendKeys ('e', ConsoleKey.Escape, false, false, false); + Application.Driver?.SendKeys ('e', ConsoleKey.Escape, false, false, false); // Suggestion should disappear tf.Draw (); @@ -29,7 +29,7 @@ public void TestAutoAppend_AfterCloseKey_NoAutocomplete () Assert.Same (tf, Application.Top.Focused); // But can tab away - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); Assert.NotSame (tf, Application.Top.Focused); Application.Top.Dispose (); } @@ -41,14 +41,14 @@ public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter () TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" - Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false); + Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // When cancelling autocomplete - Application.Driver.SendKeys ('\0', ConsoleKey.Escape, false, false, false); + Application.Driver?.SendKeys ('\0', ConsoleKey.Escape, false, false, false); // Suggestion should disappear tf.Draw (); @@ -56,7 +56,7 @@ public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter () Assert.Equal ("f", tf.Text); // Should reappear when you press next letter - Application.Driver.SendKeys ('i', ConsoleKey.I, false, false, false); + Application.Driver?.SendKeys ('i', ConsoleKey.I, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); @@ -73,14 +73,14 @@ public void TestAutoAppend_CycleSelections (ConsoleKey cycleKey) TextField tf = GetTextFieldsInViewSuggesting ("fish", "friend"); // f is typed and suggestion is "fish" - Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false); + Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // When cycling autocomplete - Application.Driver.SendKeys (' ', cycleKey, false, false, false); + Application.Driver?.SendKeys (' ', cycleKey, false, false, false); tf.Draw (); tf.PositionCursor (); @@ -88,7 +88,7 @@ public void TestAutoAppend_CycleSelections (ConsoleKey cycleKey) Assert.Equal ("f", tf.Text); // Should be able to cycle in circles endlessly - Application.Driver.SendKeys (' ', cycleKey, false, false, false); + Application.Driver?.SendKeys (' ', cycleKey, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); @@ -103,15 +103,15 @@ public void TestAutoAppend_NoRender_WhenCursorNotAtEnd () TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" - Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false); + Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // add a space then go back 1 - Application.Driver.SendKeys (' ', ConsoleKey.Spacebar, false, false, false); - Application.Driver.SendKeys ('<', ConsoleKey.LeftArrow, false, false, false); + Application.Driver?.SendKeys (' ', ConsoleKey.Spacebar, false, false, false); + Application.Driver?.SendKeys ('<', ConsoleKey.LeftArrow, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("f", output); @@ -126,14 +126,14 @@ public void TestAutoAppend_NoRender_WhenNoMatch () TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" - Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false); + Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // x is typed and suggestion should disappear - Application.Driver.SendKeys ('x', ConsoleKey.X, false, false, false); + Application.Driver?.SendKeys ('x', ConsoleKey.X, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("fx", output); Assert.Equal ("fx", tf.Text); @@ -166,7 +166,7 @@ public void TestAutoAppend_ShowThenAccept_CasesDiffer () Assert.Equal ("my f", tf.Text); // When tab completing the case of the whole suggestion should be applied - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("my FISH", output); Assert.Equal ("my FISH", tf.Text); @@ -194,7 +194,7 @@ public void TestAutoAppend_ShowThenAccept_MatchCase () TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("fish", output); @@ -204,7 +204,7 @@ public void TestAutoAppend_ShowThenAccept_MatchCase () Assert.Same (tf, Application.Top.Focused); // Second tab should move focus (nothing to autocomplete) - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); Assert.NotSame (tf, Application.Top.Focused); Application.Top.Dispose (); } @@ -219,7 +219,7 @@ public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, s TextField tf = GetTextFieldsInViewSuggesting (overspillUsing); // f is typed we should only see 'f' up to size of View (10) - Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false); + Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre (expectRender, output); diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 4ef86d3875..d5bc04e07a 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -224,7 +224,7 @@ public void Constructors_Defaults () Assert.Equal ('_', btn.HotKeySpecifier.Value); Assert.True (btn.CanFocus); - Application.Driver.ClearContents (); + Application.Driver?.ClearContents (); btn.Draw (); expected = @$" @@ -563,7 +563,7 @@ public void Update_Only_On_Or_After_Initialize () Assert.False (btn.IsInitialized); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.True (btn.IsInitialized); Assert.Equal ("Say Hello 你", btn.Text); @@ -597,7 +597,7 @@ public void Update_Parameterless_Only_On_Or_After_Initialize () Assert.False (btn.IsInitialized); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.True (btn.IsInitialized); Assert.Equal ("Say Hello 你", btn.Text); diff --git a/UnitTests/Views/CheckBoxTests.cs b/UnitTests/Views/CheckBoxTests.cs index c7739272df..da41ec55aa 100644 --- a/UnitTests/Views/CheckBoxTests.cs +++ b/UnitTests/Views/CheckBoxTests.cs @@ -254,7 +254,7 @@ public void TextAlignment_Centered () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.Equal (Alignment.Center, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); @@ -314,7 +314,7 @@ public void TextAlignment_Justified () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 6); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 6); Assert.Equal (Alignment.Fill, checkBox1.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox1.Frame); @@ -372,7 +372,7 @@ public void TextAlignment_Left () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.Equal (Alignment.Start, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); @@ -423,7 +423,7 @@ public void TextAlignment_Right () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.Equal (Alignment.End, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index 6d42add384..efeb709fb0 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/UnitTests/Views/ContextMenuTests.cs @@ -117,9 +117,9 @@ public void ContextMenu_Is_Closed_If_Another_MenuBar_Is_Open_Or_Vice_Versa () [AutoInitShutdown] public void Draw_A_ContextMenu_Over_A_Borderless_Top () { - ((FakeDriver)Application.Driver).SetBufferSize (20, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); - Assert.Equal (new Rectangle (0, 0, 20, 15), Application.Driver.Clip); + Assert.Equal (new Rectangle (0, 0, 20, 15), Application.Driver?.Clip); TestHelpers.AssertDriverContentsWithFrameAre ("", output); var top = new Toplevel { X = 2, Y = 2, Width = 15, Height = 4 }; @@ -167,7 +167,7 @@ public void Draw_A_ContextMenu_Over_A_Dialog () var win = new Window (); top.Add (win); RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); Assert.Equal (new Rectangle (0, 0, 20, 15), win.Frame); @@ -252,9 +252,9 @@ public void Draw_A_ContextMenu_Over_A_Dialog () [AutoInitShutdown] public void Draw_A_ContextMenu_Over_A_Top_Dialog () { - ((FakeDriver)Application.Driver).SetBufferSize (20, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); - Assert.Equal (new Rectangle (0, 0, 20, 15), Application.Driver.Clip); + Assert.Equal (new Rectangle (0, 0, 20, 15), Application.Driver?.Clip); TestHelpers.AssertDriverContentsWithFrameAre ("", output); // Don't use Dialog here as it has more layout logic. Use Window instead. @@ -542,7 +542,7 @@ top.Subviews [0] output ); - ((FakeDriver)Application.Driver).SetBufferSize (40, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 20); cm.Position = new Point (41, -2); cm.Show (); Application.Refresh (); @@ -677,7 +677,7 @@ top.Subviews [0] output ); - ((FakeDriver)Application.Driver).SetBufferSize (18, 8); + ((FakeDriver)Application.Driver!).SetBufferSize (18, 8); cm.Position = new Point (19, 10); cm.Show (); Application.Refresh (); @@ -891,7 +891,7 @@ public void RequestStop_While_ContextMenu_Is_Open_Does_Not_Throws () [AutoInitShutdown] public void Show_Display_At_Zero_If_The_Toplevel_Height_Is_Less_Than_The_Menu_Height () { - ((FakeDriver)Application.Driver).SetBufferSize (80, 3); + ((FakeDriver)Application.Driver!).SetBufferSize (80, 3); var cm = new ContextMenu { @@ -929,7 +929,7 @@ public void Show_Display_At_Zero_If_The_Toplevel_Height_Is_Less_Than_The_Menu_He [AutoInitShutdown] public void Show_Display_At_Zero_If_The_Toplevel_Width_Is_Less_Than_The_Menu_Width () { - ((FakeDriver)Application.Driver).SetBufferSize (5, 25); + ((FakeDriver)Application.Driver!).SetBufferSize (5, 25); var cm = new ContextMenu { diff --git a/UnitTests/Views/FrameViewTests.cs b/UnitTests/Views/FrameViewTests.cs index 056b457471..88a5c786cb 100644 --- a/UnitTests/Views/FrameViewTests.cs +++ b/UnitTests/Views/FrameViewTests.cs @@ -37,7 +37,7 @@ public void Constructors_Defaults () [AutoInitShutdown] public void Draw_Defaults () { - ((FakeDriver)Application.Driver).SetBufferSize (10, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); var fv = new FrameView (); Assert.Equal (string.Empty, fv.Title); Assert.Equal (string.Empty, fv.Text); diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index 24a19c77dc..78ed678a87 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -97,7 +97,7 @@ public void AutoSize_Stays_True_AnchorEnd () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); var expected = @" ┌────────────────────────────┐ @@ -137,7 +137,7 @@ public void AutoSize_Stays_True_Center () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); var expected = @" ┌────────────────────────────┐ @@ -179,7 +179,7 @@ public void AutoSize_Stays_True_With_EmptyText () label.Text = "Say Hello 你"; Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); var expected = @" ┌────────────────────────────┐ @@ -414,7 +414,7 @@ public void Update_Only_On_Or_After_Initialize () Assert.False (label.IsInitialized); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.True (label.IsInitialized); Assert.Equal ("Say Hello 你", label.Text); @@ -446,7 +446,7 @@ public void Update_Parameterless_Only_On_Or_After_Initialize () Assert.False (label.IsInitialized); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.True (label.IsInitialized); Assert.Equal ("Say Hello 你", label.Text); @@ -473,7 +473,7 @@ public void Full_Border () var label = new Label { BorderStyle = LineStyle.Single, Text = "Test" }; label.BeginInit (); label.EndInit (); - label.SetRelativeLayout (Application.Driver.Screen.Size); + label.SetRelativeLayout (Application.Driver!.Screen.Size); Assert.Equal (new (0, 0, 4, 1), label.Viewport); Assert.Equal (new (0, 0, 6, 3), label.Frame); @@ -881,7 +881,7 @@ public void AnchorEnd_Better_Than_Bottom_Equal_Inside_Window () Toplevel top = new (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (40, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 10); Assert.Equal (29, label.Text.Length); Assert.Equal (new (0, 0, 40, 10), top.Frame); @@ -931,7 +931,7 @@ public void Bottom_Equal_Inside_Window () Toplevel top = new (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (40, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 10); Assert.Equal (new (0, 0, 40, 10), top.Frame); Assert.Equal (new (0, 0, 40, 10), win.Frame); @@ -1071,7 +1071,7 @@ public void Dim_Subtract_Operator_With_Text () { if (k.KeyCode == KeyCode.Enter) { - ((FakeDriver)Application.Driver).SetBufferSize (22, count + 4); + ((FakeDriver)Application.Driver!).SetBufferSize (22, count + 4); Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expecteds [count], output); Assert.Equal (new (0, 0, 22, count + 4), pos); @@ -1135,7 +1135,7 @@ public void Dim_Subtract_Operator_With_Text () [SetupFakeDriver] public void Label_Height_Zero_Stays_Zero () { - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); var text = "Label"; var label = new Label @@ -1223,7 +1223,7 @@ public void Dim_Add_Operator_With_Text () { if (k.KeyCode == KeyCode.Enter) { - ((FakeDriver)Application.Driver).SetBufferSize (22, count + 4); + ((FakeDriver)Application.Driver!).SetBufferSize (22, count + 4); Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expecteds [count], output); Assert.Equal (new (0, 0, 22, count + 4), pos); @@ -1299,7 +1299,7 @@ public void Label_IsEmpty_False_Minimum_Height () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 5, 1), label.Frame); @@ -1358,7 +1358,7 @@ public void Label_IsEmpty_False_Never_Return_Null_Lines () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (10, 4); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 5, 1), label.Frame); diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs index 2682fb6771..049c5f7e1f 100644 --- a/UnitTests/Views/ListViewTests.cs +++ b/UnitTests/Views/ListViewTests.cs @@ -55,7 +55,7 @@ public void Ensures_Visibility_SelectedItem_On_MoveDown_And_MoveUp () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (12, 12); + ((FakeDriver)Application.Driver!).SetBufferSize (12, 12); Application.Refresh (); Assert.Equal (-1, lv.SelectedItem); @@ -357,7 +357,7 @@ string GetContents (int line) for (var i = 0; i < 7; i++) { - item += Application.Driver.Contents [line, i].Rune; + item += Application.Driver?.Contents [line, i].Rune; } return item; diff --git a/UnitTests/Views/MenuBarTests.cs b/UnitTests/Views/MenuBarTests.cs index f7804dd426..35d08beff7 100644 --- a/UnitTests/Views/MenuBarTests.cs +++ b/UnitTests/Views/MenuBarTests.cs @@ -366,7 +366,7 @@ public void Draw_A_Menu_Over_A_Dialog () var win = new Window (); top.Add (win); RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (40, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 15); Assert.Equal (new (0, 0, 40, 15), win.Frame); @@ -556,7 +556,7 @@ void ChangeMenuTitle (string title) Assert.Equal (items [i], menu.Menus [0].Title); } - ((FakeDriver)Application.Driver).SetBufferSize (20, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); menu.OpenMenu (); firstIteration = false; Application.RunIteration (ref rsDialog, ref firstIteration); @@ -590,9 +590,9 @@ void ChangeMenuTitle (string title) [AutoInitShutdown] public void Draw_A_Menu_Over_A_Top_Dialog () { - ((FakeDriver)Application.Driver).SetBufferSize (40, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 15); - Assert.Equal (new (0, 0, 40, 15), Application.Driver.Clip); + Assert.Equal (new (0, 0, 40, 15), Application.Driver?.Clip); TestHelpers.AssertDriverContentsWithFrameAre (@"", output); List items = new () @@ -734,7 +734,7 @@ void ChangeMenuTitle (string title) Assert.Equal (items [i], menu.Menus [0].Title); } - ((FakeDriver)Application.Driver).SetBufferSize (20, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); menu.OpenMenu (); firstIteration = false; Application.RunIteration (ref rs, ref firstIteration); @@ -805,7 +805,7 @@ public void DrawFrame_With_Negative_Positions () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver).SetBufferSize (7, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (7, 5); menu.OpenMenu (); Application.Refresh (); @@ -821,7 +821,7 @@ public void DrawFrame_With_Negative_Positions () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver).SetBufferSize (7, 3); + ((FakeDriver)Application.Driver!).SetBufferSize (7, 3); menu.OpenMenu (); Application.Refresh (); @@ -878,7 +878,7 @@ public void DrawFrame_With_Negative_Positions_Disabled_Border () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver).SetBufferSize (3, 2); + ((FakeDriver)Application.Driver!).SetBufferSize (3, 2); menu.OpenMenu (); Application.Refresh (); @@ -891,7 +891,7 @@ public void DrawFrame_With_Negative_Positions_Disabled_Border () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver).SetBufferSize (3, 1); + ((FakeDriver)Application.Driver!).SetBufferSize (3, 1); menu.OpenMenu (); Application.Refresh (); @@ -1519,7 +1519,7 @@ public void MenuBar_In_Window_Without_Other_Views_With_Top_Init () Toplevel top = new (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (40, 8); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -1630,7 +1630,7 @@ public void MenuBar_In_Window_Without_Other_Views_With_Top_Init_With_Parameterle Application.Iteration += (s, a) => { - ((FakeDriver)Application.Driver).SetBufferSize (40, 8); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -1741,7 +1741,7 @@ public void MenuBar_In_Window_Without_Other_Views_Without_Top_Init () ] }; win.Add (menu); - ((FakeDriver)Application.Driver).SetBufferSize (40, 8); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); Application.Begin (win); TestHelpers.AssertDriverContentsWithFrameAre ( @@ -1827,7 +1827,7 @@ public void MenuBar_In_Window_Without_Other_Views_Without_Top_Init () [AutoInitShutdown] public void MenuBar_In_Window_Without_Other_Views_Without_Top_Init_With_Run_T () { - ((FakeDriver)Application.Driver).SetBufferSize (40, 8); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); Application.Iteration += (s, a) => { @@ -2758,7 +2758,7 @@ public void Resizing_Close_Menus () output ); - ((FakeDriver)Application.Driver).SetBufferSize (20, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); firstIteration = false; Application.RunIteration (ref rs, ref firstIteration); diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index 9c7202d684..5ca0fc5662 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -881,7 +881,7 @@ public void Visible_False_Does_Not_Clear () var overlapped = new Overlapped (); var win1 = new Window { Width = 5, Height = 5, Visible = false }; var win2 = new Window { X = 1, Y = 1, Width = 5, Height = 5 }; - ((FakeDriver)Application.Driver).SetBufferSize (10, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); RunState rsOverlapped = Application.Begin (overlapped); // Need to fool MainLoop into thinking it's running diff --git a/UnitTests/Views/RadioGroupTests.cs b/UnitTests/Views/RadioGroupTests.cs index 75aab7b188..4c828f2c16 100644 --- a/UnitTests/Views/RadioGroupTests.cs +++ b/UnitTests/Views/RadioGroupTests.cs @@ -219,7 +219,7 @@ public void Orientation_Width_Height_Vertical_Horizontal_Space () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (30, 5); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); Assert.Equal (Orientation.Vertical, rg.Orientation); Assert.Equal (2, rg.RadioLabels.Length); diff --git a/UnitTests/Views/ScrollBarViewTests.cs b/UnitTests/Views/ScrollBarViewTests.cs index f3f9f7c244..15c463d789 100644 --- a/UnitTests/Views/ScrollBarViewTests.cs +++ b/UnitTests/Views/ScrollBarViewTests.cs @@ -173,7 +173,7 @@ public void Both_Default_Draws_Correctly () super.Add (vert); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (width, height); + ((FakeDriver)Application.Driver!).SetBufferSize (width, height); var expected = @" ┌─┐ @@ -703,7 +703,7 @@ public void Horizontal_Default_Draws_Correctly () var sbv = new ScrollBarView { Id = "sbv", Size = width * 2, ShowScrollIndicator = true }; super.Add (sbv); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (width, height); + ((FakeDriver)Application.Driver!).SetBufferSize (width, height); var expected = @" ┌──────────────────────────────────────┐ @@ -829,7 +829,7 @@ public void Hosting_ShowBothScrollIndicator_Invisible () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (45, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (45, 20); Assert.True (scrollBar.AutoHideScrollBars); Assert.False (scrollBar.ShowScrollIndicator); @@ -867,7 +867,7 @@ public void Hosting_ShowBothScrollIndicator_Invisible () Assert.Equal (new Rectangle (0, 0, 45, 20), pos); textView.WordWrap = true; - ((FakeDriver)Application.Driver).SetBufferSize (26, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (26, 20); Application.Refresh (); Assert.True (textView.WordWrap); @@ -904,7 +904,7 @@ public void Hosting_ShowBothScrollIndicator_Invisible () pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); Assert.Equal (new Rectangle (0, 0, 26, 20), pos); - ((FakeDriver)Application.Driver).SetBufferSize (10, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); Application.Refresh (); Assert.True (textView.WordWrap); @@ -1229,7 +1229,7 @@ public void Vertical_Default_Draws_Correctly () super.Add (sbv); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (width, height); + ((FakeDriver)Application.Driver!).SetBufferSize (width, height); var expected = @" ┌─┐ diff --git a/UnitTests/Views/ScrollViewTests.cs b/UnitTests/Views/ScrollViewTests.cs index ceab4e63a0..c4d14c2358 100644 --- a/UnitTests/Views/ScrollViewTests.cs +++ b/UnitTests/Views/ScrollViewTests.cs @@ -362,7 +362,7 @@ public void Constructors_Defaults () [SetupFakeDriver] public void ContentBottomRightCorner_Draw () { - ((FakeDriver)Application.Driver).SetBufferSize (30, 30); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 30); var top = new View { Width = 30, Height = 30, ColorScheme = new() { Normal = Attribute.Default } }; diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index 515e3f8bda..6740120f69 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -2196,7 +2196,7 @@ public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect () [SetupFakeDriver] public void TestEnumerableDataSource_BasicTypes () { - ((FakeDriver)Application.Driver).SetBufferSize(100,100); + ((FakeDriver)Application.Driver!).SetBufferSize(100,100); var tv = new TableView (); tv.ColorScheme = Colors.ColorSchemes ["TopLevel"]; tv.Viewport = new (0, 0, 50, 6); diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index e506179334..ed3a357759 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -67,7 +67,7 @@ string GetContents () for (var i = 0; i < 16; i++) { - item += Application.Driver.Contents [0, i].Rune; + item += Application.Driver?.Contents [0, i].Rune; } return item; @@ -164,7 +164,7 @@ public void CaptionedTextField_DoesNotOverspillBounds (string caption, string ex // Caption has no effect when focused tf.Caption = caption; - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); Assert.False (tf.HasFocus); tf.Draw (); @@ -184,7 +184,7 @@ public void CaptionedTextField_DoesNotOverspillViewport_Unicode () TextField tf = GetTextFieldsInView (); tf.Caption = caption; - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); Assert.False (tf.HasFocus); tf.Draw (); @@ -205,7 +205,7 @@ public void CaptionedTextField_DoNotRenderCaption_WhenTextPresent (string conten TestHelpers.AssertDriverContentsAre ("", output); tf.Caption = "Enter txt"; - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); // Caption should appear when not focused and no text Assert.False (tf.HasFocus); @@ -234,7 +234,7 @@ public void CaptionedTextField_RendersCaption_WhenNotFocused () tf.Draw (); TestHelpers.AssertDriverContentsAre ("", output); - Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false); + Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); Assert.False (tf.HasFocus); tf.Draw (); @@ -347,7 +347,7 @@ public void Copy_Paste_Surrogate_Pairs () Assert.Equal ( "TextField with some more test text. Unicode shouldn't 𝔹Aℝ𝔽!", - Application.Driver.Clipboard.GetClipboardData () + Application.Driver?.Clipboard.GetClipboardData () ); Assert.Equal (string.Empty, _textField.Text); _textField.Paste (); @@ -374,7 +374,7 @@ void _textField_TextChanging (object sender, CancelEventArgs e) Assert.Equal (32, _textField.CursorPosition); _textField.SelectAll (); _textField.Cut (); - Assert.Equal ("TAB to jump between text fields.", Application.Driver.Clipboard.GetClipboardData ()); + Assert.Equal ("TAB to jump between text fields.", Application.Driver?.Clipboard.GetClipboardData ()); Assert.Equal (string.Empty, _textField.Text); Assert.Equal (0, _textField.CursorPosition); _textField.Paste (); diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs index f260dd162c..f9ff520dd6 100644 --- a/UnitTests/Views/TextViewTests.cs +++ b/UnitTests/Views/TextViewTests.cs @@ -609,7 +609,7 @@ public void Copy_Paste_Surrogate_Pairs () Assert.Equal ( "TextView with some more test text. Unicode shouldn't 𝔹Aℝ𝔽!", - Application.Driver.Clipboard.GetClipboardData () + Application.Driver?.Clipboard.GetClipboardData () ); Assert.Equal (string.Empty, _textView.Text); _textView.Paste (); @@ -1018,7 +1018,7 @@ public void DesiredCursorVisibility_Horizontal_Navigation () tv.NewMouseEvent (new MouseEvent { Flags = MouseFlags.WheeledRight }); Assert.Equal (Math.Min (i + 1, 11), tv.LeftColumn); Application.PositionCursor (top); - Application.Driver.GetCursorVisibility (out CursorVisibility cursorVisibility); + Application.Driver!.GetCursorVisibility (out CursorVisibility cursorVisibility); Assert.Equal (CursorVisibility.Invisible, cursorVisibility); } @@ -1028,7 +1028,7 @@ public void DesiredCursorVisibility_Horizontal_Navigation () Assert.Equal (i - 1, tv.LeftColumn); Application.PositionCursor (top); - Application.Driver.GetCursorVisibility (out CursorVisibility cursorVisibility); + Application.Driver!.GetCursorVisibility (out CursorVisibility cursorVisibility); if (i - 1 == 0) { @@ -1070,7 +1070,7 @@ public void DesiredCursorVisibility_Vertical_Navigation () tv.NewMouseEvent (new MouseEvent { Flags = MouseFlags.WheeledDown }); Application.PositionCursor (top); Assert.Equal (i + 1, tv.TopRow); - Application.Driver.GetCursorVisibility (out CursorVisibility cursorVisibility); + Application.Driver!.GetCursorVisibility (out CursorVisibility cursorVisibility); Assert.Equal (CursorVisibility.Invisible, cursorVisibility); } @@ -1081,7 +1081,7 @@ public void DesiredCursorVisibility_Vertical_Navigation () Assert.Equal (i - 1, tv.TopRow); Application.PositionCursor (top); - Application.Driver.GetCursorVisibility (out CursorVisibility cursorVisibility); + Application.Driver!.GetCursorVisibility (out CursorVisibility cursorVisibility); if (i - 1 == 0) { @@ -6697,7 +6697,7 @@ public void TextView_InsertText_Newline_CRLF () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (15, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); Application.Refresh (); //this passes @@ -6774,7 +6774,7 @@ public void TextView_InsertText_Newline_LF () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (15, 15); + ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); Application.Refresh (); //this passes @@ -6899,8 +6899,8 @@ This is the second line. _output ); - ((FakeDriver)Application.Driver).SetBufferSize (6, 25); - tv.SetRelativeLayout (Application.Driver.Screen.Size); + ((FakeDriver)Application.Driver!).SetBufferSize (6, 25); + tv.SetRelativeLayout (Application.Driver!.Screen.Size); tv.Draw (); Assert.Equal (new Point (4, 2), tv.CursorPosition); Assert.Equal (new Point (12, 0), cp); diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index abbfdeb59c..c19c72bb1a 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -44,8 +44,8 @@ public void Application_Top_GetLocationThatFits_To_Driver_Rows_And_Cols () Assert.Equal ("Top1", Application.Top.Text); Assert.Equal (0, Application.Top.Frame.X); Assert.Equal (0, Application.Top.Frame.Y); - Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); - Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + Assert.Equal (Application.Driver!.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver!.Rows, Application.Top.Frame.Height); Application.OnKeyPressed (new (Key.CtrlMask | Key.R)); @@ -54,8 +54,8 @@ public void Application_Top_GetLocationThatFits_To_Driver_Rows_And_Cols () Assert.Equal ("Top2", Application.Top.Text); Assert.Equal (0, Application.Top.Frame.X); Assert.Equal (0, Application.Top.Frame.Y); - Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); - Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + Assert.Equal (Application.Driver!.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver!.Rows, Application.Top.Frame.Height); Application.OnKeyPressed (new (Key.CtrlMask | Key.C)); @@ -64,8 +64,8 @@ public void Application_Top_GetLocationThatFits_To_Driver_Rows_And_Cols () Assert.Equal ("Top1", Application.Top.Text); Assert.Equal (0, Application.Top.Frame.X); Assert.Equal (0, Application.Top.Frame.Y); - Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); - Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + Assert.Equal (Application.Driver!.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver!.Rows, Application.Top.Frame.Height); Application.OnKeyPressed (new (Key.CtrlMask | Key.R)); @@ -74,8 +74,8 @@ public void Application_Top_GetLocationThatFits_To_Driver_Rows_And_Cols () Assert.Equal ("Top2", Application.Top.Text); Assert.Equal (0, Application.Top.Frame.X); Assert.Equal (0, Application.Top.Frame.Y); - Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); - Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + Assert.Equal (Application.Driver!.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver!.Rows, Application.Top.Frame.Height); Application.OnKeyPressed (new (Key.CtrlMask | Key.C)); @@ -84,8 +84,8 @@ public void Application_Top_GetLocationThatFits_To_Driver_Rows_And_Cols () Assert.Equal ("Top1", Application.Top.Text); Assert.Equal (0, Application.Top.Frame.X); Assert.Equal (0, Application.Top.Frame.Y); - Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); - Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + Assert.Equal (Application.Driver!.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver!.Rows, Application.Top.Frame.Height); Application.OnKeyPressed (new (Key.CtrlMask | Key.Q)); @@ -675,7 +675,7 @@ public void Mouse_Drag_On_Top_With_Superview_Null () if (iterations == 0) { - ((FakeDriver)Application.Driver).SetBufferSize (15, 7); + ((FakeDriver)Application.Driver!).SetBufferSize (15, 7); // Don't use MessageBox here; it's too complicated for this unit test; just use Window testWindow = new () @@ -794,7 +794,7 @@ public void Mouse_Drag_On_Top_With_Superview_Not_Null () if (iterations == 0) { - ((FakeDriver)Application.Driver).SetBufferSize (30, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (30, 10); } else if (iterations == 1) { @@ -896,10 +896,10 @@ public void GetLocationThatFits_With_Border_Null_Not_Throws () top.BeginInit (); top.EndInit (); - Exception exception = Record.Exception (() => ((FakeDriver)Application.Driver).SetBufferSize (0, 10)); + Exception exception = Record.Exception (() => ((FakeDriver)Application.Driver!).SetBufferSize (0, 10)); Assert.Null (exception); - exception = Record.Exception (() => ((FakeDriver)Application.Driver).SetBufferSize (10, 0)); + exception = Record.Exception (() => ((FakeDriver)Application.Driver!).SetBufferSize (10, 0)); Assert.Null (exception); } @@ -1085,13 +1085,13 @@ public void PositionCursor_SetCursorVisibility_To_Invisible_If_Focused_Is_Null ( Assert.True (tf.HasFocus); Application.PositionCursor (top); - Application.Driver.GetCursorVisibility (out CursorVisibility cursor); + Application.Driver!.GetCursorVisibility (out CursorVisibility cursor); Assert.Equal (CursorVisibility.Default, cursor); view.Enabled = false; Assert.False (tf.HasFocus); Application.PositionCursor (top); - Application.Driver.GetCursorVisibility (out cursor); + Application.Driver!.GetCursorVisibility (out cursor); Assert.Equal (CursorVisibility.Invisible, cursor); top.Dispose (); } @@ -1209,7 +1209,7 @@ public void Window_Viewport_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_L Toplevel top = new (); var window = new Window { Width = 20, Height = 3, Arrangement = ViewArrangement.Movable }; RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (40, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 10); RunState rsWindow = Application.Begin (window); Application.Refresh (); Assert.Equal (new (0, 0, 40, 10), top.Frame); @@ -1232,7 +1232,7 @@ public void Window_Viewport_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_L Assert.Equal (new (0, 0, 20, 3), window.Frame); // Changes Top size to same size as Dialog more menu and scroll bar - ((FakeDriver)Application.Driver).SetBufferSize (20, 3); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 3); Application.OnMouseEvent ( new () @@ -1245,7 +1245,7 @@ public void Window_Viewport_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_L Assert.Equal (new (0, 0, 20, 3), window.Frame); // Changes Top size smaller than Dialog size - ((FakeDriver)Application.Driver).SetBufferSize (19, 2); + ((FakeDriver)Application.Driver!).SetBufferSize (19, 2); Application.OnMouseEvent ( new () @@ -1338,7 +1338,7 @@ public void Begin_With_Window_Sets_Size_Correctly () { Toplevel top = new (); RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 20); var testWindow = new Window { X = 2, Y = 1, Width = 15, Height = 10 }; Assert.Equal (new (2, 1, 15, 10), testWindow.Frame); @@ -1360,7 +1360,7 @@ public void Draw_A_Top_Subview_On_A_Window () var win = new Window (); top.Add (win); RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 20); Assert.Equal (new (0, 0, 20, 20), win.Frame); @@ -1389,8 +1389,8 @@ void OnDrawContentComplete (object sender, DrawEventArgs e) { Assert.Equal (new (1, 3, 18, 16), viewAddedToTop.Frame); - Rectangle savedClip = Application.Driver.Clip; - Application.Driver.Clip = top.Frame; + Rectangle savedClip = Application.Driver!.Clip; + Application.Driver!.Clip = top.Frame; viewAddedToTop.Draw (); top.Move (2, 15); View.Driver.AddStr ("One"); @@ -1398,7 +1398,7 @@ void OnDrawContentComplete (object sender, DrawEventArgs e) View.Driver.AddStr ("Two"); top.Move (2, 17); View.Driver.AddStr ("Three"); - Application.Driver.Clip = savedClip; + Application.Driver!.Clip = savedClip; Application.Current.DrawContentComplete -= OnDrawContentComplete; } diff --git a/UnitTests/Views/TreeTableSourceTests.cs b/UnitTests/Views/TreeTableSourceTests.cs index 0b54be84d3..39e18327bf 100644 --- a/UnitTests/Views/TreeTableSourceTests.cs +++ b/UnitTests/Views/TreeTableSourceTests.cs @@ -29,7 +29,7 @@ public void Dispose () [SetupFakeDriver] public void TestTreeTableSource_BasicExpanding_WithKeyboard () { - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); TableView tv = GetTreeTable (out _); tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; @@ -88,7 +88,7 @@ public void TestTreeTableSource_BasicExpanding_WithKeyboard () [SetupFakeDriver] public void TestTreeTableSource_BasicExpanding_WithMouse () { - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); TableView tv = GetTreeTable (out _); diff --git a/UnitTests/Views/TreeViewTests.cs b/UnitTests/Views/TreeViewTests.cs index 11d85acdb0..a770d1d9d2 100644 --- a/UnitTests/Views/TreeViewTests.cs +++ b/UnitTests/Views/TreeViewTests.cs @@ -114,7 +114,7 @@ public void CursorVisibility_MultiSelect () tv.SelectAll (); tv.CursorVisibility = CursorVisibility.Default; Application.PositionCursor (top); - Application.Driver.GetCursorVisibility (out CursorVisibility visibility); + Application.Driver!.GetCursorVisibility (out CursorVisibility visibility); Assert.Equal (CursorVisibility.Default, tv.CursorVisibility); Assert.Equal (CursorVisibility.Default, visibility); top.Dispose (); diff --git a/UnitTests/Views/WindowTests.cs b/UnitTests/Views/WindowTests.cs index 24ea778763..6df5361e37 100644 --- a/UnitTests/Views/WindowTests.cs +++ b/UnitTests/Views/WindowTests.cs @@ -53,7 +53,7 @@ public void MenuBar_And_StatusBar_Inside_Window () Toplevel top = new (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -70,7 +70,7 @@ public void MenuBar_And_StatusBar_Inside_Window () _output ); - ((FakeDriver)Application.Driver).SetBufferSize (40, 20); + ((FakeDriver)Application.Driver!).SetBufferSize (40, 20); TestHelpers.AssertDriverContentsWithFrameAre ( @" @@ -97,7 +97,7 @@ public void MenuBar_And_StatusBar_Inside_Window () _output ); - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); + ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); TestHelpers.AssertDriverContentsWithFrameAre ( @" From 2e0a9a7c688f822adec71c98a6205a17d5c65f6f Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 22 Jul 2024 16:59:10 -0600 Subject: [PATCH 65/96] Fixed nullable warnings --- Terminal.Gui/Application/Application.Mouse.cs | 2 +- Terminal.Gui/Application/Application.cs | 4 ++-- Terminal.Gui/View/Layout/ViewLayout.cs | 6 +++--- Terminal.sln.DotSettings | 3 ++- UICatalog/UICatalog.cs | 10 +++++----- UnitTests/ConsoleDrivers/ClipRegionTests.cs | 4 ++-- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs index 4d3fb61298..7cad055d58 100644 --- a/Terminal.Gui/Application/Application.Mouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -195,7 +195,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) { // This occurs when there are multiple overlapped "tops" // E.g. "Mdi" - in the Background Worker Scenario - View? top = FindDeepestTop (Top, mouseEvent.Position); + View? top = FindDeepestTop (Top!, mouseEvent.Position); view = View.FindDeepestView (top, mouseEvent.Position); if (view is { } && view != OverlappedTop && top != Current && top is { }) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 1561413e9a..c8aa2536a5 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -19,7 +19,7 @@ namespace Terminal.Gui; public static partial class Application { /// Gets all cultures supported by the application without the invariant language. - public static List SupportedCultures { get; private set; } + public static List? SupportedCultures { get; private set; } internal static List GetSupportedCultures () { @@ -257,7 +257,7 @@ private static bool MoveCurrent (Toplevel top) foreach (Toplevel? t in savedToplevels) { - if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) + if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) { lock (_topLevels) { diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 637d1f0ca4..4e2531745b 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -344,7 +344,7 @@ public Dim? Width if (found is { }) { start = found; - viewportOffset = found.Parent.Frame.Location; + viewportOffset = found.Parent?.Frame.Location ?? Point.Empty; } int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X); @@ -796,7 +796,7 @@ internal void CollectDim (Dim? dim, View from, ref HashSet nNodes, ref Has //} if (dv.Target != this) { - nEdges.Add ((dv.Target, from)); + nEdges.Add ((dv.Target!, from)); } return; @@ -819,7 +819,7 @@ internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref Hash //} if (pv.Target != this) { - nEdges.Add ((pv.Target, from)); + nEdges.Add ((pv.Target!, from)); } return; diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings index e83ed70261..bf87e5ea90 100644 --- a/Terminal.sln.DotSettings +++ b/Terminal.sln.DotSettings @@ -1,4 +1,4 @@ - + BackingField Inherit True @@ -390,6 +390,7 @@ <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy> <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> True ..\Terminal.sln.ToDo.DotSettings diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index dd4184cb4a..f9ac4a5034 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -666,7 +666,7 @@ public void ConfigChanged () MiIsMouseDisabled!.Checked = Application.IsMouseDisabled; - Application.Top.SetNeedsDisplay (); + Application.Top!.SetNeedsDisplay (); } public MenuItem []? CreateThemeMenuItems () @@ -835,7 +835,7 @@ private MenuItem [] CreateDiagnosticFlagsMenuItems () } Diagnostics = _diagnosticFlags; - Application.Top.SetNeedsDisplay (); + Application.Top!.SetNeedsDisplay (); }; menuItems.Add (item); } @@ -1061,7 +1061,7 @@ private void LoadedHandler (object? sender, EventArgs? args) ShowStatusBar = StatusBar.Visible; int height = StatusBar.Visible ? 1 : 0; - CategoryList.Height = Dim.Fill (height); + CategoryList!.Height = Dim.Fill (height); ScenarioList.Height = Dim.Fill (height); // ContentPane.Height = Dim.Fill (height); @@ -1071,7 +1071,7 @@ private void LoadedHandler (object? sender, EventArgs? args) } Loaded -= LoadedHandler; - CategoryList.EnsureSelectedItemVisible (); + CategoryList!.EnsureSelectedItemVisible (); ScenarioList.EnsureSelectedCellIsVisible (); } @@ -1082,7 +1082,7 @@ private void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e) if (_selectedScenario is null) { // Save selected item state - _cachedCategoryIndex = CategoryList.SelectedItem; + _cachedCategoryIndex = CategoryList!.SelectedItem; _cachedScenarioIndex = ScenarioList.SelectedRow; // Create new instance of scenario (even though Scenarios contains instances) diff --git a/UnitTests/ConsoleDrivers/ClipRegionTests.cs b/UnitTests/ConsoleDrivers/ClipRegionTests.cs index 8a90f2e4de..73eff741dd 100644 --- a/UnitTests/ConsoleDrivers/ClipRegionTests.cs +++ b/UnitTests/ConsoleDrivers/ClipRegionTests.cs @@ -7,12 +7,12 @@ namespace Terminal.Gui.DriverTests; public class ClipRegionTests { - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper _output; public ClipRegionTests (ITestOutputHelper output) { ConsoleDriver.RunningUnitTests = true; - this.output = output; + this._output = output; } [Theory] From 0b8e4342d4aff89afbe09f872e510e19892ea189 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 22 Jul 2024 17:05:06 -0600 Subject: [PATCH 66/96] Applicaation Toplevel handling moved to separate file --- Terminal.Gui/Application/Application.Run.cs | 5 + .../Application/Application.Toplevel.cs | 214 ++++++++++++++++++ Terminal.Gui/Application/Application.cs | 7 +- 3 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 Terminal.Gui/Application/Application.Toplevel.cs diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 34189d9fc9..06c1912302 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -5,6 +5,8 @@ namespace Terminal.Gui; public static partial class Application // Run (Begin, Run, End, Stop) { + // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`. + // This variable is set in `End` in this case so that `Begin` correctly sets `Top`. private static Toplevel _cachedRunStateToplevel; /// @@ -485,6 +487,9 @@ public static void Invoke (Action action) ); } + // TODO: Determine if this is really needed. The only code that calls WakeUp I can find + // is ProgressBarStyles, and it's not clear it needs to. + /// Wakes up the running application that might be waiting on input. public static void Wakeup () { MainLoop?.Wakeup (); } diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs new file mode 100644 index 0000000000..6d25f7d022 --- /dev/null +++ b/Terminal.Gui/Application/Application.Toplevel.cs @@ -0,0 +1,214 @@ +namespace Terminal.Gui; + +public static partial class Application // Toplevel handling +{ + /// Holds the stack of TopLevel views. + + // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What + // about TopLevels that are just a SubView of another View? + internal static readonly Stack _topLevels = new (); + + /// The object used for the application on startup () + /// The top. + public static Toplevel Top { get; private set; } + + /// + /// The current object. This is updated in enters and leaves to + /// point to the current + /// . + /// + /// + /// Only relevant in scenarios where is . + /// + /// The current. + public static Toplevel Current { get; private set; } + + private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) + { + if (!topLevel.Running + || (topLevel == Current && topLevel.Visible) + || OverlappedTop == null + || _topLevels.Peek ().Modal) + { + return; + } + + foreach (Toplevel top in _topLevels.Reverse ()) + { + if (top.Modal && top != Current) + { + MoveCurrent (top); + + return; + } + } + + if (!topLevel.Visible && topLevel == Current) + { + OverlappedMoveNext (); + } + } + + private static Toplevel FindDeepestTop (Toplevel start, in Point location) + { + if (!start.Frame.Contains (location)) + { + return null; + } + + if (_topLevels is { Count: > 0 }) + { + int rx = location.X - start.Frame.X; + int ry = location.Y - start.Frame.Y; + + foreach (Toplevel t in _topLevels) + { + if (t != Current) + { + if (t != start && t.Visible && t.Frame.Contains (rx, ry)) + { + start = t; + + break; + } + } + } + } + + return start; + } + + private static View FindTopFromView (View view) + { + View top = view?.SuperView is { } && view?.SuperView != Top + ? view.SuperView + : view; + + while (top?.SuperView is { } && top?.SuperView != Top) + { + top = top.SuperView; + } + + return top; + } + + private static bool MoveCurrent (Toplevel top) + { + // The Current is modal and the top is not modal Toplevel then + // the Current must be moved above the first not modal Toplevel. + if (OverlappedTop is { } + && top != OverlappedTop + && top != Current + && Current?.Modal == true + && !_topLevels.Peek ().Modal) + { + lock (_topLevels) + { + _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); + } + + var index = 0; + Toplevel [] savedToplevels = _topLevels.ToArray (); + + foreach (Toplevel t in savedToplevels) + { + if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) + { + lock (_topLevels) + { + _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); + } + } + + index++; + } + + return false; + } + + // The Current and the top are both not running Toplevel then + // the top must be moved above the first not running Toplevel. + if (OverlappedTop is { } + && top != OverlappedTop + && top != Current + && Current?.Running == false + && top?.Running == false) + { + lock (_topLevels) + { + _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); + } + + var index = 0; + + foreach (Toplevel t in _topLevels.ToArray ()) + { + if (!t.Running && t != Current && index > 0) + { + lock (_topLevels) + { + _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); + } + } + + index++; + } + + return false; + } + + if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top) + || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) + || (OverlappedTop is { } && Current?.Modal == false && top != Current) + || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop)) + { + lock (_topLevels) + { + _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Current = top; + } + } + + return true; + } + + /// Invoked when the terminal's size changed. The new size of the terminal is provided. + /// + /// Event handlers can set to to prevent + /// from changing it's size to match the new terminal size. + /// + public static event EventHandler SizeChanging; + + /// + /// Called when the application's size changes. Sets the size of all s and fires the + /// event. + /// + /// The new size. + /// if the size was changed. + public static bool OnSizeChanging (SizeChangedEventArgs args) + { + SizeChanging?.Invoke (null, args); + + if (args.Cancel || args.Size is null) + { + return false; + } + + foreach (Toplevel t in _topLevels) + { + t.SetRelativeLayout (args.Size.Value); + t.LayoutSubviews (); + t.PositionToplevels (); + t.OnSizeChanging (new (args.Size)); + + if (PositionCursor (t)) + { + Driver.UpdateCursor (); + } + } + + Refresh (); + + return true; + } +} diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index c8aa2536a5..202b660440 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -9,13 +9,16 @@ namespace Terminal.Gui; /// /// /// Application.Init(); -/// var win = new Window ($"Example App ({Application.QuitKey} to quit)"); +/// var win = new Window() +/// { +/// Title = $"Example App ({Application.QuitKey} to quit)" +/// }; /// Application.Run(win); /// win.Dispose(); /// Application.Shutdown(); /// /// -/// TODO: Flush this out. +/// public static partial class Application { /// Gets all cultures supported by the application without the invariant language. From 2939108bfe248475a0ce0a8cd2bbfb86cc4555c2 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 22 Jul 2024 17:06:21 -0600 Subject: [PATCH 67/96] Added Toplevel to spelling dict --- Terminal.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings index bf87e5ea90..0142f001bd 100644 --- a/Terminal.sln.DotSettings +++ b/Terminal.sln.DotSettings @@ -405,6 +405,7 @@ True True True + True True True From 250050c8a2729846cfd4a43373a18feb92df31fd Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 22 Jul 2024 17:50:45 -0600 Subject: [PATCH 68/96] Toplevel cleanup --- .../Application/Application.Initialization.cs | 1 - .../Application/Application.Keyboard.cs | 3 + Terminal.Gui/Application/Application.cs | 218 ------------------ Terminal.Gui/Views/Toplevel.cs | 57 +++-- 4 files changed, 29 insertions(+), 250 deletions(-) diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index a971850e39..16b7b83d42 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -40,7 +40,6 @@ public static partial class Application // Initialization (Init/Shutdown) internal static bool _initialized; internal static int _mainThreadId = -1; - // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. // // Called from: diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index e8d6982862..703a84982c 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -25,6 +25,7 @@ public static Key AlternateForwardKey private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) { + // TODO: The fact Top has it's own AlternateForwardKey and events is needlessly complex. Remove it. foreach (Toplevel top in _topLevels.ToArray ()) { top.OnAlternateForwardKeyChanged (e); @@ -52,6 +53,7 @@ public static Key AlternateBackwardKey private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) { + // TODO: The fact Top has it's own AlternateBackwardKey and events is needlessly complex. Remove it. foreach (Toplevel top in _topLevels.ToArray ()) { top.OnAlternateBackwardKeyChanged (oldKey); @@ -79,6 +81,7 @@ public static Key QuitKey private static void OnQuitKeyChanged (KeyChangedEventArgs e) { + // TODO: The fact Top has it's own QuitKey and events is needlessly complex. Remove it. // Duplicate the list so if it changes during enumeration we're safe foreach (Toplevel top in _topLevels.ToArray ()) { diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 202b660440..3b4c9d9f7a 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -137,232 +137,14 @@ internal static void ResetState (bool ignoreDisposed = false) SynchronizationContext.SetSynchronizationContext (null); } - // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`. - // This field is set in `End` in this case so that `Begin` correctly sets `Top`. - - // TODO: Determine if this is really needed. The only code that calls WakeUp I can find - // is ProgressBarStyles, and it's not clear it needs to. - - #region Toplevel handling - - /// Holds the stack of TopLevel views. - - // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What - // about TopLevels that are just a SubView of another View? - internal static readonly Stack _topLevels = new (); - - /// The object used for the application on startup () - /// The top. - public static Toplevel? Top { get; private set; } - - /// - /// The current object. This is updated in enters and leaves to - /// point to the current - /// . - /// - /// - /// Only relevant in scenarios where is . - /// - /// The current. - public static Toplevel? Current { get; private set; } - - private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) - { - if (!topLevel.Running - || (topLevel == Current && topLevel.Visible) - || OverlappedTop == null - || _topLevels.Peek ().Modal) - { - return; - } - - foreach (Toplevel? top in _topLevels.Reverse ()) - { - if (top.Modal && top != Current) - { - MoveCurrent (top); - - return; - } - } - - if (!topLevel.Visible && topLevel == Current) - { - OverlappedMoveNext (); - } - } - #nullable enable - private static Toplevel? FindDeepestTop (Toplevel start, in Point location) - { - if (!start.Frame.Contains (location)) - { - return null; - } - - if (_topLevels is { Count: > 0 }) - { - int rx = location.X - start.Frame.X; - int ry = location.Y - start.Frame.Y; - - foreach (Toplevel? t in _topLevels) - { - if (t != Current) - { - if (t != start && t.Visible && t.Frame.Contains (rx, ry)) - { - start = t; - - break; - } - } - } - } - - return start; - } #nullable restore - private static View FindTopFromView (View view) - { - View top = view?.SuperView is { } && view?.SuperView != Top - ? view.SuperView - : view; - - while (top?.SuperView is { } && top?.SuperView != Top) - { - top = top.SuperView; - } - - return top; - } - #nullable enable // Only return true if the Current has changed. - private static bool MoveCurrent (Toplevel top) - { - // The Current is modal and the top is not modal Toplevel then - // the Current must be moved above the first not modal Toplevel. - if (OverlappedTop is { } - && top != OverlappedTop - && top != Current - && Current?.Modal == true - && !_topLevels.Peek ().Modal) - { - lock (_topLevels) - { - _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); - } - - var index = 0; - Toplevel? [] savedToplevels = _topLevels.ToArray (); - - foreach (Toplevel? t in savedToplevels) - { - if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); - } - } - - index++; - } - - return false; - } - - // The Current and the top are both not running Toplevel then - // the top must be moved above the first not running Toplevel. - if (OverlappedTop is { } - && top != OverlappedTop - && top != Current - && Current?.Running == false - && top?.Running == false) - { - lock (_topLevels) - { - _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); - } - - var index = 0; - - foreach (Toplevel? t in _topLevels.ToArray ()) - { - if (!t.Running && t != Current && index > 0) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); - } - } - - index++; - } - - return false; - } - - if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top) - || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) - || (OverlappedTop is { } && Current?.Modal == false && top != Current) - || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop)) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } - } - - return true; - } #nullable restore - /// Invoked when the terminal's size changed. The new size of the terminal is provided. - /// - /// Event handlers can set to to prevent - /// from changing it's size to match the new terminal size. - /// - public static event EventHandler SizeChanging; - - /// - /// Called when the application's size changes. Sets the size of all s and fires the - /// event. - /// - /// The new size. - /// if the size was changed. - public static bool OnSizeChanging (SizeChangedEventArgs args) - { - SizeChanging?.Invoke (null, args); - - if (args.Cancel || args.Size is null) - { - return false; - } - - foreach (Toplevel t in _topLevels) - { - t.SetRelativeLayout (args.Size.Value); - t.LayoutSubviews (); - t.PositionToplevels (); - t.OnSizeChanging (new (args.Size)); - - if (PositionCursor (t)) - { - Driver.UpdateCursor (); - } - } - - Refresh (); - - return true; - } - - #endregion Toplevel handling - /// /// Gets a string representation of the Application as rendered by . /// diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 617dfa7fc8..32d8f8a89e 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -6,7 +6,7 @@ namespace Terminal.Gui; /// /// /// -/// Toplevels can run as modal (popup) views, started by calling +/// Toplevel views can run as modal (popup) views, started by calling /// . They return control to the caller when /// has been called (which sets the /// property to false). @@ -14,7 +14,7 @@ namespace Terminal.Gui; /// /// A Toplevel is created when an application initializes Terminal.Gui by calling . /// The application Toplevel can be accessed via . Additional Toplevels can be created -/// and run (e.g. s. To run a Toplevel, create the and call +/// and run (e.g. s). To run a Toplevel, create the and call /// . /// /// @@ -259,32 +259,30 @@ public override void OnDrawContent (Rectangle viewport) return; } - if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) + if (NeedsDisplay || SubViewNeedsDisplay /*|| LayoutNeeded*/) { - //Driver.SetAttribute (GetNormalColor ()); - // TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc... Clear (); - LayoutSubviews (); - PositionToplevels (); - - if (this == Application.OverlappedTop) - { - foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) - { - if (top.Frame.IntersectsWith (Viewport)) - { - if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) - { - top.SetNeedsLayout (); - top.SetNeedsDisplay (top.Viewport); - top.Draw (); - top.OnRenderLineCanvas (); - } - } - } - } + //LayoutSubviews (); + //PositionToplevels (); + + //if (this == Application.OverlappedTop) + //{ + // foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) + // { + // if (top.Frame.IntersectsWith (Viewport)) + // { + // if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) + // { + // top.SetNeedsLayout (); + // top.SetNeedsDisplay (top.Viewport); + // top.Draw (); + // top.OnRenderLineCanvas (); + // } + // } + // } + //} - // This should not be here, but in base + // BUGBUG: This appears to be a hack to get ScrollBarViews to render correctly. foreach (View view in Subviews) { if (view.Frame.IntersectsWith (Viewport) && !OutsideTopFrame (this)) @@ -296,12 +294,6 @@ public override void OnDrawContent (Rectangle viewport) } base.OnDrawContent (viewport); - - // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true - //if (this.MenuBar is { } && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu is { }) { - // // TODO: Hack until we can get compositing working right. - // this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Viewport); - //} } } @@ -315,6 +307,9 @@ public override void OnDrawContent (Rectangle viewport) /// Called from before the redraws for the first /// time. /// + /// + /// Overrides must call base.OnLoaded() to ensure any Toplevel subviews are initialized properly and the event is raised. + /// public virtual void OnLoaded () { IsLoaded = true; From 47010805cdb3312135da0b3367ebbc1cac54e7e5 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 23 Jul 2024 09:41:21 -0600 Subject: [PATCH 69/96] Toplevel.cs organization --- Terminal.Gui/Views/Toplevel.cs | 668 ++++++++++++++++++--------------- 1 file changed, 356 insertions(+), 312 deletions(-) diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 32d8f8a89e..fa526904d6 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -27,12 +27,45 @@ public partial class Toplevel : View /// public Toplevel () { + CanFocus = true; Arrangement = ViewArrangement.Fixed; Width = Dim.Fill (); Height = Dim.Fill (); ColorScheme = Colors.ColorSchemes ["TopLevel"]; + ConfigureKeyBindings (); + + MouseClick += Toplevel_MouseClick; + } + + // TODO: IRunnable: Re-implement - Modal means IRunnable, ViewArrangement.Overlapped where modalView.Z > allOtherViews.Max (v = v.Z). + /// + /// Determines whether the is modal or not. If set to false (the default): + /// + /// + /// events will propagate keys upwards. + /// + /// + /// The Toplevel will act as an embedded view (not a modal/pop-up). + /// + /// + /// If set to true: + /// + /// + /// events will NOT propagate keys upwards. + /// + /// + /// The Toplevel will and look like a modal (pop-up) (e.g. see . + /// + /// + /// + public bool Modal { get; set; } + + #region Keyboard & Mouse + + private void ConfigureKeyBindings () + { // Things this view knows how to do AddCommand ( Command.QuitToplevel, @@ -134,107 +167,19 @@ public Toplevel () KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix #endif - MouseClick += Toplevel_MouseClick; - - CanFocus = true; - } - - private void Toplevel_MouseClick (object sender, MouseEventEventArgs e) - { - e.Handled = InvokeCommand (Command.HotKey) == true; } - /// - /// if was already loaded by the - /// , otherwise. - /// - public bool IsLoaded { get; private set; } - - /// Gets or sets the menu for this Toplevel. - public virtual MenuBar MenuBar { get; set; } - - /// - /// Determines whether the is modal or not. If set to false (the default): - /// - /// - /// events will propagate keys upwards. - /// - /// - /// The Toplevel will act as an embedded view (not a modal/pop-up). - /// - /// - /// If set to true: - /// - /// - /// events will NOT propagate keys upwards. - /// - /// - /// The Toplevel will and look like a modal (pop-up) (e.g. see . - /// - /// - /// - public bool Modal { get; set; } - - /// Gets or sets whether the main loop for this is running or not. - /// Setting this property directly is discouraged. Use instead. - public bool Running { get; set; } - - /// Gets or sets the status bar for this Toplevel. - public virtual StatusBar StatusBar { get; set; } - - /// Invoked when the Toplevel becomes the Toplevel. - public event EventHandler Activate; - - /// - public override View Add (View view) - { - CanFocus = true; - AddMenuStatusBar (view); - return base.Add (view); - } - - /// - /// Invoked when the last child of the Toplevel is closed from by - /// . - /// - public event EventHandler AllChildClosed; + private void Toplevel_MouseClick (object sender, MouseEventEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; } + // TODO: Deprecate - No need for this at View level; having at Application is sufficient. /// Invoked when the is changed. public event EventHandler AlternateBackwardKeyChanged; + // TODO: Deprecate - No need for this at View level; having at Application is sufficient. /// Invoked when the is changed. public event EventHandler AlternateForwardKeyChanged; - /// - /// Invoked when a child of the Toplevel is closed by - /// . - /// - public event EventHandler ChildClosed; - - /// Invoked when a child Toplevel's has been loaded. - public event EventHandler ChildLoaded; - - /// Invoked when a cjhild Toplevel's has been unloaded. - public event EventHandler ChildUnloaded; - - /// Invoked when the Toplevel's is closed by . - public event EventHandler Closed; - - /// - /// Invoked when the Toplevel's is being closed by - /// . - /// - public event EventHandler Closing; - - /// Invoked when the Toplevel ceases to be the Toplevel. - public event EventHandler Deactivate; - - /// - /// Invoked when the has begun to be loaded. A Loaded event handler - /// is a good place to finalize initialization before calling . - /// - public event EventHandler Loaded; - + // TODO: Deprecate - No need for this at View level; having at Application is sufficient. /// Virtual method to invoke the event. /// public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) @@ -243,6 +188,7 @@ public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) AlternateBackwardKeyChanged?.Invoke (this, e); } + // TODO: Deprecate - No need for this at View level; having at Application is sufficient. /// Virtual method to invoke the event. /// public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) @@ -251,189 +197,171 @@ public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) AlternateForwardKeyChanged?.Invoke (this, e); } - /// - public override void OnDrawContent (Rectangle viewport) + /// Virtual method to invoke the event. + /// + public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) { - if (!Visible) - { - return; - } + KeyBindings.Replace (e.OldKey, e.NewKey); + QuitKeyChanged?.Invoke (this, e); + } - if (NeedsDisplay || SubViewNeedsDisplay /*|| LayoutNeeded*/) - { - Clear (); - //LayoutSubviews (); - //PositionToplevels (); + /// Invoked when the is changed. + public event EventHandler QuitKeyChanged; - //if (this == Application.OverlappedTop) - //{ - // foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) - // { - // if (top.Frame.IntersectsWith (Viewport)) - // { - // if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) - // { - // top.SetNeedsLayout (); - // top.SetNeedsDisplay (top.Viewport); - // top.Draw (); - // top.OnRenderLineCanvas (); - // } - // } - // } - //} + #endregion - // BUGBUG: This appears to be a hack to get ScrollBarViews to render correctly. - foreach (View view in Subviews) - { - if (view.Frame.IntersectsWith (Viewport) && !OutsideTopFrame (this)) - { - //view.SetNeedsLayout (); - view.SetNeedsDisplay (); - view.SetSubViewNeedsDisplay (); - } - } + #region Subviews - base.OnDrawContent (viewport); - } - } + // TODO: Deprecate - Any view can host a menubar in v2 + /// Gets or sets the menu for this Toplevel. + public virtual MenuBar MenuBar { get; set; } - /// - public override bool OnEnter (View view) { return MostFocused?.OnEnter (view) ?? base.OnEnter (view); } + // TODO: Deprecate - Any view can host a statusbar in v2 + /// Gets or sets the status bar for this Toplevel. + public virtual StatusBar StatusBar { get; set; } /// - public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } - - /// - /// Called from before the redraws for the first - /// time. - /// - /// - /// Overrides must call base.OnLoaded() to ensure any Toplevel subviews are initialized properly and the event is raised. - /// - public virtual void OnLoaded () + public override View Add (View view) { - IsLoaded = true; - - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) - { - tl.OnLoaded (); - } + CanFocus = true; + AddMenuStatusBar (view); - Loaded?.Invoke (this, EventArgs.Empty); + return base.Add (view); } - /// Virtual method to invoke the event. - /// - public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) + /// + public override View Remove (View view) { - KeyBindings.Replace (e.OldKey, e.NewKey); - QuitKeyChanged?.Invoke (this, e); + if (this is Toplevel { MenuBar: { } }) + { + RemoveMenuStatusBar (view); + } + + return base.Remove (view); } /// - public override Point? PositionCursor () + public override void RemoveAll () { - if (!IsOverlappedContainer) + if (this == Application.Top) { - if (Focused is null) - { - EnsureFocus (); - } - - return null; + MenuBar?.Dispose (); + MenuBar = null; + StatusBar?.Dispose (); + StatusBar = null; } - // This code path only happens when the Toplevel is an Overlapped container + base.RemoveAll (); + } - if (Focused is null) + internal void AddMenuStatusBar (View view) + { + if (view is MenuBar) { - // TODO: this is an Overlapped hack - foreach (Toplevel top in Application.OverlappedChildren) - { - if (top != this && top.Visible) - { - top.SetFocus (); + MenuBar = view as MenuBar; + } - return null; - } - } + if (view is StatusBar) + { + StatusBar = view as StatusBar; } + } - var cursor2 = base.PositionCursor (); + internal void RemoveMenuStatusBar (View view) + { + if (view is MenuBar) + { + MenuBar?.Dispose (); + MenuBar = null; + } - return null; + if (view is StatusBar) + { + StatusBar?.Dispose (); + StatusBar = null; + } } + // TODO: Overlapped - Rename to AllSubviewsClosed - Move to View? /// - /// Adjusts the location and size of within this Toplevel. Virtual method enabling - /// implementation of specific positions for inherited views. + /// Invoked when the last child of the Toplevel is closed from by + /// . /// - /// The Toplevel to adjust. - public virtual void PositionToplevel (Toplevel top) - { + public event EventHandler AllChildClosed; - View superView = GetLocationEnsuringFullVisibility ( - top, - top.Frame.X, - top.Frame.Y, - out int nx, - out int ny, - out StatusBar sb - ); + // TODO: Overlapped - Rename to *Subviews* - Move to View? + /// + /// Invoked when a child of the Toplevel is closed by + /// . + /// + public event EventHandler ChildClosed; - if (superView is null) - { - return; - } + // TODO: Overlapped - Rename to *Subviews* - Move to View? + /// Invoked when a child Toplevel's has been loaded. + public event EventHandler ChildLoaded; - var layoutSubviews = false; - var maxWidth = 0; + // TODO: Overlapped - Rename to *Subviews* - Move to View? + /// Invoked when a cjhild Toplevel's has been unloaded. + public event EventHandler ChildUnloaded; - if (superView.Margin is { } && superView == top.SuperView) - { - maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; - } + #endregion - if ((superView != top || top?.SuperView is { } || (top != Application.Top && top.Modal) || (top?.SuperView is null && top.IsOverlapped)) - && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y)) - { - if ((top.X is null || top.X is PosAbsolute) && top.Frame.X != nx) - { - top.X = nx; - layoutSubviews = true; - } + #region Life Cycle - if ((top.Y is null || top.Y is PosAbsolute) && top.Frame.Y != ny) - { - top.Y = ny; - layoutSubviews = true; - } - } + // TODO: IRunnable: Re-implement as a property on IRunnable + /// Gets or sets whether the main loop for this is running or not. + /// Setting this property directly is discouraged. Use instead. + public bool Running { get; set; } - // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. - if (sb != null - && !top.Subviews.Contains (sb) - && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) - && top.Height is DimFill - && -top.Height.GetAnchor (0) < 1) - { - top.Height = Dim.Fill (sb.Visible ? 1 : 0); - layoutSubviews = true; - } + // TODO: IRunnable: Re-implement in IRunnable + /// + /// if was already loaded by the + /// , otherwise. + /// + public bool IsLoaded { get; private set; } - if (superView.LayoutNeeded || layoutSubviews) - { - superView.LayoutSubviews (); - } + // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Activating/Activate + /// Invoked when the Toplevel becomes the Toplevel. + public event EventHandler Activate; + + // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Deactivating/Deactivate? + /// Invoked when the Toplevel ceases to be the Toplevel. + public event EventHandler Deactivate; + + /// Invoked when the Toplevel's is closed by . + public event EventHandler Closed; + + /// + /// Invoked when the Toplevel's is being closed by + /// . + /// + public event EventHandler Closing; + + /// + /// Invoked when the has begun to be loaded. A Loaded event handler + /// is a good place to finalize initialization before calling . + /// + public event EventHandler Loaded; + + /// + /// Called from before the redraws for the first + /// time. + /// + /// + /// Overrides must call base.OnLoaded() to ensure any Toplevel subviews are initialized properly and the + /// event is raised. + /// + public virtual void OnLoaded () + { + IsLoaded = true; - if (LayoutNeeded) + foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { - LayoutSubviews (); + tl.OnLoaded (); } - } - /// Invoked when the is changed. - public event EventHandler QuitKeyChanged; + Loaded?.Invoke (this, EventArgs.Empty); + } /// /// Invoked when the main loop has started it's first iteration. Subscribe to this event to @@ -445,31 +373,6 @@ out StatusBar sb /// public event EventHandler Ready; - /// - public override View Remove (View view) - { - if (this is Toplevel { MenuBar: { } }) - { - RemoveMenuStatusBar (view); - } - - return base.Remove (view); - } - - /// - public override void RemoveAll () - { - if (this == Application.Top) - { - MenuBar?.Dispose (); - MenuBar = null; - StatusBar?.Dispose (); - StatusBar = null; - } - - base.RemoveAll (); - } - /// /// Stops and closes this . If this Toplevel is the top-most Toplevel, /// will be called, causing the application to exit. @@ -527,37 +430,22 @@ public virtual void RequestStop () } } - /// - /// Stops and closes the specified by . If is - /// the top-most Toplevel, will be called, causing the application to - /// exit. - /// - /// The Toplevel to request stop. - public virtual void RequestStop (Toplevel top) { top.RequestStop (); } - - /// Invoked when the terminal has been resized. The new of the terminal is provided. - public event EventHandler SizeChanging; - /// /// Invoked when the Toplevel has been unloaded. A Unloaded event handler is a good place /// to dispose objects after calling . /// public event EventHandler Unloaded; - internal void AddMenuStatusBar (View view) - { - if (view is MenuBar) - { - MenuBar = view as MenuBar; - } + internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (this, new (deactivated)); } - if (view is StatusBar) - { - StatusBar = view as StatusBar; - } - } + /// + /// Stops and closes the specified by . If is + /// the top-most Toplevel, will be called, causing the application to + /// exit. + /// + /// The Toplevel to request stop. + public virtual void RequestStop (Toplevel top) { top.RequestStop (); } - internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (this, new ToplevelEventArgs (deactivated)); } internal virtual void OnAllChildClosed () { AllChildClosed?.Invoke (this, EventArgs.Empty); } internal virtual void OnChildClosed (Toplevel top) @@ -567,12 +455,12 @@ internal virtual void OnChildClosed (Toplevel top) SetSubViewNeedsDisplay (); } - ChildClosed?.Invoke (this, new ToplevelEventArgs (top)); + ChildClosed?.Invoke (this, new (top)); } - internal virtual void OnChildLoaded (Toplevel top) { ChildLoaded?.Invoke (this, new ToplevelEventArgs (top)); } - internal virtual void OnChildUnloaded (Toplevel top) { ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top)); } - internal virtual void OnClosed (Toplevel top) { Closed?.Invoke (this, new ToplevelEventArgs (top)); } + internal virtual void OnChildLoaded (Toplevel top) { ChildLoaded?.Invoke (this, new (top)); } + internal virtual void OnChildUnloaded (Toplevel top) { ChildUnloaded?.Invoke (this, new (top)); } + internal virtual void OnClosed (Toplevel top) { Closed?.Invoke (this, new (top)); } internal virtual bool OnClosing (ToplevelClosingEventArgs ev) { @@ -581,7 +469,7 @@ internal virtual bool OnClosing (ToplevelClosingEventArgs ev) return ev.Cancel; } - internal virtual void OnDeactivate (Toplevel activated) { Deactivate?.Invoke (this, new ToplevelEventArgs (activated)); } + internal virtual void OnDeactivate (Toplevel activated) { Deactivate?.Invoke (this, new (activated)); } /// /// Called from after the has entered the first iteration @@ -597,9 +485,6 @@ internal virtual void OnReady () Ready?.Invoke (this, EventArgs.Empty); } - // TODO: Make cancelable? - internal virtual void OnSizeChanging (SizeChangedEventArgs size) { SizeChanging?.Invoke (this, size); } - /// Called from before the is disposed. internal virtual void OnUnloaded () { @@ -611,35 +496,79 @@ internal virtual void OnUnloaded () Unloaded?.Invoke (this, EventArgs.Empty); } - // TODO: v2 - Not sure this is needed anymore. - internal void PositionToplevels () + private void QuitToplevel () { - PositionToplevel (this); - - foreach (View top in Subviews) + if (Application.OverlappedTop is { }) { - if (top is Toplevel) - { - PositionToplevel ((Toplevel)top); - } + RequestStop (this); + } + else + { + Application.RequestStop (); } } - internal void RemoveMenuStatusBar (View view) + #endregion + + #region Draw + + /// + public override void OnDrawContent (Rectangle viewport) { - if (view is MenuBar) + if (!Visible) { - MenuBar?.Dispose (); - MenuBar = null; + return; } - if (view is StatusBar) + if (NeedsDisplay || SubViewNeedsDisplay /*|| LayoutNeeded*/) { - StatusBar?.Dispose (); - StatusBar = null; + Clear (); + + //LayoutSubviews (); + //PositionToplevels (); + + //if (this == Application.OverlappedTop) + //{ + // foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) + // { + // if (top.Frame.IntersectsWith (Viewport)) + // { + // if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) + // { + // top.SetNeedsLayout (); + // top.SetNeedsDisplay (top.Viewport); + // top.Draw (); + // top.OnRenderLineCanvas (); + // } + // } + // } + //} + + // BUGBUG: This appears to be a hack to get ScrollBarViews to render correctly. + foreach (View view in Subviews) + { + if (view.Frame.IntersectsWith (Viewport) && !OutsideTopFrame (this)) + { + //view.SetNeedsLayout (); + view.SetNeedsDisplay (); + view.SetSubViewNeedsDisplay (); + } + } + + base.OnDrawContent (viewport); } } + #endregion + + #region Focus + + /// + public override bool OnEnter (View view) { return MostFocused?.OnEnter (view) ?? base.OnEnter (view); } + + /// + public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } + private void FocusNearestView (IEnumerable views, NavigationDirection direction) { if (views is null) @@ -785,6 +714,117 @@ private void MovePreviousViewOrTop () } } + #endregion + + #region Size / Position Management + + // TODO: Make cancelable? + internal virtual void OnSizeChanging (SizeChangedEventArgs size) { SizeChanging?.Invoke (this, size); } + + /// + public override Point? PositionCursor () + { + if (!IsOverlappedContainer) + { + if (Focused is null) + { + EnsureFocus (); + } + + return null; + } + + // This code path only happens when the Toplevel is an Overlapped container + + if (Focused is null) + { + // TODO: this is an Overlapped hack + foreach (Toplevel top in Application.OverlappedChildren) + { + if (top != this && top.Visible) + { + top.SetFocus (); + + return null; + } + } + } + + Point? cursor2 = base.PositionCursor (); + + return null; + } + + /// + /// Adjusts the location and size of within this Toplevel. Virtual method enabling + /// implementation of specific positions for inherited views. + /// + /// The Toplevel to adjust. + public virtual void PositionToplevel (Toplevel top) + { + View superView = GetLocationEnsuringFullVisibility ( + top, + top.Frame.X, + top.Frame.Y, + out int nx, + out int ny, + out StatusBar sb + ); + + if (superView is null) + { + return; + } + + var layoutSubviews = false; + var maxWidth = 0; + + if (superView.Margin is { } && superView == top.SuperView) + { + maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; + } + + if ((superView != top || top?.SuperView is { } || (top != Application.Top && top.Modal) || (top?.SuperView is null && top.IsOverlapped)) + && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y)) + { + if ((top.X is null || top.X is PosAbsolute) && top.Frame.X != nx) + { + top.X = nx; + layoutSubviews = true; + } + + if ((top.Y is null || top.Y is PosAbsolute) && top.Frame.Y != ny) + { + top.Y = ny; + layoutSubviews = true; + } + } + + // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. + if (sb != null + && !top.Subviews.Contains (sb) + && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) + && top.Height is DimFill + && -top.Height.GetAnchor (0) < 1) + { + top.Height = Dim.Fill (sb.Visible ? 1 : 0); + layoutSubviews = true; + } + + if (superView.LayoutNeeded || layoutSubviews) + { + superView.LayoutSubviews (); + } + + if (LayoutNeeded) + { + LayoutSubviews (); + } + } + + /// Invoked when the terminal has been resized. The new of the terminal is provided. + public event EventHandler SizeChanging; + private bool OutsideTopFrame (Toplevel top) { if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) @@ -795,17 +835,21 @@ private bool OutsideTopFrame (Toplevel top) return false; } - private void QuitToplevel () + // TODO: v2 - Not sure this is needed anymore. + internal void PositionToplevels () { - if (Application.OverlappedTop is { }) - { - RequestStop (this); - } - else + PositionToplevel (this); + + foreach (View top in Subviews) { - Application.RequestStop (); + if (top is Toplevel) + { + PositionToplevel ((Toplevel)top); + } } } + + #endregion } /// From d44e8d3b81b0d98f9c022d1ef2c8839728c1f9df Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 23 Jul 2024 09:59:30 -0600 Subject: [PATCH 70/96] More Toplevel.cs organization & docs --- Terminal.Gui/Input/Command.cs | 1 + Terminal.Gui/Views/Toplevel.cs | 61 ++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index 566cc9336c..c6a03606b7 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -221,6 +221,7 @@ public enum Command /// Pastes the current selection. Paste, + // TODO: IRunnable - Should be renamed QuitRunnable /// Quit a . QuitToplevel, diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index fa526904d6..e14d025096 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -39,7 +39,10 @@ public Toplevel () MouseClick += Toplevel_MouseClick; } - // TODO: IRunnable: Re-implement - Modal means IRunnable, ViewArrangement.Overlapped where modalView.Z > allOtherViews.Max (v = v.Z). + + #region Keyboard & Mouse + + // TODO: IRunnable: Re-implement - Modal means IRunnable, ViewArrangement.Overlapped where modalView.Z > allOtherViews.Max (v = v.Z), and exclusive key/mouse input. /// /// Determines whether the is modal or not. If set to false (the default): /// @@ -62,13 +65,12 @@ public Toplevel () /// public bool Modal { get; set; } - #region Keyboard & Mouse - + // TODO: Overlapped: Figure out how these keybindings should work. private void ConfigureKeyBindings () { // Things this view knows how to do AddCommand ( - Command.QuitToplevel, + Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic. () => { QuitToplevel (); @@ -77,8 +79,10 @@ private void ConfigureKeyBindings () } ); + /// TODO: Overlapped: Add Command.ShowHide + AddCommand ( - Command.Suspend, + Command.Suspend, // TODO: Move to Application () => { Driver.Suspend (); @@ -89,7 +93,7 @@ private void ConfigureKeyBindings () ); AddCommand ( - Command.NextView, + Command.NextView, // TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { MoveNextView (); @@ -99,7 +103,7 @@ private void ConfigureKeyBindings () ); AddCommand ( - Command.PreviousView, + Command.PreviousView,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { MovePreviousView (); @@ -109,7 +113,7 @@ private void ConfigureKeyBindings () ); AddCommand ( - Command.NextViewOrTop, + Command.NextViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { MoveNextViewOrTop (); @@ -119,7 +123,7 @@ private void ConfigureKeyBindings () ); AddCommand ( - Command.PreviousViewOrTop, + Command.PreviousViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { MovePreviousViewOrTop (); @@ -132,7 +136,7 @@ private void ConfigureKeyBindings () Command.Refresh, () => { - Application.Refresh (); + Application.Refresh (); // TODO: Move to Application return true; } @@ -525,24 +529,25 @@ public override void OnDrawContent (Rectangle viewport) Clear (); //LayoutSubviews (); - //PositionToplevels (); - - //if (this == Application.OverlappedTop) - //{ - // foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) - // { - // if (top.Frame.IntersectsWith (Viewport)) - // { - // if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) - // { - // top.SetNeedsLayout (); - // top.SetNeedsDisplay (top.Viewport); - // top.Draw (); - // top.OnRenderLineCanvas (); - // } - // } - // } - //} + PositionToplevels (); + + if (this == Application.OverlappedTop) + { + // This enables correct draw behavior when switching between overlapped subviews + foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) + { + if (top.Frame.IntersectsWith (Viewport)) + { + if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) + { + top.SetNeedsLayout (); + top.SetNeedsDisplay (top.Viewport); + top.Draw (); + top.OnRenderLineCanvas (); + } + } + } + } // BUGBUG: This appears to be a hack to get ScrollBarViews to render correctly. foreach (View view in Subviews) From d813b1f137c87c86271d71539285cc64343b1826 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 23 Jul 2024 14:19:57 -0600 Subject: [PATCH 71/96] Fixed dumb enum cast in KeyBinding code --- Terminal.Gui/Application/Application.Keyboard.cs | 3 ++- Terminal.Gui/Input/Command.cs | 4 +++- Terminal.Gui/Input/KeyBindingScope.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 703a84982c..59fecc14a3 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -144,7 +144,8 @@ public static bool OnKeyDown (Key keyEvent) { foreach (View view in binding.Value) { - if (view is {} && view.KeyBindings.TryGet (binding.Key, (KeyBindingScope)0xFFFF, out KeyBinding kb)) + if (view is { } + && view.KeyBindings.TryGet (binding.Key, KeyBindingScope.Focused | KeyBindingScope.HotKey | KeyBindingScope.Application, out KeyBinding kb)) { //bool? handled = view.InvokeCommands (kb.Commands, binding.Key, kb); bool? handled = view?.OnInvokingKeyBindings (keyEvent, kb.Scope); diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index c6a03606b7..42dcb16e62 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -221,10 +221,12 @@ public enum Command /// Pastes the current selection. Paste, - // TODO: IRunnable - Should be renamed QuitRunnable + /// TODO: IRunnable: Rename to Command.Quit to make more generic. /// Quit a . QuitToplevel, + /// TODO: Overlapped: Add Command.ShowHide + /// Suspend an application (Only implemented in ). Suspend, diff --git a/Terminal.Gui/Input/KeyBindingScope.cs b/Terminal.Gui/Input/KeyBindingScope.cs index 0c75299c7b..633e6d7b0b 100644 --- a/Terminal.Gui/Input/KeyBindingScope.cs +++ b/Terminal.Gui/Input/KeyBindingScope.cs @@ -45,5 +45,5 @@ public enum KeyBindingScope /// any of its subviews, and if the key was not bound to a . /// /// - Application = 4 + Application = 4, } From fe5cbe4df3192d22474f3f0fd834004dc3d84711 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 23 Jul 2024 14:42:50 -0600 Subject: [PATCH 72/96] More Toplevel.cs organization & docs --- Terminal.Gui/Application/Application.Keyboard.cs | 9 +++++++-- Terminal.Gui/Application/Application.Run.cs | 1 + Terminal.Gui/Application/Application.Toplevel.cs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 59fecc14a3..009d8c425d 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -230,9 +230,14 @@ public static bool OnKeyUp (Key a) /// This is an internal method used by the class to add Application key bindings. /// /// The key being bound. - /// The view that is bound to the key. - internal static void AddKeyBinding (Key key, View view) + /// The view that is bound to the key. If , will be used. + internal static void AddKeyBinding (Key key, [CanBeNull] View view) { + if (view is null) + { + view = Current; + } + if (!_keyBindings.ContainsKey (key)) { _keyBindings [key] = []; diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 06c1912302..e5e51a0fba 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -577,6 +577,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) Iteration?.Invoke (null, new ()); EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); + // TODO: Overlapped - Move elsewhere if (state.Toplevel != Current) { OverlappedTop?.OnDeactivate (state.Toplevel); diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs index 6d25f7d022..b08f9e9606 100644 --- a/Terminal.Gui/Application/Application.Toplevel.cs +++ b/Terminal.Gui/Application/Application.Toplevel.cs @@ -18,7 +18,7 @@ public static partial class Application // Toplevel handling /// . /// /// - /// Only relevant in scenarios where is . + /// This will only be distinct from in scenarios where is . /// /// The current. public static Toplevel Current { get; private set; } From f8e8aff29f46a1e9cbd15a7137313d46f67abb8e Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 23 Jul 2024 15:36:36 -0600 Subject: [PATCH 73/96] More Toplevel.cs organization & docs --- .../Application/Application.Initialization.cs | 3 + .../Application/Application.Keyboard.cs | 46 +++- Terminal.Gui/Application/Application.Mouse.cs | 4 +- .../Application/Application.Navigation.cs | 211 ++++++++++++++++++ .../Application/Application.Overlapped.cs | 7 + Terminal.Gui/Application/Application.Run.cs | 1 + .../Application/Application.Toplevel.cs | 22 +- Terminal.Gui/View/ViewSubViews.cs | 32 ++- Terminal.Gui/Views/Toplevel.cs | 27 ++- Terminal.Gui/Views/ToplevelOverlapped.cs | 209 ----------------- 10 files changed, 319 insertions(+), 243 deletions(-) create mode 100644 Terminal.Gui/Application/Application.Navigation.cs create mode 100644 Terminal.Gui/Application/Application.Overlapped.cs diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 16b7b83d42..0434eb873d 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -88,6 +89,8 @@ internal static void InternalInit ( Load (); Apply (); + AddToplevelKeyBindings (); + // Ignore Configuration for ForceDriver if driverName is specified if (!string.IsNullOrEmpty (driverName)) { diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 009d8c425d..a299a7aee0 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +#nullable enable +using System.Text.Json.Serialization; namespace Terminal.Gui; @@ -140,7 +141,7 @@ public static bool OnKeyDown (Key keyEvent) // Invoke any global (Application-scoped) KeyBindings. // The first view that handles the key will stop the loop. - foreach (KeyValuePair> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode)) + foreach (KeyValuePair> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode)) { foreach (View view in binding.Value) { @@ -154,7 +155,6 @@ public static bool OnKeyDown (Key keyEvent) { return true; } - } } } @@ -216,12 +216,12 @@ public static bool OnKeyUp (Key a) /// /// The key bindings. /// - private static readonly Dictionary> _keyBindings = new (); + private static readonly Dictionary> _keyBindings = new (); /// /// Gets the list of key bindings. /// - public static Dictionary> GetKeyBindings () { return _keyBindings; } + public static Dictionary> GetKeyBindings () { return _keyBindings; } /// /// Adds an scoped key binding. @@ -231,13 +231,8 @@ public static bool OnKeyUp (Key a) /// /// The key being bound. /// The view that is bound to the key. If , will be used. - internal static void AddKeyBinding (Key key, [CanBeNull] View view) + internal static void AddKeyBinding (Key key, View? view) { - if (view is null) - { - view = Current; - } - if (!_keyBindings.ContainsKey (key)) { _keyBindings [key] = []; @@ -246,6 +241,35 @@ internal static void AddKeyBinding (Key key, [CanBeNull] View view) _keyBindings [key].Add (view); } + internal static void AddToplevelKeyBindings () + { +// // Default keybindings for this view +// AddKeyBinding (Application.QuitKey, null); +// AddKeyBinding (Key.CursorRight, null); +// AddKeyBinding (Key.CursorDown, null); +// AddKeyBinding (Key.CursorLeft, null); +// AddKeyBinding (Key.CursorUp, null); +// AddKeyBinding (Key.Tab, null); +// AddKeyBinding (Key.Tab.WithShift, null); +// AddKeyBinding (Key.Tab.WithCtrl, null); +// AddKeyBinding (Key.Tab.WithShift.WithCtrl, null); +// AddKeyBinding (Key.F5, null); +// AddKeyBinding (Application.AlternateForwardKey, null); // Needed on Unix +// AddKeyBinding (Application.AlternateBackwardKey, null); // Needed on Unix + +// if (Environment.OSVersion.Platform == PlatformID.Unix) +// { +// AddKeyBinding (Key.Z.WithCtrl, null); +// } + +//#if UNIX_KEY_BINDINGS +// KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix +// KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix +// KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix +// KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix +//#endif + } + /// /// Gets the list of Views that have key bindings. /// diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs index 7cad055d58..61fc6d63e4 100644 --- a/Terminal.Gui/Application/Application.Mouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -1,5 +1,5 @@ -namespace Terminal.Gui; - +#nullable enable +namespace Terminal.Gui; public static partial class Application // Mouse handling { #region Mouse handling diff --git a/Terminal.Gui/Application/Application.Navigation.cs b/Terminal.Gui/Application/Application.Navigation.cs new file mode 100644 index 0000000000..0bbce67e6b --- /dev/null +++ b/Terminal.Gui/Application/Application.Navigation.cs @@ -0,0 +1,211 @@ +namespace Terminal.Gui; + +public static partial class Application +{ + /// + /// Gets the list of the Overlapped children which are not modal from the + /// . + /// + public static List OverlappedChildren + { + get + { + if (OverlappedTop is { }) + { + List _overlappedChildren = new (); + + foreach (Toplevel top in _topLevels) + { + if (top != OverlappedTop && !top.Modal) + { + _overlappedChildren.Add (top); + } + } + + return _overlappedChildren; + } + + return null; + } + } + +#nullable enable + /// + /// The object used for the application on startup which + /// is true. + /// + public static Toplevel? OverlappedTop + { + get + { + if (Top is { IsOverlappedContainer: true }) + { + return Top; + } + + return null; + } + } +#nullable restore + + /// Brings the superview of the most focused overlapped view is on front. + public static void BringOverlappedTopToFront () + { + if (OverlappedTop is { }) + { + return; + } + + View top = FindTopFromView (Top?.MostFocused); + + if (top is Toplevel && Top.Subviews.Count > 1 && Top.Subviews [^1] != top) + { + Top.BringSubviewToFront (top); + } + } + + /// Gets the current visible Toplevel overlapped child that matches the arguments pattern. + /// The type. + /// The strings to exclude. + /// The matched view. + public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null) + { + if (OverlappedTop is null) + { + return null; + } + + foreach (Toplevel top in OverlappedChildren) + { + if (type is { } && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false) + { + return top; + } + + if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data.ToString ()) == true) + { + continue; + } + + return top; + } + + return null; + } + + /// + /// Move to the next Overlapped child from the and set it as the if + /// it is not already. + /// + /// + /// + public static bool MoveToOverlappedChild (Toplevel top) + { + if (top.Visible && OverlappedTop is { } && Current?.Modal == false) + { + lock (_topLevels) + { + _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Current = top; + } + + return true; + } + + return false; + } + + /// Move to the next Overlapped child from the . + public static void OverlappedMoveNext () + { + if (OverlappedTop is { } && !Current.Modal) + { + lock (_topLevels) + { + _topLevels.MoveNext (); + var isOverlapped = false; + + while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) + { + if (!isOverlapped && _topLevels.Peek () == OverlappedTop) + { + isOverlapped = true; + } + else if (isOverlapped && _topLevels.Peek () == OverlappedTop) + { + MoveCurrent (Top); + + break; + } + + _topLevels.MoveNext (); + } + + Current = _topLevels.Peek (); + } + } + } + + /// Move to the previous Overlapped child from the . + public static void OverlappedMovePrevious () + { + if (OverlappedTop is { } && !Current.Modal) + { + lock (_topLevels) + { + _topLevels.MovePrevious (); + var isOverlapped = false; + + while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) + { + if (!isOverlapped && _topLevels.Peek () == OverlappedTop) + { + isOverlapped = true; + } + else if (isOverlapped && _topLevels.Peek () == OverlappedTop) + { + MoveCurrent (Top); + + break; + } + + _topLevels.MovePrevious (); + } + + Current = _topLevels.Peek (); + } + } + } + + private static bool OverlappedChildNeedsDisplay () + { + if (OverlappedTop is null) + { + return false; + } + + foreach (Toplevel top in _topLevels) + { + if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) + { + OverlappedTop.SetSubViewNeedsDisplay (); + + return true; + } + } + + return false; + } + + private static bool SetCurrentOverlappedAsTop () + { + if (OverlappedTop is null && Current != Top && Current?.SuperView is null && Current?.Modal == false) + { + Top = Current; + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/Terminal.Gui/Application/Application.Overlapped.cs b/Terminal.Gui/Application/Application.Overlapped.cs new file mode 100644 index 0000000000..58eccfa3a2 --- /dev/null +++ b/Terminal.Gui/Application/Application.Overlapped.cs @@ -0,0 +1,7 @@ +#nullable enable +namespace Terminal.Gui; + +public static partial class Application // App-level View Navigation +{ + +} \ No newline at end of file diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index e5e51a0fba..541ad71417 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs index b08f9e9606..d8996a3838 100644 --- a/Terminal.Gui/Application/Application.Toplevel.cs +++ b/Terminal.Gui/Application/Application.Toplevel.cs @@ -1,10 +1,10 @@ +#nullable enable namespace Terminal.Gui; public static partial class Application // Toplevel handling { - /// Holds the stack of TopLevel views. - // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What + /// Holds the stack of TopLevel views. // about TopLevels that are just a SubView of another View? internal static readonly Stack _topLevels = new (); @@ -12,6 +12,7 @@ public static partial class Application // Toplevel handling /// The top. public static Toplevel Top { get; private set; } + // TODO: Determine why this can't just return _topLevels.Peek()? /// /// The current object. This is updated in enters and leaves to /// point to the current @@ -23,6 +24,9 @@ public static partial class Application // Toplevel handling /// The current. public static Toplevel Current { get; private set; } + /// + /// If is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current. + /// private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) { if (!topLevel.Running @@ -49,6 +53,12 @@ private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) } } + /// + /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the . + /// + /// + /// + /// private static Toplevel FindDeepestTop (Toplevel start, in Point location) { if (!start.Frame.Contains (location)) @@ -78,6 +88,9 @@ private static Toplevel FindDeepestTop (Toplevel start, in Point location) return start; } + /// + /// Given , returns the first Superview up the chain that is . + /// private static View FindTopFromView (View view) { View top = view?.SuperView is { } && view?.SuperView != Top @@ -92,6 +105,11 @@ private static View FindTopFromView (View view) return top; } + /// + /// If the is not the then is moved to the top of the Toplevel stack and made Current. + /// + /// + /// private static bool MoveCurrent (Toplevel top) { // The Current is modal and the top is not modal Toplevel then diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 99f9e119b5..38863828a9 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -627,7 +627,7 @@ private void SetFocus (View view) } } - /// Causes the specified view and the entire parent hierarchy to have the focused order updated. + /// Causes this view to be focused and entire Superview hierarchy to have the focused order updated. public void SetFocus () { if (!CanBeVisible (this) || !Enabled) @@ -651,7 +651,7 @@ public void SetFocus () } /// - /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, + /// If there is no focused subview, calls or based on . /// does nothing. /// public void EnsureFocus () @@ -669,7 +669,9 @@ public void EnsureFocus () } } - /// Focuses the first focusable subview if one exists. + /// + /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself. + /// public void FocusFirst () { if (!CanBeVisible (this)) @@ -695,7 +697,9 @@ public void FocusFirst () } } - /// Focuses the last focusable subview if one exists. + /// + /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself. + /// public void FocusLast () { if (!CanBeVisible (this)) @@ -725,7 +729,9 @@ public void FocusLast () } } - /// Focuses the previous view. + /// + /// Focuses the previous view in . If there is no previous view, the focus is set to the view itself. + /// /// if previous was focused, otherwise. public bool FocusPrev () { @@ -736,7 +742,7 @@ public bool FocusPrev () FocusDirection = NavigationDirection.Backward; - if (_tabIndexes is null || _tabIndexes.Count == 0) + if (TabIndexes is null || TabIndexes.Count == 0) { return false; } @@ -750,10 +756,10 @@ public bool FocusPrev () int focusedIdx = -1; - for (int i = _tabIndexes.Count; i > 0;) + for (int i = TabIndexes.Count; i > 0;) { i--; - View w = _tabIndexes [i]; + View w = TabIndexes [i]; if (w.HasFocus) { @@ -791,7 +797,9 @@ public bool FocusPrev () return false; } - /// Focuses the next view. + /// + /// Focuses the previous view in . If there is no previous view, the focus is set to the view itself. + /// /// if next was focused, otherwise. public bool FocusNext () { @@ -802,7 +810,7 @@ public bool FocusNext () FocusDirection = NavigationDirection.Forward; - if (_tabIndexes is null || _tabIndexes.Count == 0) + if (TabIndexes is null || TabIndexes.Count == 0) { return false; } @@ -816,9 +824,9 @@ public bool FocusNext () int focusedIdx = -1; - for (var i = 0; i < _tabIndexes.Count; i++) + for (var i = 0; i < TabIndexes.Count; i++) { - View w = _tabIndexes [i]; + View w = TabIndexes [i]; if (w.HasFocus) { diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index e14d025096..0e70904927 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -80,7 +80,7 @@ private void ConfigureKeyBindings () ); /// TODO: Overlapped: Add Command.ShowHide - + AddCommand ( Command.Suspend, // TODO: Move to Application () => @@ -566,7 +566,7 @@ public override void OnDrawContent (Rectangle viewport) #endregion - #region Focus + #region Navigation /// public override bool OnEnter (View view) { return MostFocused?.OnEnter (view) ?? base.OnEnter (view); } @@ -574,9 +574,14 @@ public override void OnDrawContent (Rectangle viewport) /// public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } - private void FocusNearestView (IEnumerable views, NavigationDirection direction) + /// + /// Sets the focus to the next view in the list. If the last view is focused, the first view is focused. + /// + /// + /// + private void FocusNearestView (IEnumerable viewsInTabIndexes, NavigationDirection direction) { - if (views is null) + if (viewsInTabIndexes is null) { return; } @@ -585,7 +590,7 @@ private void FocusNearestView (IEnumerable views, NavigationDirection dire var focusProcessed = false; var idx = 0; - foreach (View v in views) + foreach (View v in viewsInTabIndexes) { if (v == this) { @@ -610,15 +615,20 @@ private void FocusNearestView (IEnumerable views, NavigationDirection dire return; } } - else if (found && !focusProcessed && idx == views.Count () - 1) + else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1) { - views.ToList () [0].SetFocus (); + viewsInTabIndexes.ToList () [0].SetFocus (); } idx++; } } + /// + /// Gets the deepest focused subview of the specified . + /// + /// + /// private View GetDeepestFocusedSubview (View view) { if (view is null) @@ -637,6 +647,9 @@ private View GetDeepestFocusedSubview (View view) return view; } + /// + /// Moves the focus to + /// private void MoveNextView () { View old = GetDeepestFocusedSubview (Focused); diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs index 0d93ef79f6..3541ea0917 100644 --- a/Terminal.Gui/Views/ToplevelOverlapped.cs +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -9,212 +9,3 @@ public partial class Toplevel public bool IsOverlappedContainer { get; set; } } -public static partial class Application -{ - /// - /// Gets the list of the Overlapped children which are not modal from the - /// . - /// - public static List OverlappedChildren - { - get - { - if (OverlappedTop is { }) - { - List _overlappedChildren = new (); - - foreach (Toplevel top in _topLevels) - { - if (top != OverlappedTop && !top.Modal) - { - _overlappedChildren.Add (top); - } - } - - return _overlappedChildren; - } - - return null; - } - } - - #nullable enable - /// - /// The object used for the application on startup which - /// is true. - /// - public static Toplevel? OverlappedTop - { - get - { - if (Top is { IsOverlappedContainer: true }) - { - return Top; - } - - return null; - } - } - #nullable restore - - /// Brings the superview of the most focused overlapped view is on front. - public static void BringOverlappedTopToFront () - { - if (OverlappedTop is { }) - { - return; - } - - View top = FindTopFromView (Top?.MostFocused); - - if (top is Toplevel && Top.Subviews.Count > 1 && Top.Subviews [^1] != top) - { - Top.BringSubviewToFront (top); - } - } - - /// Gets the current visible Toplevel overlapped child that matches the arguments pattern. - /// The type. - /// The strings to exclude. - /// The matched view. - public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null) - { - if (OverlappedTop is null) - { - return null; - } - - foreach (Toplevel top in OverlappedChildren) - { - if (type is { } && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false) - { - return top; - } - - if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data.ToString ()) == true) - { - continue; - } - - return top; - } - - return null; - } - - /// - /// Move to the next Overlapped child from the and set it as the if - /// it is not already. - /// - /// - /// - public static bool MoveToOverlappedChild (Toplevel top) - { - if (top.Visible && OverlappedTop is { } && Current?.Modal == false) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } - - return true; - } - - return false; - } - - /// Move to the next Overlapped child from the . - public static void OverlappedMoveNext () - { - if (OverlappedTop is { } && !Current.Modal) - { - lock (_topLevels) - { - _topLevels.MoveNext (); - var isOverlapped = false; - - while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) - { - if (!isOverlapped && _topLevels.Peek () == OverlappedTop) - { - isOverlapped = true; - } - else if (isOverlapped && _topLevels.Peek () == OverlappedTop) - { - MoveCurrent (Top); - - break; - } - - _topLevels.MoveNext (); - } - - Current = _topLevels.Peek (); - } - } - } - - /// Move to the previous Overlapped child from the . - public static void OverlappedMovePrevious () - { - if (OverlappedTop is { } && !Current.Modal) - { - lock (_topLevels) - { - _topLevels.MovePrevious (); - var isOverlapped = false; - - while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) - { - if (!isOverlapped && _topLevels.Peek () == OverlappedTop) - { - isOverlapped = true; - } - else if (isOverlapped && _topLevels.Peek () == OverlappedTop) - { - MoveCurrent (Top); - - break; - } - - _topLevels.MovePrevious (); - } - - Current = _topLevels.Peek (); - } - } - } - - private static bool OverlappedChildNeedsDisplay () - { - if (OverlappedTop is null) - { - return false; - } - - foreach (Toplevel top in _topLevels) - { - if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) - { - OverlappedTop.SetSubViewNeedsDisplay (); - - return true; - } - } - - return false; - } - - private static bool SetCurrentOverlappedAsTop () - { - if (OverlappedTop is null && Current != Top && Current?.SuperView is null && Current?.Modal == false) - { - Top = Current; - - return true; - } - - return false; - } -} From feaf5c0f6c34f0767d660fa5916580b8835e7569 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 23 Jul 2024 18:12:43 -0600 Subject: [PATCH 74/96] WIP (Very Broken) try to move keybindings out of Toplevel to app --- .../Application/Application.Initialization.cs | 2 +- .../Application/Application.Keyboard.cs | 207 ++++++++++++++---- Terminal.Gui/Input/KeyBindings.cs | 19 +- Terminal.Gui/View/ViewKeyboard.cs | 2 +- 4 files changed, 179 insertions(+), 51 deletions(-) diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 0434eb873d..2e184b2853 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -89,7 +89,7 @@ internal static void InternalInit ( Load (); Apply (); - AddToplevelKeyBindings (); + AddApplicationKeyBindings (); // Ignore Configuration for ForceDriver if driverName is specified if (!string.IsNullOrEmpty (driverName)) diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index a299a7aee0..209520f47c 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -213,61 +213,172 @@ public static bool OnKeyUp (Key a) return false; } - /// - /// The key bindings. - /// - private static readonly Dictionary> _keyBindings = new (); + /// Gets the key bindings for this view. + public static KeyBindings KeyBindings { get; internal set; } = new (); /// - /// Gets the list of key bindings. + /// Commands for Application. /// - public static Dictionary> GetKeyBindings () { return _keyBindings; } + private static Dictionary> CommandImplementations { get; } = new (); /// - /// Adds an scoped key binding. + /// + /// Sets the function that will be invoked for a . + /// + /// + /// If AddCommand has already been called for will + /// replace the old one. + /// /// /// - /// This is an internal method used by the class to add Application key bindings. + /// + /// This version of AddCommand is for commands that do not require a . + /// /// - /// The key being bound. - /// The view that is bound to the key. If , will be used. - internal static void AddKeyBinding (Key key, View? view) + /// The command. + /// The function. + private static void AddCommand (Command command, Func f) { - if (!_keyBindings.ContainsKey (key)) - { - _keyBindings [key] = []; - } - - _keyBindings [key].Add (view); + CommandImplementations [command] = ctx => f (); } - internal static void AddToplevelKeyBindings () + ///// + ///// The key bindings. + ///// + //private static readonly Dictionary> _keyBindings = new (); + + ///// + ///// Gets the list of key bindings. + ///// + //public static Dictionary> GetKeyBindings () { return _keyBindings; } + + ///// + ///// Adds an scoped key binding. + ///// + ///// + ///// This is an internal method used by the class to add Application key bindings. + ///// + ///// The key being bound. + ///// The view that is bound to the key. If , will be used. + //internal static void AddKeyBinding (Key key, View? view) + //{ + // if (!_keyBindings.ContainsKey (key)) + // { + // _keyBindings [key] = []; + // } + + // _keyBindings [key].Add (view); + //} + + internal static void AddApplicationKeyBindings () { -// // Default keybindings for this view -// AddKeyBinding (Application.QuitKey, null); -// AddKeyBinding (Key.CursorRight, null); -// AddKeyBinding (Key.CursorDown, null); -// AddKeyBinding (Key.CursorLeft, null); -// AddKeyBinding (Key.CursorUp, null); -// AddKeyBinding (Key.Tab, null); -// AddKeyBinding (Key.Tab.WithShift, null); -// AddKeyBinding (Key.Tab.WithCtrl, null); -// AddKeyBinding (Key.Tab.WithShift.WithCtrl, null); -// AddKeyBinding (Key.F5, null); -// AddKeyBinding (Application.AlternateForwardKey, null); // Needed on Unix -// AddKeyBinding (Application.AlternateBackwardKey, null); // Needed on Unix - -// if (Environment.OSVersion.Platform == PlatformID.Unix) -// { -// AddKeyBinding (Key.Z.WithCtrl, null); -// } - -//#if UNIX_KEY_BINDINGS -// KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix -// KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix -// KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix -// KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix -//#endif + // Things this view knows how to do + AddCommand ( + Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic. + () => + { + if (OverlappedTop is { }) + { + RequestStop (Current); + } + else + { + Application.RequestStop (); + } + + return true; + } + ); + + AddCommand ( + Command.Suspend, + () => + { + Driver?.Suspend (); + + return true; + } + ); + + AddCommand ( + Command.NextView, // TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + () => + { + Current.MoveNextView (); + + return true; + } + ); + + AddCommand ( + Command.PreviousView,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + () => + { + Current.MovePreviousView (); + + return true; + } + ); + + AddCommand ( + Command.NextViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + () => + { + Current.MoveNextViewOrTop (); + + return true; + } + ); + + AddCommand ( + Command.PreviousViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + () => + { + Current.MovePreviousViewOrTop (); + + return true; + } + ); + + AddCommand ( + Command.Refresh, + () => + { + Refresh (); + + return true; + } + ); + + + KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel); + + KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView); + KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView); + KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView); + KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView); + + KeyBindings.Add (Key.Tab, KeyBindingScope.Application, Command.NextView); + KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.Application, Command.PreviousView); + KeyBindings.Add (Key.Tab.WithCtrl, KeyBindingScope.Application, Command.NextViewOrTop); + KeyBindings.Add (Key.Tab.WithShift.WithCtrl, KeyBindingScope.Application, Command.PreviousViewOrTop); + + // TODO: Refresh Key should be configurable + KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh); + KeyBindings.Add (Application.AlternateForwardKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix + KeyBindings.Add (Application.AlternateBackwardKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix + + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend); + } + +#if UNIX_KEY_BINDINGS + KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix + KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix + KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix + KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix +#endif } /// @@ -277,7 +388,15 @@ internal static void AddToplevelKeyBindings () /// This is an internal method used by the class to add Application key bindings. /// /// The list of Views that have Application-scoped key bindings. - internal static List GetViewsWithKeyBindings () { return _keyBindings.Values.SelectMany (v => v).ToList (); } + internal static List GetViewKeyBindings () + { + // Get the list of views that do not have Application-scoped key bindings + return KeyBindings.Bindings + .Where (kv => kv.Value.Scope != KeyBindingScope.Application) + .Select (kv => kv.Value) + .Distinct () + .ToList (); + } /// /// Gets the list of Views that have key bindings for the specified key. diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs index a551696068..0ea164a401 100644 --- a/Terminal.Gui/Input/KeyBindings.cs +++ b/Terminal.Gui/Input/KeyBindings.cs @@ -11,7 +11,7 @@ public class KeyBindings { /// /// Initializes a new instance. This constructor is used when the are not bound to a - /// , such as in unit tests. + /// . This is used for Application.KeyBindings and unit tests. /// public KeyBindings () { } @@ -21,6 +21,9 @@ public KeyBindings () { } /// /// The view that the are bound to. /// + /// + /// If , the are not bound to a . This is used for Application.KeyBindings. + /// public View? BoundView { get; } // TODO: Add a dictionary comparer that ignores Scope @@ -33,6 +36,11 @@ public KeyBindings () { } /// public void Add (Key key, KeyBinding binding) { + if (BoundView is { } && binding.Scope.FastHasFlags (KeyBindingScope.Application)) + { + throw new ArgumentException ("Application scoped KeyBindings must be added via Application.KeyBindings.Add"); + } + if (TryGet (key, out KeyBinding _)) { Bindings [key] = binding; @@ -40,10 +48,6 @@ public void Add (Key key, KeyBinding binding) else { Bindings.Add (key, binding); - if (binding.Scope.HasFlag (KeyBindingScope.Application)) - { - Application.AddKeyBinding (key, BoundView); - } } } @@ -67,6 +71,11 @@ public void Add (Key key, KeyBinding binding) /// public void Add (Key key, KeyBindingScope scope, params Command [] commands) { + if (BoundView is { } && scope.FastHasFlags (KeyBindingScope.Application)) + { + throw new ArgumentException ("Application scoped KeyBindings must be added via Application.KeyBindings.Add"); + } + if (key is null || !key.IsValid) { //throw new ArgumentException ("Invalid Key", nameof (commands)); diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index d103d894d8..36ec6e6614 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -641,7 +641,7 @@ public virtual bool OnKeyUp (Key keyEvent) /// public virtual bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope) { - // fire event only if there's an hotkey binding for the key + // fire event only if there's a hotkey binding for the key if (KeyBindings.TryGet (keyEvent, scope, out KeyBinding kb)) { InvokingKeyBindings?.Invoke (this, keyEvent); From c03dd320318f3a2d287dfed973c6df013871ce99 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 12:28:30 -0600 Subject: [PATCH 75/96] Moved Toplevel keybindings out of Toplevel to Application. Still need to move navigation code out of Toplevel --- .../Application/Application.Keyboard.cs | 139 +++-- Terminal.Gui/Application/Application.cs | 2 +- Terminal.Gui/Input/KeyBinding.cs | 16 + Terminal.Gui/Input/KeyBindings.cs | 138 ++++- Terminal.Gui/View/ViewKeyboard.cs | 15 +- Terminal.Gui/Views/DateField.cs | 26 +- Terminal.Gui/Views/FileDialog.cs | 10 +- Terminal.Gui/Views/Menu/Menu.cs | 3 + Terminal.Gui/Views/Menu/MenuBarItem.cs | 1 + Terminal.Gui/Views/MenuBarv2.cs | 2 - Terminal.Gui/Views/Shortcut.cs | 530 +++++++++--------- Terminal.Gui/Views/Slider.cs | 4 + Terminal.Gui/Views/StatusBar.cs | 2 - .../TableView/CheckBoxTableSourceWrapper.cs | 2 +- Terminal.Gui/Views/TableView/TableView.cs | 2 +- Terminal.Gui/Views/TextField.cs | 5 +- Terminal.Gui/Views/TextValidateField.cs | 1 - Terminal.Gui/Views/TextView.cs | 18 +- Terminal.Gui/Views/TimeField.cs | 26 +- Terminal.Gui/Views/Toplevel.cs | 16 +- Terminal.Gui/Views/TreeView/TreeView.cs | 2 +- UICatalog/Scenarios/KeyBindings.cs | 37 +- UICatalog/Scenarios/ListColumns.cs | 2 +- UICatalog/Scenarios/TableEditor.cs | 2 +- UICatalog/UICatalog.cs | 2 + UnitTests/Application/ApplicationTests.cs | 4 +- UnitTests/Application/KeyboardTests.cs | 132 ++--- UnitTests/Input/KeyBindingTests.cs | 64 +-- UnitTests/View/MouseTests.cs | 188 +------ UnitTests/View/NavigationTests.cs | 219 +++++++- UnitTests/View/ViewKeyBindingTests.cs | 5 +- UnitTests/Views/AllViewsTests.cs | 8 +- UnitTests/Views/OverlappedTests.cs | 78 ++- UnitTests/Views/TableViewTests.cs | 8 +- UnitTests/Views/ToplevelTests.cs | 32 +- UnitTests/Views/TreeTableSourceTests.cs | 2 +- 36 files changed, 954 insertions(+), 789 deletions(-) diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 209520f47c..c725b13bd3 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -1,5 +1,6 @@ #nullable enable using System.Text.Json.Serialization; +using static System.Formats.Asn1.AsnWriter; namespace Terminal.Gui; @@ -19,6 +20,15 @@ public static Key AlternateForwardKey { Key oldKey = _alternateForwardKey; _alternateForwardKey = value; + + if (_alternateForwardKey == Key.Empty) + { + KeyBindings.Remove (_alternateForwardKey); + } + else + { + KeyBindings.ReplaceKey (oldKey, _alternateForwardKey); + } OnAlternateForwardKeyChanged (new (oldKey, value)); } } @@ -47,6 +57,16 @@ public static Key AlternateBackwardKey { Key oldKey = _alternateBackwardKey; _alternateBackwardKey = value; + + if (_alternateBackwardKey == Key.Empty) + { + KeyBindings.Remove (_alternateBackwardKey); + } + else + { + KeyBindings.ReplaceKey (oldKey, _alternateBackwardKey); + } + OnAlternateBackwardKeyChanged (new (oldKey, value)); } } @@ -75,6 +95,14 @@ public static Key QuitKey { Key oldKey = _quitKey; _quitKey = value; + if (_quitKey == Key.Empty) + { + KeyBindings.Remove (_quitKey); + } + else + { + KeyBindings.ReplaceKey (oldKey, _quitKey); + } OnQuitKeyChanged (new (oldKey, value)); } } @@ -139,26 +167,55 @@ public static bool OnKeyDown (Key keyEvent) } } - // Invoke any global (Application-scoped) KeyBindings. + // Invoke any Application-scoped KeyBindings. // The first view that handles the key will stop the loop. - foreach (KeyValuePair> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode)) + foreach (var binding in KeyBindings.Bindings.Where (b => b.Key == keyEvent.KeyCode)) { - foreach (View view in binding.Value) + if (binding.Value.BoundView is { }) { - if (view is { } - && view.KeyBindings.TryGet (binding.Key, KeyBindingScope.Focused | KeyBindingScope.HotKey | KeyBindingScope.Application, out KeyBinding kb)) + bool? handled = binding.Value.BoundView?.InvokeCommands (binding.Value.Commands, binding.Key, binding.Value); + + if (handled != null && (bool)handled) { - //bool? handled = view.InvokeCommands (kb.Commands, binding.Key, kb); - bool? handled = view?.OnInvokingKeyBindings (keyEvent, kb.Scope); + return true; + } + } + else + { + if (!KeyBindings.TryGet (keyEvent, KeyBindingScope.Application, out KeyBinding appBinding)) + { + continue; + } - if (handled != null && (bool)handled) + bool? toReturn = null; + + foreach (Command command in appBinding.Commands) + { + if (!CommandImplementations.ContainsKey (command)) { - return true; + throw new NotSupportedException ( + @$"A KeyBinding was set up for the command {command} ({keyEvent}) but that command is not supported by Application." + ); + } + + if (CommandImplementations.TryGetValue (command, out Func? implementation)) + { + var context = new CommandContext (command, keyEvent, appBinding); // Create the context here + toReturn = implementation (context); + } + + // if ever see a true then that's what we will return + if (toReturn ?? false) + { + toReturn = true; } } + + return toReturn ?? true; } } + return false; } @@ -344,7 +401,7 @@ internal static void AddApplicationKeyBindings () Command.Refresh, () => { - Refresh (); + Refresh (); return true; } @@ -370,7 +427,7 @@ internal static void AddApplicationKeyBindings () if (Environment.OSVersion.Platform == PlatformID.Unix) { - KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend); + KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend); } #if UNIX_KEY_BINDINGS @@ -398,37 +455,16 @@ internal static List GetViewKeyBindings () .ToList (); } - /// - /// Gets the list of Views that have key bindings for the specified key. - /// - /// - /// This is an internal method used by the class to add Application key bindings. - /// - /// The key to check. - /// Outputs the list of views bound to - /// if successful. - internal static bool TryGetKeyBindings (Key key, out List views) { return _keyBindings.TryGetValue (key, out views); } - - /// - /// Removes an scoped key binding. - /// - /// - /// This is an internal method used by the class to remove Application key bindings. - /// - /// The key that was bound. - /// The view that is bound to the key. - internal static void RemoveKeyBinding (Key key, View view) - { - if (_keyBindings.TryGetValue (key, out List views)) - { - views.Remove (view); - - if (views.Count == 0) - { - _keyBindings.Remove (key); - } - } - } + ///// + ///// Gets the list of Views that have key bindings for the specified key. + ///// + ///// + ///// This is an internal method used by the class to add Application key bindings. + ///// + ///// The key to check. + ///// Outputs the list of views bound to + ///// if successful. + //internal static bool TryGetKeyBindings (Key key, out List views) { return _keyBindings.TryGetValue (key, out views); } /// /// Removes all scoped key bindings for the specified view. @@ -437,19 +473,12 @@ internal static void RemoveKeyBinding (Key key, View view) /// This is an internal method used by the class to remove Application key bindings. /// /// The view that is bound to the key. - internal static void ClearKeyBindings (View view) + internal static void RemoveKeyBindings (View view) { - foreach (Key key in _keyBindings.Keys) - { - _keyBindings [key].Remove (view); - } + var list = KeyBindings.Bindings + .Where (kv => kv.Value.Scope != KeyBindingScope.Application) + .Select (kv => kv.Value) + .Distinct () + .ToList (); } - - /// - /// Removes all scoped key bindings for the specified view. - /// - /// - /// This is an internal method used by the class to remove Application key bindings. - /// - internal static void ClearKeyBindings () { _keyBindings.Clear (); } } diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 3b4c9d9f7a..76c05922bf 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -126,7 +126,7 @@ internal static void ResetState (bool ignoreDisposed = false) KeyDown = null; KeyUp = null; SizeChanging = null; - ClearKeyBindings (); + KeyBindings.Clear (); Colors.Reset (); diff --git a/Terminal.Gui/Input/KeyBinding.cs b/Terminal.Gui/Input/KeyBinding.cs index baac073840..8b5a4201d1 100644 --- a/Terminal.Gui/Input/KeyBinding.cs +++ b/Terminal.Gui/Input/KeyBinding.cs @@ -21,12 +21,28 @@ public KeyBinding (Command [] commands, KeyBindingScope scope, object? context = Context = context; } + /// Initializes a new instance. + /// The commands this key binding will invoke. + /// The scope of the . + /// The view the key binding is bound to. + /// Arbitrary context that can be associated with this key binding. + public KeyBinding (Command [] commands, KeyBindingScope scope, View? boundView, object? context = null) + { + Commands = commands; + Scope = scope; + BoundView = boundView; + Context = context; + } + /// The commands this key binding will invoke. public Command [] Commands { get; set; } /// The scope of the . public KeyBindingScope Scope { get; set; } + /// The view the key binding is bound to. + public View? BoundView { get; set; } + /// /// Arbitrary context that can be associated with this key binding. /// diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs index 0ea164a401..8ca45568ea 100644 --- a/Terminal.Gui/Input/KeyBindings.cs +++ b/Terminal.Gui/Input/KeyBindings.cs @@ -1,6 +1,7 @@ #nullable enable using System.Diagnostics; +using Microsoft.CodeAnalysis; namespace Terminal.Gui; @@ -34,7 +35,8 @@ public KeyBindings () { } /// Adds a to the collection. /// /// - public void Add (Key key, KeyBinding binding) + /// Optional View for bindings. + public void Add (Key key, KeyBinding binding, View? boundViewForAppScope = null) { if (BoundView is { } && binding.Scope.FastHasFlags (KeyBindingScope.Application)) { @@ -43,10 +45,19 @@ public void Add (Key key, KeyBinding binding) if (TryGet (key, out KeyBinding _)) { - Bindings [key] = binding; + throw new InvalidOperationException(@$"A key binding for {key} exists ({binding})."); + //Bindings [key] = binding; } else { + if (BoundView is { }) + { + binding.BoundView = BoundView; + } + else + { + binding.BoundView = boundViewForAppScope; + } Bindings.Add (key, binding); } } @@ -64,12 +75,13 @@ public void Add (Key key, KeyBinding binding) /// /// The key to check. /// The scope for the command. + /// Optional View for bindings. /// /// The command to invoked on the when is pressed. When /// multiple commands are provided,they will be applied in sequence. The bound strike will be /// consumed if any took effect. /// - public void Add (Key key, KeyBindingScope scope, params Command [] commands) + public void Add (Key key, KeyBindingScope scope, View? boundViewForAppScope = null, params Command [] commands) { if (BoundView is { } && scope.FastHasFlags (KeyBindingScope.Application)) { @@ -87,13 +99,43 @@ public void Add (Key key, KeyBindingScope scope, params Command [] commands) throw new ArgumentException (@"At least one command must be specified", nameof (commands)); } - if (TryGet (key, out KeyBinding _)) + if (TryGet (key, out KeyBinding binding)) { - Bindings [key] = new (commands, scope); + throw new InvalidOperationException (@$"A key binding for {key} exists ({binding})."); + //Bindings [key] = new (commands, scope, BoundView); } else { - Add (key, new KeyBinding (commands, scope)); + Add (key, new KeyBinding (commands, scope, BoundView), boundViewForAppScope); + } + } + + public void Add (Key key, KeyBindingScope scope, params Command [] commands) + { + if (BoundView is { } && scope.FastHasFlags (KeyBindingScope.Application)) + { + throw new ArgumentException ("Application scoped KeyBindings must be added via Application.KeyBindings.Add"); + } + + if (key is null || !key.IsValid) + { + //throw new ArgumentException ("Invalid Key", nameof (commands)); + return; + } + + if (commands.Length == 0) + { + throw new ArgumentException (@"At least one command must be specified", nameof (commands)); + } + + if (TryGet (key, out KeyBinding binding)) + { + throw new InvalidOperationException (@$"A key binding for {key} exists ({binding})."); + //Bindings [key] = new (commands, scope, BoundView); + } + else + { + Add (key, new KeyBinding (commands, scope, BoundView), null); } } @@ -103,8 +145,42 @@ public void Add (Key key, KeyBindingScope scope, params Command [] commands) /// View - see ). /// /// - /// This is a helper function for for - /// scoped commands. + /// This is a helper function for . If used for a View ( is set), the scope will be set to . + /// Otherwise, it will be set to . + /// + /// + /// If the key is already bound to a different array of s it will be rebound + /// . + /// + /// + /// + /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch + /// focus to another view and perform multiple commands there). + /// + /// The key to check. + /// Optional View for bindings. + /// + /// The command to invoked on the when is pressed. When + /// multiple commands are provided,they will be applied in sequence. The bound strike will be + /// consumed if any took effect. + /// + public void Add (Key key, View? boundViewForAppScope = null, params Command [] commands) + { + if (BoundView is null && boundViewForAppScope is null) + { + throw new ArgumentException (@"Application scoped KeyBindings must provide a bound view to Add.", nameof(boundViewForAppScope)); + } + Add (key, BoundView is { } ? KeyBindingScope.Focused : KeyBindingScope.Application, boundViewForAppScope, commands); + } + + /// + /// + /// Adds a new key combination that will trigger the commands in (if supported by the + /// View - see ). + /// + /// + /// This is a helper function for . If used for a View ( is set), the scope will be set to . + /// Otherwise, it will be set to . /// /// /// If the key is already bound to a different array of s it will be rebound @@ -123,14 +199,16 @@ public void Add (Key key, KeyBindingScope scope, params Command [] commands) /// public void Add (Key key, params Command [] commands) { - Add (key, KeyBindingScope.Focused, commands); + if (BoundView is null) + { + throw new ArgumentException (@"Application scoped KeyBindings must provide a boundViewForAppScope to Add."); + } + Add (key, BoundView is { } ? KeyBindingScope.Focused : KeyBindingScope.Application, null, commands); } /// Removes all objects from the collection. public void Clear () { - Application.ClearKeyBindings (BoundView); - Bindings.Clear (); } @@ -201,17 +279,23 @@ public Command [] GetCommands (Key key) /// Removes a from the collection. /// - public void Remove (Key key) + /// Optional View for bindings. + public void Remove (Key key, View? boundViewForAppScope = null) { + + if (!TryGet (key, out KeyBinding binding)) + { + return; + } + Bindings.Remove (key); - Application.RemoveKeyBinding (key, BoundView); } /// Replaces a key combination already bound to a set of s. /// /// The key to be replaced. /// The new key to be used. - public void Replace (Key oldKey, Key newKey) + public void ReplaceKey (Key oldKey, Key newKey) { if (!TryGet (oldKey, out KeyBinding _)) { @@ -223,6 +307,26 @@ public void Replace (Key oldKey, Key newKey) Add (newKey, value); } + /// Replaces the commands already bound to a key. + /// + /// + /// If the key is not already bound, it will be added. + /// + /// + /// The key bound to the command to be replaced. + /// The set of commands to replace the old ones with. + public void ReplaceCommands (Key key, params Command [] commands) + { + if (TryGet (key, out KeyBinding binding)) + { + binding.Commands = commands; + } + else + { + Add (key, commands); + } + } + /// Gets the commands bound with the specified Key. /// /// The key to check. @@ -233,13 +337,12 @@ public void Replace (Key oldKey, Key newKey) /// if the Key is bound; otherwise . public bool TryGet (Key key, out KeyBinding binding) { + binding = new (Array.Empty (), KeyBindingScope.Disabled, null); if (key.IsValid) { return Bindings.TryGetValue (key, out binding); } - binding = new (Array.Empty (), KeyBindingScope.Focused); - return false; } @@ -254,6 +357,7 @@ public bool TryGet (Key key, out KeyBinding binding) /// if the Key is bound; otherwise . public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding) { + binding = new (Array.Empty (), KeyBindingScope.Disabled, null); if (key.IsValid && Bindings.TryGetValue (key, out binding)) { if (scope.HasFlag (binding.Scope)) @@ -262,8 +366,6 @@ public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding) } } - binding = new (Array.Empty (), KeyBindingScope.Focused); - return false; } } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 36ec6e6614..7a905f129e 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -27,7 +27,7 @@ private void SetupKeyboard () private void DisposeKeyboard () { TitleTextFormatter.HotKeyChanged -= TitleTextFormatter_HotKeyChanged; - KeyBindings.Clear (); + Application.RemoveKeyBindings (this); } #region HotKey Support @@ -197,13 +197,17 @@ public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey, [CanBeN { KeyBinding keyBinding = new ([Command.HotKey], KeyBindingScope.HotKey, context); // Add the base and Alt key + KeyBindings.Remove (newKey); KeyBindings.Add (newKey, keyBinding); + KeyBindings.Remove (newKey.WithAlt); KeyBindings.Add (newKey.WithAlt, keyBinding); // If the Key is A..Z, add ShiftMask and AltMask | ShiftMask if (newKey.IsKeyCodeAtoZ) { + KeyBindings.Remove (newKey.WithShift); KeyBindings.Add (newKey.WithShift, keyBinding); + KeyBindings.Remove (newKey.WithShift.WithAlt); KeyBindings.Add (newKey.WithShift.WithAlt, keyBinding); } } @@ -800,11 +804,12 @@ public bool IsHotKeyKeyBound (Key key, out View boundView) #if DEBUG // TODO: Determine if App scope bindings should be fired first or last (currently last). - if (Application.TryGetKeyBindings (key, out List views)) + if (Application.KeyBindings.TryGet (key, KeyBindingScope.Focused | KeyBindingScope.HotKey, out KeyBinding b)) { - var boundView = views [0]; - var commandBinding = boundView.KeyBindings.Get (key); - Debug.WriteLine ($"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.{commandBinding.Commands [0]}: {boundView}."); + //var boundView = views [0]; + //var commandBinding = boundView.KeyBindings.Get (key); + Debug.WriteLine ( + $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.");//{commandBinding.Commands [0]}: {boundView}."); } // TODO: This is a "prototype" debug check. It may be too annoying vs. useful. diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 9627b25589..0bb3d9ad2a 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -400,26 +400,26 @@ private void SetInitialProperties (DateTime date) AddCommand (Command.RightEnd, () => MoveEnd ()); AddCommand (Command.Right, () => MoveRight ()); - // Default keybindings for this view - KeyBindings.Add (Key.Delete, Command.DeleteCharRight); - KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight); + // Replace the commands defined in TextField + KeyBindings.ReplaceCommands (Key.Delete, Command.DeleteCharRight); + KeyBindings.ReplaceCommands (Key.D.WithCtrl, Command.DeleteCharRight); - KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft); + KeyBindings.ReplaceCommands (Key.Backspace, Command.DeleteCharLeft); - KeyBindings.Add (Key.Home, Command.LeftHome); - KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome); + KeyBindings.ReplaceCommands (Key.Home, Command.LeftHome); + KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftHome); - KeyBindings.Add (Key.CursorLeft, Command.Left); - KeyBindings.Add (Key.B.WithCtrl, Command.Left); + KeyBindings.ReplaceCommands (Key.CursorLeft, Command.Left); + KeyBindings.ReplaceCommands (Key.B.WithCtrl, Command.Left); - KeyBindings.Add (Key.End, Command.RightEnd); - KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd); + KeyBindings.ReplaceCommands (Key.End, Command.RightEnd); + KeyBindings.ReplaceCommands (Key.E.WithCtrl, Command.RightEnd); - KeyBindings.Add (Key.CursorRight, Command.Right); - KeyBindings.Add (Key.F.WithCtrl, Command.Right); + KeyBindings.ReplaceCommands (Key.CursorRight, Command.Right); + KeyBindings.ReplaceCommands (Key.F.WithCtrl, Command.Right); #if UNIX_KEY_BINDINGS - KeyBindings.Add (Key.D.WithAlt, Command.DeleteCharLeft); + KeyBindings.ReplaceCommands (Key.D.WithAlt, Command.DeleteCharLeft); #endif } diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index d167f87e3c..4aa7b88e80 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -134,7 +134,7 @@ internal FileDialog (IFileSystem fileSystem) FullRowSelect = true, CollectionNavigator = new FileDialogCollectionNavigator (this) }; - _tableView.KeyBindings.Add (Key.Space, Command.Select); + _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); _tableView.MouseClick += OnTableViewMouseClick; _tableView.Style.InvertSelectedCellFirstCharacter = true; Style.TableStyle = _tableView.Style; @@ -254,10 +254,10 @@ internal FileDialog (IFileSystem fileSystem) _tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k); _tableView.SelectedCellChanged += TableView_SelectedCellChanged; - _tableView.KeyBindings.Add (Key.Home, Command.TopHome); - _tableView.KeyBindings.Add (Key.End, Command.BottomEnd); - _tableView.KeyBindings.Add (Key.Home.WithShift, Command.TopHomeExtend); - _tableView.KeyBindings.Add (Key.End.WithShift, Command.BottomEndExtend); + _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.TopHome); + _tableView.KeyBindings.ReplaceCommands (Key.End, Command.BottomEnd); + _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.TopHomeExtend); + _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.BottomEndExtend); _treeView.KeyDown += (s, k) => { diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 34e8dab30d..3e49230192 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -192,13 +192,16 @@ private void AddKeyBindings (MenuBarItem menuBarItem) if ((KeyCode)menuItem.HotKey.Value != KeyCode.Null) { + KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value); KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, keyBinding); + KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask); KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, keyBinding); } if (menuItem.Shortcut != KeyCode.Null) { keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem); + KeyBindings.Remove (menuItem.Shortcut); KeyBindings.Add (menuItem.Shortcut, keyBinding); } diff --git a/Terminal.Gui/Views/Menu/MenuBarItem.cs b/Terminal.Gui/Views/Menu/MenuBarItem.cs index ea5c35f150..81e7557370 100644 --- a/Terminal.Gui/Views/Menu/MenuBarItem.cs +++ b/Terminal.Gui/Views/Menu/MenuBarItem.cs @@ -103,6 +103,7 @@ internal void AddShortcutKeyBindings (MenuBar menuBar) if (menuItem.Shortcut != KeyCode.Null) { KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem); + menuBar.KeyBindings.Remove (menuItem.Shortcut); menuBar.KeyBindings.Add (menuItem.Shortcut, keyBinding); } diff --git a/Terminal.Gui/Views/MenuBarv2.cs b/Terminal.Gui/Views/MenuBarv2.cs index 12278a24cc..d4c598dfed 100644 --- a/Terminal.Gui/Views/MenuBarv2.cs +++ b/Terminal.Gui/Views/MenuBarv2.cs @@ -43,8 +43,6 @@ public override View Add (View view) if (view is Shortcut shortcut) { - shortcut.KeyBindingScope = KeyBindingScope.Application; - // TODO: not happy about using AlignmentModes for this. Too implied. // TODO: instead, add a property (a style enum?) to Shortcut to control this //shortcut.AlignmentModes = AlignmentModes.EndToStart; diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 7ddbe7c5a0..a26a9684df 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -440,353 +440,361 @@ public View CommandView } private void SetCommandViewDefaultLayout () -{ - CommandView.Margin.Thickness = GetMarginThickness (); - CommandView.X = Pos.Align (Alignment.End, AlignmentModes); - CommandView.Y = 0; //Pos.Center (); -} + { + CommandView.Margin.Thickness = GetMarginThickness (); + CommandView.X = Pos.Align (Alignment.End, AlignmentModes); + CommandView.Y = 0; //Pos.Center (); + } -private void Shortcut_TitleChanged (object sender, EventArgs e) -{ - // If the Title changes, update the CommandView text. - // This is a helper to make it easier to set the CommandView text. - // CommandView is public and replaceable, but this is a convenience. - _commandView.Text = Title; -} + private void Shortcut_TitleChanged (object sender, EventArgs e) + { + // If the Title changes, update the CommandView text. + // This is a helper to make it easier to set the CommandView text. + // CommandView is public and replaceable, but this is a convenience. + _commandView.Text = Title; + } -#endregion Command + #endregion Command -#region Help + #region Help -/// -/// The subview that displays the help text for the command. Internal for unit testing. -/// -internal View HelpView { get; } = new (); + /// + /// The subview that displays the help text for the command. Internal for unit testing. + /// + internal View HelpView { get; } = new (); -private void SetHelpViewDefaultLayout () -{ - HelpView.Margin.Thickness = GetMarginThickness (); - HelpView.X = Pos.Align (Alignment.End, AlignmentModes); - HelpView.Y = 0; //Pos.Center (); - HelpView.Width = Dim.Auto (DimAutoStyle.Text); - HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; - - HelpView.Visible = true; - HelpView.VerticalTextAlignment = Alignment.Center; -} + private void SetHelpViewDefaultLayout () + { + HelpView.Margin.Thickness = GetMarginThickness (); + HelpView.X = Pos.Align (Alignment.End, AlignmentModes); + HelpView.Y = 0; //Pos.Center (); + HelpView.Width = Dim.Auto (DimAutoStyle.Text); + HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; + + HelpView.Visible = true; + HelpView.VerticalTextAlignment = Alignment.Center; + } -/// -/// Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to -/// . -/// -public override string Text -{ - get => HelpView?.Text; - set + /// + /// Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to + /// . + /// + public override string Text { - if (HelpView != null) + get => HelpView?.Text; + set { - HelpView.Text = value; - ShowHide (); + if (HelpView != null) + { + HelpView.Text = value; + ShowHide (); + } } } -} -/// -/// Gets or sets the help text displayed in the middle of the Shortcut. -/// -public string HelpText -{ - get => HelpView?.Text; - set + /// + /// Gets or sets the help text displayed in the middle of the Shortcut. + /// + public string HelpText { - if (HelpView != null) + get => HelpView?.Text; + set { - HelpView.Text = value; - ShowHide (); + if (HelpView != null) + { + HelpView.Text = value; + ShowHide (); + } } } -} -#endregion Help + #endregion Help -#region Key + #region Key -private Key _key = Key.Empty; + private Key _key = Key.Empty; -/// -/// Gets or sets the that will be bound to the command. -/// -public Key Key -{ - get => _key; - set + /// + /// Gets or sets the that will be bound to the command. + /// + public Key Key { - if (value == null) + get => _key; + set { - throw new ArgumentNullException (); - } + if (value == null) + { + throw new ArgumentNullException (); + } - _key = value; + _key = value; - UpdateKeyBinding (); + UpdateKeyBinding (); - KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}"; - ShowHide (); + KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}"; + ShowHide (); + } } -} -private KeyBindingScope _keyBindingScope = KeyBindingScope.HotKey; + private KeyBindingScope _keyBindingScope = KeyBindingScope.HotKey; -/// -/// Gets or sets the scope for the key binding for how is bound to . -/// -public KeyBindingScope KeyBindingScope -{ - get => _keyBindingScope; - set + /// + /// Gets or sets the scope for the key binding for how is bound to . + /// + public KeyBindingScope KeyBindingScope { - _keyBindingScope = value; + get => _keyBindingScope; + set + { + _keyBindingScope = value; - UpdateKeyBinding (); + UpdateKeyBinding (); + } } -} -/// -/// Gets the subview that displays the key. Internal for unit testing. -/// + /// + /// Gets the subview that displays the key. Internal for unit testing. + /// -internal View KeyView { get; } = new (); + internal View KeyView { get; } = new (); -private int _minimumKeyTextSize; + private int _minimumKeyTextSize; -/// -/// Gets or sets the minimum size of the key text. Useful for aligning the key text with other s. -/// -public int MinimumKeyTextSize -{ - get => _minimumKeyTextSize; - set + /// + /// Gets or sets the minimum size of the key text. Useful for aligning the key text with other s. + /// + public int MinimumKeyTextSize { - if (value == _minimumKeyTextSize) + get => _minimumKeyTextSize; + set { - //return; - } + if (value == _minimumKeyTextSize) + { + //return; + } - _minimumKeyTextSize = value; - SetKeyViewDefaultLayout (); - CommandView.SetNeedsLayout (); - HelpView.SetNeedsLayout (); - KeyView.SetNeedsLayout (); - SetSubViewNeedsDisplay (); + _minimumKeyTextSize = value; + SetKeyViewDefaultLayout (); + CommandView.SetNeedsLayout (); + HelpView.SetNeedsLayout (); + KeyView.SetNeedsLayout (); + SetSubViewNeedsDisplay (); + } } -} - -private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; } -private void SetKeyViewDefaultLayout () -{ - KeyView.Margin.Thickness = GetMarginThickness (); - KeyView.X = Pos.Align (Alignment.End, AlignmentModes); - KeyView.Y = 0; //Pos.Center (); - KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); - KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; - - KeyView.Visible = true; - - // Right align the text in the keyview - KeyView.TextAlignment = Alignment.End; - KeyView.VerticalTextAlignment = Alignment.Center; - KeyView.KeyBindings.Clear (); -} + private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; } -private void UpdateKeyBinding () -{ - if (Key != null) + private void SetKeyViewDefaultLayout () { - // Disable the command view key bindings - CommandView.KeyBindings.Remove (Key); - CommandView.KeyBindings.Remove (CommandView.HotKey); - KeyBindings.Remove (Key); - KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept); - //KeyBindings.Add (Key, KeyBindingScope.HotKey, Command.Accept); + KeyView.Margin.Thickness = GetMarginThickness (); + KeyView.X = Pos.Align (Alignment.End, AlignmentModes); + KeyView.Y = 0; //Pos.Center (); + KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); + KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; + + KeyView.Visible = true; + + // Right align the text in the keyview + KeyView.TextAlignment = Alignment.End; + KeyView.VerticalTextAlignment = Alignment.Center; + KeyView.KeyBindings.Clear (); } -} - -#endregion Key -#region Accept Handling + private void UpdateKeyBinding () + { + if (Key != null) + { + // Disable the command view key bindings + CommandView.KeyBindings.Remove (Key); + CommandView.KeyBindings.Remove (CommandView.HotKey); -/// -/// Called when the command is received. This -/// occurs -/// - if the user clicks anywhere on the shortcut with the mouse -/// - if the user presses Key -/// - if the user presses the HotKey specified by CommandView -/// - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept). -/// -protected bool? OnAccept (CommandContext ctx) -{ - var cancel = false; + if (KeyBindingScope.FastHasFlags (KeyBindingScope.Application)) + { + Application.KeyBindings.Remove (Key); + Application.KeyBindings.Add (Key, this, Command.Accept); + } + else + { + KeyBindings.Remove (Key); + KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept); + } + } + } - switch (ctx.KeyBinding?.Scope) - { - case KeyBindingScope.Application: - cancel = base.OnAccept () == true; + #endregion Key - break; + #region Accept Handling - case KeyBindingScope.Focused: - base.OnAccept (); + /// + /// Called when the command is received. This + /// occurs + /// - if the user clicks anywhere on the shortcut with the mouse + /// - if the user presses Key + /// - if the user presses the HotKey specified by CommandView + /// - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept). + /// + protected bool? OnAccept (CommandContext ctx) + { + var cancel = false; - // cancel if we're focused - cancel = true; + switch (ctx.KeyBinding?.Scope) + { + case KeyBindingScope.Application: + cancel = base.OnAccept () == true; - break; + break; - case KeyBindingScope.HotKey: - cancel = base.OnAccept () == true; + case KeyBindingScope.Focused: + base.OnAccept (); - if (CanFocus) - { - SetFocus (); + // cancel if we're focused cancel = true; - } - break; + break; - default: - // Mouse - cancel = base.OnAccept () == true; + case KeyBindingScope.HotKey: + cancel = base.OnAccept () == true; - break; - } + if (CanFocus) + { + SetFocus (); + cancel = true; + } - CommandView.InvokeCommand (Command.Accept, ctx.Key, ctx.KeyBinding); + break; - if (Action is { }) - { - Action.Invoke (); - // Assume if there's a subscriber to Action, it's handled. - cancel = true; - } + default: + // Mouse + cancel = base.OnAccept () == true; - return cancel; -} + break; + } -/// -/// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the -/// mouse. -/// -/// -/// Note, the event is fired first, and if cancelled, the event will not be invoked. -/// -[CanBeNull] -public Action Action { get; set; } + CommandView.InvokeCommand (Command.Accept, ctx.Key, ctx.KeyBinding); -#endregion Accept Handling + if (Action is { }) + { + Action.Invoke (); + // Assume if there's a subscriber to Action, it's handled. + cancel = true; + } -private bool? OnSelect (CommandContext ctx) -{ - if (CommandView.GetSupportedCommands ().Contains (Command.Select)) - { - return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding); + return cancel; } - return false; - -} + /// + /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the + /// mouse. + /// + /// + /// Note, the event is fired first, and if cancelled, the event will not be invoked. + /// + [CanBeNull] + public Action Action { get; set; } -#region Focus + #endregion Accept Handling -/// -public override ColorScheme ColorScheme -{ - get => base.ColorScheme; - set + private bool? OnSelect (CommandContext ctx) { - base.ColorScheme = value; - SetColors (); + if (CommandView.GetSupportedCommands ().Contains (Command.Select)) + { + return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding); + } + return false; + } -} -/// -/// -internal void SetColors () -{ - // Border should match superview. - Border.ColorScheme = SuperView?.ColorScheme; - if (HasFocus) + #region Focus + + /// + public override ColorScheme ColorScheme { - // When we have focus, we invert the colors - base.ColorScheme = new (base.ColorScheme) + get => base.ColorScheme; + set { - Normal = base.ColorScheme.Focus, - HotNormal = base.ColorScheme.HotFocus, - HotFocus = base.ColorScheme.HotNormal, - Focus = base.ColorScheme.Normal - }; - } - else - { - base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme; + base.ColorScheme = value; + SetColors (); + } } - // Set KeyView's colors to show "hot" - if (IsInitialized && base.ColorScheme is { }) + /// + /// + internal void SetColors () { - var cs = new ColorScheme (base.ColorScheme) + // Border should match superview. + Border.ColorScheme = SuperView?.ColorScheme; + + if (HasFocus) { - Normal = base.ColorScheme.HotNormal, - HotNormal = base.ColorScheme.Normal - }; - KeyView.ColorScheme = cs; + // When we have focus, we invert the colors + base.ColorScheme = new (base.ColorScheme) + { + Normal = base.ColorScheme.Focus, + HotNormal = base.ColorScheme.HotFocus, + HotFocus = base.ColorScheme.HotNormal, + Focus = base.ColorScheme.Normal + }; + } + else + { + base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme; + } + + // Set KeyView's colors to show "hot" + if (IsInitialized && base.ColorScheme is { }) + { + var cs = new ColorScheme (base.ColorScheme) + { + Normal = base.ColorScheme.HotNormal, + HotNormal = base.ColorScheme.Normal + }; + KeyView.ColorScheme = cs; + } } -} -View _lastFocusedView; -/// -public override bool OnEnter (View view) -{ - SetColors (); - _lastFocusedView = view; + View _lastFocusedView; + /// + public override bool OnEnter (View view) + { + SetColors (); + _lastFocusedView = view; - return base.OnEnter (view); -} + return base.OnEnter (view); + } -/// -public override bool OnLeave (View view) -{ - SetColors (); - _lastFocusedView = this; + /// + public override bool OnLeave (View view) + { + SetColors (); + _lastFocusedView = this; - return base.OnLeave (view); -} + return base.OnLeave (view); + } -#endregion Focus + #endregion Focus -/// -protected override void Dispose (bool disposing) -{ - if (disposing) + /// + protected override void Dispose (bool disposing) { - if (CommandView?.IsAdded == false) + if (disposing) { - CommandView.Dispose (); - } + if (CommandView?.IsAdded == false) + { + CommandView.Dispose (); + } - if (HelpView?.IsAdded == false) - { - HelpView.Dispose (); - } + if (HelpView?.IsAdded == false) + { + HelpView.Dispose (); + } - if (KeyView?.IsAdded == false) - { - KeyView.Dispose (); + if (KeyView?.IsAdded == false) + { + KeyView.Dispose (); + } } - } - base.Dispose (disposing); -} + base.Dispose (disposing); + } } diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 61ac401d6e..73e479b748 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -1454,9 +1454,13 @@ private void SetKeyBindings () KeyBindings.Add (Key.CursorUp.WithCtrl, Command.LeftExtend); } + KeyBindings.Remove (Key.Home); KeyBindings.Add (Key.Home, Command.LeftHome); + KeyBindings.Remove (Key.End); KeyBindings.Add (Key.End, Command.RightEnd); + KeyBindings.Remove (Key.Enter); KeyBindings.Add (Key.Enter, Command.Accept); + KeyBindings.Remove (Key.Space); KeyBindings.Add (Key.Space, Command.Select); } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index b4df14e6b0..ce3ad268ac 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -65,8 +65,6 @@ public override View Add (View view) if (view is Shortcut shortcut) { - shortcut.KeyBindingScope = KeyBindingScope.Application; - // TODO: not happy about using AlignmentModes for this. Too implied. // TODO: instead, add a property (a style enum?) to Shortcut to control this shortcut.AlignmentModes = AlignmentModes.EndToStart; diff --git a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs index 7d2379af83..8be294c1a5 100644 --- a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs +++ b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs @@ -26,7 +26,7 @@ public CheckBoxTableSourceWrapperBase (TableView tableView, ITableSource toWrap) Wrapping = toWrap; this.tableView = tableView; - tableView.KeyBindings.Add (Key.Space, Command.Select); + tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); tableView.MouseClick += TableView_MouseClick; tableView.CellToggled += TableView_CellToggled; diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 0807991d50..67974349f8 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -319,7 +319,7 @@ public KeyCode CellActivationKey { if (cellActivationKey != value) { - KeyBindings.Replace (cellActivationKey, value); + KeyBindings.ReplaceKey (cellActivationKey, value); // of API user is mixing and matching old and new methods of keybinding then they may have lost // the old binding (e.g. with ClearKeybindings) so KeyBindings.Replace alone will fail diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index bdcb92dd5b..b5d14db0ab 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1332,7 +1332,10 @@ private MenuBarItem BuildContextMenuBarItem () ); } - private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); } + private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) + { + KeyBindings.ReplaceKey (e.OldKey.KeyCode, e.NewKey.KeyCode); + } private List DeleteSelectedText () { diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs index 35779d59bb..045f6df3ff 100644 --- a/Terminal.Gui/Views/TextValidateField.cs +++ b/Terminal.Gui/Views/TextValidateField.cs @@ -464,7 +464,6 @@ public TextValidateField () KeyBindings.Add (Key.Home, Command.LeftHome); KeyBindings.Add (Key.End, Command.RightEnd); - KeyBindings.Add (Key.Delete, Command.DeleteCharRight); KeyBindings.Add (Key.Delete, Command.DeleteCharRight); KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft); diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index bea620aeeb..598f78961c 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -2369,8 +2369,8 @@ public TextView () ); AddCommand (Command.Tab, () => ProcessTab ()); AddCommand (Command.BackTab, () => ProcessBackTab ()); - AddCommand (Command.NextView, () => ProcessMoveNextView ()); - AddCommand (Command.PreviousView, () => ProcessMovePreviousView ()); + //AddCommand (Command.NextView, () => ProcessMoveNextView ()); + //AddCommand (Command.PreviousView, () => ProcessMovePreviousView ()); AddCommand ( Command.Undo, @@ -2503,11 +2503,11 @@ public TextView () KeyBindings.Add (Key.Tab, Command.Tab); KeyBindings.Add (Key.Tab.WithShift, Command.BackTab); - KeyBindings.Add (Key.Tab.WithCtrl, Command.NextView); - KeyBindings.Add (Application.AlternateForwardKey, Command.NextView); + //KeyBindings.Add (Key.Tab.WithCtrl, Command.NextView); + //KeyBindings.Add (Application.AlternateForwardKey, Command.NextView); - KeyBindings.Add (Key.Tab.WithCtrl.WithShift, Command.PreviousView); - KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousView); + //KeyBindings.Add (Key.Tab.WithCtrl.WithShift, Command.PreviousView); + //KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousView); KeyBindings.Add (Key.Z.WithCtrl, Command.Undo); KeyBindings.Add (Key.R.WithCtrl, Command.Redo); @@ -4318,7 +4318,7 @@ private void ClearSelectedRegion () DoNeededAction (); } - private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); } + private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.ReplaceKey (e.OldKey, e.NewKey); } private bool DeleteTextBackwards () { @@ -6393,8 +6393,8 @@ private void ToggleSelecting () _selectionStartRow = CurrentRow; } - private void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); } - private void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); } + private void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.ReplaceKey (e.OldKey, e.NewKey); } + private void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.ReplaceKey (e.OldKey, e.NewKey); } // Tries to snap the cursor to the tracking column private void TrackColumn () diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs index 5f303cc98b..a473fdcea1 100644 --- a/Terminal.Gui/Views/TimeField.cs +++ b/Terminal.Gui/Views/TimeField.cs @@ -58,26 +58,26 @@ public TimeField () AddCommand (Command.RightEnd, () => MoveEnd ()); AddCommand (Command.Right, () => MoveRight ()); - // Default keybindings for this view - KeyBindings.Add (Key.Delete, Command.DeleteCharRight); - KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight); + // Replace the key bindings defined in TextField + KeyBindings.ReplaceCommands (Key.Delete, Command.DeleteCharRight); + KeyBindings.ReplaceCommands (Key.D.WithCtrl, Command.DeleteCharRight); - KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft); + KeyBindings.ReplaceCommands (Key.Backspace, Command.DeleteCharLeft); - KeyBindings.Add (Key.Home, Command.LeftHome); - KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome); + KeyBindings.ReplaceCommands (Key.Home, Command.LeftHome); + KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftHome); - KeyBindings.Add (Key.CursorLeft, Command.Left); - KeyBindings.Add (Key.B.WithCtrl, Command.Left); + KeyBindings.ReplaceCommands (Key.CursorLeft, Command.Left); + KeyBindings.ReplaceCommands (Key.B.WithCtrl, Command.Left); - KeyBindings.Add (Key.End, Command.RightEnd); - KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd); + KeyBindings.ReplaceCommands (Key.End, Command.RightEnd); + KeyBindings.ReplaceCommands (Key.E.WithCtrl, Command.RightEnd); - KeyBindings.Add (Key.CursorRight, Command.Right); - KeyBindings.Add (Key.F.WithCtrl, Command.Right); + KeyBindings.ReplaceCommands (Key.CursorRight, Command.Right); + KeyBindings.ReplaceCommands (Key.F.WithCtrl, Command.Right); #if UNIX_KEY_BINDINGS - KeyBindings.Add (Key.D.WithAlt, Command.DeleteCharLeft); + KeyBindings.ReplaceCommands (Key.D.WithAlt, Command.DeleteCharLeft); #endif } diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 0e70904927..fb31efb733 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -34,7 +34,7 @@ public Toplevel () ColorScheme = Colors.ColorSchemes ["TopLevel"]; - ConfigureKeyBindings (); + //ConfigureKeyBindings (); MouseClick += Toplevel_MouseClick; } @@ -188,7 +188,7 @@ private void ConfigureKeyBindings () /// public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) { - KeyBindings.Replace (e.OldKey, e.NewKey); + KeyBindings.ReplaceKey (e.OldKey, e.NewKey); AlternateBackwardKeyChanged?.Invoke (this, e); } @@ -197,7 +197,7 @@ public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) /// public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) { - KeyBindings.Replace (e.OldKey, e.NewKey); + KeyBindings.ReplaceKey (e.OldKey, e.NewKey); AlternateForwardKeyChanged?.Invoke (this, e); } @@ -205,7 +205,7 @@ public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) /// public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) { - KeyBindings.Replace (e.OldKey, e.NewKey); + KeyBindings.ReplaceKey (e.OldKey, e.NewKey); QuitKeyChanged?.Invoke (this, e); } @@ -650,7 +650,7 @@ private View GetDeepestFocusedSubview (View view) /// /// Moves the focus to /// - private void MoveNextView () + internal void MoveNextView () { View old = GetDeepestFocusedSubview (Focused); @@ -670,7 +670,7 @@ private void MoveNextView () } } - private void MoveNextViewOrTop () + internal void MoveNextViewOrTop () { if (Application.OverlappedTop is null) { @@ -691,7 +691,7 @@ private void MoveNextViewOrTop () } } - private void MovePreviousView () + internal void MovePreviousView () { View old = GetDeepestFocusedSubview (Focused); @@ -711,7 +711,7 @@ private void MovePreviousView () } } - private void MovePreviousViewOrTop () + internal void MovePreviousViewOrTop () { if (Application.OverlappedTop is null) { diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index f2039d9c6e..5fbde234c1 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -352,7 +352,7 @@ public KeyCode ObjectActivationKey { if (objectActivationKey != value) { - KeyBindings.Replace (ObjectActivationKey, value); + KeyBindings.ReplaceKey (ObjectActivationKey, value); objectActivationKey = value; } } diff --git a/UICatalog/Scenarios/KeyBindings.cs b/UICatalog/Scenarios/KeyBindings.cs index 8814a19917..d624445436 100644 --- a/UICatalog/Scenarios/KeyBindings.cs +++ b/UICatalog/Scenarios/KeyBindings.cs @@ -80,13 +80,10 @@ Pressing Esc or {Application.QuitKey} will cause it to quit the app. }; appWindow.Add (appBindingsListView); - foreach (var appBinding in Application.GetKeyBindings ()) + foreach (var appBinding in Application.KeyBindings.Bindings) { - foreach (var view in appBinding.Value) - { - var commands = view.KeyBindings.GetCommands (appBinding.Key); - appBindings.Add ($"{appBinding.Key} -> {view.GetType ().Name} - {commands [0]}"); - } + var commands = Application.KeyBindings.GetCommands (appBinding.Key); + appBindings.Add ($"{appBinding.Key} -> {appBinding.Value.BoundView?.GetType ().Name} - {commands [0]}"); } ObservableCollection hotkeyBindings = new (); @@ -153,10 +150,10 @@ private void AppWindow_DrawContent (object sender, DrawEventArgs e) private void AppWindow_Leave (object sender, FocusEventArgs e) { - //foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused)) - //{ - // _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}"); - //} + foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused)) + { + _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}"); + } } } @@ -166,28 +163,34 @@ public KeyBindingsDemo () { CanFocus = true; + + AddCommand (Command.Save, ctx => + { + MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok"); + return true; + }); AddCommand (Command.New, ctx => { - MessageBox.Query ("Hi", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok"); - + MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok"); return true; }); AddCommand (Command.HotKey, ctx => { - MessageBox.Query ("Hi", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok"); + MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok"); SetFocus (); return true; }); - KeyBindings.Add (Key.F3, KeyBindingScope.Focused, Command.New); - KeyBindings.Add (Key.F4, KeyBindingScope.Application, Command.New); - + KeyBindings.Add (Key.F2, KeyBindingScope.Focused, Command.Save); + KeyBindings.Add (Key.F3, Command.New); // same as specifying KeyBindingScope.Focused + Application.KeyBindings.Add (Key.F4, this, Command.New); AddCommand (Command.QuitToplevel, ctx => { + MessageBox.Query ($"{ctx.KeyBinding?.Scope}", $"Key: {ctx.Key}\nCommand: {ctx.Command}", buttons: "Ok"); Application.RequestStop (); return true; }); - KeyBindings.Add (Key.Q.WithCtrl, KeyBindingScope.Application, Command.QuitToplevel); + Application.KeyBindings.Add (Key.Q.WithAlt, this, Command.QuitToplevel); } } diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 0f45b1e5d9..12c9619cfc 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -254,7 +254,7 @@ public override void Main () // if user clicks the mouse in TableView _listColView.MouseClick += (s, e) => { _listColView.ScreenToCell (e.MouseEvent.Position, out int? clickedCol); }; - _listColView.KeyBindings.Add (Key.Space, Command.Accept); + _listColView.KeyBindings.ReplaceCommands (Key.Space, Command.Accept); top.Add (appWindow); diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index df86aceafd..ca36671ce2 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -769,7 +769,7 @@ public override void Main () } }; - _tableView.KeyBindings.Add (Key.Space, Command.Accept); + _tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Accept); // Run - Start the application. Application.Run (appWindow); diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index f9ac4a5034..dd18f0bd1d 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -605,7 +605,9 @@ public UICatalogTopLevel () ScenarioList.CellActivated += ScenarioView_OpenSelectedItem; // TableView typically is a grid where nav keys are biased for moving left/right. + ScenarioList.KeyBindings.Remove (Key.Home); ScenarioList.KeyBindings.Add (Key.Home, Command.TopHome); + ScenarioList.KeyBindings.Remove (Key.End); ScenarioList.KeyBindings.Add (Key.End, Command.BottomEnd); // Ideally, TableView.MultiSelect = false would turn off any keybindings for diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 6363233a98..f8f1f80293 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -199,7 +199,7 @@ void CheckReset () Assert.Null (Application._mouseEnteredView); // Keyboard - Assert.Empty (Application.GetViewsWithKeyBindings ()); + Assert.Empty (Application.GetViewKeyBindings ()); // Events - Can't check //Assert.Null (Application.NotifyNewRunState); @@ -233,7 +233,7 @@ void CheckReset () Application.AlternateBackwardKey = Key.A; Application.AlternateForwardKey = Key.B; Application.QuitKey = Key.C; - Application.AddKeyBinding (Key.A, new View ()); + Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Cancel); //Application.OverlappedChildren = new List (); //Application.OverlappedTop = diff --git a/UnitTests/Application/KeyboardTests.cs b/UnitTests/Application/KeyboardTests.cs index 3483b83960..4dfb5b11a5 100644 --- a/UnitTests/Application/KeyboardTests.cs +++ b/UnitTests/Application/KeyboardTests.cs @@ -71,7 +71,7 @@ public void QuitKey_Default_Is_Esc () // After Init Assert.Equal (Key.Esc, Application.QuitKey); - Application.Shutdown(); + Application.Shutdown (); } private object _timeoutLock; @@ -200,62 +200,62 @@ public void AlternateForwardKey_AlternateBackwardKey_Tests () Assert.True (v1.HasFocus); // Using default keys. - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.True (v2.HasFocus); - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.True (v3.HasFocus); - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.True (v4.HasFocus); - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.True (v1.HasFocus); - top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl); + Application.OnKeyDown (Key.Tab.WithShift.WithCtrl); Assert.True (v4.HasFocus); - top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl); + Application.OnKeyDown (Key.Tab.WithShift.WithCtrl); Assert.True (v3.HasFocus); - top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl); + Application.OnKeyDown (Key.Tab.WithShift.WithCtrl); Assert.True (v2.HasFocus); - top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl); + Application.OnKeyDown (Key.Tab.WithShift.WithCtrl); Assert.True (v1.HasFocus); - top.NewKeyDownEvent (Key.PageDown.WithCtrl); + Application.OnKeyDown (Key.PageDown.WithCtrl); Assert.True (v2.HasFocus); - top.NewKeyDownEvent (Key.PageDown.WithCtrl); + Application.OnKeyDown (Key.PageDown.WithCtrl); Assert.True (v3.HasFocus); - top.NewKeyDownEvent (Key.PageDown.WithCtrl); + Application.OnKeyDown (Key.PageDown.WithCtrl); Assert.True (v4.HasFocus); - top.NewKeyDownEvent (Key.PageDown.WithCtrl); + Application.OnKeyDown (Key.PageDown.WithCtrl); Assert.True (v1.HasFocus); - top.NewKeyDownEvent (Key.PageUp.WithCtrl); + Application.OnKeyDown (Key.PageUp.WithCtrl); Assert.True (v4.HasFocus); - top.NewKeyDownEvent (Key.PageUp.WithCtrl); + Application.OnKeyDown (Key.PageUp.WithCtrl); Assert.True (v3.HasFocus); - top.NewKeyDownEvent (Key.PageUp.WithCtrl); + Application.OnKeyDown (Key.PageUp.WithCtrl); Assert.True (v2.HasFocus); - top.NewKeyDownEvent (Key.PageUp.WithCtrl); + Application.OnKeyDown (Key.PageUp.WithCtrl); Assert.True (v1.HasFocus); // Using another's alternate keys. Application.AlternateForwardKey = Key.F7; Application.AlternateBackwardKey = Key.F6; - top.NewKeyDownEvent (Key.F7); + Application.OnKeyDown (Key.F7); Assert.True (v2.HasFocus); - top.NewKeyDownEvent (Key.F7); + Application.OnKeyDown (Key.F7); Assert.True (v3.HasFocus); - top.NewKeyDownEvent (Key.F7); + Application.OnKeyDown (Key.F7); Assert.True (v4.HasFocus); - top.NewKeyDownEvent (Key.F7); + Application.OnKeyDown (Key.F7); Assert.True (v1.HasFocus); - top.NewKeyDownEvent (Key.F6); + Application.OnKeyDown (Key.F6); Assert.True (v4.HasFocus); - top.NewKeyDownEvent (Key.F6); + Application.OnKeyDown (Key.F6); Assert.True (v3.HasFocus); - top.NewKeyDownEvent (Key.F6); + Application.OnKeyDown (Key.F6); Assert.True (v2.HasFocus); - top.NewKeyDownEvent (Key.F6); + Application.OnKeyDown (Key.F6); Assert.True (v1.HasFocus); Application.RequestStop (); @@ -321,14 +321,14 @@ public void EnsuresTopOnFront_CanFocus_False_By_Keyboard () Assert.True (win2.HasFocus); Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title); - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.True (win2.CanFocus); Assert.False (win.HasFocus); Assert.True (win2.CanFocus); Assert.True (win2.HasFocus); Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title); - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.False (win.CanFocus); Assert.False (win.HasFocus); Assert.True (win2.CanFocus); @@ -374,14 +374,14 @@ public void EnsuresTopOnFront_CanFocus_True_By_Keyboard () Assert.False (win2.HasFocus); Assert.Equal ("win", ((Window)top.Subviews [^1]).Title); - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.True (win.CanFocus); Assert.False (win.HasFocus); Assert.True (win2.CanFocus); Assert.True (win2.HasFocus); Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title); - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); Assert.True (win.CanFocus); Assert.True (win.HasFocus); Assert.True (win2.CanFocus); @@ -496,21 +496,21 @@ public void KeyBinding_OnKeyDown () Application.Begin (top); Application.OnKeyDown (Key.A); - Assert.True (invoked); + Assert.False (invoked); Assert.True (view.ApplicationCommand); invoked = false; view.ApplicationCommand = false; - view.KeyBindings.Remove (KeyCode.A); + Application.KeyBindings.Remove (KeyCode.A); Application.OnKeyDown (Key.A); // old Assert.False (invoked); Assert.False (view.ApplicationCommand); - view.KeyBindings.Add (Key.A.WithCtrl, KeyBindingScope.Application, Command.Save); + Application.KeyBindings.Add (Key.A.WithCtrl, view, Command.Save); Application.OnKeyDown (Key.A); // old Assert.False (invoked); Assert.False (view.ApplicationCommand); Application.OnKeyDown (Key.A.WithCtrl); // new - Assert.True (invoked); + Assert.False (invoked); Assert.True (view.ApplicationCommand); invoked = false; @@ -556,70 +556,60 @@ public void KeyBinding_OnKeyDown_Negative () top.Dispose (); } - [Fact] [AutoInitShutdown] - public void KeyBinding_AddKeyBinding_Adds () + public void KeyBinding_Application_KeyBindings_Add_Adds () { - View view1 = new (); - Application.AddKeyBinding (Key.A, view1); + Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Accept); + Application.KeyBindings.Add (Key.B, KeyBindingScope.Application, Command.Accept); - View view2 = new (); - Application.AddKeyBinding (Key.A, view2); - - Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); - Assert.Contains (view1, views); - Assert.Contains (view2, views); - - Assert.False (Application.TryGetKeyBindings (Key.B, out List _)); + Assert.True (Application.KeyBindings.TryGet (Key.A, out var binding)); + Assert.Null (binding.BoundView); + Assert.True (Application.KeyBindings.TryGet (Key.B, out binding)); + Assert.Null (binding.BoundView); } [Fact] [AutoInitShutdown] - public void KeyBinding_ViewKeyBindings_Add_Adds () + public void KeyBinding_View_KeyBindings_Add_Adds () { View view1 = new (); - view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); - view1.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left); - Assert.Single (Application.GetViewsWithKeyBindings ()); + Application.KeyBindings.Add (Key.A, view1, Command.Accept); View view2 = new (); - view2.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); - view2.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left); - - Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); - Assert.Contains (view1, views); - Assert.Contains (view2, views); + Application.KeyBindings.Add (Key.B, view2, Command.Accept); - Assert.False (Application.TryGetKeyBindings (Key.B, out List _)); + Assert.True (Application.KeyBindings.TryGet (Key.A, out var binding)); + Assert.Equal (view1, binding.BoundView); + Assert.True (Application.KeyBindings.TryGet (Key.B, out binding)); + Assert.Equal (view2, binding.BoundView); } [Fact] [AutoInitShutdown] - public void KeyBinding_RemoveKeyBinding_Removes () + public void KeyBinding_Application_RemoveKeyBinding_Removes () { - View view1 = new (); - Application.AddKeyBinding (Key.A, view1); + Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Accept); - Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); - Assert.Contains (view1, views); + Assert.True (Application.KeyBindings.TryGet (Key.A, out _)); - Application.RemoveKeyBinding (Key.A, view1); - Assert.False (Application.TryGetKeyBindings (Key.A, out List _)); + Application.KeyBindings.Remove (Key.A); + Assert.False (Application.KeyBindings.TryGet (Key.A, out _)); } [Fact] [AutoInitShutdown] - public void KeyBinding_ViewKeyBindings_RemoveKeyBinding_Removes () + public void KeyBinding_View_KeyBindings_RemoveKeyBinding_Removes () { + View view1 = new (); - view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); + Application.KeyBindings.Add (Key.A, view1, Command.Accept); - Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); - Assert.Contains (view1, views); + View view2 = new (); + Application.KeyBindings.Add (Key.B, view1, Command.Accept); - view1.KeyBindings.Remove (Key.A); - Assert.False (Application.TryGetKeyBindings (Key.A, out List _)); + Application.KeyBindings.Remove (Key.A, view1); + Assert.False (Application.KeyBindings.TryGet (Key.A, out _)); } // Test View for testing Application key Bindings @@ -631,9 +621,9 @@ public ScopedKeyBindingView () AddCommand (Command.HotKey, () => HotKeyCommand = true); AddCommand (Command.Left, () => FocusedCommand = true); - KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); + Application.KeyBindings.Add (Key.A, this, Command.Save); HotKey = KeyCode.H; - KeyBindings.Add (Key.F, KeyBindingScope.Focused, Command.Left); + KeyBindings.Add (Key.F, Command.Left); } public bool ApplicationCommand { get; set; } diff --git a/UnitTests/Input/KeyBindingTests.cs b/UnitTests/Input/KeyBindingTests.cs index 73a1c6b001..bf3b007fdb 100644 --- a/UnitTests/Input/KeyBindingTests.cs +++ b/UnitTests/Input/KeyBindingTests.cs @@ -21,12 +21,12 @@ public void Add_Multiple_Adds () var keyBindings = new KeyBindings (); Command [] commands = { Command.Right, Command.Left }; - keyBindings.Add (Key.A, commands); + keyBindings.Add (Key.A, KeyBindingScope.Application, commands); Command [] resultCommands = keyBindings.GetCommands (Key.A); Assert.Contains (Command.Right, resultCommands); Assert.Contains (Command.Left, resultCommands); - keyBindings.Add (Key.B, commands); + keyBindings.Add (Key.B, KeyBindingScope.Application, commands); resultCommands = keyBindings.GetCommands (Key.B); Assert.Contains (Command.Right, resultCommands); Assert.Contains (Command.Left, resultCommands); @@ -36,11 +36,11 @@ public void Add_Multiple_Adds () public void Add_Single_Adds () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.A, Command.HotKey); + keyBindings.Add (Key.A, KeyBindingScope.Application, Command.HotKey); Command [] resultCommands = keyBindings.GetCommands (Key.A); Assert.Contains (Command.HotKey, resultCommands); - keyBindings.Add (Key.B, Command.HotKey); + keyBindings.Add (Key.B, KeyBindingScope.Application, Command.HotKey); resultCommands = keyBindings.GetCommands (Key.B); Assert.Contains (Command.HotKey, resultCommands); } @@ -50,7 +50,7 @@ public void Add_Single_Adds () public void Clear_Clears () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.B, Command.HotKey); + keyBindings.Add (Key.B, KeyBindingScope.Application, Command.HotKey); keyBindings.Clear (); Command [] resultCommands = keyBindings.GetCommands (Key.A); Assert.Empty (resultCommands); @@ -78,7 +78,7 @@ public void GetCommands_Unknown_ReturnsEmpty () public void GetCommands_WithCommands_ReturnsCommands () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.A, Command.HotKey); + keyBindings.Add (Key.A, KeyBindingScope.Application, Command.HotKey); Command [] resultCommands = keyBindings.GetCommands (Key.A); Assert.Contains (Command.HotKey, resultCommands); } @@ -88,8 +88,8 @@ public void GetCommands_WithMultipleBindings_ReturnsCommands () { var keyBindings = new KeyBindings (); Command [] commands = { Command.Right, Command.Left }; - keyBindings.Add (Key.A, commands); - keyBindings.Add (Key.B, commands); + keyBindings.Add (Key.A, KeyBindingScope.Application, commands); + keyBindings.Add (Key.B, KeyBindingScope.Application, commands); Command [] resultCommands = keyBindings.GetCommands (Key.A); Assert.Contains (Command.Right, resultCommands); Assert.Contains (Command.Left, resultCommands); @@ -103,7 +103,7 @@ public void GetCommands_WithMultipleCommands_ReturnsCommands () { var keyBindings = new KeyBindings (); Command [] commands = { Command.Right, Command.Left }; - keyBindings.Add (Key.A, commands); + keyBindings.Add (Key.A, KeyBindingScope.Application, commands); Command [] resultCommands = keyBindings.GetCommands (Key.A); Assert.Contains (Command.Right, resultCommands); Assert.Contains (Command.Left, resultCommands); @@ -114,10 +114,10 @@ public void GetKeyFromCommands_MultipleCommands () { var keyBindings = new KeyBindings (); Command [] commands1 = { Command.Right, Command.Left }; - keyBindings.Add (Key.A, commands1); + keyBindings.Add (Key.A, KeyBindingScope.Application, commands1); Command [] commands2 = { Command.LineUp, Command.LineDown }; - keyBindings.Add (Key.B, commands2); + keyBindings.Add (Key.B, KeyBindingScope.Application, commands2); Key key = keyBindings.GetKeyFromCommands (commands1); Assert.Equal (Key.A, key); @@ -133,7 +133,7 @@ public void GetKeyFromCommands_MultipleCommands () public void GetKeyFromCommands_OneCommand () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.A, Command.Right); + keyBindings.Add (Key.A, KeyBindingScope.Application, Command.Right); Key key = keyBindings.GetKeyFromCommands (Command.Right); Assert.Equal (Key.A, key); @@ -154,66 +154,66 @@ public void GetKeyFromCommands_Unknown_Throws_InvalidOperationException () public void GetKeyFromCommands_WithCommands_ReturnsKey () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.A, Command.HotKey); + keyBindings.Add (Key.A, KeyBindingScope.Application, Command.HotKey); Key resultKey = keyBindings.GetKeyFromCommands (Command.HotKey); Assert.Equal (Key.A, resultKey); } // Add should not allow duplicates [Fact] - public void Add_Replaces_If_Exists () + public void Add_Throws_If_Exists () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.A, Command.HotKey); - keyBindings.Add (Key.A, Command.Accept); + keyBindings.Add (Key.A, KeyBindingScope.Application, Command.HotKey); + Assert.Throws (() => keyBindings.Add (Key.A, KeyBindingScope.Application, Command.Accept)); Command [] resultCommands = keyBindings.GetCommands (Key.A); - Assert.DoesNotContain (Command.HotKey, resultCommands); + Assert.Contains (Command.HotKey, resultCommands); keyBindings = new (); keyBindings.Add (Key.A, KeyBindingScope.Focused, Command.HotKey); - keyBindings.Add (Key.A, KeyBindingScope.Focused, Command.Accept); + Assert.Throws (() => keyBindings.Add (Key.A, KeyBindingScope.Focused, Command.Accept)); resultCommands = keyBindings.GetCommands (Key.A); - Assert.DoesNotContain (Command.HotKey, resultCommands); + Assert.Contains (Command.HotKey, resultCommands); keyBindings = new (); keyBindings.Add (Key.A, KeyBindingScope.HotKey, Command.HotKey); - keyBindings.Add (Key.A, KeyBindingScope.Focused, Command.Accept); + Assert.Throws (() => keyBindings.Add (Key.A, KeyBindingScope.Focused, Command.Accept)); resultCommands = keyBindings.GetCommands (Key.A); - Assert.DoesNotContain (Command.HotKey, resultCommands); + Assert.Contains (Command.HotKey, resultCommands); keyBindings = new (); keyBindings.Add (Key.A, new KeyBinding (new [] { Command.HotKey }, KeyBindingScope.HotKey)); - keyBindings.Add (Key.A, new KeyBinding (new [] { Command.Accept }, KeyBindingScope.HotKey)); + Assert.Throws (() => keyBindings.Add (Key.A, new KeyBinding (new [] { Command.Accept }, KeyBindingScope.HotKey))); resultCommands = keyBindings.GetCommands (Key.A); - Assert.DoesNotContain (Command.HotKey, resultCommands); + Assert.Contains (Command.HotKey, resultCommands); } [Fact] public void Replace_Key () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.A, Command.HotKey); - keyBindings.Add (Key.B, Command.HotKey); - keyBindings.Add (Key.C, Command.HotKey); - keyBindings.Add (Key.D, Command.HotKey); + keyBindings.Add (Key.A, KeyBindingScope.Application, Command.HotKey); + keyBindings.Add (Key.B, KeyBindingScope.Application, Command.HotKey); + keyBindings.Add (Key.C, KeyBindingScope.Application, Command.HotKey); + keyBindings.Add (Key.D, KeyBindingScope.Application, Command.HotKey); - keyBindings.Replace (Key.A, Key.E); + keyBindings.ReplaceKey (Key.A, Key.E); Assert.Empty (keyBindings.GetCommands (Key.A)); Assert.Contains (Command.HotKey, keyBindings.GetCommands (Key.E)); - keyBindings.Replace (Key.B, Key.F); + keyBindings.ReplaceKey (Key.B, Key.F); Assert.Empty (keyBindings.GetCommands (Key.B)); Assert.Contains (Command.HotKey, keyBindings.GetCommands (Key.F)); - keyBindings.Replace (Key.C, Key.G); + keyBindings.ReplaceKey (Key.C, Key.G); Assert.Empty (keyBindings.GetCommands (Key.C)); Assert.Contains (Command.HotKey, keyBindings.GetCommands (Key.G)); - keyBindings.Replace (Key.D, Key.H); + keyBindings.ReplaceKey (Key.D, Key.H); Assert.Empty (keyBindings.GetCommands (Key.D)); Assert.Contains (Command.HotKey, keyBindings.GetCommands (Key.H)); } @@ -312,7 +312,7 @@ public void TryGet_Unknown_ReturnsFalse () public void TryGet_WithCommands_ReturnsTrue () { var keyBindings = new KeyBindings (); - keyBindings.Add (Key.A, Command.HotKey); + keyBindings.Add (Key.A, KeyBindingScope.Application, Command.HotKey); bool result = keyBindings.TryGet (Key.A, out KeyBinding bindings); Assert.True (result); Assert.Contains (Command.HotKey, bindings.Commands); diff --git a/UnitTests/View/MouseTests.cs b/UnitTests/View/MouseTests.cs index a0bef94f58..56548aff4c 100644 --- a/UnitTests/View/MouseTests.cs +++ b/UnitTests/View/MouseTests.cs @@ -92,193 +92,7 @@ public void WheeledLeft_WheeledRight (MouseFlags mouseFlags, MouseFlags expected view.NewMouseEvent (new MouseEvent () { Flags = mouseFlags }); Assert.Equal (mouseFlagsFromEvent, expectedMouseFlagsFromEvent); } - - [Theory] - [MemberData (nameof (AllViewTypes))] - - public void AllViews_Enter_Leave_Events (Type viewType) - { - var view = CreateInstanceIfNotGeneric (viewType); - - if (view == null) - { - output.WriteLine ($"Ignoring {viewType} - It's a Generic"); - return; - } - - if (!view.CanFocus) - { - output.WriteLine ($"Ignoring {viewType} - It can't focus."); - - return; - } - - if (view is Toplevel && ((Toplevel)view).Modal) - { - output.WriteLine ($"Ignoring {viewType} - It's a Modal Toplevel"); - - return; - } - - Application.Init (new FakeDriver ()); - - Toplevel top = new () - { - Height = 10, - Width = 10 - }; - - View otherView = new () - { - X = 0, Y = 0, - Height = 1, - Width = 1, - CanFocus = true, - }; - - view.X = Pos.Right (otherView); - view.Y = 0; - view.Width = 10; - view.Height = 1; - - var nEnter = 0; - var nLeave = 0; - - view.Enter += (s, e) => nEnter++; - view.Leave += (s, e) => nLeave++; - - top.Add (view, otherView); - Application.Begin (top); - - // Start with the focus on our test view - view.SetFocus (); - - Assert.Equal (1, nEnter); - Assert.Equal (0, nLeave); - - // Use keyboard to navigate to next view (otherView). - if (view is TextView) - { - top.NewKeyDownEvent (Key.Tab.WithCtrl); - } - else if (view is DatePicker) - { - for (var i = 0; i < 4; i++) - { - top.NewKeyDownEvent (Key.Tab.WithCtrl); - } - } - else - { - top.NewKeyDownEvent (Key.Tab); - } - - Assert.Equal (1, nEnter); - Assert.Equal (1, nLeave); - - top.NewKeyDownEvent (Key.Tab); - - Assert.Equal (2, nEnter); - Assert.Equal (1, nLeave); - - top.Dispose (); - Application.Shutdown (); - } - - - [Theory] - [MemberData (nameof (AllViewTypes))] - - public void AllViews_Enter_Leave_Events_Visible_False (Type viewType) - { - var view = CreateInstanceIfNotGeneric (viewType); - - if (view == null) - { - output.WriteLine ($"Ignoring {viewType} - It's a Generic"); - return; - } - - if (!view.CanFocus) - { - output.WriteLine ($"Ignoring {viewType} - It can't focus."); - - return; - } - - if (view is Toplevel && ((Toplevel)view).Modal) - { - output.WriteLine ($"Ignoring {viewType} - It's a Modal Toplevel"); - - return; - } - - Application.Init (new FakeDriver ()); - - Toplevel top = new () - { - Height = 10, - Width = 10 - }; - - View otherView = new () - { - X = 0, Y = 0, - Height = 1, - Width = 1, - CanFocus = true, - }; - - view.Visible = false; - view.X = Pos.Right (otherView); - view.Y = 0; - view.Width = 10; - view.Height = 1; - - var nEnter = 0; - var nLeave = 0; - - view.Enter += (s, e) => nEnter++; - view.Leave += (s, e) => nLeave++; - - top.Add (view, otherView); - Application.Begin (top); - - // Start with the focus on our test view - view.SetFocus (); - - Assert.Equal (0, nEnter); - Assert.Equal (0, nLeave); - - // Use keyboard to navigate to next view (otherView). - if (view is TextView) - { - top.NewKeyDownEvent (Key.Tab.WithCtrl); - } - else if (view is DatePicker) - { - for (var i = 0; i < 4; i++) - { - top.NewKeyDownEvent (Key.Tab.WithCtrl); - } - } - else - { - top.NewKeyDownEvent (Key.Tab); - } - - Assert.Equal (0, nEnter); - Assert.Equal (0, nLeave); - - top.NewKeyDownEvent (Key.Tab); - - Assert.Equal (0, nEnter); - Assert.Equal (0, nLeave); - - top.Dispose (); - Application.Shutdown (); - } - + [Fact] public void NewMouseEvent_Invokes_MouseEvent_Properly () { diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/NavigationTests.cs index 5a7019c191..1933c16628 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/NavigationTests.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui.ViewTests; -public class NavigationTests (ITestOutputHelper output) +public class NavigationTests (ITestOutputHelper output) : TestsAllViews { [Fact] public void BringSubviewForward_Subviews_vs_TabIndexes () @@ -324,13 +324,13 @@ public void CanFocus_Sets_To_False_On_Single_View_Focus_View_On_Another_Toplevel Assert.True (view2.CanFocus); Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - Assert.True (top.NewKeyDownEvent (Key.Tab)); + Assert.True (Application.OnKeyDown (Key.Tab)); Assert.True (view1.CanFocus); Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus Assert.True (view2.CanFocus); Assert.True (view2.HasFocus); - Assert.True (top.NewKeyDownEvent (Key.Tab)); + Assert.True (Application.OnKeyDown (Key.Tab)); Assert.True (view1.CanFocus); Assert.True (view1.HasFocus); Assert.True (view2.CanFocus); @@ -365,13 +365,13 @@ public void CanFocus_Sets_To_False_On_Toplevel_Focus_View_On_Another_Toplevel () Assert.True (view2.CanFocus); Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - Assert.True (top.NewKeyDownEvent (Key.Tab.WithCtrl)); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); Assert.True (view1.CanFocus); Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus Assert.True (view2.CanFocus); Assert.True (view2.HasFocus); - Assert.True (top.NewKeyDownEvent (Key.Tab.WithCtrl)); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); Assert.True (view1.CanFocus); Assert.True (view1.HasFocus); Assert.True (view2.CanFocus); @@ -417,14 +417,14 @@ public void CanFocus_Sets_To_False_With_Two_Views_Focus_Another_View_On_The_Same Assert.True (view2.CanFocus); Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - Assert.True (top.NewKeyDownEvent (Key.Tab.WithCtrl)); - Assert.True (top.NewKeyDownEvent (Key.Tab.WithCtrl)); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); Assert.True (view1.CanFocus); Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus Assert.True (view2.CanFocus); Assert.True (view2.HasFocus); - Assert.True (top.NewKeyDownEvent (Key.Tab.WithCtrl)); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); Assert.True (view1.CanFocus); Assert.True (view1.HasFocus); Assert.True (view2.CanFocus); @@ -530,6 +530,7 @@ public void Enabled_Sets_Also_Sets_Subviews () } [Fact] + [AutoInitShutdown] public void FocusNearestView_Ensure_Focus_Ordered () { var top = new Toplevel (); @@ -544,16 +545,17 @@ public void FocusNearestView_Ensure_Focus_Ordered () frm.Add (frmSubview); top.Add (frm); - top.NewKeyDownEvent (Key.Tab); + Application.Begin (top); Assert.Equal ("WindowSubview", top.MostFocused.Text); - top.NewKeyDownEvent (Key.Tab); + + Application.OnKeyDown (Key.Tab); Assert.Equal ("FrameSubview", top.MostFocused.Text); - top.NewKeyDownEvent (Key.Tab); + Application.OnKeyDown (Key.Tab); Assert.Equal ("WindowSubview", top.MostFocused.Text); - top.NewKeyDownEvent (Key.Tab.WithShift); + Application.OnKeyDown (Key.Tab.WithShift); Assert.Equal ("FrameSubview", top.MostFocused.Text); - top.NewKeyDownEvent (Key.Tab.WithShift); + Application.OnKeyDown (Key.Tab.WithShift); Assert.Equal ("WindowSubview", top.MostFocused.Text); top.Dispose (); } @@ -605,7 +607,7 @@ public void FocusNext_Does_Not_Throws_If_A_View_Was_Removed_From_The_Collection Assert.False (removed); Assert.Null (view3); - Assert.True (top1.NewKeyDownEvent (Key.Tab.WithCtrl)); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); Assert.True (top1.HasFocus); Assert.False (view1.HasFocus); Assert.True (view2.HasFocus); @@ -613,7 +615,7 @@ public void FocusNext_Does_Not_Throws_If_A_View_Was_Removed_From_The_Collection Assert.NotNull (view3); Exception exception = - Record.Exception (() => top1.NewKeyDownEvent (Key.Tab.WithCtrl)); + Record.Exception (() => Application.OnKeyDown (Key.Tab.WithCtrl)); Assert.Null (exception); Assert.True (removed); Assert.Null (view3); @@ -1582,4 +1584,191 @@ public void Most_Focused_NoSubviews () Assert.True (view.HasFocus); Assert.Null (view.MostFocused); // BUGBUG: Should be view } + + + [Theory] + [MemberData (nameof (AllViewTypes))] + + public void AllViews_Enter_Leave_Events (Type viewType) + { + var view = CreateInstanceIfNotGeneric (viewType); + + if (view == null) + { + output.WriteLine ($"Ignoring {viewType} - It's a Generic"); + return; + } + + if (!view.CanFocus) + { + output.WriteLine ($"Ignoring {viewType} - It can't focus."); + + return; + } + + if (view is Toplevel && ((Toplevel)view).Modal) + { + output.WriteLine ($"Ignoring {viewType} - It's a Modal Toplevel"); + + return; + } + + Application.Init (new FakeDriver ()); + + Toplevel top = new () + { + Height = 10, + Width = 10 + }; + + View otherView = new () + { + X = 0, Y = 0, + Height = 1, + Width = 1, + CanFocus = true, + }; + + view.X = Pos.Right (otherView); + view.Y = 0; + view.Width = 10; + view.Height = 1; + + var nEnter = 0; + var nLeave = 0; + + view.Enter += (s, e) => nEnter++; + view.Leave += (s, e) => nLeave++; + + top.Add (view, otherView); + Application.Begin (top); + + // Start with the focus on our test view + view.SetFocus (); + + Assert.Equal (1, nEnter); + Assert.Equal (0, nLeave); + + // Use keyboard to navigate to next view (otherView). + if (view is TextView) + { + Application.OnKeyDown (Key.Tab.WithCtrl); + } + else if (view is DatePicker) + { + for (var i = 0; i < 4; i++) + { + Application.OnKeyDown (Key.Tab.WithCtrl); + } + } + else + { + Application.OnKeyDown (Key.Tab); + } + + Assert.Equal (1, nEnter); + Assert.Equal (1, nLeave); + + Application.OnKeyDown (Key.Tab); + + Assert.Equal (2, nEnter); + Assert.Equal (1, nLeave); + + top.Dispose (); + Application.Shutdown (); + } + + + [Theory] + [MemberData (nameof (AllViewTypes))] + + public void AllViews_Enter_Leave_Events_Visible_False (Type viewType) + { + var view = CreateInstanceIfNotGeneric (viewType); + + if (view == null) + { + output.WriteLine ($"Ignoring {viewType} - It's a Generic"); + return; + } + + if (!view.CanFocus) + { + output.WriteLine ($"Ignoring {viewType} - It can't focus."); + + return; + } + + if (view is Toplevel && ((Toplevel)view).Modal) + { + output.WriteLine ($"Ignoring {viewType} - It's a Modal Toplevel"); + + return; + } + + Application.Init (new FakeDriver ()); + + Toplevel top = new () + { + Height = 10, + Width = 10 + }; + + View otherView = new () + { + X = 0, Y = 0, + Height = 1, + Width = 1, + CanFocus = true, + }; + + view.Visible = false; + view.X = Pos.Right (otherView); + view.Y = 0; + view.Width = 10; + view.Height = 1; + + var nEnter = 0; + var nLeave = 0; + + view.Enter += (s, e) => nEnter++; + view.Leave += (s, e) => nLeave++; + + top.Add (view, otherView); + Application.Begin (top); + + // Start with the focus on our test view + view.SetFocus (); + + Assert.Equal (0, nEnter); + Assert.Equal (0, nLeave); + + // Use keyboard to navigate to next view (otherView). + if (view is TextView) + { + Application.OnKeyDown (Key.Tab.WithCtrl); + } + else if (view is DatePicker) + { + for (var i = 0; i < 4; i++) + { + Application.OnKeyDown (Key.Tab.WithCtrl); + } + } + else + { + Application.OnKeyDown (Key.Tab); + } + + Assert.Equal (0, nEnter); + Assert.Equal (0, nLeave); + + top.NewKeyDownEvent (Key.Tab); + + Assert.Equal (0, nEnter); + Assert.Equal (0, nLeave); + + top.Dispose (); + Application.Shutdown (); + } } diff --git a/UnitTests/View/ViewKeyBindingTests.cs b/UnitTests/View/ViewKeyBindingTests.cs index d10d8a0c09..2ac278a7b1 100644 --- a/UnitTests/View/ViewKeyBindingTests.cs +++ b/UnitTests/View/ViewKeyBindingTests.cs @@ -19,7 +19,8 @@ public void Focus_KeyBinding () Application.Begin (top); Application.OnKeyDown (Key.A); - Assert.True (invoked); + Assert.False (invoked); + Assert.True (view.ApplicationCommand); invoked = false; Application.OnKeyDown (Key.H); @@ -134,7 +135,7 @@ public ScopedKeyBindingView () AddCommand (Command.HotKey, () => HotKeyCommand = true); AddCommand (Command.Left, () => FocusedCommand = true); - KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); + Application.KeyBindings.Add (Key.A, this, Command.Save); HotKey = KeyCode.H; KeyBindings.Add (Key.F, KeyBindingScope.Focused, Command.Left); } diff --git a/UnitTests/Views/AllViewsTests.cs b/UnitTests/Views/AllViewsTests.cs index 92232d5515..7b6945e7b6 100644 --- a/UnitTests/Views/AllViewsTests.cs +++ b/UnitTests/Views/AllViewsTests.cs @@ -108,21 +108,21 @@ public void AllViews_Enter_Leave_Events (Type viewType) if (vType is TextView) { - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); } else if (vType is DatePicker) { for (var i = 0; i < 4; i++) { - top.NewKeyDownEvent (Key.Tab.WithCtrl); + Application.OnKeyDown (Key.Tab.WithCtrl); } } else { - top.NewKeyDownEvent (Key.Tab); + Application.OnKeyDown (Key.Tab); } - top.NewKeyDownEvent (Key.Tab); + Application.OnKeyDown (Key.Tab); Assert.Equal (2, vTypeEnter); Assert.Equal (1, vTypeLeave); diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index 5ca0fc5662..2cf4fa96de 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -1030,28 +1030,30 @@ public void KeyBindings_Command_With_OverlappedTop () var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; var lblTf1W1 = new Label { Text = "Enter text in TextField on Win1:" }; - var tf1W1 = new TextField { X = Pos.Right (lblTf1W1) + 1, Width = Dim.Fill (), Text = "Text1 on Win1" }; + var tf1W1 = new TextField { Id="tf1W1", X = Pos.Right (lblTf1W1) + 1, Width = Dim.Fill (), Text = "Text1 on Win1" }; var lblTvW1 = new Label { Y = Pos.Bottom (lblTf1W1) + 1, Text = "Enter text in TextView on Win1:" }; var tvW1 = new TextView { + Id = "tvW1", X = Pos.Left (tf1W1), Width = Dim.Fill (), Height = 2, Text = "First line Win1\nSecond line Win1" }; var lblTf2W1 = new Label { Y = Pos.Bottom (lblTvW1) + 1, Text = "Enter text in TextField on Win1:" }; - var tf2W1 = new TextField { X = Pos.Left (tf1W1), Width = Dim.Fill (), Text = "Text2 on Win1" }; + var tf2W1 = new TextField { Id = "tf2W1", X = Pos.Left (tf1W1), Width = Dim.Fill (), Text = "Text2 on Win1" }; win1.Add (lblTf1W1, tf1W1, lblTvW1, tvW1, lblTf2W1, tf2W1); var win2 = new Window { Id = "win2", Width = Dim.Percent (50), Height = Dim.Fill () }; var lblTf1W2 = new Label { Text = "Enter text in TextField on Win2:" }; - var tf1W2 = new TextField { X = Pos.Right (lblTf1W2) + 1, Width = Dim.Fill (), Text = "Text1 on Win2" }; + var tf1W2 = new TextField { Id = "tf1W2", X = Pos.Right (lblTf1W2) + 1, Width = Dim.Fill (), Text = "Text1 on Win2" }; var lblTvW2 = new Label { Y = Pos.Bottom (lblTf1W2) + 1, Text = "Enter text in TextView on Win2:" }; var tvW2 = new TextView { + Id = "tvW2", X = Pos.Left (tf1W2), Width = Dim.Fill (), Height = 2, Text = "First line Win1\nSecond line Win2" }; var lblTf2W2 = new Label { Y = Pos.Bottom (lblTvW2) + 1, Text = "Enter text in TextField on Win2:" }; - var tf2W2 = new TextField { X = Pos.Left (tf1W2), Width = Dim.Fill (), Text = "Text2 on Win2" }; + var tf2W2 = new TextField { Id = "tf2W2", X = Pos.Left (tf1W2), Width = Dim.Fill (), Text = "Text2 on Win2" }; win2.Add (lblTf1W2, tf1W2, lblTvW2, tvW2, lblTf2W2, tf2W2); win1.Closing += (s, e) => isRunning = false; @@ -1104,72 +1106,69 @@ public void KeyBindings_Command_With_OverlappedTop () Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal ($"\tFirst line Win1{Environment.NewLine}Second line Win1", tvW1.Text); - Assert.True ( - Application.OnKeyDown (Key.Tab.WithShift) - ); + Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text); - Assert.True ( - Application.OnKeyDown (Key.Tab.WithCtrl) - ); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); // move to win2 + Assert.Equal (win2, Application.OverlappedChildren [0]); + + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl.WithShift)); // move back to win1 Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.Tab)); + + Assert.Equal (tvW1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.Tab)); // text view eats tab Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tvW1, win1.MostFocused); + + tvW1.AllowsTab = false; + Assert.True (Application.OnKeyDown (Key.Tab)); // text view eats tab + Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (tf2W1, win1.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorRight)); Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tf1W1, win1.MostFocused); #if UNIX_KEY_BINDINGS Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.I.WithCtrl))); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); #endif - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithShift) - ); + Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorLeft)); + Assert.True (Application.OnKeyDown (Key.CursorLeft)); // The view to the left of tvW1 is tf2W1, but tvW1 is still focused and eats cursor keys Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tvW1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorUp)); Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tvW1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.MostFocused); - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithCtrl) - ); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); // Move to win2 Assert.Equal (win2, Application.OverlappedChildren [0]); Assert.Equal (tf1W2, win2.MostFocused); tf2W2.SetFocus (); Assert.True (tf2W2.HasFocus); - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithCtrl.WithShift) - ); + Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl.WithShift)); Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Application.AlternateForwardKey)); Assert.Equal (win2, Application.OverlappedChildren [0]); Assert.Equal (tf2W2, win2.MostFocused); Assert.True (Application.OnKeyDown (Application.AlternateBackwardKey)); Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tf1W1, win1.MostFocused); #if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.B.WithCtrl))); + Assert.True (Application.OnKeyDown (new (Key.B.WithCtrl))); #else Assert.True (Application.OnKeyDown (Key.CursorLeft)); #endif @@ -1180,20 +1179,17 @@ Application.OverlappedChildren [0] Assert.Equal (tvW1, win1.MostFocused); Assert.Equal (Point.Empty, tvW1.CursorPosition); - Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.End.WithCtrl) - ); + Assert.True (Application.OnKeyDown (Key.End.WithCtrl)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.Equal (new (16, 1), tvW1.CursorPosition); #if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.F.WithCtrl))); + Assert.True (Application.OnKeyDown (new (Key.F.WithCtrl))); #else Assert.True (Application.OnKeyDown (Key.CursorRight)); #endif Assert.Equal (win1, Application.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tvW1, win1.MostFocused); #if UNIX_KEY_BINDINGS Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.L.WithCtrl))); diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index 6740120f69..a87bb533ef 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -2993,7 +2993,7 @@ public void TestToggleCells_MultiSelectOn () dt.Rows.Add (1, 2, 3, 4, 5, 6); tableView.MultiSelect = true; - tableView.KeyBindings.Add (Key.Space, Command.Select); + tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); Point selectedCell = tableView.GetAllSelectedCells ().Single (); Assert.Equal (0, selectedCell.X); @@ -3065,7 +3065,7 @@ public void TestToggleCells_MultiSelectOn_FullRowSelect () dt.Rows.Add (1, 2, 3, 4, 5, 6); tableView.FullRowSelect = true; tableView.MultiSelect = true; - tableView.KeyBindings.Add (Key.Space, Command.Select); + tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); // Toggle Select Cell 0,0 tableView.NewKeyDownEvent (new() { KeyCode = KeyCode.Space }); @@ -3101,7 +3101,7 @@ public void TestToggleCells_MultiSelectOn_SquareSelectToggled () dt.Rows.Add (1, 2, 3, 4, 5, 6); dt.Rows.Add (1, 2, 3, 4, 5, 6); tableView.MultiSelect = true; - tableView.KeyBindings.Add (Key.Space, Command.Select); + tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); // Make a square selection tableView.NewKeyDownEvent (new() { KeyCode = KeyCode.ShiftMask | KeyCode.CursorDown }); @@ -3142,7 +3142,7 @@ public void TestToggleCells_MultiSelectOn_Two_SquareSelects_BothToggled () dt.Rows.Add (1, 2, 3, 4, 5, 6); dt.Rows.Add (1, 2, 3, 4, 5, 6); tableView.MultiSelect = true; - tableView.KeyBindings.Add (Key.Space, Command.Select); + tableView.KeyBindings.ReplaceCommands (Key.Space, Command.Select); // Make first square selection (0,0 to 1,1) tableView.NewKeyDownEvent (new() { KeyCode = KeyCode.ShiftMask | KeyCode.CursorDown }); diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index c19c72bb1a..7a5f9700c3 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -485,27 +485,31 @@ public void KeyBindings_Command () Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text); Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); Assert.Equal (win1, top.Focused); - Assert.Equal (tf2W1, top.MostFocused); + Assert.Equal (tf2W1, top.MostFocused); // tf2W1 is last subview in win1 - tabbing should take us to first subview of win2 Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf1W1, top.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorRight)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf1W1, top.MostFocused); - Assert.True (Application.OnKeyDown (Key.CursorDown)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (win2, top.Focused); + Assert.Equal (tf1W2, top.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorRight)); // move char to right in tf1W2 + Assert.Equal (win2, top.Focused); + Assert.Equal (tf1W2, top.MostFocused); + Assert.True (Application.OnKeyDown (Key.CursorDown)); // move down to next view (tvW2) + Assert.Equal (win2, top.Focused); + Assert.Equal (tvW2, top.MostFocused); #if UNIX_KEY_BINDINGS Assert.True (Application.OnKeyDown (new (Key.I.WithCtrl))); Assert.Equal (win1, top.Focused); Assert.Equal (tf2W1, top.MostFocused); #endif - Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); // Ignored. TextView eats shift-tab by default + Assert.Equal (win2, top.Focused); + Assert.Equal (tvW2, top.MostFocused); + tvW2.AllowsTab = false; + Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); + Assert.Equal (win2, top.Focused); + Assert.Equal (tf1W2, top.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorLeft)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf1W1, top.MostFocused); + Assert.Equal (win2, top.Focused); + Assert.Equal (tf1W2, top.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorUp)); Assert.Equal (win1, top.Focused); Assert.Equal (tf2W1, top.MostFocused); diff --git a/UnitTests/Views/TreeTableSourceTests.cs b/UnitTests/Views/TreeTableSourceTests.cs index 39e18327bf..a1a319b1b5 100644 --- a/UnitTests/Views/TreeTableSourceTests.cs +++ b/UnitTests/Views/TreeTableSourceTests.cs @@ -187,7 +187,7 @@ public void TestTreeTableSource_CombinedWithCheckboxes () Assert.Equal (0, tv.SelectedRow); Assert.Equal (1, tv.SelectedColumn); - top.NewKeyDownEvent (Key.CursorRight); + Application.OnKeyDown (Key.CursorRight); tv.Draw (); From 22dcbc1a782539e5ee423ef66e0bd9ee5ab6cf62 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 12:36:45 -0600 Subject: [PATCH 76/96] removed un needed key handling code from TextView --- .../Application/Application.Keyboard.cs | 12 ++-- Terminal.Gui/Views/TextView.cs | 58 +------------------ 2 files changed, 11 insertions(+), 59 deletions(-) diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index c725b13bd3..84fade3cb8 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -358,9 +358,10 @@ internal static void AddApplicationKeyBindings () ); AddCommand ( - Command.NextView, // TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + Command.NextView, () => { + // TODO: Move this method to Application.Navigation.cs Current.MoveNextView (); return true; @@ -368,9 +369,10 @@ internal static void AddApplicationKeyBindings () ); AddCommand ( - Command.PreviousView,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + Command.PreviousView, () => { + // TODO: Move this method to Application.Navigation.cs Current.MovePreviousView (); return true; @@ -378,9 +380,10 @@ internal static void AddApplicationKeyBindings () ); AddCommand ( - Command.NextViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + Command.NextViewOrTop, () => { + // TODO: Move this method to Application.Navigation.cs Current.MoveNextViewOrTop (); return true; @@ -388,9 +391,10 @@ internal static void AddApplicationKeyBindings () ); AddCommand ( - Command.PreviousViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) + Command.PreviousViewOrTop, () => { + // TODO: Move this method to Application.Navigation.cs Current.MovePreviousViewOrTop (); return true; diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 598f78961c..db8627b47e 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -2369,8 +2369,6 @@ public TextView () ); AddCommand (Command.Tab, () => ProcessTab ()); AddCommand (Command.BackTab, () => ProcessBackTab ()); - //AddCommand (Command.NextView, () => ProcessMoveNextView ()); - //AddCommand (Command.PreviousView, () => ProcessMovePreviousView ()); AddCommand ( Command.Undo, @@ -2503,12 +2501,6 @@ public TextView () KeyBindings.Add (Key.Tab, Command.Tab); KeyBindings.Add (Key.Tab.WithShift, Command.BackTab); - //KeyBindings.Add (Key.Tab.WithCtrl, Command.NextView); - //KeyBindings.Add (Application.AlternateForwardKey, Command.NextView); - - //KeyBindings.Add (Key.Tab.WithCtrl.WithShift, Command.PreviousView); - //KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousView); - KeyBindings.Add (Key.Z.WithCtrl, Command.Undo); KeyBindings.Add (Key.R.WithCtrl, Command.Redo); @@ -5365,16 +5357,6 @@ private void MoveLeft () DoNeededAction (); } - private bool MoveNextView () - { - if (Application.OverlappedTop is { }) - { - return SuperView?.FocusNext () == true; - } - - return false; - } - private void MovePageDown () { int nPageDnShift = Viewport.Height - 1; @@ -5431,16 +5413,6 @@ private void MovePageUp () DoNeededAction (); } - private bool MovePreviousView () - { - if (Application.OverlappedTop is { }) - { - return SuperView?.FocusPrev () == true; - } - - return false; - } - private void MoveRight () { List currentLine = GetCurrentLine (); @@ -5617,7 +5589,7 @@ private bool ProcessBackTab () if (!AllowsTab || _isReadOnly) { - return ProcessMovePreviousView (); + return false; } if (CurrentColumn > 0) @@ -5889,21 +5861,7 @@ private void ProcessMoveLeftExtend () StartSelecting (); MoveLeft (); } - - private bool ProcessMoveNextView () - { - ResetColumnTrack (); - - return MoveNextView (); - } - - private bool ProcessMovePreviousView () - { - ResetColumnTrack (); - - return MovePreviousView (); - } - + private bool ProcessMoveRight () { // if the user presses Right (without any control keys) @@ -6163,7 +6121,7 @@ private bool ProcessTab () if (!AllowsTab || _isReadOnly) { - return ProcessMoveNextView (); + return false; } InsertText (new Key ((KeyCode)'\t')); @@ -6369,13 +6327,6 @@ private string StringFromRunes (List cells) private void TextView_Initialized (object sender, EventArgs e) { Autocomplete.HostControl = this; - - if (Application.Top is { }) - { - Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged!; - Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged!; - } - OnContentsChanged (); } @@ -6393,9 +6344,6 @@ private void ToggleSelecting () _selectionStartRow = CurrentRow; } - private void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.ReplaceKey (e.OldKey, e.NewKey); } - private void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.ReplaceKey (e.OldKey, e.NewKey); } - // Tries to snap the cursor to the tracking column private void TrackColumn () { From c088f2e98cfbad285a7291f9db5eec429ce87d84 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 12:38:59 -0600 Subject: [PATCH 77/96] removed unneeded key handling code from Toplevel --- Terminal.Gui/Views/Toplevel.cs | 125 --------------------------------- 1 file changed, 125 deletions(-) diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index fb31efb733..4c23b215f2 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -31,15 +31,10 @@ public Toplevel () Arrangement = ViewArrangement.Fixed; Width = Dim.Fill (); Height = Dim.Fill (); - ColorScheme = Colors.ColorSchemes ["TopLevel"]; - - //ConfigureKeyBindings (); - MouseClick += Toplevel_MouseClick; } - #region Keyboard & Mouse // TODO: IRunnable: Re-implement - Modal means IRunnable, ViewArrangement.Overlapped where modalView.Z > allOtherViews.Max (v = v.Z), and exclusive key/mouse input. @@ -65,114 +60,6 @@ public Toplevel () /// public bool Modal { get; set; } - // TODO: Overlapped: Figure out how these keybindings should work. - private void ConfigureKeyBindings () - { - // Things this view knows how to do - AddCommand ( - Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic. - () => - { - QuitToplevel (); - - return true; - } - ); - - /// TODO: Overlapped: Add Command.ShowHide - - AddCommand ( - Command.Suspend, // TODO: Move to Application - () => - { - Driver.Suspend (); - ; - - return true; - } - ); - - AddCommand ( - Command.NextView, // TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) - () => - { - MoveNextView (); - - return true; - } - ); - - AddCommand ( - Command.PreviousView,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) - () => - { - MovePreviousView (); - - return true; - } - ); - - AddCommand ( - Command.NextViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) - () => - { - MoveNextViewOrTop (); - - return true; - } - ); - - AddCommand ( - Command.PreviousViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) - () => - { - MovePreviousViewOrTop (); - - return true; - } - ); - - AddCommand ( - Command.Refresh, - () => - { - Application.Refresh (); // TODO: Move to Application - - return true; - } - ); - - // Default keybindings for this view - KeyBindings.Add (Application.QuitKey, Command.QuitToplevel); - - KeyBindings.Add (Key.CursorRight, Command.NextView); - KeyBindings.Add (Key.CursorDown, Command.NextView); - KeyBindings.Add (Key.CursorLeft, Command.PreviousView); - KeyBindings.Add (Key.CursorUp, Command.PreviousView); - - KeyBindings.Add (Key.Tab, Command.NextView); - KeyBindings.Add (Key.Tab.WithShift, Command.PreviousView); - KeyBindings.Add (Key.Tab.WithCtrl, Command.NextViewOrTop); - KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop); - - // TODO: Refresh Key should be configurable - KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh); - KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix - KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix - - if (Environment.OSVersion.Platform == PlatformID.Unix) - { - KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend); - } - -#if UNIX_KEY_BINDINGS - KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix - KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix - KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix - KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix -#endif - } - private void Toplevel_MouseClick (object sender, MouseEventEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; } // TODO: Deprecate - No need for this at View level; having at Application is sufficient. @@ -500,18 +387,6 @@ internal virtual void OnUnloaded () Unloaded?.Invoke (this, EventArgs.Empty); } - private void QuitToplevel () - { - if (Application.OverlappedTop is { }) - { - RequestStop (this); - } - else - { - Application.RequestStop (); - } - } - #endregion #region Draw From 4a56b84324a67fb990aa781ac82212c1574597e2 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 12:41:28 -0600 Subject: [PATCH 78/96] removed unneeded AlternateBack/FormardKey code from Toplevel --- .../Application/Application.Keyboard.cs | 32 -------- Terminal.Gui/Views/Toplevel.cs | 37 --------- UnitTests/Views/ToplevelTests.cs | 76 +------------------ 3 files changed, 1 insertion(+), 144 deletions(-) diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 84fade3cb8..c05ba6c343 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -29,20 +29,10 @@ public static Key AlternateForwardKey { KeyBindings.ReplaceKey (oldKey, _alternateForwardKey); } - OnAlternateForwardKeyChanged (new (oldKey, value)); } } } - private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) - { - // TODO: The fact Top has it's own AlternateForwardKey and events is needlessly complex. Remove it. - foreach (Toplevel top in _topLevels.ToArray ()) - { - top.OnAlternateForwardKeyChanged (e); - } - } - private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. @@ -66,21 +56,10 @@ public static Key AlternateBackwardKey { KeyBindings.ReplaceKey (oldKey, _alternateBackwardKey); } - - OnAlternateBackwardKeyChanged (new (oldKey, value)); } } } - private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) - { - // TODO: The fact Top has it's own AlternateBackwardKey and events is needlessly complex. Remove it. - foreach (Toplevel top in _topLevels.ToArray ()) - { - top.OnAlternateBackwardKeyChanged (oldKey); - } - } - private static Key _quitKey = Key.Empty; // Defined in config.json /// Gets or sets the key to quit the application. @@ -103,21 +82,10 @@ public static Key QuitKey { KeyBindings.ReplaceKey (oldKey, _quitKey); } - OnQuitKeyChanged (new (oldKey, value)); } } } - private static void OnQuitKeyChanged (KeyChangedEventArgs e) - { - // TODO: The fact Top has it's own QuitKey and events is needlessly complex. Remove it. - // Duplicate the list so if it changes during enumeration we're safe - foreach (Toplevel top in _topLevels.ToArray ()) - { - top.OnQuitKeyChanged (e); - } - } - /// /// Event fired when the user presses a key. Fired by . /// diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 4c23b215f2..3a862bd696 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -62,43 +62,6 @@ public Toplevel () private void Toplevel_MouseClick (object sender, MouseEventEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; } - // TODO: Deprecate - No need for this at View level; having at Application is sufficient. - /// Invoked when the is changed. - public event EventHandler AlternateBackwardKeyChanged; - - // TODO: Deprecate - No need for this at View level; having at Application is sufficient. - /// Invoked when the is changed. - public event EventHandler AlternateForwardKeyChanged; - - // TODO: Deprecate - No need for this at View level; having at Application is sufficient. - /// Virtual method to invoke the event. - /// - public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.ReplaceKey (e.OldKey, e.NewKey); - AlternateBackwardKeyChanged?.Invoke (this, e); - } - - // TODO: Deprecate - No need for this at View level; having at Application is sufficient. - /// Virtual method to invoke the event. - /// - public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.ReplaceKey (e.OldKey, e.NewKey); - AlternateForwardKeyChanged?.Invoke (this, e); - } - - /// Virtual method to invoke the event. - /// - public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.ReplaceKey (e.OldKey, e.NewKey); - QuitKeyChanged?.Invoke (this, e); - } - - /// Invoked when the is changed. - public event EventHandler QuitKeyChanged; - #endregion #region Subviews diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 7a5f9700c3..2d4a0a323b 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -573,23 +573,6 @@ public void Added_Event_Should_Not_Be_Used_To_Initialize_Toplevel_Events () void View_Added (object sender, SuperViewChangedEventArgs e) { - Assert.Throws ( - () => - Application.Top.AlternateForwardKeyChanged += - (s, e) => alternateForwardKey = (KeyCode)e.OldKey - ); - - Assert.Throws ( - () => - Application.Top.AlternateBackwardKeyChanged += - (s, e) => alternateBackwardKey = (KeyCode)e.OldKey - ); - - Assert.Throws ( - () => - Application.Top.QuitKeyChanged += (s, e) => - quitKey = (KeyCode)e.OldKey - ); Assert.False (wasAdded); wasAdded = true; view.Added -= View_Added; @@ -605,64 +588,7 @@ void View_Added (object sender, SuperViewChangedEventArgs e) Application.Shutdown (); } - - [Fact] - [AutoInitShutdown] - public void AlternateForwardKeyChanged_AlternateBackwardKeyChanged_QuitKeyChanged_Events () - { - Key alternateForwardKey = KeyCode.Null; - Key alternateBackwardKey = KeyCode.Null; - Key quitKey = KeyCode.Null; - - Key previousQuitKey = Application.QuitKey; - - Toplevel top = new (); - var view = new View (); - view.Initialized += View_Initialized; - - void View_Initialized (object sender, EventArgs e) - { - top.AlternateForwardKeyChanged += (s, e) => alternateForwardKey = e.OldKey; - top.AlternateBackwardKeyChanged += (s, e) => alternateBackwardKey = e.OldKey; - top.QuitKeyChanged += (s, e) => quitKey = e.OldKey; - } - - var win = new Window (); - win.Add (view); - top.Add (win); - Application.Begin (top); - - Assert.Equal (KeyCode.Null, alternateForwardKey); - Assert.Equal (KeyCode.Null, alternateBackwardKey); - Assert.Equal (KeyCode.Null, quitKey); - - Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey); - Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey); - Assert.Equal (Key.Esc, Application.QuitKey); - - Application.AlternateForwardKey = KeyCode.A; - Application.AlternateBackwardKey = KeyCode.B; - Application.QuitKey = KeyCode.C; - - Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, alternateForwardKey); - Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, alternateBackwardKey); - Assert.Equal (previousQuitKey, quitKey); - - Assert.Equal (KeyCode.A, Application.AlternateForwardKey); - Assert.Equal (KeyCode.B, Application.AlternateBackwardKey); - Assert.Equal (KeyCode.C, Application.QuitKey); - - // Replacing the defaults keys to avoid errors on others unit tests that are using it. - Application.AlternateForwardKey = Key.PageDown.WithCtrl; - Application.AlternateBackwardKey = Key.PageUp.WithCtrl; - Application.QuitKey = previousQuitKey; - - Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey); - Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey); - Assert.Equal (previousQuitKey, Application.QuitKey); - top.Dispose (); - } - + [Fact] [AutoInitShutdown] public void Mouse_Drag_On_Top_With_Superview_Null () From 0c56dfeb5a5d2e995291a717f4ee451552736be4 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 12:56:55 -0600 Subject: [PATCH 79/96] Moved view navigation out of Toplevel and into Application (via ViewNavigation static class). --- .../Application/Application.Keyboard.cs | 8 +- .../Application/Application.Overlapped.cs | 163 ++++++++++++++++++ Terminal.Gui/Input/KeyBindings.cs | 19 ++ Terminal.Gui/Views/Toplevel.cs | 160 +---------------- 4 files changed, 187 insertions(+), 163 deletions(-) diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index c05ba6c343..bc6507c863 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -330,7 +330,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - Current.MoveNextView (); + ViewNavigation.MoveNextView (); return true; } @@ -341,7 +341,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - Current.MovePreviousView (); + ViewNavigation.MovePreviousView (); return true; } @@ -352,7 +352,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - Current.MoveNextViewOrTop (); + ViewNavigation.MoveNextViewOrTop (); return true; } @@ -363,7 +363,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - Current.MovePreviousViewOrTop (); + ViewNavigation.MovePreviousViewOrTop (); return true; } diff --git a/Terminal.Gui/Application/Application.Overlapped.cs b/Terminal.Gui/Application/Application.Overlapped.cs index 58eccfa3a2..1ac547fde5 100644 --- a/Terminal.Gui/Application/Application.Overlapped.cs +++ b/Terminal.Gui/Application/Application.Overlapped.cs @@ -1,6 +1,169 @@ #nullable enable +using static Terminal.Gui.View; +using System.Reflection; + namespace Terminal.Gui; +internal static class ViewNavigation +{ + /// + /// Gets the deepest focused subview of the specified . + /// + /// + /// + internal static View GetDeepestFocusedSubview (View view) + { + if (view is null) + { + return null; + } + + foreach (View v in view.Subviews) + { + if (v.HasFocus) + { + return GetDeepestFocusedSubview (v); + } + } + + return view; + } + + /// + /// Sets the focus to the next view in the list. If the last view is focused, the first view is focused. + /// + /// + /// + internal static void FocusNearestView (IEnumerable? viewsInTabIndexes, NavigationDirection direction) + { + if (viewsInTabIndexes is null) + { + return; + } + + var found = false; + var focusProcessed = false; + var idx = 0; + + foreach (View v in viewsInTabIndexes) + { + if (v == Application.Current) + { + found = true; + } + + if (found && v != Application.Current) + { + if (direction == NavigationDirection.Forward) + { + Application.Current.SuperView?.FocusNext (); + } + else + { + Application.Current.SuperView?.FocusPrev (); + } + + focusProcessed = true; + + if (Application.Current.SuperView?.Focused is { } && Application.Current.SuperView.Focused != Application.Current) + { + return; + } + } + else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1) + { + viewsInTabIndexes.ToList () [0].SetFocus (); + } + + idx++; + } + } + /// + /// Moves the focus to + /// + internal static void MoveNextView () + { + View old = GetDeepestFocusedSubview (Application.Current.Focused); + + if (!Application.Current.FocusNext ()) + { + Application.Current.FocusNext (); + } + + if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) + { + old?.SetNeedsDisplay (); + Application.Current.Focused?.SetNeedsDisplay (); + } + else + { + FocusNearestView (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward); + } + } + + internal static void MoveNextViewOrTop () + { + if (Application.OverlappedTop is null) + { + Toplevel top = Application.Current.Modal ? Application.Current : Application.Top; + top.FocusNext (); + + if (top.Focused is null) + { + top.FocusNext (); + } + + top.SetNeedsDisplay (); + Application.BringOverlappedTopToFront (); + } + else + { + Application.OverlappedMoveNext (); + } + } + + internal static void MovePreviousView () + { + View old = GetDeepestFocusedSubview (Application.Current.Focused); + + if (!Application.Current.FocusPrev ()) + { + Application.Current.FocusPrev (); + } + + if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) + { + old?.SetNeedsDisplay (); + Application.Current.Focused?.SetNeedsDisplay (); + } + else + { + FocusNearestView (Application.Current.SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward); + } + } + + internal static void MovePreviousViewOrTop () + { + if (Application.OverlappedTop is null) + { + Toplevel top = Application.Current.Modal ? Application.Current : Application.Top; + top.FocusPrev (); + + if (top.Focused is null) + { + top.FocusPrev (); + } + + top.SetNeedsDisplay (); + Application.BringOverlappedTopToFront (); + } + else + { + Application.OverlappedMovePrevious (); + } + } +} + public static partial class Application // App-level View Navigation { diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs index 8ca45568ea..89ffde7adc 100644 --- a/Terminal.Gui/Input/KeyBindings.cs +++ b/Terminal.Gui/Input/KeyBindings.cs @@ -110,6 +110,25 @@ public void Add (Key key, KeyBindingScope scope, View? boundViewForAppScope = nu } } + + /// + /// Adds a new key combination that will trigger the commands in . + /// + /// If the key is already bound to a different array of s it will be rebound + /// . + /// + /// + /// + /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch + /// focus to another view and perform multiple commands there). + /// + /// The key to check. + /// The scope for the command. + /// + /// The command to invoked on the when is pressed. When + /// multiple commands are provided,they will be applied in sequence. The bound strike will be + /// consumed if any took effect. + /// public void Add (Key key, KeyBindingScope scope, params Command [] commands) { if (BoundView is { } && scope.FastHasFlags (KeyBindingScope.Application)) diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 3a862bd696..11d2162100 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -411,165 +411,7 @@ public override void OnDrawContent (Rectangle viewport) /// public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } - - /// - /// Sets the focus to the next view in the list. If the last view is focused, the first view is focused. - /// - /// - /// - private void FocusNearestView (IEnumerable viewsInTabIndexes, NavigationDirection direction) - { - if (viewsInTabIndexes is null) - { - return; - } - - var found = false; - var focusProcessed = false; - var idx = 0; - - foreach (View v in viewsInTabIndexes) - { - if (v == this) - { - found = true; - } - - if (found && v != this) - { - if (direction == NavigationDirection.Forward) - { - SuperView?.FocusNext (); - } - else - { - SuperView?.FocusPrev (); - } - - focusProcessed = true; - - if (SuperView.Focused is { } && SuperView.Focused != this) - { - return; - } - } - else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1) - { - viewsInTabIndexes.ToList () [0].SetFocus (); - } - - idx++; - } - } - - /// - /// Gets the deepest focused subview of the specified . - /// - /// - /// - private View GetDeepestFocusedSubview (View view) - { - if (view is null) - { - return null; - } - - foreach (View v in view.Subviews) - { - if (v.HasFocus) - { - return GetDeepestFocusedSubview (v); - } - } - - return view; - } - - /// - /// Moves the focus to - /// - internal void MoveNextView () - { - View old = GetDeepestFocusedSubview (Focused); - - if (!FocusNext ()) - { - FocusNext (); - } - - if (old != Focused && old != Focused?.Focused) - { - old?.SetNeedsDisplay (); - Focused?.SetNeedsDisplay (); - } - else - { - FocusNearestView (SuperView?.TabIndexes, NavigationDirection.Forward); - } - } - - internal void MoveNextViewOrTop () - { - if (Application.OverlappedTop is null) - { - Toplevel top = Modal ? this : Application.Top; - top.FocusNext (); - - if (top.Focused is null) - { - top.FocusNext (); - } - - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); - } - else - { - Application.OverlappedMoveNext (); - } - } - - internal void MovePreviousView () - { - View old = GetDeepestFocusedSubview (Focused); - - if (!FocusPrev ()) - { - FocusPrev (); - } - - if (old != Focused && old != Focused?.Focused) - { - old?.SetNeedsDisplay (); - Focused?.SetNeedsDisplay (); - } - else - { - FocusNearestView (SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward); - } - } - - internal void MovePreviousViewOrTop () - { - if (Application.OverlappedTop is null) - { - Toplevel top = Modal ? this : Application.Top; - top.FocusPrev (); - - if (top.Focused is null) - { - top.FocusPrev (); - } - - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); - } - else - { - Application.OverlappedMovePrevious (); - } - } - + #endregion #region Size / Position Management From 73a9dc37c48587e4d042ada19ece2296b17b0fe5 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 14:15:32 -0600 Subject: [PATCH 80/96] Fixed nullable warnings 2 --- .../Application/Application.Initialization.cs | 32 ++-- .../Application/Application.Keyboard.cs | 6 +- Terminal.Gui/Application/Application.Mouse.cs | 14 +- .../Application/Application.Overlapped.cs | 20 +-- Terminal.Gui/Application/Application.Run.cs | 74 +++++---- .../Application/Application.Toplevel.cs | 6 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 5 +- Terminal.Gui/View/Adornment/Margin.cs | 4 +- Terminal.Gui/View/Adornment/ShadowView.cs | 2 +- Terminal.Gui/View/Layout/Dim.cs | 2 +- Terminal.Gui/View/Layout/DimView.cs | 2 +- Terminal.Gui/View/Layout/Pos.cs | 4 +- Terminal.Gui/View/Layout/PosView.cs | 2 +- Terminal.Gui/Views/Menu/MenuBar.cs | 2 +- UICatalog/UICatalog.cs | 2 +- UnitTests/Views/MenuBarTests.cs | 140 ++++++++---------- UnitTests/Views/ToplevelTests.cs | 9 +- 17 files changed, 153 insertions(+), 173 deletions(-) diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 2e184b2853..0d9b2caf51 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -36,7 +36,7 @@ public static partial class Application // Initialization (Init/Shutdown) /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static void Init (ConsoleDriver driver = null, string driverName = null) { InternalInit (driver, driverName); } + public static void Init (ConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); } internal static bool _initialized; internal static int _mainThreadId = -1; @@ -53,8 +53,8 @@ public static partial class Application // Initialization (Init/Shutdown) [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] internal static void InternalInit ( - ConsoleDriver driver = null, - string driverName = null, + ConsoleDriver? driver = null, + string? driverName = null, bool calledViaRunT = false ) { @@ -114,17 +114,17 @@ internal static void InternalInit ( } else { - List drivers = GetDriverTypes (); - Type driverType = drivers.FirstOrDefault (t => t.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase)); + List drivers = GetDriverTypes (); + Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase)); if (driverType is { }) { - Driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Driver = (ConsoleDriver)Activator.CreateInstance (driverType)!; } else { throw new ArgumentException ( - $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}" + $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t!.Name))}" ); } } @@ -132,7 +132,7 @@ internal static void InternalInit ( try { - MainLoop = Driver.Init (); + MainLoop = Driver!.Init (); } catch (InvalidOperationException ex) { @@ -159,22 +159,22 @@ internal static void InternalInit ( InitializedChanged?.Invoke (null, new (in _initialized)); } - private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); } - private static void Driver_KeyDown (object sender, Key e) { OnKeyDown (e); } - private static void Driver_KeyUp (object sender, Key e) { OnKeyUp (e); } - private static void Driver_MouseEvent (object sender, MouseEvent e) { OnMouseEvent (e); } + private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); } + private static void Driver_KeyDown (object? sender, Key e) { OnKeyDown (e); } + private static void Driver_KeyUp (object? sender, Key e) { OnKeyUp (e); } + private static void Driver_MouseEvent (object? sender, MouseEvent e) { OnMouseEvent (e); } /// Gets of list of types that are available. /// [RequiresUnreferencedCode ("AOT")] - public static List GetDriverTypes () + public static List GetDriverTypes () { // use reflection to get the list of drivers - List driverTypes = new (); + List driverTypes = new (); foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ()) { - foreach (Type type in asm.GetTypes ()) + foreach (Type? type in asm.GetTypes ()) { if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) { @@ -207,5 +207,5 @@ public static void Shutdown () /// /// Intended to support unit tests that need to know when the application has been initialized. /// - public static event EventHandler> InitializedChanged; + public static event EventHandler>? InitializedChanged; } diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index bc6507c863..10419bf808 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -98,7 +98,7 @@ public static Key QuitKey /// and events. /// Fired after and before . /// - public static event EventHandler KeyDown; + public static event EventHandler? KeyDown; /// /// Called by the when the user presses a key. Fires the event @@ -199,7 +199,7 @@ public static bool OnKeyDown (Key keyEvent) /// and events. /// Fired after . /// - public static event EventHandler KeyUp; + public static event EventHandler? KeyUp; /// /// Called by the when the user releases a key. Fires the event @@ -304,7 +304,7 @@ internal static void AddApplicationKeyBindings () { if (OverlappedTop is { }) { - RequestStop (Current); + RequestStop (Current!); } else { diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs index 61fc6d63e4..713e6375d9 100644 --- a/Terminal.Gui/Application/Application.Mouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -9,32 +9,32 @@ public static partial class Application // Mouse handling public static bool IsMouseDisabled { get; set; } /// The current object that wants continuous mouse button pressed events. - public static View WantContinuousButtonPressedView { get; private set; } + public static View? WantContinuousButtonPressedView { get; private set; } /// /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to /// this view until the view calls or the mouse is released. /// - public static View MouseGrabView { get; private set; } + public static View? MouseGrabView { get; private set; } /// Invoked when a view wants to grab the mouse; can be canceled. - public static event EventHandler GrabbingMouse; + public static event EventHandler? GrabbingMouse; /// Invoked when a view wants un-grab the mouse; can be canceled. - public static event EventHandler UnGrabbingMouse; + public static event EventHandler? UnGrabbingMouse; /// Invoked after a view has grabbed the mouse. - public static event EventHandler GrabbedMouse; + public static event EventHandler? GrabbedMouse; /// Invoked after a view has un-grabbed the mouse. - public static event EventHandler UnGrabbedMouse; + public static event EventHandler? UnGrabbedMouse; /// /// Grabs the mouse, forcing all mouse events to be routed to the specified view until /// is called. /// /// View that will receive all mouse events until is invoked. - public static void GrabMouse (View view) + public static void GrabMouse (View? view) { if (view is null) { diff --git a/Terminal.Gui/Application/Application.Overlapped.cs b/Terminal.Gui/Application/Application.Overlapped.cs index 1ac547fde5..26e931c989 100644 --- a/Terminal.Gui/Application/Application.Overlapped.cs +++ b/Terminal.Gui/Application/Application.Overlapped.cs @@ -11,7 +11,7 @@ internal static class ViewNavigation /// /// /// - internal static View GetDeepestFocusedSubview (View view) + internal static View? GetDeepestFocusedSubview (View? view) { if (view is null) { @@ -30,7 +30,7 @@ internal static View GetDeepestFocusedSubview (View view) } /// - /// Sets the focus to the next view in the list. If the last view is focused, the first view is focused. + /// Sets the focus to the next view in the list. If the last view is focused, the first view is focused. /// /// /// @@ -56,11 +56,11 @@ internal static void FocusNearestView (IEnumerable? viewsInTabIndexes, Nav { if (direction == NavigationDirection.Forward) { - Application.Current.SuperView?.FocusNext (); + Application.Current!.SuperView?.FocusNext (); } else { - Application.Current.SuperView?.FocusPrev (); + Application.Current!.SuperView?.FocusPrev (); } focusProcessed = true; @@ -83,7 +83,7 @@ internal static void FocusNearestView (IEnumerable? viewsInTabIndexes, Nav /// internal static void MoveNextView () { - View old = GetDeepestFocusedSubview (Application.Current.Focused); + View? old = GetDeepestFocusedSubview (Application.Current!.Focused); if (!Application.Current.FocusNext ()) { @@ -105,8 +105,8 @@ internal static void MoveNextViewOrTop () { if (Application.OverlappedTop is null) { - Toplevel top = Application.Current.Modal ? Application.Current : Application.Top; - top.FocusNext (); + Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; + top!.FocusNext (); if (top.Focused is null) { @@ -124,7 +124,7 @@ internal static void MoveNextViewOrTop () internal static void MovePreviousView () { - View old = GetDeepestFocusedSubview (Application.Current.Focused); + View? old = GetDeepestFocusedSubview (Application.Current!.Focused); if (!Application.Current.FocusPrev ()) { @@ -146,8 +146,8 @@ internal static void MovePreviousViewOrTop () { if (Application.OverlappedTop is null) { - Toplevel top = Application.Current.Modal ? Application.Current : Application.Top; - top.FocusPrev (); + Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; + top!.FocusPrev (); if (top.Focused is null) { diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 541ad71417..bbff9c1ba6 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -8,7 +8,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) { // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`. // This variable is set in `End` in this case so that `Begin` correctly sets `Top`. - private static Toplevel _cachedRunStateToplevel; + private static Toplevel? _cachedRunStateToplevel; /// /// Notify that a new was created ( was called). The token is @@ -19,7 +19,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// must also subscribe to and manually dispose of the token /// when the application is done. /// - public static event EventHandler NotifyNewRunState; + public static event EventHandler? NotifyNewRunState; /// Notify that an existent is stopping ( was called). /// @@ -27,7 +27,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// must also subscribe to and manually dispose of the token /// when the application is done. /// - public static event EventHandler NotifyStopRunState; + public static event EventHandler? NotifyStopRunState; /// Building block API: Prepares the provided for execution. /// @@ -96,9 +96,9 @@ public static RunState Begin (Toplevel toplevel) throw new ObjectDisposedException (Top.GetType ().FullName); } } - else if (OverlappedTop is { } && toplevel != Top && _topLevels.Contains (Top)) + else if (OverlappedTop is { } && toplevel != Top && _topLevels.Contains (Top!)) { - Top.OnLeave (toplevel); + Top!.OnLeave (toplevel); } // BUGBUG: We should not depend on `Id` internally. @@ -120,7 +120,7 @@ public static RunState Begin (Toplevel toplevel) } else { - Toplevel dup = _topLevels.FirstOrDefault (x => x.Id == toplevel.Id); + Toplevel? dup = _topLevels.FirstOrDefault (x => x.Id == toplevel.Id); if (dup is null) { @@ -150,7 +150,7 @@ public static RunState Begin (Toplevel toplevel) if (toplevel.Visible) { Current?.OnDeactivate (toplevel); - Toplevel previousCurrent = Current; + Toplevel previousCurrent = Current!; Current = toplevel; Current.OnActivate (previousCurrent); @@ -161,11 +161,10 @@ public static RunState Begin (Toplevel toplevel) refreshDriver = false; } } - else if ((OverlappedTop != null - && toplevel != OverlappedTop + else if ((toplevel != OverlappedTop && Current?.Modal == true && !_topLevels.Peek ().Modal) - || (OverlappedTop is { } && toplevel != OverlappedTop && Current?.Running == false)) + || (toplevel != OverlappedTop && Current?.Running == false)) { refreshDriver = false; MoveCurrent (toplevel); @@ -173,10 +172,10 @@ public static RunState Begin (Toplevel toplevel) else { refreshDriver = false; - MoveCurrent (Current); + MoveCurrent (Current!); } - toplevel.SetRelativeLayout (Driver.Screen.Size); + toplevel.SetRelativeLayout (Driver!.Screen.Size); toplevel.LayoutSubviews (); toplevel.PositionToplevels (); @@ -216,7 +215,7 @@ public static RunState Begin (Toplevel toplevel) internal static bool PositionCursor (View view) { // Find the most focused view and position the cursor there. - View mostFocused = view?.MostFocused; + View? mostFocused = view?.MostFocused; if (mostFocused is null) { @@ -233,7 +232,7 @@ internal static bool PositionCursor (View view) // If the view is not visible or enabled, don't position the cursor if (!mostFocused.Visible || !mostFocused.Enabled) { - Driver.GetCursorVisibility (out CursorVisibility current); + Driver!.GetCursorVisibility (out CursorVisibility current); if (current != CursorVisibility.Invisible) { @@ -245,7 +244,7 @@ internal static bool PositionCursor (View view) // If the view is not visible within it's superview, don't position the cursor Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty }); - Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen; + Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen; if (!superViewViewport.IntersectsWith (mostFocusedViewport)) { @@ -254,7 +253,7 @@ internal static bool PositionCursor (View view) Point? cursor = mostFocused.PositionCursor (); - Driver.GetCursorVisibility (out CursorVisibility currentCursorVisibility); + Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility); if (cursor is { }) { @@ -306,7 +305,7 @@ internal static bool PositionCursor (View view) /// The created object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static Toplevel Run (Func errorHandler = null, ConsoleDriver driver = null) { return Run (errorHandler, driver); } + public static Toplevel Run (Func? errorHandler = null, ConsoleDriver? driver = null) { return Run (errorHandler, driver); } /// /// Runs the application by creating a -derived object of type T and calling @@ -331,7 +330,7 @@ internal static bool PositionCursor (View view) /// The created T object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static T Run (Func errorHandler = null, ConsoleDriver driver = null) + public static T Run (Func? errorHandler = null, ConsoleDriver? driver = null) where T : Toplevel, new () { if (!_initialized) @@ -385,7 +384,7 @@ public static T Run (Func errorHandler = null, ConsoleDriver /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, /// rethrows when null). /// - public static void Run (Toplevel view, Func errorHandler = null) + public static void Run (Toplevel view, Func? errorHandler = null) { ArgumentNullException.ThrowIfNull (view); @@ -460,7 +459,7 @@ public static void Run (Toplevel view, Func errorHandler = null /// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a /// token that can be used to stop the timeout by calling . /// - public static object AddTimeout (TimeSpan time, Func callback) { return MainLoop?.AddTimeout (time, callback); } + public static object AddTimeout (TimeSpan time, Func callback) { return MainLoop!.AddTimeout (time, callback); } /// Removes a previously scheduled timeout /// The token parameter is the value returned by . @@ -498,8 +497,7 @@ public static void Invoke (Action action) public static void Refresh () { // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear - Driver.ClearContents (); - View last = null; + Driver!.ClearContents (); foreach (Toplevel v in _topLevels.Reverse ()) { @@ -509,8 +507,6 @@ public static void Refresh () v.SetSubViewNeedsDisplay (); v.Draw (); } - - last = v; } Driver.Refresh (); @@ -518,11 +514,11 @@ public static void Refresh () /// This event is raised on each iteration of the main loop. /// See also - public static event EventHandler Iteration; + public static event EventHandler? Iteration; /// The driver for the application /// The main loop. - internal static MainLoop MainLoop { get; private set; } + internal static MainLoop? MainLoop { get; private set; } /// /// Set to true to cause to be called after the first iteration. Set to false (the default) to @@ -661,17 +657,17 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) /// property on the currently running to false. /// /// - public static void RequestStop (Toplevel top = null) + public static void RequestStop (Toplevel? top = null) { - if (OverlappedTop is null || top is null || (OverlappedTop is null && top is { })) + if (OverlappedTop is null || top is null) { top = Current; } if (OverlappedTop != null - && top.IsOverlappedContainer + && top!.IsOverlappedContainer && top?.Running == true - && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) + && (Current?.Modal == false || Current is { Modal: true, Running: false })) { OverlappedTop.RequestStop (); } @@ -679,7 +675,7 @@ public static void RequestStop (Toplevel top = null) && top != Current && Current?.Running == true && Current?.Modal == true - && top.Modal + && top!.Modal && top.Running) { var ev = new ToplevelClosingEventArgs (Current); @@ -708,13 +704,13 @@ public static void RequestStop (Toplevel top = null) && top != Current && Current?.Modal == false && Current?.Running == true - && !top.Running) + && !top!.Running) || (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false && Current?.Running == false - && !top.Running + && !top!.Running && _topLevels.ToArray () [1].Running)) { MoveCurrent (top); @@ -722,7 +718,7 @@ public static void RequestStop (Toplevel top = null) else if (OverlappedTop != null && Current != top && Current?.Running == true - && !top.Running + && !top!.Running && Current?.Modal == true && top.Modal) { @@ -734,9 +730,9 @@ public static void RequestStop (Toplevel top = null) && Current == top && OverlappedTop?.Running == true && Current?.Running == true - && top.Running + && top!.Running && Current?.Modal == true - && top.Modal) + && top!.Modal) { // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top, // both are the same, so needed to set the Current.Running to false too. @@ -747,13 +743,13 @@ public static void RequestStop (Toplevel top = null) { Toplevel currentTop; - if (top == Current || (Current?.Modal == true && !top.Modal)) + if (top == Current || (Current?.Modal == true && !top!.Modal)) { - currentTop = Current; + currentTop = Current!; } else { - currentTop = top; + currentTop = top!; } if (!currentTop.Running) diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs index d8996a3838..e272ea7aab 100644 --- a/Terminal.Gui/Application/Application.Toplevel.cs +++ b/Terminal.Gui/Application/Application.Toplevel.cs @@ -10,7 +10,7 @@ public static partial class Application // Toplevel handling /// The object used for the application on startup () /// The top. - public static Toplevel Top { get; private set; } + public static Toplevel? Top { get; private set; } // TODO: Determine why this can't just return _topLevels.Peek()? /// @@ -22,7 +22,7 @@ public static partial class Application // Toplevel handling /// This will only be distinct from in scenarios where is . /// /// The current. - public static Toplevel Current { get; private set; } + public static Toplevel? Current { get; private set; } /// /// If is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current. @@ -195,7 +195,7 @@ private static bool MoveCurrent (Toplevel top) /// Event handlers can set to to prevent /// from changing it's size to match the new terminal size. /// - public static event EventHandler SizeChanging; + public static event EventHandler? SizeChanging; /// /// Called when the application's size changes. Sets the size of all s and fires the diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index cad728b72f..8e6a08d598 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -1,3 +1,4 @@ +#nullable enable // // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations. // @@ -16,7 +17,7 @@ public abstract class ConsoleDriver { // As performance is a concern, we keep track of the dirty lines and only refresh those. // This is in addition to the dirty flag on each cell. - internal bool [] _dirtyLines; + internal bool []? _dirtyLines; // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application? /// Gets the location and size of the terminal screen. @@ -443,7 +444,7 @@ public virtual void Move (int col, int row) public abstract bool SetCursorVisibility (CursorVisibility visibility); /// The event fired when the terminal is resized. - public event EventHandler SizeChanged; + public event EventHandler? SizeChanged; /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. /// This is only implemented in . diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index 046965e321..8ef63fe043 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -222,10 +222,10 @@ private void Margin_LayoutStarted (object? sender, LayoutEventArgs e) // Adjust the shadow such that it is drawn aligned with the Border if (ShadowStyle != ShadowStyle.None && _rightShadow is { } && _bottomShadow is { }) { - _rightShadow.Y = Parent.Border.Thickness.Top > 0 + _rightShadow.Y = Parent is { } && Parent.Border.Thickness.Top > 0 ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.Settings.FastHasFlags (BorderSettings.Title) ? 1 : 0) : 1; - _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1; + _bottomShadow.X = Parent is { } && Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1; } } } diff --git a/Terminal.Gui/View/Adornment/ShadowView.cs b/Terminal.Gui/View/Adornment/ShadowView.cs index c5e7a428aa..ad06dc754c 100644 --- a/Terminal.Gui/View/Adornment/ShadowView.cs +++ b/Terminal.Gui/View/Adornment/ShadowView.cs @@ -15,7 +15,7 @@ public override Attribute GetNormalColor () { var attr = Attribute.Default; - if (adornment.Parent.SuperView is { }) + if (adornment.Parent?.SuperView is { }) { attr = adornment.Parent.SuperView.GetNormalColor (); } diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index 7dfc6eb2e3..3102d5d3c4 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -232,7 +232,7 @@ internal virtual int Calculate (int location, int superviewContentSize, View us, } var newDim = new DimCombine (AddOrSubtract.Add, left, right); - (left as DimView)?.Target.SetNeedsLayout (); + (left as DimView)?.Target?.SetNeedsLayout (); return newDim; } diff --git a/Terminal.Gui/View/Layout/DimView.cs b/Terminal.Gui/View/Layout/DimView.cs index 09ea96800c..e95efd4fb2 100644 --- a/Terminal.Gui/View/Layout/DimView.cs +++ b/Terminal.Gui/View/Layout/DimView.cs @@ -30,7 +30,7 @@ public DimView (View? view, Dimension dimension) public override bool Equals (object? other) { return other is DimView abs && abs.Target == Target && abs.Dimension == Dimension; } /// - public override int GetHashCode () { return Target.GetHashCode (); } + public override int GetHashCode () { return Target!.GetHashCode (); } /// /// Gets the View the dimension is anchored to. diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index 853bfa0abb..63e14b67f2 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -379,7 +379,7 @@ public bool Has (Type type, out Pos pos) if (left is PosView view) { - view.Target.SetNeedsLayout (); + view.Target?.SetNeedsLayout (); } return newPos; @@ -408,7 +408,7 @@ public bool Has (Type type, out Pos pos) if (left is PosView view) { - view.Target.SetNeedsLayout (); + view.Target?.SetNeedsLayout (); } return newPos; diff --git a/Terminal.Gui/View/Layout/PosView.cs b/Terminal.Gui/View/Layout/PosView.cs index fdf5bf784e..8ceba980fc 100644 --- a/Terminal.Gui/View/Layout/PosView.cs +++ b/Terminal.Gui/View/Layout/PosView.cs @@ -28,7 +28,7 @@ public class PosView (View? view, Side side) : Pos public override bool Equals (object? other) { return other is PosView abs && abs.Target == Target && abs.Side == Side; } /// - public override int GetHashCode () { return Target.GetHashCode (); } + public override int GetHashCode () { return Target!.GetHashCode (); } /// public override string ToString () diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 0b4e574dec..3428b2c93d 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -1594,7 +1594,7 @@ private MenuBar GetMouseGrabViewInstance (View view) /// - public bool EnableForDesign (in TContext context) where TContext : notnull + public bool EnableForDesign (ref readonly TContext context) where TContext : notnull { if (context is not Func actionFn) { diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index dd18f0bd1d..a2cc9a4749 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -715,7 +715,7 @@ public void ConfigChanged () } ColorScheme = Colors.ColorSchemes [_topLevelColorScheme]; - Application.Top.SetNeedsDisplay (); + Application.Top!.SetNeedsDisplay (); }; schemeMenuItems.Add (item); } diff --git a/UnitTests/Views/MenuBarTests.cs b/UnitTests/Views/MenuBarTests.cs index 35d08beff7..6f9de46836 100644 --- a/UnitTests/Views/MenuBarTests.cs +++ b/UnitTests/Views/MenuBarTests.cs @@ -1,6 +1,4 @@ -using UICatalog.Scenarios; -using Xunit.Abstractions; - +using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; @@ -34,13 +32,13 @@ public void AllowNullChecked_Get_Set () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); Assert.True ( menu._openMenu.NewMouseEvent ( - new() { Position = new (0, 0), Flags = MouseFlags.Button1Clicked, View = menu._openMenu } + new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked, View = menu._openMenu } ) ); Application.MainLoop.RunIteration (); @@ -54,7 +52,7 @@ public void AllowNullChecked_Get_Set () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); Application.Refresh (); @@ -63,16 +61,14 @@ public void AllowNullChecked_Get_Set () @$" Nullable Checked ┌──────────────────────┐ -│ { - CM.Glyphs.CheckStateNone -} Check this out 你 │ +│ {CM.Glyphs.CheckStateNone} Check this out 你 │ └──────────────────────┘", output ); Assert.True ( menu._openMenu.NewMouseEvent ( - new() { Position = new (0, 0), Flags = MouseFlags.Button1Clicked, View = menu._openMenu } + new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked, View = menu._openMenu } ) ); Application.MainLoop.RunIteration (); @@ -84,13 +80,13 @@ Nullable Checked Assert.True ( menu.NewMouseEvent ( - new() { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); Assert.True ( menu._openMenu.NewMouseEvent ( - new() { Position = new (0, 0), Flags = MouseFlags.Button1Clicked, View = menu._openMenu } + new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked, View = menu._openMenu } ) ); Application.MainLoop.RunIteration (); @@ -185,7 +181,7 @@ public void Constructors_Defaults () Assert.True (menuBar.WantMousePositionReports); Assert.False (menuBar.IsMenuOpen); - menuBar = new() { Menus = [] }; + menuBar = new () { Menus = [] }; Assert.Equal (0, menuBar.X); Assert.Equal (0, menuBar.Y); Assert.IsType (menuBar.Width); @@ -296,7 +292,7 @@ public void Disabled_MenuItem_Is_Never_Selected () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); top.Draw (); @@ -317,7 +313,7 @@ public void Disabled_MenuItem_Is_Never_Selected () Assert.True ( top.Subviews [1] .NewMouseEvent ( - new() { Position = new (0, 2), Flags = MouseFlags.Button1Clicked, View = top.Subviews [1] } + new () { Position = new (0, 2), Flags = MouseFlags.Button1Clicked, View = top.Subviews [1] } ) ); top.Subviews [1].Draw (); @@ -338,7 +334,7 @@ top.Subviews [1] Assert.True ( top.Subviews [1] .NewMouseEvent ( - new() { Position = new (0, 2), Flags = MouseFlags.ReportMousePosition, View = top.Subviews [1] } + new () { Position = new (0, 2), Flags = MouseFlags.ReportMousePosition, View = top.Subviews [1] } ) ); top.Subviews [1].Draw (); @@ -516,7 +512,7 @@ void ChangeMenuTitle (string title) output ); - Application.OnMouseEvent (new() { Position = new (20, 5), Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (20, 5), Flags = MouseFlags.Button1Clicked }); firstIteration = false; @@ -549,7 +545,7 @@ void ChangeMenuTitle (string title) { menu.OpenMenu (); - Application.OnMouseEvent (new() { Position = new (20, 5 + i), Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (20, 5 + i), Flags = MouseFlags.Button1Clicked }); firstIteration = false; Application.RunIteration (ref rsDialog, ref firstIteration); @@ -705,7 +701,7 @@ void ChangeMenuTitle (string title) output ); - Application.OnMouseEvent (new() { Position = new (20, 5), Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (20, 5), Flags = MouseFlags.Button1Clicked }); firstIteration = false; @@ -727,7 +723,7 @@ void ChangeMenuTitle (string title) { menu.OpenMenu (); - Application.OnMouseEvent (new() { Position = new (20, 5 + i), Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (20, 5 + i), Flags = MouseFlags.Button1Clicked }); firstIteration = false; Application.RunIteration (ref rs, ref firstIteration); @@ -1253,15 +1249,15 @@ params KeyCode [] keys MenuItem mbiCurrent = null; MenuItem miCurrent = null; - MenuBar menu = new MenuBar (); - menu.EnableForDesign ( - new Func (s => - { - miAction = s as string; + var menu = new MenuBar (); - return true; - }) - ); + Func fn = s => + { + miAction = s as string; + + return true; + }; + menu.EnableForDesign (ref fn); menu.Key = KeyCode.F9; menu.MenuOpening += (s, e) => mbiCurrent = e.CurrentMenu; @@ -1303,15 +1299,17 @@ public void KeyBindings_Shortcut_Commands (string expectedAction, params KeyCode MenuItem mbiCurrent = null; MenuItem miCurrent = null; - MenuBar menu = new MenuBar (); + var menu = new MenuBar (); + menu.EnableForDesign ( - new Func (s => - { - miAction = s as string; + new Func ( + s => + { + miAction = s as string; - return true; - }) - ); + return true; + }) + ); menu.Key = KeyCode.F9; menu.MenuOpening += (s, e) => mbiCurrent = e.CurrentMenu; @@ -1478,13 +1476,13 @@ .Children [0] top.Add (menu); Application.Begin (top); - Assert.True (menu.NewMouseEvent (new() { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu })); + Assert.True (menu.NewMouseEvent (new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu })); Assert.True (menu.IsMenuOpen); top.Draw (); TestHelpers.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); - Assert.True (menu.NewMouseEvent (new() { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu })); + Assert.True (menu.NewMouseEvent (new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); top.Draw (); TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); @@ -1985,7 +1983,7 @@ public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKey top.Remove (menu); // Now test WITH HotKeys - menu = new() + menu = new () { Menus = [ @@ -2114,9 +2112,9 @@ public void MenuBar_With_Action_But_Without_MenuItems_Not_Throw () { Menus = [ - new() { Title = "Test 1", Action = () => { } }, + new () { Title = "Test 1", Action = () => { } }, - new() { Title = "Test 2", Action = () => { } } + new () { Title = "Test 2", Action = () => { } } ] }; @@ -2204,7 +2202,7 @@ public void MenuOpened_On_Disabled_MenuItem () // open the menu Assert.True ( menu.NewMouseEvent ( - new() { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); Assert.True (menu.IsMenuOpen); @@ -2213,7 +2211,7 @@ public void MenuOpened_On_Disabled_MenuItem () Assert.True ( mCurrent.NewMouseEvent ( - new() { Position = new (1, 1), Flags = MouseFlags.ReportMousePosition, View = mCurrent } + new () { Position = new (1, 1), Flags = MouseFlags.ReportMousePosition, View = mCurrent } ) ); Assert.True (menu.IsMenuOpen); @@ -2222,7 +2220,7 @@ public void MenuOpened_On_Disabled_MenuItem () Assert.True ( mCurrent.NewMouseEvent ( - new() { Position = new (1, 1), Flags = MouseFlags.ReportMousePosition, View = mCurrent } + new () { Position = new (1, 1), Flags = MouseFlags.ReportMousePosition, View = mCurrent } ) ); Assert.True (menu.IsMenuOpen); @@ -2231,7 +2229,7 @@ public void MenuOpened_On_Disabled_MenuItem () Assert.True ( mCurrent.NewMouseEvent ( - new() { Position = new (1, 2), Flags = MouseFlags.ReportMousePosition, View = mCurrent } + new () { Position = new (1, 2), Flags = MouseFlags.ReportMousePosition, View = mCurrent } ) ); Assert.True (menu.IsMenuOpen); @@ -2241,7 +2239,7 @@ public void MenuOpened_On_Disabled_MenuItem () // close the menu Assert.True ( menu.NewMouseEvent ( - new() { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); Assert.False (menu.IsMenuOpen); @@ -2411,7 +2409,7 @@ public void MouseEvent_Test () // Click on Edit Assert.True ( menu.NewMouseEvent ( - new() { Position = new (10, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (10, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); Assert.True (menu.IsMenuOpen); @@ -2421,7 +2419,7 @@ public void MouseEvent_Test () // Click on Paste Assert.True ( mCurrent.NewMouseEvent ( - new() { Position = new (10, 2), Flags = MouseFlags.ReportMousePosition, View = mCurrent } + new () { Position = new (10, 2), Flags = MouseFlags.ReportMousePosition, View = mCurrent } ) ); Assert.True (menu.IsMenuOpen); @@ -2435,7 +2433,7 @@ public void MouseEvent_Test () // Edit menu is open. Click on the menu at Y = -1, which is outside the menu. Assert.False ( mCurrent.NewMouseEvent ( - new() { Position = new (10, i), Flags = MouseFlags.ReportMousePosition, View = menu } + new () { Position = new (10, i), Flags = MouseFlags.ReportMousePosition, View = menu } ) ); } @@ -2444,7 +2442,7 @@ public void MouseEvent_Test () // Edit menu is open. Click on the menu at Y = i. Assert.True ( mCurrent.NewMouseEvent ( - new() { Position = new (10, i), Flags = MouseFlags.ReportMousePosition, View = mCurrent } + new () { Position = new (10, i), Flags = MouseFlags.ReportMousePosition, View = mCurrent } ) ); } @@ -2609,7 +2607,7 @@ public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () Application.Begin (top); Assert.True (tf.HasFocus); - Assert.True (menu.NewMouseEvent (new() { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu })); + Assert.True (menu.NewMouseEvent (new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); top.Draw (); @@ -2617,7 +2615,7 @@ public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (8, 0), Flags = MouseFlags.ReportMousePosition, View = menu } + new () { Position = new (8, 0), Flags = MouseFlags.ReportMousePosition, View = menu } ) ); Assert.True (menu.IsMenuOpen); @@ -2627,7 +2625,7 @@ public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (15, 0), Flags = MouseFlags.ReportMousePosition, View = menu } + new () { Position = new (15, 0), Flags = MouseFlags.ReportMousePosition, View = menu } ) ); Assert.True (menu.IsMenuOpen); @@ -2637,7 +2635,7 @@ public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (8, 0), Flags = MouseFlags.ReportMousePosition, View = menu } + new () { Position = new (8, 0), Flags = MouseFlags.ReportMousePosition, View = menu } ) ); Assert.True (menu.IsMenuOpen); @@ -2647,7 +2645,7 @@ public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (1, 0), Flags = MouseFlags.ReportMousePosition, View = menu } + new () { Position = new (1, 0), Flags = MouseFlags.ReportMousePosition, View = menu } ) ); Assert.True (menu.IsMenuOpen); @@ -2655,7 +2653,7 @@ public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () top.Draw (); TestHelpers.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); - Assert.True (menu.NewMouseEvent (new() { Position = new (8, 0), Flags = MouseFlags.Button1Pressed, View = menu })); + Assert.True (menu.NewMouseEvent (new () { Position = new (8, 0), Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); top.Draw (); @@ -2991,7 +2989,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); top.Draw (); @@ -3010,7 +3008,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () Assert.False ( menu.NewMouseEvent ( - new() + new () { Position = new (1, 2), Flags = MouseFlags.ReportMousePosition, View = Application.Top.Subviews [1] } @@ -3033,7 +3031,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () Assert.False ( menu.NewMouseEvent ( - new() + new () { Position = new (1, 1), Flags = MouseFlags.ReportMousePosition, View = Application.Top.Subviews [1] } @@ -3055,7 +3053,7 @@ public void UseSubMenusSingleFrame_False_By_Mouse () Assert.False ( menu.NewMouseEvent ( - new() { Position = new (70, 2), Flags = MouseFlags.Button1Clicked, View = Application.Top } + new () { Position = new (70, 2), Flags = MouseFlags.Button1Clicked, View = Application.Top } ) ); top.Draw (); @@ -3488,7 +3486,7 @@ public void UseSubMenusSingleFrame_True_Without_Border () Assert.True ( menu.NewMouseEvent ( - new() { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } + new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu } ) ); top.Draw (); @@ -3505,7 +3503,7 @@ public void UseSubMenusSingleFrame_True_Without_Border () Assert.False ( menu.NewMouseEvent ( - new() { Position = new (1, 2), Flags = MouseFlags.Button1Clicked, View = Application.Top.Subviews [1] } + new () { Position = new (1, 2), Flags = MouseFlags.Button1Clicked, View = Application.Top.Subviews [1] } ) ); top.Draw (); @@ -3523,7 +3521,7 @@ public void UseSubMenusSingleFrame_True_Without_Border () Assert.False ( menu.NewMouseEvent ( - new() { Position = new (1, 1), Flags = MouseFlags.Button1Clicked, View = Application.Top.Subviews [2] } + new () { Position = new (1, 1), Flags = MouseFlags.Button1Clicked, View = Application.Top.Subviews [2] } ) ); top.Draw (); @@ -3540,7 +3538,7 @@ public void UseSubMenusSingleFrame_True_Without_Border () Assert.False ( menu.NewMouseEvent ( - new() { Position = new (70, 2), Flags = MouseFlags.Button1Clicked, View = Application.Top } + new () { Position = new (70, 2), Flags = MouseFlags.Button1Clicked, View = Application.Top } ) ); top.Draw (); @@ -3619,13 +3617,7 @@ public string MenuBarText public string ExpectedBottomRow (int i) { - return $"{ - CM.Glyphs.LLCorner - }{ - new (CM.Glyphs.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3) - }{ - CM.Glyphs.LRCorner - } \n"; + return $"{CM.Glyphs.LLCorner}{new (CM.Glyphs.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{CM.Glyphs.LRCorner} \n"; } // The 3 spaces at end are a result of Menu.cs line 1062 where `pos` is calculated (` + spacesAfterTitle`) @@ -3654,13 +3646,7 @@ public string ExpectedSubMenuOpen (int i) // 1 space before the Title and 2 spaces after the Title/Check/Help public string ExpectedTopRow (int i) { - return $"{ - CM.Glyphs.ULCorner - }{ - new (CM.Glyphs.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3) - }{ - CM.Glyphs.URCorner - } \n"; + return $"{CM.Glyphs.ULCorner}{new (CM.Glyphs.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{CM.Glyphs.URCorner} \n"; } // Padding for the X of the sub menu Frame diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 2d4a0a323b..624da69ac9 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -491,7 +491,7 @@ public void KeyBindings_Command () Assert.Equal (tf1W2, top.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorRight)); // move char to right in tf1W2 Assert.Equal (win2, top.Focused); - Assert.Equal (tf1W2, top.MostFocused); + Assert.Equal (tf1W2, top.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorDown)); // move down to next view (tvW2) Assert.Equal (win2, top.Focused); Assert.Equal (tvW2, top.MostFocused); @@ -504,7 +504,7 @@ public void KeyBindings_Command () Assert.Equal (win2, top.Focused); Assert.Equal (tvW2, top.MostFocused); tvW2.AllowsTab = false; - Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); + Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); Assert.Equal (win2, top.Focused); Assert.Equal (tf1W2, top.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorLeft)); @@ -563,9 +563,6 @@ public void KeyBindings_Command () [Fact] public void Added_Event_Should_Not_Be_Used_To_Initialize_Toplevel_Events () { - Key alternateForwardKey = default; - Key alternateBackwardKey = default; - Key quitKey = default; var wasAdded = false; var view = new View (); @@ -588,7 +585,7 @@ void View_Added (object sender, SuperViewChangedEventArgs e) Application.Shutdown (); } - + [Fact] [AutoInitShutdown] public void Mouse_Drag_On_Top_With_Superview_Null () From 04dbe68dbf72725dbcbdbcc0700fa142b7606503 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 14:20:08 -0600 Subject: [PATCH 81/96] Fixed nullable warnings 3 --- Terminal.Gui/Application/Application.Run.cs | 37 ++++++++++----------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index bbff9c1ba6..9a79af3d4d 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -537,7 +537,7 @@ public static void RunLoop (RunState state) for (state.Toplevel.Running = true; state.Toplevel?.Running == true;) { - MainLoop.Running = true; + MainLoop!.Running = true; if (EndAfterFirstIteration && !firstIteration) { @@ -547,7 +547,7 @@ public static void RunLoop (RunState state) RunIteration (ref state, ref firstIteration); } - MainLoop.Running = false; + MainLoop!.Running = false; // Run one last iteration to consume any outstanding input events from Driver // This is important for remaining OnKeyUp events. @@ -562,7 +562,7 @@ public static void RunLoop (RunState state) /// public static void RunIteration (ref RunState state, ref bool firstIteration) { - if (MainLoop.Running && MainLoop.EventsPending ()) + if (MainLoop!.Running && MainLoop.EventsPending ()) { // Notify Toplevel it's ready if (firstIteration) @@ -580,7 +580,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) OverlappedTop?.OnDeactivate (state.Toplevel); state.Toplevel = Current; OverlappedTop?.OnActivate (state.Toplevel); - Top.SetSubViewNeedsDisplay (); + Top!.SetSubViewNeedsDisplay (); Refresh (); } } @@ -592,9 +592,9 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) return; } - if (state.Toplevel != Top && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) + if (state.Toplevel != Top && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { - state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame); + state.Toplevel!.SetNeedsDisplay (state.Toplevel.Frame); Top.Draw (); foreach (Toplevel top in _topLevels.Reverse ()) @@ -610,8 +610,8 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) if (_topLevels.Count == 1 && state.Toplevel == Top - && (Driver.Cols != state.Toplevel.Frame.Width - || Driver.Rows != state.Toplevel.Frame.Height) + && (Driver!.Cols != state.Toplevel!.Frame.Width + || Driver!.Rows != state.Toplevel.Frame.Height) && (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) @@ -619,18 +619,18 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) Driver.ClearContents (); } - if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ()) + if (state.Toplevel!.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ()) { state.Toplevel.SetNeedsDisplay (); state.Toplevel.Draw (); - Driver.UpdateScreen (); + Driver!.UpdateScreen (); //Driver.UpdateCursor (); } if (PositionCursor (state.Toplevel)) { - Driver.UpdateCursor (); + Driver!.UpdateCursor (); } // else @@ -642,7 +642,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) //Driver.UpdateCursor (); } - if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) + if (state.Toplevel != Top && !state.Toplevel.Modal && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { Top.Draw (); } @@ -673,8 +673,7 @@ public static void RequestStop (Toplevel? top = null) } else if (OverlappedTop != null && top != Current - && Current?.Running == true - && Current?.Modal == true + && Current is { Running: true, Modal: true } && top!.Modal && top.Running) { @@ -702,14 +701,12 @@ public static void RequestStop (Toplevel? top = null) else if ((OverlappedTop != null && top != OverlappedTop && top != Current - && Current?.Modal == false - && Current?.Running == true + && Current is { Modal: false, Running: true } && !top!.Running) || (OverlappedTop != null && top != OverlappedTop && top != Current - && Current?.Modal == false - && Current?.Running == false + && Current is { Modal: false, Running: false } && !top!.Running && _topLevels.ToArray () [1].Running)) { @@ -815,7 +812,7 @@ public static void End (RunState runState) // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel // is a child of MidTop, and we should notify the OverlappedTop that it is closing - if (OverlappedTop is { } && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop) + if (OverlappedTop is { } && !runState.Toplevel!.Modal && runState.Toplevel != OverlappedTop) { OverlappedTop.OnChildClosed (runState.Toplevel); } @@ -841,7 +838,7 @@ public static void End (RunState runState) else { SetCurrentOverlappedAsTop (); - runState.Toplevel.OnLeave (Current); + runState.Toplevel!.OnLeave (Current); Current.OnEnter (runState.Toplevel); } From 3b351891067d6677fa501df33993842d3a098b7d Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 14:36:20 -0600 Subject: [PATCH 82/96] Fixed nullable warnings 4 --- .../Application/Application.Toplevel.cs | 15 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 201 +++++++++--------- 2 files changed, 116 insertions(+), 100 deletions(-) diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs index e272ea7aab..d45360d648 100644 --- a/Terminal.Gui/Application/Application.Toplevel.cs +++ b/Terminal.Gui/Application/Application.Toplevel.cs @@ -59,7 +59,7 @@ private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) /// /// /// - private static Toplevel FindDeepestTop (Toplevel start, in Point location) + private static Toplevel? FindDeepestTop (Toplevel start, in Point location) { if (!start.Frame.Contains (location)) { @@ -91,15 +91,20 @@ private static Toplevel FindDeepestTop (Toplevel start, in Point location) /// /// Given , returns the first Superview up the chain that is . /// - private static View FindTopFromView (View view) + private static View? FindTopFromView (View? view) { - View top = view?.SuperView is { } && view?.SuperView != Top + if (view is null) + { + return null; + } + + View top = view.SuperView is { } && view.SuperView != Top ? view.SuperView : view; while (top?.SuperView is { } && top?.SuperView != Top) { - top = top.SuperView; + top = top!.SuperView; } return top; @@ -221,7 +226,7 @@ public static bool OnSizeChanging (SizeChangedEventArgs args) if (PositionCursor (t)) { - Driver.UpdateCursor (); + Driver?.UpdateCursor (); } } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 8e6a08d598..5e9a7dad33 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -46,7 +46,7 @@ public Rectangle Clip } /// Get the operating system clipboard. - public IClipboard Clipboard { get; internal set; } + public IClipboard? Clipboard { get; internal set; } /// /// Gets the column last set by . and are used by @@ -70,7 +70,7 @@ internal set /// is called. /// The format of the array is rows, columns. The first index is the row, the second index is the column. /// - public Cell [,] Contents { get; internal set; } + public Cell [,]? Contents { get; internal set; } /// The leftmost column in the terminal. public virtual int Left { get; internal set; } = 0; @@ -125,125 +125,133 @@ public void AddRune (Rune rune) int runeWidth = -1; bool validLocation = IsValidLocation (Col, Row); + if (Contents is null) + { + return; + } + if (validLocation) { rune = rune.MakePrintable (); runeWidth = rune.GetColumns (); - if (runeWidth == 0 && rune.IsCombiningMark ()) + lock (Contents) { - // AtlasEngine does not support NON-NORMALIZED combining marks in a way - // compatible with the driver architecture. Any CMs (except in the first col) - // are correctly combined with the base char, but are ALSO treated as 1 column - // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. - // - // Until this is addressed (see Issue #), we do our best by - // a) Attempting to normalize any CM with the base char to it's left - // b) Ignoring any CMs that don't normalize - if (Col > 0) + if (runeWidth == 0 && rune.IsCombiningMark ()) { - if (Contents [Row, Col - 1].CombiningMarks.Count > 0) - { - // Just add this mark to the list - Contents [Row, Col - 1].CombiningMarks.Add (rune); - - // Ignore. Don't move to next column (let the driver figure out what to do). - } - else + // AtlasEngine does not support NON-NORMALIZED combining marks in a way + // compatible with the driver architecture. Any CMs (except in the first col) + // are correctly combined with the base char, but are ALSO treated as 1 column + // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. + // + // Until this is addressed (see Issue #), we do our best by + // a) Attempting to normalize any CM with the base char to it's left + // b) Ignoring any CMs that don't normalize + if (Col > 0) { - // Attempt to normalize the cell to our left combined with this mark - string combined = Contents [Row, Col - 1].Rune + rune.ToString (); - - // Normalize to Form C (Canonical Composition) - string normalized = combined.Normalize (NormalizationForm.FormC); - - if (normalized.Length == 1) + if (Contents [Row, Col - 1].CombiningMarks.Count > 0) { - // It normalized! We can just set the Cell to the left with the - // normalized codepoint - Contents [Row, Col - 1].Rune = (Rune)normalized [0]; + // Just add this mark to the list + Contents [Row, Col - 1].CombiningMarks.Add (rune); - // Ignore. Don't move to next column because we're already there + // Ignore. Don't move to next column (let the driver figure out what to do). } else { - // It didn't normalize. Add it to the Cell to left's CM list - Contents [Row, Col - 1].CombiningMarks.Add (rune); - - // Ignore. Don't move to next column (let the driver figure out what to do). + // Attempt to normalize the cell to our left combined with this mark + string combined = Contents [Row, Col - 1].Rune + rune.ToString (); + + // Normalize to Form C (Canonical Composition) + string normalized = combined.Normalize (NormalizationForm.FormC); + + if (normalized.Length == 1) + { + // It normalized! We can just set the Cell to the left with the + // normalized codepoint + Contents [Row, Col - 1].Rune = (Rune)normalized [0]; + + // Ignore. Don't move to next column because we're already there + } + else + { + // It didn't normalize. Add it to the Cell to left's CM list + Contents [Row, Col - 1].CombiningMarks.Add (rune); + + // Ignore. Don't move to next column (let the driver figure out what to do). + } } - } - Contents [Row, Col - 1].Attribute = CurrentAttribute; - Contents [Row, Col - 1].IsDirty = true; + Contents [Row, Col - 1].Attribute = CurrentAttribute; + Contents [Row, Col - 1].IsDirty = true; + } + else + { + // Most drivers will render a combining mark at col 0 as the mark + Contents [Row, Col].Rune = rune; + Contents [Row, Col].Attribute = CurrentAttribute; + Contents [Row, Col].IsDirty = true; + Col++; + } } else { - // Most drivers will render a combining mark at col 0 as the mark - Contents [Row, Col].Rune = rune; Contents [Row, Col].Attribute = CurrentAttribute; Contents [Row, Col].IsDirty = true; - Col++; - } - } - else - { - Contents [Row, Col].Attribute = CurrentAttribute; - Contents [Row, Col].IsDirty = true; - if (Col > 0) - { - // Check if cell to left has a wide glyph - if (Contents [Row, Col - 1].Rune.GetColumns () > 1) + if (Col > 0) { - // Invalidate cell to left - Contents [Row, Col - 1].Rune = Rune.ReplacementChar; - Contents [Row, Col - 1].IsDirty = true; + // Check if cell to left has a wide glyph + if (Contents [Row, Col - 1].Rune.GetColumns () > 1) + { + // Invalidate cell to left + Contents [Row, Col - 1].Rune = Rune.ReplacementChar; + Contents [Row, Col - 1].IsDirty = true; + } } - } - if (runeWidth < 1) - { - Contents [Row, Col].Rune = Rune.ReplacementChar; - } - else if (runeWidth == 1) - { - Contents [Row, Col].Rune = rune; - - if (Col < Clip.Right - 1) - { - Contents [Row, Col + 1].IsDirty = true; - } - } - else if (runeWidth == 2) - { - if (Col == Clip.Right - 1) + if (runeWidth < 1) { - // We're at the right edge of the clip, so we can't display a wide character. - // TODO: Figure out if it is better to show a replacement character or ' ' Contents [Row, Col].Rune = Rune.ReplacementChar; } - else + else if (runeWidth == 1) { Contents [Row, Col].Rune = rune; if (Col < Clip.Right - 1) { - // Invalidate cell to right so that it doesn't get drawn - // TODO: Figure out if it is better to show a replacement character or ' ' - Contents [Row, Col + 1].Rune = Rune.ReplacementChar; Contents [Row, Col + 1].IsDirty = true; } } - } - else - { - // This is a non-spacing character, so we don't need to do anything - Contents [Row, Col].Rune = (Rune)' '; - Contents [Row, Col].IsDirty = false; - } + else if (runeWidth == 2) + { + if (Col == Clip.Right - 1) + { + // We're at the right edge of the clip, so we can't display a wide character. + // TODO: Figure out if it is better to show a replacement character or ' ' + Contents [Row, Col].Rune = Rune.ReplacementChar; + } + else + { + Contents [Row, Col].Rune = rune; + + if (Col < Clip.Right - 1) + { + // Invalidate cell to right so that it doesn't get drawn + // TODO: Figure out if it is better to show a replacement character or ' ' + Contents [Row, Col + 1].Rune = Rune.ReplacementChar; + Contents [Row, Col + 1].IsDirty = true; + } + } + } + else + { + // This is a non-spacing character, so we don't need to do anything + Contents [Row, Col].Rune = (Rune)' '; + Contents [Row, Col].IsDirty = false; + } - _dirtyLines [Row] = true; + _dirtyLines! [Row] = true; + } } } @@ -258,14 +266,17 @@ public void AddRune (Rune rune) if (validLocation && Col < Clip.Right) { - // This is a double-width character, and we are not at the end of the line. - // Col now points to the second column of the character. Ensure it doesn't - // Get rendered. - Contents [Row, Col].IsDirty = false; - Contents [Row, Col].Attribute = CurrentAttribute; - - // TODO: Determine if we should wipe this out (for now now) - //Contents [Row, Col].Rune = (Rune)' '; + lock (Contents!) + { + // This is a double-width character, and we are not at the end of the line. + // Col now points to the second column of the character. Ensure it doesn't + // Get rendered. + Contents [Row, Col].IsDirty = false; + Contents [Row, Col].Attribute = CurrentAttribute; + + // TODO: Determine if we should wipe this out (for now now) + //Contents [Row, Col].Rune = (Rune)' '; + } } Col++; @@ -332,7 +343,7 @@ public void ClearContents () /// public void SetContentsAsDirty () { - lock (Contents) + lock (Contents!) { for (var row = 0; row < Rows; row++) { @@ -358,7 +369,7 @@ public void SetContentsAsDirty () public void FillRect (Rectangle rect, Rune rune = default) { rect = Rectangle.Intersect (rect, Clip); - lock (Contents) + lock (Contents!) { for (int r = rect.Y; r < rect.Y + rect.Height; r++) { From 689c0cd93f6cc2892fe3cacaf56ed7a7be09c416 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 14:37:23 -0600 Subject: [PATCH 83/96] Fixed nullable warnings 5 --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 8ff127a4ba..180745d9df 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1266,18 +1266,18 @@ public override bool EnsureCursorVisibility () return WinConsole?.WriteANSI (sb.ToString ()) ?? false; } - if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) - { - GetCursorVisibility (out CursorVisibility cursorVisibility); - _cachedCursorVisibility = cursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); + //if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) + //{ + // GetCursorVisibility (out CursorVisibility cursorVisibility); + // _cachedCursorVisibility = cursorVisibility; + // SetCursorVisibility (CursorVisibility.Invisible); - return false; - } + // return false; + //} - SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default); + //SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default); - return _cachedCursorVisibility == CursorVisibility.Default; + //return _cachedCursorVisibility == CursorVisibility.Default; } #endregion Cursor Handling From ff47aa29b9fe29e28c7bd52ed1aa718f0f87aa6a Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 14:39:34 -0600 Subject: [PATCH 84/96] Fixed nullable warnings 6 --- UnitTests/Views/ToplevelTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 624da69ac9..8688c5d4ae 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -1049,12 +1049,12 @@ public void IsLoaded_With_Sub_Toplevel_Application_Begin_NeedDisplay () Assert.False (subTop.IsLoaded); Assert.Equal (new (0, 0, 20, 10), view.Frame); - view.LayoutStarted += view_LayoutStarted; + view.LayoutStarted += ViewLayoutStarted; - void view_LayoutStarted (object sender, LayoutEventArgs e) + void ViewLayoutStarted (object sender, LayoutEventArgs e) { Assert.Equal (new (0, 0, 20, 10), view._needsDisplayRect); - view.LayoutStarted -= view_LayoutStarted; + view.LayoutStarted -= ViewLayoutStarted; } Application.Begin (top); From 022050db730f7fc53d2dfb8e171e6da2ca82d137 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 15:09:48 -0600 Subject: [PATCH 85/96] Fixed nullable warnings 7 --- CommunityToolkitExample/Program.cs | 2 +- .../Application/Application.Initialization.cs | 17 +- .../Application/Application.Keyboard.cs | 4 +- Terminal.Gui/Application/Application.Mouse.cs | 22 +- .../Application/Application.Navigation.cs | 197 ++++++++++++++++-- Terminal.Gui/Application/Application.Run.cs | 6 +- .../Application/Application.Toplevel.cs | 142 ------------- Terminal.Gui/Application/Application.cs | 6 +- .../Application/MainLoopSyncContext.cs | 2 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 10 +- Terminal.Gui/View/Adornment/ShadowView.cs | 4 +- Terminal.Gui/View/EventArgs.cs | 2 +- Terminal.Gui/View/Layout/Dim.cs | 2 +- Terminal.Gui/View/Layout/DimView.cs | 4 +- Terminal.Gui/View/Layout/PosView.cs | 8 +- Terminal.Gui/View/View.cs | 2 +- Terminal.Gui/Views/TextValidateField.cs | 8 +- Terminal.Gui/Views/Tile.cs | 2 +- UICatalog/Scenarios/Buttons.cs | 4 +- UICatalog/Scenarios/ListViewWithSelection.cs | 2 +- UICatalog/Scenarios/MenuBarScenario.cs | 8 +- UICatalog/UICatalog.cs | 2 +- UnitTests/Application/ApplicationTests.cs | 20 +- UnitTests/Application/KeyboardTests.cs | 2 +- UnitTests/UICatalog/ScenarioTests.cs | 2 +- UnitTests/View/DrawTests.cs | 8 +- UnitTests/Views/MenuBarTests.cs | 21 +- 27 files changed, 264 insertions(+), 245 deletions(-) diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs index 0d4f21c302..a9ababfec6 100644 --- a/CommunityToolkitExample/Program.cs +++ b/CommunityToolkitExample/Program.cs @@ -12,7 +12,7 @@ private static void Main (string [] args) Services = ConfigureServices (); Application.Init (); Application.Run (Services.GetRequiredService ()); - Application.Top.Dispose(); + Application.Top?.Dispose(); Application.Shutdown (); } diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 0d9b2caf51..37b34a3580 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -38,8 +38,8 @@ public static partial class Application // Initialization (Init/Shutdown) [RequiresDynamicCode ("AOT")] public static void Init (ConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); } - internal static bool _initialized; - internal static int _mainThreadId = -1; + internal static bool IsInitialized { get; set; } + internal static int MainThreadId { get; set; } = -1; // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop. // @@ -58,12 +58,12 @@ internal static void InternalInit ( bool calledViaRunT = false ) { - if (_initialized && driver is null) + if (IsInitialized && driver is null) { return; } - if (_initialized) + if (IsInitialized) { throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); } @@ -154,9 +154,9 @@ internal static void InternalInit ( SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); SupportedCultures = GetSupportedCultures (); - _mainThreadId = Thread.CurrentThread.ManagedThreadId; - _initialized = true; - InitializedChanged?.Invoke (null, new (in _initialized)); + MainThreadId = Thread.CurrentThread.ManagedThreadId; + bool init = IsInitialized = true; + InitializedChanged?.Invoke (null, new (init)); } private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); } @@ -198,7 +198,8 @@ public static void Shutdown () // TODO: Throw an exception if Init hasn't been called. ResetState (); PrintJsonErrors (); - InitializedChanged?.Invoke (null, new (in _initialized)); + bool init = IsInitialized; + InitializedChanged?.Invoke (null, new (in init)); } /// diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 10419bf808..59c78a83ee 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -110,7 +110,7 @@ public static Key QuitKey /// if the key was handled. public static bool OnKeyDown (Key keyEvent) { - if (!_initialized) + if (!IsInitialized) { return true; } @@ -210,7 +210,7 @@ public static bool OnKeyDown (Key keyEvent) /// if the key was handled. public static bool OnKeyUp (Key a) { - if (!_initialized) + if (!IsInitialized) { return true; } diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs index 713e6375d9..be7c38df64 100644 --- a/Terminal.Gui/Application/Application.Mouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -64,7 +64,7 @@ public static void UngrabMouse () } } - private static bool OnGrabbingMouse (View view) + private static bool OnGrabbingMouse (View? view) { if (view is null) { @@ -77,7 +77,7 @@ private static bool OnGrabbingMouse (View view) return evArgs.Cancel; } - private static bool OnUnGrabbingMouse (View view) + private static bool OnUnGrabbingMouse (View? view) { if (view is null) { @@ -90,7 +90,7 @@ private static bool OnUnGrabbingMouse (View view) return evArgs.Cancel; } - private static void OnGrabbedMouse (View view) + private static void OnGrabbedMouse (View? view) { if (view is null) { @@ -100,7 +100,7 @@ private static void OnGrabbedMouse (View view) GrabbedMouse?.Invoke (view, new (view)); } - private static void OnUnGrabbedMouse (View view) + private static void OnUnGrabbedMouse (View? view) { if (view is null) { @@ -113,7 +113,7 @@ private static void OnUnGrabbedMouse (View view) #nullable enable // Used by OnMouseEvent to track the last view that was clicked on. - internal static View? _mouseEnteredView; + internal static View? MouseEnteredView { get; set; } /// Event fired when a mouse move or click occurs. Coordinates are screen relative. /// @@ -166,7 +166,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false) { // The mouse has moved outside the bounds of the view that grabbed the mouse - _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent); + MouseEnteredView?.NewMouseLeaveEvent (mouseEvent); } //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); @@ -242,16 +242,16 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) return; } - if (_mouseEnteredView is null) + if (MouseEnteredView is null) { - _mouseEnteredView = view; + MouseEnteredView = view; view.NewMouseEnterEvent (me); } - else if (_mouseEnteredView != view) + else if (MouseEnteredView != view) { - _mouseEnteredView.NewMouseLeaveEvent (me); + MouseEnteredView.NewMouseLeaveEvent (me); view.NewMouseEnterEvent (me); - _mouseEnteredView = view; + MouseEnteredView = view; } if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition) diff --git a/Terminal.Gui/Application/Application.Navigation.cs b/Terminal.Gui/Application/Application.Navigation.cs index 0bbce67e6b..d3f42fd64d 100644 --- a/Terminal.Gui/Application/Application.Navigation.cs +++ b/Terminal.Gui/Application/Application.Navigation.cs @@ -1,3 +1,4 @@ +#nullable enable namespace Terminal.Gui; public static partial class Application @@ -6,30 +7,32 @@ public static partial class Application /// Gets the list of the Overlapped children which are not modal from the /// . /// - public static List OverlappedChildren + public static List? OverlappedChildren { get { if (OverlappedTop is { }) { - List _overlappedChildren = new (); + List overlappedChildren = new (); - foreach (Toplevel top in _topLevels) + lock (_topLevels) { - if (top != OverlappedTop && !top.Modal) + foreach (Toplevel top in _topLevels) { - _overlappedChildren.Add (top); + if (top != OverlappedTop && !top.Modal) + { + overlappedChildren.Add (top); + } } } - return _overlappedChildren; + return overlappedChildren; } return null; } } -#nullable enable /// /// The object used for the application on startup which /// is true. @@ -46,7 +49,6 @@ public static Toplevel? OverlappedTop return null; } } -#nullable restore /// Brings the superview of the most focused overlapped view is on front. public static void BringOverlappedTopToFront () @@ -56,9 +58,9 @@ public static void BringOverlappedTopToFront () return; } - View top = FindTopFromView (Top?.MostFocused); + View? top = FindTopFromView (Top?.MostFocused); - if (top is Toplevel && Top.Subviews.Count > 1 && Top.Subviews [^1] != top) + if (top is Toplevel && Top?.Subviews.Count > 1 && Top.Subviews [^1] != top) { Top.BringSubviewToFront (top); } @@ -68,9 +70,9 @@ public static void BringOverlappedTopToFront () /// The type. /// The strings to exclude. /// The matched view. - public static Toplevel GetTopOverlappedChild (Type type = null, string [] exclude = null) + public static Toplevel? GetTopOverlappedChild (Type? type = null, string []? exclude = null) { - if (OverlappedTop is null) + if (OverlappedChildren is null || OverlappedTop is null) { return null; } @@ -118,7 +120,7 @@ public static bool MoveToOverlappedChild (Toplevel top) /// Move to the next Overlapped child from the . public static void OverlappedMoveNext () { - if (OverlappedTop is { } && !Current.Modal) + if (OverlappedTop is { } && !Current!.Modal) { lock (_topLevels) { @@ -133,7 +135,7 @@ public static void OverlappedMoveNext () } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { - MoveCurrent (Top); + MoveCurrent (Top!); break; } @@ -149,7 +151,7 @@ public static void OverlappedMoveNext () /// Move to the previous Overlapped child from the . public static void OverlappedMovePrevious () { - if (OverlappedTop is { } && !Current.Modal) + if (OverlappedTop is { } && !Current!.Modal) { lock (_topLevels) { @@ -164,7 +166,7 @@ public static void OverlappedMovePrevious () } else if (isOverlapped && _topLevels.Peek () == OverlappedTop) { - MoveCurrent (Top); + MoveCurrent (Top!); break; } @@ -184,13 +186,16 @@ private static bool OverlappedChildNeedsDisplay () return false; } - foreach (Toplevel top in _topLevels) + lock (_topLevels) { - if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) + foreach (Toplevel top in _topLevels) { - OverlappedTop.SetSubViewNeedsDisplay (); + if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) + { + OverlappedTop.SetSubViewNeedsDisplay (); - return true; + return true; + } } } @@ -208,4 +213,154 @@ private static bool SetCurrentOverlappedAsTop () return false; } -} \ No newline at end of file + + /// + /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the . + /// + /// + /// + /// + private static Toplevel? FindDeepestTop (Toplevel start, in Point location) + { + if (!start.Frame.Contains (location)) + { + return null; + } + + lock (_topLevels) + { + if (_topLevels is not { Count: > 0 }) + { + return start; + } + + int rx = location.X - start.Frame.X; + int ry = location.Y - start.Frame.Y; + + foreach (Toplevel t in _topLevels) + { + if (t == Current) + { + continue; + } + + if (t != start && t.Visible && t.Frame.Contains (rx, ry)) + { + start = t; + + break; + } + } + } + + return start; + } + + /// + /// Given , returns the first Superview up the chain that is . + /// + private static View? FindTopFromView (View? view) + { + if (view is null) + { + return null; + } + + View top = view.SuperView is { } && view.SuperView != Top + ? view.SuperView + : view; + + while (top?.SuperView is { } && top?.SuperView != Top) + { + top = top!.SuperView; + } + + return top; + } + + /// + /// If the is not the then is moved to the top of + /// the Toplevel stack and made Current. + /// + /// + /// + private static bool MoveCurrent (Toplevel top) + { + // The Current is modal and the top is not modal Toplevel then + // the Current must be moved above the first not modal Toplevel. + if (OverlappedTop is { } + && top != OverlappedTop + && top != Current + && Current?.Modal == true + && !_topLevels.Peek ().Modal) + { + lock (_topLevels) + { + _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); + } + + var index = 0; + Toplevel [] savedToplevels = _topLevels.ToArray (); + + foreach (Toplevel t in savedToplevels) + { + if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) + { + lock (_topLevels) + { + _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); + } + } + + index++; + } + + return false; + } + + // The Current and the top are both not running Toplevel then + // the top must be moved above the first not running Toplevel. + if (OverlappedTop is { } + && top != OverlappedTop + && top != Current + && Current?.Running == false + && top?.Running == false) + { + lock (_topLevels) + { + _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); + } + + var index = 0; + + foreach (Toplevel t in _topLevels.ToArray ()) + { + if (!t.Running && t != Current && index > 0) + { + lock (_topLevels) + { + _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); + } + } + + index++; + } + + return false; + } + + if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top) + || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) + || (OverlappedTop is { } && Current?.Modal == false && top != Current) + || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop)) + { + lock (_topLevels) + { + _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Current = top; + } + } + + return true; + } +} diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 9a79af3d4d..87bfde4c0c 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -333,7 +333,7 @@ internal static bool PositionCursor (View view) public static T Run (Func? errorHandler = null, ConsoleDriver? driver = null) where T : Toplevel, new () { - if (!_initialized) + if (!IsInitialized) { // Init() has NOT been called. InternalInit (driver, null, true); @@ -388,7 +388,7 @@ public static void Run (Toplevel view, Func? errorHandler = nul { ArgumentNullException.ThrowIfNull (view); - if (_initialized) + if (IsInitialized) { if (Driver is null) { @@ -824,7 +824,7 @@ public static void End (RunState runState) } else { - if (_topLevels.Count > 1 && _topLevels.Peek () == OverlappedTop && OverlappedChildren.Any (t => t.Visible) is { }) + if (_topLevels.Count > 1 && _topLevels.Peek () == OverlappedTop && OverlappedChildren?.Any (t => t.Visible) != null) { OverlappedMoveNext (); } diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs index d45360d648..a050e28f10 100644 --- a/Terminal.Gui/Application/Application.Toplevel.cs +++ b/Terminal.Gui/Application/Application.Toplevel.cs @@ -53,148 +53,6 @@ private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) } } - /// - /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the . - /// - /// - /// - /// - private static Toplevel? FindDeepestTop (Toplevel start, in Point location) - { - if (!start.Frame.Contains (location)) - { - return null; - } - - if (_topLevels is { Count: > 0 }) - { - int rx = location.X - start.Frame.X; - int ry = location.Y - start.Frame.Y; - - foreach (Toplevel t in _topLevels) - { - if (t != Current) - { - if (t != start && t.Visible && t.Frame.Contains (rx, ry)) - { - start = t; - - break; - } - } - } - } - - return start; - } - - /// - /// Given , returns the first Superview up the chain that is . - /// - private static View? FindTopFromView (View? view) - { - if (view is null) - { - return null; - } - - View top = view.SuperView is { } && view.SuperView != Top - ? view.SuperView - : view; - - while (top?.SuperView is { } && top?.SuperView != Top) - { - top = top!.SuperView; - } - - return top; - } - - /// - /// If the is not the then is moved to the top of the Toplevel stack and made Current. - /// - /// - /// - private static bool MoveCurrent (Toplevel top) - { - // The Current is modal and the top is not modal Toplevel then - // the Current must be moved above the first not modal Toplevel. - if (OverlappedTop is { } - && top != OverlappedTop - && top != Current - && Current?.Modal == true - && !_topLevels.Peek ().Modal) - { - lock (_topLevels) - { - _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); - } - - var index = 0; - Toplevel [] savedToplevels = _topLevels.ToArray (); - - foreach (Toplevel t in savedToplevels) - { - if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); - } - } - - index++; - } - - return false; - } - - // The Current and the top are both not running Toplevel then - // the top must be moved above the first not running Toplevel. - if (OverlappedTop is { } - && top != OverlappedTop - && top != Current - && Current?.Running == false - && top?.Running == false) - { - lock (_topLevels) - { - _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); - } - - var index = 0; - - foreach (Toplevel t in _topLevels.ToArray ()) - { - if (!t.Running && t != Current && index > 0) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); - } - } - - index++; - } - - return false; - } - - if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top) - || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) - || (OverlappedTop is { } && Current?.Modal == false && top != Current) - || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop)) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } - } - - return true; - } - /// Invoked when the terminal's size changed. The new size of the terminal is provided. /// /// Event handlers can set to to prevent diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 76c05922bf..75fbd7191a 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -84,7 +84,7 @@ internal static void ResetState (bool ignoreDisposed = false) // MainLoop stuff MainLoop?.Dispose (); MainLoop = null; - _mainThreadId = -1; + MainThreadId = -1; Iteration = null; EndAfterFirstIteration = false; @@ -108,10 +108,10 @@ internal static void ResetState (bool ignoreDisposed = false) NotifyNewRunState = null; NotifyStopRunState = null; MouseGrabView = null; - _initialized = false; + IsInitialized = false; // Mouse - _mouseEnteredView = null; + MouseEnteredView = null; WantContinuousButtonPressedView = null; MouseEvent = null; GrabbedMouse = null; diff --git a/Terminal.Gui/Application/MainLoopSyncContext.cs b/Terminal.Gui/Application/MainLoopSyncContext.cs index 5290a20767..749c76268c 100644 --- a/Terminal.Gui/Application/MainLoopSyncContext.cs +++ b/Terminal.Gui/Application/MainLoopSyncContext.cs @@ -23,7 +23,7 @@ public override void Post (SendOrPostCallback d, object state) //_mainLoop.Driver.Wakeup (); public override void Send (SendOrPostCallback d, object state) { - if (Thread.CurrentThread.ManagedThreadId == Application._mainThreadId) + if (Thread.CurrentThread.ManagedThreadId == Application.MainThreadId) { d (state); } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 5e9a7dad33..e99521d1e2 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -351,7 +351,7 @@ public void SetContentsAsDirty () { Contents [row, c].IsDirty = true; } - _dirtyLines [row] = true; + _dirtyLines! [row] = true; } } } @@ -380,7 +380,7 @@ public void FillRect (Rectangle rect, Rune rune = default) Rune = (rune != default ? rune : (Rune)' '), Attribute = CurrentAttribute, IsDirty = true }; - _dirtyLines [r] = true; + _dirtyLines! [r] = true; } } } @@ -561,7 +561,7 @@ public virtual Attribute MakeColor (in Color foreground, in Color background) #region Mouse and Keyboard /// Event fired when a key is pressed down. This is a precursor to . - public event EventHandler KeyDown; + public event EventHandler? KeyDown; /// /// Called when a key is pressed down. Fires the event. This is a precursor to @@ -575,7 +575,7 @@ public virtual Attribute MakeColor (in Color foreground, in Color background) /// Drivers that do not support key release events will fire this event after processing is /// complete. /// - public event EventHandler KeyUp; + public event EventHandler? KeyUp; /// Called when a key is released. Fires the event. /// @@ -586,7 +586,7 @@ public virtual Attribute MakeColor (in Color foreground, in Color background) public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); } /// Event fired when a mouse event occurs. - public event EventHandler MouseEvent; + public event EventHandler? MouseEvent; /// Called when a mouse event occurs. Fires the event. /// diff --git a/Terminal.Gui/View/Adornment/ShadowView.cs b/Terminal.Gui/View/Adornment/ShadowView.cs index ad06dc754c..e6009759a3 100644 --- a/Terminal.Gui/View/Adornment/ShadowView.cs +++ b/Terminal.Gui/View/Adornment/ShadowView.cs @@ -109,7 +109,7 @@ private void DrawHorizontalShadowTransparent (Rectangle viewport) for (int i = screen.X; i < screen.X + screen.Width - 1; i++) { Driver.Move (i, screen.Y); - Driver.AddRune (Driver.Contents [screen.Y, i].Rune); + Driver.AddRune (Driver.Contents! [screen.Y, i].Rune); } } @@ -133,7 +133,7 @@ private void DrawVerticalShadowTransparent (Rectangle viewport) for (int i = screen.Y; i < screen.Y + viewport.Height; i++) { Driver.Move (screen.X, i); - Driver.AddRune (Driver.Contents [i, screen.X].Rune); + Driver.AddRune (Driver.Contents! [i, screen.X].Rune); } } } diff --git a/Terminal.Gui/View/EventArgs.cs b/Terminal.Gui/View/EventArgs.cs index 03309a0f55..1de2347c69 100644 --- a/Terminal.Gui/View/EventArgs.cs +++ b/Terminal.Gui/View/EventArgs.cs @@ -11,7 +11,7 @@ public class EventArgs : EventArgs where T : notnull /// Initializes a new instance of the class. /// The current value of the property. /// The type of the value. - public EventArgs (ref readonly T currentValue) { CurrentValue = currentValue; } + public EventArgs (in T currentValue) { CurrentValue = currentValue; } /// The current value of the property. public T CurrentValue { get; } diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index 3102d5d3c4..f6f50f7985 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -257,7 +257,7 @@ internal virtual int Calculate (int location, int superviewContentSize, View us, } var newDim = new DimCombine (AddOrSubtract.Subtract, left, right); - (left as DimView)?.Target.SetNeedsLayout (); + (left as DimView)?.Target?.SetNeedsLayout (); return newDim; } diff --git a/Terminal.Gui/View/Layout/DimView.cs b/Terminal.Gui/View/Layout/DimView.cs index e95efd4fb2..7a7568a95c 100644 --- a/Terminal.Gui/View/Layout/DimView.cs +++ b/Terminal.Gui/View/Layout/DimView.cs @@ -52,8 +52,8 @@ internal override int GetAnchor (int size) { return Dimension switch { - Dimension.Height => Target.Frame.Height, - Dimension.Width => Target.Frame.Width, + Dimension.Height => Target!.Frame.Height, + Dimension.Width => Target!.Frame.Width, _ => 0 }; } diff --git a/Terminal.Gui/View/Layout/PosView.cs b/Terminal.Gui/View/Layout/PosView.cs index 8ceba980fc..a46f6898a7 100644 --- a/Terminal.Gui/View/Layout/PosView.cs +++ b/Terminal.Gui/View/Layout/PosView.cs @@ -47,10 +47,10 @@ internal override int GetAnchor (int size) { return Side switch { - Side.Left => Target.Frame.X, - Side.Top => Target.Frame.Y, - Side.Right => Target.Frame.Right, - Side.Bottom => Target.Frame.Bottom, + Side.Left => Target!.Frame.X, + Side.Top => Target!.Frame.Y, + Side.Right => Target!.Frame.Right, + Side.Bottom => Target!.Frame.Bottom, _ => 0 }; } diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 68eb1853f3..d340a5aa4b 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -490,7 +490,7 @@ private void SetTitleTextFormatterSize () /// Called when the has been changed. Invokes the event. protected void OnTitleChanged () { - TitleChanged?.Invoke (this, new (ref _title)); + TitleChanged?.Invoke (this, new (in _title)); } /// diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs index 045f6df3ff..b0df130b30 100644 --- a/Terminal.Gui/Views/TextValidateField.cs +++ b/Terminal.Gui/Views/TextValidateField.cs @@ -206,7 +206,7 @@ public bool Delete (int pos) if (result) { - OnTextChanged (new EventArgs (ref oldValue)); + OnTextChanged (new EventArgs (in oldValue)); } return result; @@ -220,7 +220,7 @@ public bool InsertAt (char ch, int pos) if (result) { - OnTextChanged (new EventArgs (ref oldValue)); + OnTextChanged (new EventArgs (in oldValue)); } return result; @@ -333,7 +333,7 @@ public bool Delete (int pos) { string oldValue = Text; _text.RemoveAt (pos); - OnTextChanged (new EventArgs (ref oldValue)); + OnTextChanged (new EventArgs (in oldValue)); } return true; @@ -349,7 +349,7 @@ public bool InsertAt (char ch, int pos) { string oldValue = Text; _text.Insert (pos, (Rune)ch); - OnTextChanged (new EventArgs (ref oldValue)); + OnTextChanged (new EventArgs (in oldValue)); return true; } diff --git a/Terminal.Gui/Views/Tile.cs b/Terminal.Gui/Views/Tile.cs index ebc4b9f599..5224db8b4b 100644 --- a/Terminal.Gui/Views/Tile.cs +++ b/Terminal.Gui/Views/Tile.cs @@ -62,7 +62,7 @@ public string Title /// The new to be replaced. public virtual void OnTitleChanged (string oldTitle, string newTitle) { - var args = new EventArgs (ref newTitle); + var args = new EventArgs (in newTitle); TitleChanged?.Invoke (this, args); } diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index aab815a967..2ea67238e8 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -527,8 +527,8 @@ public T Value } _value = value; - _number.Text = _value.ToString (); - ValueChanged?.Invoke (this, new (ref _value)); + _number.Text = _value.ToString ()!; + ValueChanged?.Invoke (this, new (in _value)); } } diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index afcd8a5423..27e1bf5d2c 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -205,7 +205,7 @@ public bool IsMarked (int item) /// public event NotifyCollectionChangedEventHandler CollectionChanged; - public int Count => Scenarios != null ? Scenarios.Count : 0; + public int Count => Scenarios?.Count ?? 0; public int Length { get; private set; } public bool SuspendCollectionChangedEvent { get => throw new System.NotImplementedException (); set => throw new System.NotImplementedException (); } diff --git a/UICatalog/Scenarios/MenuBarScenario.cs b/UICatalog/Scenarios/MenuBarScenario.cs index 73c767f798..b9c6bee678 100644 --- a/UICatalog/Scenarios/MenuBarScenario.cs +++ b/UICatalog/Scenarios/MenuBarScenario.cs @@ -1,5 +1,6 @@ using System; using Terminal.Gui; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace UICatalog.Scenarios; @@ -64,14 +65,17 @@ public override void Main () menuBar.Key = KeyCode.F9; menuBar.Title = "TestMenuBar"; - bool fnAction (string s) + bool FnAction (string s) { _lastAction.Text = s; return true; } + + // Declare a variable for the function + Func fnActionVariable = FnAction; - menuBar.EnableForDesign ((Func)fnAction); + menuBar.EnableForDesign (ref fnActionVariable); menuBar.MenuOpening += (s, e) => { diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index a2cc9a4749..a9014d0171 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -104,7 +104,7 @@ private static int Main (string [] args) // If no driver is provided, the default driver is used. Option driverOption = new Option ("--driver", "The ConsoleDriver to use.").FromAmong ( Application.GetDriverTypes () - .Select (d => d.Name) + .Select (d => d!.Name) .ToArray () ); diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index f8f1f80293..c600254a2a 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -162,7 +162,7 @@ public void Init_ResetState_Resets_Properties (Type driverType) // Set some values Application.Init (driverName: driverType.Name); - Application._initialized = true; + Application.IsInitialized = true; // Reset Application.ResetState (); @@ -191,12 +191,12 @@ void CheckReset () Assert.Null (Application.OverlappedTop); // Internal properties - Assert.False (Application._initialized); + Assert.False (Application.IsInitialized); Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures); Assert.False (Application._forceFakeConsole); - Assert.Equal (-1, Application._mainThreadId); + Assert.Equal (-1, Application.MainThreadId); Assert.Empty (Application._topLevels); - Assert.Null (Application._mouseEnteredView); + Assert.Null (Application.MouseEnteredView); // Keyboard Assert.Empty (Application.GetViewKeyBindings ()); @@ -218,12 +218,12 @@ void CheckReset () CheckReset (); // Set the values that can be set - Application._initialized = true; + Application.IsInitialized = true; Application._forceFakeConsole = true; - Application._mainThreadId = 1; + Application.MainThreadId = 1; //Application._topLevels = new List (); - Application._mouseEnteredView = new (); + Application.MouseEnteredView = new (); //Application.SupportedCultures = new List (); Application.Force16Colors = true; @@ -237,7 +237,7 @@ void CheckReset () //Application.OverlappedChildren = new List (); //Application.OverlappedTop = - Application._mouseEnteredView = new (); + Application.MouseEnteredView = new (); //Application.WantContinuousButtonPressedView = new View (); @@ -413,7 +413,7 @@ public void InitWithoutTopLevelFactory_Begin_End_Cleans_Up () [AutoInitShutdown] public void Internal_Properties_Correct () { - Assert.True (Application._initialized); + Assert.True (Application.IsInitialized); Assert.Null (Application.Top); RunState rs = Application.Begin (new ()); Assert.Equal (Application.Top, rs.Toplevel); @@ -1206,7 +1206,7 @@ void OnApplicationOnIteration (object s, IterationEventArgs a) Thread.Sleep ((int)timeoutTime / 10); // Worst case scenario - something went wrong - if (Application._initialized && iteration > 25) + if (Application.IsInitialized && iteration > 25) { _output.WriteLine ($"Too many iterations ({iteration}): Calling Application.RequestStop."); Application.RequestStop (); diff --git a/UnitTests/Application/KeyboardTests.cs b/UnitTests/Application/KeyboardTests.cs index 4dfb5b11a5..a61519037d 100644 --- a/UnitTests/Application/KeyboardTests.cs +++ b/UnitTests/Application/KeyboardTests.cs @@ -169,7 +169,7 @@ void OnApplicationOnIteration (object s, IterationEventArgs a) _output.WriteLine ("Iteration: {0}", iteration); iteration++; Assert.True (iteration < 2, "Too many iterations, something is wrong."); - if (Application._initialized) + if (Application.IsInitialized) { _output.WriteLine (" Pressing QuitKey"); Application.OnKeyDown (Application.QuitKey); diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index 22a9e25c6d..5e0dc2a5ce 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -115,7 +115,7 @@ bool ForceCloseCallback () void OnApplicationOnIteration (object s, IterationEventArgs a) { - if (Application._initialized) + if (Application.IsInitialized) { // Press QuitKey //_output.WriteLine ($"Forcing Quit with {Application.QuitKey}"); diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 52b4659f83..19ed80383d 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -48,16 +48,16 @@ public void AddRune_Is_Constrained_To_Viewport () view.Draw (); // Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen) - Assert.Equal ((Rune)' ', Application.Driver?.Contents [2, 2].Rune); + Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune); view.AddRune (0, 0, Rune.ReplacementChar); - Assert.Equal (Rune.ReplacementChar, Application.Driver?.Contents [2, 2].Rune); + Assert.Equal (Rune.ReplacementChar, Application.Driver?.Contents! [2, 2].Rune); view.AddRune (-1, -1, Rune.ReplacementChar); - Assert.Equal ((Rune)'M', Application.Driver?.Contents [1, 1].Rune); + Assert.Equal ((Rune)'M', Application.Driver?.Contents! [1, 1].Rune); view.AddRune (1, 1, Rune.ReplacementChar); - Assert.Equal ((Rune)'M', Application.Driver?.Contents [3, 3].Rune); + Assert.Equal ((Rune)'M', Application.Driver?.Contents! [3, 3].Rune); View.Diagnostics = ViewDiagnosticFlags.Off; } diff --git a/UnitTests/Views/MenuBarTests.cs b/UnitTests/Views/MenuBarTests.cs index 6f9de46836..76598902c6 100644 --- a/UnitTests/Views/MenuBarTests.cs +++ b/UnitTests/Views/MenuBarTests.cs @@ -1301,15 +1301,16 @@ public void KeyBindings_Shortcut_Commands (string expectedAction, params KeyCode var menu = new MenuBar (); - menu.EnableForDesign ( - new Func ( - s => - { - miAction = s as string; - - return true; - }) - ); + bool FnAction (string s) + { + miAction = s; + + return true; + } + // Declare a variable for the function + Func fnActionVariable = FnAction; + + menu.EnableForDesign (ref fnActionVariable); menu.Key = KeyCode.F9; menu.MenuOpening += (s, e) => mbiCurrent = e.CurrentMenu; @@ -1329,7 +1330,7 @@ public void KeyBindings_Shortcut_Commands (string expectedAction, params KeyCode foreach (KeyCode key in keys) { Assert.True (top.NewKeyDownEvent (new (key))); - Application.MainLoop.RunIteration (); + Application.MainLoop!.RunIteration (); } Assert.Equal (expectedAction, miAction); From f37ec5e04f314385859d8c00285283d24a4b1c96 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 15:42:04 -0600 Subject: [PATCH 86/96] Moved Overlapped stuff to ApplicationOverlap static class. Fixed nullable warnings. --- .../Application/Application .Screen.cs | 45 ++ .../Application/Application.Initialization.cs | 8 +- .../Application/Application.Keyboard.cs | 14 +- Terminal.Gui/Application/Application.Mouse.cs | 12 +- .../Application/Application.Navigation.cs | 376 ++++------------- .../Application/Application.Overlapped.cs | 391 +++++++++++++----- Terminal.Gui/Application/Application.Run.cs | 116 +++--- .../Application/Application.Toplevel.cs | 57 +-- Terminal.Gui/Application/Application.cs | 4 +- Terminal.Gui/View/Adornment/Border.cs | 2 +- Terminal.Gui/View/ViewSubViews.cs | 4 +- Terminal.Gui/Views/TileView.cs | 2 +- Terminal.Gui/Views/Toplevel.cs | 10 +- Terminal.Gui/Views/ToplevelOverlapped.cs | 2 +- .../Scenarios/BackgroundWorkerCollection.cs | 26 +- UnitTests/Application/ApplicationTests.cs | 24 +- UnitTests/Views/OverlappedTests.cs | 269 ++++++------ 17 files changed, 684 insertions(+), 678 deletions(-) create mode 100644 Terminal.Gui/Application/Application .Screen.cs diff --git a/Terminal.Gui/Application/Application .Screen.cs b/Terminal.Gui/Application/Application .Screen.cs new file mode 100644 index 0000000000..7770ae13c8 --- /dev/null +++ b/Terminal.Gui/Application/Application .Screen.cs @@ -0,0 +1,45 @@ +#nullable enable +namespace Terminal.Gui; + +public static partial class Application // Screen related stuff +{ + /// Invoked when the terminal's size changed. The new size of the terminal is provided. + /// + /// Event handlers can set to to prevent + /// from changing it's size to match the new terminal size. + /// + public static event EventHandler? SizeChanging; + + /// + /// Called when the application's size changes. Sets the size of all s and fires the + /// event. + /// + /// The new size. + /// if the size was changed. + public static bool OnSizeChanging (SizeChangedEventArgs args) + { + SizeChanging?.Invoke (null, args); + + if (args.Cancel || args.Size is null) + { + return false; + } + + foreach (Toplevel t in TopLevels) + { + t.SetRelativeLayout (args.Size.Value); + t.LayoutSubviews (); + t.PositionToplevels (); + t.OnSizeChanging (new (args.Size)); + + if (PositionCursor (t)) + { + Driver?.UpdateCursor (); + } + } + + Refresh (); + + return true; + } +} diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs index 37b34a3580..a2aacaab59 100644 --- a/Terminal.Gui/Application/Application.Initialization.cs +++ b/Terminal.Gui/Application/Application.Initialization.cs @@ -146,10 +146,10 @@ internal static void InternalInit ( ); } - Driver.SizeChanged += (s, args) => OnSizeChanging (args); - Driver.KeyDown += (s, args) => OnKeyDown (args); - Driver.KeyUp += (s, args) => OnKeyUp (args); - Driver.MouseEvent += (s, args) => OnMouseEvent (args); + Driver.SizeChanged += Driver_SizeChanged; + Driver.KeyDown += Driver_KeyDown; + Driver.KeyUp += Driver_KeyUp; + Driver.MouseEvent += Driver_MouseEvent; SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 59c78a83ee..d26dcd4327 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -122,7 +122,7 @@ public static bool OnKeyDown (Key keyEvent) return true; } - foreach (Toplevel topLevel in _topLevels.ToList ()) + foreach (Toplevel topLevel in TopLevels.ToList ()) { if (topLevel.NewKeyDownEvent (keyEvent)) { @@ -222,7 +222,7 @@ public static bool OnKeyUp (Key a) return true; } - foreach (Toplevel topLevel in _topLevels.ToList ()) + foreach (Toplevel topLevel in TopLevels.ToList ()) { if (topLevel.NewKeyUpEvent (a)) { @@ -302,7 +302,7 @@ internal static void AddApplicationKeyBindings () Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic. () => { - if (OverlappedTop is { }) + if (ApplicationOverlapped.OverlappedTop is { }) { RequestStop (Current!); } @@ -330,7 +330,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - ViewNavigation.MoveNextView (); + ApplicationNavigation.MoveNextView (); return true; } @@ -341,7 +341,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - ViewNavigation.MovePreviousView (); + ApplicationNavigation.MovePreviousView (); return true; } @@ -352,7 +352,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - ViewNavigation.MoveNextViewOrTop (); + ApplicationNavigation.MoveNextViewOrTop (); return true; } @@ -363,7 +363,7 @@ internal static void AddApplicationKeyBindings () () => { // TODO: Move this method to Application.Navigation.cs - ViewNavigation.MovePreviousViewOrTop (); + ApplicationNavigation.MovePreviousViewOrTop (); return true; } diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs index be7c38df64..2c497b7611 100644 --- a/Terminal.Gui/Application/Application.Mouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -187,20 +187,20 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (view is not Adornment) { - if ((view is null || view == OverlappedTop) + if ((view is null || view == ApplicationOverlapped.OverlappedTop) && Current is { Modal: false } - && OverlappedTop != null + && ApplicationOverlapped.OverlappedTop != null && mouseEvent.Flags != MouseFlags.ReportMousePosition && mouseEvent.Flags != 0) { // This occurs when there are multiple overlapped "tops" // E.g. "Mdi" - in the Background Worker Scenario - View? top = FindDeepestTop (Top!, mouseEvent.Position); + View? top = ApplicationOverlapped.FindDeepestTop (Top!, mouseEvent.Position); view = View.FindDeepestView (top, mouseEvent.Position); - if (view is { } && view != OverlappedTop && top != Current && top is { }) + if (view is { } && view != ApplicationOverlapped.OverlappedTop && top != Current && top is { }) { - MoveCurrent ((Toplevel)top); + ApplicationOverlapped.MoveCurrent ((Toplevel)top); } } } @@ -295,7 +295,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) }; } - BringOverlappedTopToFront (); + ApplicationOverlapped.BringOverlappedTopToFront (); } #endregion Mouse handling diff --git a/Terminal.Gui/Application/Application.Navigation.cs b/Terminal.Gui/Application/Application.Navigation.cs index d3f42fd64d..44bc6e99ad 100644 --- a/Terminal.Gui/Application/Application.Navigation.cs +++ b/Terminal.Gui/Application/Application.Navigation.cs @@ -1,366 +1,162 @@ #nullable enable namespace Terminal.Gui; -public static partial class Application +internal static class ApplicationNavigation { /// - /// Gets the list of the Overlapped children which are not modal from the - /// . + /// Gets the deepest focused subview of the specified . /// - public static List? OverlappedChildren + /// + /// + internal static View? GetDeepestFocusedSubview (View? view) { - get + if (view is null) { - if (OverlappedTop is { }) - { - List overlappedChildren = new (); - - lock (_topLevels) - { - foreach (Toplevel top in _topLevels) - { - if (top != OverlappedTop && !top.Modal) - { - overlappedChildren.Add (top); - } - } - } - - return overlappedChildren; - } - return null; } - } - /// - /// The object used for the application on startup which - /// is true. - /// - public static Toplevel? OverlappedTop - { - get + foreach (View v in view.Subviews) { - if (Top is { IsOverlappedContainer: true }) + if (v.HasFocus) { - return Top; + return GetDeepestFocusedSubview (v); } - - return null; } + + return view; } - /// Brings the superview of the most focused overlapped view is on front. - public static void BringOverlappedTopToFront () + /// + /// Sets the focus to the next view in the list. If the last view is focused, the first view is focused. + /// + /// + /// + internal static void FocusNearestView (IEnumerable? viewsInTabIndexes, View.NavigationDirection direction) { - if (OverlappedTop is { }) + if (viewsInTabIndexes is null) { return; } - View? top = FindTopFromView (Top?.MostFocused); - - if (top is Toplevel && Top?.Subviews.Count > 1 && Top.Subviews [^1] != top) - { - Top.BringSubviewToFront (top); - } - } - - /// Gets the current visible Toplevel overlapped child that matches the arguments pattern. - /// The type. - /// The strings to exclude. - /// The matched view. - public static Toplevel? GetTopOverlappedChild (Type? type = null, string []? exclude = null) - { - if (OverlappedChildren is null || OverlappedTop is null) - { - return null; - } + var found = false; + var focusProcessed = false; + var idx = 0; - foreach (Toplevel top in OverlappedChildren) + foreach (View v in viewsInTabIndexes) { - if (type is { } && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false) + if (v == Application.Current) { - return top; + found = true; } - if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data.ToString ()) == true) + if (found && v != Application.Current) + { + if (direction == View.NavigationDirection.Forward) + { + Application.Current!.SuperView?.FocusNext (); + } + else + { + Application.Current!.SuperView?.FocusPrev (); + } + + focusProcessed = true; + + if (Application.Current.SuperView?.Focused is { } && Application.Current.SuperView.Focused != Application.Current) + { + return; + } + } + else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1) { - continue; + viewsInTabIndexes.ToList () [0].SetFocus (); } - return top; + idx++; } - - return null; } - /// - /// Move to the next Overlapped child from the and set it as the if - /// it is not already. + /// Moves the focus to /// - /// - /// - public static bool MoveToOverlappedChild (Toplevel top) + internal static void MoveNextView () { - if (top.Visible && OverlappedTop is { } && Current?.Modal == false) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } + View? old = GetDeepestFocusedSubview (Application.Current!.Focused); - return true; + if (!Application.Current.FocusNext ()) + { + Application.Current.FocusNext (); } - return false; - } - - /// Move to the next Overlapped child from the . - public static void OverlappedMoveNext () - { - if (OverlappedTop is { } && !Current!.Modal) + if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) { - lock (_topLevels) - { - _topLevels.MoveNext (); - var isOverlapped = false; - - while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) - { - if (!isOverlapped && _topLevels.Peek () == OverlappedTop) - { - isOverlapped = true; - } - else if (isOverlapped && _topLevels.Peek () == OverlappedTop) - { - MoveCurrent (Top!); - - break; - } - - _topLevels.MoveNext (); - } - - Current = _topLevels.Peek (); - } + old?.SetNeedsDisplay (); + Application.Current.Focused?.SetNeedsDisplay (); } - } - - /// Move to the previous Overlapped child from the . - public static void OverlappedMovePrevious () - { - if (OverlappedTop is { } && !Current!.Modal) + else { - lock (_topLevels) - { - _topLevels.MovePrevious (); - var isOverlapped = false; - - while (_topLevels.Peek () == OverlappedTop || !_topLevels.Peek ().Visible) - { - if (!isOverlapped && _topLevels.Peek () == OverlappedTop) - { - isOverlapped = true; - } - else if (isOverlapped && _topLevels.Peek () == OverlappedTop) - { - MoveCurrent (Top!); - - break; - } - - _topLevels.MovePrevious (); - } - - Current = _topLevels.Peek (); - } + FocusNearestView (Application.Current.SuperView?.TabIndexes, View.NavigationDirection.Forward); } } - private static bool OverlappedChildNeedsDisplay () + internal static void MoveNextViewOrTop () { - if (OverlappedTop is null) + if (ApplicationOverlapped.OverlappedTop is null) { - return false; - } + Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; + top!.FocusNext (); - lock (_topLevels) - { - foreach (Toplevel top in _topLevels) + if (top.Focused is null) { - if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) - { - OverlappedTop.SetSubViewNeedsDisplay (); - - return true; - } + top.FocusNext (); } - } - - return false; - } - private static bool SetCurrentOverlappedAsTop () - { - if (OverlappedTop is null && Current != Top && Current?.SuperView is null && Current?.Modal == false) + top.SetNeedsDisplay (); + ApplicationOverlapped.BringOverlappedTopToFront (); + } + else { - Top = Current; - - return true; + ApplicationOverlapped.OverlappedMoveNext (); } - - return false; } - /// - /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the . - /// - /// - /// - /// - private static Toplevel? FindDeepestTop (Toplevel start, in Point location) + internal static void MovePreviousView () { - if (!start.Frame.Contains (location)) - { - return null; - } + View? old = GetDeepestFocusedSubview (Application.Current!.Focused); - lock (_topLevels) + if (!Application.Current.FocusPrev ()) { - if (_topLevels is not { Count: > 0 }) - { - return start; - } - - int rx = location.X - start.Frame.X; - int ry = location.Y - start.Frame.Y; - - foreach (Toplevel t in _topLevels) - { - if (t == Current) - { - continue; - } - - if (t != start && t.Visible && t.Frame.Contains (rx, ry)) - { - start = t; - - break; - } - } + Application.Current.FocusPrev (); } - return start; - } - - /// - /// Given , returns the first Superview up the chain that is . - /// - private static View? FindTopFromView (View? view) - { - if (view is null) + if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) { - return null; + old?.SetNeedsDisplay (); + Application.Current.Focused?.SetNeedsDisplay (); } - - View top = view.SuperView is { } && view.SuperView != Top - ? view.SuperView - : view; - - while (top?.SuperView is { } && top?.SuperView != Top) + else { - top = top!.SuperView; + FocusNearestView (Application.Current.SuperView?.TabIndexes?.Reverse (), View.NavigationDirection.Backward); } - - return top; } - /// - /// If the is not the then is moved to the top of - /// the Toplevel stack and made Current. - /// - /// - /// - private static bool MoveCurrent (Toplevel top) + internal static void MovePreviousViewOrTop () { - // The Current is modal and the top is not modal Toplevel then - // the Current must be moved above the first not modal Toplevel. - if (OverlappedTop is { } - && top != OverlappedTop - && top != Current - && Current?.Modal == true - && !_topLevels.Peek ().Modal) + if (ApplicationOverlapped.OverlappedTop is null) { - lock (_topLevels) - { - _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); - } - - var index = 0; - Toplevel [] savedToplevels = _topLevels.ToArray (); + Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; + top!.FocusPrev (); - foreach (Toplevel t in savedToplevels) + if (top.Focused is null) { - if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); - } - } - - index++; + top.FocusPrev (); } - return false; + top.SetNeedsDisplay (); + ApplicationOverlapped.BringOverlappedTopToFront (); } - - // The Current and the top are both not running Toplevel then - // the top must be moved above the first not running Toplevel. - if (OverlappedTop is { } - && top != OverlappedTop - && top != Current - && Current?.Running == false - && top?.Running == false) + else { - lock (_topLevels) - { - _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); - } - - var index = 0; - - foreach (Toplevel t in _topLevels.ToArray ()) - { - if (!t.Running && t != Current && index > 0) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); - } - } - - index++; - } - - return false; - } - - if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top) - || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) - || (OverlappedTop is { } && Current?.Modal == false && top != Current) - || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop)) - { - lock (_topLevels) - { - _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); - Current = top; - } + ApplicationOverlapped.OverlappedMovePrevious (); } - - return true; } } diff --git a/Terminal.Gui/Application/Application.Overlapped.cs b/Terminal.Gui/Application/Application.Overlapped.cs index 26e931c989..ce88c15675 100644 --- a/Terminal.Gui/Application/Application.Overlapped.cs +++ b/Terminal.Gui/Application/Application.Overlapped.cs @@ -1,170 +1,373 @@ #nullable enable -using static Terminal.Gui.View; -using System.Reflection; - namespace Terminal.Gui; -internal static class ViewNavigation +/// +/// Helper class for managing overlapped views in the application. +/// +public static class ApplicationOverlapped { /// - /// Gets the deepest focused subview of the specified . + /// Gets the list of the Overlapped children which are not modal from the + /// . /// - /// - /// - internal static View? GetDeepestFocusedSubview (View? view) + public static List? OverlappedChildren { - if (view is null) + get { + if (OverlappedTop is { }) + { + List overlappedChildren = new (); + + lock (Application.TopLevels) + { + foreach (Toplevel top in Application.TopLevels) + { + if (top != OverlappedTop && !top.Modal) + { + overlappedChildren.Add (top); + } + } + } + + return overlappedChildren; + } + return null; } + } - foreach (View v in view.Subviews) + /// + /// The object used for the application on startup which + /// is true. + /// + public static Toplevel? OverlappedTop + { + get { - if (v.HasFocus) + if (Application.Top is { IsOverlappedContainer: true }) { - return GetDeepestFocusedSubview (v); + return Application.Top; } - } - return view; + return null; + } } - /// - /// Sets the focus to the next view in the list. If the last view is focused, the first view is focused. - /// - /// - /// - internal static void FocusNearestView (IEnumerable? viewsInTabIndexes, NavigationDirection direction) + /// Brings the superview of the most focused overlapped view is on front. + public static void BringOverlappedTopToFront () { - if (viewsInTabIndexes is null) + if (OverlappedTop is { }) { return; } - var found = false; - var focusProcessed = false; - var idx = 0; + View? top = FindTopFromView (Application.Top?.MostFocused); - foreach (View v in viewsInTabIndexes) + if (top is Toplevel && Application.Top?.Subviews.Count > 1 && Application.Top.Subviews [^1] != top) { - if (v == Application.Current) - { - found = true; - } - - if (found && v != Application.Current) - { - if (direction == NavigationDirection.Forward) - { - Application.Current!.SuperView?.FocusNext (); - } - else - { - Application.Current!.SuperView?.FocusPrev (); - } + Application.Top.BringSubviewToFront (top); + } + } - focusProcessed = true; + /// Gets the current visible Toplevel overlapped child that matches the arguments pattern. + /// The type. + /// The strings to exclude. + /// The matched view. + public static Toplevel? GetTopOverlappedChild (Type? type = null, string []? exclude = null) + { + if (OverlappedChildren is null || OverlappedTop is null) + { + return null; + } - if (Application.Current.SuperView?.Focused is { } && Application.Current.SuperView.Focused != Application.Current) - { - return; - } + foreach (Toplevel top in OverlappedChildren) + { + if (type is { } && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false) + { + return top; } - else if (found && !focusProcessed && idx == viewsInTabIndexes.Count () - 1) + + if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data.ToString ()) == true) { - viewsInTabIndexes.ToList () [0].SetFocus (); + continue; } - idx++; + return top; } + + return null; } + /// - /// Moves the focus to + /// Move to the next Overlapped child from the and set it as the if + /// it is not already. /// - internal static void MoveNextView () + /// + /// + public static bool MoveToOverlappedChild (Toplevel? top) { - View? old = GetDeepestFocusedSubview (Application.Current!.Focused); - - if (!Application.Current.FocusNext ()) + if (top is null) { - Application.Current.FocusNext (); + return false; } + if (top.Visible && OverlappedTop is { } && Application.Current?.Modal == false) + { + lock (Application.TopLevels) + { + Application.TopLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Application.Current = top; + } + + return true; + } + + return false; + } - if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) + /// Move to the next Overlapped child from the . + public static void OverlappedMoveNext () + { + if (OverlappedTop is { } && !Application.Current!.Modal) { - old?.SetNeedsDisplay (); - Application.Current.Focused?.SetNeedsDisplay (); + lock (Application.TopLevels) + { + Application.TopLevels.MoveNext (); + var isOverlapped = false; + + while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible) + { + if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop) + { + isOverlapped = true; + } + else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop) + { + MoveCurrent (Application.Top!); + + break; + } + + Application.TopLevels.MoveNext (); + } + + Application.Current = Application.TopLevels.Peek (); + } } - else + } + + /// Move to the previous Overlapped child from the . + public static void OverlappedMovePrevious () + { + if (OverlappedTop is { } && !Application.Current!.Modal) { - FocusNearestView (Application.Current.SuperView?.TabIndexes, NavigationDirection.Forward); + lock (Application.TopLevels) + { + Application.TopLevels.MovePrevious (); + var isOverlapped = false; + + while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible) + { + if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop) + { + isOverlapped = true; + } + else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop) + { + MoveCurrent (Application.Top!); + + break; + } + + Application.TopLevels.MovePrevious (); + } + + Application.Current = Application.TopLevels.Peek (); + } } } - internal static void MoveNextViewOrTop () + internal static bool OverlappedChildNeedsDisplay () { - if (Application.OverlappedTop is null) + if (OverlappedTop is null) { - Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; - top!.FocusNext (); + return false; + } - if (top.Focused is null) + lock (Application.TopLevels) + { + foreach (Toplevel top in Application.TopLevels) { - top.FocusNext (); - } + if (top != Application.Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) + { + OverlappedTop.SetSubViewNeedsDisplay (); - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); + return true; + } + } } - else + + return false; + } + + internal static bool SetCurrentOverlappedAsTop () + { + if (OverlappedTop is null && Application.Current != Application.Top && Application.Current?.SuperView is null && Application.Current?.Modal == false) { - Application.OverlappedMoveNext (); + Application.Top = Application.Current; + + return true; } + + return false; } - internal static void MovePreviousView () + /// + /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the . + /// + /// + /// + /// + internal static Toplevel? FindDeepestTop (Toplevel start, in Point location) { - View? old = GetDeepestFocusedSubview (Application.Current!.Focused); + if (!start.Frame.Contains (location)) + { + return null; + } - if (!Application.Current.FocusPrev ()) + lock (Application.TopLevels) { - Application.Current.FocusPrev (); + if (Application.TopLevels is not { Count: > 0 }) + { + return start; + } + + int rx = location.X - start.Frame.X; + int ry = location.Y - start.Frame.Y; + + foreach (Toplevel t in Application.TopLevels) + { + if (t == Application.Current) + { + continue; + } + + if (t != start && t.Visible && t.Frame.Contains (rx, ry)) + { + start = t; + + break; + } + } } - if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) + return start; + } + + /// + /// Given , returns the first Superview up the chain that is . + /// + internal static View? FindTopFromView (View? view) + { + if (view is null) { - old?.SetNeedsDisplay (); - Application.Current.Focused?.SetNeedsDisplay (); + return null; } - else + + View top = view.SuperView is { } && view.SuperView != Application.Top + ? view.SuperView + : view; + + while (top?.SuperView is { } && top?.SuperView != Application.Top) { - FocusNearestView (Application.Current.SuperView?.TabIndexes?.Reverse (), NavigationDirection.Backward); + top = top!.SuperView; } + + return top; } - internal static void MovePreviousViewOrTop () + /// + /// If the is not the then is moved to the top of + /// the Toplevel stack and made Current. + /// + /// + /// + internal static bool MoveCurrent (Toplevel top) { - if (Application.OverlappedTop is null) + // The Current is modal and the top is not modal Toplevel then + // the Current must be moved above the first not modal Toplevel. + if (OverlappedTop is { } + && top != OverlappedTop + && top != Application.Current + && Application.Current?.Modal == true + && !Application.TopLevels.Peek ().Modal) + { + lock (Application.TopLevels) + { + Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ()); + } + + var index = 0; + Toplevel [] savedToplevels = Application.TopLevels.ToArray (); + + foreach (Toplevel t in savedToplevels) + { + if (!t!.Modal && t != Application.Current && t != top && t != savedToplevels [index]) + { + lock (Application.TopLevels) + { + Application.TopLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); + } + } + + index++; + } + + return false; + } + + // The Current and the top are both not running Toplevel then + // the top must be moved above the first not running Toplevel. + if (OverlappedTop is { } + && top != OverlappedTop + && top != Application.Current + && Application.Current?.Running == false + && top?.Running == false) { - Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; - top!.FocusPrev (); + lock (Application.TopLevels) + { + Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ()); + } - if (top.Focused is null) + var index = 0; + + foreach (Toplevel t in Application.TopLevels.ToArray ()) { - top.FocusPrev (); + if (!t.Running && t != Application.Current && index > 0) + { + lock (Application.TopLevels) + { + Application.TopLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); + } + } + + index++; } - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); + return false; } - else + + if ((OverlappedTop is { } && top?.Modal == true && Application.TopLevels.Peek () != top) + || (OverlappedTop is { } && Application.Current != OverlappedTop && Application.Current?.Modal == false && top == OverlappedTop) + || (OverlappedTop is { } && Application.Current?.Modal == false && top != Application.Current) + || (OverlappedTop is { } && Application.Current?.Modal == true && top == OverlappedTop)) { - Application.OverlappedMovePrevious (); + lock (Application.TopLevels) + { + Application.TopLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Application.Current = top; + } } + + return true; } } - -public static partial class Application // App-level View Navigation -{ - -} \ No newline at end of file diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 87bfde4c0c..3e088c348d 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -54,7 +54,7 @@ public static RunState Begin (Toplevel toplevel) } #endif - if (toplevel.IsOverlappedContainer && OverlappedTop != toplevel && OverlappedTop is { }) + if (toplevel.IsOverlappedContainer && ApplicationOverlapped.OverlappedTop != toplevel && ApplicationOverlapped.OverlappedTop is { }) { throw new InvalidOperationException ("Only one Overlapped Container is allowed."); } @@ -72,7 +72,7 @@ public static RunState Begin (Toplevel toplevel) } #if DEBUG_IDISPOSABLE - if (Top is { } && toplevel != Top && !_topLevels.Contains (Top)) + if (Top is { } && toplevel != Top && !TopLevels.Contains (Top)) { // This assertion confirm if the Top was already disposed Debug.Assert (Top.WasDisposed); @@ -80,9 +80,9 @@ public static RunState Begin (Toplevel toplevel) } #endif - lock (_topLevels) + lock (TopLevels) { - if (Top is { } && toplevel != Top && !_topLevels.Contains (Top)) + if (Top is { } && toplevel != Top && !TopLevels.Contains (Top)) { // If Top was already disposed and isn't on the Toplevels Stack, // clean it up here if is the same as _cachedRunStateToplevel @@ -96,7 +96,7 @@ public static RunState Begin (Toplevel toplevel) throw new ObjectDisposedException (Top.GetType ().FullName); } } - else if (OverlappedTop is { } && toplevel != Top && _topLevels.Contains (Top!)) + else if (ApplicationOverlapped.OverlappedTop is { } && toplevel != Top && TopLevels.Contains (Top!)) { Top!.OnLeave (toplevel); } @@ -106,29 +106,29 @@ public static RunState Begin (Toplevel toplevel) if (string.IsNullOrEmpty (toplevel.Id)) { var count = 1; - var id = (_topLevels.Count + count).ToString (); + var id = (TopLevels.Count + count).ToString (); - while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) is { }) + while (TopLevels.Count > 0 && TopLevels.FirstOrDefault (x => x.Id == id) is { }) { count++; - id = (_topLevels.Count + count).ToString (); + id = (TopLevels.Count + count).ToString (); } - toplevel.Id = (_topLevels.Count + count).ToString (); + toplevel.Id = (TopLevels.Count + count).ToString (); - _topLevels.Push (toplevel); + TopLevels.Push (toplevel); } else { - Toplevel? dup = _topLevels.FirstOrDefault (x => x.Id == toplevel.Id); + Toplevel? dup = TopLevels.FirstOrDefault (x => x.Id == toplevel.Id); if (dup is null) { - _topLevels.Push (toplevel); + TopLevels.Push (toplevel); } } - if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) + if (TopLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) { throw new ArgumentException ("There are duplicates Toplevel IDs"); } @@ -141,7 +141,7 @@ public static RunState Begin (Toplevel toplevel) var refreshDriver = true; - if (OverlappedTop is null + if (ApplicationOverlapped.OverlappedTop is null || toplevel.IsOverlappedContainer || (Current?.Modal == false && toplevel.Modal) || (Current?.Modal == false && !toplevel.Modal) @@ -154,25 +154,25 @@ public static RunState Begin (Toplevel toplevel) Current = toplevel; Current.OnActivate (previousCurrent); - SetCurrentOverlappedAsTop (); + ApplicationOverlapped.SetCurrentOverlappedAsTop (); } else { refreshDriver = false; } } - else if ((toplevel != OverlappedTop + else if ((toplevel != ApplicationOverlapped.OverlappedTop && Current?.Modal == true - && !_topLevels.Peek ().Modal) - || (toplevel != OverlappedTop && Current?.Running == false)) + && !TopLevels.Peek ().Modal) + || (toplevel != ApplicationOverlapped.OverlappedTop && Current?.Running == false)) { refreshDriver = false; - MoveCurrent (toplevel); + ApplicationOverlapped.MoveCurrent (toplevel); } else { refreshDriver = false; - MoveCurrent (Current!); + ApplicationOverlapped.MoveCurrent (Current!); } toplevel.SetRelativeLayout (Driver!.Screen.Size); @@ -180,11 +180,11 @@ public static RunState Begin (Toplevel toplevel) toplevel.LayoutSubviews (); toplevel.PositionToplevels (); toplevel.FocusFirst (); - BringOverlappedTopToFront (); + ApplicationOverlapped.BringOverlappedTopToFront (); if (refreshDriver) { - OverlappedTop?.OnChildLoaded (toplevel); + ApplicationOverlapped.OverlappedTop?.OnChildLoaded (toplevel); toplevel.OnLoaded (); toplevel.SetNeedsDisplay (); toplevel.Draw (); @@ -427,7 +427,7 @@ public static void Run (Toplevel view, Func? errorHandler = nul if (runState.Toplevel is null) { #if DEBUG_IDISPOSABLE - Debug.Assert (_topLevels.Count == 0); + Debug.Assert (TopLevels.Count == 0); #endif runState.Dispose (); @@ -499,7 +499,7 @@ public static void Refresh () // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear Driver!.ClearContents (); - foreach (Toplevel v in _topLevels.Reverse ()) + foreach (Toplevel v in TopLevels.Reverse ()) { if (v.Visible) { @@ -577,9 +577,9 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) // TODO: Overlapped - Move elsewhere if (state.Toplevel != Current) { - OverlappedTop?.OnDeactivate (state.Toplevel); + ApplicationOverlapped.OverlappedTop?.OnDeactivate (state.Toplevel); state.Toplevel = Current; - OverlappedTop?.OnActivate (state.Toplevel); + ApplicationOverlapped.OverlappedTop?.OnActivate (state.Toplevel); Top!.SetSubViewNeedsDisplay (); Refresh (); } @@ -597,7 +597,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) state.Toplevel!.SetNeedsDisplay (state.Toplevel.Frame); Top.Draw (); - foreach (Toplevel top in _topLevels.Reverse ()) + foreach (Toplevel top in TopLevels.Reverse ()) { if (top != Top && top != state.Toplevel) { @@ -608,7 +608,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) } } - if (_topLevels.Count == 1 + if (TopLevels.Count == 1 && state.Toplevel == Top && (Driver!.Cols != state.Toplevel!.Frame.Width || Driver!.Rows != state.Toplevel.Frame.Height) @@ -619,7 +619,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) Driver.ClearContents (); } - if (state.Toplevel!.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ()) + if (state.Toplevel!.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || ApplicationOverlapped.OverlappedChildNeedsDisplay ()) { state.Toplevel.SetNeedsDisplay (); state.Toplevel.Draw (); @@ -659,19 +659,19 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) /// public static void RequestStop (Toplevel? top = null) { - if (OverlappedTop is null || top is null) + if (ApplicationOverlapped.OverlappedTop is null || top is null) { top = Current; } - if (OverlappedTop != null + if (ApplicationOverlapped.OverlappedTop != null && top!.IsOverlappedContainer && top?.Running == true && (Current?.Modal == false || Current is { Modal: true, Running: false })) { - OverlappedTop.RequestStop (); + ApplicationOverlapped.OverlappedTop.RequestStop (); } - else if (OverlappedTop != null + else if (ApplicationOverlapped.OverlappedTop != null && top != Current && Current is { Running: true, Modal: true } && top!.Modal @@ -698,21 +698,21 @@ public static void RequestStop (Toplevel? top = null) top.Running = false; OnNotifyStopRunState (top); } - else if ((OverlappedTop != null - && top != OverlappedTop + else if ((ApplicationOverlapped.OverlappedTop != null + && top != ApplicationOverlapped.OverlappedTop && top != Current && Current is { Modal: false, Running: true } && !top!.Running) - || (OverlappedTop != null - && top != OverlappedTop + || (ApplicationOverlapped.OverlappedTop != null + && top != ApplicationOverlapped.OverlappedTop && top != Current && Current is { Modal: false, Running: false } && !top!.Running - && _topLevels.ToArray () [1].Running)) + && TopLevels.ToArray () [1].Running)) { - MoveCurrent (top); + ApplicationOverlapped.MoveCurrent (top); } - else if (OverlappedTop != null + else if (ApplicationOverlapped.OverlappedTop != null && Current != top && Current?.Running == true && !top!.Running @@ -723,9 +723,9 @@ public static void RequestStop (Toplevel? top = null) Current.Running = false; OnNotifyStopRunState (Current); } - else if (OverlappedTop != null + else if (ApplicationOverlapped.OverlappedTop != null && Current == top - && OverlappedTop?.Running == true + && ApplicationOverlapped.OverlappedTop?.Running == true && Current?.Running == true && top!.Running && Current?.Modal == true @@ -784,9 +784,9 @@ public static void End (RunState runState) { ArgumentNullException.ThrowIfNull (runState); - if (OverlappedTop is { }) + if (ApplicationOverlapped.OverlappedTop is { }) { - OverlappedTop.OnChildUnloaded (runState.Toplevel); + ApplicationOverlapped.OverlappedTop.OnChildUnloaded (runState.Toplevel); } else { @@ -795,16 +795,16 @@ public static void End (RunState runState) // End the RunState.Toplevel // First, take it off the Toplevel Stack - if (_topLevels.Count > 0) + if (TopLevels.Count > 0) { - if (_topLevels.Peek () != runState.Toplevel) + if (TopLevels.Peek () != runState.Toplevel) { // If the top of the stack is not the RunState.Toplevel then // this call to End is not balanced with the call to Begin that started the RunState throw new ArgumentException ("End must be balanced with calls to Begin"); } - _topLevels.Pop (); + TopLevels.Pop (); } // Notify that it is closing @@ -812,32 +812,32 @@ public static void End (RunState runState) // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel // is a child of MidTop, and we should notify the OverlappedTop that it is closing - if (OverlappedTop is { } && !runState.Toplevel!.Modal && runState.Toplevel != OverlappedTop) + if (ApplicationOverlapped.OverlappedTop is { } && !runState.Toplevel!.Modal && runState.Toplevel != ApplicationOverlapped.OverlappedTop) { - OverlappedTop.OnChildClosed (runState.Toplevel); + ApplicationOverlapped.OverlappedTop.OnChildClosed (runState.Toplevel); } // Set Current and Top to the next TopLevel on the stack - if (_topLevels.Count == 0) + if (TopLevels.Count == 0) { Current = null; } else { - if (_topLevels.Count > 1 && _topLevels.Peek () == OverlappedTop && OverlappedChildren?.Any (t => t.Visible) != null) + if (TopLevels.Count > 1 && TopLevels.Peek () == ApplicationOverlapped.OverlappedTop && ApplicationOverlapped.OverlappedChildren?.Any (t => t.Visible) != null) { - OverlappedMoveNext (); + ApplicationOverlapped.OverlappedMoveNext (); } - Current = _topLevels.Peek (); + Current = TopLevels.Peek (); - if (_topLevels.Count == 1 && Current == OverlappedTop) + if (TopLevels.Count == 1 && Current == ApplicationOverlapped.OverlappedTop) { - OverlappedTop.OnAllChildClosed (); + ApplicationOverlapped.OverlappedTop.OnAllChildClosed (); } else { - SetCurrentOverlappedAsTop (); + ApplicationOverlapped.SetCurrentOverlappedAsTop (); runState.Toplevel!.OnLeave (Current); Current.OnEnter (runState.Toplevel); } @@ -848,9 +848,9 @@ public static void End (RunState runState) // Don't dispose runState.Toplevel. It's up to caller dispose it // If it's not the same as the current in the RunIteration, // it will be fixed later in the next RunIteration. - if (OverlappedTop is { } && !_topLevels.Contains (OverlappedTop)) + if (ApplicationOverlapped.OverlappedTop is { } && !TopLevels.Contains (ApplicationOverlapped.OverlappedTop)) { - _cachedRunStateToplevel = OverlappedTop; + _cachedRunStateToplevel = ApplicationOverlapped.OverlappedTop; } else { diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs index a050e28f10..a1844b2d06 100644 --- a/Terminal.Gui/Application/Application.Toplevel.cs +++ b/Terminal.Gui/Application/Application.Toplevel.cs @@ -4,13 +4,13 @@ namespace Terminal.Gui; public static partial class Application // Toplevel handling { // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What + /// Holds the stack of TopLevel views. - // about TopLevels that are just a SubView of another View? - internal static readonly Stack _topLevels = new (); + internal static Stack TopLevels { get; } = new (); /// The object used for the application on startup () /// The top. - public static Toplevel? Top { get; private set; } + public static Toplevel? Top { get; internal set; } // TODO: Determine why this can't just return _topLevels.Peek()? /// @@ -22,7 +22,7 @@ public static partial class Application // Toplevel handling /// This will only be distinct from in scenarios where is . /// /// The current. - public static Toplevel? Current { get; private set; } + public static Toplevel? Current { get; internal set; } /// /// If is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current. @@ -31,17 +31,17 @@ private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) { if (!topLevel.Running || (topLevel == Current && topLevel.Visible) - || OverlappedTop == null - || _topLevels.Peek ().Modal) + || ApplicationOverlapped.OverlappedTop == null + || TopLevels.Peek ().Modal) { return; } - foreach (Toplevel top in _topLevels.Reverse ()) + foreach (Toplevel top in TopLevels.Reverse ()) { if (top.Modal && top != Current) { - MoveCurrent (top); + ApplicationOverlapped.MoveCurrent (top); return; } @@ -49,47 +49,8 @@ private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) if (!topLevel.Visible && topLevel == Current) { - OverlappedMoveNext (); + ApplicationOverlapped.OverlappedMoveNext (); } } - /// Invoked when the terminal's size changed. The new size of the terminal is provided. - /// - /// Event handlers can set to to prevent - /// from changing it's size to match the new terminal size. - /// - public static event EventHandler? SizeChanging; - - /// - /// Called when the application's size changes. Sets the size of all s and fires the - /// event. - /// - /// The new size. - /// if the size was changed. - public static bool OnSizeChanging (SizeChangedEventArgs args) - { - SizeChanging?.Invoke (null, args); - - if (args.Cancel || args.Size is null) - { - return false; - } - - foreach (Toplevel t in _topLevels) - { - t.SetRelativeLayout (args.Size.Value); - t.LayoutSubviews (); - t.PositionToplevels (); - t.OnSizeChanging (new (args.Size)); - - if (PositionCursor (t)) - { - Driver?.UpdateCursor (); - } - } - - Refresh (); - - return true; - } } diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 75fbd7191a..b4b4d13105 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -56,12 +56,12 @@ internal static void ResetState (bool ignoreDisposed = false) // Shutdown is the bookend for Init. As such it needs to clean up all resources // Init created. Apps that do any threading will need to code defensively for this. // e.g. see Issue #537 - foreach (Toplevel? t in _topLevels) + foreach (Toplevel? t in TopLevels) { t!.Running = false; } - _topLevels.Clear (); + TopLevels.Clear (); Current = null; #if DEBUG_IDISPOSABLE diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index ef841e5b19..41fd02f9d2 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -293,7 +293,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) { Parent.SetFocus (); - Application.BringOverlappedTopToFront (); + ApplicationOverlapped.BringOverlappedTopToFront (); // Only start grabbing if the user clicks in the Thickness area // Adornment.Contains takes Parent SuperView=relative coords. diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 38863828a9..5da8dd76ae 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -456,7 +456,7 @@ public bool CanFocus Application.Current.FocusNext (); } - Application.BringOverlappedTopToFront (); + ApplicationOverlapped.BringOverlappedTopToFront (); } } @@ -489,7 +489,7 @@ public bool CanFocus if (this is Toplevel && Application.Current.Focused != this) { - Application.BringOverlappedTopToFront (); + ApplicationOverlapped.BringOverlappedTopToFront (); } } diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 54ec8a4d2a..6eb5fa3bf4 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -908,7 +908,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { // Start a Drag SetFocus (); - Application.BringOverlappedTopToFront (); + ApplicationOverlapped.BringOverlappedTopToFront (); if (mouseEvent.Flags == MouseFlags.Button1Pressed) { diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 11d2162100..d13ae07663 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -239,7 +239,7 @@ public virtual void RequestStop () || Application.Current?.Modal == false || (Application.Current?.Modal == true && Application.Current?.Running == false))) { - foreach (Toplevel child in Application.OverlappedChildren) + foreach (Toplevel child in ApplicationOverlapped.OverlappedChildren!) { var ev = new ToplevelClosingEventArgs (this); @@ -369,10 +369,10 @@ public override void OnDrawContent (Rectangle viewport) //LayoutSubviews (); PositionToplevels (); - if (this == Application.OverlappedTop) + if (this == ApplicationOverlapped.OverlappedTop) { // This enables correct draw behavior when switching between overlapped subviews - foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) + foreach (Toplevel top in ApplicationOverlapped.OverlappedChildren!.AsEnumerable ().Reverse ()) { if (top.Frame.IntersectsWith (Viewport)) { @@ -437,7 +437,7 @@ public override void OnDrawContent (Rectangle viewport) if (Focused is null) { // TODO: this is an Overlapped hack - foreach (Toplevel top in Application.OverlappedChildren) + foreach (Toplevel top in ApplicationOverlapped.OverlappedChildren!) { if (top != this && top.Visible) { @@ -607,7 +607,7 @@ public int GetHashCode (Toplevel obj) /// /// Implements the to sort the from the -/// if needed. +/// if needed. /// public sealed class ToplevelComparer : IComparer { diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs index 3541ea0917..06dac36941 100644 --- a/Terminal.Gui/Views/ToplevelOverlapped.cs +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -3,7 +3,7 @@ public partial class Toplevel { /// Gets or sets if this Toplevel is in overlapped mode within a Toplevel container. - public bool IsOverlapped => Application.OverlappedTop is { } && Application.OverlappedTop != this && !Modal; + public bool IsOverlapped => ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != this && !Modal; /// Gets or sets if this Toplevel is a container for overlapped children. public bool IsOverlappedContainer { get; set; } diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index b2f6cdfa91..d1ce6b825c 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -20,10 +20,10 @@ public override void Main () Application.Run ().Dispose (); #if DEBUG_IDISPOSABLE - if (Application.OverlappedChildren is { }) + if (ApplicationOverlapped.OverlappedChildren is { }) { - Debug.Assert (Application.OverlappedChildren?.Count == 0); - Debug.Assert (Application.Top == Application.OverlappedTop); + Debug.Assert (ApplicationOverlapped.OverlappedChildren?.Count == 0); + Debug.Assert (Application.Top == ApplicationOverlapped.OverlappedTop); } #endif @@ -134,7 +134,7 @@ private MenuBarItem OpenedWindows () { var index = 1; List menuItems = new (); - List sortedChildren = Application.OverlappedChildren; + List sortedChildren = ApplicationOverlapped.OverlappedChildren; sortedChildren.Sort (new ToplevelComparer ()); foreach (Toplevel top in sortedChildren) @@ -151,7 +151,7 @@ private MenuBarItem OpenedWindows () string topTitle = top is Window ? ((Window)top).Title : top.Data.ToString (); string itemTitle = item.Title.Substring (index.ToString ().Length + 1); - if (top == Application.GetTopOverlappedChild () && topTitle == itemTitle) + if (top == ApplicationOverlapped.GetTopOverlappedChild () && topTitle == itemTitle) { item.Checked = true; } @@ -160,7 +160,7 @@ private MenuBarItem OpenedWindows () item.Checked = false; } - item.Action += () => { Application.MoveToOverlappedChild (top); }; + item.Action += () => { ApplicationOverlapped.MoveToOverlappedChild (top); }; menuItems.Add (item); } @@ -188,7 +188,7 @@ private MenuBarItem View () { List menuItems = new (); var item = new MenuItem { Title = "WorkerApp", CheckType = MenuItemCheckStyle.Checked }; - Toplevel top = Application.OverlappedChildren?.Find (x => x.Data.ToString () == "WorkerApp"); + Toplevel top = ApplicationOverlapped.OverlappedChildren?.Find (x => x.Data.ToString () == "WorkerApp"); if (top != null) { @@ -197,16 +197,16 @@ private MenuBarItem View () item.Action += () => { - Toplevel top = Application.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp"); + Toplevel top = ApplicationOverlapped.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp"); item.Checked = top.Visible = (bool)!item.Checked; if (top.Visible) { - Application.MoveToOverlappedChild (top); + ApplicationOverlapped.MoveToOverlappedChild (top); } else { - Application.OverlappedTop.SetNeedsDisplay (); + ApplicationOverlapped.OverlappedTop!.SetNeedsDisplay (); } }; menuItems.Add (item); @@ -373,14 +373,14 @@ private void WorkerApp_Closed (object sender, ToplevelEventArgs e) } private void WorkerApp_Closing (object sender, ToplevelClosingEventArgs e) { - Toplevel top = Application.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp"); + Toplevel top = ApplicationOverlapped.OverlappedChildren!.Find (x => x.Data.ToString () == "WorkerApp"); if (Visible && top == this) { Visible = false; e.Cancel = true; - Application.OverlappedMoveNext (); + ApplicationOverlapped.OverlappedMoveNext (); } } @@ -481,7 +481,7 @@ public void RunWorker () _stagingsUi.Add (stagingUI); _stagingWorkers.Remove (staging); #if DEBUG_IDISPOSABLE - if (Application.OverlappedTop is null) + if (ApplicationOverlapped.OverlappedTop is null) { stagingUI.Dispose (); return; diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index c600254a2a..004f07e2eb 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -89,12 +89,12 @@ public void Init_Begin_End_Cleans_Up () RunState runstate = null; - EventHandler NewRunStateFn = (s, e) => + EventHandler newRunStateFn = (s, e) => { Assert.NotNull (e.State); runstate = e.State; }; - Application.NotifyNewRunState += NewRunStateFn; + Application.NotifyNewRunState += newRunStateFn; var topLevel = new Toplevel (); RunState rs = Application.Begin (topLevel); @@ -105,7 +105,7 @@ public void Init_Begin_End_Cleans_Up () Assert.Equal (topLevel, Application.Top); Assert.Equal (topLevel, Application.Current); - Application.NotifyNewRunState -= NewRunStateFn; + Application.NotifyNewRunState -= newRunStateFn; Application.End (runstate); Assert.Null (Application.Current); @@ -187,15 +187,15 @@ void CheckReset () Assert.Equal (Key.Empty, Application.AlternateBackwardKey); Assert.Equal (Key.Empty, Application.AlternateForwardKey); Assert.Equal (Key.Empty, Application.QuitKey); - Assert.Null (Application.OverlappedChildren); - Assert.Null (Application.OverlappedTop); + Assert.Null (ApplicationOverlapped.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedTop); // Internal properties Assert.False (Application.IsInitialized); Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures); Assert.False (Application._forceFakeConsole); Assert.Equal (-1, Application.MainThreadId); - Assert.Empty (Application._topLevels); + Assert.Empty (Application.TopLevels); Assert.Null (Application.MouseEnteredView); // Keyboard @@ -235,8 +235,8 @@ void CheckReset () Application.QuitKey = Key.C; Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Cancel); - //Application.OverlappedChildren = new List (); - //Application.OverlappedTop = + //ApplicationOverlapped.OverlappedChildren = new List (); + //ApplicationOverlapped.OverlappedTop = Application.MouseEnteredView = new (); //Application.WantContinuousButtonPressedView = new View (); @@ -378,12 +378,12 @@ public void InitWithoutTopLevelFactory_Begin_End_Cleans_Up () RunState runstate = null; - EventHandler NewRunStateFn = (s, e) => + EventHandler newRunStateFn = (s, e) => { Assert.NotNull (e.State); runstate = e.State; }; - Application.NotifyNewRunState += NewRunStateFn; + Application.NotifyNewRunState += newRunStateFn; RunState rs = Application.Begin (topLevel); Assert.NotNull (rs); @@ -393,7 +393,7 @@ public void InitWithoutTopLevelFactory_Begin_End_Cleans_Up () Assert.Equal (topLevel, Application.Top); Assert.Equal (topLevel, Application.Current); - Application.NotifyNewRunState -= NewRunStateFn; + Application.NotifyNewRunState -= newRunStateFn; Application.End (runstate); Assert.Null (Application.Current); @@ -419,7 +419,7 @@ public void Internal_Properties_Correct () Assert.Equal (Application.Top, rs.Toplevel); Assert.Null (Application.MouseGrabView); // public Assert.Null (Application.WantContinuousButtonPressedView); // public - Assert.False (Application.MoveToOverlappedChild (Application.Top)); + Assert.False (ApplicationOverlapped.MoveToOverlappedChild (Application.Top!)); Application.Top.Dispose (); } diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index 2cf4fa96de..c1b0ec7331 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -1,4 +1,5 @@ -using System.Threading; +#nullable enable +using System.Threading; using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; @@ -30,25 +31,25 @@ public void AllChildClosed_Event_Test () overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); c3.RequestStop (); c2.RequestStop (); c1.RequestStop (); @@ -66,31 +67,31 @@ public void AllChildClosed_Event_Test () Assert.False (Application.Current.Running); // But the Children order were reorder by Running = false - Assert.True (Application.OverlappedChildren [0] == c3); - Assert.True (Application.OverlappedChildren [1] == c2); - Assert.True (Application.OverlappedChildren [^1] == c1); + Assert.True (ApplicationOverlapped.OverlappedChildren! [0] == c3); + Assert.True (ApplicationOverlapped.OverlappedChildren [1] == c2); + Assert.True (ApplicationOverlapped.OverlappedChildren [^1] == c1); } else if (iterations == 2) { // The Current is c2 and Current.Running is false. Assert.True (Application.Current == c2); Assert.False (Application.Current.Running); - Assert.True (Application.OverlappedChildren [0] == c2); - Assert.True (Application.OverlappedChildren [^1] == c1); + Assert.True (ApplicationOverlapped.OverlappedChildren ![0] == c2); + Assert.True (ApplicationOverlapped.OverlappedChildren [^1] == c1); } else if (iterations == 1) { // The Current is c1 and Current.Running is false. Assert.True (Application.Current == c1); Assert.False (Application.Current.Running); - Assert.True (Application.OverlappedChildren [^1] == c1); + Assert.True (ApplicationOverlapped.OverlappedChildren! [^1] == c1); } else { // The Current is overlapped. Assert.True (Application.Current == overlapped); Assert.False (Application.Current.Running); - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); } iterations--; @@ -98,8 +99,8 @@ public void AllChildClosed_Event_Test () Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -119,31 +120,31 @@ public void Application_RequestStop_With_Params_On_A_Not_OverlappedContainer_Alw top1.Ready += (s, e) => { - Assert.Null (Application.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedChildren); Application.Run (top2); }; top2.Ready += (s, e) => { - Assert.Null (Application.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedChildren); Application.Run (top3); }; top3.Ready += (s, e) => { - Assert.Null (Application.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedChildren); Application.Run (top4); }; top4.Ready += (s, e) => { - Assert.Null (Application.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedChildren); Application.Run (d); }; d.Ready += (s, e) => { - Assert.Null (Application.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedChildren); // This will close the d because on a not OverlappedContainer the Application.Current it always used. Application.RequestStop (top1); @@ -154,7 +155,7 @@ public void Application_RequestStop_With_Params_On_A_Not_OverlappedContainer_Alw Application.Iteration += (s, a) => { - Assert.Null (Application.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedChildren); if (iterations == 4) { @@ -183,7 +184,7 @@ public void Application_RequestStop_With_Params_On_A_Not_OverlappedContainer_Alw Application.Run (top1); - Assert.Null (Application.OverlappedChildren); + Assert.Null (ApplicationOverlapped.OverlappedChildren); top1.Dispose (); } @@ -261,37 +262,37 @@ public void overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (d1); }; d1.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (d2); }; d2.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Assert.True (Application.Current == d2); Assert.True (Application.Current.Running); @@ -326,11 +327,11 @@ public void } else { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); for (var i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); + Assert.Equal ((iterations - i + 1).ToString (), ApplicationOverlapped.OverlappedChildren [i].Id); } } @@ -339,8 +340,8 @@ public void Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -363,37 +364,37 @@ public void overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (d1); }; d1.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c4); }; c4.Ready += (s, e) => { - Assert.Equal (4, Application.OverlappedChildren.Count); + Assert.Equal (4, ApplicationOverlapped.OverlappedChildren!.Count); // Trying to close the Dialog1 d1.RequestStop (); @@ -415,13 +416,13 @@ public void } else { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); for (var i = 0; i < iterations; i++) { Assert.Equal ( (iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (), - Application.OverlappedChildren [i].Id + ApplicationOverlapped.OverlappedChildren [i].Id ); } } @@ -431,8 +432,8 @@ Application.OverlappedChildren [i].Id Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -441,7 +442,7 @@ Application.OverlappedChildren [i].Id [AutoInitShutdown] public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_With_Running_Set_To_False () { - var overlapped = new Overlapped (); + Overlapped? overlapped = new Overlapped (); var c1 = new Toplevel (); var c2 = new Window (); var c3 = new Window (); @@ -451,25 +452,25 @@ public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_ overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); c3.RequestStop (); c1.RequestStop (); }; @@ -486,30 +487,30 @@ public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_ Assert.False (Application.Current.Running); // But the Children order were reorder by Running = false - Assert.True (Application.OverlappedChildren [0] == c3); - Assert.True (Application.OverlappedChildren [1] == c1); - Assert.True (Application.OverlappedChildren [^1] == c2); + Assert.True (ApplicationOverlapped.OverlappedChildren! [0] == c3); + Assert.True (ApplicationOverlapped.OverlappedChildren [1] == c1); + Assert.True (ApplicationOverlapped.OverlappedChildren [^1] == c2); } else if (iterations == 2) { // The Current is c1 and Current.Running is false. Assert.True (Application.Current == c1); Assert.False (Application.Current.Running); - Assert.True (Application.OverlappedChildren [0] == c1); - Assert.True (Application.OverlappedChildren [^1] == c2); + Assert.True (ApplicationOverlapped.OverlappedChildren! [0] == c1); + Assert.True (ApplicationOverlapped.OverlappedChildren [^1] == c2); } else if (iterations == 1) { // The Current is c2 and Current.Running is false. Assert.True (Application.Current == c2); Assert.False (Application.Current.Running); - Assert.True (Application.OverlappedChildren [^1] == c2); + Assert.True (ApplicationOverlapped.OverlappedChildren! [^1] == c2); } else { // The Current is overlapped. Assert.True (Application.Current == overlapped); - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); } iterations--; @@ -517,8 +518,8 @@ public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_ Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -526,7 +527,7 @@ public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_ [Fact] public void MoveToOverlappedChild_Throw_NullReferenceException_Passing_Null_Parameter () { - Assert.Throws (delegate { Application.MoveToOverlappedChild (null); }); + Assert.Throws (delegate { ApplicationOverlapped.MoveToOverlappedChild (null); }); } [Fact] @@ -544,11 +545,11 @@ public void OverlappedContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevel overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (logger); }; - logger.Ready += (s, e) => Assert.Single (Application.OverlappedChildren); + logger.Ready += (s, e) => Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Iteration += (s, a) => { @@ -559,7 +560,7 @@ public void OverlappedContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevel stage.Ready += (s, e) => { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); stage.RequestStop (); }; @@ -570,7 +571,7 @@ public void OverlappedContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevel allStageClosed = true; } - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); if (running) { @@ -581,7 +582,7 @@ public void OverlappedContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevel rpt.Ready += (s, e) => { iterations++; - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren.Count); }; Application.Run (rpt); @@ -593,28 +594,28 @@ public void OverlappedContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevel else if (iterations == 11 && running) { running = false; - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); } else if (!overlappedRequestStop && running && !allStageClosed) { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); } else if (!overlappedRequestStop && !running && allStageClosed) { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); overlappedRequestStop = true; - overlapped.RequestStop (); + overlapped?.RequestStop (); } else { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); } }; Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -652,32 +653,32 @@ public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_With_ overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (d); }; // Also easy because the Overlapped Container handles all at once d.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); // This will not close the OverlappedContainer because d is a modal Toplevel Application.RequestStop (overlapped); @@ -696,11 +697,11 @@ public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_With_ } else { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); for (var i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); + Assert.Equal ((iterations - i + 1).ToString (), ApplicationOverlapped.OverlappedChildren [i].Id); } } @@ -709,8 +710,8 @@ public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_With_ Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -731,32 +732,32 @@ public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_Witho overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (d); }; //More harder because it's sequential. d.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); // Close the Dialog Application.RequestStop (); @@ -776,11 +777,11 @@ public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_Witho } else { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); for (var i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); + Assert.Equal ((iterations - i + 1).ToString (), ApplicationOverlapped.OverlappedChildren [i].Id); } } @@ -789,8 +790,8 @@ public void OverlappedContainer_With_Application_RequestStop_OverlappedTop_Witho Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -811,32 +812,32 @@ public void OverlappedContainer_With_Toplevel_RequestStop_Balanced () overlapped.Ready += (s, e) => { - Assert.Empty (Application.OverlappedChildren); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); Application.Run (c1); }; c1.Ready += (s, e) => { - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Run (c2); }; c2.Ready += (s, e) => { - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (c3); }; c3.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); Application.Run (d); }; // More easy because the Overlapped Container handles all at once d.Ready += (s, e) => { - Assert.Equal (3, Application.OverlappedChildren.Count); + Assert.Equal (3, ApplicationOverlapped.OverlappedChildren!.Count); // This will not close the OverlappedContainer because d is a modal Toplevel and will be closed. overlapped.RequestStop (); @@ -855,11 +856,11 @@ public void OverlappedContainer_With_Toplevel_RequestStop_Balanced () } else { - Assert.Equal (iterations, Application.OverlappedChildren.Count); + Assert.Equal (iterations, ApplicationOverlapped.OverlappedChildren!.Count); for (var i = 0; i < iterations; i++) { - Assert.Equal ((iterations - i + 1).ToString (), Application.OverlappedChildren [i].Id); + Assert.Equal ((iterations - i + 1).ToString (), ApplicationOverlapped.OverlappedChildren [i].Id); } } @@ -868,8 +869,8 @@ public void OverlappedContainer_With_Toplevel_RequestStop_Balanced () Application.Run (overlapped); - Assert.Empty (Application.OverlappedChildren); - Assert.NotNull (Application.OverlappedTop); + Assert.Empty (ApplicationOverlapped.OverlappedChildren!); + Assert.NotNull (ApplicationOverlapped.OverlappedTop); Assert.NotNull (Application.Top); overlapped.Dispose (); } @@ -885,7 +886,7 @@ public void Visible_False_Does_Not_Clear () RunState rsOverlapped = Application.Begin (overlapped); // Need to fool MainLoop into thinking it's running - Application.MainLoop.Running = true; + Application.MainLoop!.Running = true; // RunIteration must be call on each iteration because // it's using the Begin and not the Run method @@ -894,7 +895,7 @@ public void Visible_False_Does_Not_Clear () Assert.Equal (overlapped, rsOverlapped.Toplevel); Assert.Equal (Application.Top, rsOverlapped.Toplevel); - Assert.Equal (Application.OverlappedTop, rsOverlapped.Toplevel); + Assert.Equal (ApplicationOverlapped.OverlappedTop, rsOverlapped.Toplevel); Assert.Equal (Application.Current, rsOverlapped.Toplevel); Assert.Equal (overlapped, Application.Current); @@ -903,7 +904,7 @@ public void Visible_False_Does_Not_Clear () Assert.Equal (overlapped, rsOverlapped.Toplevel); Assert.Equal (Application.Top, rsOverlapped.Toplevel); - Assert.Equal (Application.OverlappedTop, rsOverlapped.Toplevel); + Assert.Equal (ApplicationOverlapped.OverlappedTop, rsOverlapped.Toplevel); // The win1 Visible is false and cannot be set as the Current Assert.Equal (Application.Current, rsOverlapped.Toplevel); Assert.Equal (overlapped, Application.Current); @@ -916,7 +917,7 @@ public void Visible_False_Does_Not_Clear () // and not the original overlapped Assert.Equal (win2, rsOverlapped.Toplevel); Assert.Equal (Application.Top, overlapped); - Assert.Equal (Application.OverlappedTop, overlapped); + Assert.Equal (ApplicationOverlapped.OverlappedTop, overlapped); Assert.Equal (Application.Current, rsWin2.Toplevel); Assert.Equal (win2, Application.Current); Assert.Equal (win1, rsWin1.Toplevel); @@ -931,7 +932,7 @@ public void Visible_False_Does_Not_Clear () Assert.Equal (win2, rsOverlapped.Toplevel); Assert.Equal (Application.Top, overlapped); - Assert.Equal (Application.OverlappedTop, overlapped); + Assert.Equal (ApplicationOverlapped.OverlappedTop, overlapped); Assert.Equal (Application.Current, rsWin2.Toplevel); Assert.Equal (win2, Application.Current); Assert.Equal (win1, rsWin1.Toplevel); @@ -945,7 +946,7 @@ public void Visible_False_Does_Not_Clear () Assert.Equal (win2, rsOverlapped.Toplevel); Assert.Equal (Application.Top, overlapped); - Assert.Equal (Application.OverlappedTop, overlapped); + Assert.Equal (ApplicationOverlapped.OverlappedTop, overlapped); Assert.Equal (Application.Current, rsWin2.Toplevel); Assert.Equal (win2, Application.Current); Assert.Equal (win1, rsWin1.Toplevel); @@ -963,7 +964,7 @@ public void Visible_False_Does_Not_Clear () #endif Assert.Null (rsOverlapped.Toplevel); Assert.Equal (Application.Top, overlapped); - Assert.Equal (Application.OverlappedTop, overlapped); + Assert.Equal (ApplicationOverlapped.OverlappedTop, overlapped); Assert.Equal (Application.Current, rsWin1.Toplevel); Assert.Equal (win1, Application.Current); Assert.Equal (win1, rsWin1.Toplevel); @@ -978,7 +979,7 @@ public void Visible_False_Does_Not_Clear () #endif Assert.Null (rsOverlapped.Toplevel); Assert.Equal (Application.Top, overlapped); - Assert.Equal (Application.OverlappedTop, overlapped); + Assert.Equal (ApplicationOverlapped.OverlappedTop, overlapped); Assert.Equal (Application.Current, overlapped); Assert.Null (rsWin1.Toplevel); // See here that the only Toplevel that needs to End is the overlapped @@ -994,7 +995,7 @@ public void Visible_False_Does_Not_Clear () #endif Assert.Null (rsOverlapped.Toplevel); Assert.Equal (Application.Top, overlapped); - Assert.Equal (Application.OverlappedTop, overlapped); + Assert.Equal (ApplicationOverlapped.OverlappedTop, overlapped); Assert.Null (Application.Current); Assert.Null (rsWin1.Toplevel); Assert.Null (rsWin2.Toplevel); @@ -1021,10 +1022,10 @@ private class Overlapped : Toplevel public void KeyBindings_Command_With_OverlappedTop () { Toplevel top = new (); - Assert.Null (Application.OverlappedTop); + Assert.Null (ApplicationOverlapped.OverlappedTop); top.IsOverlappedContainer = true; Application.Begin (top); - Assert.Equal (Application.Top, Application.OverlappedTop); + Assert.Equal (Application.Top, ApplicationOverlapped.OverlappedTop); var isRunning = true; @@ -1060,7 +1061,7 @@ public void KeyBindings_Command_With_OverlappedTop () Assert.Null (top.Focused); Assert.Equal (top, Application.Current); Assert.True (top.IsCurrentTop); - Assert.Equal (top, Application.OverlappedTop); + Assert.Equal (top, ApplicationOverlapped.OverlappedTop); Application.Begin (win1); Assert.Equal (new (0, 0, 40, 25), win1.Frame); Assert.NotEqual (top, Application.Current); @@ -1072,7 +1073,7 @@ public void KeyBindings_Command_With_OverlappedTop () Assert.Null (top.MostFocused); Assert.Equal (tf1W1, win1.MostFocused); Assert.True (win1.IsOverlapped); - Assert.Single (Application.OverlappedChildren); + Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Begin (win2); Assert.Equal (new (0, 0, 40, 25), win2.Frame); Assert.NotEqual (top, Application.Current); @@ -1083,16 +1084,16 @@ public void KeyBindings_Command_With_OverlappedTop () Assert.Null (top.Focused); Assert.Null (top.MostFocused); Assert.Equal (tf1W2, win2.MostFocused); - Assert.Equal (2, Application.OverlappedChildren.Count); + Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); - Application.MoveToOverlappedChild (win1); + ApplicationOverlapped.MoveToOverlappedChild (win1); Assert.Equal (win1, Application.Current); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); win1.Running = true; Assert.True (Application.OnKeyDown (Application.QuitKey)); Assert.False (isRunning); Assert.False (win1.Running); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.True ( Application.OnKeyDown (Key.Z.WithCtrl) @@ -1110,77 +1111,77 @@ public void KeyBindings_Command_With_OverlappedTop () Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text); Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); // move to win2 - Assert.Equal (win2, Application.OverlappedChildren [0]); + Assert.Equal (win2, ApplicationOverlapped.OverlappedChildren [0]); Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl.WithShift)); // move back to win1 - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.Tab)); // text view eats tab - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); tvW1.AllowsTab = false; Assert.True (Application.OnKeyDown (Key.Tab)); // text view eats tab - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorRight)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorDown)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); #if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.I.WithCtrl))); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.True (ApplicationOverlapped.OverlappedChildren [0].ProcessKeyDown (new (Key.I.WithCtrl))); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); #endif Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorLeft)); // The view to the left of tvW1 is tf2W1, but tvW1 is still focused and eats cursor keys - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorUp)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl)); // Move to win2 - Assert.Equal (win2, Application.OverlappedChildren [0]); + Assert.Equal (win2, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf1W2, win2.MostFocused); tf2W2.SetFocus (); Assert.True (tf2W2.HasFocus); Assert.True (Application.OnKeyDown (Key.Tab.WithCtrl.WithShift)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Application.AlternateForwardKey)); - Assert.Equal (win2, Application.OverlappedChildren [0]); + Assert.Equal (win2, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf2W2, win2.MostFocused); Assert.True (Application.OnKeyDown (Application.AlternateBackwardKey)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorDown)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); #if UNIX_KEY_BINDINGS Assert.True (Application.OnKeyDown (new (Key.B.WithCtrl))); #else Assert.True (Application.OnKeyDown (Key.CursorLeft)); #endif - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); Assert.True (Application.OnKeyDown (Key.CursorDown)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.Equal (Point.Empty, tvW1.CursorPosition); Assert.True (Application.OnKeyDown (Key.End.WithCtrl)); - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.Equal (new (16, 1), tvW1.CursorPosition); #if UNIX_KEY_BINDINGS @@ -1188,11 +1189,11 @@ public void KeyBindings_Command_With_OverlappedTop () #else Assert.True (Application.OnKeyDown (Key.CursorRight)); #endif - Assert.Equal (win1, Application.OverlappedChildren [0]); + Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); #if UNIX_KEY_BINDINGS - Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.L.WithCtrl))); + Assert.True (ApplicationOverlapped.OverlappedChildren [0].ProcessKeyDown (new (Key.L.WithCtrl))); #endif win2.Dispose (); win1.Dispose (); From cb3e80666f645c7ca31e0a98e7af9e03f4068fd9 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 15:43:35 -0600 Subject: [PATCH 87/96] Moved Overlapped stuff to ApplicationOverlap static class. Fixed nullable warnings. --- Terminal.Gui/Application/Application.Overlapped.cs | 6 ++---- UnitTests/Views/OverlappedTests.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Application/Application.Overlapped.cs b/Terminal.Gui/Application/Application.Overlapped.cs index ce88c15675..cf9212d80f 100644 --- a/Terminal.Gui/Application/Application.Overlapped.cs +++ b/Terminal.Gui/Application/Application.Overlapped.cs @@ -106,10 +106,8 @@ public static void BringOverlappedTopToFront () /// public static bool MoveToOverlappedChild (Toplevel? top) { - if (top is null) - { - return false; - } + ArgumentNullException.ThrowIfNull (top); + if (top.Visible && OverlappedTop is { } && Application.Current?.Modal == false) { lock (Application.TopLevels) diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index c1b0ec7331..91555ca917 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -527,7 +527,7 @@ public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_ [Fact] public void MoveToOverlappedChild_Throw_NullReferenceException_Passing_Null_Parameter () { - Assert.Throws (delegate { ApplicationOverlapped.MoveToOverlappedChild (null); }); + Assert.Throws (delegate { ApplicationOverlapped.MoveToOverlappedChild (null); }); } [Fact] From cbecae5d47aea31611137cd060679e6445b6830c Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 15:48:41 -0600 Subject: [PATCH 88/96] Moved Overlapped stuff to ApplicationOverlap static class. Fixed nullable warnings. --- Terminal.Gui/Application/Application.Navigation.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Terminal.Gui/Application/Application.Navigation.cs b/Terminal.Gui/Application/Application.Navigation.cs index 44bc6e99ad..fde03913b2 100644 --- a/Terminal.Gui/Application/Application.Navigation.cs +++ b/Terminal.Gui/Application/Application.Navigation.cs @@ -1,6 +1,9 @@ #nullable enable namespace Terminal.Gui; +/// +/// Helper class for navigation. +/// internal static class ApplicationNavigation { /// From 331d9726d7210578dfb195f1c4d5886288dc42cf Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 16:18:20 -0600 Subject: [PATCH 89/96] nullable enable TopLevel --- .../Application/Application.Overlapped.cs | 21 +++++- Terminal.Gui/Application/Application.Run.cs | 2 +- Terminal.Gui/View/Layout/ViewLayout.cs | 2 +- Terminal.Gui/Views/Toplevel.cs | 73 +++++++++++-------- Terminal.Gui/Views/ToplevelOverlapped.cs | 3 - UICatalog/UICatalog.cs | 6 +- UnitTests/Views/OverlappedTests.cs | 16 ++-- UnitTests/Views/ToplevelTests.cs | 2 +- 8 files changed, 72 insertions(+), 53 deletions(-) diff --git a/Terminal.Gui/Application/Application.Overlapped.cs b/Terminal.Gui/Application/Application.Overlapped.cs index cf9212d80f..8ae6457173 100644 --- a/Terminal.Gui/Application/Application.Overlapped.cs +++ b/Terminal.Gui/Application/Application.Overlapped.cs @@ -1,4 +1,6 @@ #nullable enable +using System.Reflection; + namespace Terminal.Gui; /// @@ -6,6 +8,17 @@ namespace Terminal.Gui; /// public static class ApplicationOverlapped { + + /// + /// Gets or sets if is in overlapped mode within a Toplevel container. + /// + /// + /// + public static bool IsOverlapped (Toplevel? top) + { + return ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != top && !top!.Modal; + } + /// /// Gets the list of the Overlapped children which are not modal from the /// . @@ -99,7 +112,7 @@ public static void BringOverlappedTopToFront () } /// - /// Move to the next Overlapped child from the and set it as the if + /// Move to the next Overlapped child from the and set it as the if /// it is not already. /// /// @@ -262,7 +275,7 @@ internal static bool SetCurrentOverlappedAsTop () } /// - /// Given , returns the first Superview up the chain that is . + /// Given , returns the first Superview up the chain that is . /// internal static View? FindTopFromView (View? view) { @@ -284,7 +297,7 @@ internal static bool SetCurrentOverlappedAsTop () } /// - /// If the is not the then is moved to the top of + /// If the is not the then is moved to the top of /// the Toplevel stack and made Current. /// /// @@ -361,7 +374,7 @@ internal static bool MoveCurrent (Toplevel top) { lock (Application.TopLevels) { - Application.TopLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); + Application.TopLevels.MoveTo (top!, 0, new ToplevelEqualityComparer ()); Application.Current = top; } } diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 3e088c348d..57097cbda2 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -579,7 +579,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) { ApplicationOverlapped.OverlappedTop?.OnDeactivate (state.Toplevel); state.Toplevel = Current; - ApplicationOverlapped.OverlappedTop?.OnActivate (state.Toplevel); + ApplicationOverlapped.OverlappedTop?.OnActivate (state.Toplevel!); Top!.SetSubViewNeedsDisplay (); Refresh (); } diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 4e2531745b..72a9bf14c3 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -404,7 +404,7 @@ public Dim? Width int targetY, out int nx, out int ny, - out StatusBar statusBar + out StatusBar? statusBar ) { int maxDimension; diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index d13ae07663..26fe724c80 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -1,3 +1,4 @@ +#nullable enable namespace Terminal.Gui; /// @@ -60,7 +61,7 @@ public Toplevel () /// public bool Modal { get; set; } - private void Toplevel_MouseClick (object sender, MouseEventEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; } + private void Toplevel_MouseClick (object? sender, MouseEventEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; } #endregion @@ -68,11 +69,11 @@ public Toplevel () // TODO: Deprecate - Any view can host a menubar in v2 /// Gets or sets the menu for this Toplevel. - public virtual MenuBar MenuBar { get; set; } + public MenuBar? MenuBar { get; set; } // TODO: Deprecate - Any view can host a statusbar in v2 /// Gets or sets the status bar for this Toplevel. - public virtual StatusBar StatusBar { get; set; } + public StatusBar? StatusBar { get; set; } /// public override View Add (View view) @@ -141,22 +142,22 @@ internal void RemoveMenuStatusBar (View view) /// Invoked when the last child of the Toplevel is closed from by /// . /// - public event EventHandler AllChildClosed; + public event EventHandler? AllChildClosed; // TODO: Overlapped - Rename to *Subviews* - Move to View? /// /// Invoked when a child of the Toplevel is closed by /// . /// - public event EventHandler ChildClosed; + public event EventHandler? ChildClosed; // TODO: Overlapped - Rename to *Subviews* - Move to View? /// Invoked when a child Toplevel's has been loaded. - public event EventHandler ChildLoaded; + public event EventHandler? ChildLoaded; // TODO: Overlapped - Rename to *Subviews* - Move to View? /// Invoked when a cjhild Toplevel's has been unloaded. - public event EventHandler ChildUnloaded; + public event EventHandler? ChildUnloaded; #endregion @@ -176,26 +177,26 @@ internal void RemoveMenuStatusBar (View view) // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Activating/Activate /// Invoked when the Toplevel becomes the Toplevel. - public event EventHandler Activate; + public event EventHandler? Activate; // TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Deactivating/Deactivate? /// Invoked when the Toplevel ceases to be the Toplevel. - public event EventHandler Deactivate; + public event EventHandler? Deactivate; /// Invoked when the Toplevel's is closed by . - public event EventHandler Closed; + public event EventHandler? Closed; /// /// Invoked when the Toplevel's is being closed by /// . /// - public event EventHandler Closing; + public event EventHandler? Closing; /// /// Invoked when the has begun to be loaded. A Loaded event handler /// is a good place to finalize initialization before calling . /// - public event EventHandler Loaded; + public event EventHandler? Loaded; /// /// Called from before the redraws for the first @@ -209,8 +210,9 @@ public virtual void OnLoaded () { IsLoaded = true; - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) + foreach (var view in Subviews.Where (v => v is Toplevel)) { + var tl = (Toplevel)view; tl.OnLoaded (); } @@ -225,7 +227,7 @@ public virtual void OnLoaded () /// on this . /// /// - public event EventHandler Ready; + public event EventHandler? Ready; /// /// Stops and closes this . If this Toplevel is the top-most Toplevel, @@ -288,7 +290,7 @@ public virtual void RequestStop () /// Invoked when the Toplevel has been unloaded. A Unloaded event handler is a good place /// to dispose objects after calling . /// - public event EventHandler Unloaded; + public event EventHandler? Unloaded; internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (this, new (deactivated)); } @@ -331,8 +333,9 @@ internal virtual bool OnClosing (ToplevelClosingEventArgs ev) /// internal virtual void OnReady () { - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) + foreach (var view in Subviews.Where (v => v is Toplevel)) { + var tl = (Toplevel)view; tl.OnReady (); } @@ -342,8 +345,9 @@ internal virtual void OnReady () /// Called from before the is disposed. internal virtual void OnUnloaded () { - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) + foreach (var view in Subviews.Where (v => v is Toplevel)) { + var tl = (Toplevel)view; tl.OnUnloaded (); } @@ -411,7 +415,7 @@ public override void OnDrawContent (Rectangle viewport) /// public override bool OnLeave (View view) { return MostFocused?.OnLeave (view) ?? base.OnLeave (view); } - + #endregion #region Size / Position Management @@ -458,15 +462,20 @@ public override void OnDrawContent (Rectangle viewport) /// implementation of specific positions for inherited views. /// /// The Toplevel to adjust. - public virtual void PositionToplevel (Toplevel top) + public virtual void PositionToplevel (Toplevel? top) { - View superView = GetLocationEnsuringFullVisibility ( + if (top is null) + { + return; + } + + View? superView = GetLocationEnsuringFullVisibility ( top, top.Frame.X, top.Frame.Y, out int nx, out int ny, - out StatusBar sb + out StatusBar? sb ); if (superView is null) @@ -482,25 +491,25 @@ out StatusBar sb maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right; } - if ((superView != top || top?.SuperView is { } || (top != Application.Top && top.Modal) || (top?.SuperView is null && top.IsOverlapped)) - && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y)) + if ((superView != top || top?.SuperView is { } || (top != Application.Top && top!.Modal) || (top?.SuperView is null && ApplicationOverlapped.IsOverlapped (top))) + && (top!.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y)) { - if ((top.X is null || top.X is PosAbsolute) && top.Frame.X != nx) + if (top?.X is null or PosAbsolute && top?.Frame.X != nx) { - top.X = nx; + top!.X = nx; layoutSubviews = true; } - if ((top.Y is null || top.Y is PosAbsolute) && top.Frame.Y != ny) + if (top?.Y is null or PosAbsolute && top?.Frame.Y != ny) { - top.Y = ny; + top!.Y = ny; layoutSubviews = true; } } // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. if (sb != null - && !top.Subviews.Contains (sb) + && !top!.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) && top.Height is DimFill && -top.Height.GetAnchor (0) < 1) @@ -521,7 +530,7 @@ out StatusBar sb } /// Invoked when the terminal has been resized. The new of the terminal is provided. - public event EventHandler SizeChanging; + public event EventHandler? SizeChanging; private bool OutsideTopFrame (Toplevel top) { @@ -560,7 +569,7 @@ public class ToplevelEqualityComparer : IEqualityComparer /// The first object of type to compare. /// The second object of type to compare. /// if the specified objects are equal; otherwise, . - public bool Equals (Toplevel x, Toplevel y) + public bool Equals (Toplevel? x, Toplevel? y) { if (y is null && x is null) { @@ -623,7 +632,7 @@ public sealed class ToplevelComparer : IComparer /// equals .Greater than zero is greater than /// . /// - public int Compare (Toplevel x, Toplevel y) + public int Compare (Toplevel? x, Toplevel? y) { if (ReferenceEquals (x, y)) { @@ -640,6 +649,6 @@ public int Compare (Toplevel x, Toplevel y) return 1; } - return string.Compare (x.Id, y.Id); + return string.CompareOrdinal (x.Id, y.Id); } } diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs index 06dac36941..28513c4ced 100644 --- a/Terminal.Gui/Views/ToplevelOverlapped.cs +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -2,9 +2,6 @@ public partial class Toplevel { - /// Gets or sets if this Toplevel is in overlapped mode within a Toplevel container. - public bool IsOverlapped => ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != this && !Modal; - /// Gets or sets if this Toplevel is a container for overlapped children. public bool IsOverlappedContainer { get; set; } } diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index a9014d0171..86352717a1 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -658,7 +658,7 @@ public void ConfigChanged () ColorScheme = Colors.ColorSchemes [_topLevelColorScheme]; - MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey; + MenuBar!.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey; if (StatusBar is { }) { @@ -942,7 +942,7 @@ private MenuItem [] CreateDisabledEnabledMenuBorder () { MiIsMenuBorderDisabled.Checked = (bool)!MiIsMenuBorderDisabled.Checked!; - MenuBar.MenusBorderStyle = !(bool)MiIsMenuBorderDisabled.Checked + MenuBar!.MenusBorderStyle = !(bool)MiIsMenuBorderDisabled.Checked ? LineStyle.Single : LineStyle.None; }; @@ -985,7 +985,7 @@ private MenuItem [] CreateDisabledEnableUseSubMenusSingleFrame () MiUseSubMenusSingleFrame.Action += () => { MiUseSubMenusSingleFrame.Checked = (bool)!MiUseSubMenusSingleFrame.Checked!; - MenuBar.UseSubMenusSingleFrame = (bool)MiUseSubMenusSingleFrame.Checked; + MenuBar!.UseSubMenusSingleFrame = (bool)MiUseSubMenusSingleFrame.Checked; }; menuItems.Add (MiUseSubMenusSingleFrame); diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index 91555ca917..80c8e18c2a 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -231,11 +231,11 @@ public void IsOverlappedChild_Testing () Application.Iteration += (s, a) => { - Assert.False (overlapped.IsOverlapped); - Assert.True (c1.IsOverlapped); - Assert.True (c2.IsOverlapped); - Assert.True (c3.IsOverlapped); - Assert.False (d.IsOverlapped); + Assert.False (ApplicationOverlapped.IsOverlapped(overlapped)); + Assert.True (ApplicationOverlapped.IsOverlapped(c1)); + Assert.True (ApplicationOverlapped.IsOverlapped(c2)); + Assert.True (ApplicationOverlapped.IsOverlapped(c3)); + Assert.False (ApplicationOverlapped.IsOverlapped(d)); overlapped.RequestStop (); }; @@ -1068,11 +1068,11 @@ public void KeyBindings_Command_With_OverlappedTop () Assert.False (top.IsCurrentTop); Assert.Equal (win1, Application.Current); Assert.True (win1.IsCurrentTop); - Assert.True (win1.IsOverlapped); + Assert.True (ApplicationOverlapped.IsOverlapped(win1)); Assert.Null (top.Focused); Assert.Null (top.MostFocused); Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (win1.IsOverlapped); + Assert.True (ApplicationOverlapped.IsOverlapped(win1)); Assert.Single (ApplicationOverlapped.OverlappedChildren!); Application.Begin (win2); Assert.Equal (new (0, 0, 40, 25), win2.Frame); @@ -1080,7 +1080,7 @@ public void KeyBindings_Command_With_OverlappedTop () Assert.False (top.IsCurrentTop); Assert.Equal (win2, Application.Current); Assert.True (win2.IsCurrentTop); - Assert.True (win2.IsOverlapped); + Assert.True (ApplicationOverlapped.IsOverlapped(win2)); Assert.Null (top.Focused); Assert.Null (top.MostFocused); Assert.Equal (tf1W2, win2.MostFocused); diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 8688c5d4ae..9af790add0 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -17,7 +17,7 @@ public void Constructor_Default () Assert.Null (top.MenuBar); Assert.Null (top.StatusBar); Assert.False (top.IsOverlappedContainer); - Assert.False (top.IsOverlapped); + Assert.False (ApplicationOverlapped.IsOverlapped(top)); } [Fact] From ca4d10b5093dc406f1b07c648e27f17086b2cee0 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 19:22:59 -0600 Subject: [PATCH 90/96] WIP: Modify Focus logic to properly deal with ViewArrangement.Overlapped. --- Terminal.Gui/View/Adornment/Border.cs | 9 +- Terminal.Gui/View/ViewArrangement.cs | 13 +- Terminal.Gui/View/ViewSubViews.cs | 62 ++++-- UICatalog/Scenarios/ViewExperiments.cs | 251 ++++++------------------- 4 files changed, 129 insertions(+), 206 deletions(-) diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 41fd02f9d2..241b795c55 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -279,10 +279,11 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) return true; } - if (!Parent.CanFocus) - { - return false; - } + // BUGBUG: Shouldn't non-focusable views be draggable?? + //if (!Parent.CanFocus) + //{ + // return false; + //} if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable)) { diff --git a/Terminal.Gui/View/ViewArrangement.cs b/Terminal.Gui/View/ViewArrangement.cs index b24529caf3..df13c4762d 100644 --- a/Terminal.Gui/View/ViewArrangement.cs +++ b/Terminal.Gui/View/ViewArrangement.cs @@ -53,7 +53,18 @@ public enum ViewArrangement /// /// If is also set, the top will not be resizable. /// - Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable + Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable, + + /// + /// The view overlap other views. + /// + /// + /// + /// When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to the next/prev view in the next/prev Tabindex). + /// Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views. + /// + /// + Overlapped = 32 } public partial class View { diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 5da8dd76ae..79cca431ea 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -672,7 +672,7 @@ public void EnsureFocus () /// /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself. /// - public void FocusFirst () + public void FocusFirst (bool overlapped = false) { if (!CanBeVisible (this)) { @@ -686,7 +686,7 @@ public void FocusFirst () return; } - foreach (View view in _tabIndexes) + foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped))) { if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { @@ -700,7 +700,7 @@ public void FocusFirst () /// /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself. /// - public void FocusLast () + public void FocusLast (bool overlapped = false) { if (!CanBeVisible (this)) { @@ -714,15 +714,11 @@ public void FocusLast () return; } - for (int i = _tabIndexes.Count; i > 0;) + foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ()) { - i--; - - View v = _tabIndexes [i]; - - if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) + if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { - SetFocus (v); + SetFocus (view); return; } @@ -777,6 +773,18 @@ public bool FocusPrev () { Focused.SetHasFocus (false, w); + // If the focused view is overlapped don't focus on the next if it's not overlapped. + if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + return false; + } + + // If the focused view is not overlapped and the next is, skip it + if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + continue; + } + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { w.FocusLast (); @@ -788,9 +796,19 @@ public bool FocusPrev () } } + // There's no prev view in tab indexes. if (Focused is { }) { + // Leave Focused Focused.SetHasFocus (false, this); + + if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + FocusLast (true); + return true; + } + + // Signal to caller no next view was found Focused = null; } @@ -798,7 +816,7 @@ public bool FocusPrev () } /// - /// Focuses the previous view in . If there is no previous view, the focus is set to the view itself. + /// Focuses the next view in . If there is no next view, the focus is set to the view itself. /// /// if next was focused, otherwise. public bool FocusNext () @@ -844,6 +862,18 @@ public bool FocusNext () { Focused.SetHasFocus (false, w); + // If the focused view is overlapped don't focus on the next if it's not overlapped. + if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + return false; + } + + // If the focused view is not overlapped and the next is, skip it + if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + continue; + } + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { w.FocusFirst (); @@ -855,9 +885,19 @@ public bool FocusNext () } } + // There's no next view in tab indexes. if (Focused is { }) { + // Leave Focused Focused.SetHasFocus (false, this); + + if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + FocusFirst (true); + return true; + } + + // Signal to caller no next view was found Focused = null; } diff --git a/UICatalog/Scenarios/ViewExperiments.cs b/UICatalog/Scenarios/ViewExperiments.cs index 5d08f0aa6c..263a507d95 100644 --- a/UICatalog/Scenarios/ViewExperiments.cs +++ b/UICatalog/Scenarios/ViewExperiments.cs @@ -18,233 +18,104 @@ public override void Main () Title = GetQuitKeyAndName () }; - var containerLabel = new Label + var view = new View { - X = 0, - Y = 0, + X = 2, + Y = 2, + Height = Dim.Auto (), + Width = Dim.Auto (), + Title = "View1", + ColorScheme = Colors.ColorSchemes ["Base"], + Id = "View1", + CanFocus = true, // Can't drag without this? BUGBUG + Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped + }; - Width = Dim.Fill (), - Height = 3 + Button button = new () + { + Title = "Button_1", }; - app.Add (containerLabel); + view.Add (button); - var view = new View + button = new () { - X = 2, - Y = Pos.Bottom (containerLabel), - Height = Dim.Fill (2), - Width = Dim.Fill (2), - Title = "View with 2xMargin, 2xBorder, & 2xPadding", - ColorScheme = Colors.ColorSchemes ["Base"], - Id = "DaView" + Y = Pos.Bottom (button), + Title = "Button_2", }; + view.Add (button); //app.Add (view); - view.Margin.Thickness = new (2, 2, 2, 2); + view.Margin.Thickness = new (0); view.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; view.Margin.Data = "Margin"; - view.Border.Thickness = new (3); - view.Border.LineStyle = LineStyle.Single; + view.Border.Thickness = new (1); + view.Border.LineStyle = LineStyle.Double; view.Border.ColorScheme = view.ColorScheme; view.Border.Data = "Border"; - view.Padding.Thickness = new (2); + view.Padding.Thickness = new (0); view.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; view.Padding.Data = "Padding"; - var window1 = new Window + var view2 = new View { - X = 2, - Y = 3, - Height = 7, - Width = 17, - Title = "Window 1", - Text = "Window #2", - TextAlignment = Alignment.Center - }; - - window1.Margin.Thickness = new (0); - window1.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; - window1.Margin.Data = "Margin"; - window1.Border.Thickness = new (1); - window1.Border.LineStyle = LineStyle.Single; - window1.Border.ColorScheme = view.ColorScheme; - window1.Border.Data = "Border"; - window1.Padding.Thickness = new (0); - window1.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; - window1.Padding.Data = "Padding"; - - view.Add (window1); - - var window2 = new Window - { - X = Pos.Right (window1) + 1, - Y = 3, - Height = 5, - Width = 37, - Title = "Window2", - Text = "Window #2 (Right(window1)+1", - TextAlignment = Alignment.Center - }; - - //view3.InitializeFrames (); - window2.Margin.Thickness = new (1, 1, 0, 0); - window2.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; - window2.Margin.Data = "Margin"; - window2.Border.Thickness = new (1, 1, 1, 1); - window2.Border.LineStyle = LineStyle.Single; - window2.Border.ColorScheme = view.ColorScheme; - window2.Border.Data = "Border"; - window2.Padding.Thickness = new (1, 1, 0, 0); - window2.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; - window2.Padding.Data = "Padding"; - - view.Add (window2); - - var view4 = new View - { - X = Pos.Right (window2) + 1, - Y = 3, - Height = 5, - Width = 37, - Title = "View4", - Text = "View #4 (Right(window2)+1", - TextAlignment = Alignment.Center - }; - - //view4.InitializeFrames (); - view4.Margin.Thickness = new (0, 0, 1, 1); - view4.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; - view4.Margin.Data = "Margin"; - view4.Border.Thickness = new (1, 1, 1, 1); - view4.Border.LineStyle = LineStyle.Single; - view4.Border.ColorScheme = view.ColorScheme; - view4.Border.Data = "Border"; - view4.Padding.Thickness = new (0, 0, 1, 1); - view4.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; - view4.Padding.Data = "Padding"; - - view.Add (view4); - - var view5 = new View - { - X = Pos.Right (view4) + 1, - Y = 3, - Height = Dim.Fill (2), - Width = Dim.Fill (), - Title = "View5", - Text = "View #5 (Right(view4)+1 Fill", - TextAlignment = Alignment.Center - }; - - //view5.InitializeFrames (); - view5.Margin.Thickness = new (0, 0, 0, 0); - view5.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; - view5.Margin.Data = "Margin"; - view5.Border.Thickness = new (1, 1, 1, 1); - view5.Border.LineStyle = LineStyle.Single; - view5.Border.ColorScheme = view.ColorScheme; - view5.Border.Data = "Border"; - view5.Padding.Thickness = new (0, 0, 0, 0); - view5.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; - view5.Padding.Data = "Padding"; - - view.Add (view5); - - var label = new Label { Text = "AutoSize true; 1;1:", X = 1, Y = 1 }; - view.Add (label); - - var edit = new TextField - { - Text = "Right (label)", - X = Pos.Right (label), - Y = 1, - Width = 15, - Height = 1 + X = Pos.Right (view), + Y = Pos.Bottom (view), + Height = Dim.Auto (), + Width = Dim.Auto (), + Title = "View2", + ColorScheme = Colors.ColorSchemes ["Base"], + Id = "View2", + CanFocus = true, // Can't drag without this? BUGBUG + Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped }; - view.Add (edit); - edit = new() - { - Text = "Right (edit) + 1", - X = Pos.Right (edit) + 1, - Y = 1, - Width = 20, - Height = 1 - }; - view.Add (edit); - var label50 = new View + button = new () { - Title = "Border Inherit Demo", - Text = "Center();50%", - X = Pos.Center (), - Y = Pos.Percent (50), - Width = 30, - TextAlignment = Alignment.Center + Title = "Button_3", }; - label50.Border.Thickness = new (1, 3, 1, 1); - label50.Height = 5; - view.Add (label50); + view2.Add (button); - edit = new() + button = new () { - Text = "0 + Percent(50);70%", - X = 0 + Pos.Percent (50), - Y = Pos.Percent (70), - Width = 30, - Height = 1 + Y = Pos.Bottom (button), + Title = "Button_4", }; - view.Add (edit); - - edit = new() { Text = "AnchorEnd ();AnchorEnd ()", X = Pos.AnchorEnd (), Y = Pos.AnchorEnd (), Width = 30, Height = 1 }; - view.Add (edit); - - edit = new() + view2.Add (button); + + view2.Add (button); + view2.Margin.Thickness = new (0); + view2.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; + view2.Margin.Data = "Margin"; + view2.Border.Thickness = new (1); + view2.Border.LineStyle = LineStyle.Double; + view2.Border.ColorScheme = view2.ColorScheme; + view2.Border.Data = "Border"; + view2.Padding.Thickness = new (0); + view2.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; + view2.Padding.Data = "Padding"; + + button = new () { - Text = "Left;AnchorEnd (2)", - X = 0, - Y = Pos.AnchorEnd (2), - Width = 30, - Height = 1 + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + Title = "Button_5", }; - view.Add (edit); - - view.LayoutComplete += (s, e) => - { - containerLabel.Text = - $"Container.Frame: { - app.Frame - } .Bounds: { - app.Viewport - }\nView.Frame: { - view.Frame - } .Viewport: { - view.Viewport - } .viewportOffset: { - view.GetViewportOffsetFromFrame () - }\n .Padding.Frame: { - view.Padding.Frame - } .Padding.Viewport: { - view.Padding.Viewport - }"; - }; - - view.X = Pos.Center (); var editor = new AdornmentsEditor { X = 0, - Y = Pos.Bottom (containerLabel), + Y = 0, AutoSelectViewToEdit = true }; app.Add (editor); - view.X = 36; + view.X = 34; view.Y = 4; - view.Width = Dim.Fill (); - view.Height = Dim.Fill (); app.Add (view); + app.Add (view2); + app.Add (button); Application.Run (app); app.Dispose (); From 3a40851848ef741076f80a5de2dafcabdbf57d37 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 24 Jul 2024 19:45:25 -0600 Subject: [PATCH 91/96] WIP: More - Modify Focus logic to properly deal with ViewArrangement.Overlapped. --- .../Application/Application.Navigation.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Application/Application.Navigation.cs b/Terminal.Gui/Application/Application.Navigation.cs index fde03913b2..6f80320b88 100644 --- a/Terminal.Gui/Application/Application.Navigation.cs +++ b/Terminal.Gui/Application/Application.Navigation.cs @@ -1,4 +1,6 @@ #nullable enable +using System.Security.Cryptography; + namespace Terminal.Gui; /// @@ -78,8 +80,10 @@ internal static void FocusNearestView (IEnumerable? viewsInTabIndexes, Vie idx++; } } + /// - /// Moves the focus to + /// Moves the focus to the next view. Honors and will only move to the next subview + /// if the current and next subviews are not overlapped. /// internal static void MoveNextView () { @@ -101,19 +105,40 @@ internal static void MoveNextView () } } + /// + /// Moves the focus to the next subview or the next subview that has set. + /// internal static void MoveNextViewOrTop () { if (ApplicationOverlapped.OverlappedTop is null) { Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; - top!.FocusNext (); - if (top.Focused is null) + if (!Application.Current.FocusNext ()) { - top.FocusNext (); + Application.Current.FocusNext (); } - top.SetNeedsDisplay (); + if (top != Application.Current.Focused && top != Application.Current.Focused?.Focused) + { + top?.SetNeedsDisplay (); + Application.Current.Focused?.SetNeedsDisplay (); + } + else + { + FocusNearestView (Application.Current.SuperView?.TabIndexes, View.NavigationDirection.Forward); + } + + + + //top!.FocusNext (); + + //if (top.Focused is null) + //{ + // top.FocusNext (); + //} + + //top.SetNeedsDisplay (); ApplicationOverlapped.BringOverlappedTopToFront (); } else @@ -122,6 +147,10 @@ internal static void MoveNextViewOrTop () } } + /// + /// Moves the focus to the next view. Honors and will only move to the next subview + /// if the current and next subviews are not overlapped. + /// internal static void MovePreviousView () { View? old = GetDeepestFocusedSubview (Application.Current!.Focused); From d874f5628295f2811f59ba1321be11f5bb385af5 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 25 Jul 2024 10:40:28 -0600 Subject: [PATCH 92/96] Reorganized View source files to get my head straight --- .../Application/Application.Keyboard.cs | 30 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 42 - .../ConsoleDrivers/CursorVisibility.cs | 44 + Terminal.Gui/View/DrawEventArgs.cs | 29 + Terminal.Gui/View/Layout/LayoutEventArgs.cs | 12 + .../View/Navigation/FocusEventArgs.cs | 27 + .../{ViewAdornments.cs => View.Adornments.cs} | 2 +- Terminal.Gui/View/View.Arrangement.cs | 15 + .../View/{ViewContent.cs => View.Content.cs} | 0 Terminal.Gui/View/View.Cursor.cs | 35 + ...ViewDiagnostics.cs => View.Diagnostics.cs} | 0 .../View/{ViewDrawing.cs => View.Drawing.cs} | 2 +- Terminal.Gui/View/View.Hierarchy.cs | 320 ++++++ .../{ViewKeyboard.cs => View.Keyboard.cs} | 115 +-- .../{Layout/ViewLayout.cs => View.Layout.cs} | 2 +- .../View/{ViewMouse.cs => View.Mouse.cs} | 2 +- Terminal.Gui/View/View.Navigation.cs | 813 +++++++++++++++ .../View/{ViewText.cs => View.Text.cs} | 2 +- Terminal.Gui/View/ViewArrangement.cs | 30 +- Terminal.Gui/View/ViewEventArgs.cs | 67 +- Terminal.Gui/View/ViewSubViews.cs | 948 ------------------ UICatalog/Scenarios/ViewExperiments.cs | 25 +- 22 files changed, 1318 insertions(+), 1244 deletions(-) create mode 100644 Terminal.Gui/ConsoleDrivers/CursorVisibility.cs create mode 100644 Terminal.Gui/View/DrawEventArgs.cs create mode 100644 Terminal.Gui/View/Layout/LayoutEventArgs.cs create mode 100644 Terminal.Gui/View/Navigation/FocusEventArgs.cs rename Terminal.Gui/View/{ViewAdornments.cs => View.Adornments.cs} (99%) create mode 100644 Terminal.Gui/View/View.Arrangement.cs rename Terminal.Gui/View/{ViewContent.cs => View.Content.cs} (100%) create mode 100644 Terminal.Gui/View/View.Cursor.cs rename Terminal.Gui/View/{ViewDiagnostics.cs => View.Diagnostics.cs} (100%) rename Terminal.Gui/View/{ViewDrawing.cs => View.Drawing.cs} (99%) create mode 100644 Terminal.Gui/View/View.Hierarchy.cs rename Terminal.Gui/View/{ViewKeyboard.cs => View.Keyboard.cs} (91%) rename Terminal.Gui/View/{Layout/ViewLayout.cs => View.Layout.cs} (99%) rename Terminal.Gui/View/{ViewMouse.cs => View.Mouse.cs} (99%) create mode 100644 Terminal.Gui/View/View.Navigation.cs rename Terminal.Gui/View/{ViewText.cs => View.Text.cs} (99%) delete mode 100644 Terminal.Gui/View/ViewSubViews.cs diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index d26dcd4327..969d4c31ee 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -267,34 +267,6 @@ private static void AddCommand (Command command, Func f) CommandImplementations [command] = ctx => f (); } - ///// - ///// The key bindings. - ///// - //private static readonly Dictionary> _keyBindings = new (); - - ///// - ///// Gets the list of key bindings. - ///// - //public static Dictionary> GetKeyBindings () { return _keyBindings; } - - ///// - ///// Adds an scoped key binding. - ///// - ///// - ///// This is an internal method used by the class to add Application key bindings. - ///// - ///// The key being bound. - ///// The view that is bound to the key. If , will be used. - //internal static void AddKeyBinding (Key key, View? view) - //{ - // if (!_keyBindings.ContainsKey (key)) - // { - // _keyBindings [key] = []; - // } - - // _keyBindings [key].Add (view); - //} - internal static void AddApplicationKeyBindings () { // Things this view knows how to do @@ -326,7 +298,7 @@ internal static void AddApplicationKeyBindings () ); AddCommand ( - Command.NextView, + Command.NextView, () => { // TODO: Move this method to Application.Navigation.cs diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index e99521d1e2..dc5c785a09 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -603,48 +603,6 @@ public virtual Attribute MakeColor (in Color foreground, in Color background) #endregion } -/// Terminal Cursor Visibility settings. -/// -/// Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under -/// Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the -/// CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize -/// parameter value to be used under Windows -/// -public enum CursorVisibility -{ - /// Cursor caret has default - /// - /// Works under Xterm-like terminal otherwise this is equivalent to . This default directly - /// depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking. - /// - Default = 0x00010119, - - /// Cursor caret is hidden - Invisible = 0x03000019, - - /// Cursor caret is normally shown as a blinking underline bar _ - Underline = 0x03010119, - - /// Cursor caret is normally shown as a underline bar _ - /// Under Windows, this is equivalent to - UnderlineFix = 0x04010119, - - /// Cursor caret is displayed a blinking vertical bar | - /// Works under Xterm-like terminal otherwise this is equivalent to - Vertical = 0x05010119, - - /// Cursor caret is displayed a blinking vertical bar | - /// Works under Xterm-like terminal otherwise this is equivalent to - VerticalFix = 0x06010119, - - /// Cursor caret is displayed as a blinking block ▉ - Box = 0x01020164, - - /// Cursor caret is displayed a block ▉ - /// Works under Xterm-like terminal otherwise this is equivalent to - BoxFix = 0x02020164 -} - /// /// The enumeration encodes key information from s and provides a /// consistent way for application code to specify keys and receive key events. diff --git a/Terminal.Gui/ConsoleDrivers/CursorVisibility.cs b/Terminal.Gui/ConsoleDrivers/CursorVisibility.cs new file mode 100644 index 0000000000..b96d31fd43 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/CursorVisibility.cs @@ -0,0 +1,44 @@ +#nullable enable +namespace Terminal.Gui; + +/// Terminal Cursor Visibility settings. +/// +/// Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under +/// Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the +/// CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize +/// parameter value to be used under Windows +/// +public enum CursorVisibility +{ + /// Cursor caret has default + /// + /// Works under Xterm-like terminal otherwise this is equivalent to . This default directly + /// depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking. + /// + Default = 0x00010119, + + /// Cursor caret is hidden + Invisible = 0x03000019, + + /// Cursor caret is normally shown as a blinking underline bar _ + Underline = 0x03010119, + + /// Cursor caret is normally shown as a underline bar _ + /// Under Windows, this is equivalent to + UnderlineFix = 0x04010119, + + /// Cursor caret is displayed a blinking vertical bar | + /// Works under Xterm-like terminal otherwise this is equivalent to + Vertical = 0x05010119, + + /// Cursor caret is displayed a blinking vertical bar | + /// Works under Xterm-like terminal otherwise this is equivalent to + VerticalFix = 0x06010119, + + /// Cursor caret is displayed as a blinking block ▉ + Box = 0x01020164, + + /// Cursor caret is displayed a block ▉ + /// Works under Xterm-like terminal otherwise this is equivalent to + BoxFix = 0x02020164 +} diff --git a/Terminal.Gui/View/DrawEventArgs.cs b/Terminal.Gui/View/DrawEventArgs.cs new file mode 100644 index 0000000000..32c07c711d --- /dev/null +++ b/Terminal.Gui/View/DrawEventArgs.cs @@ -0,0 +1,29 @@ +namespace Terminal.Gui; + +/// Event args for draw events +public class DrawEventArgs : EventArgs +{ + /// Creates a new instance of the class. + /// + /// The Content-relative rectangle describing the new visible viewport into the + /// . + /// + /// + /// The Content-relative rectangle describing the old visible viewport into the + /// . + /// + public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport) + { + NewViewport = newViewport; + OldViewport = oldViewport; + } + + /// If set to true, the draw operation will be canceled, if applicable. + public bool Cancel { get; set; } + + /// Gets the Content-relative rectangle describing the old visible viewport into the . + public Rectangle OldViewport { get; } + + /// Gets the Content-relative rectangle describing the currently visible viewport into the . + public Rectangle NewViewport { get; } +} diff --git a/Terminal.Gui/View/Layout/LayoutEventArgs.cs b/Terminal.Gui/View/Layout/LayoutEventArgs.cs new file mode 100644 index 0000000000..dac959af07 --- /dev/null +++ b/Terminal.Gui/View/Layout/LayoutEventArgs.cs @@ -0,0 +1,12 @@ +namespace Terminal.Gui; + +/// Event arguments for the event. +public class LayoutEventArgs : EventArgs +{ + /// Creates a new instance of the class. + /// The view that the event is about. + public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; } + + /// The viewport of the before it was laid out. + public Size OldContentSize { get; set; } +} diff --git a/Terminal.Gui/View/Navigation/FocusEventArgs.cs b/Terminal.Gui/View/Navigation/FocusEventArgs.cs new file mode 100644 index 0000000000..6d8d282673 --- /dev/null +++ b/Terminal.Gui/View/Navigation/FocusEventArgs.cs @@ -0,0 +1,27 @@ +namespace Terminal.Gui; + +/// Defines the event arguments for +public class FocusEventArgs : EventArgs +{ + /// Constructs. + /// The view that is losing focus. + /// The view that is gaining focus. + public FocusEventArgs (View leaving, View entering) { + Leaving = leaving; + Entering = entering; + } + + /// + /// Indicates if the current focus event has already been processed and the driver should stop notifying any other + /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the + /// subscriber method. + /// + public bool Handled { get; set; } + + /// Indicates the view that is losing focus. + public View Leaving { get; set; } + + /// Indicates the view that is gaining focus. + public View Entering { get; set; } + +} diff --git a/Terminal.Gui/View/ViewAdornments.cs b/Terminal.Gui/View/View.Adornments.cs similarity index 99% rename from Terminal.Gui/View/ViewAdornments.cs rename to Terminal.Gui/View/View.Adornments.cs index accb15aba9..2d179079e8 100644 --- a/Terminal.Gui/View/ViewAdornments.cs +++ b/Terminal.Gui/View/View.Adornments.cs @@ -3,7 +3,7 @@ namespace Terminal.Gui; -public partial class View +public partial class View // Adornments { /// /// Initializes the Adornments of the View. Called by the constructor. diff --git a/Terminal.Gui/View/View.Arrangement.cs b/Terminal.Gui/View/View.Arrangement.cs new file mode 100644 index 0000000000..0fea93324b --- /dev/null +++ b/Terminal.Gui/View/View.Arrangement.cs @@ -0,0 +1,15 @@ +namespace Terminal.Gui; + +public partial class View +{ + /// + /// Gets or sets the user actions that are enabled for the view within it's . + /// + /// + /// + /// Sizing or moving a view is only possible if the is part of a and + /// the relevant position and dimensions of the are independent of other SubViews + /// + /// + public ViewArrangement Arrangement { get; set; } +} diff --git a/Terminal.Gui/View/ViewContent.cs b/Terminal.Gui/View/View.Content.cs similarity index 100% rename from Terminal.Gui/View/ViewContent.cs rename to Terminal.Gui/View/View.Content.cs diff --git a/Terminal.Gui/View/View.Cursor.cs b/Terminal.Gui/View/View.Cursor.cs new file mode 100644 index 0000000000..bdba7d85f1 --- /dev/null +++ b/Terminal.Gui/View/View.Cursor.cs @@ -0,0 +1,35 @@ +namespace Terminal.Gui; + +public partial class View +{ + /// + /// Gets or sets the cursor style to be used when the view is focused. The default is + /// . + /// + public CursorVisibility CursorVisibility { get; set; } = CursorVisibility.Invisible; + + /// + /// Positions the cursor in the right position based on the currently focused view in the chain. + /// + /// + /// + /// Views that are focusable should override to make sure that the cursor is + /// placed in a location that makes sense. Some terminals do not have a way of hiding the cursor, so it can be + /// distracting to have the cursor left at the last focused view. So views should make sure that they place the + /// cursor in a visually sensible place. The default implementation of will place the + /// cursor at either the hotkey (if defined) or 0,0. + /// + /// + /// Viewport-relative cursor position. Return to ensure the cursor is not visible. + public virtual Point? PositionCursor () + { + if (IsInitialized && CanFocus && HasFocus) + { + // By default, position the cursor at the hotkey (if any) or 0, 0. + Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); + } + + // Returning null will hide the cursor. + return null; + } +} diff --git a/Terminal.Gui/View/ViewDiagnostics.cs b/Terminal.Gui/View/View.Diagnostics.cs similarity index 100% rename from Terminal.Gui/View/ViewDiagnostics.cs rename to Terminal.Gui/View/View.Diagnostics.cs diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/View.Drawing.cs similarity index 99% rename from Terminal.Gui/View/ViewDrawing.cs rename to Terminal.Gui/View/View.Drawing.cs index 73fa5d550f..1077f39176 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/View.Drawing.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; -public partial class View +public partial class View // Drawing APIs { private ColorScheme _colorScheme; diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs new file mode 100644 index 0000000000..125baf33fa --- /dev/null +++ b/Terminal.Gui/View/View.Hierarchy.cs @@ -0,0 +1,320 @@ +namespace Terminal.Gui; + +public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.) +{ + private static readonly IList _empty = new List (0).AsReadOnly (); + internal bool _addingView; + private List _subviews; // This is null, and allocated on demand. + private View _superView; + + /// Indicates whether the view was added to . + public bool IsAdded { get; private set; } + + /// This returns a list of the subviews contained by this view. + /// The subviews. + public IList Subviews => _subviews?.AsReadOnly () ?? _empty; + + /// Returns the container for this view, or null if this view has not been added to a container. + /// The super view. + public virtual View SuperView + { + get => _superView; + set => throw new NotImplementedException (); + } + + // Internally, we use InternalSubviews rather than subviews, as we do not expect us + // to make the same mistakes our users make when they poke at the Subviews. + internal IList InternalSubviews => _subviews ?? _empty; + + /// Adds a subview (child) to this view. + /// + /// + /// The Views that have been added to this view can be retrieved via the property. See also + /// + /// + /// + /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes + /// the lifecycle of the subviews to be transferred to this View. + /// + /// + /// The view to add. + /// The view that was added. + public virtual View Add (View view) + { + if (view is null) + { + return view; + } + + if (_subviews is null) + { + _subviews = new (); + } + + if (_tabIndexes is null) + { + _tabIndexes = new (); + } + + _subviews.Add (view); + _tabIndexes.Add (view); + view._superView = this; + + if (view.CanFocus) + { + _addingView = true; + + if (SuperView?.CanFocus == false) + { + SuperView._addingView = true; + SuperView.CanFocus = true; + SuperView._addingView = false; + } + + // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying. + CanFocus = true; + view._tabIndex = _tabIndexes.IndexOf (view); + _addingView = false; + } + + if (view.Enabled && !Enabled) + { + view._oldEnabled = true; + view.Enabled = false; + } + + OnAdded (new (this, view)); + + if (IsInitialized && !view.IsInitialized) + { + view.BeginInit (); + view.EndInit (); + } + + CheckDimAuto (); + SetNeedsLayout (); + SetNeedsDisplay (); + + return view; + } + + /// Adds the specified views (children) to the view. + /// Array of one or more views (can be optional parameter). + /// + /// + /// The Views that have been added to this view can be retrieved via the property. See also + /// and . + /// + /// + /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes + /// the lifecycle of the subviews to be transferred to this View. + /// + /// + public void Add (params View [] views) + { + if (views is null) + { + return; + } + + foreach (View view in views) + { + Add (view); + } + } + + /// Event fired when this view is added to another. + public event EventHandler Added; + + /// Get the top superview of a given . + /// The superview view. + public View GetTopSuperView (View view = null, View superview = null) + { + View top = superview ?? Application.Top; + + for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) + { + top = v; + + if (top == superview) + { + break; + } + } + + return top; + } + + /// Method invoked when a subview is being added to this view. + /// Event where is the subview being added. + public virtual void OnAdded (SuperViewChangedEventArgs e) + { + View view = e.Child; + view.IsAdded = true; + view.OnResizeNeeded (); + view.Added?.Invoke (this, e); + } + + /// Method invoked when a subview is being removed from this view. + /// Event args describing the subview being removed. + public virtual void OnRemoved (SuperViewChangedEventArgs e) + { + View view = e.Child; + view.IsAdded = false; + view.Removed?.Invoke (this, e); + } + + /// Removes a subview added via or from this View. + /// + /// + /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the + /// Subview's + /// lifecycle to be transferred to the caller; the caller muse call . + /// + /// + public virtual View Remove (View view) + { + if (view is null || _subviews is null) + { + return view; + } + + Rectangle touched = view.Frame; + _subviews.Remove (view); + _tabIndexes.Remove (view); + view._superView = null; + view._tabIndex = -1; + SetNeedsLayout (); + SetNeedsDisplay (); + + foreach (View v in _subviews) + { + if (v.Frame.IntersectsWith (touched)) + { + view.SetNeedsDisplay (); + } + } + + OnRemoved (new (this, view)); + + if (Focused == view) + { + Focused = null; + } + + return view; + } + + /// + /// Removes all subviews (children) added via or from this View. + /// + /// + /// + /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the + /// Subview's + /// lifecycle to be transferred to the caller; the caller must call on any Views that were + /// added. + /// + /// + public virtual void RemoveAll () + { + if (_subviews is null) + { + return; + } + + while (_subviews.Count > 0) + { + Remove (_subviews [0]); + } + } + + /// Event fired when this view is removed from another. + public event EventHandler Removed; + + + /// Moves one position towards the start of the list + /// The subview to move forward. + public void BringSubviewForward (View subview) + { + PerformActionForSubview ( + subview, + x => + { + int idx = _subviews.IndexOf (x); + + if (idx + 1 < _subviews.Count) + { + _subviews.Remove (x); + _subviews.Insert (idx + 1, x); + } + } + ); + } + + /// Moves to the start of the list. + /// The subview to send to the start. + public void BringSubviewToFront (View subview) + { + PerformActionForSubview ( + subview, + x => + { + _subviews.Remove (x); + _subviews.Add (x); + } + ); + } + + + /// Moves one position towards the end of the list + /// The subview to move backwards. + public void SendSubviewBackwards (View subview) + { + PerformActionForSubview ( + subview, + x => + { + int idx = _subviews.IndexOf (x); + + if (idx > 0) + { + _subviews.Remove (x); + _subviews.Insert (idx - 1, x); + } + } + ); + } + + /// Moves to the end of the list. + /// The subview to send to the end. + public void SendSubviewToBack (View subview) + { + PerformActionForSubview ( + subview, + x => + { + _subviews.Remove (x); + _subviews.Insert (0, subview); + } + ); + } + + /// + /// Internal API that runs on a subview if it is part of the list. + /// + /// + /// + private void PerformActionForSubview (View subview, Action action) + { + if (_subviews.Contains (subview)) + { + action (subview); + } + + // BUGBUG: this is odd. Why is this needed? + SetNeedsDisplay (); + subview.SetNeedsDisplay (); + } + +} diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/View.Keyboard.cs similarity index 91% rename from Terminal.Gui/View/ViewKeyboard.cs rename to Terminal.Gui/View/View.Keyboard.cs index 7a905f129e..7009ab4c6a 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/View.Keyboard.cs @@ -3,7 +3,7 @@ namespace Terminal.Gui; -public partial class View +public partial class View // Keyboard APIs { /// /// Helper to configure all things keyboard related for a View. Called from the View constructor. @@ -254,119 +254,6 @@ private void SetHotKeyFromTitle () #endregion HotKey Support - #region Tab/Focus Handling - - // This is null, and allocated on demand. - private List _tabIndexes; - - /// Gets a list of the subviews that are s. - /// The tabIndexes. - public IList TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty; - - private int _tabIndex = -1; - private int _oldTabIndex; - - /// - /// Indicates the index of the current from the list. See also: - /// . - /// - public int TabIndex - { - get => _tabIndex; - set - { - if (!CanFocus) - { - _tabIndex = -1; - - return; - } - - if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1) - { - _tabIndex = 0; - - return; - } - - if (_tabIndex == value && TabIndexes.IndexOf (this) == value) - { - return; - } - - _tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 : - value < 0 ? 0 : value; - _tabIndex = GetTabIndex (_tabIndex); - - if (SuperView._tabIndexes.IndexOf (this) != _tabIndex) - { - SuperView._tabIndexes.Remove (this); - SuperView._tabIndexes.Insert (_tabIndex, this); - SetTabIndex (); - } - } - } - - private int GetTabIndex (int idx) - { - var i = 0; - - foreach (View v in SuperView._tabIndexes) - { - if (v._tabIndex == -1 || v == this) - { - continue; - } - - i++; - } - - return Math.Min (i, idx); - } - - private void SetTabIndex () - { - var i = 0; - - foreach (View v in SuperView._tabIndexes) - { - if (v._tabIndex == -1) - { - continue; - } - - v._tabIndex = i; - i++; - } - } - - private bool _tabStop = true; - - /// - /// Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be - /// only if the is also . Set to to prevent the - /// view from being a stop-point for keyboard navigation. - /// - /// - /// The default keyboard navigation keys are Key.Tab and Key>Tab.WithShift. These can be changed by - /// modifying the key bindings (see ) of the SuperView. - /// - public bool TabStop - { - get => _tabStop; - set - { - if (_tabStop == value) - { - return; - } - - _tabStop = CanFocus && value; - } - } - - #endregion Tab/Focus Handling - #region Low-level Key handling #region Key Down Event diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/View.Layout.cs similarity index 99% rename from Terminal.Gui/View/Layout/ViewLayout.cs rename to Terminal.Gui/View/View.Layout.cs index 72a9bf14c3..deb7da682c 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -3,7 +3,7 @@ namespace Terminal.Gui; -public partial class View +public partial class View // Layout APIs { #region Frame diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/View.Mouse.cs similarity index 99% rename from Terminal.Gui/View/ViewMouse.cs rename to Terminal.Gui/View/View.Mouse.cs index 24314f583c..5f1318e214 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/View.Mouse.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; -public partial class View +public partial class View // Mouse APIs { [CanBeNull] private ColorScheme _savedHighlightColorScheme; diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs new file mode 100644 index 0000000000..4ff99c3d75 --- /dev/null +++ b/Terminal.Gui/View/View.Navigation.cs @@ -0,0 +1,813 @@ +namespace Terminal.Gui; + +public partial class View // Focus and cross-view navigation management (TabStop, TabIndex, etc...) +{ + /// Returns a value indicating if this View is currently on Top (Active) + public bool IsCurrentTop => Application.Current == this; + + // BUGBUG: This API is poorly defined and implemented. It deeply intertwines the view hierarchy with the tab order. + /// Exposed as `internal` for unit tests. Indicates focus navigation direction. + internal enum NavigationDirection + { + /// Navigate forward. + Forward, + + /// Navigate backwards. + Backward + } + + /// Invoked when this view is gaining focus (entering). + /// The view that is leaving focus. + /// , if the event was handled, otherwise. + /// + /// + /// Overrides must call the base class method to ensure that the event is raised. If the event + /// is handled, the method should return . + /// + /// + public virtual bool OnEnter (View leavingView) + { + var args = new FocusEventArgs (leavingView, this); + Enter?.Invoke (this, args); + + if (args.Handled) + { + return true; + } + + return false; + } + + /// Invoked when this view is losing focus (leaving). + /// The view that is entering focus. + /// , if the event was handled, otherwise. + /// + /// + /// Overrides must call the base class method to ensure that the event is raised. If the event + /// is handled, the method should return . + /// + /// + public virtual bool OnLeave (View enteringView) + { + var args = new FocusEventArgs (this, enteringView); + Leave?.Invoke (this, args); + + if (args.Handled) + { + return true; + } + + return false; + } + + /// Raised when the view is gaining (entering) focus. Can be cancelled. + /// + /// Raised by the virtual method. + /// + public event EventHandler Enter; + + /// Raised when the view is losing (leaving) focus. Can be cancelled. + /// + /// Raised by the virtual method. + /// + public event EventHandler Leave; + + private NavigationDirection _focusDirection; + + /// + /// Gets or sets the focus direction for this view and all subviews. + /// Setting this property will set the focus direction for all views up the SuperView hierarchy. + /// + internal NavigationDirection FocusDirection + { + get => SuperView?.FocusDirection ?? _focusDirection; + set + { + if (SuperView is { }) + { + SuperView.FocusDirection = value; + } + else + { + _focusDirection = value; + } + } + } + + private bool _hasFocus; + + /// + /// Gets or sets whether this view has focus. + /// + /// + /// + /// Causes the and virtual methods (and and + /// events to be raised) when the value changes. + /// + /// + /// Setting this property to will recursively set to + /// + /// for any focused subviews. + /// + /// + public bool HasFocus + { + // Force the specified view to have focus + set => SetHasFocus (value, this, true); + get => _hasFocus; + } + + /// + /// Internal API that sets . This method is called by HasFocus_set and other methods that + /// need to set or remove focus from a view. + /// + /// The new setting for . + /// The view that will be gaining or losing focus. + /// + /// to force Enter/Leave on regardless of whether it + /// already HasFocus or not. + /// + /// + /// If is and there is a focused subview ( + /// is not ), + /// this method will recursively remove focus from any focused subviews of . + /// + private void SetHasFocus (bool newHasFocus, View view, bool force = false) + { + if (HasFocus != newHasFocus || force) + { + _hasFocus = newHasFocus; + + if (newHasFocus) + { + OnEnter (view); + } + else + { + OnLeave (view); + } + + SetNeedsDisplay (); + } + + // Remove focus down the chain of subviews if focus is removed + if (!newHasFocus && Focused is { }) + { + View f = Focused; + f.OnLeave (view); + f.SetHasFocus (false, view); + Focused = null; + } + } + + /// Raised when has been changed. + /// + /// Raised by the virtual method. + /// + public event EventHandler CanFocusChanged; + + /// Invoked when the property from a view is changed. + /// + /// Raises the event. + /// + public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); } + + private bool _oldCanFocus; + private bool _canFocus; + + /// Gets or sets a value indicating whether this can be focused. + /// + /// + /// must also have set to . + /// + /// + public bool CanFocus + { + get => _canFocus; + set + { + if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) + { + throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); + } + + if (_canFocus == value) + { + return; + } + + _canFocus = value; + + switch (_canFocus) + { + case false when _tabIndex > -1: + TabIndex = -1; + + break; + case true when SuperView?.CanFocus == false && _addingView: + SuperView.CanFocus = true; + + break; + } + + if (_canFocus && _tabIndex == -1) + { + TabIndex = SuperView is { } ? SuperView._tabIndexes.IndexOf (this) : -1; + } + + TabStop = _canFocus; + + if (!_canFocus && SuperView?.Focused == this) + { + SuperView.Focused = null; + } + + if (!_canFocus && HasFocus) + { + SetHasFocus (false, this); + SuperView?.EnsureFocus (); + + if (SuperView is { Focused: null }) + { + SuperView.FocusNext (); + + if (SuperView.Focused is null && Application.Current is { }) + { + Application.Current.FocusNext (); + } + + ApplicationOverlapped.BringOverlappedTopToFront (); + } + } + + if (_subviews is { } && IsInitialized) + { + foreach (View view in _subviews) + { + if (view.CanFocus != value) + { + if (!value) + { + view._oldCanFocus = view.CanFocus; + view._oldTabIndex = view._tabIndex; + view.CanFocus = false; + view._tabIndex = -1; + } + else + { + if (_addingView) + { + view._addingView = true; + } + + view.CanFocus = view._oldCanFocus; + view._tabIndex = view._oldTabIndex; + view._addingView = false; + } + } + } + + if (this is Toplevel && Application.Current.Focused != this) + { + ApplicationOverlapped.BringOverlappedTopToFront (); + } + } + + OnCanFocusChanged (); + SetNeedsDisplay (); + } + } + + /// Returns the currently focused Subview inside this view, or if nothing is focused. + /// The currently focused Subview. + public View Focused { get; private set; } + + /// + /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus), or + /// if nothing is focused. + /// + /// The most focused Subview. + public View MostFocused + { + get + { + if (Focused is null) + { + return null; + } + + View most = Focused.MostFocused; + + if (most is { }) + { + return most; + } + + return Focused; + } + } + + /// Causes subview specified by to enter focus. + /// View. + private void SetFocus (View view) + { + if (view is null) + { + return; + } + + //Console.WriteLine ($"Request to focus {view}"); + if (!view.CanFocus || !view.Visible || !view.Enabled) + { + return; + } + + if (Focused?._hasFocus == true && Focused == view) + { + return; + } + + if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this) + { + if (!view._hasFocus) + { + view._hasFocus = true; + } + + return; + } + + // Make sure that this view is a subview + View c; + + for (c = view._superView; c != null; c = c._superView) + { + if (c == this) + { + break; + } + } + + if (c is null) + { + throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); + } + + if (Focused is { }) + { + Focused.SetHasFocus (false, view); + } + + View f = Focused; + Focused = view; + Focused.SetHasFocus (true, f); + Focused.EnsureFocus (); + + // Send focus upwards + if (SuperView is { }) + { + SuperView.SetFocus (this); + } + else + { + SetFocus (this); + } + } + + /// Causes this view to be focused and entire Superview hierarchy to have the focused order updated. + public void SetFocus () + { + if (!CanBeVisible (this) || !Enabled) + { + if (HasFocus) + { + SetHasFocus (false, this); + } + + return; + } + + if (SuperView is { }) + { + SuperView.SetFocus (this); + } + else + { + SetFocus (this); + } + } + + /// + /// If there is no focused subview, calls or based on + /// . + /// does nothing. + /// + public void EnsureFocus () + { + if (Focused is null && _subviews?.Count > 0) + { + if (FocusDirection == NavigationDirection.Forward) + { + FocusFirst (); + } + else + { + FocusLast (); + } + } + } + + /// + /// Focuses the last focusable view in if one exists. If there are no views in + /// then the focus is set to the view itself. + /// + public void FocusFirst (bool overlapped = false) + { + if (!CanBeVisible (this)) + { + return; + } + + if (_tabIndexes is null) + { + SuperView?.SetFocus (this); + + return; + } + + foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped))) + { + if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) + { + SetFocus (view); + + return; + } + } + } + + /// + /// Focuses the last focusable view in if one exists. If there are no views in + /// then the focus is set to the view itself. + /// + public void FocusLast (bool overlapped = false) + { + if (!CanBeVisible (this)) + { + return; + } + + if (_tabIndexes is null) + { + SuperView?.SetFocus (this); + + return; + } + + foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ()) + { + if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) + { + SetFocus (view); + + return; + } + } + } + + /// + /// Focuses the previous view in . If there is no previous view, the focus is set to the + /// view itself. + /// + /// if previous was focused, otherwise. + public bool FocusPrev () + { + if (!CanBeVisible (this)) + { + return false; + } + + FocusDirection = NavigationDirection.Backward; + + if (TabIndexes is null || TabIndexes.Count == 0) + { + return false; + } + + if (Focused is null) + { + FocusLast (); + + return Focused != null; + } + + int focusedIdx = -1; + + for (int i = TabIndexes.Count; i > 0;) + { + i--; + View w = TabIndexes [i]; + + if (w.HasFocus) + { + if (w.FocusPrev ()) + { + return true; + } + + focusedIdx = i; + + continue; + } + + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) + { + Focused.SetHasFocus (false, w); + + // If the focused view is overlapped don't focus on the next if it's not overlapped. + if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + FocusLast (true); + + return true; + } + + // If the focused view is not overlapped and the next is, skip it + if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + continue; + } + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) + { + w.FocusLast (); + } + + SetFocus (w); + + return true; + } + } + + // There's no prev view in tab indexes. + if (Focused is { }) + { + // Leave Focused + Focused.SetHasFocus (false, this); + + if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)) + { + FocusLast (true); + + return true; + } + + // Signal to caller no next view was found + Focused = null; + } + + return false; + } + + /// + /// Focuses the next view in . If there is no next view, the focus is set to the view + /// itself. + /// + /// if next was focused, otherwise. + public bool FocusNext () + { + if (!CanBeVisible (this)) + { + return false; + } + + FocusDirection = NavigationDirection.Forward; + + if (TabIndexes is null || TabIndexes.Count == 0) + { + return false; + } + + if (Focused is null) + { + FocusFirst (); + + return Focused != null; + } + + int focusedIdx = -1; + + for (var i = 0; i < TabIndexes.Count; i++) + { + View w = TabIndexes [i]; + + if (w.HasFocus) + { + if (w.FocusNext ()) + { + return true; + } + + focusedIdx = i; + + continue; + } + + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) + { + Focused.SetHasFocus (false, w); + + //// If the focused view is overlapped don't focus on the next if it's not overlapped. + //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)/* && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)*/) + //{ + // return false; + //} + + //// If the focused view is not overlapped and the next is, skip it + //if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped)) + //{ + // continue; + //} + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) + { + w.FocusFirst (); + } + + SetFocus (w); + + return true; + } + } + + // There's no next view in tab indexes. + if (Focused is { }) + { + // Leave Focused + Focused.SetHasFocus (false, this); + + //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)) + //{ + // FocusFirst (true); + // return true; + //} + + // Signal to caller no next view was found + Focused = null; + } + + return false; + } + + private View GetMostFocused (View view) + { + if (view is null) + { + return null; + } + + return view.Focused is { } ? GetMostFocused (view.Focused) : view; + } + + #region Tab/Focus Handling + + private List _tabIndexes; + + // TODO: This should be a get-only property? + // BUGBUG: This returns an AsReadOnly list, but isn't declared as such. + /// Gets a list of the subviews that are a . + /// The tabIndexes. + public IList TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty; + + // TODO: Change this to int? and use null to indicate the view is not in the tab order. + private int _tabIndex = -1; + private int _oldTabIndex; + + /// + /// Indicates the index of the current from the list. See also: + /// . + /// + /// + /// + /// If the value is -1, the view is not part of the tab order. + /// + /// + /// On set, if is , will be set to -1. + /// + /// + /// On set, if is or has not TabStops, will + /// be set to 0. + /// + /// + /// On set, if has only one TabStop, will be set to 0. + /// + /// + public int TabIndex + { + get => _tabIndex; + set + { + if (!CanFocus) + { + // BUGBUG: Property setters should set the property to the value passed in and not have side effects. + _tabIndex = -1; + + return; + } + + if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1) + { + // BUGBUG: Property setters should set the property to the value passed in and not have side effects. + _tabIndex = 0; + + return; + } + + if (_tabIndex == value && TabIndexes.IndexOf (this) == value) + { + return; + } + + _tabIndex = value > SuperView!.TabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 : + value < 0 ? 0 : value; + _tabIndex = GetGreatestTabIndexInSuperView (_tabIndex); + + if (SuperView._tabIndexes.IndexOf (this) != _tabIndex) + { + // BUGBUG: we have to use _tabIndexes and not TabIndexes because TabIndexes returns is a read-only version of _tabIndexes + SuperView._tabIndexes.Remove (this); + SuperView._tabIndexes.Insert (_tabIndex, this); + ReorderSuperViewTabIndexes (); + } + } + } + + /// + /// Gets the greatest of the 's that is less + /// than or equal to . + /// + /// + /// The minimum of and the 's . + private int GetGreatestTabIndexInSuperView (int idx) + { + var i = 0; + + foreach (View superViewTabStop in SuperView._tabIndexes) + { + if (superViewTabStop._tabIndex == -1 || superViewTabStop == this) + { + continue; + } + + i++; + } + + return Math.Min (i, idx); + } + + /// + /// Re-orders the s of the views in the 's . + /// + private void ReorderSuperViewTabIndexes () + { + var i = 0; + + foreach (View superViewTabStop in SuperView._tabIndexes) + { + if (superViewTabStop._tabIndex == -1) + { + continue; + } + + superViewTabStop._tabIndex = i; + i++; + } + } + + private bool _tabStop = true; + + /// + /// Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be + /// only if is . Set to to prevent the + /// view from being a stop-point for keyboard navigation. + /// + /// + /// The default keyboard navigation keys are Key.Tab and Key>Tab.WithShift. These can be changed by + /// modifying the key bindings (see ) of the SuperView. + /// + public bool TabStop + { + get => _tabStop; + set + { + if (_tabStop == value) + { + return; + } + + _tabStop = CanFocus && value; + } + } + + #endregion Tab/Focus Handling +} diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/View.Text.cs similarity index 99% rename from Terminal.Gui/View/ViewText.cs rename to Terminal.Gui/View/View.Text.cs index 6b9e0cf94e..664640730e 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/View.Text.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; -public partial class View +public partial class View // Text Property APIs { /// /// Initializes the Text of the View. Called by the constructor. diff --git a/Terminal.Gui/View/ViewArrangement.cs b/Terminal.Gui/View/ViewArrangement.cs index df13c4762d..0143b082e9 100644 --- a/Terminal.Gui/View/ViewArrangement.cs +++ b/Terminal.Gui/View/ViewArrangement.cs @@ -1,14 +1,16 @@ namespace Terminal.Gui; /// -/// Describes what user actions are enabled for arranging a within it's . +/// Describes what user actions are enabled for arranging a within it's +/// . /// See . /// /// -/// -/// Sizing or moving a view is only possible if the is part of a and -/// the relevant position and dimensions of the are independent of other SubViews -/// +/// +/// Sizing or moving a view is only possible if the is part of a +/// and +/// the relevant position and dimensions of the are independent of other SubViews +/// /// [Flags] public enum ViewArrangement @@ -56,26 +58,14 @@ public enum ViewArrangement Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable, /// - /// The view overlap other views. + /// The view overlap other views. /// /// /// - /// When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to the next/prev view in the next/prev Tabindex). + /// When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to + /// the next/prev view in the next/prev Tabindex). /// Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views. /// /// Overlapped = 32 } -public partial class View -{ - /// - /// Gets or sets the user actions that are enabled for the view within it's . - /// - /// - /// - /// Sizing or moving a view is only possible if the is part of a and - /// the relevant position and dimensions of the are independent of other SubViews - /// - /// - public ViewArrangement Arrangement { get; set; } -} diff --git a/Terminal.Gui/View/ViewEventArgs.cs b/Terminal.Gui/View/ViewEventArgs.cs index b17b98afe4..cdcbaa0093 100644 --- a/Terminal.Gui/View/ViewEventArgs.cs +++ b/Terminal.Gui/View/ViewEventArgs.cs @@ -13,69 +13,4 @@ public class ViewEventArgs : EventArgs /// child then sender may be the parent while is the child being added. /// public View View { get; } -} - -/// Event arguments for the event. -public class LayoutEventArgs : EventArgs -{ - /// Creates a new instance of the class. - /// The view that the event is about. - public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; } - - /// The viewport of the before it was laid out. - public Size OldContentSize { get; set; } -} - -/// Event args for draw events -public class DrawEventArgs : EventArgs -{ - /// Creates a new instance of the class. - /// - /// The Content-relative rectangle describing the new visible viewport into the - /// . - /// - /// - /// The Content-relative rectangle describing the old visible viewport into the - /// . - /// - public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport) - { - NewViewport = newViewport; - OldViewport = oldViewport; - } - - /// If set to true, the draw operation will be canceled, if applicable. - public bool Cancel { get; set; } - - /// Gets the Content-relative rectangle describing the old visible viewport into the . - public Rectangle OldViewport { get; } - - /// Gets the Content-relative rectangle describing the currently visible viewport into the . - public Rectangle NewViewport { get; } -} - -/// Defines the event arguments for -public class FocusEventArgs : EventArgs -{ - /// Constructs. - /// The view that is losing focus. - /// The view that is gaining focus. - public FocusEventArgs (View leaving, View entering) { - Leaving = leaving; - Entering = entering; - } - - /// - /// Indicates if the current focus event has already been processed and the driver should stop notifying any other - /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the - /// subscriber method. - /// - public bool Handled { get; set; } - - /// Indicates the view that is losing focus. - public View Leaving { get; set; } - - /// Indicates the view that is gaining focus. - public View Entering { get; set; } - -} +} \ No newline at end of file diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs deleted file mode 100644 index 79cca431ea..0000000000 --- a/Terminal.Gui/View/ViewSubViews.cs +++ /dev/null @@ -1,948 +0,0 @@ -using System.Diagnostics; - -namespace Terminal.Gui; - -public partial class View -{ - private static readonly IList _empty = new List (0).AsReadOnly (); - internal bool _addingView; - private List _subviews; // This is null, and allocated on demand. - private View _superView; - - /// Indicates whether the view was added to . - public bool IsAdded { get; private set; } - - /// Returns a value indicating if this View is currently on Top (Active) - public bool IsCurrentTop => Application.Current == this; - - /// This returns a list of the subviews contained by this view. - /// The subviews. - public IList Subviews => _subviews?.AsReadOnly () ?? _empty; - - /// Returns the container for this view, or null if this view has not been added to a container. - /// The super view. - public virtual View SuperView - { - get => _superView; - set => throw new NotImplementedException (); - } - - // Internally, we use InternalSubviews rather than subviews, as we do not expect us - // to make the same mistakes our users make when they poke at the Subviews. - internal IList InternalSubviews => _subviews ?? _empty; - - /// Adds a subview (child) to this view. - /// - /// - /// The Views that have been added to this view can be retrieved via the property. See also - /// - /// - /// - /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes - /// the lifecycle of the subviews to be transferred to this View. - /// - /// - /// The view to add. - /// The view that was added. - public virtual View Add (View view) - { - if (view is null) - { - return view; - } - - if (_subviews is null) - { - _subviews = new (); - } - - if (_tabIndexes is null) - { - _tabIndexes = new (); - } - - _subviews.Add (view); - _tabIndexes.Add (view); - view._superView = this; - - if (view.CanFocus) - { - _addingView = true; - - if (SuperView?.CanFocus == false) - { - SuperView._addingView = true; - SuperView.CanFocus = true; - SuperView._addingView = false; - } - - // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying. - CanFocus = true; - view._tabIndex = _tabIndexes.IndexOf (view); - _addingView = false; - } - - if (view.Enabled && !Enabled) - { - view._oldEnabled = true; - view.Enabled = false; - } - - OnAdded (new (this, view)); - - if (IsInitialized && !view.IsInitialized) - { - view.BeginInit (); - view.EndInit (); - } - - CheckDimAuto (); - SetNeedsLayout (); - SetNeedsDisplay (); - - return view; - } - - /// Adds the specified views (children) to the view. - /// Array of one or more views (can be optional parameter). - /// - /// - /// The Views that have been added to this view can be retrieved via the property. See also - /// and . - /// - /// - /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes - /// the lifecycle of the subviews to be transferred to this View. - /// - /// - public void Add (params View [] views) - { - if (views is null) - { - return; - } - - foreach (View view in views) - { - Add (view); - } - } - - /// Event fired when this view is added to another. - public event EventHandler Added; - - /// Moves the subview backwards in the hierarchy, only one step - /// The subview to send backwards - /// If you want to send the view all the way to the back use SendSubviewToBack. - public void BringSubviewForward (View subview) - { - PerformActionForSubview ( - subview, - x => - { - int idx = _subviews.IndexOf (x); - - if (idx + 1 < _subviews.Count) - { - _subviews.Remove (x); - _subviews.Insert (idx + 1, x); - } - } - ); - } - - /// Brings the specified subview to the front so it is drawn on top of any other views. - /// The subview to send to the front - /// . - public void BringSubviewToFront (View subview) - { - PerformActionForSubview ( - subview, - x => - { - _subviews.Remove (x); - _subviews.Add (x); - } - ); - } - - /// Get the top superview of a given . - /// The superview view. - public View GetTopSuperView (View view = null, View superview = null) - { - View top = superview ?? Application.Top; - - for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) - { - top = v; - - if (top == superview) - { - break; - } - } - - return top; - } - - /// Method invoked when a subview is being added to this view. - /// Event where is the subview being added. - public virtual void OnAdded (SuperViewChangedEventArgs e) - { - View view = e.Child; - view.IsAdded = true; - view.OnResizeNeeded (); - view.Added?.Invoke (this, e); - } - - /// Method invoked when a subview is being removed from this view. - /// Event args describing the subview being removed. - public virtual void OnRemoved (SuperViewChangedEventArgs e) - { - View view = e.Child; - view.IsAdded = false; - view.Removed?.Invoke (this, e); - } - - /// Removes a subview added via or from this View. - /// - /// - /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the - /// Subview's - /// lifecycle to be transferred to the caller; the caller muse call . - /// - /// - public virtual View Remove (View view) - { - if (view is null || _subviews is null) - { - return view; - } - - Rectangle touched = view.Frame; - _subviews.Remove (view); - _tabIndexes.Remove (view); - view._superView = null; - view._tabIndex = -1; - SetNeedsLayout (); - SetNeedsDisplay (); - - foreach (View v in _subviews) - { - if (v.Frame.IntersectsWith (touched)) - { - view.SetNeedsDisplay (); - } - } - - OnRemoved (new (this, view)); - - if (Focused == view) - { - Focused = null; - } - - return view; - } - - /// - /// Removes all subviews (children) added via or from this View. - /// - /// - /// - /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the - /// Subview's - /// lifecycle to be transferred to the caller; the caller must call on any Views that were - /// added. - /// - /// - public virtual void RemoveAll () - { - if (_subviews is null) - { - return; - } - - while (_subviews.Count > 0) - { - Remove (_subviews [0]); - } - } - - /// Event fired when this view is removed from another. - public event EventHandler Removed; - - /// Moves the subview backwards in the hierarchy, only one step - /// The subview to send backwards - /// If you want to send the view all the way to the back use SendSubviewToBack. - public void SendSubviewBackwards (View subview) - { - PerformActionForSubview ( - subview, - x => - { - int idx = _subviews.IndexOf (x); - - if (idx > 0) - { - _subviews.Remove (x); - _subviews.Insert (idx - 1, x); - } - } - ); - } - - /// Sends the specified subview to the front so it is the first view drawn - /// The subview to send to the front - /// . - public void SendSubviewToBack (View subview) - { - PerformActionForSubview ( - subview, - x => - { - _subviews.Remove (x); - _subviews.Insert (0, subview); - } - ); - } - - private void PerformActionForSubview (View subview, Action action) - { - if (_subviews.Contains (subview)) - { - action (subview); - } - - SetNeedsDisplay (); - subview.SetNeedsDisplay (); - } - - #region Focus - - /// Exposed as `internal` for unit tests. Indicates focus navigation direction. - internal enum NavigationDirection - { - /// Navigate forward. - Forward, - - /// Navigate backwards. - Backward - } - - /// Event fired when the view gets focus. - public event EventHandler Enter; - - /// Event fired when the view looses focus. - public event EventHandler Leave; - - private NavigationDirection _focusDirection; - - internal NavigationDirection FocusDirection - { - get => SuperView?.FocusDirection ?? _focusDirection; - set - { - if (SuperView is { }) - { - SuperView.FocusDirection = value; - } - else - { - _focusDirection = value; - } - } - } - - private bool _hasFocus; - - /// - public bool HasFocus - { - set => SetHasFocus (value, this, true); - get => _hasFocus; - } - - private void SetHasFocus (bool value, View view, bool force = false) - { - if (HasFocus != value || force) - { - _hasFocus = value; - - if (value) - { - OnEnter (view); - } - else - { - OnLeave (view); - } - - SetNeedsDisplay (); - } - - // Remove focus down the chain of subviews if focus is removed - if (!value && Focused is { }) - { - View f = Focused; - f.OnLeave (view); - f.SetHasFocus (false, view); - Focused = null; - } - } - - /// Event fired when the value is being changed. - public event EventHandler CanFocusChanged; - - /// Method invoked when the property from a view is changed. - public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); } - - private bool _oldCanFocus; - private bool _canFocus; - - /// Gets or sets a value indicating whether this can focus. - public bool CanFocus - { - get => _canFocus; - set - { - if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) - { - throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); - } - - if (_canFocus == value) - { - return; - } - - _canFocus = value; - - switch (_canFocus) - { - case false when _tabIndex > -1: - TabIndex = -1; - - break; - case true when SuperView?.CanFocus == false && _addingView: - SuperView.CanFocus = true; - - break; - } - - if (_canFocus && _tabIndex == -1) - { - TabIndex = SuperView is { } ? SuperView._tabIndexes.IndexOf (this) : -1; - } - - TabStop = _canFocus; - - if (!_canFocus && SuperView?.Focused == this) - { - SuperView.Focused = null; - } - - if (!_canFocus && HasFocus) - { - SetHasFocus (false, this); - SuperView?.EnsureFocus (); - - if (SuperView is { Focused: null }) - { - SuperView.FocusNext (); - - if (SuperView.Focused is null && Application.Current is { }) - { - Application.Current.FocusNext (); - } - - ApplicationOverlapped.BringOverlappedTopToFront (); - } - } - - if (_subviews is { } && IsInitialized) - { - foreach (View view in _subviews) - { - if (view.CanFocus != value) - { - if (!value) - { - view._oldCanFocus = view.CanFocus; - view._oldTabIndex = view._tabIndex; - view.CanFocus = false; - view._tabIndex = -1; - } - else - { - if (_addingView) - { - view._addingView = true; - } - - view.CanFocus = view._oldCanFocus; - view._tabIndex = view._oldTabIndex; - view._addingView = false; - } - } - } - - if (this is Toplevel && Application.Current.Focused != this) - { - ApplicationOverlapped.BringOverlappedTopToFront (); - } - } - - OnCanFocusChanged (); - SetNeedsDisplay (); - } - } - - /// - /// Called when a view gets focus. - /// - /// The view that is losing focus. - /// true, if the event was handled, false otherwise. - public virtual bool OnEnter (View view) - { - var args = new FocusEventArgs (view, this); - Enter?.Invoke (this, args); - - if (args.Handled) - { - return true; - } - - return false; - } - - /// Method invoked when a view loses focus. - /// The view that is getting focus. - /// true, if the event was handled, false otherwise. - public virtual bool OnLeave (View view) - { - var args = new FocusEventArgs (this, view); - Leave?.Invoke (this, args); - - if (args.Handled) - { - return true; - } - - return false; - } - - // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews. - /// Returns the currently focused Subview inside this view, or null if nothing is focused. - /// The focused. - public View Focused { get; private set; } - - // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews. - /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus). - /// The most focused View. - public View MostFocused - { - get - { - if (Focused is null) - { - return null; - } - - View most = Focused.MostFocused; - - if (most is { }) - { - return most; - } - - return Focused; - } - } - - /// Causes the specified subview to have focus. - /// View. - private void SetFocus (View view) - { - if (view is null) - { - return; - } - - //Console.WriteLine ($"Request to focus {view}"); - if (!view.CanFocus || !view.Visible || !view.Enabled) - { - return; - } - - if (Focused?._hasFocus == true && Focused == view) - { - return; - } - - if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this) - { - if (!view._hasFocus) - { - view._hasFocus = true; - } - - return; - } - - // Make sure that this view is a subview - View c; - - for (c = view._superView; c != null; c = c._superView) - { - if (c == this) - { - break; - } - } - - if (c is null) - { - throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); - } - - if (Focused is { }) - { - Focused.SetHasFocus (false, view); - } - - View f = Focused; - Focused = view; - Focused.SetHasFocus (true, f); - Focused.EnsureFocus (); - - // Send focus upwards - if (SuperView is { }) - { - SuperView.SetFocus (this); - } - else - { - SetFocus (this); - } - } - - /// Causes this view to be focused and entire Superview hierarchy to have the focused order updated. - public void SetFocus () - { - if (!CanBeVisible (this) || !Enabled) - { - if (HasFocus) - { - SetHasFocus (false, this); - } - - return; - } - - if (SuperView is { }) - { - SuperView.SetFocus (this); - } - else - { - SetFocus (this); - } - } - - /// - /// If there is no focused subview, calls or based on . - /// does nothing. - /// - public void EnsureFocus () - { - if (Focused is null && _subviews?.Count > 0) - { - if (FocusDirection == NavigationDirection.Forward) - { - FocusFirst (); - } - else - { - FocusLast (); - } - } - } - - /// - /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself. - /// - public void FocusFirst (bool overlapped = false) - { - if (!CanBeVisible (this)) - { - return; - } - - if (_tabIndexes is null) - { - SuperView?.SetFocus (this); - - return; - } - - foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped))) - { - if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) - { - SetFocus (view); - - return; - } - } - } - - /// - /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself. - /// - public void FocusLast (bool overlapped = false) - { - if (!CanBeVisible (this)) - { - return; - } - - if (_tabIndexes is null) - { - SuperView?.SetFocus (this); - - return; - } - - foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ()) - { - if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) - { - SetFocus (view); - - return; - } - } - } - - /// - /// Focuses the previous view in . If there is no previous view, the focus is set to the view itself. - /// - /// if previous was focused, otherwise. - public bool FocusPrev () - { - if (!CanBeVisible (this)) - { - return false; - } - - FocusDirection = NavigationDirection.Backward; - - if (TabIndexes is null || TabIndexes.Count == 0) - { - return false; - } - - if (Focused is null) - { - FocusLast (); - - return Focused != null; - } - - int focusedIdx = -1; - - for (int i = TabIndexes.Count; i > 0;) - { - i--; - View w = TabIndexes [i]; - - if (w.HasFocus) - { - if (w.FocusPrev ()) - { - return true; - } - - focusedIdx = i; - - continue; - } - - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) - { - Focused.SetHasFocus (false, w); - - // If the focused view is overlapped don't focus on the next if it's not overlapped. - if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)) - { - return false; - } - - // If the focused view is not overlapped and the next is, skip it - if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped)) - { - continue; - } - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - { - w.FocusLast (); - } - - SetFocus (w); - - return true; - } - } - - // There's no prev view in tab indexes. - if (Focused is { }) - { - // Leave Focused - Focused.SetHasFocus (false, this); - - if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)) - { - FocusLast (true); - return true; - } - - // Signal to caller no next view was found - Focused = null; - } - - return false; - } - - /// - /// Focuses the next view in . If there is no next view, the focus is set to the view itself. - /// - /// if next was focused, otherwise. - public bool FocusNext () - { - if (!CanBeVisible (this)) - { - return false; - } - - FocusDirection = NavigationDirection.Forward; - - if (TabIndexes is null || TabIndexes.Count == 0) - { - return false; - } - - if (Focused is null) - { - FocusFirst (); - - return Focused != null; - } - - int focusedIdx = -1; - - for (var i = 0; i < TabIndexes.Count; i++) - { - View w = TabIndexes [i]; - - if (w.HasFocus) - { - if (w.FocusNext ()) - { - return true; - } - - focusedIdx = i; - - continue; - } - - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) - { - Focused.SetHasFocus (false, w); - - // If the focused view is overlapped don't focus on the next if it's not overlapped. - if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)) - { - return false; - } - - // If the focused view is not overlapped and the next is, skip it - if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped)) - { - continue; - } - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - { - w.FocusFirst (); - } - - SetFocus (w); - - return true; - } - } - - // There's no next view in tab indexes. - if (Focused is { }) - { - // Leave Focused - Focused.SetHasFocus (false, this); - - if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)) - { - FocusFirst (true); - return true; - } - - // Signal to caller no next view was found - Focused = null; - } - - return false; - } - - private View GetMostFocused (View view) - { - if (view is null) - { - return null; - } - - return view.Focused is { } ? GetMostFocused (view.Focused) : view; - } - - /// - /// Gets or sets the cursor style to be used when the view is focused. The default is . - /// - public CursorVisibility CursorVisibility { get; set; } = CursorVisibility.Invisible; - - /// - /// Positions the cursor in the right position based on the currently focused view in the chain. - /// - /// - /// - /// Views that are focusable should override to make sure that the cursor is - /// placed in a location that makes sense. Some terminals do not have a way of hiding the cursor, so it can be - /// distracting to have the cursor left at the last focused view. So views should make sure that they place the - /// cursor in a visually sensible place. The default implementation of will place the - /// cursor at either the hotkey (if defined) or 0,0. - /// - /// - /// Viewport-relative cursor position. Return to ensure the cursor is not visible. - public virtual Point? PositionCursor () - { - if (IsInitialized && CanFocus && HasFocus) - { - // By default, position the cursor at the hotkey (if any) or 0, 0. - Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); - } - - // Returning null will hide the cursor. - return null; - } - - #endregion Focus -} diff --git a/UICatalog/Scenarios/ViewExperiments.cs b/UICatalog/Scenarios/ViewExperiments.cs index 263a507d95..4c212b8b75 100644 --- a/UICatalog/Scenarios/ViewExperiments.cs +++ b/UICatalog/Scenarios/ViewExperiments.cs @@ -27,6 +27,8 @@ public override void Main () Title = "View1", ColorScheme = Colors.ColorSchemes ["Base"], Id = "View1", + ShadowStyle = ShadowStyle.Transparent, + BorderStyle = LineStyle.Double, CanFocus = true, // Can't drag without this? BUGBUG Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped }; @@ -46,16 +48,7 @@ public override void Main () //app.Add (view); - view.Margin.Thickness = new (0); - view.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; - view.Margin.Data = "Margin"; - view.Border.Thickness = new (1); - view.Border.LineStyle = LineStyle.Double; - view.Border.ColorScheme = view.ColorScheme; - view.Border.Data = "Border"; - view.Padding.Thickness = new (0); - view.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; - view.Padding.Data = "Padding"; + view.BorderStyle = LineStyle.Double; var view2 = new View { @@ -66,6 +59,8 @@ public override void Main () Title = "View2", ColorScheme = Colors.ColorSchemes ["Base"], Id = "View2", + ShadowStyle = ShadowStyle.Transparent, + BorderStyle = LineStyle.Double, CanFocus = true, // Can't drag without this? BUGBUG Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped }; @@ -85,16 +80,6 @@ public override void Main () view2.Add (button); view2.Add (button); - view2.Margin.Thickness = new (0); - view2.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"]; - view2.Margin.Data = "Margin"; - view2.Border.Thickness = new (1); - view2.Border.LineStyle = LineStyle.Double; - view2.Border.ColorScheme = view2.ColorScheme; - view2.Border.Data = "Border"; - view2.Padding.Thickness = new (0); - view2.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; - view2.Padding.Data = "Padding"; button = new () { From 9b89fe6466576ba58cbb26a96bd1eb10ef72c98a Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 25 Jul 2024 11:21:49 -0600 Subject: [PATCH 93/96] Code cleanup and API docs - getting better understanding of navigation code. --- Terminal.Gui/View/View.Hierarchy.cs | 10 ++-- Terminal.Gui/View/View.Navigation.cs | 86 +++++++++++++++++++--------- Terminal.Gui/View/View.cs | 2 +- UICatalog/Scenarios/Notepad.cs | 1 - 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs index 125baf33fa..d662538494 100644 --- a/Terminal.Gui/View/View.Hierarchy.cs +++ b/Terminal.Gui/View/View.Hierarchy.cs @@ -3,7 +3,6 @@ namespace Terminal.Gui; public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.) { private static readonly IList _empty = new List (0).AsReadOnly (); - internal bool _addingView; private List _subviews; // This is null, and allocated on demand. private View _superView; @@ -62,19 +61,20 @@ public virtual View Add (View view) if (view.CanFocus) { - _addingView = true; + // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want. + _addingViewSoCanFocusAlsoUpdatesSuperView = true; if (SuperView?.CanFocus == false) { - SuperView._addingView = true; + SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = true; SuperView.CanFocus = true; - SuperView._addingView = false; + SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = false; } // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying. CanFocus = true; view._tabIndex = _tabIndexes.IndexOf (view); - _addingView = false; + _addingViewSoCanFocusAlsoUpdatesSuperView = false; } if (view.Enabled && !Enabled) diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs index 4ff99c3d75..3cc0c1f116 100644 --- a/Terminal.Gui/View/View.Navigation.cs +++ b/Terminal.Gui/View/View.Navigation.cs @@ -75,7 +75,7 @@ public virtual bool OnLeave (View enteringView) private NavigationDirection _focusDirection; /// - /// Gets or sets the focus direction for this view and all subviews. + /// INTERNAL API that gets or sets the focus direction for this view and all subviews. /// Setting this property will set the focus direction for all views up the SuperView hierarchy. /// internal NavigationDirection FocusDirection @@ -160,19 +160,14 @@ private void SetHasFocus (bool newHasFocus, View view, bool force = false) } } - /// Raised when has been changed. - /// - /// Raised by the virtual method. - /// - public event EventHandler CanFocusChanged; - - /// Invoked when the property from a view is changed. - /// - /// Raises the event. - /// - public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); } + // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want. + // Set to true in Add() to indicate that the view being added to a SuperView has CanFocus=true. + // Makes it so CanFocus will update the SuperView's CanFocus property. + internal bool _addingViewSoCanFocusAlsoUpdatesSuperView; + // Used to cache CanFocus on subviews when CanFocus is set to false so that it can be restored when CanFocus is changed back to true private bool _oldCanFocus; + private bool _canFocus; /// Gets or sets a value indicating whether this can be focused. @@ -180,13 +175,24 @@ private void SetHasFocus (bool newHasFocus, View view, bool force = false) /// /// must also have set to . /// + /// + /// When set to , if this view is focused, the focus will be set to the next focusable view. + /// + /// + /// When set to , the will be set to -1. + /// + /// + /// When set to , the values of and for all + /// subviews will be cached so that when is set back to , the subviews + /// will be restored to their previous values. + /// /// public bool CanFocus { get => _canFocus; set { - if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) + if (!_addingViewSoCanFocusAlsoUpdatesSuperView && IsInitialized && SuperView?.CanFocus == false && value) { throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); } @@ -204,7 +210,8 @@ public bool CanFocus TabIndex = -1; break; - case true when SuperView?.CanFocus == false && _addingView: + + case true when SuperView?.CanFocus == false && _addingViewSoCanFocusAlsoUpdatesSuperView: SuperView.CanFocus = true; break; @@ -225,8 +232,9 @@ public bool CanFocus if (!_canFocus && HasFocus) { SetHasFocus (false, this); - SuperView?.EnsureFocus (); + SuperView?.FocusFirstOrLast (); + // If EnsureFocus () didn't set focus to a view, focus the next focusable view in the application if (SuperView is { Focused: null }) { SuperView.FocusNext (); @@ -248,6 +256,7 @@ public bool CanFocus { if (!value) { + // Cache the old CanFocus and TabIndex so that they can be restored when CanFocus is changed back to true view._oldCanFocus = view.CanFocus; view._oldTabIndex = view._tabIndex; view.CanFocus = false; @@ -255,19 +264,20 @@ public bool CanFocus } else { - if (_addingView) + if (_addingViewSoCanFocusAlsoUpdatesSuperView) { - view._addingView = true; + view._addingViewSoCanFocusAlsoUpdatesSuperView = true; } + // Restore the old CanFocus and TabIndex to the values they held before CanFocus was set to false view.CanFocus = view._oldCanFocus; view._tabIndex = view._oldTabIndex; - view._addingView = false; + view._addingViewSoCanFocusAlsoUpdatesSuperView = false; } } } - if (this is Toplevel && Application.Current.Focused != this) + if (this is Toplevel && Application.Current!.Focused != this) { ApplicationOverlapped.BringOverlappedTopToFront (); } @@ -278,6 +288,18 @@ public bool CanFocus } } + /// Raised when has been changed. + /// + /// Raised by the virtual method. + /// + public event EventHandler CanFocusChanged; + + /// Invoked when the property from a view is changed. + /// + /// Raises the event. + /// + public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); } + /// Returns the currently focused Subview inside this view, or if nothing is focused. /// The currently focused Subview. public View Focused { get; private set; } @@ -361,7 +383,7 @@ private void SetFocus (View view) View f = Focused; Focused = view; Focused.SetHasFocus (true, f); - Focused.EnsureFocus (); + Focused.FocusFirstOrLast (); // Send focus upwards if (SuperView is { }) @@ -398,11 +420,11 @@ public void SetFocus () } /// - /// If there is no focused subview, calls or based on + /// INTERNAL helper for calling or based on /// . - /// does nothing. + /// FocusDirection is not public. This API is thus non-deterministic from a public API perspective. /// - public void EnsureFocus () + internal void FocusFirstOrLast () { if (Focused is null && _subviews?.Count > 0) { @@ -418,10 +440,14 @@ public void EnsureFocus () } /// - /// Focuses the last focusable view in if one exists. If there are no views in + /// Focuses the first focusable view in if one exists. If there are no views in /// then the focus is set to the view itself. /// - public void FocusFirst (bool overlapped = false) + /// + /// If , only subviews where has set + /// will be considered. + /// + public void FocusFirst (bool overlappedOnly = false) { if (!CanBeVisible (this)) { @@ -435,7 +461,7 @@ public void FocusFirst (bool overlapped = false) return; } - foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped))) + foreach (View view in _tabIndexes.Where (v => !overlappedOnly || v.Arrangement.HasFlag (ViewArrangement.Overlapped))) { if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { @@ -450,7 +476,11 @@ public void FocusFirst (bool overlapped = false) /// Focuses the last focusable view in if one exists. If there are no views in /// then the focus is set to the view itself. /// - public void FocusLast (bool overlapped = false) + /// + /// If , only subviews where has set + /// will be considered. + /// + public void FocusLast (bool overlappedOnly = false) { if (!CanBeVisible (this)) { @@ -464,7 +494,7 @@ public void FocusLast (bool overlapped = false) return; } - foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ()) + foreach (View view in _tabIndexes.Where (v => !overlappedOnly || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ()) { if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index d340a5aa4b..8eb5a93749 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -332,7 +332,7 @@ public virtual bool Enabled else { view.Enabled = view._oldEnabled; - view._addingView = _enabled; + view._addingViewSoCanFocusAlsoUpdatesSuperView = _enabled; } } } diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index dd962bfc19..2dbca01178 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -309,7 +309,6 @@ private void Split (int offset, Orientation orientation, TabView sender, OpenedF tab.CloneTo (newTabView); newTile.ContentView.Add (newTabView); - newTabView.EnsureFocus (); newTabView.FocusFirst (); newTabView.FocusNext (); } From ccec0eec1191140c945c6419a03e57126793f910 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 25 Jul 2024 12:16:10 -0600 Subject: [PATCH 94/96] Documenting focus code --- Terminal.Gui/View/View.Navigation.cs | 21 ++++++++++++++++----- Terminal.Gui/Views/Toplevel.cs | 2 +- UnitTests/Views/TextFieldTests.cs | 6 +++--- UnitTests/Views/TreeTableSourceTests.cs | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs index 3cc0c1f116..edbe5eabc8 100644 --- a/Terminal.Gui/View/View.Navigation.cs +++ b/Terminal.Gui/View/View.Navigation.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Terminal.Gui; public partial class View // Focus and cross-view navigation management (TabStop, TabIndex, etc...) @@ -603,7 +605,7 @@ public bool FocusPrev () /// Focuses the next view in . If there is no next view, the focus is set to the view /// itself. /// - /// if next was focused, otherwise. + /// if focus was changed to another subview (or stayed on this one), otherwise. public bool FocusNext () { if (!CanBeVisible (this)) @@ -622,7 +624,7 @@ public bool FocusNext () { FocusFirst (); - return Focused != null; + return Focused is { }; } int focusedIdx = -1; @@ -633,18 +635,25 @@ public bool FocusNext () if (w.HasFocus) { + // A subview has focus, tell *it* to FocusNext if (w.FocusNext ()) { + // The subview changed which of it's subviews had focus return true; } + Debug.Assert (w.HasFocus); + + // The subview has no subviews that can be next. Cache that we found a focused subview. focusedIdx = i; continue; } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) + // The subview does not have focus, but at least one other that can. Can this one be focused? + if (focusedIdx != -1 && w.CanFocus && w._tabStop && w.Visible && w.Enabled) { + // Make w Leave Focused.SetHasFocus (false, w); //// If the focused view is overlapped don't focus on the next if it's not overlapped. @@ -659,6 +668,7 @@ public bool FocusNext () // continue; //} + // QUESTION: Why do we check these again here? if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { w.FocusFirst (); @@ -673,7 +683,7 @@ public bool FocusNext () // There's no next view in tab indexes. if (Focused is { }) { - // Leave Focused + // Leave Focused.SetHasFocus (false, this); //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)) @@ -682,7 +692,8 @@ public bool FocusNext () // return true; //} - // Signal to caller no next view was found + // Signal to caller no next view was found; this will enable it to make a peer + // or view up the superview hierarchy have focus. Focused = null; } diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 26fe724c80..435bd6fc74 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -430,7 +430,7 @@ public override void OnDrawContent (Rectangle viewport) { if (Focused is null) { - EnsureFocus (); + FocusFirstOrLast (); } return null; diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index ed3a357759..4096ee4041 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -78,7 +78,7 @@ string GetContents () public void Cancel_TextChanging_ThenBackspace () { var tf = new TextField (); - tf.EnsureFocus (); + tf.FocusFirstOrLast (); tf.NewKeyDownEvent (Key.A.WithShift); Assert.Equal ("A", tf.Text); @@ -929,7 +929,7 @@ public void Paste_Always_Clear_The_SelectedText () public void Backspace_From_End () { var tf = new TextField { Text = "ABC" }; - tf.EnsureFocus (); + tf.FocusFirstOrLast (); Assert.Equal ("ABC", tf.Text); tf.BeginInit (); tf.EndInit (); @@ -956,7 +956,7 @@ public void Backspace_From_End () public void Backspace_From_Middle () { var tf = new TextField { Text = "ABC" }; - tf.EnsureFocus (); + tf.FocusFirstOrLast (); tf.CursorPosition = 2; Assert.Equal ("ABC", tf.Text); diff --git a/UnitTests/Views/TreeTableSourceTests.cs b/UnitTests/Views/TreeTableSourceTests.cs index a1a319b1b5..02625b6cf4 100644 --- a/UnitTests/Views/TreeTableSourceTests.cs +++ b/UnitTests/Views/TreeTableSourceTests.cs @@ -289,7 +289,7 @@ private TableView GetTreeTable (out TreeView tree) var top = new Toplevel (); top.Add (tableView); - top.EnsureFocus (); + top.FocusFirstOrLast (); Assert.Equal (tableView, top.MostFocused); return tableView; From 15e6b4eff2cc759ca9d3c6f36ebb8d95d38c160b Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 25 Jul 2024 14:40:11 -0600 Subject: [PATCH 95/96] Documenting focus code --- Terminal.Gui/View/View.Navigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs index edbe5eabc8..292cb5c523 100644 --- a/Terminal.Gui/View/View.Navigation.cs +++ b/Terminal.Gui/View/View.Navigation.cs @@ -653,7 +653,7 @@ public bool FocusNext () // The subview does not have focus, but at least one other that can. Can this one be focused? if (focusedIdx != -1 && w.CanFocus && w._tabStop && w.Visible && w.Enabled) { - // Make w Leave + // Make Focused Leave Focused.SetHasFocus (false, w); //// If the focused view is overlapped don't focus on the next if it's not overlapped. From 78f527e4a19b0577d5efe4e8b9cc2d316f75f09f Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 25 Jul 2024 14:54:58 -0600 Subject: [PATCH 96/96] Fixed post merge errors. --- Terminal.Gui/Application/Application .Screen.cs | 8 ++++++++ UnitTests/View/DrawTests.cs | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Application/Application .Screen.cs b/Terminal.Gui/Application/Application .Screen.cs index 7770ae13c8..a3d56da067 100644 --- a/Terminal.Gui/Application/Application .Screen.cs +++ b/Terminal.Gui/Application/Application .Screen.cs @@ -3,6 +3,14 @@ namespace Terminal.Gui; public static partial class Application // Screen related stuff { + /// + /// Gets the size of the screen. This is the size of the screen as reported by the . + /// + /// + /// If the has not been initialized, this will return a default size of 2048x2048; useful for unit tests. + /// + public static Rectangle Screen => Driver?.Screen ?? new (0, 0, 2048, 2048); + /// Invoked when the terminal's size changed. The new size of the terminal is provided. /// /// Event handlers can set to to prevent diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 955bf46516..2e02ecc1ad 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -393,7 +393,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport () var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application!.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 2, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -418,7 +418,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom () view.Border.Thickness = new Thickness (1, 1, 1, 0); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application!.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 2, 1), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -436,7 +436,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left () view.Border.Thickness = new Thickness (0, 1, 1, 1); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application!.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 1, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -461,7 +461,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right () view.Border.Thickness = new Thickness (1, 1, 0, 1); view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application!.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 1, 2), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport); @@ -487,7 +487,7 @@ public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top () view.BeginInit (); view.EndInit (); - view.SetRelativeLayout (Application!.Screen.Size); + view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (0, 0, 2, 1), view.Frame); Assert.Equal (Rectangle.Empty, view.Viewport);