diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
index 4ca317ce0..502cfb89f 100644
--- a/.github/workflows/dotnet-core.yml
+++ b/.github/workflows/dotnet-core.yml
@@ -2,19 +2,24 @@ name: Build & Test Terminal.Gui with .NET Core
on:
push:
- branches: [ main, develop, v2_develop ]
+ branches: [ v2, v2_develop ]
+ paths-ignore:
+ - '**.md'
pull_request:
- branches: [ main, develop, v2_develop ]
-
+ branches: [ v2, v2_develop ]
+ paths-ignore:
+ - '**.md'
+
jobs:
build_and_test:
- runs-on: windows-latest
-
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- - uses: actions/checkout@v4
- - name: Setup dotnet
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
@@ -34,19 +39,19 @@ jobs:
mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
# Note: this step is currently not writing to the gist for some reason
- - name: Create Test Coverage Badge
- uses: simon-k/dotnet-code-coverage-badge@v1.0.0
- id: create_coverage_badge
- with:
- label: Unit Test Coverage
- color: brightgreen
- path: UnitTests/TestResults/coverage.opencover.xml
- gist-filename: code-coverage.json
- # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27
- gist-id: 90ef67a684cb71db1817921a970f8d27
- gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }}
+ # - name: Create Test Coverage Badge
+ # uses: simon-k/dotnet-code-coverage-badge@v1.0.0
+ # id: create_coverage_badge
+ # with:
+ # label: Unit Test Coverage
+ # color: brightgreen
+ # path: UnitTests/TestResults/coverage.opencover.xml
+ # gist-filename: code-coverage.json
+ # # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27
+ # gist-id: 90ef67a684cb71db1817921a970f8d27
+ # gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }}
- - name: Print Code Coverage
- run: |
- echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%"
- echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}"
+ # - name: Print Code Coverage
+ # run: |
+ # echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%"
+ # echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 2c80e6e82..faf867527 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -25,7 +25,7 @@ jobs:
includePrerelease: true
- name: Determine Version
- uses: gittools/actions/gitversion/execute@v0
+ uses: gittools/actions/gitversion/execute@v1
with:
useConfigFile: true
#additionalArguments: /b develop
diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index 9e501f22c..947cab271 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -975,7 +975,7 @@ public static partial class Application
if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ())
{
- state.Toplevel.SetNeedsDisplay();
+ state.Toplevel.SetNeedsDisplay ();
state.Toplevel.Draw ();
Driver.UpdateScreen ();
@@ -1439,4 +1439,62 @@ public static partial class Application
}
#endregion Toplevel handling
+
+ ///
+ /// Gets a string representation of the Application as rendered by .
+ ///
+ /// A string representation of the Application
+ public new static string ToString ()
+ {
+ ConsoleDriver driver = Driver;
+
+ if (driver is null)
+ {
+ return string.Empty;
+ }
+
+ return ToString (driver);
+ }
+
+ ///
+ /// Gets a string representation of the Application rendered by the provided .
+ ///
+ /// The driver to use to render the contents.
+ /// A string representation of the Application
+ public static string ToString (ConsoleDriver driver)
+ {
+ var sb = new StringBuilder ();
+
+ Cell [,] contents = driver.Contents;
+
+ for (var r = 0; r < driver.Rows; r++)
+ {
+ for (var c = 0; c < driver.Cols; c++)
+ {
+ Rune rune = contents [r, c].Rune;
+
+ if (rune.DecodeSurrogatePair (out char [] sp))
+ {
+ sb.Append (sp);
+ }
+ else
+ {
+ sb.Append ((char)rune.Value);
+ }
+
+ if (rune.GetColumns () > 1)
+ {
+ c++;
+ }
+
+ // See Issue #2616
+ //foreach (var combMark in contents [r, c].CombiningMarks) {
+ // sb.Append ((char)combMark.Value);
+ //}
+ }
+
+ sb.AppendLine ();
+ }
+ return sb.ToString ();
+ }
}
diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs
index ad684470b..532c0af8a 100644
--- a/Terminal.Gui/Drawing/Thickness.cs
+++ b/Terminal.Gui/Drawing/Thickness.cs
@@ -1,4 +1,5 @@
-using System.Text.Json.Serialization;
+using System.Numerics;
+using System.Text.Json.Serialization;
namespace Terminal.Gui;
@@ -13,28 +14,18 @@ namespace Terminal.Gui;
/// frame,
/// with the thickness widths subtracted.
///
-/// Use the helper API ( to draw the frame with the specified thickness.
+///
+/// Use the helper API ( to draw the frame with the specified thickness.
+///
+///
+/// Thickness uses intenrally. As a result, there is a potential precision loss for very
+/// large numbers. This is typically not an issue for UI dimensions but could be relevant in other contexts.
+///
///
-public class Thickness : IEquatable
+public record struct Thickness
{
- /// Gets or sets the width of the lower side of the rectangle.
- [JsonInclude]
- public int Bottom;
-
- /// Gets or sets the width of the left side of the rectangle.
- [JsonInclude]
- public int Left;
-
- /// Gets or sets the width of the right side of the rectangle.
- [JsonInclude]
- public int Right;
-
- /// Gets or sets the width of the upper side of the rectangle.
- [JsonInclude]
- public int Top;
-
/// Initializes a new instance of the class with all widths set to 0.
- public Thickness () { }
+ public Thickness () { _sides = Vector4.Zero; }
/// Initializes a new instance of the class with a uniform width to each side.
///
@@ -56,35 +47,23 @@ public class Thickness : IEquatable
Bottom = bottom;
}
- // TODO: add operator overloads
- /// Gets an empty thickness.
- public static Thickness Empty => new (0);
+ private Vector4 _sides;
///
- /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides
- /// of the rectangle to half the specified value.
+ /// Adds the thickness widths of another to the current , returning a
+ /// new .
///
- public int Horizontal
- {
- get => Left + Right;
- set => Left = Right = value / 2;
- }
-
- ///
- /// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom
- /// sides of the rectangle to half the specified value.
- ///
- public int Vertical
- {
- get => Top + Bottom;
- set => Top = Bottom = value / 2;
- }
-
- // IEquitable
- /// Indicates whether the current object is equal to another object of the same type.
///
- /// true if the current object is equal to the other parameter; otherwise, false.
- public bool Equals (Thickness other) { return other is { } && Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; }
+ ///
+ public readonly Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }
+
+ /// Gets or sets the width of the lower side of the rectangle.
+ [JsonInclude]
+ public int Bottom
+ {
+ get => (int)_sides.W;
+ set => _sides.W = value;
+ }
///
/// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside
@@ -100,22 +79,6 @@ public class Thickness : IEquatable
return outside.Contains (location) && !inside.Contains (location);
}
- ///
- /// Adds the thickness widths of another to the current , returning a
- /// new .
- ///
- ///
- ///
- public Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }
-
- ///
- /// Adds the thickness widths of another to another .
- ///
- ///
- ///
- ///
- public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); }
-
/// Draws the rectangle with an optional diagnostics label.
///
/// If is set to
@@ -240,31 +203,8 @@ public class Thickness : IEquatable
return GetInside (rect);
}
- /// Determines whether the specified object is equal to the current object.
- /// The object to compare with the current object.
- /// true if the specified object is equal to the current object; otherwise, false.
- public override bool Equals (object obj)
- {
- //Check for null and compare run-time types.
- if (obj is null || !GetType ().Equals (obj.GetType ()))
- {
- return false;
- }
-
- return Equals ((Thickness)obj);
- }
-
- ///
- public override int GetHashCode ()
- {
- var hashCode = 1380952125;
- hashCode = hashCode * -1521134295 + Left.GetHashCode ();
- hashCode = hashCode * -1521134295 + Right.GetHashCode ();
- hashCode = hashCode * -1521134295 + Top.GetHashCode ();
- hashCode = hashCode * -1521134295 + Bottom.GetHashCode ();
-
- return hashCode;
- }
+ /// Gets an empty thickness.
+ public static Thickness Empty => new (0);
///
/// Returns a rectangle describing the location and size of the inside area of with the
@@ -289,23 +229,59 @@ public class Thickness : IEquatable
return new (x, y, width, height);
}
- ///
- public static bool operator == (Thickness left, Thickness right) { return EqualityComparer.Default.Equals (left, right); }
+ ///
+ /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides
+ /// of the rectangle to half the specified value.
+ ///
+ public int Horizontal
+ {
+ get => Left + Right;
+ set => Left = Right = value / 2;
+ }
- ///
- public static bool operator != (Thickness left, Thickness right) { return !(left == right); }
+ /// Gets or sets the width of the left side of the rectangle.
+ [JsonInclude]
+ public int Left
+ {
+ get => (int)_sides.X;
+ set => _sides.X = value;
+ }
+
+ ///
+ /// Adds the thickness widths of another to another .
+ ///
+ ///
+ ///
+ ///
+ public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); }
+
+ /// Gets or sets the width of the right side of the rectangle.
+ [JsonInclude]
+ public int Right
+ {
+ get => (int)_sides.Z;
+ set => _sides.Z = value;
+ }
+
+ /// Gets or sets the width of the upper side of the rectangle.
+ [JsonInclude]
+ public int Top
+ {
+ get => (int)_sides.Y;
+ set => _sides.Y = value;
+ }
/// Returns the thickness widths of the Thickness formatted as a string.
/// The thickness widths as a string.
public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; }
- private int validate (int width)
+ ///
+ /// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom
+ /// sides of the rectangle to half the specified value.
+ ///
+ public int Vertical
{
- if (width < 0)
- {
- throw new ArgumentException ("Thickness widths cannot be negative.");
- }
-
- return width;
+ get => Top + Bottom;
+ set => Top = Bottom = value / 2;
}
}
diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs
index 32b68eb37..899a35986 100644
--- a/UnitTests/TestHelpers.cs
+++ b/UnitTests/TestHelpers.cs
@@ -1,32 +1,28 @@
-using System.Collections;
-using System.Diagnostics;
+using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
-using UICatalog;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Terminal.Gui;
-// This class enables test functions annotated with the [AutoInitShutdown] attribute to
-// automatically call Application.Init at start of the test and Application.Shutdown after the
-// test exits.
-//
-// This is necessary because a) Application is a singleton and Init/Shutdown must be called
-// as a pair, and b) all unit test functions should be atomic..
+///
+/// This class enables test functions annotated with the [AutoInitShutdown] attribute to
+/// automatically call Application.Init at start of the test and Application.Shutdown after the
+/// test exits.
+/// This is necessary because a) Application is a singleton and Init/Shutdown must be called
+/// as a pair, and b) all unit test functions should be atomic..
+///
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
{
- private readonly Type _driverType;
-
///
/// Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and Application.Shutdown
/// are automatically called Before/After a test runs.
///
/// If true, Application.Init will be called Before the test runs.
- /// If true, Application.Shutdown will be called After the test runs.
///
/// Determines which ConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver)
/// will be used when Application.Init is called. If null FakeDriver will be used. Only valid if
@@ -65,7 +61,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
ConfigurationManager.Locations = configLocation;
}
- private bool AutoInit { get; }
+ private readonly Type _driverType;
public override void After (MethodInfo methodUnderTest)
{
@@ -102,6 +98,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
ConfigurationManager.Reset ();
#if DEBUG_IDISPOSABLE
+
// Clear out any lingering Responder instances from previous tests
if (Responder.Instances.Count == 0)
{
@@ -115,6 +112,8 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType));
}
}
+
+ private bool AutoInit { get; }
}
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
@@ -178,8 +177,8 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
public class TestDateAttribute : BeforeAfterTestAttribute
{
- private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture;
public TestDateAttribute () { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; }
+ private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture;
public override void After (MethodInfo methodUnderTest)
{
@@ -238,12 +237,12 @@ internal partial class TestHelpers
switch (match.Count)
{
case 0:
- throw new Exception (
- $"{DriverContentsToString (driver)}\n"
- + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n"
- + $" Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n"
- + $" But Was: "
- );
+ throw new (
+ $"{Application.ToString (driver)}\n"
+ + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n"
+ + $" Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n"
+ + $" But Was: "
+ );
case > 1:
throw new ArgumentException (
$"Bad value for expectedColors, {match.Count} Attributes had the same Value"
@@ -255,12 +254,12 @@ internal partial class TestHelpers
if (colorUsed != userExpected)
{
- throw new Exception (
- $"{DriverContentsToString (driver)}\n"
- + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n"
- + $" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n"
- + $" But Was: {colorUsed} ({val})\n"
- );
+ throw new (
+ $"{Application.ToString (driver)}\n"
+ + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n"
+ + $" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n"
+ + $" But Was: {colorUsed} ({val})\n"
+ );
}
}
@@ -282,7 +281,7 @@ internal partial class TestHelpers
)
{
#pragma warning restore xUnit1013 // Public method should be marked as test
- string actualLook = DriverContentsToString (driver);
+ var actualLook = Application.ToString (driver ?? Application.Driver);
if (string.Equals (expectedLook, actualLook))
{
@@ -314,8 +313,7 @@ internal partial class TestHelpers
}
///
- /// Asserts that the driver contents are equal to the expected look, and that the cursor is at the expected
- /// position.
+ /// Asserts that the driver contents are equal to the provided string.
///
///
///
@@ -337,7 +335,6 @@ internal partial class TestHelpers
Cell [,] contents = driver.Contents;
-
for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++)
{
List runes = [];
@@ -353,7 +350,7 @@ internal partial class TestHelpers
x = colIndex;
y = rowIndex;
- for (int i = 0; i < colIndex; i++)
+ for (var i = 0; i < colIndex; i++)
{
runes.InsertRange (i, [SpaceRune]);
}
@@ -433,7 +430,7 @@ internal partial class TestHelpers
if (string.Equals (expectedLook, actualLook))
{
- return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+ return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
}
// standardize line endings for the comparison
@@ -453,7 +450,7 @@ internal partial class TestHelpers
Assert.Equal (expectedLook, actualLook);
- return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+ return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
}
#pragma warning disable xUnit1013 // Public method should be marked as test
@@ -483,278 +480,6 @@ internal partial class TestHelpers
}
#pragma warning restore xUnit1013 // Public method should be marked as test
- public static string DriverContentsToString (ConsoleDriver driver = null)
- {
- var sb = new StringBuilder ();
- driver ??= Application.Driver;
-
- Cell [,] contents = driver.Contents;
-
- for (var r = 0; r < driver.Rows; r++)
- {
- for (var c = 0; c < driver.Cols; c++)
- {
- Rune rune = contents [r, c].Rune;
-
- if (rune.DecodeSurrogatePair (out char [] sp))
- {
- sb.Append (sp);
- }
- else
- {
- sb.Append ((char)rune.Value);
- }
-
- if (rune.GetColumns () > 1)
- {
- c++;
- }
-
- // See Issue #2616
- //foreach (var combMark in contents [r, c].CombiningMarks) {
- // sb.Append ((char)combMark.Value);
- //}
- }
-
- sb.AppendLine ();
- }
-
- return sb.ToString ();
- }
-
- //// TODO: Update all tests that use GetALlViews to use GetAllViewsTheoryData instead
- ///// Gets a list of instances of all classes derived from View.
- ///// List of View objects
- //public static List GetAllViews ()
- //{
- // return typeof (View).Assembly.GetTypes ()
- // .Where (
- // type => type.IsClass
- // && !type.IsAbstract
- // && type.IsPublic
- // && type.IsSubclassOf (typeof (View))
- // )
- // .Select (type => CreateView (type, type.GetConstructor (Array.Empty ())))
- // .ToList ();
- //}
-
- //public class AllViewsData : IEnumerable