diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 90152452f..a4a8b7060 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1411,7 +1411,7 @@ public class TextFormatter if (textFormatter is { Alignment: Alignment.Center }) { - return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); + return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection); } return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection); @@ -1426,7 +1426,7 @@ public class TextFormatter if (textFormatter is { VerticalAlignment: Alignment.Center }) { - return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); + return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection); } return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection); @@ -1451,7 +1451,7 @@ public class TextFormatter } else if (textFormatter is { Alignment: Alignment.Center }) { - return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); + return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection); } else if (GetRuneWidth (text, tabWidth, textDirection) > width) { @@ -1470,7 +1470,7 @@ public class TextFormatter } else if (textFormatter is { VerticalAlignment: Alignment.Center }) { - return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection); + return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection); } else if (runes.Count - zeroLength > width) { @@ -1526,7 +1526,7 @@ public class TextFormatter } else { - textCount = words.Sum (arg => arg.GetRuneCount ()); + textCount = words.Sum (arg => arg.GetRuneCount ()) - text.EnumerateRunes ().Sum (r => r.GetColumns () == 0 ? 1 : 0); } int spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0; @@ -1936,7 +1936,7 @@ public class TextFormatter private static int GetRuneWidth (Rune rune, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom) { - int runeWidth = IsHorizontalDirection (textDirection) ? rune.GetColumns () : 1; + int runeWidth = IsHorizontalDirection (textDirection) ? rune.GetColumns () : rune.GetColumns () == 0 ? 0 : 1; if (rune.Value == '\t') { diff --git a/Terminal.Gui/View/View.Keyboard.cs b/Terminal.Gui/View/View.Keyboard.cs index 642c34635..3028402a9 100644 --- a/Terminal.Gui/View/View.Keyboard.cs +++ b/Terminal.Gui/View/View.Keyboard.cs @@ -690,11 +690,11 @@ public partial class View // Keyboard APIs #if DEBUG - if (Application.KeyBindings.TryGet (key, out KeyBinding b)) - { - Debug.WriteLine ( - $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command."); - } + //if (Application.KeyBindings.TryGet (key, out KeyBinding b)) + //{ + // Debug.WriteLine ( + // $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command."); + //} // TODO: This is a "prototype" debug check. It may be too annoying vs. useful. // Scour the bindings up our View hierarchy diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index b0991711b..67c604319 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -561,8 +561,9 @@ public partial class View // Layout APIs { SuperView?.SetNeedsDraw (); } - else + else if (Application.TopLevels.Count == 1) { + // If this is the only TopLevel, we need to redraw the screen Application.ClearScreenNextIteration = true; } } @@ -801,7 +802,7 @@ public partial class View // Layout APIs { foreach (Toplevel tl in Application.TopLevels) { - // tl.SetNeedsDraw (); + // tl.SetNeedsDraw (); } } diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index 7ed43499a..1c37ced48 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -42,7 +42,7 @@ }, "All Views Tester": { "commandName": "Project", - "commandLineArgs": "\"All Views Tester\" -b" + "commandLineArgs": "\"All Views Tester\" -b -t 5000" }, "Charmap": { "commandName": "Project", diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs index 1d62c4ad7..b98d44b36 100644 --- a/UICatalog/Scenario.cs +++ b/UICatalog/Scenario.cs @@ -148,14 +148,15 @@ public class Scenario : IDisposable /// public virtual void Main () { } - private const uint MAX_NATURAL_ITERATIONS = 500; // not including needed for demo keys - private const uint ABORT_TIMEOUT_MS = 2500; - private const int DEMO_KEY_PACING_MS = 1; // Must be non-zero + private const uint BENCHMARK_MAX_NATURAL_ITERATIONS = 500; // not including needed for demo keys + private const int BENCHMARK_KEY_PACING = 1; // Must be non-zero + + public static uint BenchmarkTimeout { get; set; } = 2500; private readonly object _timeoutLock = new (); private object? _timeout; private Stopwatch? _stopwatch; - private readonly BenchmarkResults _benchmarkResults = new BenchmarkResults (); + private readonly BenchmarkResults _benchmarkResults = new (); public void StartBenchmark () { @@ -178,7 +179,7 @@ public class Scenario : IDisposable return _benchmarkResults; } - private List _demoKeys; + private List? _demoKeys; private int _currentDemoKey = 0; private void OnApplicationOnInitializedChanged (object? s, EventArgs a) @@ -187,7 +188,7 @@ public class Scenario : IDisposable { lock (_timeoutLock!) { - _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (ABORT_TIMEOUT_MS), ForceCloseCallback); + _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (BenchmarkTimeout), ForceCloseCallback); } Application.Iteration += OnApplicationOnIteration; @@ -218,7 +219,7 @@ public class Scenario : IDisposable private void OnApplicationOnIteration (object? s, IterationEventArgs a) { BenchmarkResults.IterationCount++; - if (BenchmarkResults.IterationCount > MAX_NATURAL_ITERATIONS + (_demoKeys.Count* DEMO_KEY_PACING_MS)) + if (BenchmarkResults.IterationCount > BENCHMARK_MAX_NATURAL_ITERATIONS + (_demoKeys.Count * BENCHMARK_KEY_PACING)) { Application.RequestStop (); } @@ -232,7 +233,7 @@ public class Scenario : IDisposable _demoKeys = GetDemoKeyStrokes (); Application.AddTimeout ( - new TimeSpan (0, 0, 0, 0, DEMO_KEY_PACING_MS), + new TimeSpan (0, 0, 0, 0, BENCHMARK_KEY_PACING), () => { if (_currentDemoKey >= _demoKeys.Count) @@ -271,7 +272,7 @@ public class Scenario : IDisposable } } - Debug.WriteLine ($@" Failed to Quit with {Application.QuitKey} after {ABORT_TIMEOUT_MS}ms and {BenchmarkResults.IterationCount} iterations. Force quit."); + Debug.WriteLine ($@" Failed to Quit with {Application.QuitKey} after {BenchmarkTimeout}ms and {BenchmarkResults.IterationCount} iterations. Force quit."); Application.RequestStop (); diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index fffa989f2..ffbf863b9 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -148,6 +148,10 @@ public class UICatalogApp benchmarkFlag.AddAlias ("-b"); benchmarkFlag.AddAlias ("--b"); + Option benchmarkTimeout = new Option ("--timeout", getDefaultValue: () => Scenario.BenchmarkTimeout, $"The maximum time in milliseconds to run a benchmark for. Default is {Scenario.BenchmarkTimeout}ms."); + benchmarkTimeout.AddAlias ("-t"); + benchmarkTimeout.AddAlias ("--t"); + Option resultsFile = new Option ("--file", "The file to save benchmark results to. If not specified, the results will be displayed in a TableView."); resultsFile.AddAlias ("-f"); resultsFile.AddAlias ("--f"); @@ -165,7 +169,7 @@ public class UICatalogApp var rootCommand = new RootCommand ("A comprehensive sample library for Terminal.Gui") { - scenarioArgument, benchmarkFlag, resultsFile, driverOption, + scenarioArgument, benchmarkFlag, benchmarkTimeout, resultsFile, driverOption, }; rootCommand.SetHandler ( @@ -176,6 +180,7 @@ public class UICatalogApp Scenario = context.ParseResult.GetValueForArgument (scenarioArgument), Driver = context.ParseResult.GetValueForOption (driverOption) ?? string.Empty, Benchmark = context.ParseResult.GetValueForOption (benchmarkFlag), + BenchmarkTimeout = context.ParseResult.GetValueForOption (benchmarkTimeout), ResultsFile = context.ParseResult.GetValueForOption (resultsFile) ?? string.Empty, /* etc. */ }; @@ -197,6 +202,8 @@ public class UICatalogApp return 0; } + Scenario.BenchmarkTimeout = _options.BenchmarkTimeout; + UICatalogMain (_options); return 0; @@ -332,6 +339,7 @@ public class UICatalogApp // regardless of what's in a config file. Application.ForceDriver = _forceDriver = options.Driver; + // If a Scenario name has been provided on the commandline // run it and exit when done. if (options.Scenario != "none") @@ -788,7 +796,7 @@ public class UICatalogApp { if (_statusBar.NeedsLayout) { - // throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout."); + // throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout."); } return _statusBar.Frame.Height; })), @@ -817,7 +825,7 @@ public class UICatalogApp { if (_statusBar.NeedsLayout) { - // throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout."); + // throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout."); } return _statusBar.Frame.Height; })), @@ -1378,6 +1386,8 @@ public class UICatalogApp public string Scenario; + public uint BenchmarkTimeout; + public bool Benchmark; public string ResultsFile; diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index d83ab398f..5435d0204 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -4629,6 +4629,90 @@ ssb Assert.Equal (expectedWrappedText, wrappedText); } + [Theory] + [InlineData ( + "Les Mise\u0301rables", + 14, + -1, + false, + new [] { "Les Misérables" }, + "Les Misérables" + )] + [InlineData ( + "Les Mise\u0328\u0301rables", + 14, + -2, + false, + new [] { "Les Misę́rables" }, + "Les Misę́rables" + )] + public void Format_Combining_Marks_Alignments ( + string text, + int maxWidth, + int widthOffset, + bool wrap, + IEnumerable resultLines, + string expectedText + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + + // Horizontal text direction + foreach (Alignment alignment in Enum.GetValues (typeof (Alignment))) + { + TextFormatter tf = new () { Text = text, ConstrainToSize = new (maxWidth, 1), WordWrap = wrap, Alignment = alignment }; + + List list = TextFormatter.Format ( + text, + maxWidth, + alignment, + wrap, + tf.PreserveTrailingSpaces, + tf.TabWidth, + tf.Direction, + tf.MultiLine, + tf); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + var formattedText = string.Empty; + + foreach (string txt in list) + { + formattedText += txt; + } + + Assert.Equal (expectedText, formattedText); + } + + // Vertical text direction + foreach (Alignment alignment in Enum.GetValues (typeof (Alignment))) + { + TextFormatter tf = new () + { Text = text, ConstrainToSize = new (1, maxWidth), WordWrap = wrap, VerticalAlignment = alignment, Direction = TextDirection.TopBottom_LeftRight }; + + List list = TextFormatter.Format ( + text, + maxWidth, + alignment, + wrap, + tf.PreserveTrailingSpaces, + tf.TabWidth, + tf.Direction, + tf.MultiLine, + tf); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + var formattedText = string.Empty; + + foreach (string txt in list) + { + formattedText += txt; + } + + Assert.Equal (expectedText, formattedText); + } + } + public static IEnumerable FormatEnvironmentNewLine => new List {