From 0df485a8904b0856f7e5865ef095008ea88f9474 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 9 Aug 2023 14:28:36 -0600 Subject: [PATCH] Fixes #666. Refactor `ConsoleDriver`s to simplify and remove duplicated code (#2612) * Added ClipRegion; cleaned up driver code * clip region unit tests * api docs * Moved color stuff from ConsoleDriver to Color.cs * Removes unused ConsoleDriver APIs * Code cleanup and Removes unused ConsoleDriver APIs * Code cleanup and Removes unused ConsoleDriver APIs * Work around https://github.com/gui-cs/Terminal.Gui/issues/2610 * adjusted unit tests * initial commit * Made Rows, Cols, Top, Left virtual * Made Clipboard non-virtual * Made EnableConsoleScrolling non-virtual * Made Contents non-virtual * Pulled Row/Col up * Made MoveTo virtual; fixed stupid FakeDriver cursor issue * Made CurrentAttribute non-virtual * Made SetAttribute non-virtual * Moved clipboard code out * Code cleanup * Removes dependecy on NStack from ConsoleDrivers - WIP * Fixed unit tests * Fixed unit tests * Added list of unit tests needed * Did some perf testing; tweaked code and charmap to address * Brough in code from PR #2264 (but commented) * Tons of code cleanup * Fighting with ScrollView * Fixing bugs * Fixed TabView tests * Fixed View.Visible test that was not really working * Fixed unit tests * Cleaned up clipboard APIs in attempt to track down unit test failure * Add Cut_Preserves_Selection test * Removed invalid code * Removed invalid test code; unit tests now pass * EscSeq* - Adjusted naming, added more sequences, made code more consistent, simplified, etc... * Added CSI_SetGraphicsRendition * NetDriver code cleanup * code cleanup * Cleaned up color handling in NetDriver * refixed tabview unit test * WindowsDriver color code cleanup * WindowsDriver color code cleanup * CursesDriver color code cleanup * CursesDriver - Adding _BOLD has no effect. Further up the stack we cast the return of ColorToCursesColor from int to short and the _BOLD values don't fit in a short. * CursesDriver color code - make code more accurate * CursesDriver color code - make code more accurate * Simplified ConsoleDriver.GetColors API * Simplified ConsoleDriver.GetColors API further * Improved encapslation of Attribute; prep for TrueColor & other attributes like blink * Fixes #2249. CharacterMap isn't refreshing well non-BMP code points on scroll. * Use GetRange to take some of the runes before convert to string. * Attempting to fix unit tests not being cleaned up * Fixes #2658 - ConsoleDriver.IsRuneSupported * Fixes #2658 - ConsoleDriver.IsRuneSupported (for WindowsDriver) * Check all the range values and not only the max value. * Reducing code. * Fixes #2674 - Unit test process doesn't exit * Changed Cell to support IsDirty and list of Runes * add support for rendering TrueColor output on Windows merging veeman & tznind code * add colorconverter changes * fixed merged v2_develop * Fixing merge bugs * Fixed merge bugs * Fixed merge bugs - all unit tests pass * Debugging netdriver * More netdriver diag * API docs for escutils * Update unicode scenario to stress more stuff * Contents: Now a 2D array of Cells; WIP * AddRune and ClearContents no longer virtual/abstract * WindowsDriver renders correctly again * Progress on Curses * Progress on Curses * broke windowsdriver * Cleaned up FakeMainLoop * Cleaned up some build warnings * Removed _init from AutoInitShutdown as it's not needed anymore * Removed unused var * Removed unused var * Fixed nullabiltiy warning in LineCanvas * Fixed charmap crash * Fixes #2758 in v2 * Port testonfail fix to v2 * Remove EnableConsoleScrolling * Backport #2764 from develop (clear last line) * Remove uneeded usings * Progress on unicode * Merged in changes from PR #2786, Fixes #2784 * revamp charmap rendering * Charmap option to show glyph widths * Fixed issue with wide glpyhs being overwritten * Fixed charmap startcodepoint change issue * Added abiltiy to see ncurses verison/lib * Fought with CursesDriver; giving up for now. See notes. * Leverage Wcwidth nuget library instaed of our own tables * enhanced charmap Details dialog * Final attempt at fixing curses --------- Co-authored-by: BDisp Co-authored-by: adstep --- .github/CODEOWNERS | 4 +- .github/workflows/dotnet-core.yml | 1 + .github/workflows/publish.yml | 1 + .gitignore | 1 + Terminal.Gui/Application.cs | 96 +- Terminal.Gui/Clipboard/Clipboard.cs | 196 +- Terminal.Gui/Clipboard/ClipboardBase.cs | 16 +- Terminal.Gui/Clipboard/IClipboard.cs | 4 - .../Configuration/AttributeJsonConverter.cs | 29 +- .../Configuration/ColorJsonConverter.cs | 2 +- .../Configuration/ConfigurationManager.cs | 3 +- Terminal.Gui/Configuration/ThemeScope.cs | 6 +- .../Configuration/TrueColorJsonConverter.cs | 47 + Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 1502 ++----- .../CursesDriver/ClipboardImpl.cs | 225 + .../CursesDriver/CursesDriver.cs | 1719 +++----- .../CursesDriver/UnixMainLoop.cs | 8 +- .../CursesDriver/UnmanagedLibrary.cs | 10 +- .../ConsoleDrivers/CursesDriver/binding.cs | 53 +- .../ConsoleDrivers/CursesDriver/handles.cs | 7 +- .../ConsoleDrivers/EscSeqUtils/EscSeqReq.cs | 114 + .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 1165 +++++ .../ConsoleDrivers/FakeDriver/FakeConsole.cs | 3872 ++++++++--------- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 1062 ++--- .../ConsoleDrivers/FakeDriver/FakeMainLoop.cs | 113 +- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2605 +++++------ Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 3372 +++++++------- Terminal.Gui/Drawing/Cell.cs | 19 +- Terminal.Gui/Drawing/Color.cs | 845 ++++ Terminal.Gui/Drawing/Glyphs.cs | 15 + Terminal.Gui/Drawing/LineCanvas.cs | 14 +- Terminal.Gui/Input/Command.cs | 2 +- Terminal.Gui/Input/EscSeqUtils/EscSeqReq.cs | 109 - Terminal.Gui/Input/EscSeqUtils/EscSeqUtils.cs | 907 ---- Terminal.Gui/MainLoop.cs | 39 +- Terminal.Gui/Resources/config.json | 1 - Terminal.Gui/Terminal.Gui.csproj | 12 +- Terminal.Gui/Text/RuneExtensions.cs | 146 +- Terminal.Gui/View/Frame.cs | 9 +- Terminal.Gui/View/ViewDrawing.cs | 233 +- Terminal.Gui/View/ViewLayout.cs | 9 +- Terminal.Gui/View/ViewSubViews.cs | 17 +- Terminal.Gui/Views/CheckBox.cs | 14 +- Terminal.Gui/Views/ColorPicker.cs | 9 +- Terminal.Gui/Views/HexView.cs | 4 +- Terminal.Gui/Views/Menu.cs | 1 - Terminal.Gui/Views/ScrollView.cs | 1025 +++-- Terminal.Gui/Views/StatusBar.cs | 3 +- Terminal.Gui/Views/TabView.cs | 2 +- Terminal.Gui/Views/TextField.cs | 26 +- Terminal.Gui/Views/TextView.cs | 20 +- Terminal.Gui/Views/TileView.cs | 2 +- Terminal.Gui/Views/Toplevel.cs | 10 +- Terminal.Gui/Views/ToplevelOverlapped.cs | 2 +- Terminal.sln | 4 +- UICatalog/Properties/launchSettings.json | 3 + UICatalog/Scenarios/BasicColors.cs | 4 +- UICatalog/Scenarios/CharacterMap.cs | 363 +- UICatalog/Scenarios/Generic.cs | 7 +- UICatalog/Scenarios/Images.cs | 136 + UICatalog/Scenarios/LineDrawing.cs | 5 +- UICatalog/Scenarios/ProgressBarStyles.cs | 4 +- UICatalog/Scenarios/TableEditor.cs | 1 - UICatalog/Scenarios/TrueColors.cs | 116 + UICatalog/Scenarios/Unicode.cs | 18 +- UICatalog/UICatalog.cs | 31 +- UnitTests/Application/ApplicationTests.cs | 34 +- UnitTests/Application/MainLoopTests.cs | 10 +- UnitTests/AssemblyInfo.cs | 10 - UnitTests/Clipboard/ClipboardTests.cs | 4 - .../Configuration/ConfigurationMangerTests.cs | 58 +- UnitTests/Configuration/JsonConverterTests.cs | 63 +- UnitTests/Configuration/SettingsScopeTests.cs | 6 - UnitTests/ConsoleDrivers/AddRuneTests.cs | 196 + UnitTests/ConsoleDrivers/AttributeTests.cs | 89 +- UnitTests/ConsoleDrivers/ClipRegionTests.cs | 116 + .../ConsoleDrivers/ConsoleDriverTests.cs | 13 - .../ConsoleDrivers/ConsoleScrolllingTests.cs | 122 +- UnitTests/ConsoleDrivers/ContentsTests.cs | 129 + UnitTests/ConsoleDrivers/KeyTests.cs | 70 +- UnitTests/FileServices/FileDialogTests.cs | 18 +- UnitTests/Input/EscSeqReqTests.cs | 64 +- UnitTests/Input/EscSeqUtilsTests.cs | 40 +- UnitTests/TestHelpers.cs | 226 +- UnitTests/Text/RuneTests.cs | 146 +- UnitTests/Text/StringTests.cs | 4 +- UnitTests/Text/UnicodeTests.cs | 11 +- UnitTests/UnitTests - Backup.csproj | 63 + UnitTests/UnitTests.csproj | 13 +- UnitTests/View/DrawTests.cs | 12 +- UnitTests/View/KeyboardTests.cs | 2 +- UnitTests/View/Layout/LayoutTests.cs | 6 +- UnitTests/View/ViewTests.cs | 45 +- UnitTests/Views/ListViewTests.cs | 2 +- UnitTests/Views/ProgressBarTests.cs | 2352 +++++----- UnitTests/Views/TabViewTests.cs | 5 +- UnitTests/Views/TableViewTests.cs | 2 +- UnitTests/Views/TextFieldTests.cs | 2 +- UnitTests/Views/ToplevelTests.cs | 8 +- UnitTests/Views/TreeViewTests.cs | 9 +- UnitTests/xunit.runner.json | 5 +- 101 files changed, 12555 insertions(+), 11820 deletions(-) create mode 100644 Terminal.Gui/Configuration/TrueColorJsonConverter.cs create mode 100644 Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs create mode 100644 Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs create mode 100644 Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs create mode 100644 Terminal.Gui/Drawing/Color.cs delete mode 100644 Terminal.Gui/Input/EscSeqUtils/EscSeqReq.cs delete mode 100644 Terminal.Gui/Input/EscSeqUtils/EscSeqUtils.cs create mode 100644 UICatalog/Scenarios/Images.cs create mode 100644 UICatalog/Scenarios/TrueColors.cs create mode 100644 UnitTests/ConsoleDrivers/AddRuneTests.cs create mode 100644 UnitTests/ConsoleDrivers/ClipRegionTests.cs create mode 100644 UnitTests/ConsoleDrivers/ContentsTests.cs create mode 100644 UnitTests/UnitTests - Backup.csproj diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2e79252a05..ed35267b2f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,7 +2,9 @@ # the repo. Unless a later match takes precedence, # @global-owner1 and @global-owner2 will be requested for # review when someone opens a pull request. -* @migueldeicaza @tig +* @tig + +/docs/ @tig @bdisp @tznind # Order is important; the last matching pattern takes the most # precedence. When someone opens a pull request that only diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 6c951c2453..39bc0906e4 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -29,6 +29,7 @@ jobs: - name: Test run: | + sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 34ef36b1c7..5149575773 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -50,6 +50,7 @@ jobs: #- name: Test to generate Code Coverage Report # run: | + # sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json # dotnet test --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings # mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ diff --git a/.gitignore b/.gitignore index 8376953074..8aab8590be 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ docfx/api docs/ UnitTests/TestResults +TestResults #git merge files *.orig diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index f18149b2a5..b571894a83 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.IO; using System.Text.Json.Serialization; -using static Terminal.Gui.ConfigurationManager; namespace Terminal.Gui { /// @@ -55,42 +54,7 @@ public static partial class Application { // For Unit testing - ignores UseSystemConsole internal static bool _forceFakeConsole; - - private static bool? _enableConsoleScrolling; - /// - /// The current used in the terminal. - /// - /// - /// - /// If (the default) the height of the Terminal.Gui application () - /// tracks to the height of the visible console view when the console is resized. In this case - /// scrolling in the console will be disabled and all will remain visible. - /// - /// - /// If then height of the Terminal.Gui application only tracks - /// the height of the visible console view when the console is made larger (the application will only grow in height, never shrink). - /// In this case console scrolling is enabled and the contents ( high) will scroll - /// as the console scrolls. - /// - /// This API was previously named 'HeightAsBuffer` but was renamed to make its purpose more clear. - /// - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool EnableConsoleScrolling { - get { - if (Driver == null) { - return _enableConsoleScrolling.HasValue && _enableConsoleScrolling.Value; - } - return Driver.EnableConsoleScrolling; - } - set { - _enableConsoleScrolling = value; - if (Driver == null) { - return; - } - Driver.EnableConsoleScrolling = value; - } - } - + private static List _cachedSupportedCultures; /// @@ -164,7 +128,9 @@ private static List GetSupportedCultures () // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. internal static void InternalInit (Func topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null, bool calledViaRunT = false) { - if (_initialized && driver == null) return; + if (_initialized && driver == null) { + return; + } if (_initialized) { throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown."); @@ -224,7 +190,6 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver MainLoop = new MainLoop (mainLoopDriver); try { - Driver.EnableConsoleScrolling = EnableConsoleScrolling; Driver.Init (OnTerminalResized); } catch (InvalidOperationException ex) { // This is a case where the driver is unable to initialize the console. @@ -277,6 +242,7 @@ static void ResetState () // BUGBUG: OverlappedTop is not cleared here, but it should be? + MainLoop?.Stop(); MainLoop = null; Driver?.End (); Driver = null; @@ -289,7 +255,6 @@ static void ResetState () NotifyStopRunState = null; _initialized = false; _mouseGrabView = null; - _enableConsoleScrolling = false; _lastMouseOwnerView = null; // Reset synchronization context to allow the user to run async/await, @@ -552,7 +517,8 @@ public static void Run (Toplevel view, Func errorHandler = null /// public static void Refresh () { - Driver.UpdateOffScreen (); + // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear + Driver.ClearContents(); View last = null; foreach (var v in _toplevels.Reverse ()) { if (v.Visible) { @@ -674,13 +640,14 @@ public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool Iteration?.Invoke (); EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); - if ((state.Toplevel != Current && Current?.Modal == true) - || (state.Toplevel != Current && Current?.Modal == false)) { + if (state.Toplevel != Current) { OverlappedTop?.OnDeactivate (state.Toplevel); state.Toplevel = Current; OverlappedTop?.OnActivate (state.Toplevel); Top.SetSubViewNeedsDisplay (); Refresh (); + } else if (Current.SuperView == null && Current?.Modal == true) { + Refresh (); } if (Driver.EnsureCursorVisibility ()) { state.Toplevel.SetNeedsDisplay (); @@ -690,42 +657,41 @@ public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool } firstIteration = false; - if (state.Toplevel != Top - && (!Top._needsDisplay.IsEmpty || Top._subViewNeedsDisplay || Top.LayoutNeeded)) { - state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); + if (state.Toplevel != Top && + (Top.NeedsDisplay|| Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { + state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame); + Top.Clear (); Top.Draw (); foreach (var top in _toplevels.Reverse ()) { if (top != Top && top != state.Toplevel) { top.SetNeedsDisplay (); top.SetSubViewNeedsDisplay (); + top.Clear (); top.Draw (); } } } if (_toplevels.Count == 1 && state.Toplevel == Top && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) - && (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._subViewNeedsDisplay || state.Toplevel.LayoutNeeded)) { - - Driver.SetAttribute (Colors.TopLevel.Normal); - state.Toplevel.Clear (new Rect (0, 0, Driver.Cols, Driver.Rows)); + && (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) { + state.Toplevel.Clear (); } - if (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._subViewNeedsDisplay || state.Toplevel.LayoutNeeded - || OverlappedChildNeedsDisplay ()) { + if (state.Toplevel.NeedsDisplay || + state.Toplevel.SubViewNeedsDisplay || + state.Toplevel.LayoutNeeded || + OverlappedChildNeedsDisplay ()) { + state.Toplevel.Clear (); state.Toplevel.Draw (); - //if (state.Toplevel.SuperView != null) { - // state.Toplevel.SuperView?.OnRenderLineCanvas (); - //} else { - // state.Toplevel.OnRenderLineCanvas (); - //} state.Toplevel.PositionCursor (); Driver.Refresh (); } else { Driver.UpdateCursor (); } - if (state.Toplevel != Top && !state.Toplevel.Modal - && (!Top._needsDisplay.IsEmpty || Top._subViewNeedsDisplay || Top.LayoutNeeded)) { + if (state.Toplevel != Top && + !state.Toplevel.Modal && + (Top.NeedsDisplay|| Top.SubViewNeedsDisplay || Top.LayoutNeeded)) { Top.Draw (); } } @@ -735,7 +701,7 @@ public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool /// public static void DoEvents () { - MainLoop.Driver.Wakeup (); + MainLoop.MainLoopDriver.Wakeup (); } /// @@ -1011,7 +977,6 @@ static void OnTerminalResized () { var full = new Rect (0, 0, Driver.Cols, Driver.Rows); TerminalResized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height }); - Driver.Clip = full; foreach (var t in _toplevels) { t.SetRelativeLayout (full); t.LayoutSubviews (); @@ -1073,7 +1038,7 @@ public static void GrabMouse (View view) if (!OnGrabbingMouse (view)) { OnGrabbedMouse (view); _mouseGrabView = view; - Driver.UncookMouse (); + //Driver.UncookMouse (); } } @@ -1087,7 +1052,7 @@ public static void UngrabMouse () if (!OnUnGrabbingMouse (_mouseGrabView)) { OnUnGrabbedMouse (_mouseGrabView); _mouseGrabView = null; - Driver.CookMouse (); + //Driver.CookMouse (); } } @@ -1132,10 +1097,7 @@ static void OnUnGrabbedMouse (View view) static void ProcessMouseEvent (MouseEvent me) { - bool OutsideBounds (Point p, Rect r) - { - return p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom; - } + static bool OutsideBounds (Point p, Rect r) => p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom; if (IsMouseDisabled) { return; diff --git a/Terminal.Gui/Clipboard/Clipboard.cs b/Terminal.Gui/Clipboard/Clipboard.cs index 98c0ca1810..47de47ab60 100644 --- a/Terminal.Gui/Clipboard/Clipboard.cs +++ b/Terminal.Gui/Clipboard/Clipboard.cs @@ -1,31 +1,34 @@ using System.Text; using System; +using System.Diagnostics; +using System.Threading.Tasks; -namespace Terminal.Gui { - /// - /// Provides cut, copy, and paste support for the OS clipboard. - /// - /// - /// - /// On Windows, the class uses the Windows Clipboard APIs via P/Invoke. - /// - /// - /// On Linux, when not running under Windows Subsystem for Linux (WSL), - /// the class uses the xclip command line tool. If xclip is not installed, - /// the clipboard will not work. - /// - /// - /// On Linux, when running under Windows Subsystem for Linux (WSL), - /// the class launches Windows' powershell.exe via WSL interop and uses the - /// "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets. - /// - /// - /// On the Mac, the class uses the MacO OS X pbcopy and pbpaste command line tools - /// and the Mac clipboard APIs vai P/Invoke. - /// - /// - public static class Clipboard { - static string contents; +namespace Terminal.Gui; + +/// +/// Provides cut, copy, and paste support for the OS clipboard. +/// +/// +/// +/// On Windows, the class uses the Windows Clipboard APIs via P/Invoke. +/// +/// +/// On Linux, when not running under Windows Subsystem for Linux (WSL), +/// the class uses the xclip command line tool. If xclip is not installed, +/// the clipboard will not work. +/// +/// +/// On Linux, when running under Windows Subsystem for Linux (WSL), +/// the class launches Windows' powershell.exe via WSL interop and uses the +/// "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets. +/// +/// +/// On the Mac, the class uses the MacO OS X pbcopy and pbpaste command line tools +/// and the Mac clipboard APIs vai P/Invoke. +/// +/// +public static class Clipboard { + static string _contents = string.Empty; /// /// Gets (copies from) or sets (pastes to) the contents of the OS clipboard. @@ -39,12 +42,12 @@ public static string Contents { // throw new InvalidOperationException ($"{Application.Driver.GetType ().Name}.GetClipboardData returned null instead of string.Empty"); clipData = string.Empty; } - contents = clipData; + _contents = clipData; } } catch (Exception) { - contents = string.Empty; + _contents = string.Empty; } - return contents; + return _contents; } set { try { @@ -54,51 +57,120 @@ public static string Contents { } Application.Driver.Clipboard.SetClipboardData (value); } - contents = value; + _contents = value; } catch (NotSupportedException e) { throw e; } catch (Exception) { - contents = value; + _contents = value; } } } - /// - /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. - /// - /// - /// - public static bool IsSupported { get => Application.Driver.Clipboard.IsSupported; } + /// + /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. + /// + /// + /// + public static bool IsSupported { get => Application.Driver.Clipboard.IsSupported; } - /// - /// 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 (contents != result) { - contents = result; - } - return true; + /// + /// 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 (_contents != result) { + _contents = result; } - result = string.Empty; - return false; + return true; } + result = string.Empty; + return false; + } - /// - /// Pastes the to the OS clipboard if possible. - /// - /// The text to paste to the OS clipboard. - /// the OS clipboard was set, otherwise. - public static bool TrySetClipboardData (string text) - { - if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text)) { - contents = text; - return true; - } - return false; + /// + /// Pastes the to the OS clipboard if possible. + /// + /// The text to paste to the OS clipboard. + /// the OS clipboard was set, otherwise. + public static bool TrySetClipboardData (string text) + { + if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text)) { + _contents = text; + return true; } + return false; } } + +/// +/// Helper class for console drivers to invoke shell commands to interact with the clipboard. +/// Used primarily by CursesDriver, but also used in Unit tests which is why it is in +/// ConsoleDriver.cs. +/// +internal static class ClipboardProcessRunner { + public static (int exitCode, string result) Bash (string commandLine, string inputText = "", bool waitForOutput = false) + { + var arguments = $"-c \"{commandLine}\""; + var (exitCode, result) = Process ("bash", arguments, inputText, waitForOutput); + + return (exitCode, result.TrimEnd ()); + } + + public static (int exitCode, string result) Process (string cmd, string arguments, string input = null, bool waitForOutput = true) + { + var output = string.Empty; + + using (Process process = new Process { + StartInfo = new ProcessStartInfo { + FileName = cmd, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }) { + var eventHandled = new TaskCompletionSource (); + process.Start (); + if (!string.IsNullOrEmpty (input)) { + process.StandardInput.Write (input); + process.StandardInput.Close (); + } + + if (!process.WaitForExit (5000)) { + var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}."; + throw new TimeoutException (timeoutError); + } + + if (waitForOutput && process.StandardOutput.Peek () != -1) { + output = process.StandardOutput.ReadToEnd (); + } + + if (process.ExitCode > 0) { + output = $@"Process failed to run. Command line: {cmd} {arguments}. + Output: {output} + Error: {process.StandardError.ReadToEnd ()}"; + } + + return (process.ExitCode, output); + } + } + + public static bool DoubleWaitForExit (this System.Diagnostics.Process process) + { + var result = process.WaitForExit (500); + if (result) { + process.WaitForExit (); + } + return result; + } + + public static bool FileExists (this string value) + { + return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); + } +} \ No newline at end of file diff --git a/Terminal.Gui/Clipboard/ClipboardBase.cs b/Terminal.Gui/Clipboard/ClipboardBase.cs index ee2e437d41..043aad10bd 100644 --- a/Terminal.Gui/Clipboard/ClipboardBase.cs +++ b/Terminal.Gui/Clipboard/ClipboardBase.cs @@ -22,6 +22,10 @@ public abstract class ClipboardBase : IClipboard { public string GetClipboardData () { try { + var result = GetClipboardDataImpl (); + if (result == null) { + return string.Empty; + } return GetClipboardDataImpl (); } catch (NotSupportedException ex) { throw new NotSupportedException ("Failed to copy from the OS clipboard.", ex); @@ -42,6 +46,10 @@ public string GetClipboardData () /// Thrown if it was not possible to paste to the OS clipboard. public void SetClipboardData (string text) { + if (text == null) { + throw new ArgumentNullException (nameof (text)); + } + try { SetClipboardDataImpl (text); } catch (NotSupportedException ex) { @@ -59,25 +67,21 @@ public void SetClipboardData (string text) /// /// Copies the contents of the OS clipboard to if possible. /// - /// The contents of the OS clipboard if successful, if not. + /// The contents of the OS clipboard if successful. /// the OS clipboard was retrieved, otherwise. public bool TryGetClipboardData (out string result) { + result = string.Empty; // Don't even try to read because environment is not set up. if (!IsSupported) { - result = null; return false; } try { result = GetClipboardDataImpl (); - while (result == null) { - result = GetClipboardDataImpl (); - } return true; } catch (NotSupportedException ex) { System.Diagnostics.Debug.WriteLine ($"TryGetClipboardData: {ex.Message}"); - result = null; return false; } } diff --git a/Terminal.Gui/Clipboard/IClipboard.cs b/Terminal.Gui/Clipboard/IClipboard.cs index 9619d8125b..3b2b5f7be2 100644 --- a/Terminal.Gui/Clipboard/IClipboard.cs +++ b/Terminal.Gui/Clipboard/IClipboard.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Terminal.Gui { /// diff --git a/Terminal.Gui/Configuration/AttributeJsonConverter.cs b/Terminal.Gui/Configuration/AttributeJsonConverter.cs index 11ab89ccbe..390c70a0f6 100644 --- a/Terminal.Gui/Configuration/AttributeJsonConverter.cs +++ b/Terminal.Gui/Configuration/AttributeJsonConverter.cs @@ -32,10 +32,11 @@ public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, J Attribute attribute = new Attribute (); Color foreground = (Color)(-1); Color background = (Color)(-1); - int valuePair = 0; + TrueColor? trueColorForeground = null; + TrueColor? trueColorBackground = null; while (reader.Read ()) { if (reader.TokenType == JsonTokenType.EndObject) { - if (foreground == (Color)(-1) || background == (Color)(-1)) { + if (!attribute.TrueColorForeground.HasValue || !attribute.TrueColorBackground.HasValue) { throw new JsonException ($"Both Foreground and Background colors must be provided."); } return attribute; @@ -49,8 +50,6 @@ public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, J reader.Read (); string color = $"\"{reader.GetString ()}\""; - valuePair++; - switch (propertyName.ToLower ()) { case "foreground": foreground = JsonSerializer.Deserialize (color, options); @@ -58,6 +57,12 @@ public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, J case "background": background = JsonSerializer.Deserialize (color, options); break; + case "truecolorforeground": + trueColorForeground = JsonSerializer.Deserialize (color, options); + break; + case "truecolorbackground": + trueColorBackground = JsonSerializer.Deserialize (color, options); + break; //case "Bright": // attribute.Bright = reader.GetBoolean (); // break; @@ -71,9 +76,11 @@ public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, J throw new JsonException ($"Unknown Attribute property {propertyName}."); } - if (valuePair == 2) { + if (foreground != (Color)(-1) && background != (Color)(-1)) { attribute = new Attribute (foreground, background); - valuePair = 0; + } + if (trueColorForeground.HasValue && trueColorBackground.HasValue) { + attribute = new Attribute (trueColorForeground, trueColorBackground); } } throw new JsonException (); @@ -82,10 +89,16 @@ public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, J public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options) { writer.WriteStartObject (); - writer.WritePropertyName ("Foreground"); + writer.WritePropertyName (nameof(Attribute.Foreground)); ColorJsonConverter.Instance.Write (writer, value.Foreground, options); - writer.WritePropertyName ("Background"); + writer.WritePropertyName (nameof (Attribute.Background)); ColorJsonConverter.Instance.Write (writer, value.Background, options); + if (value.TrueColorForeground.HasValue && value.TrueColorBackground.HasValue) { + writer.WritePropertyName (nameof (Attribute.TrueColorForeground)); + TrueColorJsonConverter.Instance.Write (writer, value.TrueColorForeground.Value, options); + writer.WritePropertyName (nameof (Attribute.TrueColorBackground)); + TrueColorJsonConverter.Instance.Write (writer, value.TrueColorBackground.Value, options); + } writer.WriteEndObject (); } } diff --git a/Terminal.Gui/Configuration/ColorJsonConverter.cs b/Terminal.Gui/Configuration/ColorJsonConverter.cs index 0a417ac423..7dfafe03d6 100644 --- a/Terminal.Gui/Configuration/ColorJsonConverter.cs +++ b/Terminal.Gui/Configuration/ColorJsonConverter.cs @@ -41,7 +41,7 @@ public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonS var r = int.Parse (match.Groups [1].Value); var g = int.Parse (match.Groups [2].Value); var b = int.Parse (match.Groups [3].Value); - return new TrueColor (r, g, b).ToConsoleColor (); + return TrueColor.ToConsoleColor(new TrueColor (r, g, b)); } else { throw new JsonException ($"Invalid Color: '{colorString}'"); } diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs index 7a47fc70b8..e41e9233bd 100644 --- a/Terminal.Gui/Configuration/ConfigurationManager.cs +++ b/Terminal.Gui/Configuration/ConfigurationManager.cs @@ -1,4 +1,5 @@ -global using CM = Terminal.Gui.ConfigurationManager; +global using static Terminal.Gui.ConfigurationManager; +global using CM = Terminal.Gui.ConfigurationManager; using System; using System.Collections; diff --git a/Terminal.Gui/Configuration/ThemeScope.cs b/Terminal.Gui/Configuration/ThemeScope.cs index 924388392e..529bcc44a7 100644 --- a/Terminal.Gui/Configuration/ThemeScope.cs +++ b/Terminal.Gui/Configuration/ThemeScope.cs @@ -2,7 +2,7 @@ #nullable enable -namespace Terminal.Gui; +namespace Terminal.Gui; /// /// The root object for a Theme. A Theme is a set of settings that are applied to the running @@ -49,7 +49,7 @@ public class ThemeScope : Scope { internal override bool Apply () { var ret = base.Apply (); - Application.Driver?.InitalizeColorSchemes (); + Application.Driver?.InitializeColorSchemes (); return ret; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Configuration/TrueColorJsonConverter.cs b/Terminal.Gui/Configuration/TrueColorJsonConverter.cs new file mode 100644 index 0000000000..bb1bd741cb --- /dev/null +++ b/Terminal.Gui/Configuration/TrueColorJsonConverter.cs @@ -0,0 +1,47 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Terminal.Gui { + /// + /// for . + /// + internal class TrueColorJsonConverter : JsonConverter { + private static TrueColorJsonConverter instance; + + /// + /// Singleton + /// + public static TrueColorJsonConverter Instance { + get { + if (instance == null) { + instance = new TrueColorJsonConverter (); + } + + return instance; + } + } + + public override TrueColor Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Check if the value is a string + if (reader.TokenType == JsonTokenType.String) { + // Get the color string + var colorString = reader.GetString (); + + if (!TrueColor.TryParse (colorString, out TrueColor? trueColor)) { + throw new JsonException ($"Invalid TrueColor: '{colorString}'"); + } + + return trueColor.Value; + } else { + throw new JsonException ($"Unexpected token when parsing TrueColor: {reader.TokenType}"); + } + } + + public override void Write (Utf8JsonWriter writer, TrueColor value, JsonSerializerOptions options) + { + writer.WriteStringValue (value.ToString ()); + } + } +} diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 7cc5a7fcb8..69c5a609d9 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -1,1135 +1,587 @@ -// +// // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations. // using System.Text; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using Terminal.Gui; -using static Terminal.Gui.ConfigurationManager; - -namespace Terminal.Gui { +using static Terminal.Gui.ColorScheme; + +namespace Terminal.Gui; + + +/// +/// Base class for Terminal.Gui ConsoleDriver implementations. +/// +/// +/// There are currently four implementations: +/// - (for Unix and Mac) +/// - +/// - that uses the .NET Console API +/// - for unit testing. +/// +public abstract class ConsoleDriver { /// - /// Colors that can be used to set the foreground and background colors in console applications. + /// Prepare the driver and set the key and mouse events handlers. /// - /// - /// The value indicates either no-color has been set or the color is invalid. - /// - [JsonConverter (typeof (ColorJsonConverter))] - public enum Color { - /// - /// The black color. - /// - Black, - /// - /// The blue color. - /// - Blue, - /// - /// The green color. - /// - Green, - /// - /// The cyan color. - /// - Cyan, - /// - /// The red color. - /// - Red, - /// - /// The magenta color. - /// - Magenta, - /// - /// The brown color. - /// - Brown, - /// - /// The gray color. - /// - Gray, - /// - /// The dark gray color. - /// - DarkGray, - /// - /// The bright bBlue color. - /// - BrightBlue, - /// - /// The bright green color. - /// - BrightGreen, - /// - /// The bright cyan color. - /// - BrightCyan, - /// - /// The bright red color. - /// - BrightRed, - /// - /// The bright magenta color. - /// - BrightMagenta, - /// - /// The bright yellow color. - /// - BrightYellow, - /// - /// The White color. - /// - White - } + /// The main loop. + /// The handler for ProcessKey + /// The handler for key down events + /// The handler for key up events + /// The handler for mouse events + public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler); /// - /// Indicates the RGB for true colors. + /// The handler fired when the terminal is resized. /// - public class TrueColor { - /// - /// Red color component. - /// - public int Red { get; } - /// - /// Green color component. - /// - public int Green { get; } - /// - /// Blue color component. - /// - public int Blue { get; } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// - /// - public TrueColor (int red, int green, int blue) - { - Red = red; - Green = green; - Blue = blue; - } - - /// - /// Converts true color to console color. - /// - /// - public Color ToConsoleColor () - { - var trueColorMap = new Dictionary () { - { new TrueColor (0,0,0),Color.Black}, - { new TrueColor (0, 0, 0x80),Color.Blue}, - { new TrueColor (0, 0x80, 0),Color.Green}, - { new TrueColor (0, 0x80, 0x80),Color.Cyan}, - { new TrueColor (0x80, 0, 0),Color.Red}, - { new TrueColor (0x80, 0, 0x80),Color.Magenta}, - { new TrueColor (0xC1, 0x9C, 0x00),Color.Brown}, // TODO confirm this - { new TrueColor (0xC0, 0xC0, 0xC0),Color.Gray}, - { new TrueColor (0x80, 0x80, 0x80),Color.DarkGray}, - { new TrueColor (0, 0, 0xFF),Color.BrightBlue}, - { new TrueColor (0, 0xFF, 0),Color.BrightGreen}, - { new TrueColor (0, 0xFF, 0xFF),Color.BrightCyan}, - { new TrueColor (0xFF, 0, 0),Color.BrightRed}, - { new TrueColor (0xFF, 0, 0xFF),Color.BrightMagenta }, - { new TrueColor (0xFF, 0xFF, 0),Color.BrightYellow}, - { new TrueColor (0xFF, 0xFF, 0xFF),Color.White}, - }; - // Iterate over all colors in the map - var distances = trueColorMap.Select ( - k => Tuple.Create ( - // the candidate we are considering matching against (RGB) - k.Key, - - CalculateDistance (k.Key, this) - )); - - // get the closest - var match = distances.OrderBy (t => t.Item2).First (); - return trueColorMap [match.Item1]; - } - - private float CalculateDistance (TrueColor color1, TrueColor color2) - { - // use RGB distance - return - Math.Abs (color1.Red - color2.Red) + - Math.Abs (color1.Green - color2.Green) + - Math.Abs (color1.Blue - color2.Blue); - } - } + protected Action TerminalResized; /// - /// Attributes are used as elements that contain both a foreground and a background or platform specific features. + /// The number of columns visible in the terminal. /// - /// - /// s are needed to map colors to terminal capabilities that might lack colors. - /// They encode both the foreground and the background color and are used in the - /// class to define color schemes that can be used in an application. - /// - [JsonConverter (typeof (AttributeJsonConverter))] - public struct Attribute { - /// - /// The -specific color attribute value. If is - /// the value of this property is invalid (typically because the Attribute was created before a driver was loaded) - /// and the attribute should be re-made (see ) before it is used. - /// - [JsonIgnore (Condition = JsonIgnoreCondition.Always)] - public int Value { get; } + public virtual int Cols { get; internal set; } - /// - /// The foreground color. - /// - [JsonConverter (typeof (ColorJsonConverter))] - public Color Foreground { get; } - - /// - /// The background color. - /// - [JsonConverter (typeof (ColorJsonConverter))] - public Color Background { get; } + /// + /// The number of rows visible in the terminal. + /// + public virtual int Rows { get; internal set; } - /// - /// Initializes a new instance of the struct with only the value passed to - /// and trying to get the colors if defined. - /// - /// Value. - public Attribute (int value) - { - Color foreground = default; - Color background = default; - - Initialized = false; - if (Application.Driver != null) { - Application.Driver.GetColors (value, out foreground, out background); - Initialized = true; - } - Value = value; - Foreground = foreground; - Background = background; - } + /// + /// The leftmost column in the terminal. + /// + public virtual int Left { get; internal set; } = 0; - /// - /// Initializes a new instance of the struct. - /// - /// Value. - /// Foreground - /// Background - public Attribute (int value, Color foreground, Color background) - { - Value = value; - Foreground = foreground; - Background = background; - Initialized = true; - } + /// + /// The topmost row in the terminal. + /// + public virtual int Top { get; internal set; } = 0; - /// - /// Initializes a new instance of the struct. - /// - /// Foreground - /// Background - public Attribute (Color foreground = new Color (), Color background = new Color ()) - { - var make = Make (foreground, background); - Initialized = make.Initialized; - Value = make.Value; - Foreground = foreground; - Background = background; - } + /// + /// Get the operating system clipboard. + /// + public IClipboard Clipboard { get; internal set; } - /// - /// Initializes a new instance of the struct - /// with the same colors for the foreground and background. - /// - /// The color. - public Attribute (Color color) : this (color, color) { } + /// + /// The contents of the application output. The driver outputs this buffer to the terminal when + /// is called. + /// + /// The format of the array is rows, columns, and 3 values on the last column: Rune, Attribute and Dirty Flag + /// + /// + //public int [,,] Contents { get; internal set; } - /// - /// Implicit conversion from an to the underlying, driver-specific, Int32 representation - /// of the color. - /// - /// The driver-specific color value stored in the attribute. - /// The attribute to convert - public static implicit operator int (Attribute c) - { - if (!c.Initialized) throw new InvalidOperationException ("Attribute: Attributes must be initialized by a driver before use."); - return c.Value; - } + ///// + ///// The contents of the application output. The driver outputs this buffer to the terminal when + ///// 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; } - /// - /// Implicitly convert an driver-specific color value into an - /// - /// An attribute with the specified driver-specific color value. - /// value - public static implicit operator Attribute (int v) => new Attribute (v); + /// + /// Initializes the driver + /// + /// Method to invoke when the terminal is resized. + public abstract void Init (Action terminalResized); - /// - /// Creates an from the specified foreground and background colors. - /// - /// - /// If a has not been loaded (Application.Driver == null) this - /// method will return an attribute with set to . - /// - /// The new attribute. - /// Foreground color to use. - /// Background color to use. - public static Attribute Make (Color foreground, Color background) - { - if (Application.Driver == null) { - // Create the attribute, but show it's not been initialized - return new Attribute (-1, foreground, background) { - Initialized = false - }; - } - return Application.Driver.MakeAttribute (foreground, background); - } + /// + /// Gets the column last set by . and + /// are used by and to determine where to add content. + /// + public int Col { get; internal set; } - /// - /// Gets the current from the driver. - /// - /// The current attribute. - public static Attribute Get () - { - if (Application.Driver == null) - throw new InvalidOperationException ("The Application has not been initialized"); - return Application.Driver.GetAttribute (); - } + /// + /// Gets the row last set by . and + /// are used by and to determine where to add content. + /// + public int Row { get; internal set; } - /// - /// If the attribute has been initialized by a and - /// thus has that is valid for that driver. If the - /// and colors may have been set '-1' but - /// the attribute has not been mapped to a specific color value. - /// - /// - /// Attributes that have not been initialized must eventually be initialized before being passed to a driver. - /// - [JsonIgnore] - public bool Initialized { get; internal set; } + /// + /// Updates and to the specified column and row in . + /// Used by and to determine where to add content. + /// + /// + /// + /// This does not move the cursor on the screen, it only updates the internal state of the driver. + /// + /// + /// If or are negative or beyond and , + /// the method still sets those properties. + /// + /// + /// Column to move to. + /// Row to move to. + public virtual void Move (int col, int row) + { + Col = col; + Row = row; + } - /// - /// Returns if the Attribute is valid (both foreground and background have valid color values). - /// - /// - [JsonIgnore] - public bool HasValidColors { get => (int)Foreground > -1 && (int)Background > -1; } + /// + /// Tests if the specified rune is supported by the driver. + /// + /// + /// if the rune can be properly presented; if the driver + /// does not support displaying this rune. + public virtual bool IsRuneSupported (Rune rune) + { + return Rune.IsValid (rune.Value); } /// - /// Defines the color s for common visible elements in a . - /// Containers such as and use to determine - /// the colors used by sub-views. + /// Adds the specified rune to the display at the current cursor position. /// /// - /// See also: . + /// + /// When the method returns, will be incremented by the number of columns required, + /// even if the new column value is outside of the or screen dimensions defined by . + /// + /// + /// If requires more than one column, and plus the number of columns needed + /// exceeds the or screen dimensions, the default Unicode replacement character (U+FFFD) will be added instead. + /// /// - [JsonConverter (typeof (ColorSchemeJsonConverter))] - public class ColorScheme : IEquatable { - Attribute _normal = new Attribute (Color.White, Color.Black); - Attribute _focus = new Attribute (Color.White, Color.Black); - Attribute _hotNormal = new Attribute (Color.White, Color.Black); - Attribute _hotFocus = new Attribute (Color.White, Color.Black); - Attribute _disabled = new Attribute (Color.White, Color.Black); - - /// - /// Used by and to track which ColorScheme - /// is being accessed. - /// - internal string schemeBeingSet = ""; - - /// - /// Creates a new instance. - /// - public ColorScheme () { } - - /// - /// Creates a new instance, initialized with the values from . - /// - /// The scheme to initialize the new instance with. - public ColorScheme (ColorScheme scheme) : base () - { - if (scheme != null) { - _normal = scheme.Normal; - _focus = scheme.Focus; - _hotNormal = scheme.HotNormal; - _disabled = scheme.Disabled; - _hotFocus = scheme.HotFocus; - } - } - - /// - /// Creates a new instance, initialized with the values from . - /// - /// The attribute to initialize the new instance with. - public ColorScheme (Attribute attribute) - { - _normal = attribute; - _focus = attribute; - _hotNormal = attribute; - _disabled = attribute; - _hotFocus = attribute; - } - - /// - /// The foreground and background color for text when the view is not focused, hot, or disabled. - /// - public Attribute Normal { - get { return _normal; } - set { - if (!value.HasValidColors) { - return; + /// Rune to add. + public void AddRune (Rune rune) + { + int runeWidth = -1; + var validLocation = IsValidLocation (Col, Row); + if (validLocation) { + rune = rune.MakePrintable (); + runeWidth = rune.GetColumns (); + if (runeWidth == 0 && rune.IsCombiningMark () && Col > 0) { + // This is a combining character, and we are not at the beginning of the line. + // TODO: Remove hard-coded [0] once combining pairs is supported + + // Convert Runes to string and concatenate + string combined = Contents [Row, Col - 1].Runes [0].ToString () + rune.ToString (); + + // Normalize to Form C (Canonical Composition) + string normalized = combined.Normalize (NormalizationForm.FormC); + + Contents [Row, Col - 1].Runes = new List { (Rune)normalized [0] }; ; + Contents [Row, Col - 1].Attribute = CurrentAttribute; + Contents [Row, Col - 1].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].Runes [0].GetColumns () > 1) { + // Invalidate cell to left + Contents [Row, Col - 1].Runes = new List { Rune.ReplacementChar }; + Contents [Row, Col - 1].IsDirty = true; + } } - _normal = value; - } - } - /// - /// The foreground and background color for text when the view has the focus. - /// - public Attribute Focus { - get { return _focus; } - set { - if (!value.HasValidColors) { - return; - } - _focus = value; - } - } - /// - /// The foreground and background color for text when the view is highlighted (hot). - /// - public Attribute HotNormal { - get { return _hotNormal; } - set { - if (!value.HasValidColors) { - return; + if (runeWidth < 1) { + Contents [Row, Col].Runes = new List { Rune.ReplacementChar }; + + } else if (runeWidth == 1) { + Contents [Row, Col].Runes = new List { rune }; + if (Col < Clip.Right - 1) { + Contents [Row, Col + 1].IsDirty = true; + } + } 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].Runes = new List { Rune.ReplacementChar }; + } else { + Contents [Row, Col].Runes = new List { 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].Runes = new List { 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].Runes = new List { (Rune)' ' }; + Contents [Row, Col].IsDirty = false; } - _hotNormal = value; + _dirtyLines [Row] = true; } } - /// - /// The foreground and background color for text when the view is highlighted (hot) and has focus. - /// - public Attribute HotFocus { - get { return _hotFocus; } - set { - if (!value.HasValidColors) { - return; - } - _hotFocus = value; - } + if (runeWidth is < 0 or > 0) { + Col++; } - /// - /// The default foreground and background color for text, when the view is disabled. - /// - public Attribute Disabled { - get { return _disabled; } - set { - if (!value.HasValidColors) { - return; - } - _disabled = value; - } - } + if (runeWidth > 1) { + Debug.Assert (runeWidth <= 2); + 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; - /// - /// Compares two objects for equality. - /// - /// - /// true if the two objects are equal - public override bool Equals (object obj) - { - return Equals (obj as ColorScheme); - } - - /// - /// Compares two objects for equality. - /// - /// - /// true if the two objects are equal - public bool Equals (ColorScheme other) - { - return other != null && - EqualityComparer.Default.Equals (_normal, other._normal) && - EqualityComparer.Default.Equals (_focus, other._focus) && - EqualityComparer.Default.Equals (_hotNormal, other._hotNormal) && - EqualityComparer.Default.Equals (_hotFocus, other._hotFocus) && - EqualityComparer.Default.Equals (_disabled, other._disabled); - } - - /// - /// Returns a hashcode for this instance. - /// - /// hashcode for this instance - public override int GetHashCode () - { - int hashCode = -1242460230; - hashCode = hashCode * -1521134295 + _normal.GetHashCode (); - hashCode = hashCode * -1521134295 + _focus.GetHashCode (); - hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode (); - hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode (); - hashCode = hashCode * -1521134295 + _disabled.GetHashCode (); - return hashCode; - } - - /// - /// Compares two objects for equality. - /// - /// - /// - /// true if the two objects are equivalent - public static bool operator == (ColorScheme left, ColorScheme right) - { - return EqualityComparer.Default.Equals (left, right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// - /// true if the two objects are not equivalent - public static bool operator != (ColorScheme left, ColorScheme right) - { - return !(left == right); - } - - internal void Initialize () - { - // If the new scheme was created before a driver was loaded, we need to re-make - // the attributes - if (!_normal.Initialized) { - _normal = new Attribute (_normal.Foreground, _normal.Background); - } - if (!_focus.Initialized) { - _focus = new Attribute (_focus.Foreground, _focus.Background); - } - if (!_hotNormal.Initialized) { - _hotNormal = new Attribute (_hotNormal.Foreground, _hotNormal.Background); - } - if (!_hotFocus.Initialized) { - _hotFocus = new Attribute (_hotFocus.Foreground, _hotFocus.Background); - } - if (!_disabled.Initialized) { - _disabled = new Attribute (_disabled.Foreground, _disabled.Background); + // TODO: Determine if we should wipe this out (for now now) + //Contents [Row, Col].Runes [0] = (Rune)' '; } + Col++; } } /// - /// The default s for the application. + /// Adds the specified to the display at the current cursor position. This method + /// is a convenience method that calls with the constructor. + /// + /// Character to add. + public void AddRune (char c) => AddRune (new Rune (c)); + + /// + /// Adds the to the display at the cursor position. /// /// - /// This property can be set in a Theme to change the default for the application. + /// + /// When the method returns, will be incremented by the number of columns required, + /// unless the new column value is outside of the or screen dimensions defined by . + /// + /// + /// If requires more columns than are available, the output will be clipped. + /// /// - public static class Colors { - private class SchemeNameComparerIgnoreCase : IEqualityComparer { - public bool Equals (string x, string y) - { - if (x != null && y != null) { - return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase); - } - return false; - } - - public int GetHashCode (string obj) - { - return obj.ToLowerInvariant ().GetHashCode (); - } - } - - static Colors () - { - ColorSchemes = Create (); - } - - /// - /// Creates a new dictionary of new objects. - /// - public static Dictionary Create () - { - // Use reflection to dynamically create the default set of ColorSchemes from the list defined - // by the class. - return typeof (Colors).GetProperties () - .Where (p => p.PropertyType == typeof (ColorScheme)) - .Select (p => new KeyValuePair (p.Name, new ColorScheme ())) - .ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ()); - } - - /// - /// The application Toplevel color scheme, for the default Toplevel views. - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["TopLevel"]; - /// - /// - public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The base color scheme, for the default Toplevel views. - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Base"]; - /// - /// - public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The dialog color scheme, for standard popup dialog boxes - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Dialog"]; - /// - /// - public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The menu bar color - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Menu"]; - /// - /// - public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); } - - /// - /// The color scheme for showing errors. - /// - /// - /// - /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Error"]; - /// - /// - public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); } - - static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) - { - return ColorSchemes [schemeBeingSet]; - } - - static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null) - { - ColorSchemes [schemeBeingSet] = colorScheme; - colorScheme.schemeBeingSet = schemeBeingSet; + /// String. + public void AddStr (string str) + { + foreach (var rune in str.EnumerateRunes ()) { + AddRune (rune); } - - /// - /// Provides the defined s. - /// - [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)] - [JsonConverter (typeof (DictionaryJsonConverter))] - public static Dictionary ColorSchemes { get; private set; } } + Rect _clip; + /// - /// Cursors Visibility that are displayed + /// Tests whether the specified coordinate are valid for drawing. /// - // - // Hexa value are set as 0xAABBCCDD where : - // - // AA stand for the TERMINFO DECSUSR parameter value to be used under Linux & MacOS - // BB stand for the NCurses curs_set parameter value to be used under Linux & 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 of 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, + /// The column. + /// The row. + /// if the coordinate is outside of the + /// screen bounds or outside of . otherwise. + public bool IsValidLocation (int col, int row) => + col >= 0 && row >= 0 && + col < Cols && row < Rows && + Clip.Contains (col, row); - /// - /// Cursor caret is displayed a block ▉ - /// - /// Works under Xterm-like terminal otherwise this is equivalent to - BoxFix = 0x02020164, + /// + /// Gets or sets the clip rectangle that and are + /// subject to. + /// + /// The rectangle describing the bounds of . + public Rect Clip { + get => _clip; + set => _clip = value; } /// - /// ConsoleDriver is an abstract class that defines the requirements for a console driver. - /// There are currently three implementations: (for Unix and Mac), , and that uses the .NET Console API. + /// Updates the screen to reflect all the changes that have been done to the display buffer /// - public abstract class ConsoleDriver { - - /// - /// The handler fired when the terminal is resized. - /// - protected Action TerminalResized; - - /// - /// The current number of columns in the terminal. - /// - public abstract int Cols { get; } + public abstract void Refresh (); - /// - /// The current number of rows in the terminal. - /// - public abstract int Rows { get; } - - /// - /// The current left in the terminal. - /// - public abstract int Left { get; } - - /// - /// The current top in the terminal. - /// - public abstract int Top { get; } + /// + /// Sets the position of the terminal cursor to and . + /// + public abstract void UpdateCursor (); - /// - /// Get the operation system clipboard. - /// - public abstract IClipboard Clipboard { get; } + /// + /// Gets the terminal cursor visibility. + /// + /// The current + /// upon success + public abstract bool GetCursorVisibility (out CursorVisibility visibility); - /// - /// - /// If (the default) the height of the Terminal.Gui application () - /// tracks to the height of the visible console view when the console is resized. In this case - /// scrolling in the console will be disabled and all will remain visible. - /// - /// - /// If then height of the Terminal.Gui application only tracks - /// the height of the visible console view when the console is made larger (the application will only grow in height, never shrink). - /// In this case console scrolling is enabled and the contents ( high) will scroll - /// as the console scrolls. - /// - /// - /// - /// NOTE: This functionaliy is currently broken on Windows Terminal. - /// - public abstract bool EnableConsoleScrolling { get; set; } + /// + /// Sets the terminal cursor visibility. + /// + /// The wished + /// upon success + public abstract bool SetCursorVisibility (CursorVisibility visibility); - /// - /// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag - /// - public virtual int [,,] Contents { get; } + /// + /// Determines if the terminal cursor should be visible or not and sets it accordingly. + /// + /// upon success + public abstract bool EnsureCursorVisibility (); - /// - /// Initializes the driver - /// - /// Method to invoke when the terminal is resized. - public abstract void Init (Action terminalResized); - /// - /// Moves the cursor to the specified column and row. - /// - /// Column to move the cursor to. - /// Row to move the cursor to. - public abstract void Move (int col, int row); + // 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; - /// - /// Tests if the specified rune is supported by the driver. - /// - /// - /// if the rune can be properly presented; if the driver - /// does not support displaying this rune. - public virtual bool IsRuneSupported (Rune rune) - { - if (rune.Value > RuneExtensions.MaxUnicodeCodePoint) { - return false; - } - return true; + /// + /// Clears the of the driver. + /// + public void ClearContents () + { + // TODO: This method is really "Clear Contents" now and should not be abstract (or virtual) + Contents = new Cell [Rows, Cols]; + Clip = new Rect (0, 0, Cols, Rows); + _dirtyLines = new bool [Rows]; + + lock (Contents) { + // Can raise an exception while is still resizing. + try { + for (var row = 0; row < Rows; row++) { + for (var c = 0; c < Cols; c++) { + Contents [row, c] = new Cell () { + Runes = new List { (Rune)' ' }, + Attribute = new Attribute (Color.White, Color.Black), + IsDirty = true + }; + _dirtyLines [row] = true; + } + } + } catch (IndexOutOfRangeException) { } } + } - /// - /// Adds the specified rune to the display at the current cursor position. - /// - /// Rune to add. - public abstract void AddRune (Rune rune); - - /// - /// Ensures that the column and line are in a valid range from the size of the driver. - /// - /// The column. - /// The row. - /// The clip. - /// trueif it's a valid range,falseotherwise. - public bool IsValidContent (int col, int row, Rect clip) => - col >= 0 && row >= 0 && col < Cols && row < Rows && clip.Contains (col, row); - - /// - /// Adds the to the display at the cursor position. - /// - /// String. - public abstract void AddStr (string str); - - /// - /// Prepare the driver and set the key and mouse events handlers. - /// - /// The main loop. - /// The handler for ProcessKey - /// The handler for key down events - /// The handler for key up events - /// The handler for mouse events - public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler); - - /// - /// Updates the screen to reflect all the changes that have been done to the display buffer - /// - public abstract void Refresh (); - - /// - /// Updates the location of the cursor position - /// - public abstract void UpdateCursor (); - - /// - /// Retreive the cursor caret visibility - /// - /// The current - /// true upon success - public abstract bool GetCursorVisibility (out CursorVisibility visibility); - - /// - /// Change the cursor caret visibility - /// - /// The wished - /// true upon success - public abstract bool SetCursorVisibility (CursorVisibility visibility); + /// + /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. + /// + public abstract void UpdateScreen (); - /// - /// Ensure the cursor visibility - /// - /// true upon success - public abstract bool EnsureCursorVisibility (); + #region Color Handling - /// - /// Ends the execution of the console driver. - /// - public abstract void End (); - /// - /// Resizes the clip area when the screen is resized. - /// - public abstract void ResizeScreen (); + /// + /// Gets whether the supports TrueColor output. + /// + public virtual bool SupportsTrueColor { get => false; } - /// - /// Reset and recreate the contents and the driver buffer. - /// - public abstract void UpdateOffScreen (); + private bool _useTrueColor = true; - /// - /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. - /// - public abstract void UpdateScreen (); + // TODO: Make this a ConfiguationManager setting on Application + /// + /// Gets or sets whether the should use TrueColor output. + /// + /// + /// Can only be enabled if is true, indicating + /// that the supports it. + /// + public bool UseTrueColor { + get => _useTrueColor && SupportsTrueColor; + set => this._useTrueColor = (value && SupportsTrueColor); + } - /// - /// The current attribute the driver is using. - /// - public virtual Attribute CurrentAttribute { - get => currentAttribute; - set { - if (!value.Initialized && value.HasValidColors && Application.Driver != null) { - CurrentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background); - return; - } - if (!value.Initialized) Debug.WriteLine ("ConsoleDriver.CurrentAttribute: Attributes must be initialized before use."); + Attribute _currentAttribute; - currentAttribute = value; + /// + /// The that will be used for the next or call. + /// + public Attribute CurrentAttribute { + get => _currentAttribute; + set { + if (value is { Initialized: false, HasValidColors: true } && Application.Driver != null) { + _currentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background); + return; } - } + if (!value.Initialized) Debug.WriteLine ("ConsoleDriver.CurrentAttribute: Attributes must be initialized before use."); - /// - /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. - /// - /// - /// Implementations should call base.SetAttribute(c). - /// - /// C. - public virtual void SetAttribute (Attribute c) - { - CurrentAttribute = c; - } - - /// - /// Set Colors from limit sets of colors. Not implemented by any driver: See Issue #2300. - /// - /// Foreground. - /// Background. - public abstract void SetColors (ConsoleColor foreground, ConsoleColor background); - - // Advanced uses - set colors to any pre-set pairs, you would need to init_color - // that independently with the R, G, B values. - /// - /// Advanced uses - set colors to any pre-set pairs, you would need to init_color - /// that independently with the R, G, B values. Not implemented by any driver: See Issue #2300. - /// - /// Foreground color identifier. - /// Background color identifier. - public abstract void SetColors (short foregroundColorId, short backgroundColorId); - - /// - /// Gets the foreground and background colors based on the value. - /// - /// The value. - /// The foreground. - /// The background. - /// - public abstract bool GetColors (int value, out Color foreground, out Color background); - - /// - /// Allows sending keys without typing on a keyboard. - /// - /// The character key. - /// The key. - /// If shift key is sending. - /// If alt key is sending. - /// If control key is sending. - public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control); - - /// - /// Set the handler when the terminal is resized. - /// - /// - public void SetTerminalResized (Action terminalResized) - { - TerminalResized = terminalResized; + _currentAttribute = value; } + } - /// - /// Fills the specified rectangle with the specified rune. - /// - /// - /// - public virtual void FillRect (Rect rect, Rune rune = default) - { - for (var r = rect.Y; r < rect.Y + rect.Height; r++) { - for (var c = rect.X; c < rect.X + rect.Width; c++) { - Application.Driver.Move (c, r); - Application.Driver.AddRune ((Rune)(rune == default ? ' ' : rune.Value)); - } - } - } + /// + /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. + /// + /// + /// Implementations should call base.SetAttribute(c). + /// + /// C. + public Attribute SetAttribute (Attribute c) + { + var prevAttribute = CurrentAttribute; + CurrentAttribute = c; + return prevAttribute; + } - /// - /// Enables diagnostic functions - /// - [Flags] - public enum DiagnosticFlags : uint { - /// - /// All diagnostics off - /// - Off = 0b_0000_0000, - /// - /// When enabled, will draw a - /// ruler in the frame for any side with a padding value greater than 0. - /// - FrameRuler = 0b_0000_0001, - /// - /// When enabled, will draw a - /// 'L', 'R', 'T', and 'B' when clearing 's instead of ' '. - /// - FramePadding = 0b_0000_0010, - } + /// + /// Make the attribute for the foreground and background colors. + /// + /// Foreground. + /// Background. + /// + public virtual Attribute MakeAttribute (Color fore, Color back) + { + return MakeColor (fore, back); + } - /// - /// Set flags to enable/disable diagnostics. - /// - public static DiagnosticFlags Diagnostics { get; set; } + /// + /// Gets the foreground and background colors based on a platform-dependent color value. + /// + /// The platform-dependent color value. + /// The foreground. + /// The background. + internal abstract void GetColors (int value, out Color foreground, out Color background); - /// - /// Suspend the application, typically needs to save the state, suspend the app and upon return, reset the console driver. - /// - public abstract void Suspend (); + /// + /// Gets the current . + /// + /// The current attribute. + public Attribute GetAttribute () => CurrentAttribute; - Rect clip; + /// + /// Makes an . + /// + /// The foreground color. + /// The background color. + /// The attribute for the foreground and background colors. + public abstract Attribute MakeColor (Color foreground, Color background); - /// - /// Controls the current clipping region that AddRune/AddStr is subject to. - /// - /// The clip. - public Rect Clip { - get => clip; - set => this.clip = value; + /// + /// Ensures all s in are correctly + /// initialized by the driver. + /// + public void InitializeColorSchemes () + { + // Ensure all Attributes are initialized by the driver + foreach (var s in Colors.ColorSchemes) { + s.Value.Initialize (); } + } - /// - /// Start of mouse moves. - /// - public abstract void StartReportingMouseMoves (); - - /// - /// Stop reporting mouses moves. - /// - public abstract void StopReportingMouseMoves (); + #endregion + /// + /// Enables diagnostic functions + /// + [Flags] + public enum DiagnosticFlags : uint { /// - /// Disables the cooked event processing from the mouse driver. - /// At startup, it is assumed mouse events are cooked. Not implemented by any driver: See Issue #2300. + /// All diagnostics off /// - public abstract void UncookMouse (); - + Off = 0b_0000_0000, /// - /// Enables the cooked event processing from the mouse driver. Not implemented by any driver: See Issue #2300. + /// When enabled, will draw a + /// ruler in the frame for any side with a padding value greater than 0. /// - public abstract void CookMouse (); - - private Attribute currentAttribute; - + FrameRuler = 0b_0000_0001, /// - /// Make the attribute for the foreground and background colors. + /// When enabled, will draw a + /// 'L', 'R', 'T', and 'B' when clearing 's instead of ' '. /// - /// Foreground. - /// Background. - /// - public abstract Attribute MakeAttribute (Color fore, Color back); - - /// - /// Gets the current . - /// - /// The current attribute. - public Attribute GetAttribute () => CurrentAttribute; + FramePadding = 0b_0000_0010, + } - /// - /// Make the for the . - /// - /// The foreground color. - /// The background color. - /// The attribute for the foreground and background colors. - public abstract Attribute MakeColor (Color foreground, Color background); + /// + /// Set flags to enable/disable diagnostics. + /// + public static DiagnosticFlags Diagnostics { get; set; } - /// - /// Ensures all s in are correctly - /// initialized by the driver. - /// - /// - /// This method was previsouly named CreateColors. It was reanmed to InitalizeColorSchemes when - /// was enabled. - /// - /// Flag indicating if colors are supported (not used). - public void InitalizeColorSchemes (bool supportsColors = true) - { - // Ensure all Attributes are initialized by the driver - foreach (var s in Colors.ColorSchemes) { - s.Value.Initialize (); - } + /// + /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. + /// + /// This is only implemented in . + public abstract void Suspend (); - if (!supportsColors) { - return; + /// + /// Simulates a key press. + /// + /// The key character. + /// The key. + /// If simulates the Shift key being pressed. + /// If simulates the Alt key being pressed. + /// If simulates the Ctrl key being pressed. + public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); + + // TODO: Move FillRect to ./Drawing + /// + /// Fills the specified rectangle with the specified rune. + /// + /// + /// + public void FillRect (Rect rect, Rune rune = default) + { + for (var r = rect.Y; r < rect.Y + rect.Height; r++) { + for (var c = rect.X; c < rect.X + rect.Width; c++) { + Application.Driver.Move (c, r); + Application.Driver.AddRune (rune == default ? new Rune (' ') : rune); } - - } - - internal void SetAttribute (object attribute) - { - throw new NotImplementedException (); } } /// - /// Helper class for console drivers to invoke shell commands to interact with the clipboard. - /// Used primarily by CursesDriver, but also used in Unit tests which is why it is in - /// ConsoleDriver.cs. + /// Fills the specified rectangle with the specified . This method + /// is a convenience method that calls . /// - internal static class ClipboardProcessRunner { - public static (int exitCode, string result) Bash (string commandLine, string inputText = "", bool waitForOutput = false) - { - var arguments = $"-c \"{commandLine}\""; - var (exitCode, result) = Process ("bash", arguments, inputText, waitForOutput); + /// + /// + public void FillRect (Rect rect, char c) => FillRect (rect, new Rune (c)); - return (exitCode, result.TrimEnd ()); - } + /// + /// Ends the execution of the console driver. + /// + public abstract void End (); + + /// + /// Returns the name of the driver and relevant library version information. + /// + /// + public virtual string GetVersionInfo () => GetType ().Name; +} - public static (int exitCode, string result) Process (string cmd, string arguments, string input = null, bool waitForOutput = true) - { - var output = string.Empty; - - using (Process process = new Process { - StartInfo = new ProcessStartInfo { - FileName = cmd, - Arguments = arguments, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - } - }) { - var eventHandled = new TaskCompletionSource (); - process.Start (); - if (!string.IsNullOrEmpty (input)) { - process.StandardInput.Write (input); - process.StandardInput.Close (); - } - if (!process.WaitForExit (5000)) { - var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}."; - throw new TimeoutException (timeoutError); - } +/// +/// 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 of the XTerm user configuration settings so it could be Block, I-Beam, Underline with possible blinking. + Default = 0x00010119, - if (waitForOutput && process.StandardOutput.Peek () != -1) { - output = process.StandardOutput.ReadToEnd (); - } + /// + /// Cursor caret is hidden + /// + Invisible = 0x03000019, - if (process.ExitCode > 0) { - output = $@"Process failed to run. Command line: {cmd} {arguments}. - Output: {output} - Error: {process.StandardError.ReadToEnd ()}"; - } + /// + /// Cursor caret is normally shown as a blinking underline bar _ + /// + Underline = 0x03010119, - return (process.ExitCode, output); - } - } + /// + /// Cursor caret is normally shown as a underline bar _ + /// + /// Under Windows, this is equivalent to + UnderlineFix = 0x04010119, - public static bool DoubleWaitForExit (this System.Diagnostics.Process process) - { - var result = process.WaitForExit (500); - if (result) { - process.WaitForExit (); - } - return result; - } + /// + /// Cursor caret is displayed a blinking vertical bar | + /// + /// Works under Xterm-like terminal otherwise this is equivalent to + Vertical = 0x05010119, - public static bool FileExists (this string value) - { - return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); - } - } + /// + /// 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/ConsoleDrivers/CursesDriver/ClipboardImpl.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs new file mode 100644 index 0000000000..ddf5811851 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs @@ -0,0 +1,225 @@ +using System; +using System.Runtime.InteropServices; +using Unix.Terminal; + +namespace Terminal.Gui; + +/// +/// A clipboard implementation for Linux. +/// This implementation uses the xclip command to access the clipboard. +/// +/// +/// If xclip is not installed, this implementation will not work. +/// +class CursesClipboard : ClipboardBase { + public CursesClipboard () + { + IsSupported = CheckSupport (); + } + + string _xclipPath = string.Empty; + public override bool IsSupported { get; } + + bool CheckSupport () + { +#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception. + try { + var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true); + if (exitCode == 0 && result.FileExists ()) { + _xclipPath = result; + return true; + } + } catch (Exception) { + // Permissions issue. + } +#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception. + return false; + } + + protected override string GetClipboardDataImpl () + { + var tempFileName = System.IO.Path.GetTempFileName (); + var xclipargs = "-selection clipboard -o"; + + try { + var (exitCode, result) = ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false); + if (exitCode == 0) { + if (Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } + return System.IO.File.ReadAllText (tempFileName); + } + } catch (Exception e) { + throw new NotSupportedException ($"\"{_xclipPath} {xclipargs}\" failed.", e); + } finally { + System.IO.File.Delete (tempFileName); + } + return string.Empty; + } + + protected override void SetClipboardDataImpl (string text) + { + var xclipargs = "-selection clipboard -i"; + try { + var (exitCode, _) = ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs}", text, waitForOutput: false); + if (exitCode == 0 && Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } + } catch (Exception e) { + throw new NotSupportedException ($"\"{_xclipPath} {xclipargs} < {text}\" failed", e); + } + } +} +/// +/// 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. +/// +class MacOSXClipboard : ClipboardBase { + IntPtr _nsString = objc_getClass ("NSString"); + IntPtr _nsPasteboard = objc_getClass ("NSPasteboard"); + IntPtr _utfTextType; + IntPtr _generalPasteboard; + IntPtr _initWithUtf8Register = sel_registerName ("initWithUTF8String:"); + IntPtr _allocRegister = sel_registerName ("alloc"); + IntPtr _setStringRegister = sel_registerName ("setString:forType:"); + IntPtr _stringForTypeRegister = sel_registerName ("stringForType:"); + IntPtr _utf8Register = sel_registerName ("UTF8String"); + IntPtr _nsStringPboardType; + IntPtr _generalPasteboardRegister = sel_registerName ("generalPasteboard"); + IntPtr _clearContentsRegister = sel_registerName ("clearContents"); + + public MacOSXClipboard () + { + _utfTextType = objc_msgSend (objc_msgSend (_nsString, _allocRegister), _initWithUtf8Register, "public.utf8-plain-text"); + _nsStringPboardType = objc_msgSend (objc_msgSend (_nsString, _allocRegister), _initWithUtf8Register, "NSStringPboardType"); + _generalPasteboard = objc_msgSend (_nsPasteboard, _generalPasteboardRegister); + IsSupported = CheckSupport (); + } + + public override bool IsSupported { get; } + + bool CheckSupport () + { + var (exitCode, result) = ClipboardProcessRunner.Bash ("which pbcopy", waitForOutput: true); + if (exitCode != 0 || !result.FileExists ()) { + return false; + } + (exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste", waitForOutput: true); + return exitCode == 0 && result.FileExists (); + } + + protected override string GetClipboardDataImpl () + { + var ptr = objc_msgSend (_generalPasteboard, _stringForTypeRegister, _nsStringPboardType); + var charArray = objc_msgSend (ptr, _utf8Register); + return Marshal.PtrToStringAnsi (charArray); + } + + protected override void SetClipboardDataImpl (string text) + { + IntPtr str = default; + try { + str = objc_msgSend (objc_msgSend (_nsString, _allocRegister), _initWithUtf8Register, text); + objc_msgSend (_generalPasteboard, _clearContentsRegister); + objc_msgSend (_generalPasteboard, _setStringRegister, str, _utfTextType); + } finally { + if (str != default) { + objc_msgSend (str, sel_registerName ("release")); + } + } + } + + [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_getClass (string className); + + [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector); + + [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, string arg1); + + [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1); + + [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2); + + [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] + static extern IntPtr sel_registerName (string selectorName); +} + +/// +/// A clipboard implementation for Linux, when running under WSL. +/// This implementation uses the Windows clipboard to store the data, and uses Windows' +/// powershell.exe (launched via WSL interop services) to set/get the Windows +/// clipboard. +/// +class WSLClipboard : ClipboardBase { + public WSLClipboard () + { + } + + public override bool IsSupported { + get { + return CheckSupport (); + } + } + + private static string _powershellPath = string.Empty; + + bool CheckSupport () + { + if (string.IsNullOrEmpty (_powershellPath)) { + // Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL) + var (exitCode, result) = ClipboardProcessRunner.Bash ("which pwsh.exe", waitForOutput: true); + if (exitCode > 0) { + (exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe", waitForOutput: true); + } + + if (exitCode == 0) { + _powershellPath = result; + } + } + return !string.IsNullOrEmpty (_powershellPath); + } + + protected override string GetClipboardDataImpl () + { + if (!IsSupported) { + return string.Empty; + } + + var (exitCode, output) = ClipboardProcessRunner.Process (_powershellPath, "-noprofile -command \"Get-Clipboard\""); + if (exitCode == 0) { + if (Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } + + if (output.EndsWith ("\r\n")) { + output = output.Substring (0, output.Length - 2); + } + return output; + } + return string.Empty; + } + + protected override void SetClipboardDataImpl (string text) + { + if (!IsSupported) { + return; + } + + var (exitCode, output) = ClipboardProcessRunner.Process (_powershellPath, $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""); + if (exitCode == 0) { + if (Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } + } + } +} diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index eaba76d7e4..54fda896f1 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -3,1183 +3,840 @@ // using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Threading.Tasks; using System.Text; using Unix.Terminal; -namespace Terminal.Gui { +namespace Terminal.Gui; - /// - /// This is the Curses driver for the gui.cs/Terminal framework. - /// - internal class CursesDriver : ConsoleDriver { - public override int Cols => Curses.Cols; - public override int Rows => Curses.Lines; - public override int Left => 0; - public override int Top => 0; - public override bool EnableConsoleScrolling { get; set; } - public override IClipboard Clipboard { get => clipboard; } - - CursorVisibility? initialCursorVisibility = null; - CursorVisibility? currentCursorVisibility = null; - IClipboard clipboard; - int [,,] contents; - - public override int [,,] Contents => contents; - - // Current row, and current col, tracked by Move/AddRune only - int ccol, crow; - bool needMove; - public override void Move (int col, int row) - { - ccol = col; - crow = row; +/// +/// This is the Curses driver for the gui.cs/Terminal framework. +/// +internal class CursesDriver : ConsoleDriver { + public override int Cols => Curses.Cols; + public override int Rows => Curses.Lines; - if (Clip.Contains (col, row)) { - Curses.move (row, col); - needMove = false; - } else { - Curses.move (Clip.Y, Clip.X); - needMove = true; - } - } - - static bool sync = false; + CursorVisibility? _initialCursorVisibility = null; + CursorVisibility? _currentCursorVisibility = null; - public override bool IsRuneSupported (Rune rune) - { - // See Issue #2615 - CursesDriver is broken with non-BMP characters - return base.IsRuneSupported (rune) && rune.IsBmp; - } + public override string GetVersionInfo () => $"{Curses.curses_version()}"; - public override void AddRune (Rune rune) - { - if (!IsRuneSupported (rune)) { - rune = Rune.ReplacementChar; - } + public override void Move (int col, int row) + { + base.Move (col, row); - rune = rune.MakePrintable (); - var runeWidth = rune.GetColumns (); - var validClip = IsValidContent (ccol, crow, Clip); + if (IsValidLocation (col, row)) { + Curses.move (row, col); + } else { + // Not a valid location (outside screen or clip region) + // Move within the clip region, then AddRune will actually move to Col, Row + Curses.move (Clip.Y, Clip.X); + } + } - if (validClip) { - if (needMove) { - Curses.move (crow, ccol); - needMove = false; - } - if (runeWidth == 0 && ccol > 0) { - var r = contents [crow, ccol - 1, 0]; - var s = new string (new char [] { (char)r, (char)rune.Value }); - string sn; - if (!s.IsNormalized ()) { - sn = s.Normalize (); - } else { - sn = s; - } - var c = sn [0]; - Curses.mvaddch (crow, ccol - 1, (int)(uint)c); - contents [crow, ccol - 1, 0] = c; - contents [crow, ccol - 1, 1] = CurrentAttribute; - contents [crow, ccol - 1, 2] = 1; + public override bool IsRuneSupported (Rune rune) + { + // See Issue #2615 - CursesDriver is broken with non-BMP characters + return base.IsRuneSupported (rune) && rune.IsBmp; + } - } else { - if (runeWidth < 2 && ccol > 0 - && ((Rune)(char)contents [crow, ccol - 1, 0]).GetColumns () > 1) { - - var curAtttib = CurrentAttribute; - Curses.attrset (contents [crow, ccol - 1, 1]); - Curses.mvaddch (crow, ccol - 1, (int)(uint)' '); - contents [crow, ccol - 1, 0] = (int)(uint)' '; - Curses.move (crow, ccol); - Curses.attrset (curAtttib); - - } else if (runeWidth < 2 && ccol <= Clip.Right - 1 - && ((Rune)(char)contents [crow, ccol, 0]).GetColumns () > 1) { - - var curAtttib = CurrentAttribute; - Curses.attrset (contents [crow, ccol + 1, 1]); - Curses.mvaddch (crow, ccol + 1, (int)(uint)' '); - contents [crow, ccol + 1, 0] = (int)(uint)' '; - Curses.move (crow, ccol); - Curses.attrset (curAtttib); + public override void Refresh () + { + UpdateScreen (); + UpdateCursor (); + } - } - if (runeWidth > 1 && ccol == Clip.Right - 1) { - Curses.addch ((int)(uint)' '); - contents [crow, ccol, 0] = (int)(uint)' '; - } else { - Curses.addch ((int)(uint)rune.Value); - contents [crow, ccol, 0] = (int)(uint)rune.Value; - } - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 1; - } - } else { - needMove = true; - } + private void ProcessWinChange () + { + if (Curses.CheckWinChange ()) { + ClearContents (); + TerminalResized?.Invoke (); + } + } - if (runeWidth < 0 || runeWidth > 0) { - ccol++; - } + #region Color Handling - if (runeWidth > 1) { - if (validClip && ccol < Clip.Right) { - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 0; - } - ccol++; - } + /// + /// Creates an Attribute from the provided curses-based foreground and background color numbers + /// + /// Contains the curses color number for the foreground (color, plus any attributes) + /// Contains the curses color number for the background (color, plus any attributes) + /// + static Attribute MakeColor (short foreground, short background) + { + var v = (short)((int)foreground | background << 4); + // TODO: for TrueColor - Use InitExtendedPair + Curses.InitColorPair (v, foreground, background); + return new Attribute ( + value: Curses.ColorPair (v), + foreground: CursesColorNumberToColor (foreground), + background: CursesColorNumberToColor (background)); + } - if (sync) { - UpdateScreen (); - } - } + /// + /// In the CursesDriver, colors are encoded as an int. + /// The foreground color is stored in the most significant 4 bits, + /// and the background color is stored in the least significant 4 bits. + /// The Terminal.GUi Color values are converted to curses color encoding before being encoded. + /// + public override Attribute MakeColor (Color fore, Color back) + { + return MakeColor (ColorToCursesColorNumber (fore), ColorToCursesColorNumber (back)); + } - public override void AddStr (string str) - { - // TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly - foreach (var rune in str.EnumerateRunes ()) - AddRune (rune); - } + static short ColorToCursesColorNumber (Color color) + { + switch (color) { + case Color.Black: + return Curses.COLOR_BLACK; + case Color.Blue: + return Curses.COLOR_BLUE; + case Color.Green: + return Curses.COLOR_GREEN; + case Color.Cyan: + return Curses.COLOR_CYAN; + case Color.Red: + return Curses.COLOR_RED; + case Color.Magenta: + return Curses.COLOR_MAGENTA; + case Color.Brown: + return Curses.COLOR_YELLOW; + case Color.Gray: + return Curses.COLOR_WHITE; + case Color.DarkGray: + return Curses.COLOR_GRAY; + case Color.BrightBlue: + return Curses.COLOR_BLUE | Curses.COLOR_GRAY; + case Color.BrightGreen: + return Curses.COLOR_GREEN | Curses.COLOR_GRAY; + case Color.BrightCyan: + return Curses.COLOR_CYAN | Curses.COLOR_GRAY; + case Color.BrightRed: + return Curses.COLOR_RED | Curses.COLOR_GRAY; + case Color.BrightMagenta: + return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY; + case Color.BrightYellow: + return Curses.COLOR_YELLOW | Curses.COLOR_GRAY; + case Color.White: + return Curses.COLOR_WHITE | Curses.COLOR_GRAY; + } + throw new ArgumentException ("Invalid color code"); + } - public override void Refresh () - { - Curses.raw (); - Curses.noecho (); - Curses.refresh (); - ProcessWinChange (); - } + static Color CursesColorNumberToColor (short color) + { + switch (color) { + case Curses.COLOR_BLACK: + return Color.Black; + case Curses.COLOR_BLUE: + return Color.Blue; + case Curses.COLOR_GREEN: + return Color.Green; + case Curses.COLOR_CYAN: + return Color.Cyan; + case Curses.COLOR_RED: + return Color.Red; + case Curses.COLOR_MAGENTA: + return Color.Magenta; + case Curses.COLOR_YELLOW: + return Color.Brown; + case Curses.COLOR_WHITE: + return Color.Gray; + case Curses.COLOR_GRAY: + return Color.DarkGray; + case Curses.COLOR_BLUE | Curses.COLOR_GRAY: + return Color.BrightBlue; + case Curses.COLOR_GREEN | Curses.COLOR_GRAY: + return Color.BrightGreen; + case Curses.COLOR_CYAN | Curses.COLOR_GRAY: + return Color.BrightCyan; + case Curses.COLOR_RED | Curses.COLOR_GRAY: + return Color.BrightRed; + case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY: + return Color.BrightMagenta; + case Curses.COLOR_YELLOW | Curses.COLOR_GRAY: + return Color.BrightYellow; + case Curses.COLOR_WHITE | Curses.COLOR_GRAY: + return Color.White; + } + throw new ArgumentException ("Invalid curses color code"); + } - private void ProcessWinChange () - { - if (Curses.CheckWinChange ()) { - ResizeScreen (); - UpdateOffScreen (); - TerminalResized?.Invoke (); - } - } + /// + /// In the CursesDriver, colors are encoded as an int. + /// The foreground color is stored in the most significant 4 bits, + /// and the background color is stored in the least significant 4 bits. + /// The Terminal.GUI Color values are converted to curses color encoding before being encoded. + /// + internal override void GetColors (int value, out Color foreground, out Color background) + { + // Assume a 4-bit encoded value for both foreground and background colors. + foreground = CursesColorNumberToColor ((short)((value >> 4) & 0xF)); + background = CursesColorNumberToColor ((short)(value & 0xF)); + } - public override void UpdateCursor () => Refresh (); + #endregion - public override void End () - { - StopReportingMouseMoves (); - SetCursorVisibility (CursorVisibility.Default); + public override void UpdateCursor () + { + EnsureCursorVisibility (); - Curses.endwin (); + if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) { + Curses.move (Row, Col); } + } - public override void UpdateScreen () => window.redrawwin (); + public override void End () + { + StopReportingMouseMoves (); + SetCursorVisibility (CursorVisibility.Default); - public override void SetAttribute (Attribute c) - { - base.SetAttribute (c); - Curses.attrset (CurrentAttribute); - } + // throws away any typeahead that has been typed by + // the user and has not yet been read by the program. + Curses.flushinp (); - public Curses.Window window; + Curses.endwin (); + } - //static short last_color_pair = 16; + public override void UpdateScreen () + { + for (int row = 0; row < Rows; row++) { + if (!_dirtyLines [row]) { + continue; + } + _dirtyLines [row] = false; - /// - /// Creates a curses color from the provided foreground and background colors - /// - /// Contains the curses attributes for the foreground (color, plus any attributes) - /// Contains the curses attributes for the background (color, plus any attributes) - /// - public static Attribute MakeColor (short foreground, short background) - { - var v = (short)((int)foreground | background << 4); - //Curses.InitColorPair (++last_color_pair, foreground, background); - Curses.InitColorPair (v, foreground, background); - return new Attribute ( - //value: Curses.ColorPair (last_color_pair), - value: Curses.ColorPair (v), - //foreground: (Color)foreground, - foreground: MapCursesColor (foreground), - //background: (Color)background); - background: MapCursesColor (background)); - } + for (int col = 0; col < Cols; col++) { + if (Contents [row, col].IsDirty == false) { + continue; + } + Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().Value); + + var rune = Contents [row, col].Runes [0]; + if (rune.IsBmp) { + // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell. + if (rune.GetColumns () < 2) { + Curses.mvaddch (row, col, rune.Value); + } else /*if (col + 1 < Cols)*/ { + Curses.mvaddwstr (row, col, rune.ToString ()); + } - public override Attribute MakeColor (Color fore, Color back) - { - return MakeColor ((short)MapColor (fore), (short)MapColor (back)); + } else { + Curses.mvaddwstr (row, col, rune.ToString()); + if (rune.GetColumns () > 1 && col + 1 < Cols) { + // TODO: This is a hack to deal with non-BMP and wide characters. + //col++; + Curses.mvaddch (row, ++col, '*'); + } + } + } } - int [,] colorPairs = new int [16, 16]; + Curses.move (Row, Col); + _window.wrefresh (); + } - public override void SetColors (ConsoleColor foreground, ConsoleColor background) - { - // BUGBUG: This code is never called ?? See Issue #2300 - int f = (short)foreground; - int b = (short)background; - var v = colorPairs [f, b]; - if ((v & 0x10000) == 0) { - b &= 0x7; - bool bold = (f & 0x8) != 0; - f &= 0x7; - - v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0); - colorPairs [(int)foreground, (int)background] = v | 0x1000; - } - SetAttribute (v & 0xffff); + public Curses.Window _window; + + static Key MapCursesKey (int cursesKey) + { + switch (cursesKey) { + case Curses.KeyF1: return Key.F1; + case Curses.KeyF2: return Key.F2; + case Curses.KeyF3: return Key.F3; + case Curses.KeyF4: return Key.F4; + case Curses.KeyF5: return Key.F5; + case Curses.KeyF6: return Key.F6; + case Curses.KeyF7: return Key.F7; + case Curses.KeyF8: return Key.F8; + case Curses.KeyF9: return Key.F9; + case Curses.KeyF10: return Key.F10; + case Curses.KeyF11: return Key.F11; + case Curses.KeyF12: return Key.F12; + case Curses.KeyUp: return Key.CursorUp; + case Curses.KeyDown: return Key.CursorDown; + case Curses.KeyLeft: return Key.CursorLeft; + case Curses.KeyRight: return Key.CursorRight; + case Curses.KeyHome: return Key.Home; + case Curses.KeyEnd: return Key.End; + case Curses.KeyNPage: return Key.PageDown; + case Curses.KeyPPage: return Key.PageUp; + case Curses.KeyDeleteChar: return Key.DeleteChar; + case Curses.KeyInsertChar: return Key.InsertChar; + case Curses.KeyTab: return Key.Tab; + case Curses.KeyBackTab: return Key.BackTab; + case Curses.KeyBackspace: return Key.Backspace; + case Curses.ShiftKeyUp: return Key.CursorUp | Key.ShiftMask; + case Curses.ShiftKeyDown: return Key.CursorDown | Key.ShiftMask; + case Curses.ShiftKeyLeft: return Key.CursorLeft | Key.ShiftMask; + case Curses.ShiftKeyRight: return Key.CursorRight | Key.ShiftMask; + case Curses.ShiftKeyHome: return Key.Home | Key.ShiftMask; + case Curses.ShiftKeyEnd: return Key.End | Key.ShiftMask; + case Curses.ShiftKeyNPage: return Key.PageDown | Key.ShiftMask; + case Curses.ShiftKeyPPage: return Key.PageUp | Key.ShiftMask; + case Curses.AltKeyUp: return Key.CursorUp | Key.AltMask; + case Curses.AltKeyDown: return Key.CursorDown | Key.AltMask; + case Curses.AltKeyLeft: return Key.CursorLeft | Key.AltMask; + case Curses.AltKeyRight: return Key.CursorRight | Key.AltMask; + case Curses.AltKeyHome: return Key.Home | Key.AltMask; + case Curses.AltKeyEnd: return Key.End | Key.AltMask; + case Curses.AltKeyNPage: return Key.PageDown | Key.AltMask; + case Curses.AltKeyPPage: return Key.PageUp | Key.AltMask; + case Curses.CtrlKeyUp: return Key.CursorUp | Key.CtrlMask; + case Curses.CtrlKeyDown: return Key.CursorDown | Key.CtrlMask; + case Curses.CtrlKeyLeft: return Key.CursorLeft | Key.CtrlMask; + case Curses.CtrlKeyRight: return Key.CursorRight | Key.CtrlMask; + case Curses.CtrlKeyHome: return Key.Home | Key.CtrlMask; + case Curses.CtrlKeyEnd: return Key.End | Key.CtrlMask; + case Curses.CtrlKeyNPage: return Key.PageDown | Key.CtrlMask; + case Curses.CtrlKeyPPage: return Key.PageUp | Key.CtrlMask; + case Curses.ShiftCtrlKeyUp: return Key.CursorUp | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftCtrlKeyDown: return Key.CursorDown | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftCtrlKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftCtrlKeyRight: return Key.CursorRight | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftCtrlKeyHome: return Key.Home | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftCtrlKeyEnd: return Key.End | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftCtrlKeyNPage: return Key.PageDown | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftCtrlKeyPPage: return Key.PageUp | Key.ShiftMask | Key.CtrlMask; + case Curses.ShiftAltKeyUp: return Key.CursorUp | Key.ShiftMask | Key.AltMask; + case Curses.ShiftAltKeyDown: return Key.CursorDown | Key.ShiftMask | Key.AltMask; + case Curses.ShiftAltKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.AltMask; + case Curses.ShiftAltKeyRight: return Key.CursorRight | Key.ShiftMask | Key.AltMask; + case Curses.ShiftAltKeyNPage: return Key.PageDown | Key.ShiftMask | Key.AltMask; + case Curses.ShiftAltKeyPPage: return Key.PageUp | Key.ShiftMask | Key.AltMask; + case Curses.ShiftAltKeyHome: return Key.Home | Key.ShiftMask | Key.AltMask; + case Curses.ShiftAltKeyEnd: return Key.End | Key.ShiftMask | Key.AltMask; + case Curses.AltCtrlKeyNPage: return Key.PageDown | Key.AltMask | Key.CtrlMask; + case Curses.AltCtrlKeyPPage: return Key.PageUp | Key.AltMask | Key.CtrlMask; + case Curses.AltCtrlKeyHome: return Key.Home | Key.AltMask | Key.CtrlMask; + case Curses.AltCtrlKeyEnd: return Key.End | Key.AltMask | Key.CtrlMask; + default: return Key.Unknown; } + } - Dictionary rawPairs = new Dictionary (); - public override void SetColors (short foreColorId, short backgroundColorId) - { - // BUGBUG: This code is never called ?? See Issue #2300 - int key = ((ushort)foreColorId << 16) | (ushort)backgroundColorId; - if (!rawPairs.TryGetValue (key, out var v)) { - v = MakeColor (foreColorId, backgroundColorId); - rawPairs [key] = v; - } - SetAttribute (v); - } + KeyModifiers _keyModifiers; - static Key MapCursesKey (int cursesKey) - { - switch (cursesKey) { - case Curses.KeyF1: return Key.F1; - case Curses.KeyF2: return Key.F2; - case Curses.KeyF3: return Key.F3; - case Curses.KeyF4: return Key.F4; - case Curses.KeyF5: return Key.F5; - case Curses.KeyF6: return Key.F6; - case Curses.KeyF7: return Key.F7; - case Curses.KeyF8: return Key.F8; - case Curses.KeyF9: return Key.F9; - case Curses.KeyF10: return Key.F10; - case Curses.KeyF11: return Key.F11; - case Curses.KeyF12: return Key.F12; - case Curses.KeyUp: return Key.CursorUp; - case Curses.KeyDown: return Key.CursorDown; - case Curses.KeyLeft: return Key.CursorLeft; - case Curses.KeyRight: return Key.CursorRight; - case Curses.KeyHome: return Key.Home; - case Curses.KeyEnd: return Key.End; - case Curses.KeyNPage: return Key.PageDown; - case Curses.KeyPPage: return Key.PageUp; - case Curses.KeyDeleteChar: return Key.DeleteChar; - case Curses.KeyInsertChar: return Key.InsertChar; - case Curses.KeyTab: return Key.Tab; - case Curses.KeyBackTab: return Key.BackTab; - case Curses.KeyBackspace: return Key.Backspace; - case Curses.ShiftKeyUp: return Key.CursorUp | Key.ShiftMask; - case Curses.ShiftKeyDown: return Key.CursorDown | Key.ShiftMask; - case Curses.ShiftKeyLeft: return Key.CursorLeft | Key.ShiftMask; - case Curses.ShiftKeyRight: return Key.CursorRight | Key.ShiftMask; - case Curses.ShiftKeyHome: return Key.Home | Key.ShiftMask; - case Curses.ShiftKeyEnd: return Key.End | Key.ShiftMask; - case Curses.ShiftKeyNPage: return Key.PageDown | Key.ShiftMask; - case Curses.ShiftKeyPPage: return Key.PageUp | Key.ShiftMask; - case Curses.AltKeyUp: return Key.CursorUp | Key.AltMask; - case Curses.AltKeyDown: return Key.CursorDown | Key.AltMask; - case Curses.AltKeyLeft: return Key.CursorLeft | Key.AltMask; - case Curses.AltKeyRight: return Key.CursorRight | Key.AltMask; - case Curses.AltKeyHome: return Key.Home | Key.AltMask; - case Curses.AltKeyEnd: return Key.End | Key.AltMask; - case Curses.AltKeyNPage: return Key.PageDown | Key.AltMask; - case Curses.AltKeyPPage: return Key.PageUp | Key.AltMask; - case Curses.CtrlKeyUp: return Key.CursorUp | Key.CtrlMask; - case Curses.CtrlKeyDown: return Key.CursorDown | Key.CtrlMask; - case Curses.CtrlKeyLeft: return Key.CursorLeft | Key.CtrlMask; - case Curses.CtrlKeyRight: return Key.CursorRight | Key.CtrlMask; - case Curses.CtrlKeyHome: return Key.Home | Key.CtrlMask; - case Curses.CtrlKeyEnd: return Key.End | Key.CtrlMask; - case Curses.CtrlKeyNPage: return Key.PageDown | Key.CtrlMask; - case Curses.CtrlKeyPPage: return Key.PageUp | Key.CtrlMask; - case Curses.ShiftCtrlKeyUp: return Key.CursorUp | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftCtrlKeyDown: return Key.CursorDown | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftCtrlKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftCtrlKeyRight: return Key.CursorRight | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftCtrlKeyHome: return Key.Home | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftCtrlKeyEnd: return Key.End | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftCtrlKeyNPage: return Key.PageDown | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftCtrlKeyPPage: return Key.PageUp | Key.ShiftMask | Key.CtrlMask; - case Curses.ShiftAltKeyUp: return Key.CursorUp | Key.ShiftMask | Key.AltMask; - case Curses.ShiftAltKeyDown: return Key.CursorDown | Key.ShiftMask | Key.AltMask; - case Curses.ShiftAltKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.AltMask; - case Curses.ShiftAltKeyRight: return Key.CursorRight | Key.ShiftMask | Key.AltMask; - case Curses.ShiftAltKeyNPage: return Key.PageDown | Key.ShiftMask | Key.AltMask; - case Curses.ShiftAltKeyPPage: return Key.PageUp | Key.ShiftMask | Key.AltMask; - case Curses.ShiftAltKeyHome: return Key.Home | Key.ShiftMask | Key.AltMask; - case Curses.ShiftAltKeyEnd: return Key.End | Key.ShiftMask | Key.AltMask; - case Curses.AltCtrlKeyNPage: return Key.PageDown | Key.AltMask | Key.CtrlMask; - case Curses.AltCtrlKeyPPage: return Key.PageUp | Key.AltMask | Key.CtrlMask; - case Curses.AltCtrlKeyHome: return Key.Home | Key.AltMask | Key.CtrlMask; - case Curses.AltCtrlKeyEnd: return Key.End | Key.AltMask | Key.CtrlMask; - default: return Key.Unknown; - } + KeyModifiers MapKeyModifiers (Key key) + { + if (_keyModifiers == null) { + _keyModifiers = new KeyModifiers (); } - KeyModifiers keyModifiers; - - KeyModifiers MapKeyModifiers (Key key) - { - if (keyModifiers == null) - keyModifiers = new KeyModifiers (); + if (!_keyModifiers.Shift && (key & Key.ShiftMask) != 0) { + _keyModifiers.Shift = true; + } + if (!_keyModifiers.Alt && (key & Key.AltMask) != 0) { + _keyModifiers.Alt = true; + } + if (!_keyModifiers.Ctrl && (key & Key.CtrlMask) != 0) { + _keyModifiers.Ctrl = true; + } - if (!keyModifiers.Shift && (key & Key.ShiftMask) != 0) - keyModifiers.Shift = true; - if (!keyModifiers.Alt && (key & Key.AltMask) != 0) - keyModifiers.Alt = true; - if (!keyModifiers.Ctrl && (key & Key.CtrlMask) != 0) - keyModifiers.Ctrl = true; + return _keyModifiers; + } - return keyModifiers; + void ProcessInput () + { + int wch; + var code = Curses.get_wch (out wch); + //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}"); + if (code == Curses.ERR) { + return; } - void ProcessInput () - { - int wch; - var code = Curses.get_wch (out wch); - //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}"); - if (code == Curses.ERR) - return; - - keyModifiers = new KeyModifiers (); - Key k = Key.Null; + _keyModifiers = new KeyModifiers (); + Key k = Key.Null; - if (code == Curses.KEY_CODE_YES) { - if (wch == Curses.KeyResize) { - ProcessWinChange (); - } - if (wch == Curses.KeyMouse) { - int wch2 = wch; + if (code == Curses.KEY_CODE_YES) { + while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) { + ProcessWinChange (); + code = Curses.get_wch (out wch); + } + if (wch == 0) { + return; + } + if (wch == Curses.KeyMouse) { + int wch2 = wch; - while (wch2 == Curses.KeyMouse) { - KeyEvent key = null; - ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { + while (wch2 == Curses.KeyMouse) { + KeyEvent key = null; + ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false), new ConsoleKeyInfo ('[', 0, false, false, false), new ConsoleKeyInfo ('<', 0, false, false, false) }; - code = 0; - GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki); - } - return; - } - k = MapCursesKey (wch); - if (wch >= 277 && wch <= 288) { // Shift+(F1 - F12) - wch -= 12; - k = Key.ShiftMask | MapCursesKey (wch); - } else if (wch >= 289 && wch <= 300) { // Ctrl+(F1 - F12) - wch -= 24; - k = Key.CtrlMask | MapCursesKey (wch); - } else if (wch >= 301 && wch <= 312) { // Ctrl+Shift+(F1 - F12) - wch -= 36; - k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch); - } else if (wch >= 313 && wch <= 324) { // Alt+(F1 - F12) - wch -= 48; - k = Key.AltMask | MapCursesKey (wch); - } else if (wch >= 325 && wch <= 327) { // Shift+Alt+(F1 - F3) - wch -= 60; - k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch); + code = 0; + HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); } - keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); - keyHandler (new KeyEvent (k, MapKeyModifiers (k))); - keyUpHandler (new KeyEvent (k, MapKeyModifiers (k))); return; } + k = MapCursesKey (wch); + if (wch >= 277 && wch <= 288) { + // Shift+(F1 - F12) + wch -= 12; + k = Key.ShiftMask | MapCursesKey (wch); + } else if (wch >= 289 && wch <= 300) { + // Ctrl+(F1 - F12) + wch -= 24; + k = Key.CtrlMask | MapCursesKey (wch); + } else if (wch >= 301 && wch <= 312) { + // Ctrl+Shift+(F1 - F12) + wch -= 36; + k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch); + } else if (wch >= 313 && wch <= 324) { + // Alt+(F1 - F12) + wch -= 48; + k = Key.AltMask | MapCursesKey (wch); + } else if (wch >= 325 && wch <= 327) { + // Shift+Alt+(F1 - F3) + wch -= 60; + k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch); + } + _keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); + _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + _keyUpHandler (new KeyEvent (k, MapKeyModifiers (k))); + return; + } + + // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey + if (wch == 27) { + Curses.timeout (10); + + code = Curses.get_wch (out int wch2); - // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey - if (wch == 27) { - Curses.timeout (10); - - code = Curses.get_wch (out int wch2); - - if (code == Curses.KEY_CODE_YES) { - k = Key.AltMask | MapCursesKey (wch); - } - if (code == 0) { - KeyEvent key = null; - - // The ESC-number handling, debatable. - // Simulates the AltMask itself by pressing Alt + Space. - if (wch2 == (int)Key.Space) { - k = Key.AltMask; - } else if (wch2 - (int)Key.Space >= (uint)Key.A && wch2 - (int)Key.Space <= (uint)Key.Z) { - k = (Key)((uint)Key.AltMask + (wch2 - (int)Key.Space)); - } else if (wch2 >= (uint)Key.A - 64 && wch2 <= (uint)Key.Z - 64) { - k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64)); - } else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) { - k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0)); - } else if (wch2 == Curses.KeyCSI) { - ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { + if (code == Curses.KEY_CODE_YES) { + k = Key.AltMask | MapCursesKey (wch); + } + if (code == 0) { + KeyEvent key = null; + + // The ESC-number handling, debatable. + // Simulates the AltMask itself by pressing Alt + Space. + if (wch2 == (int)Key.Space) { + k = Key.AltMask; + } else if (wch2 - (int)Key.Space >= (uint)Key.A && wch2 - (int)Key.Space <= (uint)Key.Z) { + k = (Key)((uint)Key.AltMask + (wch2 - (int)Key.Space)); + } else if (wch2 >= (uint)Key.A - 64 && wch2 <= (uint)Key.Z - 64) { + k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64)); + } else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) { + k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0)); + } else if (wch2 == Curses.KeyCSI) { + ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false), new ConsoleKeyInfo ('[', 0, false, false, false) }; - GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki); - return; + HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); + return; + } else { + // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. + if (((Key)wch2 & Key.CtrlMask) != 0) { + _keyModifiers.Ctrl = true; + } + if (wch2 == 0) { + k = Key.CtrlMask | Key.AltMask | Key.Space; + } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) { + _keyModifiers.Shift = true; + _keyModifiers.Alt = true; + } else if (wch2 < 256) { + k = (Key)wch2; + _keyModifiers.Alt = true; } else { - // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. - if (((Key)wch2 & Key.CtrlMask) != 0) { - keyModifiers.Ctrl = true; - } - if (wch2 == 0) { - k = Key.CtrlMask | Key.AltMask | Key.Space; - } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) { - keyModifiers.Shift = true; - keyModifiers.Alt = true; - } else if (wch2 < 256) { - k = (Key)wch2; - keyModifiers.Alt = true; - } else { - k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2); - } + k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2); } - key = new KeyEvent (k, MapKeyModifiers (k)); - keyDownHandler (key); - keyHandler (key); - } else { - k = Key.Esc; - keyHandler (new KeyEvent (k, MapKeyModifiers (k))); } - } else if (wch == Curses.KeyTab) { - k = MapCursesKey (wch); - keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); - keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + key = new KeyEvent (k, MapKeyModifiers (k)); + _keyDownHandler (key); + _keyHandler (key); } else { - // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa. - k = (Key)wch; - if (wch == 0) { - k = Key.CtrlMask | Key.Space; - } else if (wch >= (uint)Key.A - 64 && wch <= (uint)Key.Z - 64) { - if ((Key)(wch + 64) != Key.J) { - k = Key.CtrlMask | (Key)(wch + 64); - } - } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) { - keyModifiers.Shift = true; + k = Key.Esc; + _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + } + } else if (wch == Curses.KeyTab) { + k = MapCursesKey (wch); + _keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); + _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + } else { + // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa. + k = (Key)wch; + if (wch == 0) { + k = Key.CtrlMask | Key.Space; + } else if (wch >= (uint)Key.A - 64 && wch <= (uint)Key.Z - 64) { + if ((Key)(wch + 64) != Key.J) { + k = Key.CtrlMask | (Key)(wch + 64); } - keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); - keyHandler (new KeyEvent (k, MapKeyModifiers (k))); - keyUpHandler (new KeyEvent (k, MapKeyModifiers (k))); - } - // Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above - // will not impact KeyUp. - // This is causing ESC firing even if another keystroke was handled. - //if (wch == Curses.KeyTab) { - // keyUpHandler (new KeyEvent (MapCursesKey (wch), keyModifiers)); - //} else { - // keyUpHandler (new KeyEvent ((Key)wch, keyModifiers)); - //} - } + } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) { + _keyModifiers.Shift = true; + } + _keyDownHandler (new KeyEvent (k, MapKeyModifiers (k))); + _keyHandler (new KeyEvent (k, MapKeyModifiers (k))); + _keyUpHandler (new KeyEvent (k, MapKeyModifiers (k))); + } + // Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above + // will not impact KeyUp. + // This is causing ESC firing even if another keystroke was handled. + //if (wch == Curses.KeyTab) { + // keyUpHandler (new KeyEvent (MapCursesKey (wch), keyModifiers)); + //} else { + // keyUpHandler (new KeyEvent ((Key)wch, keyModifiers)); + //} + } - void GetEscSeq (ref int code, ref Key k, ref int wch2, ref KeyEvent key, ref ConsoleKeyInfo [] cki) - { - ConsoleKey ck = 0; - ConsoleModifiers mod = 0; - while (code == 0) { - code = Curses.get_wch (out wch2); - var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false); - if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) { - EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List mouseFlags, out Point pos, out _, ProcessContinuousButtonPressed); - if (isKeyMouse) { - foreach (var mf in mouseFlags) { - ProcessMouseEvent (mf, pos); - } - cki = null; - if (wch2 == 27) { - cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0, - false, false, false), cki); - } - } else { - k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _); - k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k); - key = new KeyEvent (k, MapKeyModifiers (k)); - keyDownHandler (key); - keyHandler (key); + void HandleEscSeqResponse (ref int code, ref Key k, ref int wch2, ref KeyEvent key, ref ConsoleKeyInfo [] cki) + { + ConsoleKey ck = 0; + ConsoleModifiers mod = 0; + while (code == 0) { + code = Curses.get_wch (out wch2); + var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false); + if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) { + EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List mouseFlags, out Point pos, out _, ProcessContinuousButtonPressed); + if (isKeyMouse) { + foreach (var mf in mouseFlags) { + ProcessMouseEvent (mf, pos); + } + cki = null; + if (wch2 == 27) { + cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0, + false, false, false), cki); } } else { - cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki); + k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _); + k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k); + key = new KeyEvent (k, MapKeyModifiers (k)); + _keyDownHandler (key); + _keyHandler (key); } + } else { + cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki); } } + } + + MouseFlags _lastMouseFlags; - void ProcessMouseEvent (MouseFlags mouseFlag, Point pos) + void ProcessMouseEvent (MouseFlags mouseFlag, Point pos) + { + bool WasButtonReleased (MouseFlags flag) { - var me = new MouseEvent () { - Flags = mouseFlag, - X = pos.X, - Y = pos.Y - }; - mouseHandler (me); + return flag.HasFlag (MouseFlags.Button1Released) || + flag.HasFlag (MouseFlags.Button2Released) || + flag.HasFlag (MouseFlags.Button3Released) || + flag.HasFlag (MouseFlags.Button4Released); } - void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos) + bool IsButtonNotPressed (MouseFlags flag) { - ProcessMouseEvent (mouseFlag, pos); + return !flag.HasFlag (MouseFlags.Button1Pressed) && + !flag.HasFlag (MouseFlags.Button2Pressed) && + !flag.HasFlag (MouseFlags.Button3Pressed) && + !flag.HasFlag (MouseFlags.Button4Pressed); } - Action keyHandler; - Action keyDownHandler; - Action keyUpHandler; - Action mouseHandler; - - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) + bool IsButtonClickedOrDoubleClicked (MouseFlags flag) { - // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - Curses.timeout (0); - this.keyHandler = keyHandler; - this.keyDownHandler = keyDownHandler; - this.keyUpHandler = keyUpHandler; - this.mouseHandler = mouseHandler; - - var mLoop = mainLoop.Driver as UnixMainLoop; - - mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => { - ProcessInput (); - return true; - }); - - mLoop.WinChanged += () => { - ProcessWinChange (); - }; + return flag.HasFlag (MouseFlags.Button1Clicked) || + flag.HasFlag (MouseFlags.Button2Clicked) || + flag.HasFlag (MouseFlags.Button3Clicked) || + flag.HasFlag (MouseFlags.Button4Clicked) || + flag.HasFlag (MouseFlags.Button1DoubleClicked) || + flag.HasFlag (MouseFlags.Button2DoubleClicked) || + flag.HasFlag (MouseFlags.Button3DoubleClicked) || + flag.HasFlag (MouseFlags.Button4DoubleClicked); } - public override void Init (Action terminalResized) - { - if (window != null) - return; + if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || + (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0)) { + return; + } - try { - window = Curses.initscr (); - Curses.set_escdelay (10); - } catch (Exception e) { - throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}"); - } + _lastMouseFlags = mouseFlag; - // Ensures that all procedures are performed at some previous closing. - Curses.doupdate (); + var me = new MouseEvent () { + Flags = mouseFlag, + X = pos.X, + Y = pos.Y + }; + _mouseHandler (me); + } - // - // We are setting Invisible as default so we could ignore XTerm DECSUSR setting - // - switch (Curses.curs_set (0)) { - case 0: - currentCursorVisibility = initialCursorVisibility = CursorVisibility.Invisible; - break; - case 1: - currentCursorVisibility = initialCursorVisibility = CursorVisibility.Underline; - Curses.curs_set (1); - break; + void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos) + { + ProcessMouseEvent (mouseFlag, pos); + } - case 2: - currentCursorVisibility = initialCursorVisibility = CursorVisibility.Box; - Curses.curs_set (2); - break; + Action _keyHandler; + Action _keyDownHandler; + Action _keyUpHandler; + Action _mouseHandler; - default: - currentCursorVisibility = initialCursorVisibility = null; - break; - } + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) + { + // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called + Curses.timeout (0); + this._keyHandler = keyHandler; + this._keyDownHandler = keyDownHandler; + this._keyUpHandler = keyUpHandler; + this._mouseHandler = mouseHandler; - if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { - clipboard = new MacOSXClipboard (); - } else { - if (Is_WSL_Platform ()) { - clipboard = new WSLClipboard (); - } else { - clipboard = new CursesClipboard (); - } - } + var mLoop = mainLoop.MainLoopDriver as UnixMainLoop; - Curses.raw (); - Curses.noecho (); + mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => { + ProcessInput (); + return true; + }); - Curses.Window.Standard.keypad (true); - TerminalResized = terminalResized; - StartReportingMouseMoves (); + mLoop.WinChanged += ProcessInput; + } - CurrentAttribute = MakeColor (Color.White, Color.Black); + public override void Init (Action terminalResized) + { + if (_window != null) { + return; + } - if (Curses.HasColors) { - Curses.StartColor (); - Curses.UseDefaultColors (); + try { + _window = Curses.initscr (); + Curses.set_escdelay (10); + } catch (Exception e) { + throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}"); + } - InitalizeColorSchemes (); - } else { - InitalizeColorSchemes (false); - - // BUGBUG: This is a hack to make the colors work on the Mac? - // The new Theme support overwrites these colors, so this is not needed? - Colors.TopLevel.Normal = Curses.COLOR_GREEN; - Colors.TopLevel.Focus = Curses.COLOR_WHITE; - Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW; - Colors.TopLevel.HotFocus = Curses.COLOR_YELLOW; - Colors.TopLevel.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; - Colors.Base.Normal = Curses.A_NORMAL; - Colors.Base.Focus = Curses.A_REVERSE; - Colors.Base.HotNormal = Curses.A_BOLD; - Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE; - Colors.Base.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; - Colors.Menu.Normal = Curses.A_REVERSE; - Colors.Menu.Focus = Curses.A_NORMAL; - Colors.Menu.HotNormal = Curses.A_BOLD; - Colors.Menu.HotFocus = Curses.A_NORMAL; - Colors.Menu.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; - Colors.Dialog.Normal = Curses.A_REVERSE; - Colors.Dialog.Focus = Curses.A_NORMAL; - Colors.Dialog.HotNormal = Curses.A_BOLD; - Colors.Dialog.HotFocus = Curses.A_NORMAL; - Colors.Dialog.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; - Colors.Error.Normal = Curses.A_BOLD; - Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE; - Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE; - Colors.Error.HotFocus = Curses.A_REVERSE; - Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; - } + // Ensures that all procedures are performed at some previous closing. + Curses.doupdate (); - ResizeScreen (); - UpdateOffScreen (); + // + // We are setting Invisible as default so we could ignore XTerm DECSUSR setting + // + switch (Curses.curs_set (0)) { + case 0: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible; + break; - } + case 1: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline; + Curses.curs_set (1); + break; - public override void ResizeScreen () - { - Clip = new Rect (0, 0, Cols, Rows); - Curses.refresh (); - } + case 2: + _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box; + Curses.curs_set (2); + break; - public override void UpdateOffScreen () - { - contents = new int [Rows, Cols, 3]; - for (int row = 0; row < Rows; row++) { - for (int col = 0; col < Cols; col++) { - //Curses.move (row, col); - //Curses.attrset (Colors.TopLevel.Normal); - //Curses.addch ((int)(uint)' '); - contents [row, col, 0] = ' '; - contents [row, col, 1] = Colors.TopLevel.Normal; - contents [row, col, 2] = 0; - } - } + default: + _currentCursorVisibility = _initialCursorVisibility = null; + break; } - public static bool Is_WSL_Platform () - { - // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell - //if (new CursesClipboard ().IsSupported) { - // // If xclip is installed on Linux under WSL, this will return true. - // return false; - //} - var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true); - if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) { - return true; + if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { + Clipboard = new MacOSXClipboard (); + } else { + if (Is_WSL_Platform ()) { + Clipboard = new WSLClipboard (); + } else { + Clipboard = new CursesClipboard (); } - return false; } - static int MapColor (Color color) - { - switch (color) { - case Color.Black: - return Curses.COLOR_BLACK; - case Color.Blue: - return Curses.COLOR_BLUE; - case Color.Green: - return Curses.COLOR_GREEN; - case Color.Cyan: - return Curses.COLOR_CYAN; - case Color.Red: - return Curses.COLOR_RED; - case Color.Magenta: - return Curses.COLOR_MAGENTA; - case Color.Brown: - return Curses.COLOR_YELLOW; - case Color.Gray: - return Curses.COLOR_WHITE; - case Color.DarkGray: - //return Curses.COLOR_BLACK | Curses.A_BOLD; - return Curses.COLOR_GRAY; - case Color.BrightBlue: - return Curses.COLOR_BLUE | Curses.A_BOLD | Curses.COLOR_GRAY; - case Color.BrightGreen: - return Curses.COLOR_GREEN | Curses.A_BOLD | Curses.COLOR_GRAY; - case Color.BrightCyan: - return Curses.COLOR_CYAN | Curses.A_BOLD | Curses.COLOR_GRAY; - case Color.BrightRed: - return Curses.COLOR_RED | Curses.A_BOLD | Curses.COLOR_GRAY; - case Color.BrightMagenta: - return Curses.COLOR_MAGENTA | Curses.A_BOLD | Curses.COLOR_GRAY; - case Color.BrightYellow: - return Curses.COLOR_YELLOW | Curses.A_BOLD | Curses.COLOR_GRAY; - case Color.White: - return Curses.COLOR_WHITE | Curses.A_BOLD | Curses.COLOR_GRAY; - } - throw new ArgumentException ("Invalid color code"); + if (!Curses.HasColors) { + throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does."); } - static Color MapCursesColor (int color) - { - switch (color) { - case Curses.COLOR_BLACK: - return Color.Black; - case Curses.COLOR_BLUE: - return Color.Blue; - case Curses.COLOR_GREEN: - return Color.Green; - case Curses.COLOR_CYAN: - return Color.Cyan; - case Curses.COLOR_RED: - return Color.Red; - case Curses.COLOR_MAGENTA: - return Color.Magenta; - case Curses.COLOR_YELLOW: - return Color.Brown; - case Curses.COLOR_WHITE: - return Color.Gray; - case Curses.COLOR_GRAY: - return Color.DarkGray; - case Curses.COLOR_BLUE | Curses.COLOR_GRAY: - return Color.BrightBlue; - case Curses.COLOR_GREEN | Curses.COLOR_GRAY: - return Color.BrightGreen; - case Curses.COLOR_CYAN | Curses.COLOR_GRAY: - return Color.BrightCyan; - case Curses.COLOR_RED | Curses.COLOR_GRAY: - return Color.BrightRed; - case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY: - return Color.BrightMagenta; - case Curses.COLOR_YELLOW | Curses.COLOR_GRAY: - return Color.BrightYellow; - case Curses.COLOR_WHITE | Curses.COLOR_GRAY: - return Color.White; - } - throw new ArgumentException ("Invalid curses color code"); - } + Curses.raw (); + Curses.noecho (); - public override Attribute MakeAttribute (Color fore, Color back) - { - var f = MapColor (fore); - //return MakeColor ((short)(f & 0xffff), (short)MapColor (back)) | ((f & Curses.A_BOLD) != 0 ? Curses.A_BOLD : 0); - return MakeColor ((short)(f & 0xffff), (short)MapColor (back)); - } + Curses.Window.Standard.keypad (true); - public override void Suspend () - { - StopReportingMouseMoves (); - Platform.Suspend (); - Curses.Window.Standard.redrawwin (); - Curses.refresh (); - StartReportingMouseMoves (); - } + TerminalResized = terminalResized; - public override void StartReportingMouseMoves () - { - Console.Out.Write (EscSeqUtils.EnableMouseEvents); - } + Curses.StartColor (); + Curses.UseDefaultColors (); + CurrentAttribute = MakeColor (Color.White, Color.Black); + InitializeColorSchemes (); - public override void StopReportingMouseMoves () - { - Console.Out.Write (EscSeqUtils.DisableMouseEvents); - } + Curses.CheckWinChange (); + ClearContents (); + Curses.refresh (); - //int lastMouseInterval; - //bool mouseGrabbed; + StartReportingMouseMoves (); + } - public override void UncookMouse () - { - //if (mouseGrabbed) - // return; - //lastMouseInterval = Curses.mouseinterval (0); - //mouseGrabbed = true; + public static bool Is_WSL_Platform () + { + // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell + //if (new CursesClipboard ().IsSupported) { + // // If xclip is installed on Linux under WSL, this will return true. + // return false; + //} + var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true); + if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) { + return true; } + return false; + } - public override void CookMouse () - { - //mouseGrabbed = false; - //Curses.mouseinterval (lastMouseInterval); - } + public override void Suspend () + { + StopReportingMouseMoves (); + Platform.Suspend (); + Curses.Window.Standard.redrawwin (); + Curses.refresh (); + StartReportingMouseMoves (); + } - /// - public override bool GetCursorVisibility (out CursorVisibility visibility) - { - visibility = CursorVisibility.Invisible; + public void StartReportingMouseMoves () + { + Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + } - if (!currentCursorVisibility.HasValue) - return false; + public void StopReportingMouseMoves () + { + Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + } - visibility = currentCursorVisibility.Value; + /// + public override bool GetCursorVisibility (out CursorVisibility visibility) + { + visibility = CursorVisibility.Invisible; - return true; - } + if (!_currentCursorVisibility.HasValue) + return false; - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - if (initialCursorVisibility.HasValue == false) - return false; + visibility = _currentCursorVisibility.Value; - Curses.curs_set (((int)visibility >> 16) & 0x000000FF); + return true; + } - if (visibility != CursorVisibility.Invisible) { - Console.Out.Write ("\x1b[{0} q", ((int)visibility >> 24) & 0xFF); - } + /// + public override bool SetCursorVisibility (CursorVisibility visibility) + { + if (_initialCursorVisibility.HasValue == false) { + return false; + } - currentCursorVisibility = visibility; + Curses.curs_set (((int)visibility >> 16) & 0x000000FF); - return true; + if (visibility != CursorVisibility.Invisible) { + Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))); } - /// - public override bool EnsureCursorVisibility () - { - return false; - } + _currentCursorVisibility = visibility; - public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) - { - Key key; + return true; + } - if (consoleKey == ConsoleKey.Packet) { - ConsoleModifiers mod = new ConsoleModifiers (); - if (shift) { - mod |= ConsoleModifiers.Shift; - } - if (alt) { - mod |= ConsoleModifiers.Alt; - } - if (control) { - mod |= ConsoleModifiers.Control; - } - var kchar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyChar, mod, out uint ckey, out _); - key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)ckey, out bool mappable); - if (mappable) { - key = (Key)kchar; - } - } else { - key = (Key)keyChar; - } + /// + public override bool EnsureCursorVisibility () + { + return false; + } - KeyModifiers km = new KeyModifiers (); + public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) + { + Key key; + + if (consoleKey == ConsoleKey.Packet) { + ConsoleModifiers mod = new ConsoleModifiers (); if (shift) { - if (keyChar == 0) { - key |= Key.ShiftMask; - } - km.Shift = shift; + mod |= ConsoleModifiers.Shift; } if (alt) { - key |= Key.AltMask; - km.Alt = alt; + mod |= ConsoleModifiers.Alt; } if (control) { - key |= Key.CtrlMask; - km.Ctrl = control; + mod |= ConsoleModifiers.Control; } - keyDownHandler (new KeyEvent (key, km)); - keyHandler (new KeyEvent (key, km)); - keyUpHandler (new KeyEvent (key, km)); - } - - public override bool GetColors (int value, out Color foreground, out Color background) - { - bool hasColor = false; - foreground = default; - background = default; - int back = -1; - IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (s => (int)s); - if (values.Contains ((value >> 12) & 0xffff)) { - hasColor = true; - back = (value >> 12) & 0xffff; - background = MapCursesColor (back); + var kchar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyChar, mod, out uint ckey, out _); + key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)ckey, out bool mappable); + if (mappable) { + key = (Key)kchar; } - if (values.Contains ((value - (back << 12)) >> 8)) { - hasColor = true; - foreground = MapCursesColor ((value - (back << 12)) >> 8); - } - return hasColor; + } else { + key = (Key)keyChar; } - } - internal static class Platform { - [DllImport ("libc")] - static extern int uname (IntPtr buf); - - [DllImport ("libc")] - static extern int killpg (int pgrp, int pid); - - static int suspendSignal; - - static int GetSuspendSignal () - { - if (suspendSignal != 0) - return suspendSignal; - - IntPtr buf = Marshal.AllocHGlobal (8192); - if (uname (buf) != 0) { - Marshal.FreeHGlobal (buf); - suspendSignal = -1; - return suspendSignal; - } - try { - switch (Marshal.PtrToStringAnsi (buf)) { - case "Darwin": - case "DragonFly": - case "FreeBSD": - case "NetBSD": - case "OpenBSD": - suspendSignal = 18; - break; - case "Linux": - // TODO: should fetch the machine name and - // if it is MIPS return 24 - suspendSignal = 20; - break; - case "Solaris": - suspendSignal = 24; - break; - default: - suspendSignal = -1; - break; - } - return suspendSignal; - } finally { - Marshal.FreeHGlobal (buf); + KeyModifiers km = new KeyModifiers (); + if (shift) { + if (keyChar == 0) { + key |= Key.ShiftMask; } + km.Shift = shift; } - - /// - /// Suspends the process by sending SIGTSTP to itself - /// - /// The suspend. - static public bool Suspend () - { - int signal = GetSuspendSignal (); - if (signal == -1) - return false; - killpg (0, signal); - return true; + if (alt) { + key |= Key.AltMask; + km.Alt = alt; } - } - - /// - /// A clipboard implementation for Linux. - /// This implementation uses the xclip command to access the clipboard. - /// - /// - /// If xclip is not installed, this implementation will not work. - /// - class CursesClipboard : ClipboardBase { - public CursesClipboard () - { - IsSupported = CheckSupport (); + if (control) { + key |= Key.CtrlMask; + km.Ctrl = control; } + _keyDownHandler (new KeyEvent (key, km)); + _keyHandler (new KeyEvent (key, km)); + _keyUpHandler (new KeyEvent (key, km)); + } - string xclipPath = string.Empty; - public override bool IsSupported { get; } - - bool CheckSupport () - { -#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception. - try { - var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true); - if (exitCode == 0 && result.FileExists ()) { - xclipPath = result; - return true; - } - } catch (Exception) { - // Permissions issue. - } -#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception. - return false; - } - protected override string GetClipboardDataImpl () - { - var tempFileName = System.IO.Path.GetTempFileName (); - var xclipargs = "-selection clipboard -o"; - - try { - var (exitCode, result) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false); - if (exitCode == 0) { - if (Application.Driver is CursesDriver) { - Curses.raw (); - Curses.noecho (); - } - return System.IO.File.ReadAllText (tempFileName); - } - } catch (Exception e) { - throw new NotSupportedException ($"\"{xclipPath} {xclipargs}\" failed.", e); - } finally { - System.IO.File.Delete (tempFileName); - } - return string.Empty; - } +} - protected override void SetClipboardDataImpl (string text) - { - var xclipargs = "-selection clipboard -i"; - try { - var (exitCode, _) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs}", text, waitForOutput: false); - if (exitCode == 0 && Application.Driver is CursesDriver) { - Curses.raw (); - Curses.noecho (); - } - } catch (Exception e) { - throw new NotSupportedException ($"\"{xclipPath} {xclipargs} < {text}\" failed", e); - } - } - } +internal static class Platform { + [DllImport ("libc")] + static extern int uname (IntPtr buf); - /// - /// 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. - /// - class MacOSXClipboard : ClipboardBase { - IntPtr nsString = objc_getClass ("NSString"); - IntPtr nsPasteboard = objc_getClass ("NSPasteboard"); - IntPtr utfTextType; - IntPtr generalPasteboard; - IntPtr initWithUtf8Register = sel_registerName ("initWithUTF8String:"); - IntPtr allocRegister = sel_registerName ("alloc"); - IntPtr setStringRegister = sel_registerName ("setString:forType:"); - IntPtr stringForTypeRegister = sel_registerName ("stringForType:"); - IntPtr utf8Register = sel_registerName ("UTF8String"); - IntPtr nsStringPboardType; - IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard"); - IntPtr clearContentsRegister = sel_registerName ("clearContents"); - - public MacOSXClipboard () - { - utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text"); - nsStringPboardType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "NSStringPboardType"); - generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister); - IsSupported = CheckSupport (); - } + [DllImport ("libc")] + static extern int killpg (int pgrp, int pid); - public override bool IsSupported { get; } + static int _suspendSignal; - bool CheckSupport () - { - var (exitCode, result) = ClipboardProcessRunner.Bash ("which pbcopy", waitForOutput: true); - if (exitCode != 0 || !result.FileExists ()) { - return false; - } - (exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste", waitForOutput: true); - return exitCode == 0 && result.FileExists (); + static int GetSuspendSignal () + { + if (_suspendSignal != 0) { + return _suspendSignal; } - protected override string GetClipboardDataImpl () - { - var ptr = objc_msgSend (generalPasteboard, stringForTypeRegister, nsStringPboardType); - var charArray = objc_msgSend (ptr, utf8Register); - return Marshal.PtrToStringAnsi (charArray); + IntPtr buf = Marshal.AllocHGlobal (8192); + if (uname (buf) != 0) { + Marshal.FreeHGlobal (buf); + _suspendSignal = -1; + return _suspendSignal; } - - protected override void SetClipboardDataImpl (string text) - { - IntPtr str = default; - try { - str = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, text); - objc_msgSend (generalPasteboard, clearContentsRegister); - objc_msgSend (generalPasteboard, setStringRegister, str, utfTextType); - } finally { - if (str != default) { - objc_msgSend (str, sel_registerName ("release")); - } + try { + switch (Marshal.PtrToStringAnsi (buf)) { + case "Darwin": + case "DragonFly": + case "FreeBSD": + case "NetBSD": + case "OpenBSD": + _suspendSignal = 18; + break; + case "Linux": + // TODO: should fetch the machine name and + // if it is MIPS return 24 + _suspendSignal = 20; + break; + case "Solaris": + _suspendSignal = 24; + break; + default: + _suspendSignal = -1; + break; } + return _suspendSignal; + } finally { + Marshal.FreeHGlobal (buf); } - - [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_getClass (string className); - - [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector); - - [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, string arg1); - - [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1); - - [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2); - - [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")] - static extern IntPtr sel_registerName (string selectorName); } /// - /// A clipboard implementation for Linux, when running under WSL. - /// This implementation uses the Windows clipboard to store the data, and uses Windows' - /// powershell.exe (launched via WSL interop services) to set/get the Windows - /// clipboard. + /// Suspends the process by sending SIGTSTP to itself /// - class WSLClipboard : ClipboardBase { - bool isSupported = false; - public WSLClipboard () - { - isSupported = CheckSupport (); - } - - public override bool IsSupported { - get { - return isSupported = CheckSupport (); - } - } - - private static string powershellPath = string.Empty; - - bool CheckSupport () - { - if (string.IsNullOrEmpty (powershellPath)) { - // Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL) - var (exitCode, result) = ClipboardProcessRunner.Bash ("which pwsh.exe", waitForOutput: true); - if (exitCode > 0) { - (exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe", waitForOutput: true); - } - - if (exitCode == 0) { - powershellPath = result; - } - } - return !string.IsNullOrEmpty (powershellPath); - } - - protected override string GetClipboardDataImpl () - { - if (!IsSupported) { - return string.Empty; - } - - var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, "-noprofile -command \"Get-Clipboard\""); - if (exitCode == 0) { - if (Application.Driver is CursesDriver) { - Curses.raw (); - Curses.noecho (); - } - - if (output.EndsWith ("\r\n")) { - output = output.Substring (0, output.Length - 2); - } - return output; - } - return string.Empty; - } - - protected override void SetClipboardDataImpl (string text) - { - if (!IsSupported) { - return; - } - - var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""); - if (exitCode == 0) { - if (Application.Driver is CursesDriver) { - Curses.raw (); - Curses.noecho (); - } - } + /// The suspend. + static public bool Suspend () + { + int signal = GetSuspendSignal (); + if (signal == -1) { + return false; } + killpg (0, signal); + return true; } -} +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index 7448e5340e..48d8fbcb22 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -97,8 +97,8 @@ void IMainLoopDriver.Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; pipe (wakeupPipes); - AddWatch (wakeupPipes [0], Condition.PollIn, ml => { - read (wakeupPipes [0], ignore, readHandle); + AddWatch (wakeupPipes [1], Condition.PollIn, ml => { + read (wakeupPipes [1], ignore, readHandle); return true; }); } @@ -212,5 +212,9 @@ void IMainLoopDriver.Iteration () } } } + public void TearDown () + { + //throw new NotImplementedException (); + } } } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs index 67dbe236fd..1edb532756 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs @@ -91,7 +91,7 @@ static UnmanagedLibrary () const int RTLD_LAZY = 1; const int RTLD_GLOBAL = 8; - readonly string libraryPath; + public readonly string LibraryPath; readonly IntPtr handle; public IntPtr NativeLibraryHandle => handle; @@ -104,13 +104,15 @@ static UnmanagedLibrary () public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath) { if (isFullPath) { - this.libraryPath = FirstValidLibraryPath (libraryPathAlternatives); - this.handle = PlatformSpecificLoadLibrary (this.libraryPath); + this.LibraryPath = FirstValidLibraryPath (libraryPathAlternatives); + this.handle = PlatformSpecificLoadLibrary (this.LibraryPath); } else { foreach (var lib in libraryPathAlternatives) { this.handle = PlatformSpecificLoadLibrary (lib); - if (this.handle != IntPtr.Zero) + if (this.handle != IntPtr.Zero) { + this.LibraryPath = lib; break; + } } } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs index 8a0ed3da78..da2cf3ccf5 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs @@ -71,6 +71,7 @@ public struct MouseEvent { //static bool use_naked_driver; static UnmanagedLibrary curses_library; + static NativeMethods methods; [DllImport ("libc")] @@ -97,6 +98,13 @@ static void FindNCurses () cols_ptr = get_ptr ("COLS"); } + static public string curses_version () + { + var v = methods.curses_version (); + return $"{Marshal.PtrToStringAnsi (v)}, {curses_library.LibraryPath}"; + + } + static public Window initscr () { setlocale (LC_ALL, ""); @@ -116,6 +124,9 @@ static public Window initscr () "or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig"); Environment.Exit (1); } + + //Console.Error.WriteLine ($"using curses {Curses.curses_version ()}"); + return main_window; } @@ -141,7 +152,10 @@ public static bool CheckWinChange () console_sharp_get_dims (out l, out c); - if (l == 1 || l != lines || c != cols) { + if (l < 1) { + l = 1; + } + if (l != lines || c != cols) { lines = l; cols = c; return true; @@ -250,7 +264,35 @@ static public int IsAlt (int key) public static int StartColor () => methods.start_color (); public static bool HasColors => methods.has_colors (); + /// + /// The init_pair routine changes the definition of a color-pair.It takes + /// three arguments: the number of the color-pair to be changed, the fore- + /// ground color number, and the background color number.For portable ap- + /// plications: + /// + /// o The first argument must be a legal color pair value.If default + /// colors are used (see use_default_colors(3x)) the upper limit is ad- + /// justed to allow for extra pairs which use a default color in fore- + /// ground and/or background. + /// + /// o The second and third arguments must be legal color values. + /// + /// If the color-pair was previously initialized, the screen is refreshed + /// and all occurrences of that color-pair are changed to the new defini- + /// tion. + /// + /// As an extension, ncurses allows you to set color pair 0 via the as- + /// sume_default_colors (3x) routine, or to specify the use of default col- + /// ors (color number -1) if you first invoke the use_default_colors (3x) + /// routine. + /// + /// + /// + /// + /// public static int InitColorPair (short pair, short foreground, short background) => methods.init_pair (pair, foreground, background); + // TODO: Upgrade to ncurses 6.1 and use the extended version + //public static int InitExtendedPair (int pair, int foreground, int background) => methods.init_extended_pair (pair, foreground, background); public static int UseDefaultColors () => methods.use_default_colors (); public static int ColorPairs => methods.COLOR_PAIRS (); @@ -294,10 +336,12 @@ static public int IsAlt (int key) static public int move (int line, int col) => methods.move (line, col); static public int curs_set (int visibility) => methods.curs_set (visibility); //static public int addch (int ch) => methods.addch (ch); + static public int echochar (int ch) => methods.echochar (ch); static public int addwstr (string s) => methods.addwstr (s); static public int mvaddwstr (int y, int x, string s) => methods.mvaddwstr (y, x, s); static public int wmove (IntPtr win, int line, int col) => methods.wmove (win, line, col); static public int waddch (IntPtr win, int ch) => methods.waddch (win, ch); + //static public int wechochar (IntPtr win, int ch) => methods.wechochar (win, ch); static public int attron (int attrs) => methods.attron (attrs); static public int attroff (int attrs) => methods.attroff (attrs); static public int attrset (int attrs) => methods.attrset (attrs); @@ -369,6 +413,7 @@ internal class Delegates { public delegate int move (int line, int col); public delegate int curs_set (int visibility); public delegate int addch (int ch); + public delegate int echochar (int ch); public delegate int mvaddch (int y, int x, int ch); public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s); public delegate int mvaddwstr (int y, int x, [MarshalAs (UnmanagedType.LPWStr)] string s); @@ -402,6 +447,7 @@ internal class Delegates { public delegate int savetty (); public delegate int resetty (); public delegate int set_escdelay (int size); + public delegate IntPtr curses_version (); } internal class NativeMethods { @@ -443,11 +489,13 @@ internal class NativeMethods { public readonly Delegates.move move; public readonly Delegates.curs_set curs_set; public readonly Delegates.addch addch; + public readonly Delegates.echochar echochar; public readonly Delegates.mvaddch mvaddch; public readonly Delegates.addwstr addwstr; public readonly Delegates.mvaddwstr mvaddwstr; public readonly Delegates.wmove wmove; public readonly Delegates.waddch waddch; + //public readonly Delegates.wechochar wechochar; public readonly Delegates.attron attron; public readonly Delegates.attroff attroff; public readonly Delegates.attrset attrset; @@ -476,6 +524,7 @@ internal class NativeMethods { public readonly Delegates.savetty savetty; public readonly Delegates.resetty resetty; public readonly Delegates.set_escdelay set_escdelay; + public readonly Delegates.curses_version curses_version; public UnmanagedLibrary UnmanagedLibrary; public NativeMethods (UnmanagedLibrary lib) @@ -519,6 +568,7 @@ public NativeMethods (UnmanagedLibrary lib) move = lib.GetNativeMethodDelegate ("move"); curs_set = lib.GetNativeMethodDelegate ("curs_set"); addch = lib.GetNativeMethodDelegate ("addch"); + echochar = lib.GetNativeMethodDelegate ("echochar"); mvaddch = lib.GetNativeMethodDelegate ("mvaddch"); addwstr = lib.GetNativeMethodDelegate ("addwstr"); mvaddwstr = lib.GetNativeMethodDelegate ("mvaddwstr"); @@ -552,6 +602,7 @@ public NativeMethods (UnmanagedLibrary lib) savetty = lib.GetNativeMethodDelegate ("savetty"); resetty = lib.GetNativeMethodDelegate ("resetty"); set_escdelay = lib.GetNativeMethodDelegate ("set_escdelay"); + curses_version = lib.GetNativeMethodDelegate ("curses_version"); } } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs index 8a46149d50..0158a32512 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs @@ -149,7 +149,12 @@ public int addch (char ch) { return Curses.waddch (Handle, ch); } - + + //public int echochar (char ch) + //{ + // return Curses.wechochar (Handle, ch); + //} + public int refresh () { return Curses.wrefresh (Handle); diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs new file mode 100644 index 0000000000..bce7ba8c8e --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; + +namespace Terminal.Gui { + /// + /// Represents the status of an ANSI escape sequence request made to the terminal using . + /// + /// + /// + public class EscSeqReqStatus { + /// + /// Gets the Escape Sequence Termintor (e.g. ESC[8t ... t is the terminator). + /// + public string Terminator { get; } + /// + /// Gets the number of requests. + /// + public int NumRequests { get; } + /// + /// Gets the number of unfinished requests. + /// + public int NumOutstanding { get; set; } + + /// + /// Creates a new state of escape sequence request. + /// + /// The terminator. + /// The number of requests. + public EscSeqReqStatus (string terminator, int numReq) + { + Terminator = terminator; + NumRequests = NumOutstanding = numReq; + } + } + + // TODO: This class is a singleton. It should use the singleton pattern. + /// + /// Manages ANSI Escape Sequence requests and responses. The list of contains the status of the request. + /// Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator). + /// + public class EscSeqRequests { + /// + /// Gets the list. + /// + public List Statuses { get; } = new List (); + + /// + /// Adds a new request for the ANSI Escape Sequence defined by . Adds a + /// instance to list. + /// + /// The terminator. + /// The number of requests. + public void Add (string terminator, int numReq = 1) + { + lock (Statuses) { + var found = Statuses.Find (x => x.Terminator == terminator); + if (found == null) { + Statuses.Add (new EscSeqReqStatus (terminator, numReq)); + } else if (found != null && found.NumOutstanding < found.NumRequests) { + found.NumOutstanding = Math.Min (found.NumOutstanding + numReq, found.NumRequests); + } + } + } + + /// + /// Removes a request defined by . If a matching is found + /// and the number of outstanding + /// requests is greater than 0, the number of outstanding requests is decremented. If the number of outstanding requests + /// is 0, the is removed from . + /// + /// The terminating string. + public void Remove (string terminator) + { + lock (Statuses) { + var found = Statuses.Find (x => x.Terminator == terminator); + if (found == null) { + return; + } + if (found != null && found.NumOutstanding == 0) { + Statuses.Remove (found); + } else if (found != null && found.NumOutstanding > 0) { + found.NumOutstanding--; + if (found.NumOutstanding == 0) { + Statuses.Remove (found); + } + } + } + } + + /// + /// Indicates if a with the exists + /// in the list. + /// + /// + /// if exist, otherwise. + public bool HasResponse (string terminator) + { + lock (Statuses) { + var found = Statuses.Find (x => x.Terminator == terminator); + if (found == null) { + return false; + } + if (found != null && found.NumOutstanding > 0) { + return true; + } else { + // BUGBUG: Why does an API that returns a bool remove the entry from the list? + // NetDriver and Unit tests never exercise this line of code. Maybe Curses does? + Statuses.Remove (found); + } + return false; + } + } + } +} diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs new file mode 100644 index 0000000000..3d71b1a21f --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -0,0 +1,1165 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Terminal.Gui; +/// +/// Provides a platform-independent API for managing ANSI escape sequences. +/// +/// +/// Useful resources: +/// * https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences +/// * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html +/// * https://vt100.net/ +/// +public static class EscSeqUtils { + /// + /// Escape key code (ASCII 27/0x1B). + /// + public static readonly char KeyEsc = (char)Key.Esc; + + /// + /// ESC [ - The CSI (Control Sequence Introducer). + /// + public static readonly string CSI = $"{KeyEsc}["; + + /// + /// ESC [ ? 1003 h - Enable mouse event tracking. + /// + public static readonly string CSI_EnableAnyEventMouse = CSI + "?1003h"; + + /// + /// ESC [ ? 1006 h - Enable SGR (Select Graphic Rendition). + /// + public static readonly string CSI_EnableSgrExtModeMouse = CSI + "?1006h"; + + /// + /// ESC [ ? 1015 h - Enable URXVT (Unicode Extended Virtual Terminal). + /// + public static readonly string CSI_EnableUrxvtExtModeMouse = CSI + "?1015h"; + + /// + /// ESC [ ? 1003 l - Disable any mouse event tracking. + /// + public static readonly string CSI_DisableAnyEventMouse = CSI + "?1003l"; + + /// + /// ESC [ ? 1006 l - Disable SGR (Select Graphic Rendition). + /// + public static readonly string CSI_DisableSgrExtModeMouse = CSI + "?1006l"; + + /// + /// ESC [ ? 1015 l - Disable URXVT (Unicode Extended Virtual Terminal). + /// + public static readonly string CSI_DisableUrxvtExtModeMouse = CSI + "?1015l"; + + /// + /// Control sequence for enabling mouse events. + /// + public static string CSI_EnableMouseEvents { get; set; } = + CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse; + + /// + /// Control sequence for disabling mouse events. + /// + public static string CSI_DisableMouseEvents { get; set; } = + CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse; + + /// + /// ESC [ ? 1047 h - Activate xterm alternative buffer (no backscroll) + /// + public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h"; + + /// + /// ESC [ ? 1047 l - Restore xterm working buffer (with backscroll) + /// + public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l"; + + /// + /// ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll) + /// + public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h"; + + /// + /// ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll) + /// + public static readonly string CSI_RestoreCursorAndActivateAltBufferWithBackscroll = CSI + "?1049l"; + + /// + /// Options for ANSI ESC "[xJ" - Clears part of the screen. + /// + public enum ClearScreenOptions { + /// + /// If n is 0 (or missing), clear from cursor to end of screen. + /// + CursorToEndOfScreen = 0, + /// + /// If n is 1, clear from cursor to beginning of the screen. + /// + CursorToBeginningOfScreen = 1, + /// + /// If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS). + /// + EntireScreen = 2, + /// + /// If n is 3, clear entire screen and delete all lines saved in the scrollback buffer + /// + EntireScreenAndScrollbackBuffer = 3 + } + + /// + /// ESC [ x J - Clears part of the screen. See . + /// + /// + /// + public static string CSI_ClearScreen (ClearScreenOptions option) => $"{CSI}{(int)option}J"; + + #region Cursor + //ESC [ M - RI Reverse Index – Performs the reverse operation of \n, moves cursor up one line, maintains horizontal position, scrolls buffer if necessary* + + /// + /// ESC [ 7 - Save Cursor Position in Memory** + /// + public static readonly string CSI_SaveCursorPosition = CSI + "7"; + + /// + /// ESC [ 8 - DECSR Restore Cursor Position from Memory** + /// + public static readonly string CSI_RestoreCursorPosition = CSI + "8"; + + /// + /// ESC [ 8 ; height ; width t - Set Terminal Window Size + /// https://terminalguide.namepad.de/seq/csi_st-8/ + /// + public static string CSI_SetTerminalWindowSize (int height, int width) => $"{CSI}8;{height};{width}t"; + + //ESC [ < n > A - CUU - Cursor Up Cursor up by < n > + //ESC [ < n > B - CUD - Cursor Down Cursor down by < n > + //ESC [ < n > C - CUF - Cursor Forward Cursor forward (Right) by < n > + //ESC [ < n > D - CUB - Cursor Backward Cursor backward (Left) by < n > + //ESC [ < n > E - CNL - Cursor Next Line - Cursor down < n > lines from current position + //ESC [ < n > F - CPL - Cursor Previous Line Cursor up < n > lines from current position + //ESC [ < n > G - CHA - Cursor Horizontal Absolute Cursor moves to < n > th position horizontally in the current line + //ESC [ < n > d - VPA - Vertical Line Position Absolute Cursor moves to the < n > th position vertically in the current column + + /// + /// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column of the y line + /// + /// + /// + /// + public static string CSI_SetCursorPosition (int x, int y) => $"{CSI}{y};{x}H"; + + + //ESC [ ; f - HVP Horizontal Vertical Position* Cursor moves to; coordinate within the viewport, where is the column of the line + //ESC [ s - ANSISYSSC Save Cursor – Ansi.sys emulation **With no parameters, performs a save cursor operation like DECSC + //ESC [ u - ANSISYSRC Restore Cursor – Ansi.sys emulation **With no parameters, performs a restore cursor operation like DECRC + //ESC [ ? 12 h - ATT160 Text Cursor Enable Blinking Start the cursor blinking + //ESC [ ? 12 l - ATT160 Text Cursor Disable Blinking Stop blinking the cursor + /// + /// ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show Show the cursor + /// + public static readonly string CSI_ShowCursor = CSI + "?25h"; + + /// + /// ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide Hide the cursor + /// + public static readonly string CSI_HideCursor = CSI + "?25l"; + //ESC [ ? 12 h - ATT160 Text Cursor Enable Blinking Start the cursor blinking + //ESC [ ? 12 l - ATT160 Text Cursor Disable Blinking Stop blinking the cursor + //ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show Show the cursor + //ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide Hide the cursor + + /// + /// Styles for ANSI ESC "[x q" - Set Cursor Style + /// + public enum DECSCUSR_Style { + /// + /// DECSCUSR - User Shape - Default cursor shape configured by the user + /// + UserShape = 0, + /// + /// DECSCUSR - Blinking Block - Blinking block cursor shape + /// + BlinkingBlock = 1, + /// + /// DECSCUSR - Steady Block - Steady block cursor shape + /// + SteadyBlock = 2, + /// + /// DECSCUSR - Blinking Underline - Blinking underline cursor shape + /// + BlinkingUnderline = 3, + /// + /// DECSCUSR - Steady Underline - Steady underline cursor shape + /// + SteadyUnderline = 4, + /// + /// DECSCUSR - Blinking Bar - Blinking bar cursor shape + /// + BlinkingBar = 5, + /// + /// DECSCUSR - Steady Bar - Steady bar cursor shape + /// + SteadyBar = 6 + } + + /// + /// ESC [ n SP q - Select Cursor Style (DECSCUSR) + /// https://terminalguide.namepad.de/seq/csi_sq_t_space/ + /// + /// + /// + public static string CSI_SetCursorStyle (DECSCUSR_Style style) => $"{CSI}{(int)style} q"; + + #endregion + + #region Colors + /// + /// ESC [ (n) m - SGR - Set Graphics Rendition - Set the format of the screen and text as specified by (n) + /// This command is special in that the (n) position can accept between 0 and 16 parameters separated by semicolons. + /// When no parameters are specified, it is treated the same as a single 0 parameter. + /// https://terminalguide.namepad.de/seq/csi_sm/ + /// + public static string CSI_SetGraphicsRendition (params int [] parameters) => $"{CSI}{string.Join (";", parameters)}m"; + + /// + /// ESC[38;5;{id}m - Set foreground color (256 colors) + /// + public static string CSI_SetForegroundColor (int id) => $"{CSI}38;5;{id}m"; + + /// + /// ESC[48;5;{id}m - Set background color (256 colors) + /// + public static string CSI_SetBackgroundColor (int id) => $"{CSI}48;5;{id}m"; + + /// + /// ESC[38;2;{r};{g};{b}m Set foreground color as RGB. + /// + public static string CSI_SetForegroundColorRGB (int r, int g, int b) => $"{CSI}38;2;{r};{g};{b}m"; + + /// + /// ESC[48;2;{r};{g};{b}m Set background color as RGB. + /// + public static string CSI_SetBackgroundColorRGB (int r, int g, int b) => $"{CSI}48;2;{r};{g};{b}m"; + + #endregion + + #region Requests + /// + /// ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR) + /// https://terminalguide.namepad.de/seq/csi_sn__p-6/ + /// + public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n"; + + /// + /// The terminal reply to . ESC [ ? (y) ; (x) R + /// + public const string CSI_RequestCursorPositionReport_Terminator = "R"; + + /// + /// ESC [ 0 c - Send Device Attributes (Primary DA) + /// + /// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Application-Program-Command-functions + /// https://www.xfree86.org/current/ctlseqs.html + /// Windows Terminal v1.17 and below emits “\x1b[?1;0c”, indicating "VT101 with No Options". + /// Windows Terminal v1.18+ emits: \x1b[?61;6;7;22;23;24;28;32;42c" + /// See https://github.com/microsoft/terminal/pull/14906 + /// + /// 61 - The device conforms to level 1 of the character cell display architecture + /// (See https://github.com/microsoft/terminal/issues/15693#issuecomment-1633304497) + /// 6 = Selective erase + /// 7 = Soft fonts + /// 22 = Color text + /// 23 = Greek character sets + /// 24 = Turkish character sets + /// 28 = Rectangular area operations + /// 32 = Text macros + /// 42 = ISO Latin-2 character set + /// + /// + public static readonly string CSI_SendDeviceAttributes = CSI + "0c"; + + /// + /// ESC [ > 0 c - Send Device Attributes (Secondary DA) + /// Windows Terminal v1.18+ emits: "\x1b[>0;10;1c" (vt100, firmware version 1.0, vt220) + /// + public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c"; + + /// + /// The terminator indicating a reply to or + /// + /// + public const string CSI_ReportDeviceAttributes_Terminator = "c"; + + /// + /// CSI 1 8 t | yes | yes | yes | report window size in chars + /// https://terminalguide.namepad.de/seq/csi_st-18/ + /// + public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t"; + + /// + /// The terminator indicating a reply to : ESC [ 8 ; height ; width t + /// + public const string CSI_ReportTerminalSizeInChars_Terminator = "t"; + + /// + /// The value of the response to indicating value 1 and 2 are the terminal size in chars. + /// + public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8"; + #endregion + + /// + /// Ensures a console key is mapped to one that works correctly with ANSI escape sequences. + /// + /// The . + /// The modified. + public static ConsoleKeyInfo MapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + { + ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo; + ConsoleKey key; + var keyChar = consoleKeyInfo.KeyChar; + switch ((uint)keyChar) { + case 0: + if (consoleKeyInfo.Key == (ConsoleKey)64) { // Ctrl+Space in Windows. + newConsoleKeyInfo = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + } + break; + case uint n when n > 0 && n <= KeyEsc: + if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r') { + key = ConsoleKey.Enter; + newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + } else if (consoleKeyInfo.Key == 0) { + key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1); + newConsoleKeyInfo = new ConsoleKeyInfo ((char)key, + key, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + true); + } + break; + case 127: // DEL + newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, ConsoleKey.Backspace, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, + (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); + break; + default: + newConsoleKeyInfo = consoleKeyInfo; + break; + } + + return newConsoleKeyInfo; + } + + /// + /// A helper to resize the as needed. + /// + /// The . + /// The array to resize. + /// The resized. + public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki) + { + Array.Resize (ref cki, cki == null ? 1 : cki.Length + 1); + cki [cki.Length - 1] = consoleKeyInfo; + return cki; + } + + /// + /// Decodes an ANSI escape sequence. + /// + /// The which may contain a request. + /// The which may changes. + /// The which may changes. + /// The array. + /// The which may changes. + /// The control returned by the method. + /// The code returned by the method. + /// The values returned by the method. + /// The terminator returned by the method. + /// Indicates if the escape sequence is a mouse event. + /// The button state. + /// The position. + /// Indicates if the escape sequence is a response to a request. + /// The handler that will process the event. + public static void DecodeEscSeq (EscSeqRequests escSeqRequests, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string [] values, out string terminator, out bool isMouse, out List buttonState, out Point pos, out bool isResponse, Action continuousButtonPressedHandler) + { + char [] kChars = GetKeyCharArray (cki); + (c1Control, code, values, terminator) = GetEscapeResult (kChars); + isMouse = false; + buttonState = new List () { 0 }; + pos = default; + isResponse = false; + switch (c1Control) { + case "ESC": + if (values == null && string.IsNullOrEmpty (terminator)) { + key = ConsoleKey.Escape; + newConsoleKeyInfo = new ConsoleKeyInfo (cki [0].KeyChar, key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + } else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26) { + key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1); + newConsoleKeyInfo = new ConsoleKeyInfo (cki [1].KeyChar, + key, + false, + true, + true); + } else { + if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) { + key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0]; + } else { + key = (ConsoleKey)cki [1].KeyChar; + } + newConsoleKeyInfo = new ConsoleKeyInfo ((char)key, + (ConsoleKey)Math.Min ((uint)key, 255), + false, + true, + false); + } + break; + case "SS3": + key = GetConsoleKey (terminator [0], values [0], ref mod); + newConsoleKeyInfo = new ConsoleKeyInfo ('\0', + key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + break; + case "CSI": + if (!string.IsNullOrEmpty (code) && code == "<") { + GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler); + isMouse = true; + return; + } else if (escSeqRequests != null && escSeqRequests.HasResponse (terminator)) { + isResponse = true; + escSeqRequests.Remove (terminator); + return; + } + key = GetConsoleKey (terminator [0], values [0], ref mod); + if (key != 0 && values.Length > 1) { + mod |= GetConsoleModifiers (values [1]); + } + newConsoleKeyInfo = new ConsoleKeyInfo ('\0', + key, + (mod & ConsoleModifiers.Shift) != 0, + (mod & ConsoleModifiers.Alt) != 0, + (mod & ConsoleModifiers.Control) != 0); + break; + } + } + + /// + /// Gets all the needed information about a escape sequence. + /// + /// The array with all chars. + /// + /// The c1Control returned by , code, values and terminating. + /// + public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar) + { + if (kChar == null || kChar.Length == 0) { + return (null, null, null, null); + } + if (kChar [0] != KeyEsc) { + throw new InvalidOperationException ("Invalid escape character!"); + } + if (kChar.Length == 1) { + return ("ESC", null, null, null); + } + if (kChar.Length == 2) { + return ("ESC", null, null, kChar [1].ToString ()); + } + string c1Control = GetC1ControlChar (kChar [1]); + string code = null; + int nSep = kChar.Count (x => x == ';') + 1; + string [] values = new string [nSep]; + int valueIdx = 0; + string terminating = ""; + for (int i = 2; i < kChar.Length; i++) { + var c = kChar [i]; + if (char.IsDigit (c)) { + values [valueIdx] += c.ToString (); + } else if (c == ';') { + valueIdx++; + } else if (valueIdx == nSep - 1 || i == kChar.Length - 1) { + terminating += c.ToString (); + } else { + code += c.ToString (); + } + } + + return (c1Control, code, values, terminating); + } + + /// + /// Gets the c1Control used in the called escape sequence. + /// + /// The char used. + /// The c1Control. + public static string GetC1ControlChar (char c) + { + // These control characters are used in the vtXXX emulation. + switch (c) { + case 'D': + return "IND"; // Index + case 'E': + return "NEL"; // Next Line + case 'H': + return "HTS"; // Tab Set + case 'M': + return "RI"; // Reverse Index + case 'N': + return "SS2"; // Single Shift Select of G2 Character Set: affects next character only + case 'O': + return "SS3"; // Single Shift Select of G3 Character Set: affects next character only + case 'P': + return "DCS"; // Device Control String + case 'V': + return "SPA"; // Start of Guarded Area + case 'W': + return "EPA"; // End of Guarded Area + case 'X': + return "SOS"; // Start of String + case 'Z': + return "DECID"; // Return Terminal ID Obsolete form of CSI c (DA) + case '[': + return "CSI"; // Control Sequence Introducer + case '\\': + return "ST"; // String Terminator + case ']': + return "OSC"; // Operating System Command + case '^': + return "PM"; // Privacy Message + case '_': + return "APC"; // Application Program Command + default: + return ""; // Not supported + } + } + + /// + /// Gets the from the value. + /// + /// The value. + /// The or zero. + public static ConsoleModifiers GetConsoleModifiers (string value) + { + switch (value) { + case "2": + return ConsoleModifiers.Shift; + case "3": + return ConsoleModifiers.Alt; + case "4": + return ConsoleModifiers.Shift | ConsoleModifiers.Alt; + case "5": + return ConsoleModifiers.Control; + case "6": + return ConsoleModifiers.Shift | ConsoleModifiers.Control; + case "7": + return ConsoleModifiers.Alt | ConsoleModifiers.Control; + case "8": + return ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control; + default: + return 0; + } + } + + /// + /// Gets the depending on terminating and value. + /// + /// The terminating. + /// The value. + /// The which may changes. + /// The and probably the . + public static ConsoleKey GetConsoleKey (char terminating, string value, ref ConsoleModifiers mod) + { + ConsoleKey key; + switch (terminating) { + case 'A': + key = ConsoleKey.UpArrow; + break; + case 'B': + key = ConsoleKey.DownArrow; + break; + case 'C': + key = ConsoleKey.RightArrow; + break; + case 'D': + key = ConsoleKey.LeftArrow; + break; + case 'F': + key = ConsoleKey.End; + break; + case 'H': + key = ConsoleKey.Home; + break; + case 'P': + key = ConsoleKey.F1; + break; + case 'Q': + key = ConsoleKey.F2; + break; + case 'R': + key = ConsoleKey.F3; + break; + case 'S': + key = ConsoleKey.F4; + break; + case 'Z': + key = ConsoleKey.Tab; + mod |= ConsoleModifiers.Shift; + break; + case '~': + switch (value) { + case "2": + key = ConsoleKey.Insert; + break; + case "3": + key = ConsoleKey.Delete; + break; + case "5": + key = ConsoleKey.PageUp; + break; + case "6": + key = ConsoleKey.PageDown; + break; + case "15": + key = ConsoleKey.F5; + break; + case "17": + key = ConsoleKey.F6; + break; + case "18": + key = ConsoleKey.F7; + break; + case "19": + key = ConsoleKey.F8; + break; + case "20": + key = ConsoleKey.F9; + break; + case "21": + key = ConsoleKey.F10; + break; + case "23": + key = ConsoleKey.F11; + break; + case "24": + key = ConsoleKey.F12; + break; + default: + key = 0; + break; + } + break; + default: + key = 0; + break; + } + + return key; + } + + /// + /// A helper to get only the from the array. + /// + /// + /// The char array of the escape sequence. + public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki) + { + char [] kChar = new char [] { }; + var length = 0; + foreach (var kc in cki) { + length++; + Array.Resize (ref kChar, length); + kChar [length - 1] = kc.KeyChar; + } + + return kChar; + } + + private static MouseFlags? lastMouseButtonPressed; + //private static MouseFlags? lastMouseButtonReleased; + private static bool isButtonPressed; + //private static bool isButtonReleased; + private static bool isButtonClicked; + private static bool isButtonDoubleClicked; + private static bool isButtonTripleClicked; + private static Point point; + + /// + /// Gets the mouse button flags and the position. + /// + /// The array. + /// The mouse button flags. + /// The mouse position. + /// The handler that will process the event. + public static void GetMouse (ConsoleKeyInfo [] cki, out List mouseFlags, out Point pos, Action continuousButtonPressedHandler) + { + MouseFlags buttonState = 0; + pos = new Point (); + int buttonCode = 0; + bool foundButtonCode = false; + int foundPoint = 0; + string value = ""; + var kChar = GetKeyCharArray (cki); + //System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}"); + for (int i = 0; i < kChar.Length; i++) { + var c = kChar [i]; + if (c == '<') { + foundButtonCode = true; + } else if (foundButtonCode && c != ';') { + value += c.ToString (); + } else if (c == ';') { + if (foundButtonCode) { + foundButtonCode = false; + buttonCode = int.Parse (value); + } + if (foundPoint == 1) { + pos.X = int.Parse (value) - 1; + } + value = ""; + foundPoint++; + } else if (foundPoint > 0 && c != 'm' && c != 'M') { + value += c.ToString (); + } else if (c == 'm' || c == 'M') { + //pos.Y = int.Parse (value) + Console.WindowTop - 1; + pos.Y = int.Parse (value) - 1; + + switch (buttonCode) { + case 0: + case 8: + case 16: + case 24: + case 32: + case 36: + case 40: + case 48: + case 56: + buttonState = c == 'M' ? MouseFlags.Button1Pressed + : MouseFlags.Button1Released; + break; + case 1: + case 9: + case 17: + case 25: + case 33: + case 37: + case 41: + case 45: + case 49: + case 53: + case 57: + case 61: + buttonState = c == 'M' ? MouseFlags.Button2Pressed + : MouseFlags.Button2Released; + break; + case 2: + case 10: + case 14: + case 18: + case 22: + case 26: + case 30: + case 34: + case 42: + case 46: + case 50: + case 54: + case 58: + case 62: + buttonState = c == 'M' ? MouseFlags.Button3Pressed + : MouseFlags.Button3Released; + break; + case 35: + //// Needed for Windows OS + //if (isButtonPressed && c == 'm' + // && (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed + // || lastMouseEvent.ButtonState == MouseFlags.Button2Pressed + // || lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) { + + // switch (lastMouseEvent.ButtonState) { + // case MouseFlags.Button1Pressed: + // buttonState = MouseFlags.Button1Released; + // break; + // case MouseFlags.Button2Pressed: + // buttonState = MouseFlags.Button2Released; + // break; + // case MouseFlags.Button3Pressed: + // buttonState = MouseFlags.Button3Released; + // break; + // } + //} else { + // buttonState = MouseFlags.ReportMousePosition; + //} + //break; + case 39: + case 43: + case 47: + case 51: + case 55: + case 59: + case 63: + buttonState = MouseFlags.ReportMousePosition; + break; + case 64: + buttonState = MouseFlags.WheeledUp; + break; + case 65: + buttonState = MouseFlags.WheeledDown; + break; + case 68: + case 72: + case 80: + buttonState = MouseFlags.WheeledLeft; // Shift/Ctrl+WheeledUp + break; + case 69: + case 73: + case 81: + buttonState = MouseFlags.WheeledRight; // Shift/Ctrl+WheeledDown + break; + } + // Modifiers. + switch (buttonCode) { + case 8: + case 9: + case 10: + case 43: + buttonState |= MouseFlags.ButtonAlt; + break; + case 14: + case 47: + buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift; + break; + case 16: + case 17: + case 18: + case 51: + buttonState |= MouseFlags.ButtonCtrl; + break; + case 22: + case 55: + buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift; + break; + case 24: + case 25: + case 26: + case 59: + buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt; + break; + case 30: + case 63: + buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt; + break; + case 32: + case 33: + case 34: + buttonState |= MouseFlags.ReportMousePosition; + break; + case 36: + case 37: + buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift; + break; + case 39: + case 68: + case 69: + buttonState |= MouseFlags.ButtonShift; + break; + case 40: + case 41: + case 42: + buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt; + break; + case 45: + case 46: + buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift; + break; + case 48: + case 49: + case 50: + buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl; + break; + case 53: + case 54: + buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift; + break; + case 56: + case 57: + case 58: + buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt; + break; + case 61: + case 62: + buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt; + break; + } + } + } + + mouseFlags = new List () { MouseFlags.AllEvents }; + + if (lastMouseButtonPressed != null && !isButtonPressed && !buttonState.HasFlag (MouseFlags.ReportMousePosition) + && !buttonState.HasFlag (MouseFlags.Button1Released) + && !buttonState.HasFlag (MouseFlags.Button2Released) + && !buttonState.HasFlag (MouseFlags.Button3Released) + && !buttonState.HasFlag (MouseFlags.Button4Released)) { + + lastMouseButtonPressed = null; + isButtonPressed = false; + } + + if (!isButtonClicked && !isButtonDoubleClicked && ((buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || + buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed) && lastMouseButtonPressed == null) || + isButtonPressed && lastMouseButtonPressed != null && buttonState.HasFlag (MouseFlags.ReportMousePosition)) { + + mouseFlags [0] = buttonState; + lastMouseButtonPressed = buttonState; + isButtonPressed = true; + + if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) { + point = new Point () { + X = pos.X, + Y = pos.Y + }; + + Application.MainLoop.AddIdle (() => { + Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler)); + return false; + }); + } else if (mouseFlags [0] == MouseFlags.ReportMousePosition) { + isButtonPressed = false; + } + + } else if (isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || + buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) { + + mouseFlags [0] = GetButtonTripleClicked (buttonState); + isButtonDoubleClicked = false; + isButtonTripleClicked = true; + + } else if (isButtonClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || + buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) { + + mouseFlags [0] = GetButtonDoubleClicked (buttonState); + isButtonClicked = false; + isButtonDoubleClicked = true; + Application.MainLoop.AddIdle (() => { + Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); + return false; + }); + + } + //else if (isButtonReleased && !isButtonClicked && buttonState == MouseFlags.ReportMousePosition) { + // mouseFlag [0] = GetButtonClicked ((MouseFlags)lastMouseButtonReleased); + // lastMouseButtonReleased = null; + // isButtonReleased = false; + // isButtonClicked = true; + // Application.MainLoop.AddIdle (() => { + // Task.Run (async () => await ProcessButtonClickedAsync ()); + // return false; + // }); + + //} + else if (!isButtonClicked && !isButtonDoubleClicked && (buttonState == MouseFlags.Button1Released || buttonState == MouseFlags.Button2Released || + buttonState == MouseFlags.Button3Released || buttonState == MouseFlags.Button4Released)) { + + mouseFlags [0] = buttonState; + isButtonPressed = false; + + if (isButtonTripleClicked) { + isButtonTripleClicked = false; + } else if (pos.X == point.X && pos.Y == point.Y) { + mouseFlags.Add (GetButtonClicked (buttonState)); + isButtonClicked = true; + Application.MainLoop.AddIdle (() => { + Task.Run (async () => await ProcessButtonClickedAsync ()); + return false; + }); + } + + point = pos; + + //if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) { + // lastMouseButtonReleased = buttonState; + // isButtonPressed = false; + // isButtonReleased = true; + //} else { + // lastMouseButtonPressed = null; + // isButtonPressed = false; + //} + + } else if (buttonState == MouseFlags.WheeledUp) { + + mouseFlags [0] = MouseFlags.WheeledUp; + + } else if (buttonState == MouseFlags.WheeledDown) { + + mouseFlags [0] = MouseFlags.WheeledDown; + + } else if (buttonState == MouseFlags.WheeledLeft) { + + mouseFlags [0] = MouseFlags.WheeledLeft; + + } else if (buttonState == MouseFlags.WheeledRight) { + + mouseFlags [0] = MouseFlags.WheeledRight; + + } else if (buttonState == MouseFlags.ReportMousePosition) { + mouseFlags [0] = MouseFlags.ReportMousePosition; + + } else { + mouseFlags [0] = buttonState; + //foreach (var flag in buttonState.GetUniqueFlags()) { + // mouseFlag [0] |= flag; + //} + } + + mouseFlags [0] = SetControlKeyStates (buttonState, mouseFlags [0]); + //buttonState = mouseFlags; + + //System.Diagnostics.Debug.WriteLine ($"buttonState: {buttonState} X: {pos.X} Y: {pos.Y}"); + //foreach (var mf in mouseFlags) { + // System.Diagnostics.Debug.WriteLine ($"mouseFlags: {mf} X: {pos.X} Y: {pos.Y}"); + //} + } + + private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action continuousButtonPressedHandler) + { + while (isButtonPressed) { + await Task.Delay (100); + //var me = new MouseEvent () { + // X = point.X, + // Y = point.Y, + // Flags = mouseFlag + //}; + + var view = Application.WantContinuousButtonPressedView; + if (view == null) + break; + if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { + Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point)); + } + } + } + + private static async Task ProcessButtonClickedAsync () + { + await Task.Delay (300); + isButtonClicked = false; + } + + private static async Task ProcessButtonDoubleClickedAsync () + { + await Task.Delay (300); + isButtonDoubleClicked = false; + } + + private static MouseFlags GetButtonClicked (MouseFlags mouseFlag) + { + MouseFlags mf = default; + switch (mouseFlag) { + case MouseFlags.Button1Released: + mf = MouseFlags.Button1Clicked; + break; + + case MouseFlags.Button2Released: + mf = MouseFlags.Button2Clicked; + break; + + case MouseFlags.Button3Released: + mf = MouseFlags.Button3Clicked; + break; + } + return mf; + } + + private static MouseFlags GetButtonDoubleClicked (MouseFlags mouseFlag) + { + MouseFlags mf = default; + switch (mouseFlag) { + case MouseFlags.Button1Pressed: + mf = MouseFlags.Button1DoubleClicked; + break; + + case MouseFlags.Button2Pressed: + mf = MouseFlags.Button2DoubleClicked; + break; + + case MouseFlags.Button3Pressed: + mf = MouseFlags.Button3DoubleClicked; + break; + } + return mf; + } + + private static MouseFlags GetButtonTripleClicked (MouseFlags mouseFlag) + { + MouseFlags mf = default; + switch (mouseFlag) { + case MouseFlags.Button1Pressed: + mf = MouseFlags.Button1TripleClicked; + break; + + case MouseFlags.Button2Pressed: + mf = MouseFlags.Button2TripleClicked; + break; + + case MouseFlags.Button3Pressed: + mf = MouseFlags.Button3TripleClicked; + break; + } + return mf; + } + + private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag) + { + if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0) + mouseFlag |= MouseFlags.ButtonCtrl; + + if ((buttonState & MouseFlags.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0) + mouseFlag |= MouseFlags.ButtonShift; + + if ((buttonState & MouseFlags.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0) + mouseFlag |= MouseFlags.ButtonAlt; + return mouseFlag; + } + + // TODO: Move this out of here and into ConsoleDriver or somewhere else. + /// + /// Get the terminal that holds the console driver. + /// + /// The process. + /// If supported the executable console process, null otherwise. + public static Process GetParentProcess (Process process) + { + if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { + return null; + } + + string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + process.Id; + using (ManagementObjectSearcher mos = new ManagementObjectSearcher (query)) { + foreach (ManagementObject mo in mos.Get ()) { + if (mo ["ParentProcessId"] != null) { + try { + var id = Convert.ToInt32 (mo ["ParentProcessId"]); + return Process.GetProcessById (id); + } catch { + } + } + } + } + return null; + } +} diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs index 75a58f9c32..69b2032765 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs @@ -5,2013 +5,2007 @@ using System.Collections.Generic; using System.IO; using System.Text; +using Rune = System.Text.Rune; -namespace Terminal.Gui { +namespace Terminal.Gui; #pragma warning disable RCS1138 // Add summary to documentation comment. +/// +/// +/// +public static class FakeConsole { +#pragma warning restore RCS1138 // Add summary to documentation comment. + + // + // Summary: + // Gets or sets the width of the console window. + // + // Returns: + // The width of the console window measured in columns. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight + // property is less than or equal to 0.-or-The value of the System.Console.WindowHeight + // property plus the value of the System.Console.WindowTop property is greater than + // or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth + // property or the value of the System.Console.WindowHeight property is greater + // than the largest possible window width or height for the current screen resolution + // and console font. + // + // T:System.IO.IOException: + // Error reading or writing information. +#pragma warning disable RCS1138 // Add summary to documentation comment. + + /// + /// Specifies the initial console width. + /// + public const int WIDTH = 80; + + /// + /// Specifies the initial console height. + /// + public const int HEIGHT = 25; + /// /// /// - public static class FakeConsole { -#pragma warning restore RCS1138 // Add summary to documentation comment. + public static int WindowWidth { get; set; } = WIDTH; + // + // Summary: + // Gets a value that indicates whether output has been redirected from the standard + // output stream. + // + // Returns: + // true if output is redirected; otherwise, false. + /// + /// + /// + public static bool IsOutputRedirected { get; } + // + // Summary: + // Gets a value that indicates whether the error output stream has been redirected + // from the standard error stream. + // + // Returns: + // true if error output is redirected; otherwise, false. + /// + /// + /// + public static bool IsErrorRedirected { get; } + // + // Summary: + // Gets the standard input stream. + // + // Returns: + // A System.IO.TextReader that represents the standard input stream. + /// + /// + /// + public static TextReader In { get; } + // + // Summary: + // Gets the standard output stream. + // + // Returns: + // A System.IO.TextWriter that represents the standard output stream. + /// + /// + /// + public static TextWriter Out { get; } + // + // Summary: + // Gets the standard error output stream. + // + // Returns: + // A System.IO.TextWriter that represents the standard error output stream. + /// + /// + /// + public static TextWriter Error { get; } + // + // Summary: + // Gets or sets the encoding the console uses to read input. + // + // Returns: + // The encoding used to read console input. + // + // Exceptions: + // T:System.ArgumentNullException: + // The property value in a set operation is null. + // + // T:System.IO.IOException: + // An error occurred during the execution of this operation. + // + // T:System.Security.SecurityException: + // Your application does not have permission to perform this operation. + /// + /// + /// + public static Encoding InputEncoding { get; set; } + // + // Summary: + // Gets or sets the encoding the console uses to write output. + // + // Returns: + // The encoding used to write console output. + // + // Exceptions: + // T:System.ArgumentNullException: + // The property value in a set operation is null. + // + // T:System.IO.IOException: + // An error occurred during the execution of this operation. + // + // T:System.Security.SecurityException: + // Your application does not have permission to perform this operation. + /// + /// + /// + public static Encoding OutputEncoding { get; set; } + // + // Summary: + // Gets or sets the background color of the console. + // + // Returns: + // A value that specifies the background color of the console; that is, the color + // that appears behind each character. The default is black. + // + // Exceptions: + // T:System.ArgumentException: + // The color specified in a set operation is not a valid member of System.ConsoleColor. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + + static ConsoleColor _defaultBackgroundColor = ConsoleColor.Black; - // - // Summary: - // Gets or sets the width of the console window. - // - // Returns: - // The width of the console window measured in columns. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight - // property is less than or equal to 0.-or-The value of the System.Console.WindowHeight - // property plus the value of the System.Console.WindowTop property is greater than - // or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth - // property or the value of the System.Console.WindowHeight property is greater - // than the largest possible window width or height for the current screen resolution - // and console font. - // - // T:System.IO.IOException: - // Error reading or writing information. -#pragma warning disable RCS1138 // Add summary to documentation comment. + /// + /// + /// + public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor; + + // + // Summary: + // Gets or sets the foreground color of the console. + // + // Returns: + // A System.ConsoleColor that specifies the foreground color of the console; that + // is, the color of each character that is displayed. The default is gray. + // + // Exceptions: + // T:System.ArgumentException: + // The color specified in a set operation is not a valid member of System.ConsoleColor. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + + static ConsoleColor _defaultForegroundColor = ConsoleColor.Gray; - /// - /// Specifies the initial console width. - /// - public const int WIDTH = 80; - - /// - /// Specifies the initial console height. - /// - public const int HEIGHT = 25; - - /// - /// - /// - public static int WindowWidth { get; set; } = WIDTH; - // - // Summary: - // Gets a value that indicates whether output has been redirected from the standard - // output stream. - // - // Returns: - // true if output is redirected; otherwise, false. - /// - /// - /// - public static bool IsOutputRedirected { get; } - // - // Summary: - // Gets a value that indicates whether the error output stream has been redirected - // from the standard error stream. - // - // Returns: - // true if error output is redirected; otherwise, false. - /// - /// - /// - public static bool IsErrorRedirected { get; } - // - // Summary: - // Gets the standard input stream. - // - // Returns: - // A System.IO.TextReader that represents the standard input stream. - /// - /// - /// - public static TextReader In { get; } - // - // Summary: - // Gets the standard output stream. - // - // Returns: - // A System.IO.TextWriter that represents the standard output stream. - /// - /// - /// - public static TextWriter Out { get; } - // - // Summary: - // Gets the standard error output stream. - // - // Returns: - // A System.IO.TextWriter that represents the standard error output stream. - /// - /// - /// - public static TextWriter Error { get; } - // - // Summary: - // Gets or sets the encoding the console uses to read input. - // - // Returns: - // The encoding used to read console input. - // - // Exceptions: - // T:System.ArgumentNullException: - // The property value in a set operation is null. - // - // T:System.IO.IOException: - // An error occurred during the execution of this operation. - // - // T:System.Security.SecurityException: - // Your application does not have permission to perform this operation. - /// - /// - /// - public static Encoding InputEncoding { get; set; } - // - // Summary: - // Gets or sets the encoding the console uses to write output. - // - // Returns: - // The encoding used to write console output. - // - // Exceptions: - // T:System.ArgumentNullException: - // The property value in a set operation is null. - // - // T:System.IO.IOException: - // An error occurred during the execution of this operation. - // - // T:System.Security.SecurityException: - // Your application does not have permission to perform this operation. - /// - /// - /// - public static Encoding OutputEncoding { get; set; } - // - // Summary: - // Gets or sets the background color of the console. - // - // Returns: - // A value that specifies the background color of the console; that is, the color - // that appears behind each character. The default is black. - // - // Exceptions: - // T:System.ArgumentException: - // The color specified in a set operation is not a valid member of System.ConsoleColor. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - - static ConsoleColor _defaultBackgroundColor = ConsoleColor.Black; - - /// - /// - /// - public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor; - - // - // Summary: - // Gets or sets the foreground color of the console. - // - // Returns: - // A System.ConsoleColor that specifies the foreground color of the console; that - // is, the color of each character that is displayed. The default is gray. - // - // Exceptions: - // T:System.ArgumentException: - // The color specified in a set operation is not a valid member of System.ConsoleColor. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - - static ConsoleColor _defaultForegroundColor = ConsoleColor.Gray; - - /// - /// - /// - public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor; - // - // Summary: - // Gets or sets the height of the buffer area. - // - // Returns: - // The current height, in rows, of the buffer area. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // The value in a set operation is less than or equal to zero.-or- The value in - // a set operation is greater than or equal to System.Int16.MaxValue.-or- The value - // in a set operation is less than System.Console.WindowTop + System.Console.WindowHeight. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static int BufferHeight { get; set; } = HEIGHT; - // - // Summary: - // Gets or sets the width of the buffer area. - // - // Returns: - // The current width, in columns, of the buffer area. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // The value in a set operation is less than or equal to zero.-or- The value in - // a set operation is greater than or equal to System.Int16.MaxValue.-or- The value - // in a set operation is less than System.Console.WindowLeft + System.Console.WindowWidth. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static int BufferWidth { get; set; } = WIDTH; - // - // Summary: - // Gets or sets the height of the console window area. - // - // Returns: - // The height of the console window measured in rows. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight - // property is less than or equal to 0.-or-The value of the System.Console.WindowHeight - // property plus the value of the System.Console.WindowTop property is greater than - // or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth - // property or the value of the System.Console.WindowHeight property is greater - // than the largest possible window width or height for the current screen resolution - // and console font. - // - // T:System.IO.IOException: - // Error reading or writing information. - /// - /// - /// - public static int WindowHeight { get; set; } = HEIGHT; - // - // Summary: - // Gets or sets a value indicating whether the combination of the System.ConsoleModifiers.Control - // modifier key and System.ConsoleKey.C console key (Ctrl+C) is treated as ordinary - // input or as an interruption that is handled by the operating system. - // - // Returns: - // true if Ctrl+C is treated as ordinary input; otherwise, false. - // - // Exceptions: - // T:System.IO.IOException: - // Unable to get or set the input mode of the console input buffer. - /// - /// - /// - public static bool TreatControlCAsInput { get; set; } - // - // Summary: - // Gets the largest possible number of console window columns, based on the current - // font and screen resolution. - // - // Returns: - // The width of the largest possible console window measured in columns. - /// - /// - /// - public static int LargestWindowWidth { get; } - // - // Summary: - // Gets the largest possible number of console window rows, based on the current - // font and screen resolution. - // - // Returns: - // The height of the largest possible console window measured in rows. - /// - /// - /// - public static int LargestWindowHeight { get; } - // - // Summary: - // Gets or sets the leftmost position of the console window area relative to the - // screen buffer. - // - // Returns: - // The leftmost console window position measured in columns. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // In a set operation, the value to be assigned is less than zero.-or-As a result - // of the assignment, System.Console.WindowLeft plus System.Console.WindowWidth - // would exceed System.Console.BufferWidth. - // - // T:System.IO.IOException: - // Error reading or writing information. - /// - /// - /// - public static int WindowLeft { get; set; } - // - // Summary: - // Gets or sets the top position of the console window area relative to the screen - // buffer. - // - // Returns: - // The uppermost console window position measured in rows. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // In a set operation, the value to be assigned is less than zero.-or-As a result - // of the assignment, System.Console.WindowTop plus System.Console.WindowHeight - // would exceed System.Console.BufferHeight. - // - // T:System.IO.IOException: - // Error reading or writing information. - /// - /// - /// - public static int WindowTop { get; set; } - // - // Summary: - // Gets or sets the column position of the cursor within the buffer area. - // - // Returns: - // The current position, in columns, of the cursor. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // The value in a set operation is less than zero.-or- The value in a set operation - // is greater than or equal to System.Console.BufferWidth. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static int CursorLeft { get; set; } - // - // Summary: - // Gets or sets the row position of the cursor within the buffer area. - // - // Returns: - // The current position, in rows, of the cursor. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // The value in a set operation is less than zero.-or- The value in a set operation - // is greater than or equal to System.Console.BufferHeight. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static int CursorTop { get; set; } - // - // Summary: - // Gets or sets the height of the cursor within a character cell. - // - // Returns: - // The size of the cursor expressed as a percentage of the height of a character - // cell. The property value ranges from 1 to 100. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // The value specified in a set operation is less than 1 or greater than 100. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static int CursorSize { get; set; } - // - // Summary: - // Gets or sets a value indicating whether the cursor is visible. - // - // Returns: - // true if the cursor is visible; otherwise, false. - // - // Exceptions: - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static bool CursorVisible { get; set; } - // - // Summary: - // Gets or sets the title to display in the console title bar. - // - // Returns: - // The string to be displayed in the title bar of the console. The maximum length - // of the title string is 24500 characters. - // - // Exceptions: - // T:System.InvalidOperationException: - // In a get operation, the retrieved title is longer than 24500 characters. - // - // T:System.ArgumentOutOfRangeException: - // In a set operation, the specified title is longer than 24500 characters. - // - // T:System.ArgumentNullException: - // In a set operation, the specified title is null. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static string Title { get; set; } - // - // Summary: - // Gets a value indicating whether a key press is available in the input stream. - // - // Returns: - // true if a key press is available; otherwise, false. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.InvalidOperationException: - // Standard input is redirected to a file instead of the keyboard. - /// - /// - /// - public static bool KeyAvailable { get; } - // - // Summary: - // Gets a value indicating whether the NUM LOCK keyboard toggle is turned on or - // turned off. - // - // Returns: - // true if NUM LOCK is turned on; false if NUM LOCK is turned off. - /// - /// - /// - public static bool NumberLock { get; } - // - // Summary: - // Gets a value indicating whether the CAPS LOCK keyboard toggle is turned on or - // turned off. - // - // Returns: - // true if CAPS LOCK is turned on; false if CAPS LOCK is turned off. - /// - /// - /// - public static bool CapsLock { get; } - // - // Summary: - // Gets a value that indicates whether input has been redirected from the standard - // input stream. - // - // Returns: - // true if input is redirected; otherwise, false. - /// - /// - /// - public static bool IsInputRedirected { get; } - - // - // Summary: - // Plays the sound of a beep through the console speaker. - // - // Exceptions: - // T:System.Security.HostProtectionException: - // This method was executed on a server, such as SQL Server, that does not permit - // access to a user interface. - /// - /// - /// - public static void Beep () - { - throw new NotImplementedException (); - } - // - // Summary: - // Plays the sound of a beep of a specified frequency and duration through the console - // speaker. - // - // Parameters: - // frequency: - // The frequency of the beep, ranging from 37 to 32767 hertz. - // - // duration: - // The duration of the beep measured in milliseconds. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // frequency is less than 37 or more than 32767 hertz.-or- duration is less than - // or equal to zero. - // - // T:System.Security.HostProtectionException: - // This method was executed on a server, such as SQL Server, that does not permit - // access to the console. - /// - /// - /// - public static void Beep (int frequency, int duration) - { - throw new NotImplementedException (); - } - // - // Summary: - // Clears the console buffer and corresponding console window of display information. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - - static char [,] _buffer = new char [WindowWidth, WindowHeight]; - - /// - /// - /// - public static void Clear () - { - _buffer = new char [BufferWidth, BufferHeight]; - SetCursorPosition (0, 0); - } + /// + /// + /// + public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor; + // + // Summary: + // Gets or sets the height of the buffer area. + // + // Returns: + // The current height, in rows, of the buffer area. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // The value in a set operation is less than or equal to zero.-or- The value in + // a set operation is greater than or equal to System.Int16.MaxValue.-or- The value + // in a set operation is less than System.Console.WindowTop + System.Console.WindowHeight. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static int BufferHeight { get; set; } = HEIGHT; + // + // Summary: + // Gets or sets the width of the buffer area. + // + // Returns: + // The current width, in columns, of the buffer area. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // The value in a set operation is less than or equal to zero.-or- The value in + // a set operation is greater than or equal to System.Int16.MaxValue.-or- The value + // in a set operation is less than System.Console.WindowLeft + System.Console.WindowWidth. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static int BufferWidth { get; set; } = WIDTH; + + // + // Summary: + // Gets or sets the height of the console window area. + // + // Returns: + // The height of the console window measured in rows. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight + // property is less than or equal to 0.-or-The value of the System.Console.WindowHeight + // property plus the value of the System.Console.WindowTop property is greater than + // or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth + // property or the value of the System.Console.WindowHeight property is greater + // than the largest possible window width or height for the current screen resolution + // and console font. + // + // T:System.IO.IOException: + // Error reading or writing information. + /// + /// + /// + public static int WindowHeight { get; set; } = HEIGHT; + // + // Summary: + // Gets or sets a value indicating whether the combination of the System.ConsoleModifiers.Control + // modifier key and System.ConsoleKey.C console key (Ctrl+C) is treated as ordinary + // input or as an interruption that is handled by the operating system. + // + // Returns: + // true if Ctrl+C is treated as ordinary input; otherwise, false. + // + // Exceptions: + // T:System.IO.IOException: + // Unable to get or set the input mode of the console input buffer. + /// + /// + /// + public static bool TreatControlCAsInput { get; set; } + // + // Summary: + // Gets the largest possible number of console window columns, based on the current + // font and screen resolution. + // + // Returns: + // The width of the largest possible console window measured in columns. + /// + /// + /// + public static int LargestWindowWidth { get; } + // + // Summary: + // Gets the largest possible number of console window rows, based on the current + // font and screen resolution. + // + // Returns: + // The height of the largest possible console window measured in rows. + /// + /// + /// + public static int LargestWindowHeight { get; } + // + // Summary: + // Gets or sets the leftmost position of the console window area relative to the + // screen buffer. + // + // Returns: + // The leftmost console window position measured in columns. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // In a set operation, the value to be assigned is less than zero.-or-As a result + // of the assignment, System.Console.WindowLeft plus System.Console.WindowWidth + // would exceed System.Console.BufferWidth. + // + // T:System.IO.IOException: + // Error reading or writing information. + /// + /// + /// + public static int WindowLeft { get; set; } + // + // Summary: + // Gets or sets the top position of the console window area relative to the screen + // buffer. + // + // Returns: + // The uppermost console window position measured in rows. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // In a set operation, the value to be assigned is less than zero.-or-As a result + // of the assignment, System.Console.WindowTop plus System.Console.WindowHeight + // would exceed System.Console.BufferHeight. + // + // T:System.IO.IOException: + // Error reading or writing information. + /// + /// + /// + public static int WindowTop { get; set; } + // + // Summary: + // Gets or sets the column position of the cursor within the buffer area. + // + // Returns: + // The current position, in columns, of the cursor. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // The value in a set operation is less than zero.-or- The value in a set operation + // is greater than or equal to System.Console.BufferWidth. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static int CursorLeft { get; set; } + // + // Summary: + // Gets or sets the row position of the cursor within the buffer area. + // + // Returns: + // The current position, in rows, of the cursor. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // The value in a set operation is less than zero.-or- The value in a set operation + // is greater than or equal to System.Console.BufferHeight. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static int CursorTop { get; set; } + // + // Summary: + // Gets or sets the height of the cursor within a character cell. + // + // Returns: + // The size of the cursor expressed as a percentage of the height of a character + // cell. The property value ranges from 1 to 100. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // The value specified in a set operation is less than 1 or greater than 100. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static int CursorSize { get; set; } + // + // Summary: + // Gets or sets a value indicating whether the cursor is visible. + // + // Returns: + // true if the cursor is visible; otherwise, false. + // + // Exceptions: + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static bool CursorVisible { get; set; } + // + // Summary: + // Gets or sets the title to display in the console title bar. + // + // Returns: + // The string to be displayed in the title bar of the console. The maximum length + // of the title string is 24500 characters. + // + // Exceptions: + // T:System.InvalidOperationException: + // In a get operation, the retrieved title is longer than 24500 characters. + // + // T:System.ArgumentOutOfRangeException: + // In a set operation, the specified title is longer than 24500 characters. + // + // T:System.ArgumentNullException: + // In a set operation, the specified title is null. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static string Title { get; set; } + // + // Summary: + // Gets a value indicating whether a key press is available in the input stream. + // + // Returns: + // true if a key press is available; otherwise, false. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.InvalidOperationException: + // Standard input is redirected to a file instead of the keyboard. + /// + /// + /// + public static bool KeyAvailable { get; } + // + // Summary: + // Gets a value indicating whether the NUM LOCK keyboard toggle is turned on or + // turned off. + // + // Returns: + // true if NUM LOCK is turned on; false if NUM LOCK is turned off. + /// + /// + /// + public static bool NumberLock { get; } + // + // Summary: + // Gets a value indicating whether the CAPS LOCK keyboard toggle is turned on or + // turned off. + // + // Returns: + // true if CAPS LOCK is turned on; false if CAPS LOCK is turned off. + /// + /// + /// + public static bool CapsLock { get; } + // + // Summary: + // Gets a value that indicates whether input has been redirected from the standard + // input stream. + // + // Returns: + // true if input is redirected; otherwise, false. + /// + /// + /// + public static bool IsInputRedirected { get; } + + // + // Summary: + // Plays the sound of a beep through the console speaker. + // + // Exceptions: + // T:System.Security.HostProtectionException: + // This method was executed on a server, such as SQL Server, that does not permit + // access to a user interface. + /// + /// + /// + public static void Beep () + { + throw new NotImplementedException (); + } + // + // Summary: + // Plays the sound of a beep of a specified frequency and duration through the console + // speaker. + // + // Parameters: + // frequency: + // The frequency of the beep, ranging from 37 to 32767 hertz. + // + // duration: + // The duration of the beep measured in milliseconds. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // frequency is less than 37 or more than 32767 hertz.-or- duration is less than + // or equal to zero. + // + // T:System.Security.HostProtectionException: + // This method was executed on a server, such as SQL Server, that does not permit + // access to the console. + /// + /// + /// + public static void Beep (int frequency, int duration) + { + throw new NotImplementedException (); + } + // + // Summary: + // Clears the console buffer and corresponding console window of display information. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. - // - // Summary: - // Copies a specified source area of the screen buffer to a specified destination - // area. - // - // Parameters: - // sourceLeft: - // The leftmost column of the source area. - // - // sourceTop: - // The topmost row of the source area. - // - // sourceWidth: - // The number of columns in the source area. - // - // sourceHeight: - // The number of rows in the source area. - // - // targetLeft: - // The leftmost column of the destination area. - // - // targetTop: - // The topmost row of the destination area. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // One or more of the parameters is less than zero.-or- sourceLeft or targetLeft - // is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop - // is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight - // is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth - // is greater than or equal to System.Console.BufferWidth. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop) - { - throw new NotImplementedException (); - } + static char [,] _buffer = new char [WindowWidth, WindowHeight]; - // - // Summary: - // Copies a specified source area of the screen buffer to a specified destination - // area. - // - // Parameters: - // sourceLeft: - // The leftmost column of the source area. - // - // sourceTop: - // The topmost row of the source area. - // - // sourceWidth: - // The number of columns in the source area. - // - // sourceHeight: - // The number of rows in the source area. - // - // targetLeft: - // The leftmost column of the destination area. - // - // targetTop: - // The topmost row of the destination area. - // - // sourceChar: - // The character used to fill the source area. - // - // sourceForeColor: - // The foreground color used to fill the source area. - // - // sourceBackColor: - // The background color used to fill the source area. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // One or more of the parameters is less than zero.-or- sourceLeft or targetLeft - // is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop - // is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight - // is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth - // is greater than or equal to System.Console.BufferWidth. - // - // T:System.ArgumentException: - // One or both of the color parameters is not a member of the System.ConsoleColor - // enumeration. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - //[SecuritySafeCritical] - /// - /// - /// - public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop, char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor) - { - throw new NotImplementedException (); - } + /// + /// + /// + public static void Clear () + { + _buffer = new char [BufferWidth, BufferHeight]; + SetCursorPosition (0, 0); + } - // - // Summary: - // Acquires the standard error stream. - // - // Returns: - // The standard error stream. - /// - /// - /// - public static Stream OpenStandardError () - { - throw new NotImplementedException (); - } + // + // Summary: + // Copies a specified source area of the screen buffer to a specified destination + // area. + // + // Parameters: + // sourceLeft: + // The leftmost column of the source area. + // + // sourceTop: + // The topmost row of the source area. + // + // sourceWidth: + // The number of columns in the source area. + // + // sourceHeight: + // The number of rows in the source area. + // + // targetLeft: + // The leftmost column of the destination area. + // + // targetTop: + // The topmost row of the destination area. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // One or more of the parameters is less than zero.-or- sourceLeft or targetLeft + // is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop + // is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight + // is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth + // is greater than or equal to System.Console.BufferWidth. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop) + { + throw new NotImplementedException (); + } - // - // Summary: - // Acquires the standard error stream, which is set to a specified buffer size. - // - // Parameters: - // bufferSize: - // The internal stream buffer size. - // - // Returns: - // The standard error stream. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // bufferSize is less than or equal to zero. - /// - /// - /// - public static Stream OpenStandardError (int bufferSize) - { - throw new NotImplementedException (); - } + // + // Summary: + // Copies a specified source area of the screen buffer to a specified destination + // area. + // + // Parameters: + // sourceLeft: + // The leftmost column of the source area. + // + // sourceTop: + // The topmost row of the source area. + // + // sourceWidth: + // The number of columns in the source area. + // + // sourceHeight: + // The number of rows in the source area. + // + // targetLeft: + // The leftmost column of the destination area. + // + // targetTop: + // The topmost row of the destination area. + // + // sourceChar: + // The character used to fill the source area. + // + // sourceForeColor: + // The foreground color used to fill the source area. + // + // sourceBackColor: + // The background color used to fill the source area. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // One or more of the parameters is less than zero.-or- sourceLeft or targetLeft + // is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop + // is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight + // is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth + // is greater than or equal to System.Console.BufferWidth. + // + // T:System.ArgumentException: + // One or both of the color parameters is not a member of the System.ConsoleColor + // enumeration. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + //[SecuritySafeCritical] + /// + /// + /// + public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop, char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor) + { + throw new NotImplementedException (); + } - // - // Summary: - // Acquires the standard input stream, which is set to a specified buffer size. - // - // Parameters: - // bufferSize: - // The internal stream buffer size. - // - // Returns: - // The standard input stream. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // bufferSize is less than or equal to zero. - /// - /// - /// - public static Stream OpenStandardInput (int bufferSize) - { - throw new NotImplementedException (); - } + // + // Summary: + // Acquires the standard error stream. + // + // Returns: + // The standard error stream. + /// + /// + /// + public static Stream OpenStandardError () + { + throw new NotImplementedException (); + } - // - // Summary: - // Acquires the standard input stream. - // - // Returns: - // The standard input stream. - /// - /// - /// - public static Stream OpenStandardInput () - { - throw new NotImplementedException (); - } + // + // Summary: + // Acquires the standard error stream, which is set to a specified buffer size. + // + // Parameters: + // bufferSize: + // The internal stream buffer size. + // + // Returns: + // The standard error stream. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // bufferSize is less than or equal to zero. + /// + /// + /// + public static Stream OpenStandardError (int bufferSize) + { + throw new NotImplementedException (); + } - // - // Summary: - // Acquires the standard output stream, which is set to a specified buffer size. - // - // Parameters: - // bufferSize: - // The internal stream buffer size. - // - // Returns: - // The standard output stream. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // bufferSize is less than or equal to zero. - /// - /// - /// - public static Stream OpenStandardOutput (int bufferSize) - { - throw new NotImplementedException (); - } + // + // Summary: + // Acquires the standard input stream, which is set to a specified buffer size. + // + // Parameters: + // bufferSize: + // The internal stream buffer size. + // + // Returns: + // The standard input stream. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // bufferSize is less than or equal to zero. + /// + /// + /// + public static Stream OpenStandardInput (int bufferSize) + { + throw new NotImplementedException (); + } - // - // Summary: - // Acquires the standard output stream. - // - // Returns: - // The standard output stream. - /// - /// - /// - public static Stream OpenStandardOutput () - { - throw new NotImplementedException (); - } + // + // Summary: + // Acquires the standard input stream. + // + // Returns: + // The standard input stream. + /// + /// + /// + public static Stream OpenStandardInput () + { + throw new NotImplementedException (); + } - // - // Summary: - // Reads the next character from the standard input stream. - // - // Returns: - // The next character from the input stream, or negative one (-1) if there are currently - // no more characters to be read. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static int Read () - { - throw new NotImplementedException (); - } + // + // Summary: + // Acquires the standard output stream, which is set to a specified buffer size. + // + // Parameters: + // bufferSize: + // The internal stream buffer size. + // + // Returns: + // The standard output stream. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // bufferSize is less than or equal to zero. + /// + /// + /// + public static Stream OpenStandardOutput (int bufferSize) + { + throw new NotImplementedException (); + } - // - // Summary: - // Obtains the next character or function key pressed by the user. The pressed key - // is optionally displayed in the console window. - // - // Parameters: - // intercept: - // Determines whether to display the pressed key in the console window. true to - // not display the pressed key; otherwise, false. - // - // Returns: - // An object that describes the System.ConsoleKey constant and Unicode character, - // if any, that correspond to the pressed console key. The System.ConsoleKeyInfo - // object also describes, in a bitwise combination of System.ConsoleModifiers values, - // whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously - // with the console key. - // - // Exceptions: - // T:System.InvalidOperationException: - // The System.Console.In property is redirected from some stream other than the - // console. - //[SecuritySafeCritical] - /// - /// - /// - public static ConsoleKeyInfo ReadKey (bool intercept) - { - if (MockKeyPresses.Count > 0) { - return MockKeyPresses.Pop (); - } else { - return new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', false, false, false); - } - } + // + // Summary: + // Acquires the standard output stream. + // + // Returns: + // The standard output stream. + /// + /// + /// + public static Stream OpenStandardOutput () + { + throw new NotImplementedException (); + } - /// - /// A stack of keypresses to return when ReadKey is called. - /// - public static Stack MockKeyPresses = new Stack (); - - /// - /// Helper to push a onto . - /// - /// - public static void PushMockKeyPress (Key key) - { - MockKeyPresses.Push (new ConsoleKeyInfo ( - (char)(key & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask), - ConsoleKeyMapping.GetConsoleKeyFromKey (key), - key.HasFlag (Key.ShiftMask), - key.HasFlag (Key.AltMask), - key.HasFlag (Key.CtrlMask))); - } + // + // Summary: + // Reads the next character from the standard input stream. + // + // Returns: + // The next character from the input stream, or negative one (-1) if there are currently + // no more characters to be read. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static int Read () + { + throw new NotImplementedException (); + } - // - // Summary: - // Obtains the next character or function key pressed by the user. The pressed key - // is displayed in the console window. - // - // Returns: - // An object that describes the System.ConsoleKey constant and Unicode character, - // if any, that correspond to the pressed console key. The System.ConsoleKeyInfo - // object also describes, in a bitwise combination of System.ConsoleModifiers values, - // whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously - // with the console key. - // - // Exceptions: - // T:System.InvalidOperationException: - // The System.Console.In property is redirected from some stream other than the - // console. - /// - /// - /// - public static ConsoleKeyInfo ReadKey () - { - throw new NotImplementedException (); - } + // + // Summary: + // Obtains the next character or function key pressed by the user. The pressed key + // is optionally displayed in the console window. + // + // Parameters: + // intercept: + // Determines whether to display the pressed key in the console window. true to + // not display the pressed key; otherwise, false. + // + // Returns: + // An object that describes the System.ConsoleKey constant and Unicode character, + // if any, that correspond to the pressed console key. The System.ConsoleKeyInfo + // object also describes, in a bitwise combination of System.ConsoleModifiers values, + // whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously + // with the console key. + // + // Exceptions: + // T:System.InvalidOperationException: + // The System.Console.In property is redirected from some stream other than the + // console. + //[SecuritySafeCritical] - // - // Summary: - // Reads the next line of characters from the standard input stream. - // - // Returns: - // The next line of characters from the input stream, or null if no more lines are - // available. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.OutOfMemoryException: - // There is insufficient memory to allocate a buffer for the returned string. - // - // T:System.ArgumentOutOfRangeException: - // The number of characters in the next line of characters is greater than System.Int32.MaxValue. - /// - /// - /// - public static string ReadLine () - { - throw new NotImplementedException (); - } + /// + /// A stack of keypresses to return when ReadKey is called. + /// + public static Stack MockKeyPresses = new Stack (); - // - // Summary: - // Sets the foreground and background console colors to their defaults. - // - // Exceptions: - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - //[SecuritySafeCritical] - /// - /// - /// - public static void ResetColor () - { - BackgroundColor = _defaultBackgroundColor; - ForegroundColor = _defaultForegroundColor; - } + /// + /// Helper to push a onto . + /// + /// + public static void PushMockKeyPress (Key key) + { + MockKeyPresses.Push (new ConsoleKeyInfo ( + (char)(key & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask), + ConsoleKeyMapping.GetConsoleKeyFromKey (key), + key.HasFlag (Key.ShiftMask), + key.HasFlag (Key.AltMask), + key.HasFlag (Key.CtrlMask))); + } - // - // Summary: - // Sets the height and width of the screen buffer area to the specified values. - // - // Parameters: - // width: - // The width of the buffer area measured in columns. - // - // height: - // The height of the buffer area measured in rows. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // height or width is less than or equal to zero.-or- height or width is greater - // than or equal to System.Int16.MaxValue.-or- width is less than System.Console.WindowLeft - // + System.Console.WindowWidth.-or- height is less than System.Console.WindowTop - // + System.Console.WindowHeight. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - //[SecuritySafeCritical] - /// - /// - /// - public static void SetBufferSize (int width, int height) - { - BufferWidth = width; - BufferHeight = height; - _buffer = new char [BufferWidth, BufferHeight]; - } + // + // Summary: + // Obtains the next character or function key pressed by the user. The pressed key + // is displayed in the console window. + // + // Returns: + // An object that describes the System.ConsoleKey constant and Unicode character, + // if any, that correspond to the pressed console key. The System.ConsoleKeyInfo + // object also describes, in a bitwise combination of System.ConsoleModifiers values, + // whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously + // with the console key. + // + // Exceptions: + // T:System.InvalidOperationException: + // The System.Console.In property is redirected from some stream other than the + // console. + /// + /// + /// + public static ConsoleKeyInfo ReadKey () + { + throw new NotImplementedException (); + } - // - // Summary: - // Sets the position of the cursor. - // - // Parameters: - // left: - // The column position of the cursor. Columns are numbered from left to right starting - // at 0. - // - // top: - // The row position of the cursor. Rows are numbered from top to bottom starting - // at 0. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // left or top is less than zero.-or- left is greater than or equal to System.Console.BufferWidth.-or- - // top is greater than or equal to System.Console.BufferHeight. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - //[SecuritySafeCritical] - /// - /// - /// - public static void SetCursorPosition (int left, int top) - { - CursorLeft = left; - CursorTop = top; - WindowLeft = Math.Max (Math.Min (left, BufferWidth - WindowWidth), 0); - WindowTop = Math.Max (Math.Min (top, BufferHeight - WindowHeight), 0); - } + // + // Summary: + // Reads the next line of characters from the standard input stream. + // + // Returns: + // The next line of characters from the input stream, or null if no more lines are + // available. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.OutOfMemoryException: + // There is insufficient memory to allocate a buffer for the returned string. + // + // T:System.ArgumentOutOfRangeException: + // The number of characters in the next line of characters is greater than System.Int32.MaxValue. + /// + /// + /// + public static string ReadLine () + { + throw new NotImplementedException (); + } - // - // Summary: - // Sets the System.Console.Error property to the specified System.IO.TextWriter - // object. - // - // Parameters: - // newError: - // A stream that is the new standard error output. - // - // Exceptions: - // T:System.ArgumentNullException: - // newError is null. - // - // T:System.Security.SecurityException: - // The caller does not have the required permission. - //[SecuritySafeCritical] - /// - /// - /// - public static void SetError (TextWriter newError) - { - throw new NotImplementedException (); - } + // + // Summary: + // Sets the foreground and background console colors to their defaults. + // + // Exceptions: + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + //[SecuritySafeCritical] + /// + /// + /// + public static void ResetColor () + { + BackgroundColor = _defaultBackgroundColor; + ForegroundColor = _defaultForegroundColor; + } - // - // Summary: - // Sets the System.Console.In property to the specified System.IO.TextReader object. - // - // Parameters: - // newIn: - // A stream that is the new standard input. - // - // Exceptions: - // T:System.ArgumentNullException: - // newIn is null. - // - // T:System.Security.SecurityException: - // The caller does not have the required permission. - //[SecuritySafeCritical] - /// - /// - /// - public static void SetIn (TextReader newIn) - { - throw new NotImplementedException (); - } + // + // Summary: + // Sets the height and width of the screen buffer area to the specified values. + // + // Parameters: + // width: + // The width of the buffer area measured in columns. + // + // height: + // The height of the buffer area measured in rows. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // height or width is less than or equal to zero.-or- height or width is greater + // than or equal to System.Int16.MaxValue.-or- width is less than System.Console.WindowLeft + // + System.Console.WindowWidth.-or- height is less than System.Console.WindowTop + // + System.Console.WindowHeight. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + //[SecuritySafeCritical] + /// + /// + /// + public static void SetBufferSize (int width, int height) + { + BufferWidth = width; + BufferHeight = height; + _buffer = new char [BufferWidth, BufferHeight]; + } - // - // Summary: - // Sets the System.Console.Out property to the specified System.IO.TextWriter object. - // - // Parameters: - // newOut: - // A stream that is the new standard output. - // - // Exceptions: - // T:System.ArgumentNullException: - // newOut is null. - // - // T:System.Security.SecurityException: - // The caller does not have the required permission. - //[SecuritySafeCritical] - /// - /// - /// - /// - public static void SetOut (TextWriter newOut) - { - throw new NotImplementedException (); - } + // + // Summary: + // Sets the position of the cursor. + // + // Parameters: + // left: + // The column position of the cursor. Columns are numbered from left to right starting + // at 0. + // + // top: + // The row position of the cursor. Rows are numbered from top to bottom starting + // at 0. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // left or top is less than zero.-or- left is greater than or equal to System.Console.BufferWidth.-or- + // top is greater than or equal to System.Console.BufferHeight. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + //[SecuritySafeCritical] + /// + /// + /// + public static void SetCursorPosition (int left, int top) + { + CursorLeft = left; + CursorTop = top; + WindowLeft = Math.Max (Math.Min (left, BufferWidth - WindowWidth), 0); + WindowTop = Math.Max (Math.Min (top, BufferHeight - WindowHeight), 0); + } - // - // Summary: - // Sets the position of the console window relative to the screen buffer. - // - // Parameters: - // left: - // The column position of the upper left corner of the console window. - // - // top: - // The row position of the upper left corner of the console window. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // left or top is less than zero.-or- left + System.Console.WindowWidth is greater - // than System.Console.BufferWidth.-or- top + System.Console.WindowHeight is greater - // than System.Console.BufferHeight. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - //[SecuritySafeCritical] - /// - /// - /// - /// - /// - public static void SetWindowPosition (int left, int top) - { - WindowLeft = left; - WindowTop = top; - } + // + // Summary: + // Sets the System.Console.Error property to the specified System.IO.TextWriter + // object. + // + // Parameters: + // newError: + // A stream that is the new standard error output. + // + // Exceptions: + // T:System.ArgumentNullException: + // newError is null. + // + // T:System.Security.SecurityException: + // The caller does not have the required permission. + //[SecuritySafeCritical] + /// + /// + /// + public static void SetError (TextWriter newError) + { + throw new NotImplementedException (); + } - // - // Summary: - // Sets the height and width of the console window to the specified values. - // - // Parameters: - // width: - // The width of the console window measured in columns. - // - // height: - // The height of the console window measured in rows. - // - // Exceptions: - // T:System.ArgumentOutOfRangeException: - // width or height is less than or equal to zero.-or- width plus System.Console.WindowLeft - // or height plus System.Console.WindowTop is greater than or equal to System.Int16.MaxValue. - // -or- width or height is greater than the largest possible window width or height - // for the current screen resolution and console font. - // - // T:System.Security.SecurityException: - // The user does not have permission to perform this action. - // - // T:System.IO.IOException: - // An I/O error occurred. - //[SecuritySafeCritical] - /// - /// - /// - /// - /// - public static void SetWindowSize (int width, int height) - { - WindowWidth = width; - WindowHeight = height; - } + // + // Summary: + // Sets the System.Console.In property to the specified System.IO.TextReader object. + // + // Parameters: + // newIn: + // A stream that is the new standard input. + // + // Exceptions: + // T:System.ArgumentNullException: + // newIn is null. + // + // T:System.Security.SecurityException: + // The caller does not have the required permission. + //[SecuritySafeCritical] + /// + /// + /// + public static void SetIn (TextReader newIn) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified string value to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (string value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Sets the System.Console.Out property to the specified System.IO.TextWriter object. + // + // Parameters: + // newOut: + // A stream that is the new standard output. + // + // Exceptions: + // T:System.ArgumentNullException: + // newOut is null. + // + // T:System.Security.SecurityException: + // The caller does not have the required permission. + //[SecuritySafeCritical] + /// + /// + /// + /// + public static void SetOut (TextWriter newOut) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified object to the standard output - // stream. - // - // Parameters: - // value: - // The value to write, or null. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (object value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Sets the position of the console window relative to the screen buffer. + // + // Parameters: + // left: + // The column position of the upper left corner of the console window. + // + // top: + // The row position of the upper left corner of the console window. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // left or top is less than zero.-or- left + System.Console.WindowWidth is greater + // than System.Console.BufferWidth.-or- top + System.Console.WindowHeight is greater + // than System.Console.BufferHeight. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + //[SecuritySafeCritical] + /// + /// + /// + /// + /// + public static void SetWindowPosition (int left, int top) + { + WindowLeft = left; + WindowTop = top; + } - // - // Summary: - // Writes the text representation of the specified 64-bit unsigned integer value - // to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - //[CLSCompliant (false)] - /// - /// - /// - /// - public static void Write (ulong value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Sets the height and width of the console window to the specified values. + // + // Parameters: + // width: + // The width of the console window measured in columns. + // + // height: + // The height of the console window measured in rows. + // + // Exceptions: + // T:System.ArgumentOutOfRangeException: + // width or height is less than or equal to zero.-or- width plus System.Console.WindowLeft + // or height plus System.Console.WindowTop is greater than or equal to System.Int16.MaxValue. + // -or- width or height is greater than the largest possible window width or height + // for the current screen resolution and console font. + // + // T:System.Security.SecurityException: + // The user does not have permission to perform this action. + // + // T:System.IO.IOException: + // An I/O error occurred. + //[SecuritySafeCritical] + /// + /// + /// + /// + /// + public static void SetWindowSize (int width, int height) + { + WindowWidth = width; + WindowHeight = height; + } - // - // Summary: - // Writes the text representation of the specified 64-bit signed integer value to - // the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (long value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the specified string value to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (string value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified objects to the standard output - // stream using the specified format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg0: - // The first object to write using format. - // - // arg1: - // The second object to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - /// - public static void Write (string format, object arg0, object arg1) - { + // + // Summary: + // Writes the text representation of the specified object to the standard output + // stream. + // + // Parameters: + // value: + // The value to write, or null. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (object value) + { + if (value is Rune rune) { + Write ((char)rune.Value); + } else { throw new NotImplementedException (); } + } - // - // Summary: - // Writes the text representation of the specified 32-bit signed integer value to - // the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (int value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 64-bit unsigned integer value + // to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + //[CLSCompliant (false)] + /// + /// + /// + /// + public static void Write (ulong value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified object to the standard output - // stream using the specified format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg0: - // An object to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - public static void Write (string format, object arg0) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 64-bit signed integer value to + // the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (long value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified 32-bit unsigned integer value - // to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - //[CLSCompliant (false)] - /// - /// - /// - /// - public static void Write (uint value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified objects to the standard output + // stream using the specified format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg0: + // The first object to write using format. + // + // arg1: + // The second object to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + /// + public static void Write (string format, object arg0, object arg1) + { + throw new NotImplementedException (); + } - //[CLSCompliant (false)] - /// - /// - /// - /// - /// - /// - /// - /// - public static void Write (string format, object arg0, object arg1, object arg2, object arg3) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 32-bit signed integer value to + // the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (int value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified array of objects to the standard - // output stream using the specified format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg: - // An array of objects to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format or arg is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - public static void Write (string format, params object [] arg) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified object to the standard output + // stream using the specified format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg0: + // An object to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + public static void Write (string format, object arg0) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified Boolean value to the standard - // output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (bool value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 32-bit unsigned integer value + // to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + //[CLSCompliant (false)] + /// + /// + /// + /// + public static void Write (uint value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified Unicode character value to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (char value) - { - _buffer [CursorLeft, CursorTop] = value; - } + //[CLSCompliant (false)] + /// + /// + /// + /// + /// + /// + /// + /// + public static void Write (string format, object arg0, object arg1, object arg2, object arg3) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified array of Unicode characters to the standard output stream. - // - // Parameters: - // buffer: - // A Unicode character array. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (char [] buffer) - { - _buffer [CursorLeft, CursorTop] = (char)0; - foreach (var ch in buffer) { - _buffer [CursorLeft, CursorTop] += ch; - } - } + // + // Summary: + // Writes the text representation of the specified array of objects to the standard + // output stream using the specified format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg: + // An array of objects to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format or arg is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + public static void Write (string format, params object [] arg) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified subarray of Unicode characters to the standard output stream. - // - // Parameters: - // buffer: - // An array of Unicode characters. - // - // index: - // The starting position in buffer. - // - // count: - // The number of characters to write. - // - // Exceptions: - // T:System.ArgumentNullException: - // buffer is null. - // - // T:System.ArgumentOutOfRangeException: - // index or count is less than zero. - // - // T:System.ArgumentException: - // index plus count specify a position that is not within buffer. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - /// - /// - public static void Write (char [] buffer, int index, int count) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified Boolean value to the standard + // output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (bool value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified objects to the standard output - // stream using the specified format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg0: - // The first object to write using format. - // - // arg1: - // The second object to write using format. - // - // arg2: - // The third object to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - /// - /// - public static void Write (string format, object arg0, object arg1, object arg2) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the specified Unicode character value to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (char value) + { + _buffer [CursorLeft, CursorTop] = value; + } - // - // Summary: - // Writes the text representation of the specified System.Decimal value to the standard - // output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (decimal value) - { - throw new NotImplementedException (); + // + // Summary: + // Writes the specified array of Unicode characters to the standard output stream. + // + // Parameters: + // buffer: + // A Unicode character array. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (char [] buffer) + { + _buffer [CursorLeft, CursorTop] = (char)0; + foreach (var ch in buffer) { + _buffer [CursorLeft, CursorTop] += ch; } + } - // - // Summary: - // Writes the text representation of the specified single-precision floating-point - // value to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (float value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the specified subarray of Unicode characters to the standard output stream. + // + // Parameters: + // buffer: + // An array of Unicode characters. + // + // index: + // The starting position in buffer. + // + // count: + // The number of characters to write. + // + // Exceptions: + // T:System.ArgumentNullException: + // buffer is null. + // + // T:System.ArgumentOutOfRangeException: + // index or count is less than zero. + // + // T:System.ArgumentException: + // index plus count specify a position that is not within buffer. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + /// + /// + public static void Write (char [] buffer, int index, int count) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified double-precision floating-point - // value to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void Write (double value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified objects to the standard output + // stream using the specified format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg0: + // The first object to write using format. + // + // arg1: + // The second object to write using format. + // + // arg2: + // The third object to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + /// + /// + public static void Write (string format, object arg0, object arg1, object arg2) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the current line terminator to the standard output stream. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - public static void WriteLine () - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified System.Decimal value to the standard + // output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (decimal value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified single-precision floating-point - // value, followed by the current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (float value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified single-precision floating-point + // value to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (float value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified 32-bit signed integer value, - // followed by the current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (int value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified double-precision floating-point + // value to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void Write (double value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified 32-bit unsigned integer value, - // followed by the current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - //[CLSCompliant (false)] - /// - /// - /// - /// - public static void WriteLine (uint value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the current line terminator to the standard output stream. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + public static void WriteLine () + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified 64-bit signed integer value, - // followed by the current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (long value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified single-precision floating-point + // value, followed by the current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (float value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified 64-bit unsigned integer value, - // followed by the current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - //[CLSCompliant (false)] - /// - /// - /// - /// - public static void WriteLine (ulong value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 32-bit signed integer value, + // followed by the current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (int value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified object, followed by the current - // line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (object value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 32-bit unsigned integer value, + // followed by the current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + //[CLSCompliant (false)] + /// + /// + /// + /// + public static void WriteLine (uint value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified string value, followed by the current line terminator, to - // the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (string value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 64-bit signed integer value, + // followed by the current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (long value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified object, followed by the current - // line terminator, to the standard output stream using the specified format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg0: - // An object to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - public static void WriteLine (string format, object arg0) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified 64-bit unsigned integer value, + // followed by the current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + //[CLSCompliant (false)] + /// + /// + /// + /// + public static void WriteLine (ulong value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified objects, followed by the current - // line terminator, to the standard output stream using the specified format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg0: - // The first object to write using format. - // - // arg1: - // The second object to write using format. - // - // arg2: - // The third object to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - /// - /// - public static void WriteLine (string format, object arg0, object arg1, object arg2) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified object, followed by the current + // line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (object value) + { + throw new NotImplementedException (); + } - //[CLSCompliant (false)] - /// - /// - /// - /// - /// - /// - /// - /// - public static void WriteLine (string format, object arg0, object arg1, object arg2, object arg3) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the specified string value, followed by the current line terminator, to + // the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (string value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified array of objects, followed by - // the current line terminator, to the standard output stream using the specified - // format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg: - // An array of objects to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format or arg is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - public static void WriteLine (string format, params object [] arg) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified object, followed by the current + // line terminator, to the standard output stream using the specified format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg0: + // An object to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + public static void WriteLine (string format, object arg0) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified subarray of Unicode characters, followed by the current - // line terminator, to the standard output stream. - // - // Parameters: - // buffer: - // An array of Unicode characters. - // - // index: - // The starting position in buffer. - // - // count: - // The number of characters to write. - // - // Exceptions: - // T:System.ArgumentNullException: - // buffer is null. - // - // T:System.ArgumentOutOfRangeException: - // index or count is less than zero. - // - // T:System.ArgumentException: - // index plus count specify a position that is not within buffer. - // - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - /// - /// - public static void WriteLine (char [] buffer, int index, int count) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified objects, followed by the current + // line terminator, to the standard output stream using the specified format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg0: + // The first object to write using format. + // + // arg1: + // The second object to write using format. + // + // arg2: + // The third object to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + /// + /// + public static void WriteLine (string format, object arg0, object arg1, object arg2) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified System.Decimal value, followed - // by the current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (decimal value) - { - throw new NotImplementedException (); - } + //[CLSCompliant (false)] + /// + /// + /// + /// + /// + /// + /// + /// + public static void WriteLine (string format, object arg0, object arg1, object arg2, object arg3) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified array of Unicode characters, followed by the current line - // terminator, to the standard output stream. - // - // Parameters: - // buffer: - // A Unicode character array. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (char [] buffer) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified array of objects, followed by + // the current line terminator, to the standard output stream using the specified + // format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg: + // An array of objects to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format or arg is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + public static void WriteLine (string format, params object [] arg) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the specified Unicode character, followed by the current line terminator, - // value to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (char value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the specified subarray of Unicode characters, followed by the current + // line terminator, to the standard output stream. + // + // Parameters: + // buffer: + // An array of Unicode characters. + // + // index: + // The starting position in buffer. + // + // count: + // The number of characters to write. + // + // Exceptions: + // T:System.ArgumentNullException: + // buffer is null. + // + // T:System.ArgumentOutOfRangeException: + // index or count is less than zero. + // + // T:System.ArgumentException: + // index plus count specify a position that is not within buffer. + // + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + /// + /// + public static void WriteLine (char [] buffer, int index, int count) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified Boolean value, followed by the - // current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (bool value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the text representation of the specified System.Decimal value, followed + // by the current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (decimal value) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified objects, followed by the current - // line terminator, to the standard output stream using the specified format information. - // - // Parameters: - // format: - // A composite format string (see Remarks). - // - // arg0: - // The first object to write using format. - // - // arg1: - // The second object to write using format. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - // - // T:System.ArgumentNullException: - // format is null. - // - // T:System.FormatException: - // The format specification in format is invalid. - /// - /// - /// - /// - /// - /// - public static void WriteLine (string format, object arg0, object arg1) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the specified array of Unicode characters, followed by the current line + // terminator, to the standard output stream. + // + // Parameters: + // buffer: + // A Unicode character array. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (char [] buffer) + { + throw new NotImplementedException (); + } - // - // Summary: - // Writes the text representation of the specified double-precision floating-point - // value, followed by the current line terminator, to the standard output stream. - // - // Parameters: - // value: - // The value to write. - // - // Exceptions: - // T:System.IO.IOException: - // An I/O error occurred. - /// - /// - /// - /// - public static void WriteLine (double value) - { - throw new NotImplementedException (); - } + // + // Summary: + // Writes the specified Unicode character, followed by the current line terminator, + // value to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (char value) + { + throw new NotImplementedException (); + } + // + // Summary: + // Writes the text representation of the specified Boolean value, followed by the + // current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (bool value) + { + throw new NotImplementedException (); + } + + // + // Summary: + // Writes the text representation of the specified objects, followed by the current + // line terminator, to the standard output stream using the specified format information. + // + // Parameters: + // format: + // A composite format string (see Remarks). + // + // arg0: + // The first object to write using format. + // + // arg1: + // The second object to write using format. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + // + // T:System.ArgumentNullException: + // format is null. + // + // T:System.FormatException: + // The format specification in format is invalid. + /// + /// + /// + /// + /// + /// + public static void WriteLine (string format, object arg0, object arg1) + { + throw new NotImplementedException (); } -} + + // + // Summary: + // Writes the text representation of the specified double-precision floating-point + // value, followed by the current line terminator, to the standard output stream. + // + // Parameters: + // value: + // The value to write. + // + // Exceptions: + // T:System.IO.IOException: + // An I/O error occurred. + /// + /// + /// + /// + public static void WriteLine (double value) + { + throw new NotImplementedException (); + } + +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 2f7df9534d..5e1ae851f5 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -2,6 +2,7 @@ // FakeDriver.cs: A fake ConsoleDriver for unit tests. // using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -11,717 +12,564 @@ // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; - -namespace Terminal.Gui { - /// - /// Implements a mock ConsoleDriver for unit testing - /// - public class FakeDriver : ConsoleDriver { +using Unix.Terminal; +using static Terminal.Gui.WindowsConsole; +using System.Drawing; + +namespace Terminal.Gui; +/// +/// Implements a mock ConsoleDriver for unit testing +/// +public class FakeDriver : ConsoleDriver { #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public class Behaviors { + public class Behaviors { - public bool UseFakeClipboard { get; internal set; } - public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; } - public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; } + public bool UseFakeClipboard { get; internal set; } + public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; } + public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; } - public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false) - { - UseFakeClipboard = useFakeClipboard; - FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException; - FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue; + public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false) + { + UseFakeClipboard = useFakeClipboard; + FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException; + FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue; - // double check usage is correct - Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false); - Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false); - } + // double check usage is correct + Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false); + Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false); } + } - public static FakeDriver.Behaviors FakeBehaviors = new Behaviors (); - - int cols, rows, left, top; - public override int Cols => cols; - public override int Rows => rows; - // Only handling left here because not all terminals has a horizontal scroll bar. - public override int Left => 0; - public override int Top => 0; - public override bool EnableConsoleScrolling { get; set; } - private IClipboard clipboard = null; - public override IClipboard Clipboard => clipboard; - - // The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag - int [,,] contents; - bool [] dirtyLine; - - /// - /// Assists with testing, the format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag - /// - public override int [,,] Contents => contents; - - //void UpdateOffscreen () - //{ - // int cols = Cols; - // int rows = Rows; - - // contents = new int [rows, cols, 3]; - // for (int r = 0; r < rows; r++) { - // for (int c = 0; c < cols; c++) { - // contents [r, c, 0] = ' '; - // contents [r, c, 1] = MakeColor (ConsoleColor.Gray, ConsoleColor.Black); - // contents [r, c, 2] = 0; - // } - // } - // dirtyLine = new bool [rows]; - // for (int row = 0; row < rows; row++) - // dirtyLine [row] = true; - //} - - static bool sync = false; - - public FakeDriver () - { - if (FakeBehaviors.UseFakeClipboard) { - clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse); + public static FakeDriver.Behaviors FakeBehaviors = new Behaviors (); + + public FakeDriver () + { + if (FakeBehaviors.UseFakeClipboard) { + Clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse); + } else { + if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { + Clipboard = new WindowsClipboard (); + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { + Clipboard = new MacOSXClipboard (); } else { - if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { - clipboard = new WindowsClipboard (); - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { - clipboard = new MacOSXClipboard (); + if (CursesDriver.Is_WSL_Platform ()) { + Clipboard = new WSLClipboard (); } else { - if (CursesDriver.Is_WSL_Platform ()) { - clipboard = new WSLClipboard (); - } else { - clipboard = new CursesClipboard (); - } + Clipboard = new CursesClipboard (); } } } + } - bool needMove; - // Current row, and current col, tracked by Move/AddCh only - int ccol, crow; - public override void Move (int col, int row) - { - ccol = col; - crow = row; + public override void End () + { + FakeConsole.ResetColor (); + FakeConsole.Clear (); + } + + public override void Init (Action terminalResized) + { + FakeConsole.MockKeyPresses.Clear (); + + TerminalResized = terminalResized; + + Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH; + Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT; + FakeConsole.Clear (); + ResizeScreen (); + // Call InitializeColorSchemes before UpdateOffScreen as it references Colors + CurrentAttribute = MakeColor (Color.White, Color.Black); + InitializeColorSchemes (); + ClearContents (); + } - if (Clip.Contains (col, row)) { - FakeConsole.CursorTop = row; - FakeConsole.CursorLeft = col; - needMove = false; - } else { - FakeConsole.CursorTop = Clip.Y; - FakeConsole.CursorLeft = Clip.X; - needMove = true; - } - } - public override void AddRune (Rune rune) - { - rune = rune.MakePrintable (); - var runeWidth = rune.GetColumns (); - var validClip = IsValidContent (ccol, crow, Clip); - - if (validClip) { - if (needMove) { - //MockConsole.CursorLeft = ccol; - //MockConsole.CursorTop = crow; - needMove = false; - } - if (runeWidth == 0 && ccol > 0) { - var r = contents [crow, ccol - 1, 0]; - var s = new string (new char [] { (char)r, (char)rune.Value }); - string sn; - if (!s.IsNormalized ()) { - sn = s.Normalize (); - } else { - sn = s; + public override void UpdateScreen () + { + var savedRow = FakeConsole.CursorTop; + var savedCol = FakeConsole.CursorLeft; + var savedCursorVisible = FakeConsole.CursorVisible; + + var top = 0; + var left = 0; + var rows = Rows; + var cols = Cols; + System.Text.StringBuilder output = new System.Text.StringBuilder (); + Attribute redrawAttr = new Attribute (); + var lastCol = -1; + + for (var row = top; row < rows; row++) { + if (!_dirtyLines [row]) { + continue; + } + + FakeConsole.CursorTop = row; + FakeConsole.CursorLeft = 0; + + _dirtyLines [row] = false; + output.Clear (); + for (var col = left; col < cols; col++) { + lastCol = -1; + var outputWidth = 0; + for (; col < cols; col++) { + if (!Contents [row, col].IsDirty) { + if (output.Length > 0) { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + } else if (lastCol == -1) { + lastCol = col; + } + if (lastCol + 1 < cols) + lastCol++; + continue; } - var c = sn [0]; - contents [crow, ccol - 1, 0] = c; - contents [crow, ccol - 1, 1] = CurrentAttribute; - contents [crow, ccol - 1, 2] = 1; - } else { - if (runeWidth < 2 && ccol > 0 - && ((Rune)contents [crow, ccol - 1, 0]).GetColumns () > 1) { - - contents [crow, ccol - 1, 0] = (int)(uint)' '; - - } else if (runeWidth < 2 && ccol <= Clip.Right - 1 - && ((Rune)contents [crow, ccol, 0]).GetColumns () > 1) { - - contents [crow, ccol + 1, 0] = (int)(uint)' '; - contents [crow, ccol + 1, 2] = 1; + if (lastCol == -1) { + lastCol = col; + } + Attribute attr = Contents [row, col].Attribute.Value; + // Performance: Only send the escape sequence if the attribute has changed. + if (attr != redrawAttr) { + redrawAttr = attr; + FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground; + FakeConsole.BackgroundColor = (ConsoleColor)attr.Background; } - if (runeWidth > 1 && ccol == Clip.Right - 1) { - contents [crow, ccol, 0] = (int)(uint)' '; - } else { - contents [crow, ccol, 0] = (int)(uint)rune.Value; + outputWidth++; + var rune = (Rune)Contents [row, col].Runes [0]; + output.Append (rune.ToString ()); + if (rune.IsSurrogatePair () && rune.GetColumns () < 2) { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + FakeConsole.CursorLeft--; } - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 1; - - dirtyLine [crow] = true; + Contents [row, col].IsDirty = false; } - } else { - needMove = true; - } - - if (runeWidth < 0 || runeWidth > 0) { - ccol++; } + if (output.Length > 0) { + FakeConsole.CursorTop = row; + FakeConsole.CursorLeft = lastCol; - if (runeWidth > 1) { - if (validClip && ccol < Clip.Right) { - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 0; + foreach (var c in output.ToString ()) { + FakeConsole.Write (c); } - ccol++; - } - - //if (ccol == Cols) { - // ccol = 0; - // if (crow + 1 < Rows) - // crow++; - //} - if (sync) { - UpdateScreen (); } } + FakeConsole.CursorTop = 0; + FakeConsole.CursorLeft = 0; - public override void AddStr (string str) - { - foreach (var rune in str.EnumerateRunes ()) - AddRune (rune); - } + //SetCursorVisibility (savedVisibitity); - public override void End () + void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { - FakeConsole.ResetColor (); - FakeConsole.Clear (); + FakeConsole.CursorTop = row; + FakeConsole.CursorLeft = lastCol; + foreach (var c in output.ToString ()) { + FakeConsole.Write (c); + } + + output.Clear (); + lastCol += outputWidth; + outputWidth = 0; } - public override Attribute MakeColor (Color foreground, Color background) - { - return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background); - } + FakeConsole.CursorTop = savedRow; + FakeConsole.CursorLeft = savedCol; + FakeConsole.CursorVisible = savedCursorVisible; + } - static Attribute MakeColor (ConsoleColor f, ConsoleColor b) - { - // Encode the colors into the int value. - return new Attribute ( - value: ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff), - foreground: (Color)f, - background: (Color)b - ); - } + public override void Refresh () + { + UpdateScreen (); + UpdateCursor (); + } - public override void Init (Action terminalResized) - { - FakeConsole.MockKeyPresses.Clear (); - - TerminalResized = terminalResized; - - cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH; - rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT; - FakeConsole.Clear (); - ResizeScreen (); - // Call InitalizeColorSchemes before UpdateOffScreen as it references Colors - CurrentAttribute = MakeColor (Color.White, Color.Black); - InitalizeColorSchemes (); - UpdateOffScreen (); - } + #region Color Handling - public override Attribute MakeAttribute (Color fore, Color back) - { - return MakeColor ((ConsoleColor)fore, (ConsoleColor)back); - } + // Cache the list of ConsoleColor values. + private static readonly HashSet ConsoleColorValues = new HashSet ( + Enum.GetValues (typeof (ConsoleColor)).OfType ().Select (c => (int)c) + ); - int redrawColor = -1; - void SetColor (int color) - { - redrawColor = color; - IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (s => (int)s); - if (values.Contains (color & 0xffff)) { - FakeConsole.BackgroundColor = (ConsoleColor)(color & 0xffff); - } - if (values.Contains ((color >> 16) & 0xffff)) { - FakeConsole.ForegroundColor = (ConsoleColor)((color >> 16) & 0xffff); - } + void SetColor (int color) + { + if (ConsoleColorValues.Contains (color & 0xffff)) { + FakeConsole.BackgroundColor = (ConsoleColor)(color & 0xffff); } + if (ConsoleColorValues.Contains ((color >> 16) & 0xffff)) { + FakeConsole.ForegroundColor = (ConsoleColor)((color >> 16) & 0xffff); + } + } - public override void UpdateScreen () - { - int top = Top; - int left = Left; - int rows = Math.Min (FakeConsole.WindowHeight + top, Rows); - int cols = Cols; - - var savedRow = FakeConsole.CursorTop; - var savedCol = FakeConsole.CursorLeft; - var savedCursorVisible = FakeConsole.CursorVisible; - for (int row = top; row < rows; row++) { - if (!dirtyLine [row]) - continue; - dirtyLine [row] = false; - for (int col = left; col < cols; col++) { - FakeConsole.CursorTop = row; - FakeConsole.CursorLeft = col; - for (; col < cols; col++) { - if (contents [row, col, 2] == 0) { - FakeConsole.CursorLeft++; - continue; - } - - var color = contents [row, col, 1]; - if (color != redrawColor) - SetColor (color); + /// + /// In the FakeDriver, colors are encoded as an int; same as NetDriver + /// Extracts the foreground and background colors from the encoded value. + /// Assumes a 4-bit encoded value for both foreground and background colors. + /// + internal override void GetColors (int value, out Color foreground, out Color background) + { + // Assume a 4-bit encoded value for both foreground and background colors. + foreground = (Color)((value >> 16) & 0xF); + background = (Color)(value & 0xF); + } - Rune rune = (Rune)contents [row, col, 0]; - if (rune.DecodeSurrogatePair (out char [] spair)) { - FakeConsole.Write (spair); - } else { - FakeConsole.Write ((char)rune.Value); - } - contents [row, col, 2] = 0; - } - } - } - FakeConsole.CursorTop = savedRow; - FakeConsole.CursorLeft = savedCol; - FakeConsole.CursorVisible = savedCursorVisible; - } + /// + /// In the FakeDriver, colors are encoded as an int; same as NetDriver + /// However, the foreground color is stored in the most significant 16 bits, + /// and the background color is stored in the least significant 16 bits. + /// + public override Attribute MakeColor (Color foreground, Color background) + { + // Encode the colors into the int value. + return new Attribute ( + value: ((((int)foreground) & 0xffff) << 16) | (((int)background) & 0xffff), + foreground: foreground, + background: background + ); + } - public override void Refresh () - { - UpdateScreen (); - UpdateCursor (); - } + #endregion - public override void SetAttribute (Attribute c) - { - base.SetAttribute (c); + public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + { + if (consoleKeyInfo.Key != ConsoleKey.Packet) { + return consoleKeyInfo; } - public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) - { - if (consoleKeyInfo.Key != ConsoleKey.Packet) { - return consoleKeyInfo; - } + var mod = consoleKeyInfo.Modifiers; + var shift = (mod & ConsoleModifiers.Shift) != 0; + var alt = (mod & ConsoleModifiers.Alt) != 0; + var control = (mod & ConsoleModifiers.Control) != 0; - var mod = consoleKeyInfo.Modifiers; - var shift = (mod & ConsoleModifiers.Shift) != 0; - var alt = (mod & ConsoleModifiers.Alt) != 0; - var control = (mod & ConsoleModifiers.Control) != 0; + var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _); - var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _); + return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control); + } - return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control); + Key MapKey (ConsoleKeyInfo keyInfo) + { + switch (keyInfo.Key) { + case ConsoleKey.Escape: + return MapKeyModifiers (keyInfo, Key.Esc); + case ConsoleKey.Tab: + return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab; + case ConsoleKey.Clear: + return MapKeyModifiers (keyInfo, Key.Clear); + case ConsoleKey.Home: + return MapKeyModifiers (keyInfo, Key.Home); + case ConsoleKey.End: + return MapKeyModifiers (keyInfo, Key.End); + case ConsoleKey.LeftArrow: + return MapKeyModifiers (keyInfo, Key.CursorLeft); + case ConsoleKey.RightArrow: + return MapKeyModifiers (keyInfo, Key.CursorRight); + case ConsoleKey.UpArrow: + return MapKeyModifiers (keyInfo, Key.CursorUp); + case ConsoleKey.DownArrow: + return MapKeyModifiers (keyInfo, Key.CursorDown); + case ConsoleKey.PageUp: + return MapKeyModifiers (keyInfo, Key.PageUp); + case ConsoleKey.PageDown: + return MapKeyModifiers (keyInfo, Key.PageDown); + case ConsoleKey.Enter: + return MapKeyModifiers (keyInfo, Key.Enter); + case ConsoleKey.Spacebar: + return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar); + case ConsoleKey.Backspace: + return MapKeyModifiers (keyInfo, Key.Backspace); + case ConsoleKey.Delete: + return MapKeyModifiers (keyInfo, Key.DeleteChar); + case ConsoleKey.Insert: + return MapKeyModifiers (keyInfo, Key.InsertChar); + case ConsoleKey.PrintScreen: + return MapKeyModifiers (keyInfo, Key.PrintScreen); + + case ConsoleKey.Oem1: + case ConsoleKey.Oem2: + case ConsoleKey.Oem3: + case ConsoleKey.Oem4: + case ConsoleKey.Oem5: + case ConsoleKey.Oem6: + case ConsoleKey.Oem7: + case ConsoleKey.Oem8: + case ConsoleKey.Oem102: + case ConsoleKey.OemPeriod: + case ConsoleKey.OemComma: + case ConsoleKey.OemPlus: + case ConsoleKey.OemMinus: + if (keyInfo.KeyChar == 0) { + return Key.Unknown; + } + + return (Key)((uint)keyInfo.KeyChar); + } + + var key = keyInfo.Key; + if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { + var delta = key - ConsoleKey.A; + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta)); + } + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { + return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta)); + } + if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); + } + if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + if (keyInfo.KeyChar == 0) { + return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta)); + } else { + return (Key)((uint)keyInfo.KeyChar); + } + } + return (Key)((uint)keyInfo.KeyChar); } - - Key MapKey (ConsoleKeyInfo keyInfo) - { - switch (keyInfo.Key) { - case ConsoleKey.Escape: - return MapKeyModifiers (keyInfo, Key.Esc); - case ConsoleKey.Tab: - return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab; - case ConsoleKey.Clear: - return MapKeyModifiers (keyInfo, Key.Clear); - case ConsoleKey.Home: - return MapKeyModifiers (keyInfo, Key.Home); - case ConsoleKey.End: - return MapKeyModifiers (keyInfo, Key.End); - case ConsoleKey.LeftArrow: - return MapKeyModifiers (keyInfo, Key.CursorLeft); - case ConsoleKey.RightArrow: - return MapKeyModifiers (keyInfo, Key.CursorRight); - case ConsoleKey.UpArrow: - return MapKeyModifiers (keyInfo, Key.CursorUp); - case ConsoleKey.DownArrow: - return MapKeyModifiers (keyInfo, Key.CursorDown); - case ConsoleKey.PageUp: - return MapKeyModifiers (keyInfo, Key.PageUp); - case ConsoleKey.PageDown: - return MapKeyModifiers (keyInfo, Key.PageDown); - case ConsoleKey.Enter: - return MapKeyModifiers (keyInfo, Key.Enter); - case ConsoleKey.Spacebar: - return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar); - case ConsoleKey.Backspace: - return MapKeyModifiers (keyInfo, Key.Backspace); - case ConsoleKey.Delete: - return MapKeyModifiers (keyInfo, Key.DeleteChar); - case ConsoleKey.Insert: - return MapKeyModifiers (keyInfo, Key.InsertChar); - case ConsoleKey.PrintScreen: - return MapKeyModifiers (keyInfo, Key.PrintScreen); - - case ConsoleKey.Oem1: - case ConsoleKey.Oem2: - case ConsoleKey.Oem3: - case ConsoleKey.Oem4: - case ConsoleKey.Oem5: - case ConsoleKey.Oem6: - case ConsoleKey.Oem7: - case ConsoleKey.Oem8: - case ConsoleKey.Oem102: - case ConsoleKey.OemPeriod: - case ConsoleKey.OemComma: - case ConsoleKey.OemPlus: - case ConsoleKey.OemMinus: - if (keyInfo.KeyChar == 0) - return Key.Unknown; - - return (Key)((uint)keyInfo.KeyChar); + if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { + var delta = key - ConsoleKey.D0; + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { + return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta)); } - - var key = keyInfo.Key; - if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { - var delta = key - ConsoleKey.A; - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta)); - } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0) { - return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta)); - } else { - return (Key)((uint)keyInfo.KeyChar); - } - } - return (Key)((uint)keyInfo.KeyChar); + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta)); } - if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { - var delta = key - ConsoleKey.D0; - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta)); - } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { + if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); + } + if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) { return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); - } - } - return (Key)((uint)keyInfo.KeyChar); } - if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { - var delta = key - ConsoleKey.F1; - if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); - } - - return (Key)((uint)Key.F1 + delta); - } - if (keyInfo.KeyChar != 0) { - return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar)); + return (Key)((uint)keyInfo.KeyChar); + } + if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { + var delta = key - ConsoleKey.F1; + if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); } - return (Key)(0xffffffff); + return (Key)((uint)Key.F1 + delta); } - - KeyModifiers keyModifiers; - - private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) - { - Key keyMod = new Key (); - if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) - keyMod = Key.ShiftMask; - if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) - keyMod |= Key.CtrlMask; - if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) - keyMod |= Key.AltMask; - - return keyMod != Key.Null ? keyMod | key : key; + if (keyInfo.KeyChar != 0) { + return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar)); } - Action keyDownHandler; - Action keyHandler; - Action keyUpHandler; - private CursorVisibility savedCursorVisibility; + return (Key)(0xffffffff); + } - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) - { - this.keyDownHandler = keyDownHandler; - this.keyHandler = keyHandler; - this.keyUpHandler = keyUpHandler; + KeyModifiers keyModifiers; - // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - (mainLoop.Driver as FakeMainLoop).KeyPressed += (consoleKey) => ProcessInput (consoleKey); + private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) + { + Key keyMod = new Key (); + if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) { + keyMod = Key.ShiftMask; } - - void ProcessInput (ConsoleKeyInfo consoleKey) - { - if (consoleKey.Key == ConsoleKey.Packet) { - consoleKey = FromVKPacketToKConsoleKeyInfo (consoleKey); - } - keyModifiers = new KeyModifiers (); - if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) { - keyModifiers.Shift = true; - } - if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) { - keyModifiers.Alt = true; - } - if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) { - keyModifiers.Ctrl = true; - } - var map = MapKey (consoleKey); - if (map == (Key)0xffffffff) { - if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - keyDownHandler (new KeyEvent (map, keyModifiers)); - keyUpHandler (new KeyEvent (map, keyModifiers)); - } - return; - } - - keyDownHandler (new KeyEvent (map, keyModifiers)); - keyHandler (new KeyEvent (map, keyModifiers)); - keyUpHandler (new KeyEvent (map, keyModifiers)); + if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) { + keyMod |= Key.CtrlMask; + } + if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) { + keyMod |= Key.AltMask; } - /// - public override bool GetCursorVisibility (out CursorVisibility visibility) - { - visibility = FakeConsole.CursorVisible - ? CursorVisibility.Default - : CursorVisibility.Invisible; + return keyMod != Key.Null ? keyMod | key : key; + } - return FakeConsole.CursorVisible; - } + Action _keyDownHandler; + Action _keyHandler; + Action _keyUpHandler; + private CursorVisibility _savedCursorVisibility; - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - savedCursorVisibility = visibility; - return FakeConsole.CursorVisible = visibility == CursorVisibility.Default; - } + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) + { + _keyDownHandler = keyDownHandler; + _keyHandler = keyHandler; + _keyUpHandler = keyUpHandler; - /// - public override bool EnsureCursorVisibility () - { - if (!(ccol >= 0 && crow >= 0 && ccol < Cols && crow < Rows)) { - GetCursorVisibility (out CursorVisibility cursorVisibility); - savedCursorVisibility = cursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - return false; - } + // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called + (mainLoop.MainLoopDriver as FakeMainLoop).KeyPressed += (consoleKey) => ProcessInput (consoleKey); + } - SetCursorVisibility (savedCursorVisibility); - return FakeConsole.CursorVisible; + void ProcessInput (ConsoleKeyInfo consoleKey) + { + if (consoleKey.Key == ConsoleKey.Packet) { + consoleKey = FromVKPacketToKConsoleKeyInfo (consoleKey); } - - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) - { - ProcessInput (new ConsoleKeyInfo (keyChar, key, shift, alt, control)); + keyModifiers = new KeyModifiers (); + if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) { + keyModifiers.Shift = true; } - - public void SetBufferSize (int width, int height) - { - FakeConsole.SetBufferSize (width, height); - cols = width; - rows = height; - if (!EnableConsoleScrolling) { - SetWindowSize (width, height); - } - ProcessResize (); + if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) { + keyModifiers.Alt = true; } - - public void SetWindowSize (int width, int height) - { - FakeConsole.SetWindowSize (width, height); - if (!EnableConsoleScrolling) { - if (width != cols || height != rows) { - SetBufferSize (width, height); - cols = width; - rows = height; - } - } - ProcessResize (); + if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) { + keyModifiers.Ctrl = true; } - - public void SetWindowPosition (int left, int top) - { - if (EnableConsoleScrolling) { - this.left = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0); - this.top = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0); - } else if (this.left > 0 || this.top > 0) { - this.left = 0; - this.top = 0; + var map = MapKey (consoleKey); + if (map == (Key)0xffffffff) { + if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + _keyDownHandler (new KeyEvent (map, keyModifiers)); + _keyUpHandler (new KeyEvent (map, keyModifiers)); } - FakeConsole.SetWindowPosition (this.left, this.top); + return; } - void ProcessResize () - { - ResizeScreen (); - UpdateOffScreen (); - TerminalResized?.Invoke (); - } + _keyDownHandler (new KeyEvent (map, keyModifiers)); + _keyHandler (new KeyEvent (map, keyModifiers)); + _keyUpHandler (new KeyEvent (map, keyModifiers)); + } - public override void ResizeScreen () - { - if (!EnableConsoleScrolling) { - if (FakeConsole.WindowHeight > 0) { - // Can raise an exception while is still resizing. - try { -#pragma warning disable CA1416 - FakeConsole.CursorTop = 0; - FakeConsole.CursorLeft = 0; - FakeConsole.WindowTop = 0; - FakeConsole.WindowLeft = 0; -#pragma warning restore CA1416 - } catch (System.IO.IOException) { - return; - } catch (ArgumentOutOfRangeException) { - return; - } - } - } else { - try { -#pragma warning disable CA1416 - FakeConsole.WindowLeft = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0); - FakeConsole.WindowTop = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0); -#pragma warning restore CA1416 - } catch (Exception) { - return; - } - } + /// + public override bool GetCursorVisibility (out CursorVisibility visibility) + { + visibility = FakeConsole.CursorVisible + ? CursorVisibility.Default + : CursorVisibility.Invisible; - Clip = new Rect (0, 0, Cols, Rows); - } + return FakeConsole.CursorVisible; + } - public override void UpdateOffScreen () - { - contents = new int [Rows, Cols, 3]; - dirtyLine = new bool [Rows]; + /// + public override bool SetCursorVisibility (CursorVisibility visibility) + { + _savedCursorVisibility = visibility; + return FakeConsole.CursorVisible = visibility == CursorVisibility.Default; + } - // Can raise an exception while is still resizing. - try { - for (int row = 0; row < rows; row++) { - for (int c = 0; c < cols; c++) { - contents [row, c, 0] = ' '; - contents [row, c, 1] = (ushort)Colors.TopLevel.Normal; - contents [row, c, 2] = 0; - dirtyLine [row] = true; - } - } - } catch (IndexOutOfRangeException) { } + /// + public override bool EnsureCursorVisibility () + { + if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) { + GetCursorVisibility (out CursorVisibility cursorVisibility); + _savedCursorVisibility = cursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + return false; } - public override bool GetColors (int value, out Color foreground, out Color background) - { - bool hasColor = false; - foreground = default; - background = default; - IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (s => (int)s); - if (values.Contains (value & 0xffff)) { - hasColor = true; - background = (Color)(ConsoleColor)(value & 0xffff); - } - if (values.Contains ((value >> 16) & 0xffff)) { - hasColor = true; - foreground = (Color)(ConsoleColor)((value >> 16) & 0xffff); - } - return hasColor; - } + SetCursorVisibility (_savedCursorVisibility); + return FakeConsole.CursorVisible; + } - #region Unused - public override void UpdateCursor () - { - if (!EnsureCursorVisibility ()) - return; + public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) + { + ProcessInput (new ConsoleKeyInfo (keyChar, key, shift, alt, control)); + } - // Prevents the exception of size changing during resizing. - try { - if (ccol >= 0 && ccol < FakeConsole.BufferWidth && crow >= 0 && crow < FakeConsole.BufferHeight) { - FakeConsole.SetCursorPosition (ccol, crow); - } - } catch (System.IO.IOException) { - } catch (ArgumentOutOfRangeException) { - } - } + public void SetBufferSize (int width, int height) + { + FakeConsole.SetBufferSize (width, height); + Cols = width; + Rows = height; + SetWindowSize (width, height); + ProcessResize (); + } - public override void StartReportingMouseMoves () - { + public void SetWindowSize (int width, int height) + { + FakeConsole.SetWindowSize (width, height); + if (width != Cols || height != Rows) { + SetBufferSize (width, height); + Cols = width; + Rows = height; } + ProcessResize (); + } - public override void StopReportingMouseMoves () - { + public void SetWindowPosition (int left, int top) + { + if (Left > 0 || Top > 0) { + Left = 0; + Top = 0; } + FakeConsole.SetWindowPosition (Left, Top); + } - public override void Suspend () - { - } + void ProcessResize () + { + ResizeScreen (); + ClearContents (); + TerminalResized?.Invoke (); + } - public override void SetColors (ConsoleColor foreground, ConsoleColor background) - { + public virtual void ResizeScreen () + { + if (FakeConsole.WindowHeight > 0) { + // Can raise an exception while is still resizing. + try { + FakeConsole.CursorTop = 0; + FakeConsole.CursorLeft = 0; + FakeConsole.WindowTop = 0; + FakeConsole.WindowLeft = 0; + } catch (System.IO.IOException) { + return; + } catch (ArgumentOutOfRangeException) { + return; + } } - public override void SetColors (short foregroundColorId, short backgroundColorId) - { - throw new NotImplementedException (); - } + Clip = new Rect (0, 0, Cols, Rows); + } - public override void CookMouse () - { + public override void UpdateCursor () + { + if (!EnsureCursorVisibility ()) { + return; } - public override void UncookMouse () - { + // Prevents the exception of size changing during resizing. + try { + // BUGBUG: Why is this using BufferWidth/Height and now Cols/Rows? + if (Col >= 0 && Col < FakeConsole.BufferWidth && Row >= 0 && Row < FakeConsole.BufferHeight) { + FakeConsole.SetCursorPosition (Col, Row); + } + } catch (System.IO.IOException) { + } catch (ArgumentOutOfRangeException) { } + } - #endregion + #region Not Implemented + public override void Suspend () + { + throw new NotImplementedException (); + } + #endregion - public class FakeClipboard : ClipboardBase { - public Exception FakeException = null; + public class FakeClipboard : ClipboardBase { + public Exception FakeException = null; - string contents = string.Empty; + string _contents = string.Empty; - bool isSupportedAlwaysFalse = false; + bool _isSupportedAlwaysFalse = false; - public override bool IsSupported => !isSupportedAlwaysFalse; + public override bool IsSupported => !_isSupportedAlwaysFalse; - public FakeClipboard (bool fakeClipboardThrowsNotSupportedException = false, bool isSupportedAlwaysFalse = false) - { - this.isSupportedAlwaysFalse = isSupportedAlwaysFalse; - if (fakeClipboardThrowsNotSupportedException) { - FakeException = new NotSupportedException ("Fake clipboard exception"); - } + public FakeClipboard (bool fakeClipboardThrowsNotSupportedException = false, bool isSupportedAlwaysFalse = false) + { + _isSupportedAlwaysFalse = isSupportedAlwaysFalse; + if (fakeClipboardThrowsNotSupportedException) { + FakeException = new NotSupportedException ("Fake clipboard exception"); } + } - protected override string GetClipboardDataImpl () - { - if (FakeException != null) { - throw FakeException; - } - return contents; + protected override string GetClipboardDataImpl () + { + if (FakeException != null) { + throw FakeException; } + return _contents; + } - protected override void SetClipboardDataImpl (string text) - { - if (FakeException != null) { - throw FakeException; - } - contents = text; + protected override void SetClipboardDataImpl (string text) + { + if (text == null) { + throw new ArgumentNullException (nameof (text)); + } + if (FakeException != null) { + throw FakeException; } + _contents = text; } + } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - } } \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index 06a08a0698..69b9686603 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -1,98 +1,37 @@ using System; -using System.Threading; -namespace Terminal.Gui { - /// - /// 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. - /// - /// - /// This implementation is used for FakeDriver. - /// - public class FakeMainLoop : IMainLoopDriver { - AutoResetEvent keyReady = new AutoResetEvent (false); - AutoResetEvent waitForProbe = new AutoResetEvent (false); - ConsoleKeyInfo? keyResult = null; - MainLoop mainLoop; - //Func consoleKeyReaderFn = () => ; +namespace Terminal.Gui; - /// - /// Invoked when a Key is pressed. - /// - public Action KeyPressed; +internal class FakeMainLoop : IMainLoopDriver { - /// - /// Creates an instance of the FakeMainLoop. is not used. - /// - /// - public FakeMainLoop (ConsoleDriver consoleDriver = null) - { - // consoleDriver is not needed/used in FakeConsole - } - - void MockKeyReader () - { - while (true) { - waitForProbe.WaitOne (); - keyResult = FakeConsole.ReadKey (true); - keyReady.Set (); - } - } - - void IMainLoopDriver.Setup (MainLoop mainLoop) - { - this.mainLoop = mainLoop; - Thread readThread = new Thread (MockKeyReader); - readThread.Start (); - } - - void IMainLoopDriver.Wakeup () - { - } - - bool IMainLoopDriver.EventsPending (bool wait) - { - keyResult = null; - waitForProbe.Set (); - - if (CheckTimers (wait, out var waitTimeout)) { - return true; - } + public Action KeyPressed; - keyReady.WaitOne (waitTimeout); - return keyResult.HasValue; - } - - bool CheckTimers (bool wait, out int waitTimeout) - { - long now = DateTime.UtcNow.Ticks; - - if (mainLoop.timeouts.Count > 0) { - waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); - if (waitTimeout < 0) - return true; - } else { - waitTimeout = -1; - } + public FakeMainLoop (ConsoleDriver consoleDriver = null) + { + // No implementation needed for FakeMainLoop + } - if (!wait) - waitTimeout = 0; + public void Setup (MainLoop mainLoop) + { + // No implementation needed for FakeMainLoop + } - int ic; - lock (mainLoop.idleHandlers) { - ic = mainLoop.idleHandlers.Count; - } + public void Wakeup () + { + // No implementation needed for FakeMainLoop + } - return ic > 0; - } + public bool EventsPending (bool wait) + { + // Always return true for FakeMainLoop + return true; + } - void IMainLoopDriver.Iteration () - { - if (keyResult.HasValue) { - KeyPressed?.Invoke (keyResult.Value); - keyResult = null; - } + public void Iteration () + { + if (FakeConsole.MockKeyPresses.Count > 0) { + KeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ()); } } -} \ No newline at end of file +} + diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 6d9fa4930a..2253765275 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -13,1638 +13,1361 @@ using System.Threading.Tasks; using System.Text; -namespace Terminal.Gui { - internal class NetWinVTConsole { - IntPtr InputHandle, OutputHandle, ErrorHandle; - uint originalInputConsoleMode, originalOutputConsoleMode, originalErrorConsoleMode; +namespace Terminal.Gui; +internal class NetWinVTConsole { + IntPtr _inputHandle, _outputHandle, _errorHandle; + uint _originalInputConsoleMode, _originalOutputConsoleMode, _originalErrorConsoleMode; - public NetWinVTConsole () - { - InputHandle = GetStdHandle (STD_INPUT_HANDLE); - if (!GetConsoleMode (InputHandle, out uint mode)) { - throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}."); - } - originalInputConsoleMode = mode; - if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) { - mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; - if (!SetConsoleMode (InputHandle, mode)) { - throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}."); - } - } - - OutputHandle = GetStdHandle (STD_OUTPUT_HANDLE); - if (!GetConsoleMode (OutputHandle, out mode)) { - throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}."); - } - originalOutputConsoleMode = mode; - if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) { - mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; - if (!SetConsoleMode (OutputHandle, mode)) { - throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}."); - } + public NetWinVTConsole () + { + _inputHandle = GetStdHandle (STD_INPUT_HANDLE); + if (!GetConsoleMode (_inputHandle, out uint mode)) { + throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}."); + } + _originalInputConsoleMode = mode; + if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) { + mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + if (!SetConsoleMode (_inputHandle, mode)) { + throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}."); } + } - ErrorHandle = GetStdHandle (STD_ERROR_HANDLE); - if (!GetConsoleMode (ErrorHandle, out mode)) { - throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}."); - } - originalErrorConsoleMode = mode; - if ((mode & (DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) { - mode |= DISABLE_NEWLINE_AUTO_RETURN; - if (!SetConsoleMode (ErrorHandle, mode)) { - throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}."); - } + _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); + if (!GetConsoleMode (_outputHandle, out mode)) { + throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}."); + } + _originalOutputConsoleMode = mode; + if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + if (!SetConsoleMode (_outputHandle, mode)) { + throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}."); } } - public void Cleanup () - { - if (!SetConsoleMode (InputHandle, originalInputConsoleMode)) { - throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}."); - } - if (!SetConsoleMode (OutputHandle, originalOutputConsoleMode)) { - throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}."); - } - if (!SetConsoleMode (ErrorHandle, originalErrorConsoleMode)) { - throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}."); + _errorHandle = GetStdHandle (STD_ERROR_HANDLE); + if (!GetConsoleMode (_errorHandle, out mode)) { + throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}."); + } + _originalErrorConsoleMode = mode; + if ((mode & (DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) { + mode |= DISABLE_NEWLINE_AUTO_RETURN; + if (!SetConsoleMode (_errorHandle, mode)) { + throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}."); } } + } - const int STD_INPUT_HANDLE = -10; - const int STD_OUTPUT_HANDLE = -11; - const int STD_ERROR_HANDLE = -12; - - // Input modes. - const uint ENABLE_PROCESSED_INPUT = 1; - const uint ENABLE_LINE_INPUT = 2; - const uint ENABLE_ECHO_INPUT = 4; - const uint ENABLE_WINDOW_INPUT = 8; - const uint ENABLE_MOUSE_INPUT = 16; - const uint ENABLE_INSERT_MODE = 32; - const uint ENABLE_QUICK_EDIT_MODE = 64; - const uint ENABLE_EXTENDED_FLAGS = 128; - const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512; - - // Output modes. - const uint ENABLE_PROCESSED_OUTPUT = 1; - const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2; - const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; - const uint DISABLE_NEWLINE_AUTO_RETURN = 8; - const uint ENABLE_LVB_GRID_WORLDWIDE = 10; - - [DllImport ("kernel32.dll", SetLastError = true)] - static extern IntPtr GetStdHandle (int nStdHandle); - - [DllImport ("kernel32.dll")] - static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode); - - [DllImport ("kernel32.dll")] - static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); - - [DllImport ("kernel32.dll")] - static extern uint GetLastError (); + public void Cleanup () + { + if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode)) { + throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}."); + } + if (!SetConsoleMode (_outputHandle, _originalOutputConsoleMode)) { + throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}."); + } + if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode)) { + throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}."); + } } - internal class NetEvents { - ManualResetEventSlim inputReady = new ManualResetEventSlim (false); - ManualResetEventSlim waitForStart = new ManualResetEventSlim (false); - ManualResetEventSlim winChange = new ManualResetEventSlim (false); - Queue inputResultQueue = new Queue (); - ConsoleDriver consoleDriver; - volatile ConsoleKeyInfo [] cki = null; - static volatile bool isEscSeq; - int lastWindowHeight; - bool stopTasks; + const int STD_INPUT_HANDLE = -10; + const int STD_OUTPUT_HANDLE = -11; + const int STD_ERROR_HANDLE = -12; + + // Input modes. + const uint ENABLE_PROCESSED_INPUT = 1; + const uint ENABLE_LINE_INPUT = 2; + const uint ENABLE_ECHO_INPUT = 4; + const uint ENABLE_WINDOW_INPUT = 8; + const uint ENABLE_MOUSE_INPUT = 16; + const uint ENABLE_INSERT_MODE = 32; + const uint ENABLE_QUICK_EDIT_MODE = 64; + const uint ENABLE_EXTENDED_FLAGS = 128; + const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512; + + // Output modes. + const uint ENABLE_PROCESSED_OUTPUT = 1; + const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2; + const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; + const uint DISABLE_NEWLINE_AUTO_RETURN = 8; + const uint ENABLE_LVB_GRID_WORLDWIDE = 10; + + [DllImport ("kernel32.dll", SetLastError = true)] + static extern IntPtr GetStdHandle (int nStdHandle); + + [DllImport ("kernel32.dll")] + static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode); + + [DllImport ("kernel32.dll")] + static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); + + [DllImport ("kernel32.dll")] + static extern uint GetLastError (); +} + +internal class NetEvents { + ManualResetEventSlim _inputReady = new ManualResetEventSlim (false); + ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false); + ManualResetEventSlim _winChange = new ManualResetEventSlim (false); + Queue _inputResultQueue = new Queue (); + ConsoleDriver _consoleDriver; + volatile ConsoleKeyInfo [] _cki = null; + volatile static bool _isEscSeq; + bool _stopTasks; #if PROCESS_REQUEST - bool neededProcessRequest; + bool _neededProcessRequest; #endif - public bool IsTerminalWithOptions { get; set; } - public EscSeqReqProc EscSeqReqProc { get; } = new EscSeqReqProc (); + public EscSeqRequests EscSeqRequests { get; } = new EscSeqRequests (); - public NetEvents (ConsoleDriver consoleDriver) - { - if (consoleDriver == null) { - throw new ArgumentNullException ("Console driver instance must be provided."); - } - this.consoleDriver = consoleDriver; - Task.Run (ProcessInputResultQueue); - Task.Run (CheckWinChange); - } + public NetEvents (ConsoleDriver consoleDriver) + { + _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver)); + Task.Run (ProcessInputResultQueue); + Task.Run (CheckWindowSizeChange); + } - internal void StopTasks () - { - stopTasks = true; - } + internal void StopTasks () + { + _stopTasks = true; + } - public InputResult? ReadConsoleInput () - { - while (true) { - if (stopTasks) { - return null; - } - waitForStart.Set (); - winChange.Set (); + public InputResult? ReadConsoleInput () + { + while (true) { + if (_stopTasks) { + return null; + } + _waitForStart.Set (); + _winChange.Set (); - if (inputResultQueue.Count == 0) { - inputReady.Wait (); - inputReady.Reset (); - } + if (_inputResultQueue.Count == 0) { + _inputReady.Wait (); + _inputReady.Reset (); + } #if PROCESS_REQUEST - neededProcessRequest = false; + _neededProcessRequest = false; #endif - if (inputResultQueue.Count > 0) { - return inputResultQueue.Dequeue (); - } - } - } - - void ProcessInputResultQueue () - { - while (true) { - waitForStart.Wait (); - waitForStart.Reset (); - - if (inputResultQueue.Count == 0) { - GetConsoleKey (); - } - - inputReady.Set (); + if (_inputResultQueue.Count > 0) { + return _inputResultQueue.Dequeue (); } } + } - void GetConsoleKey () - { - ConsoleKey key = 0; - ConsoleModifiers mod = 0; - ConsoleKeyInfo newConsoleKeyInfo = default; - - while (true) { - ConsoleKeyInfo consoleKeyInfo = Console.ReadKey (true); - if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !isEscSeq) - || (consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq)) { - if (cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq) { - cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0, - false, false, false), cki); - } - isEscSeq = true; - newConsoleKeyInfo = consoleKeyInfo; - cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki); - if (!Console.KeyAvailable) { - DecodeEscSeq (ref newConsoleKeyInfo, ref key, cki, ref mod); - cki = null; - isEscSeq = false; + void ProcessInputResultQueue () + { + while (true) { + _waitForStart.Wait (); + _waitForStart.Reset (); + + if (_inputResultQueue.Count == 0) { + ConsoleKey key = 0; + ConsoleModifiers mod = 0; + ConsoleKeyInfo newConsoleKeyInfo = default; + + while (true) { + ConsoleKeyInfo consoleKeyInfo = Console.ReadKey (true); + if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !_isEscSeq) + || (consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq)) { + if (_cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq) { + _cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0, + false, false, false), _cki); + } + _isEscSeq = true; + newConsoleKeyInfo = consoleKeyInfo; + _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki); + if (Console.KeyAvailable) continue; + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + break; + } else if (consoleKeyInfo.KeyChar == (char)Key.Esc && _isEscSeq && _cki != null) { + if (_cki != null) { + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + } + break; + } else { + _inputResultQueue.Enqueue (new InputResult { + EventType = EventType.Key, + ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo) + }); + _isEscSeq = false; break; } - } else if (consoleKeyInfo.KeyChar == (char)Key.Esc && isEscSeq && cki != null) { - DecodeEscSeq (ref newConsoleKeyInfo, ref key, cki, ref mod); - cki = null; - break; - } else { - GetConsoleInputType (consoleKeyInfo); - isEscSeq = false; - break; } } - } - void CheckWinChange () - { - while (true) { - if (stopTasks) { - return; - } - winChange.Wait (); - winChange.Reset (); - WaitWinChange (); - inputReady.Set (); - } + _inputReady.Set (); } + } - void WaitWinChange () + void CheckWindowSizeChange () + { + void RequestWindowSize () { while (true) { - // HACK: Sleep for 10ms to mitigate high CPU usage (see issue #1502). 10ms was tested to address the problem, but may not be correct. - Thread.Sleep (10); - if (stopTasks) { - return; - } - switch (IsTerminalWithOptions) { - case false: - int buffHeight, buffWidth; - if (((NetDriver)consoleDriver).IsWinPlatform) { - buffHeight = Math.Max (Console.BufferHeight, 0); - buffWidth = Math.Max (Console.BufferWidth, 0); - } else { - buffHeight = consoleDriver.Rows; - buffWidth = consoleDriver.Cols; - } - if (IsWinChanged ( - Math.Max (Console.WindowHeight, 0), - Math.Max (Console.WindowWidth, 0), - buffHeight, - buffWidth)) { + // Wait for a while then check if screen has changed sizes + Task.Delay (500).Wait (); - return; - } - break; - case true: - //Request the size of the text area in characters. - EscSeqReqProc.Add ("t"); - Console.Out.Write ("\x1b[18t"); - break; + if (_stopTasks) { + return; } - } - } - - bool IsWinChanged (int winHeight, int winWidth, int buffHeight, int buffWidth) - { - if (!consoleDriver.EnableConsoleScrolling) { - if (winWidth != consoleDriver.Cols || winHeight != consoleDriver.Rows) { - var w = Math.Max (winWidth, 0); - var h = Math.Max (winHeight, 0); - GetWindowSizeEvent (new Size (w, h)); - return true; + int buffHeight, buffWidth; + if (((NetDriver)_consoleDriver).IsWinPlatform) { + buffHeight = Math.Max (Console.BufferHeight, 0); + buffWidth = Math.Max (Console.BufferWidth, 0); + } else { + buffHeight = _consoleDriver.Rows; + buffWidth = _consoleDriver.Cols; } - } else { - if (winWidth != consoleDriver.Cols || winHeight != lastWindowHeight - || buffWidth != consoleDriver.Cols || buffHeight != consoleDriver.Rows) { + if (EnqueueWindowSizeEvent ( + Math.Max (Console.WindowHeight, 0), + Math.Max (Console.WindowWidth, 0), + buffHeight, + buffWidth)) { - lastWindowHeight = Math.Max (winHeight, 0); - GetWindowSizeEvent (new Size (winWidth, lastWindowHeight)); - return true; + return; } } - return false; } - void GetWindowSizeEvent (Size size) - { - WindowSizeEvent windowSizeEvent = new WindowSizeEvent () { - Size = size - }; - - inputResultQueue.Enqueue (new InputResult () { - EventType = EventType.WindowSize, - WindowSizeEvent = windowSizeEvent - }); - } - - void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo) - { - InputResult inputResult = new InputResult { - EventType = EventType.Key - }; - MouseEvent mouseEvent = new MouseEvent (); - ConsoleKeyInfo newConsoleKeyInfo = EscSeqUtils.GetConsoleInputKey (consoleKeyInfo); - if (inputResult.EventType == EventType.Key) { - inputResult.ConsoleKeyInfo = newConsoleKeyInfo; - } else { - inputResult.MouseEvent = mouseEvent; - } - - inputResultQueue.Enqueue (inputResult); - } - - void DecodeEscSeq (ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod) - { - string c1Control, code, terminating; - string [] values; - // isKeyMouse is true if it's CSI<, false otherwise - bool isKeyMouse; - bool isReq; - List mouseFlags; - Point pos; - EscSeqUtils.DecodeEscSeq (EscSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed); - - if (isKeyMouse) { - foreach (var mf in mouseFlags) { - GetMouseEvent (MapMouseFlags (mf), pos); - } + while (true) { + if (_stopTasks) { return; - } else if (isReq) { - GetRequestEvent (c1Control, code, values, terminating); - return; - } - InputResult inputResult = new InputResult { - EventType = EventType.Key, - ConsoleKeyInfo = newConsoleKeyInfo - }; - - inputResultQueue.Enqueue (inputResult); - } - - void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos) - { - GetMouseEvent (MapMouseFlags (mouseFlag), pos); - } - - MouseButtonState MapMouseFlags (MouseFlags mouseFlags) - { - MouseButtonState mbs = default; - foreach (var flag in Enum.GetValues (mouseFlags.GetType ())) { - if (mouseFlags.HasFlag ((MouseFlags)flag)) { - switch (flag) { - case MouseFlags.Button1Pressed: - mbs |= MouseButtonState.Button1Pressed; - break; - case MouseFlags.Button1Released: - mbs |= MouseButtonState.Button1Released; - break; - case MouseFlags.Button1Clicked: - mbs |= MouseButtonState.Button1Clicked; - break; - case MouseFlags.Button1DoubleClicked: - mbs |= MouseButtonState.Button1DoubleClicked; - break; - case MouseFlags.Button1TripleClicked: - mbs |= MouseButtonState.Button1TripleClicked; - break; - case MouseFlags.Button2Pressed: - mbs |= MouseButtonState.Button2Pressed; - break; - case MouseFlags.Button2Released: - mbs |= MouseButtonState.Button2Released; - break; - case MouseFlags.Button2Clicked: - mbs |= MouseButtonState.Button2Clicked; - break; - case MouseFlags.Button2DoubleClicked: - mbs |= MouseButtonState.Button2DoubleClicked; - break; - case MouseFlags.Button2TripleClicked: - mbs |= MouseButtonState.Button2TripleClicked; - break; - case MouseFlags.Button3Pressed: - mbs |= MouseButtonState.Button3Pressed; - break; - case MouseFlags.Button3Released: - mbs |= MouseButtonState.Button3Released; - break; - case MouseFlags.Button3Clicked: - mbs |= MouseButtonState.Button3Clicked; - break; - case MouseFlags.Button3DoubleClicked: - mbs |= MouseButtonState.Button3DoubleClicked; - break; - case MouseFlags.Button3TripleClicked: - mbs |= MouseButtonState.Button3TripleClicked; - break; - case MouseFlags.WheeledUp: - mbs |= MouseButtonState.ButtonWheeledUp; - break; - case MouseFlags.WheeledDown: - mbs |= MouseButtonState.ButtonWheeledDown; - break; - case MouseFlags.WheeledLeft: - mbs |= MouseButtonState.ButtonWheeledLeft; - break; - case MouseFlags.WheeledRight: - mbs |= MouseButtonState.ButtonWheeledRight; - break; - case MouseFlags.Button4Pressed: - mbs |= MouseButtonState.Button4Pressed; - break; - case MouseFlags.Button4Released: - mbs |= MouseButtonState.Button4Released; - break; - case MouseFlags.Button4Clicked: - mbs |= MouseButtonState.Button4Clicked; - break; - case MouseFlags.Button4DoubleClicked: - mbs |= MouseButtonState.Button4DoubleClicked; - break; - case MouseFlags.Button4TripleClicked: - mbs |= MouseButtonState.Button4TripleClicked; - break; - case MouseFlags.ButtonShift: - mbs |= MouseButtonState.ButtonShift; - break; - case MouseFlags.ButtonCtrl: - mbs |= MouseButtonState.ButtonCtrl; - break; - case MouseFlags.ButtonAlt: - mbs |= MouseButtonState.ButtonAlt; - break; - case MouseFlags.ReportMousePosition: - mbs |= MouseButtonState.ReportMousePosition; - break; - case MouseFlags.AllEvents: - mbs |= MouseButtonState.AllEvents; - break; - } - } } - return mbs; + _winChange.Wait (); + _winChange.Reset (); + RequestWindowSize (); + _inputReady.Set (); } + } - Point lastCursorPosition; + /// + /// Enqueue a window size event if the window size has changed. + /// + /// + /// + /// + /// + /// + bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth) + { + if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) return false; + var w = Math.Max (winWidth, 0); + var h = Math.Max (winHeight, 0); + _inputResultQueue.Enqueue (new InputResult () { + EventType = EventType.WindowSize, + WindowSizeEvent = new WindowSizeEvent () { + Size = new Size (w, h) + } + }); + return true; + } - void GetRequestEvent (string c1Control, string code, string [] values, string terminating) - { - EventType eventType = new EventType (); - switch (terminating) { - case "R": // Reports cursor position as CSI r ; c R - Point point = new Point { - X = int.Parse (values [1]) - 1, - Y = int.Parse (values [0]) - 1 - }; - if (lastCursorPosition.Y != point.Y) { - lastCursorPosition = point; - eventType = EventType.WindowPosition; - var winPositionEv = new WindowPositionEvent () { - CursorPosition = point - }; - inputResultQueue.Enqueue (new InputResult () { - EventType = eventType, - WindowPositionEvent = winPositionEv - }); - } else { - return; - } - break; - case "c": - try { - var parent = EscSeqUtils.GetParentProcess (Process.GetCurrentProcess ()); - if (parent == null) { Debug.WriteLine ("Not supported!"); } - } catch (Exception ex) { - Debug.WriteLine (ex.Message); - } + // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event) + void ProcessRequestResponse (ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod) + { + // isMouse is true if it's CSI<, false otherwise + EscSeqUtils.DecodeEscSeq (EscSeqRequests, ref newConsoleKeyInfo, ref key, cki, ref mod, + out var c1Control, out var code, out var values, out var terminating, + out var isMouse, out var mouseFlags, + out var pos, out var isReq, + (f, p) => HandleMouseEvent (MapMouseFlags (f), p)); + + if (isMouse) { + foreach (var mf in mouseFlags) { + HandleMouseEvent (MapMouseFlags (mf), pos); + } + return; + } else if (isReq) { + HandleRequestResponseEvent (c1Control, code, values, terminating); + return; + } + HandleKeyboardEvent (newConsoleKeyInfo); + } - if (c1Control == "CSI" && values.Length == 2 - && values [0] == "1" && values [1] == "0") { - // Reports CSI?1;0c ("VT101 with No Options") - IsTerminalWithOptions = false; - } else { - IsTerminalWithOptions = true; - } - break; - case "t": - switch (values [0]) { - case "8": - IsWinChanged ( - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0), - Math.Max (int.Parse (values [1]), 0), - Math.Max (int.Parse (values [2]), 0)); + MouseButtonState MapMouseFlags (MouseFlags mouseFlags) + { + MouseButtonState mbs = default; + foreach (var flag in Enum.GetValues (mouseFlags.GetType ())) { + if (mouseFlags.HasFlag ((MouseFlags)flag)) { + switch (flag) { + case MouseFlags.Button1Pressed: + mbs |= MouseButtonState.Button1Pressed; + break; + case MouseFlags.Button1Released: + mbs |= MouseButtonState.Button1Released; + break; + case MouseFlags.Button1Clicked: + mbs |= MouseButtonState.Button1Clicked; + break; + case MouseFlags.Button1DoubleClicked: + mbs |= MouseButtonState.Button1DoubleClicked; + break; + case MouseFlags.Button1TripleClicked: + mbs |= MouseButtonState.Button1TripleClicked; + break; + case MouseFlags.Button2Pressed: + mbs |= MouseButtonState.Button2Pressed; + break; + case MouseFlags.Button2Released: + mbs |= MouseButtonState.Button2Released; + break; + case MouseFlags.Button2Clicked: + mbs |= MouseButtonState.Button2Clicked; + break; + case MouseFlags.Button2DoubleClicked: + mbs |= MouseButtonState.Button2DoubleClicked; + break; + case MouseFlags.Button2TripleClicked: + mbs |= MouseButtonState.Button2TripleClicked; break; - default: - SetRequestedEvent (c1Control, code, values, terminating); + case MouseFlags.Button3Pressed: + mbs |= MouseButtonState.Button3Pressed; + break; + case MouseFlags.Button3Released: + mbs |= MouseButtonState.Button3Released; + break; + case MouseFlags.Button3Clicked: + mbs |= MouseButtonState.Button3Clicked; + break; + case MouseFlags.Button3DoubleClicked: + mbs |= MouseButtonState.Button3DoubleClicked; + break; + case MouseFlags.Button3TripleClicked: + mbs |= MouseButtonState.Button3TripleClicked; + break; + case MouseFlags.WheeledUp: + mbs |= MouseButtonState.ButtonWheeledUp; + break; + case MouseFlags.WheeledDown: + mbs |= MouseButtonState.ButtonWheeledDown; + break; + case MouseFlags.WheeledLeft: + mbs |= MouseButtonState.ButtonWheeledLeft; + break; + case MouseFlags.WheeledRight: + mbs |= MouseButtonState.ButtonWheeledRight; + break; + case MouseFlags.Button4Pressed: + mbs |= MouseButtonState.Button4Pressed; + break; + case MouseFlags.Button4Released: + mbs |= MouseButtonState.Button4Released; + break; + case MouseFlags.Button4Clicked: + mbs |= MouseButtonState.Button4Clicked; + break; + case MouseFlags.Button4DoubleClicked: + mbs |= MouseButtonState.Button4DoubleClicked; + break; + case MouseFlags.Button4TripleClicked: + mbs |= MouseButtonState.Button4TripleClicked; + break; + case MouseFlags.ButtonShift: + mbs |= MouseButtonState.ButtonShift; + break; + case MouseFlags.ButtonCtrl: + mbs |= MouseButtonState.ButtonCtrl; + break; + case MouseFlags.ButtonAlt: + mbs |= MouseButtonState.ButtonAlt; + break; + case MouseFlags.ReportMousePosition: + mbs |= MouseButtonState.ReportMousePosition; + break; + case MouseFlags.AllEvents: + mbs |= MouseButtonState.AllEvents; break; } - break; - default: - SetRequestedEvent (c1Control, code, values, terminating); - break; } - - inputReady.Set (); } + return mbs; + } - void SetRequestedEvent (string c1Control, string code, string [] values, string terminating) - { - EventType eventType = EventType.RequestResponse; - var requestRespEv = new RequestResponseEvent () { - ResultTuple = (c1Control, code, values, terminating) - }; - inputResultQueue.Enqueue (new InputResult () { - EventType = eventType, - RequestResponseEvent = requestRespEv - }); - } + Point _lastCursorPosition; - void GetMouseEvent (MouseButtonState buttonState, Point pos) - { - MouseEvent mouseEvent = new MouseEvent () { - Position = pos, - ButtonState = buttonState, + void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + { + switch (terminating) { + // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed. + case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator: + Point point = new Point { + X = int.Parse (values [1]) - 1, + Y = int.Parse (values [0]) - 1 }; - - inputResultQueue.Enqueue (new InputResult () { - EventType = EventType.Mouse, - MouseEvent = mouseEvent - }); - - inputReady.Set (); - } - - public enum EventType { - Key = 1, - Mouse = 2, - WindowSize = 3, - WindowPosition = 4, - RequestResponse = 5 - } - - [Flags] - public enum MouseButtonState { - Button1Pressed = 0x1, - Button1Released = 0x2, - Button1Clicked = 0x4, - Button1DoubleClicked = 0x8, - Button1TripleClicked = 0x10, - Button2Pressed = 0x20, - Button2Released = 0x40, - Button2Clicked = 0x80, - Button2DoubleClicked = 0x100, - Button2TripleClicked = 0x200, - Button3Pressed = 0x400, - Button3Released = 0x800, - Button3Clicked = 0x1000, - Button3DoubleClicked = 0x2000, - Button3TripleClicked = 0x4000, - ButtonWheeledUp = 0x8000, - ButtonWheeledDown = 0x10000, - ButtonWheeledLeft = 0x20000, - ButtonWheeledRight = 0x40000, - Button4Pressed = 0x80000, - Button4Released = 0x100000, - Button4Clicked = 0x200000, - Button4DoubleClicked = 0x400000, - Button4TripleClicked = 0x800000, - ButtonShift = 0x1000000, - ButtonCtrl = 0x2000000, - ButtonAlt = 0x4000000, - ReportMousePosition = 0x8000000, - AllEvents = -1 - } - - public struct MouseEvent { - public Point Position; - public MouseButtonState ButtonState; - } - - public struct WindowSizeEvent { - public Size Size; - } - - public struct WindowPositionEvent { - public int Top; - public int Left; - public Point CursorPosition; - } - - public struct RequestResponseEvent { - public (string c1Control, string code, string [] values, string terminating) ResultTuple; - } - - public struct InputResult { - public EventType EventType; - public ConsoleKeyInfo ConsoleKeyInfo; - public MouseEvent MouseEvent; - public WindowSizeEvent WindowSizeEvent; - public WindowPositionEvent WindowPositionEvent; - public RequestResponseEvent RequestResponseEvent; - } - } - - internal class NetDriver : ConsoleDriver { - const int COLOR_BLACK = 30; - const int COLOR_RED = 31; - const int COLOR_GREEN = 32; - const int COLOR_YELLOW = 33; - const int COLOR_BLUE = 34; - const int COLOR_MAGENTA = 35; - const int COLOR_CYAN = 36; - const int COLOR_WHITE = 37; - const int COLOR_BRIGHT_BLACK = 90; - const int COLOR_BRIGHT_RED = 91; - const int COLOR_BRIGHT_GREEN = 92; - const int COLOR_BRIGHT_YELLOW = 93; - const int COLOR_BRIGHT_BLUE = 94; - const int COLOR_BRIGHT_MAGENTA = 95; - const int COLOR_BRIGHT_CYAN = 96; - const int COLOR_BRIGHT_WHITE = 97; - - int cols, rows, left, top; - - public override int Cols => cols; - public override int Rows => rows; - public override int Left => left; - public override int Top => top; - public override bool EnableConsoleScrolling { get; set; } - public NetWinVTConsole NetWinConsole { get; } - public bool IsWinPlatform { get; } - public override IClipboard Clipboard { get; } - public override int [,,] Contents => contents; - - int largestBufferHeight; - - public NetDriver () - { - var p = Environment.OSVersion.Platform; - if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { - IsWinPlatform = true; - NetWinConsole = new NetWinVTConsole (); - } - if (IsWinPlatform) { - Clipboard = new WindowsClipboard (); - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { - Clipboard = new MacOSXClipboard (); + if (_lastCursorPosition.Y != point.Y) { + _lastCursorPosition = point; + var eventType = EventType.WindowPosition; + var winPositionEv = new WindowPositionEvent () { + CursorPosition = point + }; + _inputResultQueue.Enqueue (new InputResult () { + EventType = eventType, + WindowPositionEvent = winPositionEv + }); } else { - if (CursesDriver.Is_WSL_Platform ()) { - Clipboard = new WSLClipboard (); - } else { - Clipboard = new CursesClipboard (); - } - } - } - - // The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag - int [,,] contents; - bool [] dirtyLine; - - static bool sync = false; - - // Current row, and current col, tracked by Move/AddCh only - int ccol, crow; - - public override void Move (int col, int row) - { - ccol = col; - crow = row; - } - - public override void AddRune (Rune rune) - { - if (contents.Length != Rows * Cols * 3) { return; } - rune = rune.MakePrintable (); - var runeWidth = rune.GetColumns (); - var validClip = IsValidContent (ccol, crow, Clip); - - if (validClip) { - if (runeWidth == 0 && ccol > 0) { - var r = contents [crow, ccol - 1, 0]; - var s = new string (new char [] { (char)r, (char)rune.Value }); - string sn; - if (!s.IsNormalized ()) { - sn = s.Normalize (); - } else { - sn = s; - } - var c = sn [0]; - contents [crow, ccol - 1, 0] = c; - contents [crow, ccol - 1, 1] = CurrentAttribute; - contents [crow, ccol - 1, 2] = 1; - - } else { - if (runeWidth < 2 && ccol > 0 - && ((Rune)(char)contents [crow, ccol - 1, 0]).GetColumns () > 1) { - - contents [crow, ccol - 1, 0] = (int)(uint)' '; - - } else if (runeWidth < 2 && ccol <= Clip.Right - 1 - && ((Rune)(char)contents [crow, ccol, 0]).GetColumns () > 1) { - - contents [crow, ccol + 1, 0] = (int)(uint)' '; - contents [crow, ccol + 1, 2] = 1; - - } - if (runeWidth > 1 && ccol == Clip.Right - 1) { - contents [crow, ccol, 0] = (int)(uint)' '; - } else { - contents [crow, ccol, 0] = (int)(uint)rune.Value; - } - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 1; - - } - dirtyLine [crow] = true; - } + break; - if (runeWidth < 0 || runeWidth > 0) { - ccol++; - } - - if (runeWidth > 1) { - if (validClip && ccol < Clip.Right) { - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 0; - } - ccol++; - } - - if (sync) { - UpdateScreen (); + case EscSeqUtils.CSI_ReportTerminalSizeInChars_Terminator: + switch (values [0]) { + case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue: + EnqueueWindowSizeEvent ( + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0), + Math.Max (int.Parse (values [1]), 0), + Math.Max (int.Parse (values [2]), 0)); + break; + default: + EnqueueRequestResponseEvent (c1Control, code, values, terminating); + break; } + break; + default: + EnqueueRequestResponseEvent (c1Control, code, values, terminating); + break; } - public override void AddStr (string str) - { - foreach (var rune in str.EnumerateRunes ()) - AddRune (rune); - } + _inputReady.Set (); + } - public override void End () - { - mainLoop.netEvents.StopTasks (); + void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating) + { + EventType eventType = EventType.RequestResponse; + var requestRespEv = new RequestResponseEvent () { + ResultTuple = (c1Control, code, values, terminating) + }; + _inputResultQueue.Enqueue (new InputResult () { + EventType = eventType, + RequestResponseEvent = requestRespEv + }); + } - if (IsWinPlatform) { - NetWinConsole.Cleanup (); - } + void HandleMouseEvent (MouseButtonState buttonState, Point pos) + { + MouseEvent mouseEvent = new MouseEvent () { + Position = pos, + ButtonState = buttonState, + }; - StopReportingMouseMoves (); - Console.ResetColor (); + _inputResultQueue.Enqueue (new InputResult () { + EventType = EventType.Mouse, + MouseEvent = mouseEvent + }); - //Disable alternative screen buffer. - Console.Out.Write ("\x1b[?1049l"); + _inputReady.Set (); + } - //Set cursor key to cursor. - Console.Out.Write ("\x1b[?25h"); + public enum EventType { + Key = 1, + Mouse = 2, + WindowSize = 3, + WindowPosition = 4, + RequestResponse = 5 + } - Console.Out.Close (); - } + [Flags] + public enum MouseButtonState { + Button1Pressed = 0x1, + Button1Released = 0x2, + Button1Clicked = 0x4, + Button1DoubleClicked = 0x8, + Button1TripleClicked = 0x10, + Button2Pressed = 0x20, + Button2Released = 0x40, + Button2Clicked = 0x80, + Button2DoubleClicked = 0x100, + Button2TripleClicked = 0x200, + Button3Pressed = 0x400, + Button3Released = 0x800, + Button3Clicked = 0x1000, + Button3DoubleClicked = 0x2000, + Button3TripleClicked = 0x4000, + ButtonWheeledUp = 0x8000, + ButtonWheeledDown = 0x10000, + ButtonWheeledLeft = 0x20000, + ButtonWheeledRight = 0x40000, + Button4Pressed = 0x80000, + Button4Released = 0x100000, + Button4Clicked = 0x200000, + Button4DoubleClicked = 0x400000, + Button4TripleClicked = 0x800000, + ButtonShift = 0x1000000, + ButtonCtrl = 0x2000000, + ButtonAlt = 0x4000000, + ReportMousePosition = 0x8000000, + AllEvents = -1 + } - public override Attribute MakeColor (Color foreground, Color background) - { - return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background); - } + public struct MouseEvent { + public Point Position; + public MouseButtonState ButtonState; + } - static Attribute MakeColor (ConsoleColor f, ConsoleColor b) - { - // Encode the colors into the int value. - return new Attribute ( - value: ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff), - foreground: (Color)f, - background: (Color)b - ); - } + public struct WindowSizeEvent { + public Size Size; + } - public override void Init (Action terminalResized) - { - TerminalResized = terminalResized; + public struct WindowPositionEvent { + public int Top; + public int Left; + public Point CursorPosition; + } - //Enable alternative screen buffer. - Console.Out.Write ("\x1b[?1049h"); + public struct RequestResponseEvent { + public (string c1Control, string code, string [] values, string terminating) ResultTuple; + } - //Set cursor key to application. - Console.Out.Write ("\x1b[?25l"); + public struct InputResult { + public EventType EventType; + public ConsoleKeyInfo ConsoleKeyInfo; + public MouseEvent MouseEvent; + public WindowSizeEvent WindowSizeEvent; + public WindowPositionEvent WindowPositionEvent; + public RequestResponseEvent RequestResponseEvent; + } - Console.TreatControlCAsInput = true; + void HandleKeyboardEvent (ConsoleKeyInfo cki) + { + InputResult inputResult = new InputResult { + EventType = EventType.Key, + ConsoleKeyInfo = cki + }; - if (EnableConsoleScrolling) { - largestBufferHeight = Console.BufferHeight; - } else { - largestBufferHeight = Console.WindowHeight; - } + _inputResultQueue.Enqueue (inputResult); + } +} + +internal class NetDriver : ConsoleDriver { + const int COLOR_BLACK = 30; + const int COLOR_RED = 31; + const int COLOR_GREEN = 32; + const int COLOR_YELLOW = 33; + const int COLOR_BLUE = 34; + const int COLOR_MAGENTA = 35; + const int COLOR_CYAN = 36; + const int COLOR_WHITE = 37; + const int COLOR_BRIGHT_BLACK = 90; + const int COLOR_BRIGHT_RED = 91; + const int COLOR_BRIGHT_GREEN = 92; + const int COLOR_BRIGHT_YELLOW = 93; + const int COLOR_BRIGHT_BLUE = 94; + const int COLOR_BRIGHT_MAGENTA = 95; + const int COLOR_BRIGHT_CYAN = 96; + const int COLOR_BRIGHT_WHITE = 97; + + public NetWinVTConsole NetWinConsole { get; private set; } + public bool IsWinPlatform { get; private set; } + + int _largestBufferHeight; + + public NetDriver () + { + } - cols = Console.WindowWidth; - rows = largestBufferHeight; + public override void End () + { + _mainLoop._netEvents.StopTasks (); - CurrentAttribute = MakeColor (Color.White, Color.Black); - InitalizeColorSchemes (); + if (IsWinPlatform) { + NetWinConsole.Cleanup (); + } - CurrentAttribute = MakeColor (Color.White, Color.Black); - InitalizeColorSchemes (); + StopReportingMouseMoves (); + Console.ResetColor (); - ResizeScreen (); - UpdateOffScreen (); + //Disable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndActivateAltBufferWithBackscroll); - StartReportingMouseMoves (); - } + //Set cursor key to cursor. + Console.Out.Write (EscSeqUtils.CSI_ShowCursor); - public override void ResizeScreen () - { - if (!EnableConsoleScrolling) { - if (Console.WindowHeight > 0) { - // Not supported on Unix. - if (IsWinPlatform) { - // Can raise an exception while is still resizing. - try { -#pragma warning disable CA1416 - Console.CursorTop = 0; - Console.CursorLeft = 0; - Console.WindowTop = 0; - Console.WindowLeft = 0; - if (Console.WindowHeight > Rows) { - Console.SetWindowSize (Cols, Rows); - } - Console.SetBufferSize (Cols, Rows); -#pragma warning restore CA1416 - } catch (System.IO.IOException) { - setClip (); - } catch (ArgumentOutOfRangeException) { - setClip (); - } - } else { - Console.Out.Write ($"\x1b[8;{Rows};{Cols}t"); - } - } - } else { - if (IsWinPlatform) { - if (Console.WindowHeight > 0) { - // Can raise an exception while is still resizing. - try { -#pragma warning disable CA1416 - Console.CursorTop = 0; - Console.CursorLeft = 0; - if (Console.WindowHeight > Rows) { - Console.SetWindowSize (Cols, Rows); - } - Console.SetBufferSize (Cols, Rows); -#pragma warning restore CA1416 - } catch (System.IO.IOException) { - setClip (); - } catch (ArgumentOutOfRangeException) { - setClip (); - } - } - } else { - Console.Out.Write ($"\x1b[30;{Rows};{Cols}t"); - } - } - setClip (); + Console.Out.Close (); + } - void setClip () - { - Clip = new Rect (0, 0, Cols, Rows); + public override void Init (Action terminalResized) + { + var p = Environment.OSVersion.Platform; + if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { + IsWinPlatform = true; + try { + NetWinConsole = new NetWinVTConsole (); + } catch (ApplicationException) { + // Likely running as a unit test, or in a non-interactive session. } } - - public override void UpdateOffScreen () - { - contents = new int [Rows, Cols, 3]; - dirtyLine = new bool [Rows]; - - lock (contents) { - // Can raise an exception while is still resizing. - try { - for (int row = 0; row < rows; row++) { - for (int c = 0; c < cols; c++) { - contents [row, c, 0] = ' '; - contents [row, c, 1] = (ushort)Colors.TopLevel.Normal; - contents [row, c, 2] = 0; - dirtyLine [row] = true; - } - } - } catch (IndexOutOfRangeException) { } + if (IsWinPlatform) { + Clipboard = new WindowsClipboard (); + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { + Clipboard = new MacOSXClipboard (); + } else { + if (CursesDriver.Is_WSL_Platform ()) { + Clipboard = new WSLClipboard (); + } else { + Clipboard = new CursesClipboard (); } } - public override Attribute MakeAttribute (Color fore, Color back) - { - return MakeColor ((ConsoleColor)fore, (ConsoleColor)back); - } + TerminalResized = terminalResized; - public override void Refresh () - { - UpdateScreen (); - UpdateCursor (); - } - - public override void UpdateScreen () - { - if (winChanging || Console.WindowHeight < 1 || contents.Length != Rows * Cols * 3 - || (!EnableConsoleScrolling && Rows != Console.WindowHeight) - || (EnableConsoleScrolling && Rows != largestBufferHeight)) { - return; - } - - int top = 0; - int left = 0; - int rows = Rows; - int cols = Cols; - System.Text.StringBuilder output = new System.Text.StringBuilder (); - int redrawAttr = -1; - var lastCol = -1; - - GetCursorVisibility (out CursorVisibility savedVisibitity); - SetCursorVisibility (CursorVisibility.Invisible); - - for (int row = top; row < rows; row++) { - if (Console.WindowHeight < 1) { - return; - } - if (!dirtyLine [row]) { - continue; - } - if (!SetCursorPosition (0, row)) { - return; - } - dirtyLine [row] = false; - output.Clear (); - for (int col = left; col < cols; col++) { - lastCol = -1; - var outputWidth = 0; - for (; col < cols; col++) { - if (contents [row, col, 2] == 0) { - if (output.Length > 0) { - SetCursorPosition (lastCol, row); - Console.Write (output); - output.Clear (); - lastCol += outputWidth; - outputWidth = 0; - } else if (lastCol == -1) { - lastCol = col; - } - if (lastCol + 1 < cols) - lastCol++; - continue; - } + if (NetWinConsole != null) { + //Enable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); - if (lastCol == -1) - lastCol = col; + //Set cursor key to application. + Console.Out.Write (EscSeqUtils.CSI_HideCursor); - var attr = contents [row, col, 1]; - if (attr != redrawAttr) { - redrawAttr = attr; - output.Append (WriteAttributes (attr)); - } - outputWidth++; - var rune = (Rune)contents [row, col, 0]; - char [] spair; - if (rune.DecodeSurrogatePair (out spair)) { - output.Append (spair); - } else { - output.Append ((char)rune.Value); - } - contents [row, col, 2] = 0; - } - } - if (output.Length > 0) { - SetCursorPosition (lastCol, row); - Console.Write (output); - } - } - SetCursorPosition (0, 0); - SetCursorVisibility (savedVisibitity); - } + Console.TreatControlCAsInput = true; - void SetVirtualCursorPosition (int col, int row) - { - Console.Out.Write ($"\x1b[{row + 1};{col + 1}H"); + Cols = Console.WindowWidth; + Rows = Console.WindowHeight; + } else { + // Simluate + Cols = 80; + Rows = 25; + _largestBufferHeight = Rows; } - System.Text.StringBuilder WriteAttributes (int attr) - { - const string CSI = "\x1b["; - int bg = 0; - int fg = 0; - System.Text.StringBuilder sb = new System.Text.StringBuilder (); - - IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (s => (int)s); - if (values.Contains (attr & 0xffff)) { - bg = MapColors ((ConsoleColor)(attr & 0xffff), false); - } - if (values.Contains ((attr >> 16) & 0xffff)) { - fg = MapColors ((ConsoleColor)((attr >> 16) & 0xffff)); - } - sb.Append ($"{CSI}{bg};{fg}m"); + ResizeScreen (); + ClearContents (); + CurrentAttribute = MakeColor (Color.White, Color.Black); + InitializeColorSchemes (); - return sb; - } + StartReportingMouseMoves (); + } - int MapColors (ConsoleColor color, bool isForeground = true) - { - switch (color) { - case ConsoleColor.Black: - return isForeground ? COLOR_BLACK : COLOR_BLACK + 10; - case ConsoleColor.DarkBlue: - return isForeground ? COLOR_BLUE : COLOR_BLUE + 10; - case ConsoleColor.DarkGreen: - return isForeground ? COLOR_GREEN : COLOR_GREEN + 10; - case ConsoleColor.DarkCyan: - return isForeground ? COLOR_CYAN : COLOR_CYAN + 10; - case ConsoleColor.DarkRed: - return isForeground ? COLOR_RED : COLOR_RED + 10; - case ConsoleColor.DarkMagenta: - return isForeground ? COLOR_MAGENTA : COLOR_MAGENTA + 10; - case ConsoleColor.DarkYellow: - return isForeground ? COLOR_YELLOW : COLOR_YELLOW + 10; - case ConsoleColor.Gray: - return isForeground ? COLOR_WHITE : COLOR_WHITE + 10; - case ConsoleColor.DarkGray: - return isForeground ? COLOR_BRIGHT_BLACK : COLOR_BRIGHT_BLACK + 10; - case ConsoleColor.Blue: - return isForeground ? COLOR_BRIGHT_BLUE : COLOR_BRIGHT_BLUE + 10; - case ConsoleColor.Green: - return isForeground ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_GREEN + 10; - case ConsoleColor.Cyan: - return isForeground ? COLOR_BRIGHT_CYAN : COLOR_BRIGHT_CYAN + 10; - case ConsoleColor.Red: - return isForeground ? COLOR_BRIGHT_RED : COLOR_BRIGHT_RED + 10; - case ConsoleColor.Magenta: - return isForeground ? COLOR_BRIGHT_MAGENTA : COLOR_BRIGHT_MAGENTA + 10; - case ConsoleColor.Yellow: - return isForeground ? COLOR_BRIGHT_YELLOW : COLOR_BRIGHT_YELLOW + 10; - case ConsoleColor.White: - return isForeground ? COLOR_BRIGHT_WHITE : COLOR_BRIGHT_WHITE + 10; - } - return 0; + public virtual void ResizeScreen () + { + if (NetWinConsole == null) { + return; } - bool SetCursorPosition (int col, int row) - { + if (Console.WindowHeight > 0) { + // Not supported on Unix. if (IsWinPlatform) { - // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth. + // Can raise an exception while is still resizing. try { - Console.SetCursorPosition (col, row); - return true; - } catch (Exception) { - return false; +#pragma warning disable CA1416 + Console.CursorTop = 0; + Console.CursorLeft = 0; + Console.WindowTop = 0; + Console.WindowLeft = 0; + if (Console.WindowHeight > Rows) { + Console.SetWindowSize (Cols, Rows); + } + Console.SetBufferSize (Cols, Rows); +#pragma warning restore CA1416 + } catch (System.IO.IOException) { + Clip = new Rect (0, 0, Cols, Rows); + } catch (ArgumentOutOfRangeException) { + Clip = new Rect (0, 0, Cols, Rows); } } else { - SetVirtualCursorPosition (col, row); - return true; + Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols)); } } - private void SetWindowPosition (int col, int row) - { - if (IsWinPlatform && EnableConsoleScrolling) { - var winTop = Math.Max (Rows - Console.WindowHeight - row, 0); - winTop = Math.Min (winTop, Rows - Console.WindowHeight + 1); - winTop = Math.Max (winTop, 0); - if (winTop != Console.WindowTop) { - try { - if (!EnsureBufferSize ()) { - return; - } -#pragma warning disable CA1416 - Console.SetWindowPosition (col, winTop); -#pragma warning restore CA1416 - } catch (System.IO.IOException) { + Clip = new Rect (0, 0, Cols, Rows); + } - } catch (System.ArgumentOutOfRangeException) { } - } - } - top = Console.WindowTop; - left = Console.WindowLeft; - } + public override void Refresh () + { + UpdateScreen (); + UpdateCursor (); + } - private bool EnsureBufferSize () - { -#pragma warning disable CA1416 - if (IsWinPlatform && Console.BufferHeight < Rows) { - try { - Console.SetBufferSize (Console.WindowWidth, Rows); - } catch (Exception) { - return false; - } - } -#pragma warning restore CA1416 - return true; + public override void UpdateScreen () + { + if (_winSizeChanging || Console.WindowHeight < 1 || Contents.Length != Rows * Cols || Rows != Console.WindowHeight) { + return; } - private CursorVisibility? savedCursorVisibility; + var top = 0; + var left = 0; + var rows = Rows; + var cols = Cols; + System.Text.StringBuilder output = new System.Text.StringBuilder (); + Attribute redrawAttr = new Attribute (); + var lastCol = -1; - public override void UpdateCursor () - { - EnsureCursorVisibility (); - //Debug.WriteLine ($"Before - CursorTop: {Console.CursorTop};CursorLeft: {Console.CursorLeft}"); + //GetCursorVisibility (out CursorVisibility savedVisibitity); + //SetCursorVisibility (CursorVisibility.Invisible); - if (ccol >= 0 && ccol < Cols && crow >= 0 && crow < Rows) { - SetCursorPosition (ccol, crow); - SetWindowPosition (0, crow); + for (var row = top; row < rows; row++) { + if (Console.WindowHeight < 1) { + return; } - //Debug.WriteLine ($"WindowTop: {Console.WindowTop};WindowLeft: {Console.WindowLeft}"); - //Debug.WriteLine ($"After - CursorTop: {Console.CursorTop};CursorLeft: {Console.CursorLeft}"); - } + if (!_dirtyLines [row]) { + continue; + } + if (!SetCursorPosition (0, row)) { + return; + } + _dirtyLines [row] = false; + output.Clear (); + for (var col = left; col < cols; col++) { + lastCol = -1; + var outputWidth = 0; + for (; col < cols; col++) { + if (!Contents [row, col].IsDirty) { + if (output.Length > 0) { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + } else if (lastCol == -1) { + lastCol = col; + } + if (lastCol + 1 < cols) + lastCol++; + continue; + } - public override void StartReportingMouseMoves () - { - Console.Out.Write (EscSeqUtils.EnableMouseEvents); - } + if (lastCol == -1) { + lastCol = col; + } - public override void StopReportingMouseMoves () - { - Console.Out.Write (EscSeqUtils.DisableMouseEvents); + Attribute attr = Contents [row, col].Attribute.Value; + // Performance: Only send the escape sequence if the attribute has changed. + if (attr != redrawAttr) { + redrawAttr = attr; + output.Append (EscSeqUtils.CSI_SetGraphicsRendition ( + MapColors ((ConsoleColor)attr.Background, false), MapColors ((ConsoleColor)attr.Foreground, true))); + } + outputWidth++; + var rune = (Rune)Contents [row, col].Runes [0]; + output.Append (rune.ToString ()); + if (rune.IsSurrogatePair () && rune.GetColumns () < 2) { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + Console.CursorLeft--; + } + Contents [row, col].IsDirty = false; + } + } + if (output.Length > 0) { + SetCursorPosition (lastCol, row); + Console.Write (output); + } } + SetCursorPosition (0, 0); - public override void Suspend () - { - } + //SetCursorVisibility (savedVisibitity); - public override void SetAttribute (Attribute c) + void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { - base.SetAttribute (c); + SetCursorPosition (lastCol, row); + Console.Write (output); + output.Clear (); + lastCol += outputWidth; + outputWidth = 0; } + } - public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) - { - if (consoleKeyInfo.Key != ConsoleKey.Packet) { - return consoleKeyInfo; - } + #region Color Handling + + // Cache the list of ConsoleColor values. + private static readonly HashSet ConsoleColorValues = new HashSet ( + Enum.GetValues (typeof (ConsoleColor)).OfType ().Select (c => (int)c) + ); + + // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console. + private static Dictionary colorMap = new Dictionary { + { ConsoleColor.Black, COLOR_BLACK }, + { ConsoleColor.DarkBlue, COLOR_BLUE }, + { ConsoleColor.DarkGreen, COLOR_GREEN }, + { ConsoleColor.DarkCyan, COLOR_CYAN }, + { ConsoleColor.DarkRed, COLOR_RED }, + { ConsoleColor.DarkMagenta, COLOR_MAGENTA }, + { ConsoleColor.DarkYellow, COLOR_YELLOW }, + { ConsoleColor.Gray, COLOR_WHITE }, + { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK }, + { ConsoleColor.Blue, COLOR_BRIGHT_BLUE }, + { ConsoleColor.Green, COLOR_BRIGHT_GREEN }, + { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN }, + { ConsoleColor.Red, COLOR_BRIGHT_RED }, + { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA }, + { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW }, + { ConsoleColor.White, COLOR_BRIGHT_WHITE } + }; + + // Map a ConsoleColor to a platform dependent value. + int MapColors (ConsoleColor color, bool isForeground = true) + { + return colorMap.TryGetValue (color, out var colorValue) ? colorValue + (isForeground ? 0 : 10) : 0; + } + + /// + /// In the NetDriver, colors are encoded as an int. + /// Extracts the foreground and background colors from the encoded value. + /// Assumes a 4-bit encoded value for both foreground and background colors. + /// + internal override void GetColors (int value, out Color foreground, out Color background) + { + // Assume a 4-bit encoded value for both foreground and background colors. + foreground = (Color)((value >> 16) & 0xF); + background = (Color)(value & 0xF); + } - var mod = consoleKeyInfo.Modifiers; - var shift = (mod & ConsoleModifiers.Shift) != 0; - var alt = (mod & ConsoleModifiers.Alt) != 0; - var control = (mod & ConsoleModifiers.Control) != 0; + /// + /// In the NetDriver, colors are encoded as an int. + /// However, the foreground color is stored in the most significant 16 bits, + /// and the background color is stored in the least significant 16 bits. + /// + public override Attribute MakeColor (Color foreground, Color background) + { + // Encode the colors into the int value. + return new Attribute ( + value: ((((int)foreground) & 0xffff) << 16) | (((int)background) & 0xffff), + foreground: foreground, + background: background + ); + } - var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _); + #endregion - return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control); + #region Cursor Handling + bool SetCursorPosition (int col, int row) + { + //if (IsWinPlatform) { + // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth. + try { + Console.SetCursorPosition (col, row); + return true; + } catch (Exception) { + return false; } + // BUGBUG: This breaks -usc on WSL; not sure why. But commenting out fixes. + //} else { + // // TODO: Explain why + 1 is needed (and why we do this for non-Windows). + // Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); + // return true; + //} + } - Key MapKey (ConsoleKeyInfo keyInfo) - { - MapKeyModifiers (keyInfo, (Key)keyInfo.Key); - switch (keyInfo.Key) { - case ConsoleKey.Escape: - return MapKeyModifiers (keyInfo, Key.Esc); - case ConsoleKey.Tab: - return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab; - case ConsoleKey.Home: - return MapKeyModifiers (keyInfo, Key.Home); - case ConsoleKey.End: - return MapKeyModifiers (keyInfo, Key.End); - case ConsoleKey.LeftArrow: - return MapKeyModifiers (keyInfo, Key.CursorLeft); - case ConsoleKey.RightArrow: - return MapKeyModifiers (keyInfo, Key.CursorRight); - case ConsoleKey.UpArrow: - return MapKeyModifiers (keyInfo, Key.CursorUp); - case ConsoleKey.DownArrow: - return MapKeyModifiers (keyInfo, Key.CursorDown); - case ConsoleKey.PageUp: - return MapKeyModifiers (keyInfo, Key.PageUp); - case ConsoleKey.PageDown: - return MapKeyModifiers (keyInfo, Key.PageDown); - case ConsoleKey.Enter: - return MapKeyModifiers (keyInfo, Key.Enter); - case ConsoleKey.Spacebar: - return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar); - case ConsoleKey.Backspace: - return MapKeyModifiers (keyInfo, Key.Backspace); - case ConsoleKey.Delete: - return MapKeyModifiers (keyInfo, Key.DeleteChar); - case ConsoleKey.Insert: - return MapKeyModifiers (keyInfo, Key.InsertChar); - - case ConsoleKey.Oem1: - case ConsoleKey.Oem2: - case ConsoleKey.Oem3: - case ConsoleKey.Oem4: - case ConsoleKey.Oem5: - case ConsoleKey.Oem6: - case ConsoleKey.Oem7: - case ConsoleKey.Oem8: - case ConsoleKey.Oem102: - case ConsoleKey.OemPeriod: - case ConsoleKey.OemComma: - case ConsoleKey.OemPlus: - case ConsoleKey.OemMinus: - return (Key)((uint)keyInfo.KeyChar); - } - - var key = keyInfo.Key; - if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { - var delta = key - ConsoleKey.A; - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); - } - } - return (Key)((uint)keyInfo.KeyChar); - } - if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { - var delta = key - ConsoleKey.D0; - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); - } - } - return (Key)((uint)keyInfo.KeyChar); - } - if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { - var delta = key - ConsoleKey.F1; - if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); - } + CursorVisibility? _cachedCursorVisibility; - return (Key)((uint)Key.F1 + delta); - } - if (keyInfo.KeyChar != 0) { - return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar)); - } + public override void UpdateCursor () + { + EnsureCursorVisibility (); - return (Key)(0xffffffff); + if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) { + SetCursorPosition (Col, Row); + SetWindowPosition (0, Row); } + } - KeyModifiers keyModifiers; + public override bool GetCursorVisibility (out CursorVisibility visibility) + { + visibility = _cachedCursorVisibility ?? CursorVisibility.Default; + return visibility == CursorVisibility.Default; + } - Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) - { - if (keyModifiers == null) { - keyModifiers = new KeyModifiers (); - } - Key keyMod = new Key (); - if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) { - keyMod = Key.ShiftMask; - keyModifiers.Shift = true; - } - if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) { - keyMod |= Key.CtrlMask; - keyModifiers.Ctrl = true; - } - if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) { - keyMod |= Key.AltMask; - keyModifiers.Alt = true; - } + public override bool SetCursorVisibility (CursorVisibility visibility) + { + _cachedCursorVisibility = visibility; + var isVisible = Console.CursorVisible = visibility == CursorVisibility.Default; + //Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); + return isVisible; + } - return keyMod != Key.Null ? keyMod | key : key; + public override bool EnsureCursorVisibility () + { + if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) { + GetCursorVisibility (out CursorVisibility cursorVisibility); + _cachedCursorVisibility = cursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + return false; } - Action keyHandler; - Action keyDownHandler; - Action keyUpHandler; - Action mouseHandler; - NetMainLoop mainLoop; - - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) - { - this.keyHandler = keyHandler; - this.keyDownHandler = keyDownHandler; - this.keyUpHandler = keyUpHandler; - this.mouseHandler = mouseHandler; - - var mLoop = this.mainLoop = mainLoop.Driver as NetMainLoop; + SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default); + return _cachedCursorVisibility == CursorVisibility.Default; + } + #endregion - // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called. - mLoop.ProcessInput = (e) => ProcessInput (e); + #region Size and Position Handling - // Check if terminal supports requests - this.mainLoop.netEvents.EscSeqReqProc.Add ("c"); - Console.Out.Write ("\x1b[0c"); - } + void SetWindowPosition (int col, int row) + { + Top = Console.WindowTop; + Left = Console.WindowLeft; + } - void ProcessInput (NetEvents.InputResult inputEvent) - { - switch (inputEvent.EventType) { - case NetEvents.EventType.Key: - ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo; - if (consoleKeyInfo.Key == ConsoleKey.Packet) { - consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); - } - keyModifiers = new KeyModifiers (); - var map = MapKey (consoleKeyInfo); - if (map == (Key)0xffffffff) { - return; - } - if (map == Key.Null) { - keyDownHandler (new KeyEvent (map, keyModifiers)); - keyUpHandler (new KeyEvent (map, keyModifiers)); - } else { - keyDownHandler (new KeyEvent (map, keyModifiers)); - keyHandler (new KeyEvent (map, keyModifiers)); - keyUpHandler (new KeyEvent (map, keyModifiers)); - } - break; - case NetEvents.EventType.Mouse: - mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); - break; - case NetEvents.EventType.WindowSize: - ChangeWin (inputEvent.WindowSizeEvent.Size); - break; - case NetEvents.EventType.RequestResponse: - Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple; - break; + private bool EnsureBufferSize () + { +#pragma warning disable CA1416 + if (IsWinPlatform && Console.BufferHeight < Rows) { + try { + Console.SetBufferSize (Console.WindowWidth, Rows); + } catch (Exception) { + return false; } } +#pragma warning restore CA1416 + return true; + } + #endregion - volatile bool winChanging; - void ChangeWin (Size size) - { - winChanging = true; - if (!EnableConsoleScrolling) { - largestBufferHeight = Math.Max (size.Height, 0); - } else { - largestBufferHeight = Math.Max (size.Height, largestBufferHeight); - } - top = 0; - left = 0; - cols = size.Width; - rows = largestBufferHeight; - ResizeScreen (); - UpdateOffScreen (); - winChanging = false; - TerminalResized?.Invoke (); + public void StartReportingMouseMoves () + { + Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + } + + public void StopReportingMouseMoves () + { + Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + } + + ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) + { + if (consoleKeyInfo.Key != ConsoleKey.Packet) { + return consoleKeyInfo; } - MouseEvent ToDriverMouse (NetEvents.MouseEvent me) - { - //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); + var mod = consoleKeyInfo.Modifiers; + var shift = (mod & ConsoleModifiers.Shift) != 0; + var alt = (mod & ConsoleModifiers.Alt) != 0; + var control = (mod & ConsoleModifiers.Control) != 0; - MouseFlags mouseFlag = 0; + var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _); - if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) { - mouseFlag |= MouseFlags.Button1Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1Released) != 0) { - mouseFlag |= MouseFlags.Button1Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1Clicked) != 0) { - mouseFlag |= MouseFlags.Button1Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button1DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button1TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button1TripleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2Pressed) != 0) { - mouseFlag |= MouseFlags.Button2Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2Released) != 0) { - mouseFlag |= MouseFlags.Button2Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2Clicked) != 0) { - mouseFlag |= MouseFlags.Button2Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button2DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button2TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button2TripleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3Pressed) != 0) { - mouseFlag |= MouseFlags.Button3Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3Released) != 0) { - mouseFlag |= MouseFlags.Button3Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3Clicked) != 0) { - mouseFlag |= MouseFlags.Button3Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button3DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button3TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button3TripleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledUp) != 0) { - mouseFlag |= MouseFlags.WheeledUp; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledDown) != 0) { - mouseFlag |= MouseFlags.WheeledDown; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledLeft) != 0) { - mouseFlag |= MouseFlags.WheeledLeft; - } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledRight) != 0) { - mouseFlag |= MouseFlags.WheeledRight; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4Pressed) != 0) { - mouseFlag |= MouseFlags.Button4Pressed; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4Released) != 0) { - mouseFlag |= MouseFlags.Button4Released; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4Clicked) != 0) { - mouseFlag |= MouseFlags.Button4Clicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4DoubleClicked) != 0) { - mouseFlag |= MouseFlags.Button4DoubleClicked; - } - if ((me.ButtonState & NetEvents.MouseButtonState.Button4TripleClicked) != 0) { - mouseFlag |= MouseFlags.Button4TripleClicked; + return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control); + } + + Key MapKey (ConsoleKeyInfo keyInfo) + { + MapKeyModifiers (keyInfo, (Key)keyInfo.Key); + switch (keyInfo.Key) { + case ConsoleKey.Escape: + return MapKeyModifiers (keyInfo, Key.Esc); + case ConsoleKey.Tab: + return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab; + case ConsoleKey.Home: + return MapKeyModifiers (keyInfo, Key.Home); + case ConsoleKey.End: + return MapKeyModifiers (keyInfo, Key.End); + case ConsoleKey.LeftArrow: + return MapKeyModifiers (keyInfo, Key.CursorLeft); + case ConsoleKey.RightArrow: + return MapKeyModifiers (keyInfo, Key.CursorRight); + case ConsoleKey.UpArrow: + return MapKeyModifiers (keyInfo, Key.CursorUp); + case ConsoleKey.DownArrow: + return MapKeyModifiers (keyInfo, Key.CursorDown); + case ConsoleKey.PageUp: + return MapKeyModifiers (keyInfo, Key.PageUp); + case ConsoleKey.PageDown: + return MapKeyModifiers (keyInfo, Key.PageDown); + case ConsoleKey.Enter: + return MapKeyModifiers (keyInfo, Key.Enter); + case ConsoleKey.Spacebar: + return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar); + case ConsoleKey.Backspace: + return MapKeyModifiers (keyInfo, Key.Backspace); + case ConsoleKey.Delete: + return MapKeyModifiers (keyInfo, Key.DeleteChar); + case ConsoleKey.Insert: + return MapKeyModifiers (keyInfo, Key.InsertChar); + + case ConsoleKey.Oem1: + case ConsoleKey.Oem2: + case ConsoleKey.Oem3: + case ConsoleKey.Oem4: + case ConsoleKey.Oem5: + case ConsoleKey.Oem6: + case ConsoleKey.Oem7: + case ConsoleKey.Oem8: + case ConsoleKey.Oem102: + case ConsoleKey.OemPeriod: + case ConsoleKey.OemComma: + case ConsoleKey.OemPlus: + case ConsoleKey.OemMinus: + return (Key)((uint)keyInfo.KeyChar); + } + + var key = keyInfo.Key; + if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { + var delta = key - ConsoleKey.A; + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta)); + } + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { + return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta)); + } + if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); + } } - if ((me.ButtonState & NetEvents.MouseButtonState.ReportMousePosition) != 0) { - mouseFlag |= MouseFlags.ReportMousePosition; + return (Key)((uint)keyInfo.KeyChar); + } + if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { + var delta = key - ConsoleKey.D0; + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { + return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta)); } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonShift) != 0) { - mouseFlag |= MouseFlags.ButtonShift; + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta)); } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonCtrl) != 0) { - mouseFlag |= MouseFlags.ButtonCtrl; + if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); + } } - if ((me.ButtonState & NetEvents.MouseButtonState.ButtonAlt) != 0) { - mouseFlag |= MouseFlags.ButtonAlt; + return (Key)((uint)keyInfo.KeyChar); + } + if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) { + var delta = key - ConsoleKey.F1; + if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); } - return new MouseEvent () { - X = me.Position.X, - Y = me.Position.Y, - Flags = mouseFlag - }; + return (Key)((uint)Key.F1 + delta); } - - /// - public override bool GetCursorVisibility (out CursorVisibility visibility) - { - visibility = savedCursorVisibility ?? CursorVisibility.Default; - return visibility == CursorVisibility.Default; + if (keyInfo.KeyChar != 0) { + return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar)); } - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - savedCursorVisibility = visibility; - var isVisible = Console.CursorVisible = visibility == CursorVisibility.Default; - if (isVisible) { - Console.Out.Write ("\x1b[?25h"); - } else { - Console.Out.Write ("\x1b[?25l"); - } - return isVisible; - } + return (Key)(0xffffffff); + } - /// - public override bool EnsureCursorVisibility () - { - if (!(ccol >= 0 && crow >= 0 && ccol < Cols && crow < Rows)) { - GetCursorVisibility (out CursorVisibility cursorVisibility); - savedCursorVisibility = cursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - return false; - } + KeyModifiers _keyModifiers; - SetCursorVisibility (savedCursorVisibility ?? CursorVisibility.Default); - return savedCursorVisibility == CursorVisibility.Default; + Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) + { + _keyModifiers ??= new KeyModifiers (); + Key keyMod = new Key (); + if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) { + keyMod = Key.ShiftMask; + _keyModifiers.Shift = true; + } + if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) { + keyMod |= Key.CtrlMask; + _keyModifiers.Ctrl = true; + } + if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) { + keyMod |= Key.AltMask; + _keyModifiers.Alt = true; } - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) - { - NetEvents.InputResult input = new NetEvents.InputResult (); - input.EventType = NetEvents.EventType.Key; - input.ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control); + return keyMod != Key.Null ? keyMod | key : key; + } - try { - ProcessInput (input); - } catch (OverflowException) { } - } + Action _keyHandler; + Action _keyDownHandler; + Action _keyUpHandler; + Action _mouseHandler; + NetMainLoop _mainLoop; - public override bool GetColors (int value, out Color foreground, out Color background) - { - bool hasColor = false; - foreground = default; - background = default; - IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (s => (int)s); - if (values.Contains (value & 0xffff)) { - hasColor = true; - background = (Color)(ConsoleColor)(value & 0xffff); + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) + { + _keyHandler = keyHandler; + _keyDownHandler = keyDownHandler; + _keyUpHandler = keyUpHandler; + _mouseHandler = mouseHandler; + + var mLoop = _mainLoop = mainLoop.MainLoopDriver as NetMainLoop; + + // Note: .Net API doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called. + mLoop.ProcessInput = (e) => ProcessInput (e); + } + + volatile bool _winSizeChanging; + + void ProcessInput (NetEvents.InputResult inputEvent) + { + switch (inputEvent.EventType) { + case NetEvents.EventType.Key: + ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo; + if (consoleKeyInfo.Key == ConsoleKey.Packet) { + consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo); } - if (values.Contains ((value >> 16) & 0xffff)) { - hasColor = true; - foreground = (Color)(ConsoleColor)((value >> 16) & 0xffff); + _keyModifiers = new KeyModifiers (); + var map = MapKey (consoleKeyInfo); + if (map == (Key)0xffffffff) { + return; } - return hasColor; + if (map == Key.Null) { + _keyDownHandler (new KeyEvent (map, _keyModifiers)); + _keyUpHandler (new KeyEvent (map, _keyModifiers)); + } else { + _keyDownHandler (new KeyEvent (map, _keyModifiers)); + _keyHandler (new KeyEvent (map, _keyModifiers)); + _keyUpHandler (new KeyEvent (map, _keyModifiers)); + } + break; + case NetEvents.EventType.Mouse: + _mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); + break; + case NetEvents.EventType.WindowSize: + _winSizeChanging = true; + Top = 0; + Left = 0; + Cols = inputEvent.WindowSizeEvent.Size.Width; + Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); ; + ResizeScreen (); + ClearContents (); + _winSizeChanging = false; + TerminalResized?.Invoke (); + break; + case NetEvents.EventType.RequestResponse: + // BUGBUG: What is this for? It does not seem to be used anywhere. + // It is also not clear what it does. View.Data is documented as "This property is not used internally" + Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple; + break; + case NetEvents.EventType.WindowPosition: + break; + default: + throw new ArgumentOutOfRangeException (); } + } - #region Unused - public override void SetColors (ConsoleColor foreground, ConsoleColor background) - { - } + MouseEvent ToDriverMouse (NetEvents.MouseEvent me) + { + //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}"); - public override void SetColors (short foregroundColorId, short backgroundColorId) - { - } + MouseFlags mouseFlag = 0; - public override void CookMouse () - { + if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) { + mouseFlag |= MouseFlags.Button1Pressed; } - - public override void UncookMouse () - { + if ((me.ButtonState & NetEvents.MouseButtonState.Button1Released) != 0) { + mouseFlag |= MouseFlags.Button1Released; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button1Clicked) != 0) { + mouseFlag |= MouseFlags.Button1Clicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button1DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button1DoubleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button1TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button1TripleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button2Pressed) != 0) { + mouseFlag |= MouseFlags.Button2Pressed; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button2Released) != 0) { + mouseFlag |= MouseFlags.Button2Released; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button2Clicked) != 0) { + mouseFlag |= MouseFlags.Button2Clicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button2DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button2DoubleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button2TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button2TripleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button3Pressed) != 0) { + mouseFlag |= MouseFlags.Button3Pressed; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button3Released) != 0) { + mouseFlag |= MouseFlags.Button3Released; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button3Clicked) != 0) { + mouseFlag |= MouseFlags.Button3Clicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button3DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button3DoubleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button3TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button3TripleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledUp) != 0) { + mouseFlag |= MouseFlags.WheeledUp; + } + if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledDown) != 0) { + mouseFlag |= MouseFlags.WheeledDown; } - #endregion + if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledLeft) != 0) { + mouseFlag |= MouseFlags.WheeledLeft; + } + if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledRight) != 0) { + mouseFlag |= MouseFlags.WheeledRight; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button4Pressed) != 0) { + mouseFlag |= MouseFlags.Button4Pressed; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button4Released) != 0) { + mouseFlag |= MouseFlags.Button4Released; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button4Clicked) != 0) { + mouseFlag |= MouseFlags.Button4Clicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button4DoubleClicked) != 0) { + mouseFlag |= MouseFlags.Button4DoubleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.Button4TripleClicked) != 0) { + mouseFlag |= MouseFlags.Button4TripleClicked; + } + if ((me.ButtonState & NetEvents.MouseButtonState.ReportMousePosition) != 0) { + mouseFlag |= MouseFlags.ReportMousePosition; + } + if ((me.ButtonState & NetEvents.MouseButtonState.ButtonShift) != 0) { + mouseFlag |= MouseFlags.ButtonShift; + } + if ((me.ButtonState & NetEvents.MouseButtonState.ButtonCtrl) != 0) { + mouseFlag |= MouseFlags.ButtonCtrl; + } + if ((me.ButtonState & NetEvents.MouseButtonState.ButtonAlt) != 0) { + mouseFlag |= MouseFlags.ButtonAlt; + } + + return new MouseEvent () { + X = me.Position.X, + Y = me.Position.Y, + Flags = mouseFlag + }; + } + + public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) + { + NetEvents.InputResult input = new NetEvents.InputResult { + EventType = NetEvents.EventType.Key, + ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control) + }; + + try { + ProcessInput (input); + } catch (OverflowException) { } + } - // - // These are for the .NET driver, but running natively on Windows, wont run - // on the Mono emulation - // + #region Not Implemented + public override void Suspend () + { + throw new NotImplementedException (); } + #endregion + +} + +/// +/// 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. +/// +/// +/// This implementation is used for NetDriver. +/// +internal class NetMainLoop : IMainLoopDriver { + ManualResetEventSlim _keyReady = new ManualResetEventSlim (false); + ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false); + Queue _inputResult = new Queue (); + MainLoop _mainLoop; + CancellationTokenSource _tokenSource = new CancellationTokenSource (); + internal NetEvents _netEvents; /// - /// 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. + /// Invoked when a Key is pressed. + /// + internal Action ProcessInput; + + /// + /// Initializes the class with the console driver. /// /// - /// This implementation is used for NetDriver. + /// Passing a consoleDriver is provided to capture windows resizing. /// - internal class NetMainLoop : IMainLoopDriver { - ManualResetEventSlim keyReady = new ManualResetEventSlim (false); - ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); - Queue inputResult = new Queue (); - MainLoop mainLoop; - CancellationTokenSource tokenSource = new CancellationTokenSource (); - internal NetEvents netEvents; - - /// - /// Invoked when a Key is pressed. - /// - public Action ProcessInput; - - /// - /// Initializes the class with the console driver. - /// - /// - /// Passing a consoleDriver is provided to capture windows resizing. - /// - /// The console driver used by this Net main loop. - public NetMainLoop (ConsoleDriver consoleDriver = null) - { - if (consoleDriver == null) { - throw new ArgumentNullException ("Console driver instance must be provided."); - } - netEvents = new NetEvents (consoleDriver); - } + /// The console driver used by this Net main loop. + /// + public NetMainLoop (ConsoleDriver consoleDriver = null) + { + if (consoleDriver == null) { + throw new ArgumentNullException (nameof (consoleDriver)); + } + _netEvents = new NetEvents (consoleDriver); + } - void NetInputHandler () - { - while (true) { - waitForProbe.Wait (); - waitForProbe.Reset (); - if (inputResult.Count == 0) { - inputResult.Enqueue (netEvents.ReadConsoleInput ()); - } - try { - while (inputResult.Peek () == null) { - inputResult.Dequeue (); - } - if (inputResult.Count > 0) { - keyReady.Set (); - } - } catch (InvalidOperationException) { } + void NetInputHandler () + { + while (true) { + _waitForProbe.Wait (); + _waitForProbe.Reset (); + if (_inputResult.Count == 0) { + _inputResult.Enqueue (_netEvents.ReadConsoleInput ()); } + try { + while (_inputResult.Peek () == null) { + _inputResult.Dequeue (); + } + if (_inputResult.Count > 0) { + _keyReady.Set (); + } + } catch (InvalidOperationException) { } } + } - void IMainLoopDriver.Setup (MainLoop mainLoop) - { - this.mainLoop = mainLoop; - Task.Run (NetInputHandler); - } - - void IMainLoopDriver.Wakeup () - { - keyReady.Set (); - } + void IMainLoopDriver.Setup (MainLoop mainLoop) + { + _mainLoop = mainLoop; + Task.Run (NetInputHandler); + } - bool IMainLoopDriver.EventsPending (bool wait) - { - waitForProbe.Set (); + void IMainLoopDriver.Wakeup () + { + _keyReady.Set (); + } - if (CheckTimers (wait, out var waitTimeout)) { - return true; - } + bool IMainLoopDriver.EventsPending (bool wait) + { + _waitForProbe.Set (); - try { - if (!tokenSource.IsCancellationRequested) { - keyReady.Wait (waitTimeout, tokenSource.Token); - } - } catch (OperationCanceledException) { - return true; - } finally { - keyReady.Reset (); - } + if (CheckTimers (wait, out var waitTimeout)) { + return true; + } - if (!tokenSource.IsCancellationRequested) { - return inputResult.Count > 0 || CheckTimers (wait, out _); + try { + if (!_tokenSource.IsCancellationRequested) { + _keyReady.Wait (waitTimeout, _tokenSource.Token); } - - tokenSource.Dispose (); - tokenSource = new CancellationTokenSource (); + } catch (OperationCanceledException) { return true; + } finally { + _keyReady.Reset (); } - bool CheckTimers (bool wait, out int waitTimeout) - { - long now = DateTime.UtcNow.Ticks; + if (!_tokenSource.IsCancellationRequested) { + return _inputResult.Count > 0 || CheckTimers (wait, out _); + } - if (mainLoop.timeouts.Count > 0) { - waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); - if (waitTimeout < 0) - return true; - } else { - waitTimeout = -1; - } + _tokenSource.Dispose (); + _tokenSource = new CancellationTokenSource (); + return true; + } - if (!wait) - waitTimeout = 0; + bool CheckTimers (bool wait, out int waitTimeout) + { + var now = DateTime.UtcNow.Ticks; - int ic; - lock (mainLoop.idleHandlers) { - ic = mainLoop.idleHandlers.Count; + if (_mainLoop.timeouts.Count > 0) { + waitTimeout = (int)((_mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); + if (waitTimeout < 0) { + return true; } + } else { + waitTimeout = -1; + } - return ic > 0; + if (!wait) { + waitTimeout = 0; } - void IMainLoopDriver.Iteration () - { - while (inputResult.Count > 0) { - ProcessInput?.Invoke (inputResult.Dequeue ().Value); - } + int ic; + lock (_mainLoop.idleHandlers) { + ic = _mainLoop.idleHandlers.Count; + } + + return ic > 0; + } + + void IMainLoopDriver.Iteration () + { + while (_inputResult.Count > 0) { + ProcessInput?.Invoke (_inputResult.Dequeue ().Value); } } + public void TearDown () + { + //throw new NotImplementedException (); + } } \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 16741743b8..0cec3e9c8f 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -10,616 +10,671 @@ using System.Threading; using System.Threading.Tasks; -namespace Terminal.Gui { +namespace Terminal.Gui; + +internal class WindowsConsole { + public const int STD_OUTPUT_HANDLE = -11; + public const int STD_INPUT_HANDLE = -10; + public const int STD_ERROR_HANDLE = -12; + + IntPtr _inputHandle, _outputHandle; + IntPtr _screenBuffer; + readonly uint _originalConsoleMode; + CursorVisibility? _initialCursorVisibility = null; + CursorVisibility? _currentCursorVisibility = null; + CursorVisibility? _pendingCursorVisibility = null; + readonly StringBuilder _stringBuilder = new StringBuilder (256 * 1024); + + public WindowsConsole () + { + _inputHandle = GetStdHandle (STD_INPUT_HANDLE); + _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); + _originalConsoleMode = ConsoleMode; + var newConsoleMode = _originalConsoleMode; + newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags); + newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; + newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput; + ConsoleMode = newConsoleMode; + } - internal class WindowsConsole { - public const int STD_OUTPUT_HANDLE = -11; - public const int STD_INPUT_HANDLE = -10; - public const int STD_ERROR_HANDLE = -12; + CharInfo [] _originalStdOutChars; - internal IntPtr InputHandle, OutputHandle; - IntPtr ScreenBuffer; - readonly uint originalConsoleMode; - CursorVisibility? initialCursorVisibility = null; - CursorVisibility? currentCursorVisibility = null; - CursorVisibility? pendingCursorVisibility = null; + public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord coords, SmallRect window, bool useTrueColor) + { + if (_screenBuffer == IntPtr.Zero) { + ReadFromConsoleOutput (size, coords, ref window); + } - public WindowsConsole () - { - InputHandle = GetStdHandle (STD_INPUT_HANDLE); - OutputHandle = GetStdHandle (STD_OUTPUT_HANDLE); - originalConsoleMode = ConsoleMode; - var newConsoleMode = originalConsoleMode; - newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags); - newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; - newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput; - ConsoleMode = newConsoleMode; + if (!useTrueColor) { + int i = 0; + CharInfo [] ci = new CharInfo [charInfoBuffer.Length]; + foreach (ExtendedCharInfo info in charInfoBuffer) { + ci [i++] = new CharInfo () { + Char = new CharUnion () { UnicodeChar = info.Char }, + Attributes = (ushort)info.Attribute.Value + }; + } + return WriteConsoleOutput (_screenBuffer, ci, coords, new Coord () { X = window.Left, Y = window.Top }, ref window); } - public CharInfo [] OriginalStdOutChars; - public bool WriteToConsole (Size size, CharInfo [] charInfoBuffer, Coord coords, SmallRect window) - { - if (ScreenBuffer == IntPtr.Zero) { - ReadFromConsoleOutput (size, coords, ref window); - } + return WriteConsoleTrueColorOutput (charInfoBuffer); + } - return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = window.Left, Y = window.Top }, ref window); - } + private bool WriteConsoleTrueColorOutput (ExtendedCharInfo [] charInfoBuffer) + { + _stringBuilder.Clear (); - public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) - { - ScreenBuffer = CreateConsoleScreenBuffer ( - DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - ShareMode.FileShareRead | ShareMode.FileShareWrite, - IntPtr.Zero, - 1, - IntPtr.Zero - ); - if (ScreenBuffer == INVALID_HANDLE_VALUE) { - var err = Marshal.GetLastWin32Error (); - - if (err != 0) - throw new System.ComponentModel.Win32Exception (err); - } + _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); + _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0)); - if (!initialCursorVisibility.HasValue && GetCursorVisibility (out CursorVisibility visibility)) { - initialCursorVisibility = visibility; - } + Attribute? prev = null; + foreach (var info in charInfoBuffer) { + var attr = info.Attribute; - if (!SetConsoleActiveScreenBuffer (ScreenBuffer)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + if (attr != prev) { + prev = attr; + + _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.TrueColorForeground.Value.Red, attr.TrueColorForeground.Value.Green, attr.TrueColorForeground.Value.Blue)); + _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.TrueColorBackground.Value.Red, attr.TrueColorBackground.Value.Green, attr.TrueColorBackground.Value.Blue)); } - OriginalStdOutChars = new CharInfo [size.Height * size.Width]; + if (info.Char != '\x1b') { + if (!info.Empty) { + _stringBuilder.Append (info.Char); + } - if (!ReadConsoleOutput (ScreenBuffer, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } else { + _stringBuilder.Append (' '); } } - public bool SetCursorPosition (Coord position) - { - return SetConsoleCursorPosition (ScreenBuffer, position); - } + _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); - public void SetInitialCursorVisibility () - { - if (initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility)) { - initialCursorVisibility = visibility; + string s = _stringBuilder.ToString (); + + return WriteConsole (_screenBuffer, s, (uint)(s.Length), out uint _, null); + } + + public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) + { + _screenBuffer = CreateConsoleScreenBuffer ( + DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + ShareMode.FileShareRead | ShareMode.FileShareWrite, + IntPtr.Zero, + 1, + IntPtr.Zero + ); + if (_screenBuffer == INVALID_HANDLE_VALUE) { + var err = Marshal.GetLastWin32Error (); + + if (err != 0) { + throw new System.ComponentModel.Win32Exception (err); } } - public bool GetCursorVisibility (out CursorVisibility visibility) - { - if (ScreenBuffer == IntPtr.Zero) { - visibility = CursorVisibility.Invisible; - return false; - } - if (!GetConsoleCursorInfo (ScreenBuffer, out ConsoleCursorInfo info)) { - var err = Marshal.GetLastWin32Error (); - if (err != 0) { - throw new System.ComponentModel.Win32Exception (err); - } - visibility = Gui.CursorVisibility.Default; + if (!_initialCursorVisibility.HasValue && GetCursorVisibility (out CursorVisibility visibility)) { + _initialCursorVisibility = visibility; + } - return false; - } + if (!SetConsoleActiveScreenBuffer (_screenBuffer)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } - if (!info.bVisible) - visibility = CursorVisibility.Invisible; - else if (info.dwSize > 50) - visibility = CursorVisibility.Box; - else - visibility = CursorVisibility.Underline; + _originalStdOutChars = new CharInfo [size.Height * size.Width]; - return true; + if (!ReadConsoleOutput (_screenBuffer, _originalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); } + } - public bool EnsureCursorVisibility () - { - if (initialCursorVisibility.HasValue && pendingCursorVisibility.HasValue && SetCursorVisibility (pendingCursorVisibility.Value)) { - pendingCursorVisibility = null; + public bool SetCursorPosition (Coord position) + { + return SetConsoleCursorPosition (_screenBuffer, position); + } - return true; - } + public void SetInitialCursorVisibility () + { + if (_initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility)) { + _initialCursorVisibility = visibility; + } + } + public bool GetCursorVisibility (out CursorVisibility visibility) + { + if (_screenBuffer == IntPtr.Zero) { + visibility = CursorVisibility.Invisible; return false; } - - public void ForceRefreshCursorVisibility () - { - if (currentCursorVisibility.HasValue) { - pendingCursorVisibility = currentCursorVisibility; - currentCursorVisibility = null; + if (!GetConsoleCursorInfo (_screenBuffer, out ConsoleCursorInfo info)) { + var err = Marshal.GetLastWin32Error (); + if (err != 0) { + throw new System.ComponentModel.Win32Exception (err); } + visibility = Gui.CursorVisibility.Default; + + return false; } - public bool SetCursorVisibility (CursorVisibility visibility) - { - if (initialCursorVisibility.HasValue == false) { - pendingCursorVisibility = visibility; + if (!info.bVisible) { + visibility = CursorVisibility.Invisible; + } else if (info.dwSize > 50) { + visibility = CursorVisibility.Box; + } else { + visibility = CursorVisibility.Underline; + } - return false; - } + return true; + } - if (currentCursorVisibility.HasValue == false || currentCursorVisibility.Value != visibility) { - ConsoleCursorInfo info = new ConsoleCursorInfo { - dwSize = (uint)visibility & 0x00FF, - bVisible = ((uint)visibility & 0xFF00) != 0 - }; + public bool EnsureCursorVisibility () + { + if (_initialCursorVisibility.HasValue && _pendingCursorVisibility.HasValue && SetCursorVisibility (_pendingCursorVisibility.Value)) { + _pendingCursorVisibility = null; - if (!SetConsoleCursorInfo (ScreenBuffer, ref info)) - return false; + return true; + } - currentCursorVisibility = visibility; - } + return false; + } - return true; + public void ForceRefreshCursorVisibility () + { + if (_currentCursorVisibility.HasValue) { + _pendingCursorVisibility = _currentCursorVisibility; + _currentCursorVisibility = null; } + } - public void Cleanup () - { - if (initialCursorVisibility.HasValue) { - SetCursorVisibility (initialCursorVisibility.Value); - } + public bool SetCursorVisibility (CursorVisibility visibility) + { + if (_initialCursorVisibility.HasValue == false) { + _pendingCursorVisibility = visibility; + + return false; + } - SetConsoleOutputWindow (out _); + if (_currentCursorVisibility.HasValue == false || _currentCursorVisibility.Value != visibility) { + ConsoleCursorInfo info = new ConsoleCursorInfo { + dwSize = (uint)visibility & 0x00FF, + bVisible = ((uint)visibility & 0xFF00) != 0 + }; - ConsoleMode = originalConsoleMode; - //ContinueListeningForConsoleEvents = false; - if (!SetConsoleActiveScreenBuffer (OutputHandle)) { - var err = Marshal.GetLastWin32Error (); - Console.WriteLine ("Error: {0}", err); + if (!SetConsoleCursorInfo (_screenBuffer, ref info)) { + return false; } - if (ScreenBuffer != IntPtr.Zero) - CloseHandle (ScreenBuffer); + _currentCursorVisibility = visibility; + } + + return true; + } - ScreenBuffer = IntPtr.Zero; + public void Cleanup () + { + if (_initialCursorVisibility.HasValue) { + SetCursorVisibility (_initialCursorVisibility.Value); } - internal Size GetConsoleBufferWindow (out Point position) - { - if (ScreenBuffer == IntPtr.Zero) { - position = Point.Empty; - return Size.Empty; - } + SetConsoleOutputWindow (out _); - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { - //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - position = Point.Empty; - return Size.Empty; - } - var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, - csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); + ConsoleMode = _originalConsoleMode; + if (!SetConsoleActiveScreenBuffer (_outputHandle)) { + var err = Marshal.GetLastWin32Error (); + Console.WriteLine ("Error: {0}", err); + } - return sz; + if (_screenBuffer != IntPtr.Zero) { + CloseHandle (_screenBuffer); } - internal Size GetConsoleOutputWindow (out Point position) - { - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - } - var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, - csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); + _screenBuffer = IntPtr.Zero; + } - return sz; + internal Size GetConsoleBufferWindow (out Point position) + { + if (_screenBuffer == IntPtr.Zero) { + position = Point.Empty; + return Size.Empty; } - internal Size SetConsoleWindow (short cols, short rows) - { - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - } - var maxWinSize = GetLargestConsoleWindowSize (ScreenBuffer); - var newCols = Math.Min (cols, maxWinSize.X); - var newRows = Math.Min (rows, maxWinSize.Y); - csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); - csbi.srWindow = new SmallRect (0, 0, newCols, newRows); - csbi.dwMaximumWindowSize = new Coord (newCols, newRows); - if (!SetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - } - var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); - if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) { - //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - return new Size (cols, rows); - } - SetConsoleOutputWindow (csbi); - return new Size (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) { + //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + position = Point.Empty; + return Size.Empty; } + var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); - void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) - { - if (ScreenBuffer != IntPtr.Zero && !SetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - } + return sz; + } + + internal Size GetConsoleOutputWindow (out Point position) + { + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); } + var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); - internal Size SetConsoleOutputWindow (out Point position) - { - if (ScreenBuffer == IntPtr.Zero) { - position = Point.Empty; - return Size.Empty; - } + return sz; + } - var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - } - var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, - Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); - position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); - SetConsoleOutputWindow (csbi); - var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0)); - if (!SetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - } - if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) { - throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - } + internal Size SetConsoleWindow (short cols, short rows) + { + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + var maxWinSize = GetLargestConsoleWindowSize (_screenBuffer); + var newCols = Math.Min (cols, maxWinSize.X); + var newRows = Math.Min (rows, maxWinSize.Y); + csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); + csbi.srWindow = new SmallRect (0, 0, newCols, newRows); + csbi.dwMaximumWindowSize = new Coord (newCols, newRows); + if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); + if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) { + //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + return new Size (cols, rows); + } + SetConsoleOutputWindow (csbi); + return new Size (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); + } - return sz; + void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) + { + if (_screenBuffer != IntPtr.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); } + } - //bool ContinueListeningForConsoleEvents = true; + internal Size SetConsoleOutputWindow (out Point position) + { + if (_screenBuffer == IntPtr.Zero) { + position = Point.Empty; + return Size.Empty; + } - public uint ConsoleMode { - get { - GetConsoleMode (InputHandle, out uint v); - return v; - } - set { - SetConsoleMode (InputHandle, value); - } + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, + Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); + position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); + SetConsoleOutputWindow (csbi); + var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0)); + if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); } - [Flags] - public enum ConsoleModes : uint { - EnableProcessedInput = 1, - EnableMouseInput = 16, - EnableQuickEditMode = 64, - EnableExtendedFlags = 128, - } - - [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] - public struct KeyEventRecord { - [FieldOffset (0), MarshalAs (UnmanagedType.Bool)] - public bool bKeyDown; - [FieldOffset (4), MarshalAs (UnmanagedType.U2)] - public ushort wRepeatCount; - [FieldOffset (6), MarshalAs (UnmanagedType.U2)] - public ushort wVirtualKeyCode; - [FieldOffset (8), MarshalAs (UnmanagedType.U2)] - public ushort wVirtualScanCode; - [FieldOffset (10)] - public char UnicodeChar; - [FieldOffset (12), MarshalAs (UnmanagedType.U4)] - public ControlKeyState dwControlKeyState; - } - - [Flags] - public enum ButtonState { - Button1Pressed = 1, - Button2Pressed = 4, - Button3Pressed = 8, - Button4Pressed = 16, - RightmostButtonPressed = 2 - } - - [Flags] - public enum ControlKeyState { - RightAltPressed = 1, - LeftAltPressed = 2, - RightControlPressed = 4, - LeftControlPressed = 8, - ShiftPressed = 16, - NumlockOn = 32, - ScrolllockOn = 64, - CapslockOn = 128, - EnhancedKey = 256 - } - - [Flags] - public enum EventFlags { - MouseMoved = 1, - DoubleClick = 2, - MouseWheeled = 4, - MouseHorizontalWheeled = 8 - } - - [StructLayout (LayoutKind.Explicit)] - public struct MouseEventRecord { - [FieldOffset (0)] - public Coord MousePosition; - [FieldOffset (4)] - public ButtonState ButtonState; - [FieldOffset (8)] - public ControlKeyState ControlKeyState; - [FieldOffset (12)] - public EventFlags EventFlags; - - public override string ToString () - { - return $"[Mouse({MousePosition},{ButtonState},{ControlKeyState},{EventFlags}"; - } + return sz; + } + + uint ConsoleMode { + get { + GetConsoleMode (_inputHandle, out uint v); + return v; + } + set { + SetConsoleMode (_inputHandle, value); } + } - public struct WindowBufferSizeRecord { - public Coord size; + [Flags] + public enum ConsoleModes : uint { + EnableProcessedInput = 1, + EnableMouseInput = 16, + EnableQuickEditMode = 64, + EnableExtendedFlags = 128, + } - public WindowBufferSizeRecord (short x, short y) - { - this.size = new Coord (x, y); - } + [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] + public struct KeyEventRecord { + [FieldOffset (0), MarshalAs (UnmanagedType.Bool)] + public bool bKeyDown; + [FieldOffset (4), MarshalAs (UnmanagedType.U2)] + public ushort wRepeatCount; + [FieldOffset (6), MarshalAs (UnmanagedType.U2)] + public ushort wVirtualKeyCode; + [FieldOffset (8), MarshalAs (UnmanagedType.U2)] + public ushort wVirtualScanCode; + [FieldOffset (10)] + public char UnicodeChar; + [FieldOffset (12), MarshalAs (UnmanagedType.U4)] + public ControlKeyState dwControlKeyState; + } - public override string ToString () => $"[WindowBufferSize{size}"; - } - - [StructLayout (LayoutKind.Sequential)] - public struct MenuEventRecord { - public uint dwCommandId; - } - - [StructLayout (LayoutKind.Sequential)] - public struct FocusEventRecord { - public uint bSetFocus; - } - - public enum EventType : ushort { - Focus = 0x10, - Key = 0x1, - Menu = 0x8, - Mouse = 2, - WindowBufferSize = 4 - } - - [StructLayout (LayoutKind.Explicit)] - public struct InputRecord { - [FieldOffset (0)] - public EventType EventType; - [FieldOffset (4)] - public KeyEventRecord KeyEvent; - [FieldOffset (4)] - public MouseEventRecord MouseEvent; - [FieldOffset (4)] - public WindowBufferSizeRecord WindowBufferSizeEvent; - [FieldOffset (4)] - public MenuEventRecord MenuEvent; - [FieldOffset (4)] - public FocusEventRecord FocusEvent; - - public override string ToString () - { - switch (EventType) { - case EventType.Focus: - return FocusEvent.ToString (); - case EventType.Key: - return KeyEvent.ToString (); - case EventType.Menu: - return MenuEvent.ToString (); - case EventType.Mouse: - return MouseEvent.ToString (); - case EventType.WindowBufferSize: - return WindowBufferSizeEvent.ToString (); - default: - return "Unknown event type: " + EventType; - } - } - }; + [Flags] + public enum ButtonState { + Button1Pressed = 1, + Button2Pressed = 4, + Button3Pressed = 8, + Button4Pressed = 16, + RightmostButtonPressed = 2 + } - [Flags] - enum ShareMode : uint { - FileShareRead = 1, - FileShareWrite = 2, - } + [Flags] + public enum ControlKeyState { + RightAltPressed = 1, + LeftAltPressed = 2, + RightControlPressed = 4, + LeftControlPressed = 8, + ShiftPressed = 16, + NumlockOn = 32, + ScrolllockOn = 64, + CapslockOn = 128, + EnhancedKey = 256 + } - [Flags] - enum DesiredAccess : uint { - GenericRead = 2147483648, - GenericWrite = 1073741824, - } + [Flags] + public enum EventFlags { + MouseMoved = 1, + DoubleClick = 2, + MouseWheeled = 4, + MouseHorizontalWheeled = 8 + } - [StructLayout (LayoutKind.Sequential)] - public struct ConsoleScreenBufferInfo { - public Coord dwSize; - public Coord dwCursorPosition; - public ushort wAttributes; - public SmallRect srWindow; - public Coord dwMaximumWindowSize; - } + [StructLayout (LayoutKind.Explicit)] + public struct MouseEventRecord { + [FieldOffset (0)] + public Coord MousePosition; + [FieldOffset (4)] + public ButtonState ButtonState; + [FieldOffset (8)] + public ControlKeyState ControlKeyState; + [FieldOffset (12)] + public EventFlags EventFlags; - [StructLayout (LayoutKind.Sequential)] - public struct Coord { - public short X; - public short Y; + public override readonly string ToString () => $"[Mouse({MousePosition},{ButtonState},{ControlKeyState},{EventFlags}"; - public Coord (short X, short Y) - { - this.X = X; - this.Y = Y; - } - public override string ToString () => $"({X},{Y})"; - }; + } + + public struct WindowBufferSizeRecord { + public Coord _size; - [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] - public struct CharUnion { - [FieldOffset (0)] public char UnicodeChar; - [FieldOffset (0)] public byte AsciiChar; + public WindowBufferSizeRecord (short x, short y) + { + _size = new Coord (x, y); } - [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] - public struct CharInfo { - [FieldOffset (0)] public CharUnion Char; - [FieldOffset (2)] public ushort Attributes; + public override readonly string ToString () => $"[WindowBufferSize{_size}"; + } + + [StructLayout (LayoutKind.Sequential)] + public struct MenuEventRecord { + public uint dwCommandId; + } + + [StructLayout (LayoutKind.Sequential)] + public struct FocusEventRecord { + public uint bSetFocus; + } + + public enum EventType : ushort { + Focus = 0x10, + Key = 0x1, + Menu = 0x8, + Mouse = 2, + WindowBufferSize = 4 + } + + [StructLayout (LayoutKind.Explicit)] + public struct InputRecord { + [FieldOffset (0)] + public EventType EventType; + [FieldOffset (4)] + public KeyEventRecord KeyEvent; + [FieldOffset (4)] + public MouseEventRecord MouseEvent; + [FieldOffset (4)] + public WindowBufferSizeRecord WindowBufferSizeEvent; + [FieldOffset (4)] + public MenuEventRecord MenuEvent; + [FieldOffset (4)] + public FocusEventRecord FocusEvent; + + public override readonly string ToString () + { + return EventType switch { + EventType.Focus => FocusEvent.ToString (), + EventType.Key => KeyEvent.ToString (), + EventType.Menu => MenuEvent.ToString (), + EventType.Mouse => MouseEvent.ToString (), + EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), + _ => "Unknown event type: " + EventType + }; } + }; - [StructLayout (LayoutKind.Sequential)] - public struct SmallRect { - public short Left; - public short Top; - public short Right; - public short Bottom; + [Flags] + enum ShareMode : uint { + FileShareRead = 1, + FileShareWrite = 2, + } - public SmallRect (short left, short top, short right, short bottom) - { - Left = left; - Top = top; - Right = right; - Bottom = bottom; - } + [Flags] + enum DesiredAccess : uint { + GenericRead = 2147483648, + GenericWrite = 1073741824, + } - public static void MakeEmpty (ref SmallRect rect) - { - rect.Left = -1; - } + [StructLayout (LayoutKind.Sequential)] + public struct ConsoleScreenBufferInfo { + public Coord dwSize; + public Coord dwCursorPosition; + public ushort wAttributes; + public SmallRect srWindow; + public Coord dwMaximumWindowSize; + } - public static void Update (ref SmallRect rect, short col, short row) - { - if (rect.Left == -1) { - //System.Diagnostics.Debugger.Log (0, "debug", $"damager From Empty {col},{row}\n"); - rect.Left = rect.Right = col; - rect.Bottom = rect.Top = row; - return; - } - if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom) - return; - if (col < rect.Left) - rect.Left = col; - if (col > rect.Right) - rect.Right = col; - if (row < rect.Top) - rect.Top = row; - if (row > rect.Bottom) - rect.Bottom = row; - //System.Diagnostics.Debugger.Log (0, "debug", $"Expanding {rect.ToString ()}\n"); - } + [StructLayout (LayoutKind.Sequential)] + public struct Coord { + public short X; + public short Y; - public override string ToString () - { - return $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; - } + public Coord (short x, short y) + { + X = x; + Y = y; } + public override readonly string ToString () => $"({X},{Y})"; + }; + + [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] + public struct CharUnion { + [FieldOffset (0)] public char UnicodeChar; + [FieldOffset (0)] public byte AsciiChar; + } + + [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] + public struct CharInfo { + [FieldOffset (0)] public CharUnion Char; + [FieldOffset (2)] public ushort Attributes; + } - [StructLayout (LayoutKind.Sequential)] - public struct ConsoleKeyInfoEx { - public ConsoleKeyInfo consoleKeyInfo; - public bool CapsLock; - public bool NumLock; - public bool Scrolllock; + 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 ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock) - { - this.consoleKeyInfo = consoleKeyInfo; - CapsLock = capslock; - NumLock = numlock; - Scrolllock = scrolllock; - } + public ExtendedCharInfo (char character, Attribute attribute) + { + Char = character; + Attribute = attribute; + Empty = false; } + } - [DllImport ("kernel32.dll", SetLastError = true)] - static extern IntPtr GetStdHandle (int nStdHandle); + [StructLayout (LayoutKind.Sequential)] + public struct SmallRect { + public short Left; + public short Top; + public short Right; + public short Bottom; - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool CloseHandle (IntPtr handle); + public SmallRect (short left, short top, short right, short bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } - [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] - public static extern bool ReadConsoleInput ( - IntPtr hConsoleInput, - IntPtr lpBuffer, - uint nLength, - out uint lpNumberOfEventsRead); + public static void MakeEmpty (ref SmallRect rect) + { + rect.Left = -1; + } - [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - static extern bool ReadConsoleOutput ( - IntPtr hConsoleOutput, - [Out] CharInfo [] lpBuffer, - Coord dwBufferSize, - Coord dwBufferCoord, - ref SmallRect lpReadRegion - ); + public static void Update (ref SmallRect rect, short col, short row) + { + if (rect.Left == -1) { + rect.Left = rect.Right = col; + rect.Bottom = rect.Top = row; + return; + } + if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom) + return; + if (col < rect.Left) + rect.Left = col; + if (col > rect.Right) + rect.Right = col; + if (row < rect.Top) + rect.Top = row; + if (row > rect.Bottom) + rect.Bottom = row; + } - [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutput", SetLastError = true, CharSet = CharSet.Unicode)] - static extern bool WriteConsoleOutput ( - IntPtr hConsoleOutput, - CharInfo [] lpBuffer, - Coord dwBufferSize, - Coord dwBufferCoord, - ref SmallRect lpWriteRegion - ); + public override readonly string ToString () => $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; + } - [DllImport ("kernel32.dll")] - static extern bool SetConsoleCursorPosition (IntPtr hConsoleOutput, Coord dwCursorPosition); + [StructLayout (LayoutKind.Sequential)] + public struct ConsoleKeyInfoEx { + public ConsoleKeyInfo ConsoleKeyInfo; + public bool CapsLock; + public bool NumLock; + public bool ScrollLock; - [StructLayout (LayoutKind.Sequential)] - public struct ConsoleCursorInfo { - public uint dwSize; - public bool bVisible; + public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock) + { + ConsoleKeyInfo = consoleKeyInfo; + CapsLock = capslock; + NumLock = numlock; + ScrollLock = scrolllock; } + } - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool SetConsoleCursorInfo (IntPtr hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern IntPtr GetStdHandle (int nStdHandle); + + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool CloseHandle (IntPtr handle); + + [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] + public static extern bool ReadConsoleInput ( + IntPtr hConsoleInput, + IntPtr lpBuffer, + uint nLength, + out uint lpNumberOfEventsRead); + + [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static extern bool ReadConsoleOutput ( + IntPtr hConsoleOutput, + [Out] CharInfo [] lpBuffer, + Coord dwBufferSize, + Coord dwBufferCoord, + ref SmallRect lpReadRegion + ); + + // TODO: This API is obsolete. See https://learn.microsoft.com/en-us/windows/console/writeconsoleoutput + [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutputW", SetLastError = true, CharSet = CharSet.Unicode)] + static extern bool WriteConsoleOutput ( + IntPtr hConsoleOutput, + CharInfo [] lpBuffer, + Coord dwBufferSize, + Coord dwBufferCoord, + ref SmallRect lpWriteRegion + ); + + [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)] + static extern bool WriteConsole ( + IntPtr hConsoleOutput, + String lpbufer, + UInt32 NumberOfCharsToWriten, + out UInt32 lpNumberOfCharsWritten, + object lpReserved + ); + + [DllImport ("kernel32.dll")] + static extern bool SetConsoleCursorPosition (IntPtr hConsoleOutput, Coord dwCursorPosition); + + [StructLayout (LayoutKind.Sequential)] + public struct ConsoleCursorInfo { + public uint dwSize; + public bool bVisible; + } - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool GetConsoleCursorInfo (IntPtr hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleCursorInfo (IntPtr hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo); - [DllImport ("kernel32.dll")] - static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool GetConsoleCursorInfo (IntPtr hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo); - [DllImport ("kernel32.dll")] - static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); + [DllImport ("kernel32.dll")] + static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode); - [DllImport ("kernel32.dll", SetLastError = true)] - static extern IntPtr CreateConsoleScreenBuffer ( - DesiredAccess dwDesiredAccess, - ShareMode dwShareMode, - IntPtr secutiryAttributes, - uint flags, - IntPtr screenBufferData - ); + [DllImport ("kernel32.dll")] + static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); - internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr (-1); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern IntPtr CreateConsoleScreenBuffer ( + DesiredAccess dwDesiredAccess, + ShareMode dwShareMode, + IntPtr secutiryAttributes, + uint flags, + IntPtr screenBufferData + ); - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool SetConsoleActiveScreenBuffer (IntPtr Handle); + internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr (-1); - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool GetNumberOfConsoleInputEvents (IntPtr handle, out uint lpcNumberOfEvents); - public uint InputEventCount { - get { - GetNumberOfConsoleInputEvents (InputHandle, out uint v); - return v; - } - } + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleActiveScreenBuffer (IntPtr Handle); - public InputRecord [] ReadConsoleInput () - { - const int bufferSize = 1; - var pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize); - try { - ReadConsoleInput (InputHandle, pRecord, bufferSize, - out var numberEventsRead); - - return numberEventsRead == 0 - ? null - : new [] { Marshal.PtrToStructure (pRecord) }; - } catch (Exception) { - return null; - } finally { - Marshal.FreeHGlobal (pRecord); - } + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool GetNumberOfConsoleInputEvents (IntPtr handle, out uint lpcNumberOfEvents); + + public InputRecord [] ReadConsoleInput () + { + const int bufferSize = 1; + var pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize); + try { + ReadConsoleInput (_inputHandle, pRecord, bufferSize, + out var numberEventsRead); + + return numberEventsRead == 0 + ? null + : new [] { Marshal.PtrToStructure (pRecord) }; + } catch (Exception) { + return null; + } finally { + Marshal.FreeHGlobal (pRecord); } + } #if false // Not needed on the constructor. Perhaps could be used on resizing. To study. [DllImport ("kernel32.dll", ExactSpelling = true)] @@ -639,1509 +694,1258 @@ internal void ShowWindow (int state) ShowWindow (thisConsole, state); } #endif - // See: https://github.com/gui-cs/Terminal.Gui/issues/357 - - [StructLayout (LayoutKind.Sequential)] - public struct CONSOLE_SCREEN_BUFFER_INFOEX { - public uint cbSize; - public Coord dwSize; - public Coord dwCursorPosition; - public ushort wAttributes; - public SmallRect srWindow; - public Coord dwMaximumWindowSize; - public ushort wPopupAttributes; - public bool bFullscreenSupported; - - [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)] - public COLORREF [] ColorTable; - } - - [StructLayout (LayoutKind.Explicit, Size = 4)] - public struct COLORREF { - public COLORREF (byte r, byte g, byte b) - { - Value = 0; - R = r; - G = g; - B = b; - } - - public COLORREF (uint value) - { - R = 0; - G = 0; - B = 0; - Value = value & 0x00FFFFFF; - } - - [FieldOffset (0)] - public byte R; - [FieldOffset (1)] - public byte G; - [FieldOffset (2)] - public byte B; - - [FieldOffset (0)] - public uint Value; - } - - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool GetConsoleScreenBufferInfoEx (IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi); - - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool SetConsoleScreenBufferInfoEx (IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo); - - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool SetConsoleWindowInfo ( - IntPtr hConsoleOutput, - bool bAbsolute, - [In] ref SmallRect lpConsoleWindow); - - [DllImport ("kernel32.dll", SetLastError = true)] - static extern Coord GetLargestConsoleWindowSize ( - IntPtr hConsoleOutput); + // See: https://github.com/gui-cs/Terminal.Gui/issues/357 + + [StructLayout (LayoutKind.Sequential)] + public struct CONSOLE_SCREEN_BUFFER_INFOEX { + public uint cbSize; + public Coord dwSize; + public Coord dwCursorPosition; + public ushort wAttributes; + public SmallRect srWindow; + public Coord dwMaximumWindowSize; + public ushort wPopupAttributes; + public bool bFullscreenSupported; + + [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)] + public COLORREF [] ColorTable; } - internal class WindowsDriver : ConsoleDriver { - static bool sync = false; - WindowsConsole.CharInfo [] OutputBuffer; - int cols, rows, left, top; - WindowsConsole.SmallRect damageRegion; - IClipboard clipboard; - int [,,] contents; - - public override int Cols => cols; - public override int Rows => rows; - public override int Left => left; - public override int Top => top; - public override bool EnableConsoleScrolling { get; set; } - public override IClipboard Clipboard => clipboard; - public override int [,,] Contents => contents; - - public WindowsConsole WinConsole { get; private set; } - - Action keyHandler; - Action keyDownHandler; - Action keyUpHandler; - Action mouseHandler; - - public WindowsDriver () - { - WinConsole = new WindowsConsole (); - clipboard = new WindowsClipboard (); - } - - public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) + [StructLayout (LayoutKind.Explicit, Size = 4)] + public struct COLORREF { + public COLORREF (byte r, byte g, byte b) { - this.keyHandler = keyHandler; - this.keyDownHandler = keyDownHandler; - this.keyUpHandler = keyUpHandler; - this.mouseHandler = mouseHandler; - - var mLoop = mainLoop.Driver as WindowsMainLoop; - - mLoop.ProcessInput = (e) => ProcessInput (e); - - mLoop.WinChanged = (s, e) => { - ChangeWin (e.Size); - }; + Value = 0; + R = r; + G = g; + B = b; } - private void ChangeWin (Size e) + public COLORREF (uint value) { - if (!EnableConsoleScrolling) { - var w = e.Width; - if (w == cols - 3 && e.Height < rows) { - w += 3; - } - var newSize = WinConsole.SetConsoleWindow ( - (short)Math.Max (w, 16), (short)Math.Max (e.Height, 0)); - left = 0; - top = 0; - cols = newSize.Width; - rows = newSize.Height; - ResizeScreen (); - UpdateOffScreen (); - TerminalResized.Invoke (); - } + R = 0; + G = 0; + B = 0; + Value = value & 0x00FFFFFF; } - void ProcessInput (WindowsConsole.InputRecord inputEvent) - { - switch (inputEvent.EventType) { - case WindowsConsole.EventType.Key: - var fromPacketKey = inputEvent.KeyEvent.wVirtualKeyCode == (uint)ConsoleKey.Packet; - if (fromPacketKey) { - inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent); - } - var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent)); - //var ke = inputEvent.KeyEvent; - //System.Diagnostics.Debug.WriteLine ($"fromPacketKey: {fromPacketKey}"); - //if (ke.UnicodeChar == '\0') { - // System.Diagnostics.Debug.WriteLine ("UnicodeChar: 0'\\0'"); - //} else if (ke.UnicodeChar == 13) { - // System.Diagnostics.Debug.WriteLine ("UnicodeChar: 13'\\n'"); - //} else { - // System.Diagnostics.Debug.WriteLine ($"UnicodeChar: {(uint)ke.UnicodeChar}'{ke.UnicodeChar}'"); - //} - //System.Diagnostics.Debug.WriteLine ($"bKeyDown: {ke.bKeyDown}"); - //System.Diagnostics.Debug.WriteLine ($"dwControlKeyState: {ke.dwControlKeyState}"); - //System.Diagnostics.Debug.WriteLine ($"wRepeatCount: {ke.wRepeatCount}"); - //System.Diagnostics.Debug.WriteLine ($"wVirtualKeyCode: {ke.wVirtualKeyCode}"); - //System.Diagnostics.Debug.WriteLine ($"wVirtualScanCode: {ke.wVirtualScanCode}"); - - if (map == (Key)0xffffffff) { - KeyEvent key = new KeyEvent (); - - // Shift = VK_SHIFT = 0x10 - // Ctrl = VK_CONTROL = 0x11 - // Alt = VK_MENU = 0x12 - - if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.CapslockOn)) { - inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.CapslockOn; - } - - if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ScrolllockOn)) { - inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.ScrolllockOn; - } + [FieldOffset (0)] + public byte R; + [FieldOffset (1)] + public byte G; + [FieldOffset (2)] + public byte B; - if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.NumlockOn)) { - inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.NumlockOn; - } + [FieldOffset (0)] + public uint Value; + } - switch (inputEvent.KeyEvent.dwControlKeyState) { - case WindowsConsole.ControlKeyState.RightAltPressed: - case WindowsConsole.ControlKeyState.RightAltPressed | - WindowsConsole.ControlKeyState.LeftControlPressed | - WindowsConsole.ControlKeyState.EnhancedKey: - case WindowsConsole.ControlKeyState.EnhancedKey: - key = new KeyEvent (Key.CtrlMask | Key.AltMask, keyModifiers); - break; - case WindowsConsole.ControlKeyState.LeftAltPressed: - key = new KeyEvent (Key.AltMask, keyModifiers); - break; - case WindowsConsole.ControlKeyState.RightControlPressed: - case WindowsConsole.ControlKeyState.LeftControlPressed: - key = new KeyEvent (Key.CtrlMask, keyModifiers); - break; - case WindowsConsole.ControlKeyState.ShiftPressed: - key = new KeyEvent (Key.ShiftMask, keyModifiers); - break; - case WindowsConsole.ControlKeyState.NumlockOn: - break; - case WindowsConsole.ControlKeyState.ScrolllockOn: - break; - case WindowsConsole.ControlKeyState.CapslockOn: - break; - default: - switch (inputEvent.KeyEvent.wVirtualKeyCode) { - case 0x10: - key = new KeyEvent (Key.ShiftMask, keyModifiers); - break; - case 0x11: - key = new KeyEvent (Key.CtrlMask, keyModifiers); - break; - case 0x12: - key = new KeyEvent (Key.AltMask, keyModifiers); - break; - default: - key = new KeyEvent (Key.Unknown, keyModifiers); - break; - } - break; - } + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool GetConsoleScreenBufferInfoEx (IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi); - if (inputEvent.KeyEvent.bKeyDown) - keyDownHandler (key); - else - keyUpHandler (key); - } else { - if (inputEvent.KeyEvent.bKeyDown) { - // May occurs using SendKeys - if (keyModifiers == null) - keyModifiers = new KeyModifiers (); - // Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event - keyDownHandler (new KeyEvent (map, keyModifiers)); - keyHandler (new KeyEvent (map, keyModifiers)); - } else { - keyUpHandler (new KeyEvent (map, keyModifiers)); - } - } - if (!inputEvent.KeyEvent.bKeyDown) { - keyModifiers = null; - } - break; + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleScreenBufferInfoEx (IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo); - case WindowsConsole.EventType.Mouse: - var me = ToDriverMouse (inputEvent.MouseEvent); - mouseHandler (me); - if (processButtonClick) { - mouseHandler ( - new MouseEvent () { - X = me.X, - Y = me.Y, - Flags = ProcessButtonClick (inputEvent.MouseEvent) - }); - } - break; + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleWindowInfo ( + IntPtr hConsoleOutput, + bool bAbsolute, + [In] ref SmallRect lpConsoleWindow); - case WindowsConsole.EventType.WindowBufferSize: - var winSize = WinConsole.GetConsoleBufferWindow (out Point pos); - left = pos.X; - top = pos.Y; - cols = inputEvent.WindowBufferSizeEvent.size.X; - if (EnableConsoleScrolling) { - rows = Math.Max (inputEvent.WindowBufferSizeEvent.size.Y, rows); - } else { - rows = inputEvent.WindowBufferSizeEvent.size.Y; - } - //System.Diagnostics.Debug.WriteLine ($"{EnableConsoleScrolling},{cols},{rows}"); - ResizeScreen (); - UpdateOffScreen (); - TerminalResized?.Invoke (); - break; + [DllImport ("kernel32.dll", SetLastError = true)] + static extern Coord GetLargestConsoleWindowSize ( + IntPtr hConsoleOutput); +} - case WindowsConsole.EventType.Focus: - keyModifiers = null; - break; - } - } +internal class WindowsDriver : ConsoleDriver { + WindowsConsole.ExtendedCharInfo [] _outputBuffer; + WindowsConsole.SmallRect _damageRegion; + Action _keyHandler; + Action _keyDownHandler; + Action _keyUpHandler; + Action _mouseHandler; - WindowsConsole.ButtonState? lastMouseButtonPressed = null; - bool isButtonPressed = false; - bool isButtonReleased = false; - bool isButtonDoubleClicked = false; - Point? point; - Point pointMove; - //int buttonPressedCount; - bool isOneFingerDoubleClicked = false; - bool processButtonClick; + public WindowsConsole WinConsole { get; private set; } - MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) - { - MouseFlags mouseFlag = MouseFlags.AllEvents; + public override bool SupportsTrueColor => Environment.OSVersion.Version.Build >= 14931; - //System.Diagnostics.Debug.WriteLine ( - // $"X:{mouseEvent.MousePosition.X};Y:{mouseEvent.MousePosition.Y};ButtonState:{mouseEvent.ButtonState};EventFlags:{mouseEvent.EventFlags}"); + public WindowsDriver () + { + WinConsole = new WindowsConsole (); + Clipboard = new WindowsClipboard (); + } - if (isButtonDoubleClicked || isOneFingerDoubleClicked) { - Application.MainLoop.AddIdle (() => { - Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); - return false; - }); - } + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) + { + _keyHandler = keyHandler; + _keyDownHandler = keyDownHandler; + _keyUpHandler = keyUpHandler; + _mouseHandler = mouseHandler; - // 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 - // map to the correct clicked event. - if ((lastMouseButtonPressed != null || isButtonReleased) && mouseEvent.ButtonState != 0) { - lastMouseButtonPressed = null; - //isButtonPressed = false; - isButtonReleased = false; - } + var mLoop = mainLoop.MainLoopDriver as WindowsMainLoop; - var p = new Point () { - X = mouseEvent.MousePosition.X, - Y = mouseEvent.MousePosition.Y - }; + mLoop.ProcessInput = (e) => ProcessInput (e); - //if (!isButtonPressed && buttonPressedCount < 2 - // && mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved - // && (mouseEvent.ButtonState == WindowsConsole.ButtonState.Button1Pressed - // || mouseEvent.ButtonState == WindowsConsole.ButtonState.Button2Pressed - // || mouseEvent.ButtonState == WindowsConsole.ButtonState.Button3Pressed)) { + mLoop.WinChanged = (s, e) => { + ChangeWin (e.Size); + }; + } - // lastMouseButtonPressed = mouseEvent.ButtonState; - // buttonPressedCount++; - //} else if (!isButtonPressed && buttonPressedCount > 0 && mouseEvent.ButtonState == 0 - // && mouseEvent.EventFlags == 0) { + private void ChangeWin (Size e) + { + var w = e.Width; + if (w == Cols - 3 && e.Height < Rows) { + w += 3; + } + var newSize = WinConsole.SetConsoleWindow ( + (short)Math.Max (w, 16), (short)Math.Max (e.Height, 0)); + Left = 0; + Top = 0; + Cols = newSize.Width; + Rows = newSize.Height; + ResizeScreen (); + ClearContents (); + TerminalResized.Invoke (); + } - // buttonPressedCount++; + void ProcessInput (WindowsConsole.InputRecord inputEvent) + { + switch (inputEvent.EventType) { + case WindowsConsole.EventType.Key: + var fromPacketKey = inputEvent.KeyEvent.wVirtualKeyCode == (uint)ConsoleKey.Packet; + if (fromPacketKey) { + inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent); + } + var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent)); + //var ke = inputEvent.KeyEvent; + //System.Diagnostics.Debug.WriteLine ($"fromPacketKey: {fromPacketKey}"); + //if (ke.UnicodeChar == '\0') { + // System.Diagnostics.Debug.WriteLine ("UnicodeChar: 0'\\0'"); + //} else if (ke.UnicodeChar == 13) { + // System.Diagnostics.Debug.WriteLine ("UnicodeChar: 13'\\n'"); + //} else { + // System.Diagnostics.Debug.WriteLine ($"UnicodeChar: {(uint)ke.UnicodeChar}'{ke.UnicodeChar}'"); //} - //System.Diagnostics.Debug.WriteLine ($"isButtonPressed: {isButtonPressed};buttonPressedCount: {buttonPressedCount};lastMouseButtonPressed: {lastMouseButtonPressed}"); - //System.Diagnostics.Debug.WriteLine ($"isOneFingerDoubleClicked: {isOneFingerDoubleClicked}"); - - //if (buttonPressedCount == 1 && lastMouseButtonPressed != null && p == point - // && lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed - // || lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed - // || lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) { - - // switch (lastMouseButtonPressed) { - // case WindowsConsole.ButtonState.Button1Pressed: - // mouseFlag = MouseFlags.Button1DoubleClicked; - // break; - - // case WindowsConsole.ButtonState.Button2Pressed: - // mouseFlag = MouseFlags.Button2DoubleClicked; - // break; - - // case WindowsConsole.ButtonState.Button3Pressed: - // mouseFlag = MouseFlags.Button3DoubleClicked; - // break; - // } - // isOneFingerDoubleClicked = true; - - //} else if (buttonPressedCount == 3 && lastMouseButtonPressed != null && isOneFingerDoubleClicked && p == point - // && lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed - // || lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed - // || lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) { - - // switch (lastMouseButtonPressed) { - // case WindowsConsole.ButtonState.Button1Pressed: - // mouseFlag = MouseFlags.Button1TripleClicked; - // break; - - // case WindowsConsole.ButtonState.Button2Pressed: - // mouseFlag = MouseFlags.Button2TripleClicked; - // break; - - // case WindowsConsole.ButtonState.Button3Pressed: - // mouseFlag = MouseFlags.Button3TripleClicked; - // break; - // } - // buttonPressedCount = 0; - // lastMouseButtonPressed = null; - // isOneFingerDoubleClicked = false; - // isButtonReleased = false; + //System.Diagnostics.Debug.WriteLine ($"bKeyDown: {ke.bKeyDown}"); + //System.Diagnostics.Debug.WriteLine ($"dwControlKeyState: {ke.dwControlKeyState}"); + //System.Diagnostics.Debug.WriteLine ($"wRepeatCount: {ke.wRepeatCount}"); + //System.Diagnostics.Debug.WriteLine ($"wVirtualKeyCode: {ke.wVirtualKeyCode}"); + //System.Diagnostics.Debug.WriteLine ($"wVirtualScanCode: {ke.wVirtualScanCode}"); - //} - if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) || - (lastMouseButtonPressed == null && mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.MouseMoved) && - mouseEvent.ButtonState != 0 && !isButtonReleased && !isButtonDoubleClicked)) { - switch (mouseEvent.ButtonState) { - case WindowsConsole.ButtonState.Button1Pressed: - mouseFlag = MouseFlags.Button1Pressed; - break; + if (map == (Key)0xffffffff) { + KeyEvent key = new KeyEvent (); - case WindowsConsole.ButtonState.Button2Pressed: - mouseFlag = MouseFlags.Button2Pressed; - break; + // Shift = VK_SHIFT = 0x10 + // Ctrl = VK_CONTROL = 0x11 + // Alt = VK_MENU = 0x12 - case WindowsConsole.ButtonState.RightmostButtonPressed: - mouseFlag = MouseFlags.Button3Pressed; - break; + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.CapslockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.CapslockOn; } - if (point == null) - point = p; - - if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) { - mouseFlag |= MouseFlags.ReportMousePosition; - isButtonReleased = false; - processButtonClick = false; + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ScrolllockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.ScrolllockOn; } - lastMouseButtonPressed = mouseEvent.ButtonState; - isButtonPressed = true; - - if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop.AddIdle (() => { - Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag)); - return false; - }); - } - - } else if (lastMouseButtonPressed != null && mouseEvent.EventFlags == 0 - && !isButtonReleased && !isButtonDoubleClicked && !isOneFingerDoubleClicked) { - switch (lastMouseButtonPressed) { - case WindowsConsole.ButtonState.Button1Pressed: - mouseFlag = MouseFlags.Button1Released; - break; - - case WindowsConsole.ButtonState.Button2Pressed: - mouseFlag = MouseFlags.Button2Released; - break; - case WindowsConsole.ButtonState.RightmostButtonPressed: - mouseFlag = MouseFlags.Button3Released; - break; - } - isButtonPressed = false; - isButtonReleased = true; - if (point != null && (((Point)point).X == mouseEvent.MousePosition.X && ((Point)point).Y == mouseEvent.MousePosition.Y)) { - processButtonClick = true; - } else { - point = null; + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.NumlockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.NumlockOn; } - } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved - && !isOneFingerDoubleClicked && isButtonReleased && p == point) { - mouseFlag = ProcessButtonClick (mouseEvent); - - } else if (mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.DoubleClick)) { - switch (mouseEvent.ButtonState) { - case WindowsConsole.ButtonState.Button1Pressed: - mouseFlag = MouseFlags.Button1DoubleClicked; + switch (inputEvent.KeyEvent.dwControlKeyState) { + case WindowsConsole.ControlKeyState.RightAltPressed: + case WindowsConsole.ControlKeyState.RightAltPressed | + WindowsConsole.ControlKeyState.LeftControlPressed | + WindowsConsole.ControlKeyState.EnhancedKey: + case WindowsConsole.ControlKeyState.EnhancedKey: + key = new KeyEvent (Key.CtrlMask | Key.AltMask, _keyModifiers); break; - - case WindowsConsole.ButtonState.Button2Pressed: - mouseFlag = MouseFlags.Button2DoubleClicked; + case WindowsConsole.ControlKeyState.LeftAltPressed: + key = new KeyEvent (Key.AltMask, _keyModifiers); break; - - case WindowsConsole.ButtonState.RightmostButtonPressed: - mouseFlag = MouseFlags.Button3DoubleClicked; + case WindowsConsole.ControlKeyState.RightControlPressed: + case WindowsConsole.ControlKeyState.LeftControlPressed: + key = new KeyEvent (Key.CtrlMask, _keyModifiers); break; - } - isButtonDoubleClicked = true; - } else if (mouseEvent.EventFlags == 0 && mouseEvent.ButtonState != 0 && isButtonDoubleClicked) { - switch (mouseEvent.ButtonState) { - case WindowsConsole.ButtonState.Button1Pressed: - mouseFlag = MouseFlags.Button1TripleClicked; + case WindowsConsole.ControlKeyState.ShiftPressed: + key = new KeyEvent (Key.ShiftMask, _keyModifiers); break; - - case WindowsConsole.ButtonState.Button2Pressed: - mouseFlag = MouseFlags.Button2TripleClicked; + case WindowsConsole.ControlKeyState.NumlockOn: break; - - case WindowsConsole.ButtonState.RightmostButtonPressed: - mouseFlag = MouseFlags.Button3TripleClicked; + case WindowsConsole.ControlKeyState.ScrolllockOn: break; - } - isButtonDoubleClicked = false; - } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseWheeled) { - switch ((int)mouseEvent.ButtonState) { - case int v when v > 0: - mouseFlag = MouseFlags.WheeledUp; + case WindowsConsole.ControlKeyState.CapslockOn: break; - - case int v when v < 0: - mouseFlag = MouseFlags.WheeledDown; + default: + key = inputEvent.KeyEvent.wVirtualKeyCode switch { + 0x10 => new KeyEvent (Key.ShiftMask, _keyModifiers), + 0x11 => new KeyEvent (Key.CtrlMask, _keyModifiers), + 0x12 => new KeyEvent (Key.AltMask, _keyModifiers), + _ => new KeyEvent (Key.Unknown, _keyModifiers) + }; break; } - } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseWheeled && - mouseEvent.ControlKeyState == WindowsConsole.ControlKeyState.ShiftPressed) { - switch ((int)mouseEvent.ButtonState) { - case int v when v > 0: - mouseFlag = MouseFlags.WheeledLeft; - break; - - case int v when v < 0: - mouseFlag = MouseFlags.WheeledRight; - break; + if (inputEvent.KeyEvent.bKeyDown) { + _keyDownHandler (key); + } else { + _keyUpHandler (key); + } + } else { + if (inputEvent.KeyEvent.bKeyDown) { + // May occurs using SendKeys + _keyModifiers ??= new KeyModifiers (); + // Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event + _keyDownHandler (new KeyEvent (map, _keyModifiers)); + _keyHandler (new KeyEvent (map, _keyModifiers)); + } else { + _keyUpHandler (new KeyEvent (map, _keyModifiers)); } + } + if (!inputEvent.KeyEvent.bKeyDown && inputEvent.KeyEvent.dwControlKeyState == 0) { + _keyModifiers = null; + } + break; - } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseHorizontalWheeled) { - switch ((int)mouseEvent.ButtonState) { - case int v when v < 0: - mouseFlag = MouseFlags.WheeledLeft; - break; + case WindowsConsole.EventType.Mouse: + var me = ToDriverMouse (inputEvent.MouseEvent); + _mouseHandler (me); + if (_processButtonClick) { + _mouseHandler ( + new MouseEvent () { + X = me.X, + Y = me.Y, + Flags = ProcessButtonClick (inputEvent.MouseEvent) + }); + } + break; - case int v when v > 0: - mouseFlag = MouseFlags.WheeledRight; - break; - } - - } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) { - mouseFlag = MouseFlags.ReportMousePosition; - if (mouseEvent.MousePosition.X != pointMove.X || mouseEvent.MousePosition.Y != pointMove.Y) { - pointMove = new Point (mouseEvent.MousePosition.X, mouseEvent.MousePosition.Y); - } - } else if (mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0) { - mouseFlag = 0; - } - - mouseFlag = SetControlKeyStates (mouseEvent, mouseFlag); + case WindowsConsole.EventType.Focus: + break; + } + } - //System.Diagnostics.Debug.WriteLine ( - // $"point.X:{(point != null ? ((Point)point).X : -1)};point.Y:{(point != null ? ((Point)point).Y : -1)}"); + WindowsConsole.ButtonState? _lastMouseButtonPressed = null; + bool _isButtonPressed = false; + bool _isButtonReleased = false; + bool _isButtonDoubleClicked = false; + Point? _point; + Point _pointMove; + bool _isOneFingerDoubleClicked = false; + bool _processButtonClick; + + MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) + { + MouseFlags mouseFlag = MouseFlags.AllEvents; + + //System.Diagnostics.Debug.WriteLine ( + // $"X:{mouseEvent.MousePosition.X};Y:{mouseEvent.MousePosition.Y};ButtonState:{mouseEvent.ButtonState};EventFlags:{mouseEvent.EventFlags}"); + + if (_isButtonDoubleClicked || _isOneFingerDoubleClicked) { + Application.MainLoop.AddIdle (() => { + Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); + return false; + }); + } - return new MouseEvent () { - X = mouseEvent.MousePosition.X, - Y = mouseEvent.MousePosition.Y, - Flags = mouseFlag - }; + // 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 + // map to the correct clicked event. + if ((_lastMouseButtonPressed != null || _isButtonReleased) && mouseEvent.ButtonState != 0) { + _lastMouseButtonPressed = null; + //isButtonPressed = false; + _isButtonReleased = false; } - MouseFlags ProcessButtonClick (WindowsConsole.MouseEventRecord mouseEvent) - { - MouseFlags mouseFlag = 0; - switch (lastMouseButtonPressed) { + var p = new Point () { + X = mouseEvent.MousePosition.X, + Y = mouseEvent.MousePosition.Y + }; + + if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && _lastMouseButtonPressed == null && !_isButtonDoubleClicked) || + (_lastMouseButtonPressed == null && mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.MouseMoved) && + mouseEvent.ButtonState != 0 && !_isButtonReleased && !_isButtonDoubleClicked)) { + switch (mouseEvent.ButtonState) { case WindowsConsole.ButtonState.Button1Pressed: - mouseFlag = MouseFlags.Button1Clicked; + mouseFlag = MouseFlags.Button1Pressed; break; case WindowsConsole.ButtonState.Button2Pressed: - mouseFlag = MouseFlags.Button2Clicked; + mouseFlag = MouseFlags.Button2Pressed; break; case WindowsConsole.ButtonState.RightmostButtonPressed: - mouseFlag = MouseFlags.Button3Clicked; + mouseFlag = MouseFlags.Button3Pressed; break; } - point = new Point () { - X = mouseEvent.MousePosition.X, - Y = mouseEvent.MousePosition.Y - }; - lastMouseButtonPressed = null; - isButtonReleased = false; - processButtonClick = false; - point = null; - return mouseFlag; - } - async Task ProcessButtonDoubleClickedAsync () - { - await Task.Delay (300); - isButtonDoubleClicked = false; - isOneFingerDoubleClicked = false; - //buttonPressedCount = 0; - } + if (_point == null) { + _point = p; + } - async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag) - { - while (isButtonPressed) { - await Task.Delay (100); - var me = new MouseEvent () { - X = pointMove.X, - Y = pointMove.Y, - Flags = mouseFlag - }; + if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) { + mouseFlag |= MouseFlags.ReportMousePosition; + _isButtonReleased = false; + _processButtonClick = false; + } + _lastMouseButtonPressed = mouseEvent.ButtonState; + _isButtonPressed = true; - var view = Application.WantContinuousButtonPressedView; - if (view == null) { - break; - } - if (isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop.Invoke (() => mouseHandler (me)); - } + if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { + Application.MainLoop.AddIdle (() => { + Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag)); + return false; + }); } - } - static MouseFlags SetControlKeyStates (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag) - { - if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed) || - mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftControlPressed)) - mouseFlag |= MouseFlags.ButtonCtrl; + } else if (_lastMouseButtonPressed != null && mouseEvent.EventFlags == 0 + && !_isButtonReleased && !_isButtonDoubleClicked && !_isOneFingerDoubleClicked) { + switch (_lastMouseButtonPressed) { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1Released; + break; - if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ShiftPressed)) - mouseFlag |= MouseFlags.ButtonShift; + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2Released; + break; - if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightAltPressed) || - mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftAltPressed)) - mouseFlag |= MouseFlags.ButtonAlt; - return mouseFlag; - } + case WindowsConsole.ButtonState.RightmostButtonPressed: + mouseFlag = MouseFlags.Button3Released; + break; + } + _isButtonPressed = false; + _isButtonReleased = true; + if (_point != null && (((Point)_point).X == mouseEvent.MousePosition.X && ((Point)_point).Y == mouseEvent.MousePosition.Y)) { + _processButtonClick = true; + } else { + _point = null; + } + } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved + && !_isOneFingerDoubleClicked && _isButtonReleased && p == _point) { - KeyModifiers keyModifiers; + mouseFlag = ProcessButtonClick (mouseEvent); - public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent) - { - var state = keyEvent.dwControlKeyState; - - bool shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0; - bool alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0; - bool control = (state & (WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.RightControlPressed)) != 0; - bool capslock = (state & (WindowsConsole.ControlKeyState.CapslockOn)) != 0; - bool numlock = (state & (WindowsConsole.ControlKeyState.NumlockOn)) != 0; - bool scrolllock = (state & (WindowsConsole.ControlKeyState.ScrolllockOn)) != 0; - - if (keyModifiers == null) - keyModifiers = new KeyModifiers (); - if (shift) - keyModifiers.Shift = shift; - if (alt) - keyModifiers.Alt = alt; - if (control) - keyModifiers.Ctrl = control; - if (capslock) - keyModifiers.Capslock = capslock; - if (numlock) - keyModifiers.Numlock = numlock; - if (scrolllock) - keyModifiers.Scrolllock = scrolllock; - - var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control); - - return new WindowsConsole.ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock, scrolllock); - } - - public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent) - { - if (keyEvent.wVirtualKeyCode != (uint)ConsoleKey.Packet) { - return keyEvent; - } + } else if (mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.DoubleClick)) { + switch (mouseEvent.ButtonState) { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1DoubleClicked; + break; - var mod = new ConsoleModifiers (); - if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ShiftPressed)) { - mod |= ConsoleModifiers.Shift; - } - if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightAltPressed) || - keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftAltPressed)) { - mod |= ConsoleModifiers.Alt; - } - if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftControlPressed) || - keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) { - mod |= ConsoleModifiers.Control; - } - var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyEvent.UnicodeChar, mod, out uint virtualKey, out uint scanCode); - - return new WindowsConsole.KeyEventRecord { - UnicodeChar = (char)keyChar, - bKeyDown = keyEvent.bKeyDown, - dwControlKeyState = keyEvent.dwControlKeyState, - wRepeatCount = keyEvent.wRepeatCount, - wVirtualKeyCode = (ushort)virtualKey, - wVirtualScanCode = (ushort)scanCode - }; - } + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2DoubleClicked; + break; - public Key MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) - { - var keyInfo = keyInfoEx.consoleKeyInfo; - switch (keyInfo.Key) { - case ConsoleKey.Escape: - return MapKeyModifiers (keyInfo, Key.Esc); - case ConsoleKey.Tab: - return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab; - case ConsoleKey.Clear: - return MapKeyModifiers (keyInfo, Key.Clear); - case ConsoleKey.Home: - return MapKeyModifiers (keyInfo, Key.Home); - case ConsoleKey.End: - return MapKeyModifiers (keyInfo, Key.End); - case ConsoleKey.LeftArrow: - return MapKeyModifiers (keyInfo, Key.CursorLeft); - case ConsoleKey.RightArrow: - return MapKeyModifiers (keyInfo, Key.CursorRight); - case ConsoleKey.UpArrow: - return MapKeyModifiers (keyInfo, Key.CursorUp); - case ConsoleKey.DownArrow: - return MapKeyModifiers (keyInfo, Key.CursorDown); - case ConsoleKey.PageUp: - return MapKeyModifiers (keyInfo, Key.PageUp); - case ConsoleKey.PageDown: - return MapKeyModifiers (keyInfo, Key.PageDown); - case ConsoleKey.Enter: - return MapKeyModifiers (keyInfo, Key.Enter); - case ConsoleKey.Spacebar: - return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar); - case ConsoleKey.Backspace: - return MapKeyModifiers (keyInfo, Key.Backspace); - case ConsoleKey.Delete: - return MapKeyModifiers (keyInfo, Key.DeleteChar); - case ConsoleKey.Insert: - return MapKeyModifiers (keyInfo, Key.InsertChar); - case ConsoleKey.PrintScreen: - return MapKeyModifiers (keyInfo, Key.PrintScreen); - - case ConsoleKey.NumPad0: - return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar; - case ConsoleKey.NumPad1: - return keyInfoEx.NumLock ? Key.D1 : Key.End; - case ConsoleKey.NumPad2: - return keyInfoEx.NumLock ? Key.D2 : Key.CursorDown; - case ConsoleKey.NumPad3: - return keyInfoEx.NumLock ? Key.D3 : Key.PageDown; - case ConsoleKey.NumPad4: - return keyInfoEx.NumLock ? Key.D4 : Key.CursorLeft; - case ConsoleKey.NumPad5: - return keyInfoEx.NumLock ? Key.D5 : (Key)((uint)keyInfo.KeyChar); - case ConsoleKey.NumPad6: - return keyInfoEx.NumLock ? Key.D6 : Key.CursorRight; - case ConsoleKey.NumPad7: - return keyInfoEx.NumLock ? Key.D7 : Key.Home; - case ConsoleKey.NumPad8: - return keyInfoEx.NumLock ? Key.D8 : Key.CursorUp; - case ConsoleKey.NumPad9: - return keyInfoEx.NumLock ? Key.D9 : Key.PageUp; - - case ConsoleKey.Oem1: - case ConsoleKey.Oem2: - case ConsoleKey.Oem3: - case ConsoleKey.Oem4: - case ConsoleKey.Oem5: - case ConsoleKey.Oem6: - case ConsoleKey.Oem7: - case ConsoleKey.Oem8: - case ConsoleKey.Oem102: - case ConsoleKey.OemPeriod: - case ConsoleKey.OemComma: - case ConsoleKey.OemPlus: - case ConsoleKey.OemMinus: - if (keyInfo.KeyChar == 0) - return Key.Unknown; - - return (Key)((uint)keyInfo.KeyChar); + case WindowsConsole.ButtonState.RightmostButtonPressed: + mouseFlag = MouseFlags.Button3DoubleClicked; + break; } + _isButtonDoubleClicked = true; + } else if (mouseEvent.EventFlags == 0 && mouseEvent.ButtonState != 0 && _isButtonDoubleClicked) { + switch (mouseEvent.ButtonState) { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1TripleClicked; + break; - var key = keyInfo.Key; - //var alphaBase = ((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock)) ? 'A' : 'a'; + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2TripleClicked; + break; - if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { - var delta = key - ConsoleKey.A; - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta)); - } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); - } - } - //return (Key)((uint)alphaBase + delta); - return (Key)((uint)keyInfo.KeyChar); + case WindowsConsole.ButtonState.RightmostButtonPressed: + mouseFlag = MouseFlags.Button3TripleClicked; + break; } - if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { - var delta = key - ConsoleKey.D0; - if (keyInfo.Modifiers == ConsoleModifiers.Alt) { - return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta)); - } - if (keyInfo.Modifiers == ConsoleModifiers.Control) { - return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta)); - } - if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); - } - if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); - } - } - return (Key)((uint)keyInfo.KeyChar); + _isButtonDoubleClicked = false; + } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseWheeled) { + switch ((int)mouseEvent.ButtonState) { + case int v when v > 0: + mouseFlag = MouseFlags.WheeledUp; + break; + + case int v when v < 0: + mouseFlag = MouseFlags.WheeledDown; + break; } - if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { - var delta = key - ConsoleKey.F1; - if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); - } - return (Key)((uint)Key.F1 + delta); + } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseWheeled && + mouseEvent.ControlKeyState == WindowsConsole.ControlKeyState.ShiftPressed) { + switch ((int)mouseEvent.ButtonState) { + case int v when v > 0: + mouseFlag = MouseFlags.WheeledLeft; + break; + + case int v when v < 0: + mouseFlag = MouseFlags.WheeledRight; + break; } - if (keyInfo.KeyChar != 0) { - return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar)); + + } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseHorizontalWheeled) { + switch ((int)mouseEvent.ButtonState) { + case int v when v < 0: + mouseFlag = MouseFlags.WheeledLeft; + break; + + case int v when v > 0: + mouseFlag = MouseFlags.WheeledRight; + break; } - return (Key)(0xffffffff); + } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) { + mouseFlag = MouseFlags.ReportMousePosition; + if (mouseEvent.MousePosition.X != _pointMove.X || mouseEvent.MousePosition.Y != _pointMove.Y) { + _pointMove = new Point (mouseEvent.MousePosition.X, mouseEvent.MousePosition.Y); + } + } else if (mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0) { + mouseFlag = 0; } - private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) - { - Key keyMod = new Key (); - if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) - keyMod = Key.ShiftMask; - if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) - keyMod |= Key.CtrlMask; - if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) - keyMod |= Key.AltMask; + mouseFlag = SetControlKeyStates (mouseEvent, mouseFlag); - return keyMod != Key.Null ? keyMod | key : key; - } + //System.Diagnostics.Debug.WriteLine ( + // $"point.X:{(point != null ? ((Point)point).X : -1)};point.Y:{(point != null ? ((Point)point).Y : -1)}"); - public override void Init (Action terminalResized) - { - TerminalResized = terminalResized; + return new MouseEvent () { + X = mouseEvent.MousePosition.X, + Y = mouseEvent.MousePosition.Y, + Flags = mouseFlag + }; + } - try { - // Needed for Windows Terminal - // ESC [ ? 1047 h Activate xterm alternative buffer (no backscroll) - // ESC [ ? 1047 l Restore xterm working buffer (with backscroll) - // ESC [ ? 1048 h Save cursor position - // ESC [ ? 1048 l Restore cursor position - // ESC [ ? 1049 h Save cursor position and activate xterm alternative buffer (no backscroll) - // ESC [ ? 1049 l Restore cursor position and restore xterm working buffer (with backscroll) - // Per Issue #2264 using the alterantive screen buffer is required for Windows Terminal to not - // wipe out the backscroll buffer when the application exits. - Console.Out.Write ("\x1b[?1047h"); - - // Console.Out.Flush () is not needed. See https://stackoverflow.com/a/20450486/297526 - - var winSize = WinConsole.GetConsoleOutputWindow (out Point pos); - cols = winSize.Width; - rows = winSize.Height; - WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); - - CurrentAttribute = MakeColor (Color.White, Color.Black); - InitalizeColorSchemes (); - - CurrentAttribute = MakeColor (Color.White, Color.Black); - InitalizeColorSchemes (); - - ResizeScreen (); - UpdateOffScreen (); - } catch (Win32Exception e) { - throw new InvalidOperationException ("The Windows Console output window is not available.", e); - } - } + MouseFlags ProcessButtonClick (WindowsConsole.MouseEventRecord mouseEvent) + { + MouseFlags mouseFlag = 0; + switch (_lastMouseButtonPressed) { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1Clicked; + break; + + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2Clicked; + break; + + case WindowsConsole.ButtonState.RightmostButtonPressed: + mouseFlag = MouseFlags.Button3Clicked; + break; + } + _point = new Point () { + X = mouseEvent.MousePosition.X, + Y = mouseEvent.MousePosition.Y + }; + _lastMouseButtonPressed = null; + _isButtonReleased = false; + _processButtonClick = false; + _point = null; + return mouseFlag; + } - public override void ResizeScreen () - { - OutputBuffer = new WindowsConsole.CharInfo [Rows * Cols]; - Clip = new Rect (0, 0, Cols, Rows); - damageRegion = new WindowsConsole.SmallRect () { - Top = 0, - Left = 0, - Bottom = (short)Rows, - Right = (short)Cols + async Task ProcessButtonDoubleClickedAsync () + { + await Task.Delay (300); + _isButtonDoubleClicked = false; + _isOneFingerDoubleClicked = false; + //buttonPressedCount = 0; + } + + async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag) + { + while (_isButtonPressed) { + await Task.Delay (100); + var me = new MouseEvent () { + X = _pointMove.X, + Y = _pointMove.Y, + Flags = mouseFlag }; - WinConsole.ForceRefreshCursorVisibility (); - if (!EnableConsoleScrolling) { - // ANSI ESC "[xJ" Clears part of the screen. - // If n is 0 (or missing), clear from cursor to end of screen. - // If n is 1, clear from cursor to beginning of the screen. - // If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS). - // If n is 3, clear entire screen and delete all lines saved in the scrollback buffer - // DO NOT USE 3J - even with the alternate screen buffer, it clears the entire scrollback buffer - Console.Out.Write ("\x1b[3J"); + + var view = Application.WantContinuousButtonPressedView; + if (view == null) { + break; + } + if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { + Application.MainLoop.Invoke (() => _mouseHandler (me)); } } + } - public override void UpdateOffScreen () - { - contents = new int [rows, cols, 3]; - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - int position = row * cols + col; - OutputBuffer [position].Attributes = (ushort)Colors.TopLevel.Normal; - OutputBuffer [position].Char.UnicodeChar = ' '; - contents [row, col, 0] = OutputBuffer [position].Char.UnicodeChar; - contents [row, col, 1] = OutputBuffer [position].Attributes; - contents [row, col, 2] = 0; - } - } + static MouseFlags SetControlKeyStates (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag) + { + if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed) || + mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftControlPressed)) { + mouseFlag |= MouseFlags.ButtonCtrl; } - int ccol, crow; - public override void Move (int col, int row) - { - ccol = col; - crow = row; + if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ShiftPressed)) { + mouseFlag |= MouseFlags.ButtonShift; } - int GetOutputBufferPosition () - { - return crow * Cols + ccol; + if (mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightAltPressed) || + mouseEvent.ControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftAltPressed)) { + mouseFlag |= MouseFlags.ButtonAlt; } + return mouseFlag; + } - public override bool IsRuneSupported (Rune rune) - { - // See Issue #2610 - return base.IsRuneSupported (rune) && rune.IsBmp; + KeyModifiers _keyModifiers; + + public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent) + { + var state = keyEvent.dwControlKeyState; + + var shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0; + var alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0; + var control = (state & (WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.RightControlPressed)) != 0; + var capsLock = (state & (WindowsConsole.ControlKeyState.CapslockOn)) != 0; + var numLock = (state & (WindowsConsole.ControlKeyState.NumlockOn)) != 0; + var scrollLock = (state & (WindowsConsole.ControlKeyState.ScrolllockOn)) != 0; + + _keyModifiers ??= new KeyModifiers (); + if (shift) { + _keyModifiers.Shift = true; + } + if (alt) { + _keyModifiers.Alt = true; + } + if (control) { + _keyModifiers.Ctrl = true; + } + if (capsLock) { + _keyModifiers.Capslock = true; + } + if (numLock) { + _keyModifiers.Numlock = true; + } + if (scrollLock) { + _keyModifiers.Scrolllock = true; } - public override void AddRune (Rune rune) - { - if (!IsRuneSupported (rune)) { - rune = Rune.ReplacementChar; - } + var consoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control); - rune = rune.MakePrintable (); - var runeWidth = rune.GetColumns (); - var position = GetOutputBufferPosition (); - var validClip = IsValidContent (ccol, crow, Clip); - - if (validClip) { - if (runeWidth == 0 && ccol > 0) { - var r = contents [crow, ccol - 1, 0]; - var s = new string (new char [] { (char)r, (char)rune.Value }); - string sn; - if (!s.IsNormalized ()) { - sn = s.Normalize (); - } else { - sn = s; - } - var c = sn [0]; - var prevPosition = crow * Cols + (ccol - 1); - OutputBuffer [prevPosition].Char.UnicodeChar = c; - contents [crow, ccol - 1, 0] = c; - OutputBuffer [prevPosition].Attributes = (ushort)CurrentAttribute; - contents [crow, ccol - 1, 1] = CurrentAttribute; - contents [crow, ccol - 1, 2] = 1; - WindowsConsole.SmallRect.Update (ref damageRegion, (short)(ccol - 1), (short)crow); - } else { - if (runeWidth < 2 && ccol > 0 - && ((Rune)(char)contents [crow, ccol - 1, 0]).GetColumns () > 1) { + return new WindowsConsole.ConsoleKeyInfoEx (consoleKeyInfo, capsLock, numLock, scrollLock); + } - var prevPosition = crow * Cols + (ccol - 1); - OutputBuffer [prevPosition].Char.UnicodeChar = ' '; - contents [crow, ccol - 1, 0] = (int)(uint)' '; + public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent) + { + if (keyEvent.wVirtualKeyCode != (uint)ConsoleKey.Packet) { + return keyEvent; + } - } else if (runeWidth < 2 && ccol <= Clip.Right - 1 - && ((Rune)(char)contents [crow, ccol, 0]).GetColumns () > 1) { + var mod = new ConsoleModifiers (); + if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ShiftPressed)) { + mod |= ConsoleModifiers.Shift; + } + if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightAltPressed) || + keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftAltPressed)) { + mod |= ConsoleModifiers.Alt; + } + if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftControlPressed) || + keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) { + mod |= ConsoleModifiers.Control; + } + var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyEvent.UnicodeChar, mod, out uint virtualKey, out uint scanCode); - var prevPosition = GetOutputBufferPosition () + 1; - OutputBuffer [prevPosition].Char.UnicodeChar = (char)' '; - contents [crow, ccol + 1, 0] = (int)(uint)' '; + return new WindowsConsole.KeyEventRecord { + UnicodeChar = (char)keyChar, + bKeyDown = keyEvent.bKeyDown, + dwControlKeyState = keyEvent.dwControlKeyState, + wRepeatCount = keyEvent.wRepeatCount, + wVirtualKeyCode = (ushort)virtualKey, + wVirtualScanCode = (ushort)scanCode + }; + } - } - if (runeWidth > 1 && ccol == Clip.Right - 1) { - OutputBuffer [position].Char.UnicodeChar = (char)' '; - contents [crow, ccol, 0] = (int)(uint)' '; - } else { - OutputBuffer [position].Char.UnicodeChar = (char)rune.Value; - contents [crow, ccol, 0] = (int)(uint)rune.Value; - } - OutputBuffer [position].Attributes = (ushort)CurrentAttribute; - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 1; - WindowsConsole.SmallRect.Update (ref damageRegion, (short)ccol, (short)crow); + public Key MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) + { + var keyInfo = keyInfoEx.ConsoleKeyInfo; + switch (keyInfo.Key) { + case ConsoleKey.Escape: + return MapKeyModifiers (keyInfo, Key.Esc); + case ConsoleKey.Tab: + return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab; + case ConsoleKey.Clear: + return MapKeyModifiers (keyInfo, Key.Clear); + case ConsoleKey.Home: + return MapKeyModifiers (keyInfo, Key.Home); + case ConsoleKey.End: + return MapKeyModifiers (keyInfo, Key.End); + case ConsoleKey.LeftArrow: + return MapKeyModifiers (keyInfo, Key.CursorLeft); + case ConsoleKey.RightArrow: + return MapKeyModifiers (keyInfo, Key.CursorRight); + case ConsoleKey.UpArrow: + return MapKeyModifiers (keyInfo, Key.CursorUp); + case ConsoleKey.DownArrow: + return MapKeyModifiers (keyInfo, Key.CursorDown); + case ConsoleKey.PageUp: + return MapKeyModifiers (keyInfo, Key.PageUp); + case ConsoleKey.PageDown: + return MapKeyModifiers (keyInfo, Key.PageDown); + case ConsoleKey.Enter: + return MapKeyModifiers (keyInfo, Key.Enter); + case ConsoleKey.Spacebar: + return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar); + case ConsoleKey.Backspace: + return MapKeyModifiers (keyInfo, Key.Backspace); + case ConsoleKey.Delete: + return MapKeyModifiers (keyInfo, Key.DeleteChar); + case ConsoleKey.Insert: + return MapKeyModifiers (keyInfo, Key.InsertChar); + case ConsoleKey.PrintScreen: + return MapKeyModifiers (keyInfo, Key.PrintScreen); + + case ConsoleKey.NumPad0: + return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar; + case ConsoleKey.NumPad1: + return keyInfoEx.NumLock ? Key.D1 : Key.End; + case ConsoleKey.NumPad2: + return keyInfoEx.NumLock ? Key.D2 : Key.CursorDown; + case ConsoleKey.NumPad3: + return keyInfoEx.NumLock ? Key.D3 : Key.PageDown; + case ConsoleKey.NumPad4: + return keyInfoEx.NumLock ? Key.D4 : Key.CursorLeft; + case ConsoleKey.NumPad5: + return keyInfoEx.NumLock ? Key.D5 : (Key)((uint)keyInfo.KeyChar); + case ConsoleKey.NumPad6: + return keyInfoEx.NumLock ? Key.D6 : Key.CursorRight; + case ConsoleKey.NumPad7: + return keyInfoEx.NumLock ? Key.D7 : Key.Home; + case ConsoleKey.NumPad8: + return keyInfoEx.NumLock ? Key.D8 : Key.CursorUp; + case ConsoleKey.NumPad9: + return keyInfoEx.NumLock ? Key.D9 : Key.PageUp; + + case ConsoleKey.Oem1: + case ConsoleKey.Oem2: + case ConsoleKey.Oem3: + case ConsoleKey.Oem4: + case ConsoleKey.Oem5: + case ConsoleKey.Oem6: + case ConsoleKey.Oem7: + case ConsoleKey.Oem8: + case ConsoleKey.Oem102: + case ConsoleKey.OemPeriod: + case ConsoleKey.OemComma: + case ConsoleKey.OemPlus: + case ConsoleKey.OemMinus: + if (keyInfo.KeyChar == 0) { + return Key.Unknown; + } + + return (Key)((uint)keyInfo.KeyChar); + } + + var key = keyInfo.Key; + //var alphaBase = ((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock)) ? 'A' : 'a'; + + if (key >= ConsoleKey.A && key <= ConsoleKey.Z) { + var delta = key - ConsoleKey.A; + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta)); + } + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { + return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta)); + } + if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); + } + if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); } } - - if (runeWidth < 0 || runeWidth > 0) { - ccol++; + //return (Key)((uint)alphaBase + delta); + return (Key)((uint)keyInfo.KeyChar); + } + if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { + var delta = key - ConsoleKey.D0; + if (keyInfo.Modifiers == ConsoleModifiers.Alt) { + return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta)); } - - if (runeWidth > 1) { - if (validClip && ccol < Clip.Right) { - position = GetOutputBufferPosition (); - OutputBuffer [position].Attributes = (ushort)CurrentAttribute; - OutputBuffer [position].Char.UnicodeChar = (char)0x00; - contents [crow, ccol, 0] = (int)(uint)0x00; - contents [crow, ccol, 1] = CurrentAttribute; - contents [crow, ccol, 2] = 0; - } - ccol++; + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta)); } - - if (sync) { - UpdateScreen (); + if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); + } + if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); + } } + return (Key)((uint)keyInfo.KeyChar); } + if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { + var delta = key - ConsoleKey.F1; + if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); + } - public override void AddStr (string str) - { - foreach (var rune in str.EnumerateRunes ()) - AddRune (rune); + return (Key)((uint)Key.F1 + delta); } - - public override void SetAttribute (Attribute c) - { - base.SetAttribute (c); + if (keyInfo.KeyChar != 0) { + return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar)); } - public override Attribute MakeColor (Color foreground, Color background) - { - return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background); - } + return (Key)(0xffffffff); + } - Attribute MakeColor (ConsoleColor f, ConsoleColor b) - { - // Encode the colors into the int value. - return new Attribute ( - value: ((int)f | (int)b << 4), - foreground: (Color)f, - background: (Color)b - ); + private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) + { + Key keyMod = new Key (); + if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) { + keyMod = Key.ShiftMask; } - - public override Attribute MakeAttribute (Color fore, Color back) - { - return MakeColor ((ConsoleColor)fore, (ConsoleColor)back); + if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) { + keyMod |= Key.CtrlMask; + } + if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) { + keyMod |= Key.AltMask; } - public override void Refresh () - { - UpdateScreen (); + return keyMod != Key.Null ? keyMod | key : key; + } - WinConsole.SetInitialCursorVisibility (); + public override bool IsRuneSupported (Rune rune) + { + return base.IsRuneSupported (rune) && rune.IsBmp; + } - UpdateCursor (); -#if false - var bufferCoords = new WindowsConsole.Coord (){ - X = (short)Clip.Width, - Y = (short)Clip.Height - }; + public override void Init (Action terminalResized) + { + TerminalResized = terminalResized; - var window = new WindowsConsole.SmallRect (){ - Top = 0, - Left = 0, - Right = (short)Clip.Right, - Bottom = (short)Clip.Bottom - }; + try { + // Needed for Windows Terminal + Console.Out.Write (EscSeqUtils.CSI_ActivateAltBufferNoBackscroll); - UpdateCursor(); - WinConsole.WriteToConsole (OutputBuffer, bufferCoords, window); -#endif + var winSize = WinConsole.GetConsoleOutputWindow (out Point pos); + Cols = winSize.Width; + Rows = winSize.Height; + WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); + + } catch (Win32Exception) { + // Likely running unit tests. Set WinConsole to null so we can test it elsewhere. + WinConsole = null; } - public override void UpdateScreen () - { - if (damageRegion.Left == -1) - return; + CurrentAttribute = MakeColor (Color.White, Color.Black); + InitializeColorSchemes (); - if (!EnableConsoleScrolling) { - var windowSize = WinConsole.GetConsoleBufferWindow (out _); - if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) - return; - } + _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; + Clip = new Rect (0, 0, Cols, Rows); + _damageRegion = new WindowsConsole.SmallRect () { + Top = 0, + Left = 0, + Bottom = (short)Rows, + Right = (short)Cols + }; - var bufferCoords = new WindowsConsole.Coord () { - X = (short)Clip.Width, - Y = (short)Clip.Height - }; + ClearContents (); + } + + public virtual void ResizeScreen () + { + if (WinConsole == null) { + return; + } - //var window = new WindowsConsole.SmallRect () { - // Top = 0, - // Left = 0, - // Right = (short)Clip.Right, - // Bottom = (short)Clip.Bottom - //}; + _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; + Clip = new Rect (0, 0, Cols, Rows); + _damageRegion = new WindowsConsole.SmallRect () { + Top = 0, + Left = 0, + Bottom = (short)Rows, + Right = (short)Cols + }; + _dirtyLines = new bool [Rows]; - WinConsole.WriteToConsole (new Size (Cols, Rows), OutputBuffer, bufferCoords, damageRegion); + WinConsole.ForceRefreshCursorVisibility (); + } - // System.Diagnostics.Debugger.Log (0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n"); - WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); + + public override void UpdateScreen () + { + var windowSize = WinConsole.GetConsoleBufferWindow (out _); + if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) { + return; } - CursorVisibility savedCursorVisibility; + var bufferCoords = new WindowsConsole.Coord () { + X = (short)Clip.Width, + Y = (short)Clip.Height + }; - public override void UpdateCursor () - { - if (ccol < 0 || crow < 0 || ccol > Cols || crow > Rows) { - GetCursorVisibility (out CursorVisibility cursorVisibility); - savedCursorVisibility = cursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - return; + for (int row = 0; row < Rows; row++) { + if (!_dirtyLines [row]) { + continue; } + _dirtyLines [row] = false; - SetCursorVisibility (savedCursorVisibility); - var position = new WindowsConsole.Coord () { - X = (short)ccol, - Y = (short)crow - }; - WinConsole.SetCursorPosition (position); + for (int col = 0; col < Cols; col++) { + int position = row * Cols + col; + _outputBuffer [position].Attribute = Contents [row, col].Attribute.GetValueOrDefault (); + if (Contents [row, col].IsDirty == false) { + _outputBuffer [position].Empty = true; + _outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; + continue; + } + _outputBuffer [position].Empty = false; + if (Contents [row, col].Runes [0].IsBmp) { + _outputBuffer [position].Char = (char)Contents [row, col].Runes [0].Value; + } else { + //_outputBuffer [position].Empty = true; + _outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; + if (Contents [row, col].Runes [0].GetColumns () > 1 && col + 1 < Cols) { + // TODO: This is a hack to deal with non-BMP and wide characters. + col++; + position = row * Cols + col; + _outputBuffer [position].Empty = false; + _outputBuffer [position].Char = ' '; + } + } + } } - public override void End () - { - WinConsole.Cleanup (); - WinConsole = null; + WinConsole.WriteToConsole (new Size (Cols, Rows), _outputBuffer, bufferCoords, _damageRegion, UseTrueColor); + WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); + } - // Needed for Windows Terminal - // Clear the alternative screen buffer from the cursor to the - // end of the screen. - // Note, [3J causes Windows Terminal to wipe out the entire NON ALTERNATIVE - // backbuffer! So we need to use [0J instead. - Console.Out.Write ("\x1b[0J"); + public override void Refresh () + { + UpdateScreen (); + WinConsole.SetInitialCursorVisibility (); + UpdateCursor (); + } - // Disable alternative screen buffer. - Console.Out.Write ("\x1b[?1047l"); + #region Color Handling - // Console.Out.Flush () is not needed. See https://stackoverflow.com/a/20450486/297526 - } + /// + /// In the WindowsDriver, colors are encoded as an int. + /// The background color is stored in the least significant 4 bits, + /// and the foreground color is stored in the next 4 bits. + /// + public override Attribute MakeColor (Color foreground, Color background) + { + // Encode the colors into the int value. + return new Attribute ( + value: (((int)foreground) | ((int)background << 4)), + foreground: foreground, + background: background + ); + } - /// - public override bool GetCursorVisibility (out CursorVisibility visibility) - { - return WinConsole.GetCursorVisibility (out visibility); - } + /// + /// Extracts the foreground and background colors from the encoded value. + /// Assumes a 4-bit encoded value for both foreground and background colors. + /// + internal override void GetColors (int value, out Color foreground, out Color background) + { + // Assume a 4-bit encoded value for both foreground and background colors. + foreground = (Color)((value >> 16) & 0xF); + background = (Color)(value & 0xF); + } - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - savedCursorVisibility = visibility; - return WinConsole.SetCursorVisibility (visibility); - } + #endregion - /// - public override bool EnsureCursorVisibility () - { - return WinConsole.EnsureCursorVisibility (); - } + CursorVisibility savedCursorVisibility; - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) - { - WindowsConsole.InputRecord input = new WindowsConsole.InputRecord { - EventType = WindowsConsole.EventType.Key - }; + public override void UpdateCursor () + { + if (Col < 0 || Row < 0 || Col > Cols || Row > Rows) { + GetCursorVisibility (out CursorVisibility cursorVisibility); + savedCursorVisibility = cursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + return; + } - WindowsConsole.KeyEventRecord keyEvent = new WindowsConsole.KeyEventRecord { - bKeyDown = true - }; - WindowsConsole.ControlKeyState controlKey = new WindowsConsole.ControlKeyState (); - if (shift) { - controlKey |= WindowsConsole.ControlKeyState.ShiftPressed; - keyEvent.UnicodeChar = '\0'; - keyEvent.wVirtualKeyCode = 16; - } - if (alt) { - controlKey |= WindowsConsole.ControlKeyState.LeftAltPressed; - controlKey |= WindowsConsole.ControlKeyState.RightAltPressed; - keyEvent.UnicodeChar = '\0'; - keyEvent.wVirtualKeyCode = 18; - } - if (control) { - controlKey |= WindowsConsole.ControlKeyState.LeftControlPressed; - controlKey |= WindowsConsole.ControlKeyState.RightControlPressed; - keyEvent.UnicodeChar = '\0'; - keyEvent.wVirtualKeyCode = 17; - } - keyEvent.dwControlKeyState = controlKey; + SetCursorVisibility (savedCursorVisibility); + var position = new WindowsConsole.Coord () { + X = (short)Col, + Y = (short)Row + }; + WinConsole.SetCursorPosition (position); + } - input.KeyEvent = keyEvent; + /// + public override bool GetCursorVisibility (out CursorVisibility visibility) + { + return WinConsole.GetCursorVisibility (out visibility); + } - if (shift || alt || control) { - ProcessInput (input); - } + /// + public override bool SetCursorVisibility (CursorVisibility visibility) + { + savedCursorVisibility = visibility; + return WinConsole.SetCursorVisibility (visibility); + } - keyEvent.UnicodeChar = keyChar; - if ((uint)key < 255) { - keyEvent.wVirtualKeyCode = (ushort)key; - } else { - keyEvent.wVirtualKeyCode = '\0'; - } + /// + public override bool EnsureCursorVisibility () + { + return WinConsole.EnsureCursorVisibility (); + } - input.KeyEvent = keyEvent; + public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) + { + WindowsConsole.InputRecord input = new WindowsConsole.InputRecord { + EventType = WindowsConsole.EventType.Key + }; - try { - ProcessInput (input); - } catch (OverflowException) { } finally { - keyEvent.bKeyDown = false; - input.KeyEvent = keyEvent; - ProcessInput (input); - } + WindowsConsole.KeyEventRecord keyEvent = new WindowsConsole.KeyEventRecord { + bKeyDown = true + }; + WindowsConsole.ControlKeyState controlKey = new WindowsConsole.ControlKeyState (); + if (shift) { + controlKey |= WindowsConsole.ControlKeyState.ShiftPressed; + keyEvent.UnicodeChar = '\0'; + keyEvent.wVirtualKeyCode = 16; } - - public override bool GetColors (int value, out Color foreground, out Color background) - { - bool hasColor = false; - foreground = default; - background = default; - IEnumerable values = Enum.GetValues (typeof (ConsoleColor)) - .OfType () - .Select (s => (int)s); - if (values.Contains ((value >> 4) & 0xffff)) { - hasColor = true; - background = (Color)(ConsoleColor)((value >> 4) & 0xffff); - } - if (values.Contains (value - ((int)background << 4))) { - hasColor = true; - foreground = (Color)(ConsoleColor)(value - ((int)background << 4)); - } - return hasColor; + if (alt) { + controlKey |= WindowsConsole.ControlKeyState.LeftAltPressed; + controlKey |= WindowsConsole.ControlKeyState.RightAltPressed; + keyEvent.UnicodeChar = '\0'; + keyEvent.wVirtualKeyCode = 18; } - - #region Unused - public override void SetColors (ConsoleColor foreground, ConsoleColor background) - { + if (control) { + controlKey |= WindowsConsole.ControlKeyState.LeftControlPressed; + controlKey |= WindowsConsole.ControlKeyState.RightControlPressed; + keyEvent.UnicodeChar = '\0'; + keyEvent.wVirtualKeyCode = 17; } + keyEvent.dwControlKeyState = controlKey; - public override void SetColors (short foregroundColorId, short backgroundColorId) - { - } + input.KeyEvent = keyEvent; - public override void Suspend () - { + if (shift || alt || control) { + ProcessInput (input); } - public override void StartReportingMouseMoves () - { + keyEvent.UnicodeChar = keyChar; + if ((uint)key < 255) { + keyEvent.wVirtualKeyCode = (ushort)key; + } else { + keyEvent.wVirtualKeyCode = '\0'; } - public override void StopReportingMouseMoves () - { - } + input.KeyEvent = keyEvent; - public override void UncookMouse () - { + try { + ProcessInput (input); + } catch (OverflowException) { } finally { + keyEvent.bKeyDown = false; + input.KeyEvent = keyEvent; + ProcessInput (input); } + } - public override void CookMouse () - { - } - #endregion + public override void End () + { + WinConsole?.Cleanup (); + WinConsole = null; + + // Disable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_RestoreAltBufferWithBackscroll); } + #region Not Implemented + public override void Suspend () + { + throw new NotImplementedException (); + } + #endregion +} + +/// +/// Mainloop intended to be used with the , and can +/// only be used on Windows. +/// +/// +/// This implementation is used for WindowsDriver. +/// +internal class WindowsMainLoop : IMainLoopDriver { + ManualResetEventSlim _eventReady = new ManualResetEventSlim (false); + ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false); + ManualResetEventSlim _winChange = new ManualResetEventSlim (false); + MainLoop _mainLoop; + ConsoleDriver _consoleDriver; + WindowsConsole _winConsole; + bool _winChanged; + Size _windowSize; + CancellationTokenSource _tokenSource = new CancellationTokenSource (); + + // The records that we keep fetching + Queue _resultQueue = new Queue (); + /// - /// Mainloop intended to be used with the , and can - /// only be used on Windows. + /// Invoked when a Key is pressed or released. /// - /// - /// This implementation is used for WindowsDriver. - /// - internal class WindowsMainLoop : IMainLoopDriver { - ManualResetEventSlim eventReady = new ManualResetEventSlim (false); - ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); - ManualResetEventSlim winChange = new ManualResetEventSlim (false); - MainLoop mainLoop; - ConsoleDriver consoleDriver; - WindowsConsole winConsole; - bool winChanged; - Size windowSize; - CancellationTokenSource tokenSource = new CancellationTokenSource (); - - // The records that we keep fetching - Queue resultQueue = new Queue (); - - /// - /// Invoked when a Key is pressed or released. - /// - public Action ProcessInput; - - /// - /// Invoked when the window is changed. - /// - public EventHandler WinChanged; - - public WindowsMainLoop (ConsoleDriver consoleDriver = null) - { - this.consoleDriver = consoleDriver ?? throw new ArgumentNullException ("Console driver instance must be provided."); - winConsole = ((WindowsDriver)consoleDriver).WinConsole; - } + public Action ProcessInput; - void IMainLoopDriver.Setup (MainLoop mainLoop) - { - this.mainLoop = mainLoop; - Task.Run (WindowsInputHandler); - Task.Run (CheckWinChange); - } + /// + /// Invoked when the window is changed. + /// + public EventHandler WinChanged; - void WindowsInputHandler () - { - while (true) { - waitForProbe.Wait (); - waitForProbe.Reset (); + public WindowsMainLoop (ConsoleDriver consoleDriver = null) + { + _consoleDriver = consoleDriver ?? throw new ArgumentNullException ("Console driver instance must be provided."); + _winConsole = ((WindowsDriver)consoleDriver).WinConsole; + } - if (resultQueue?.Count == 0) { - resultQueue.Enqueue (winConsole.ReadConsoleInput ()); - } + void IMainLoopDriver.Setup (MainLoop mainLoop) + { + _mainLoop = mainLoop; + Task.Run (WindowsInputHandler); + Task.Run (CheckWinChange); + } - eventReady.Set (); - } - } + void WindowsInputHandler () + { + while (true) { + _waitForProbe.Wait (); + _waitForProbe.Reset (); - void CheckWinChange () - { - while (true) { - winChange.Wait (); - winChange.Reset (); - WaitWinChange (); - winChanged = true; - eventReady.Set (); + if (_resultQueue?.Count == 0) { + _resultQueue.Enqueue (_winConsole.ReadConsoleInput ()); } + + _eventReady.Set (); } + } - void WaitWinChange () - { - while (true) { - Thread.Sleep (100); - if (!consoleDriver.EnableConsoleScrolling) { - windowSize = winConsole.GetConsoleBufferWindow (out _); - //System.Diagnostics.Debug.WriteLine ($"{consoleDriver.EnableConsoleScrolling},{windowSize.Width},{windowSize.Height}"); - if (windowSize != Size.Empty && windowSize.Width != consoleDriver.Cols - || windowSize.Height != consoleDriver.Rows) { - return; - } - } - } + void CheckWinChange () + { + while (true) { + _winChange.Wait (); + _winChange.Reset (); + WaitWinChange (); + _winChanged = true; + _eventReady.Set (); } + } - void IMainLoopDriver.Wakeup () - { - //tokenSource.Cancel (); - eventReady.Set (); + void WaitWinChange () + { + while (true) { + Task.Delay (500).Wait (); + _windowSize = _winConsole.GetConsoleBufferWindow (out _); + if (_windowSize != Size.Empty && _windowSize.Width != _consoleDriver.Cols + || _windowSize.Height != _consoleDriver.Rows) { + return; + } } + } - bool IMainLoopDriver.EventsPending (bool wait) - { - waitForProbe.Set (); - winChange.Set (); + void IMainLoopDriver.Wakeup () + { + //tokenSource.Cancel (); + _eventReady.Set (); + } - if (CheckTimers (wait, out var waitTimeout)) { - return true; - } + bool IMainLoopDriver.EventsPending (bool wait) + { + _waitForProbe.Set (); + _winChange.Set (); - try { - if (!tokenSource.IsCancellationRequested) { - eventReady.Wait (waitTimeout, tokenSource.Token); - } - } catch (OperationCanceledException) { - return true; - } finally { - eventReady.Reset (); - } + if (CheckTimers (wait, out var waitTimeout)) { + return true; + } - if (!tokenSource.IsCancellationRequested) { - return resultQueue.Count > 0 || CheckTimers (wait, out _) || winChanged; + try { + if (!_tokenSource.IsCancellationRequested) { + _eventReady.Wait (waitTimeout, _tokenSource.Token); } - - tokenSource.Dispose (); - tokenSource = new CancellationTokenSource (); + } catch (OperationCanceledException) { return true; + } finally { + _eventReady.Reset (); } - bool CheckTimers (bool wait, out int waitTimeout) - { - long now = DateTime.UtcNow.Ticks; + if (!_tokenSource.IsCancellationRequested) { + return _resultQueue.Count > 0 || CheckTimers (wait, out _) || _winChanged; + } - if (mainLoop.timeouts.Count > 0) { - waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); - if (waitTimeout < 0) - return true; - } else { - waitTimeout = -1; - } + _tokenSource.Dispose (); + _tokenSource = new CancellationTokenSource (); + return true; + } - if (!wait) - waitTimeout = 0; + bool CheckTimers (bool wait, out int waitTimeout) + { + long now = DateTime.UtcNow.Ticks; - int ic; - lock (mainLoop.idleHandlers) { - ic = mainLoop.idleHandlers.Count; - } + if (_mainLoop.timeouts.Count > 0) { + waitTimeout = (int)((_mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); + if (waitTimeout < 0) + return true; + } else { + waitTimeout = -1; + } + + if (!wait) + waitTimeout = 0; - return ic > 0; + int ic; + lock (_mainLoop.idleHandlers) { + ic = _mainLoop.idleHandlers.Count; } - void IMainLoopDriver.Iteration () - { - while (resultQueue.Count > 0) { - var inputRecords = resultQueue.Dequeue (); - if (inputRecords != null && inputRecords.Length > 0) { - var inputEvent = inputRecords [0]; - ProcessInput?.Invoke (inputEvent); - } - } - if (winChanged) { - winChanged = false; - WinChanged?.Invoke (this, new SizeChangedEventArgs (windowSize)); + return ic > 0; + } + + void IMainLoopDriver.Iteration () + { + while (_resultQueue.Count > 0) { + var inputRecords = _resultQueue.Dequeue (); + if (inputRecords != null && inputRecords.Length > 0) { + var inputEvent = inputRecords [0]; + ProcessInput?.Invoke (inputEvent); } } + if (_winChanged) { + _winChanged = false; + WinChanged?.Invoke (this, new SizeChangedEventArgs (_windowSize)); + } + } + public void TearDown () + { + //throw new NotImplementedException (); } +} - class WindowsClipboard : ClipboardBase { - public WindowsClipboard () - { - IsSupported = IsClipboardFormatAvailable (cfUnicodeText); - } +class WindowsClipboard : ClipboardBase { + public WindowsClipboard () + { + IsSupported = IsClipboardFormatAvailable (_cfUnicodeText); + } - public override bool IsSupported { get; } + public override bool IsSupported { get; } - protected override string GetClipboardDataImpl () - { - try { - if (!OpenClipboard (IntPtr.Zero)) { - return string.Empty; - } + protected override string GetClipboardDataImpl () + { + try { + if (!OpenClipboard (IntPtr.Zero)) { + return string.Empty; + } - IntPtr handle = GetClipboardData (cfUnicodeText); - if (handle == IntPtr.Zero) { - return string.Empty; - } + IntPtr handle = GetClipboardData (_cfUnicodeText); + if (handle == IntPtr.Zero) { + return string.Empty; + } - IntPtr pointer = IntPtr.Zero; + IntPtr pointer = IntPtr.Zero; - try { - pointer = GlobalLock (handle); - if (pointer == IntPtr.Zero) { - return string.Empty; - } + try { + pointer = GlobalLock (handle); + if (pointer == IntPtr.Zero) { + return string.Empty; + } - int size = GlobalSize (handle); - byte [] buff = new byte [size]; + int size = GlobalSize (handle); + byte [] buff = new byte [size]; - Marshal.Copy (pointer, buff, 0, size); + Marshal.Copy (pointer, buff, 0, size); - return Encoding.Unicode.GetString (buff).TrimEnd ('\0'); - } finally { - if (pointer != IntPtr.Zero) { - GlobalUnlock (handle); - } - } + return System.Text.Encoding.Unicode.GetString (buff).TrimEnd ('\0'); } finally { - CloseClipboard (); + if (pointer != IntPtr.Zero) { + GlobalUnlock (handle); + } } + } finally { + CloseClipboard (); } + } - protected override void SetClipboardDataImpl (string text) - { - OpenClipboard (); - - EmptyClipboard (); - IntPtr hGlobal = default; - try { - var bytes = (text.Length + 1) * 2; - hGlobal = Marshal.AllocHGlobal (bytes); - - if (hGlobal == default) { - ThrowWin32 (); - } + protected override void SetClipboardDataImpl (string text) + { + OpenClipboard (); - var target = GlobalLock (hGlobal); + EmptyClipboard (); + IntPtr hGlobal = default; + try { + var bytes = (text.Length + 1) * 2; + hGlobal = Marshal.AllocHGlobal (bytes); - if (target == default) { - ThrowWin32 (); - } + if (hGlobal == default) { + ThrowWin32 (); + } - try { - Marshal.Copy (text.ToCharArray (), 0, target, text.Length); - } finally { - GlobalUnlock (target); - } + var target = GlobalLock (hGlobal); - if (SetClipboardData (cfUnicodeText, hGlobal) == default) { - ThrowWin32 (); - } + if (target == default) { + ThrowWin32 (); + } - hGlobal = default; + try { + Marshal.Copy (text.ToCharArray (), 0, target, text.Length); } finally { - if (hGlobal != default) { - Marshal.FreeHGlobal (hGlobal); - } + GlobalUnlock (target); + } - CloseClipboard (); + if (SetClipboardData (_cfUnicodeText, hGlobal) == default) { + ThrowWin32 (); } - } - void OpenClipboard () - { - var num = 10; - while (true) { - if (OpenClipboard (default)) { - break; - } + hGlobal = default; + } finally { + if (hGlobal != default) { + Marshal.FreeHGlobal (hGlobal); + } - if (--num == 0) { - ThrowWin32 (); - } + CloseClipboard (); + } + } - Thread.Sleep (100); + void OpenClipboard () + { + var num = 10; + while (true) { + if (OpenClipboard (default)) { + break; } - } - const uint cfUnicodeText = 13; + if (--num == 0) { + ThrowWin32 (); + } - void ThrowWin32 () - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); + Thread.Sleep (100); } + } - [DllImport ("User32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - static extern bool IsClipboardFormatAvailable (uint format); + const uint _cfUnicodeText = 13; - [DllImport ("kernel32.dll", SetLastError = true)] - static extern int GlobalSize (IntPtr handle); + void ThrowWin32 () + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } - [DllImport ("kernel32.dll", SetLastError = true)] - static extern IntPtr GlobalLock (IntPtr hMem); + [DllImport ("User32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + static extern bool IsClipboardFormatAvailable (uint format); - [DllImport ("kernel32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - static extern bool GlobalUnlock (IntPtr hMem); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern int GlobalSize (IntPtr handle); - [DllImport ("user32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - static extern bool OpenClipboard (IntPtr hWndNewOwner); + [DllImport ("kernel32.dll", SetLastError = true)] + static extern IntPtr GlobalLock (IntPtr hMem); - [DllImport ("user32.dll", SetLastError = true)] - [return: MarshalAs (UnmanagedType.Bool)] - static extern bool CloseClipboard (); + [DllImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + static extern bool GlobalUnlock (IntPtr hMem); - [DllImport ("user32.dll", SetLastError = true)] - static extern IntPtr SetClipboardData (uint uFormat, IntPtr data); + [DllImport ("user32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + static extern bool OpenClipboard (IntPtr hWndNewOwner); - [DllImport ("user32.dll")] - static extern bool EmptyClipboard (); + [DllImport ("user32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + static extern bool CloseClipboard (); - [DllImport ("user32.dll", SetLastError = true)] - static extern IntPtr GetClipboardData (uint uFormat); - } -} + [DllImport ("user32.dll", SetLastError = true)] + static extern IntPtr SetClipboardData (uint uFormat, IntPtr data); + + [DllImport ("user32.dll")] + static extern bool EmptyClipboard (); + + [DllImport ("user32.dll", SetLastError = true)] + static extern IntPtr GetClipboardData (uint uFormat); +} \ No newline at end of file diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs index 8d511b5ded..f6094b337f 100644 --- a/Terminal.Gui/Drawing/Cell.cs +++ b/Terminal.Gui/Drawing/Cell.cs @@ -1,20 +1,29 @@ -using System.Text; +using System.Collections.Generic; +using System.Text; namespace Terminal.Gui; /// -/// Represents a single row/column within the . Includes the glyph and the foreground/background colors. +/// Represents a single row/column in a Terminal.Gui rendering surface +/// (e.g. and ). /// public class Cell { /// - /// The glyph to draw. + /// The list of Runes to draw in this cell. If the list is empty, the cell is blank. If the list contains + /// more than one Rune, the cell is a combining sequence. + /// (See #2616 - Support combining sequences that don't normalize) /// - public Rune? Rune { get; set; } + public List Runes { get; set; } = new List (); /// - /// The foreground color to draw the glyph with. + /// The attributes to use when drawing the Glyph. /// public Attribute? Attribute { get; set; } + /// + /// Gets or sets a value indicating whether this has + /// been modified since the last time it was drawn. + /// + public bool IsDirty { get; set; } } diff --git a/Terminal.Gui/Drawing/Color.cs b/Terminal.Gui/Drawing/Color.cs new file mode 100644 index 0000000000..f617dab498 --- /dev/null +++ b/Terminal.Gui/Drawing/Color.cs @@ -0,0 +1,845 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + +namespace Terminal.Gui { + /// + /// Colors that can be used to set the foreground and background colors in console applications. + /// + /// + /// The value indicates either no-color has been set or the color is invalid. + /// + [JsonConverter (typeof (ColorJsonConverter))] + public enum Color { + /// + /// The black color. + /// + Black, + /// + /// The blue color. + /// + Blue, + /// + /// The green color. + /// + Green, + /// + /// The cyan color. + /// + Cyan, + /// + /// The red color. + /// + Red, + /// + /// The magenta color. + /// + Magenta, + /// + /// The brown color. + /// + Brown, + /// + /// The gray color. + /// + Gray, + /// + /// The dark gray color. + /// + DarkGray, + /// + /// The bright bBlue color. + /// + BrightBlue, + /// + /// The bright green color. + /// + BrightGreen, + /// + /// The bright cyan color. + /// + BrightCyan, + /// + /// The bright red color. + /// + BrightRed, + /// + /// The bright magenta color. + /// + BrightMagenta, + /// + /// The bright yellow color. + /// + BrightYellow, + /// + /// The White color. + /// + White + } + + /// + /// Indicates the RGB for true colors. + /// + [JsonConverter (typeof (TrueColorJsonConverter))] + public readonly struct TrueColor : IEquatable { + private static readonly ImmutableDictionary TrueColorToConsoleColorMap = new Dictionary () { + { new TrueColor (0,0,0),Color.Black }, + { new TrueColor (0, 0, 0x80),Color.Blue }, + { new TrueColor (0, 0x80, 0),Color.Green}, + { new TrueColor (0, 0x80, 0x80),Color.Cyan}, + { new TrueColor (0x80, 0, 0),Color.Red}, + { new TrueColor (0x80, 0, 0x80),Color.Magenta}, + { new TrueColor (0xC1, 0x9C, 0x00),Color.Brown}, // TODO confirm this + { new TrueColor (0xC0, 0xC0, 0xC0),Color.Gray}, + { new TrueColor (0x80, 0x80, 0x80),Color.DarkGray}, + { new TrueColor (0, 0, 0xFF),Color.BrightBlue}, + { new TrueColor (0, 0xFF, 0),Color.BrightGreen}, + { new TrueColor (0, 0xFF, 0xFF),Color.BrightCyan}, + { new TrueColor (0xFF, 0, 0),Color.BrightRed}, + { new TrueColor (0xFF, 0, 0xFF),Color.BrightMagenta }, + { new TrueColor (0xFF, 0xFF, 0),Color.BrightYellow}, + { new TrueColor (0xFF, 0xFF, 0xFF),Color.White}, + }.ToImmutableDictionary (); + + /// + /// Red color component. + /// + public int Red { get; } + /// + /// Green color component. + /// + public int Green { get; } + /// + /// Blue color component. + /// + public int Blue { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// + /// + public TrueColor (int red, int green, int blue) + { + Red = red; + Green = green; + Blue = blue; + } + + /// + /// Converts the provided text to a . + /// + /// The text to analyze. + /// The parsed value. + /// A boolean value indcating whether it was successful. + public static bool TryParse (string text, [NotNullWhen (true)] out TrueColor? trueColor) + { + // empty color + if ((text == null) || (text.Length == 0)) { + trueColor = null; + return false; + } + + // #RRGGBB or #RGB + if ((text [0] == '#') && + ((text.Length == 7) || (text.Length == 4))) { + if (text.Length == 7) { + var r = Convert.ToInt32 (text.Substring (1, 2), 16); + var g = Convert.ToInt32 (text.Substring (3, 2), 16); + var b = Convert.ToInt32 (text.Substring (5, 2), 16); + trueColor = new TrueColor (r, g, b); + } else { + var rText = char.ToString (text [1]); + var gText = char.ToString (text [2]); + var bText = char.ToString (text [3]); + + var r = Convert.ToInt32 (rText + rText, 16); + var g = Convert.ToInt32 (gText + gText, 16); + var b = Convert.ToInt32 (bText + bText, 16); + trueColor = new TrueColor (r, g, b); + } + return true; + } + + // rgb(XX,YY,ZZ) + var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)"); + if (match.Success) { + var r = int.Parse (match.Groups [1].Value); + var g = int.Parse (match.Groups [2].Value); + var b = int.Parse (match.Groups [3].Value); + trueColor = new TrueColor (r, g, b); + return true; + } + + trueColor = null; + return false; + } + + /// + /// Converts a to a using a default mapping. + /// + /// The to convert. + /// + public static TrueColor? FromConsoleColor (Color consoleColor) + { + return consoleColor switch { + Color.Black => new TrueColor (0, 0, 0), + Color.Blue => new TrueColor (0, 0, 0x80), + Color.Green => new TrueColor (0, 0x80, 0), + Color.Cyan => new TrueColor (0, 0x80, 0x80), + Color.Red => new TrueColor (0x80, 0, 0), + Color.Magenta => new TrueColor (0x80, 0, 0x80), + Color.Brown => new TrueColor (0xC1, 0x9C, 0x00) // TODO confirm this + , + Color.Gray => new TrueColor (0xC0, 0xC0, 0xC0), + Color.DarkGray => new TrueColor (0x80, 0x80, 0x80), + Color.BrightBlue => new TrueColor (0, 0, 0xFF), + Color.BrightGreen => new TrueColor (0, 0xFF, 0), + Color.BrightCyan => new TrueColor (0, 0xFF, 0xFF), + Color.BrightRed => new TrueColor (0xFF, 0, 0), + Color.BrightMagenta => new TrueColor (0xFF, 0, 0xFF), + Color.BrightYellow => new TrueColor (0xFF, 0xFF, 0), + Color.White => new TrueColor (0xFF, 0xFF, 0xFF), + var _ => null + }; + ; + } + + /// + /// Converts the provided to using a default mapping. + /// + /// + /// + public static Color ToConsoleColor (TrueColor? trueColor) + { + if (trueColor.HasValue) { + return TrueColorToConsoleColorMap.MinBy (kv => CalculateDistance (kv.Key, trueColor.Value)).Value; + } else { + return (Color)(-1); + } + } + + private static float CalculateDistance (TrueColor color1, TrueColor color2) + { + // use RGB distance + return + Math.Abs (color1.Red - color2.Red) + + Math.Abs (color1.Green - color2.Green) + + Math.Abs (color1.Blue - color2.Blue); + } + + /// + public static bool operator == (TrueColor left, TrueColor right) + { + return left.Equals (right); + } + + /// + public static bool operator != (TrueColor left, TrueColor right) + { + return !left.Equals (right); + } + + /// + public override bool Equals (object obj) + { + return obj is TrueColor other && Equals (other); + } + + /// + public bool Equals (TrueColor other) + { + return + Red == other.Red && + Green == other.Green && + Blue == other.Blue; + } + + /// + public override int GetHashCode () + { + return HashCode.Combine (Red, Green, Blue); + } + + /// + public override string ToString () + { + return $"#{Red:X2}{Green:X2}{Blue:X2}"; + } + } + + /// + /// Attributes represent how text is styled when displayed in the terminal. + /// + /// + /// provides a platform independent representation of colors (and someday other forms of text styling). + /// They encode both the foreground and the background color and are used in the + /// class to define color schemes that can be used in an application. + /// + [JsonConverter (typeof (AttributeJsonConverter))] + public struct Attribute : IEquatable { + + /// + /// Default empty attribute. + /// + public static readonly Attribute Default = new Attribute (Color.White, Color.Black); + + /// + /// The -specific color value. If is + /// the value of this property is invalid (typically because the Attribute was created before a driver was loaded) + /// and the attribute should be re-made (see ) before it is used. + /// + [JsonIgnore (Condition = JsonIgnoreCondition.Always)] + internal int Value { get; } + + /// + /// The foreground color. + /// + [JsonConverter (typeof (ColorJsonConverter))] + public Color Foreground { get; private init; } + + /// + /// The background color. + /// + [JsonConverter (typeof (ColorJsonConverter))] + public Color Background { get; private init; } + + /// + /// Gets the TrueColor foreground color. + /// + [JsonConverter (typeof (TrueColorJsonConverter))] + public TrueColor? TrueColorForeground { get; private init; } + + /// + /// Gets the TrueColor background color. + /// + [JsonConverter (typeof (TrueColorJsonConverter))] + public TrueColor? TrueColorBackground { get; private init; } + + /// + /// Initializes a new instance with a platform-specific color value. + /// + /// Value. + internal Attribute (int value) + { + Color foreground = default; + Color background = default; + + Initialized = false; + if (Application.Driver != null) { + Application.Driver.GetColors (value, out foreground, out background); + Initialized = true; + } + Value = value; + Foreground = foreground; + Background = background; + TrueColorForeground = TrueColor.FromConsoleColor (foreground); + TrueColorBackground = TrueColor.FromConsoleColor (background); + } + + /// + /// Initializes a new instance of the struct. + /// + /// platform-dependent color value. + /// Foreground + /// Background + public Attribute (int value, Color foreground, Color background) + { + Foreground = foreground; + Background = background; + TrueColorForeground = TrueColor.FromConsoleColor (foreground); + TrueColorBackground = TrueColor.FromConsoleColor (background); + Value = value; + Initialized = true; + } + + /// + /// Initializes a new instance of the struct. + /// + /// Foreground + /// Background + public Attribute (Color foreground = new Color (), Color background = new Color ()) + { + Foreground = foreground; + Background = background; + TrueColorForeground = TrueColor.FromConsoleColor (foreground); + TrueColorBackground = TrueColor.FromConsoleColor (background); + + var make = Make (foreground, background); + Initialized = make.Initialized; + Value = make.Value; + } + + /// + /// Initializes a new instance of the class. Populates + /// and . Also computes + /// and (basic console colors) in case + /// driver does not support true color rendering. + /// + /// + /// + public Attribute (TrueColor? trueColorForeground, TrueColor? trueColorBackground) + { + Foreground = TrueColor.ToConsoleColor (trueColorForeground); + Background = TrueColor.ToConsoleColor (trueColorBackground); + TrueColorForeground = trueColorForeground; + TrueColorBackground = trueColorBackground; + var make = Make (Foreground, Background); + Value = make.Value; + Initialized = make.Initialized; + } + + /// + /// + /// Initializes a new instance of the class. Populates + /// and with explicit + /// fallback values for and (in case + /// driver does not support true color rendering). + /// + /// If you do not want to manually specify the fallback colors use + /// instead which auto calculates these. + /// + /// True color RGB values you would like to use. + /// True color RGB values you would like to use. + /// Simple console color replacement if driver does not support true color. + /// Simple console color replacement if driver does not support true color. + public Attribute (TrueColor trueColorForeground, TrueColor trueColorBackground, Color foreground, Color background) + { + Foreground = foreground; + Background = background; + TrueColorForeground = trueColorForeground; + TrueColorBackground = trueColorBackground; + var make = Make (Foreground, Background); + Value = make.Value; + Initialized = make.Initialized; + } + + /// + /// Initializes a new instance of the struct + /// with the same colors for the foreground and background. + /// + /// The color. + public Attribute (Color color) : this (color, color) { } + + + /// + /// Compares two attributes for equality. + /// + /// + /// + /// + public static bool operator == (Attribute left, Attribute right) => left.Equals (right); + + /// + /// Compares two attributes for inequality. + /// + /// + /// + /// + public static bool operator != (Attribute left, Attribute right) => !(left == right); + + /// + public override bool Equals (object obj) + { + return obj is Attribute other && Equals (other); + } + + /// + public bool Equals (Attribute other) + { + if (TrueColorForeground.HasValue || TrueColorBackground.HasValue) { + return + TrueColorForeground == other.TrueColorForeground && + TrueColorBackground == other.TrueColorBackground; + } + + return Value == other.Value && + Foreground == other.Foreground && + Background == other.Background; + } + + /// + public override int GetHashCode () => HashCode.Combine (Value, Foreground, Background, TrueColorForeground, TrueColorBackground); + + /// + /// Creates an from the specified foreground and background colors. + /// + /// + /// If a has not been loaded (Application.Driver == null) this + /// method will return an attribute with set to . + /// + /// The new attribute. + /// Foreground color to use. + /// Background color to use. + public static Attribute Make (Color foreground, Color background) + { + if (Application.Driver == null) { + // Create the attribute, but show it's not been initialized + return new Attribute () { + Initialized = false, + Foreground = foreground, + Background = background + }; + } + return Application.Driver.MakeAttribute (foreground, background); + } + + /// + /// Gets the current from the driver. + /// + /// The current attribute. + public static Attribute Get () + { + if (Application.Driver == null) { + throw new InvalidOperationException ("The Application has not been initialized"); + } + return Application.Driver.GetAttribute (); + } + + /// + /// If the attribute has been initialized by a and + /// thus has that is valid for that driver. If the + /// and colors may have been set '-1' but + /// the attribute has not been mapped to a specific color value. + /// + /// + /// Attributes that have not been initialized must eventually be initialized before being passed to a driver. + /// + [JsonIgnore] + public bool Initialized { get; internal set; } + + /// + /// Returns if the Attribute is valid (both foreground and background have valid color values). + /// + /// + [JsonIgnore] + public bool HasValidColors => (int)Foreground > -1 && (int)Background > -1; + + /// + public override string ToString () + { + // Note, Unit tests are dependent on this format + return $"{Foreground},{Background}"; + } + } + + /// + /// Defines the color s for common visible elements in a . + /// Containers such as and use to determine + /// the colors used by sub-views. + /// + /// + /// See also: . + /// + [JsonConverter (typeof (ColorSchemeJsonConverter))] + public class ColorScheme : IEquatable { + Attribute _normal = Attribute.Default; + Attribute _focus = Attribute.Default; + Attribute _hotNormal = Attribute.Default; + Attribute _hotFocus = Attribute.Default; + Attribute _disabled = Attribute.Default; + + /// + /// Used by and to track which ColorScheme + /// is being accessed. + /// + internal string schemeBeingSet = ""; + + /// + /// Creates a new instance. + /// + public ColorScheme () { } + + /// + /// Creates a new instance, initialized with the values from . + /// + /// The scheme to initialize the new instance with. + public ColorScheme (ColorScheme scheme) : base () + { + if (scheme != null) { + _normal = scheme.Normal; + _focus = scheme.Focus; + _hotNormal = scheme.HotNormal; + _disabled = scheme.Disabled; + _hotFocus = scheme.HotFocus; + } + } + + /// + /// Creates a new instance, initialized with the values from . + /// + /// The attribute to initialize the new instance with. + public ColorScheme (Attribute attribute) + { + _normal = attribute; + _focus = attribute; + _hotNormal = attribute; + _disabled = attribute; + _hotFocus = attribute; + } + + /// + /// The foreground and background color for text when the view is not focused, hot, or disabled. + /// + public Attribute Normal { + get { return _normal; } + set { + if (!value.HasValidColors) { + return; + } + _normal = value; + } + } + + /// + /// The foreground and background color for text when the view has the focus. + /// + public Attribute Focus { + get { return _focus; } + set { + if (!value.HasValidColors) { + return; + } + _focus = value; + } + } + + /// + /// The foreground and background color for text when the view is highlighted (hot). + /// + public Attribute HotNormal { + get { return _hotNormal; } + set { + if (!value.HasValidColors) { + return; + } + _hotNormal = value; + } + } + + /// + /// The foreground and background color for text when the view is highlighted (hot) and has focus. + /// + public Attribute HotFocus { + get { return _hotFocus; } + set { + if (!value.HasValidColors) { + return; + } + _hotFocus = value; + } + } + + /// + /// The default foreground and background color for text, when the view is disabled. + /// + public Attribute Disabled { + get { return _disabled; } + set { + if (!value.HasValidColors) { + return; + } + _disabled = value; + } + } + + /// + /// Compares two objects for equality. + /// + /// + /// true if the two objects are equal + public override bool Equals (object obj) + { + return Equals (obj as ColorScheme); + } + + /// + /// Compares two objects for equality. + /// + /// + /// true if the two objects are equal + public bool Equals (ColorScheme other) + { + return other != null && + EqualityComparer.Default.Equals (_normal, other._normal) && + EqualityComparer.Default.Equals (_focus, other._focus) && + EqualityComparer.Default.Equals (_hotNormal, other._hotNormal) && + EqualityComparer.Default.Equals (_hotFocus, other._hotFocus) && + EqualityComparer.Default.Equals (_disabled, other._disabled); + } + + /// + /// Returns a hashcode for this instance. + /// + /// hashcode for this instance + public override int GetHashCode () + { + int hashCode = -1242460230; + hashCode = hashCode * -1521134295 + _normal.GetHashCode (); + hashCode = hashCode * -1521134295 + _focus.GetHashCode (); + hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode (); + hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode (); + hashCode = hashCode * -1521134295 + _disabled.GetHashCode (); + return hashCode; + } + + /// + /// Compares two objects for equality. + /// + /// + /// + /// true if the two objects are equivalent + public static bool operator == (ColorScheme left, ColorScheme right) + { + return EqualityComparer.Default.Equals (left, right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// + /// true if the two objects are not equivalent + public static bool operator != (ColorScheme left, ColorScheme right) + { + return !(left == right); + } + + internal void Initialize () + { + // If the new scheme was created before a driver was loaded, we need to re-make + // the attributes + if (!_normal.Initialized) { + _normal = new Attribute (_normal.Foreground, _normal.Background); + } + if (!_focus.Initialized) { + _focus = new Attribute (_focus.Foreground, _focus.Background); + } + if (!_hotNormal.Initialized) { + _hotNormal = new Attribute (_hotNormal.Foreground, _hotNormal.Background); + } + if (!_hotFocus.Initialized) { + _hotFocus = new Attribute (_hotFocus.Foreground, _hotFocus.Background); + } + if (!_disabled.Initialized) { + _disabled = new Attribute (_disabled.Foreground, _disabled.Background); + } + } + } + + /// + /// The default s for the application. + /// + /// + /// This property can be set in a Theme to change the default for the application. + /// + public static class Colors { + private class SchemeNameComparerIgnoreCase : IEqualityComparer { + public bool Equals (string x, string y) + { + if (x != null && y != null) { + return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase); + } + return false; + } + + public int GetHashCode (string obj) + { + return obj.ToLowerInvariant ().GetHashCode (); + } + } + + static Colors () + { + ColorSchemes = Create (); + } + + /// + /// Creates a new dictionary of new objects. + /// + public static Dictionary Create () + { + // Use reflection to dynamically create the default set of ColorSchemes from the list defined + // by the class. + return typeof (Colors).GetProperties () + .Where (p => p.PropertyType == typeof (ColorScheme)) + .Select (p => new KeyValuePair (p.Name, new ColorScheme ())) + .ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ()); + } + + /// + /// The application Toplevel color scheme, for the default Toplevel views. + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["TopLevel"]; + /// + /// + public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The base color scheme, for the default Toplevel views. + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Base"]; + /// + /// + public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The dialog color scheme, for standard popup dialog boxes + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Dialog"]; + /// + /// + public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The menu bar color + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Menu"]; + /// + /// + public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); } + + /// + /// The color scheme for showing errors. + /// + /// + /// + /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Error"]; + /// + /// + public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); } + + static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) + { + return ColorSchemes [schemeBeingSet]; + } + + static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null) + { + ColorSchemes [schemeBeingSet] = colorScheme; + colorScheme.schemeBeingSet = schemeBeingSet; + } + + /// + /// Provides the defined s. + /// + [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)] + [JsonConverter (typeof (DictionaryJsonConverter))] + public static Dictionary ColorSchemes { get; private set; } + } + +} diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs index 6edb70107f..fa81604418 100644 --- a/Terminal.Gui/Drawing/Glyphs.cs +++ b/Terminal.Gui/Drawing/Glyphs.cs @@ -139,6 +139,11 @@ public class GlyphDefinitions { /// public Rune Collapse { get; set; } = (Rune)'-'; + /// + /// Identical To (U+226) + /// + public Rune IdenticalTo { get; set; } = (Rune)'≡'; + /// /// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610. /// @@ -166,6 +171,16 @@ public class GlyphDefinitions { /// public Rune File { get; set; } = (Rune)'☰'; + /// + /// Horizontal Ellipsis - … U+2026 + /// + public Rune HorizontalEllipsis { get; set; } = (Rune)'…'; + + /// + /// Vertical Four Dots - ⁞ U+205e + /// + public Rune VerticalFourDots { get; set; } = (Rune)'⁞'; + #region ----------------- Lines ----------------- /// /// Box Drawings Horizontal Line - Light (U+2500) - ─ diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index e7ceec8903..698b32e89c 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -73,7 +74,7 @@ public LineCanvas() ConfigurationManager.Applied += ConfigurationManager_Applied; } - private void ConfigurationManager_Applied (object sender, ConfigurationManagerEventArgs e) + private void ConfigurationManager_Applied (object? sender, ConfigurationManagerEventArgs e) { foreach (var irr in runeResolvers) { irr.Value.SetGlyphs (); @@ -142,7 +143,7 @@ public StraightLine RemoveLastLine() _lines.Remove(l); } - return l; + return l!; } /// @@ -553,14 +554,17 @@ public override void SetGlyphs () } - private Cell GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects) + private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects) { if (!intersects.Any ()) { return null; } var cell = new Cell (); - cell.Rune = GetRuneForIntersects (driver, intersects); + var rune = GetRuneForIntersects (driver, intersects); + if (rune.HasValue) { + cell.Runes.Add (rune.Value); + } cell.Attribute = GetAttributeForIntersects (intersects); return cell; } diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index 29de444eea..b6ce90623c 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -340,7 +340,7 @@ public enum Command { QuitToplevel, /// - /// Suspend a application (used on Linux). + /// Suspend a application (Only implemented in ). /// Suspend, diff --git a/Terminal.Gui/Input/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/Input/EscSeqUtils/EscSeqReq.cs deleted file mode 100644 index eaa3084ad9..0000000000 --- a/Terminal.Gui/Input/EscSeqUtils/EscSeqReq.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Terminal.Gui { - /// - /// Represents the state of an ANSI escape sequence request. - /// - /// - /// This is needed because there are some escape sequence requests responses that are equal - /// with some normal escape sequences and thus, will be only considered the responses to the - /// requests that were registered with this object. - /// - public class EscSeqReqStatus { - /// - /// Gets the terminating. - /// - public string Terminating { get; } - /// - /// Gets the number of requests. - /// - public int NumRequests { get; } - /// - /// Gets information about unfinished requests. - /// - public int NumOutstanding { get; set; } - - /// - /// Creates a new state of escape sequence request. - /// - /// The terminating. - /// The number of requests. - public EscSeqReqStatus (string terminating, int numOfReq) - { - Terminating = terminating; - NumRequests = NumOutstanding = numOfReq; - } - } - - /// - /// Manages a list of . - /// - public class EscSeqReqProc { - /// - /// Gets the list. - /// - public List EscSeqReqStats { get; } = new List (); - - /// - /// Adds a new instance to the list. - /// - /// The terminating. - /// The number of requests. - public void Add (string terminating, int numOfReq = 1) - { - lock (EscSeqReqStats) { - var found = EscSeqReqStats.Find (x => x.Terminating == terminating); - if (found == null) { - EscSeqReqStats.Add (new EscSeqReqStatus (terminating, numOfReq)); - } else if (found != null && found.NumOutstanding < found.NumRequests) { - found.NumOutstanding = Math.Min (found.NumOutstanding + numOfReq, found.NumRequests); - } - } - } - - /// - /// Removes a instance from the list. - /// - /// The terminating string. - public void Remove (string terminating) - { - lock (EscSeqReqStats) { - var found = EscSeqReqStats.Find (x => x.Terminating == terminating); - if (found == null) { - return; - } - if (found != null && found.NumOutstanding == 0) { - EscSeqReqStats.Remove (found); - } else if (found != null && found.NumOutstanding > 0) { - found.NumOutstanding--; - if (found.NumOutstanding == 0) { - EscSeqReqStats.Remove (found); - } - } - } - } - - /// - /// Indicates if a with the exist - /// in the list. - /// - /// - /// if exist, otherwise. - public bool Requested (string terminating) - { - lock (EscSeqReqStats) { - var found = EscSeqReqStats.Find (x => x.Terminating == terminating); - if (found == null) { - return false; - } - if (found != null && found.NumOutstanding > 0) { - return true; - } else { - EscSeqReqStats.Remove (found); - } - return false; - } - } - } -} diff --git a/Terminal.Gui/Input/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/Input/EscSeqUtils/EscSeqUtils.cs deleted file mode 100644 index 21a9aac594..0000000000 --- a/Terminal.Gui/Input/EscSeqUtils/EscSeqUtils.cs +++ /dev/null @@ -1,907 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Management; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Terminal.Gui { - /// - /// Provides a platform-independent API for managing ANSI escape sequence codes. - /// - public static class EscSeqUtils { - /// - /// Represents the escape key. - /// - public static readonly char KeyEsc = (char)Key.Esc; - /// - /// Represents the CSI (Control Sequence Introducer). - /// - public static readonly string KeyCSI = $"{KeyEsc}["; - /// - /// Represents the CSI for enable any mouse event tracking. - /// - public static readonly string CSI_EnableAnyEventMouse = KeyCSI + "?1003h"; - /// - /// Represents the CSI for enable SGR (Select Graphic Rendition). - /// - public static readonly string CSI_EnableSgrExtModeMouse = KeyCSI + "?1006h"; - /// - /// Represents the CSI for enable URXVT (Unicode Extended Virtual Terminal). - /// - public static readonly string CSI_EnableUrxvtExtModeMouse = KeyCSI + "?1015h"; - /// - /// Represents the CSI for disable any mouse event tracking. - /// - public static readonly string CSI_DisableAnyEventMouse = KeyCSI + "?1003l"; - /// - /// Represents the CSI for disable SGR (Select Graphic Rendition). - /// - public static readonly string CSI_DisableSgrExtModeMouse = KeyCSI + "?1006l"; - /// - /// Represents the CSI for disable URXVT (Unicode Extended Virtual Terminal). - /// - public static readonly string CSI_DisableUrxvtExtModeMouse = KeyCSI + "?1015l"; - - /// - /// Control sequence for enable mouse events. - /// - public static string EnableMouseEvents { get; set; } = - CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse; - /// - /// Control sequence for disable mouse events. - /// - public static string DisableMouseEvents { get; set; } = - CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse; - - /// - /// Ensures a console key is mapped to one that works correctly with ANSI escape sequences. - /// - /// The . - /// The modified. - public static ConsoleKeyInfo GetConsoleInputKey (ConsoleKeyInfo consoleKeyInfo) - { - ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo; - ConsoleKey key; - var keyChar = consoleKeyInfo.KeyChar; - switch ((uint)keyChar) { - case 0: - if (consoleKeyInfo.Key == (ConsoleKey)64) { // Ctrl+Space in Windows. - newConsoleKeyInfo = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); - } - break; - case uint n when (n >= '\u0001' && n <= '\u001a'): - if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r') { - key = ConsoleKey.Enter; - newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, - key, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); - } else if (consoleKeyInfo.Key == 0) { - key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1); - newConsoleKeyInfo = new ConsoleKeyInfo ((char)key, - key, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - true); - } - break; - case 127: - newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, ConsoleKey.Backspace, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0, - (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0); - break; - default: - newConsoleKeyInfo = consoleKeyInfo; - break; - } - - return newConsoleKeyInfo; - } - - /// - /// A helper to resize the as needed. - /// - /// The . - /// The array to resize. - /// The resized. - public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki) - { - Array.Resize (ref cki, cki == null ? 1 : cki.Length + 1); - cki [cki.Length - 1] = consoleKeyInfo; - return cki; - } - - /// - /// Decodes a escape sequence to been processed in the appropriate manner. - /// - /// The which may contain a request. - /// The which may changes. - /// The which may changes. - /// The array. - /// The which may changes. - /// The control returned by the method. - /// The code returned by the method. - /// The values returned by the method. - /// The terminating returned by the method. - /// Indicates if the escape sequence is a mouse key. - /// The button state. - /// The position. - /// Indicates if the escape sequence is a response to a request. - /// The handler that will process the event. - public static void DecodeEscSeq (EscSeqReqProc escSeqReqProc, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string [] values, out string terminating, out bool isKeyMouse, out List buttonState, out Point pos, out bool isReq, Action continuousButtonPressedHandler) - { - char [] kChars = GetKeyCharArray (cki); - (c1Control, code, values, terminating) = GetEscapeResult (kChars); - isKeyMouse = false; - buttonState = new List () { 0 }; - pos = default; - isReq = false; - switch (c1Control) { - case "ESC": - if (values == null && string.IsNullOrEmpty (terminating)) { - key = ConsoleKey.Escape; - newConsoleKeyInfo = new ConsoleKeyInfo (cki [0].KeyChar, key, - (mod & ConsoleModifiers.Shift) != 0, - (mod & ConsoleModifiers.Alt) != 0, - (mod & ConsoleModifiers.Control) != 0); - } else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26) { - key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1); - newConsoleKeyInfo = new ConsoleKeyInfo (cki [1].KeyChar, - key, - false, - true, - true); - } else { - if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) { - key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0]; - } else { - key = (ConsoleKey)cki [1].KeyChar; - } - newConsoleKeyInfo = new ConsoleKeyInfo ((char)key, - (ConsoleKey)Math.Min ((uint)key, 255), - false, - true, - false); - } - break; - case "SS3": - key = GetConsoleKey (terminating [0], values [0], ref mod); - newConsoleKeyInfo = new ConsoleKeyInfo ('\0', - key, - (mod & ConsoleModifiers.Shift) != 0, - (mod & ConsoleModifiers.Alt) != 0, - (mod & ConsoleModifiers.Control) != 0); - break; - case "CSI": - if (!string.IsNullOrEmpty (code) && code == "<") { - GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler); - isKeyMouse = true; - return; - } else if (escSeqReqProc != null && escSeqReqProc.Requested (terminating)) { - isReq = true; - escSeqReqProc.Remove (terminating); - return; - } - key = GetConsoleKey (terminating [0], values [0], ref mod); - if (key != 0 && values.Length > 1) { - mod |= GetConsoleModifiers (values [1]); - } - newConsoleKeyInfo = new ConsoleKeyInfo ('\0', - key, - (mod & ConsoleModifiers.Shift) != 0, - (mod & ConsoleModifiers.Alt) != 0, - (mod & ConsoleModifiers.Control) != 0); - break; - } - } - - /// - /// Gets all the needed information about a escape sequence. - /// - /// The array with all chars. - /// - /// The c1Control returned by , code, values and terminating. - /// - public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar) - { - if (kChar == null || kChar.Length == 0) { - return (null, null, null, null); - } - if (kChar [0] != '\x1b') { - throw new InvalidOperationException ("Invalid escape character!"); - } - if (kChar.Length == 1) { - return ("ESC", null, null, null); - } - if (kChar.Length == 2) { - return ("ESC", null, null, kChar [1].ToString ()); - } - string c1Control = GetC1ControlChar (kChar [1]); - string code = null; - int nSep = kChar.Count (x => x == ';') + 1; - string [] values = new string [nSep]; - int valueIdx = 0; - string terminating = ""; - for (int i = 2; i < kChar.Length; i++) { - var c = kChar [i]; - if (char.IsDigit (c)) { - values [valueIdx] += c.ToString (); - } else if (c == ';') { - valueIdx++; - } else if (valueIdx == nSep - 1 || i == kChar.Length - 1) { - terminating += c.ToString (); - } else { - code += c.ToString (); - } - } - - return (c1Control, code, values, terminating); - } - - /// - /// Gets the c1Control used in the called escape sequence. - /// - /// The char used. - /// The c1Control. - public static string GetC1ControlChar (char c) - { - // These control characters are used in the vtXXX emulation. - switch (c) { - case 'D': - return "IND"; // Index - case 'E': - return "NEL"; // Next Line - case 'H': - return "HTS"; // Tab Set - case 'M': - return "RI"; // Reverse Index - case 'N': - return "SS2"; // Single Shift Select of G2 Character Set: affects next character only - case 'O': - return "SS3"; // Single Shift Select of G3 Character Set: affects next character only - case 'P': - return "DCS"; // Device Control String - case 'V': - return "SPA"; // Start of Guarded Area - case 'W': - return "EPA"; // End of Guarded Area - case 'X': - return "SOS"; // Start of String - case 'Z': - return "DECID"; // Return Terminal ID Obsolete form of CSI c (DA) - case '[': - return "CSI"; // Control Sequence Introducer - case '\\': - return "ST"; // String Terminator - case ']': - return "OSC"; // Operating System Command - case '^': - return "PM"; // Privacy Message - case '_': - return "APC"; // Application Program Command - default: - return ""; // Not supported - } - } - - /// - /// Gets the from the value. - /// - /// The value. - /// The or zero. - public static ConsoleModifiers GetConsoleModifiers (string value) - { - switch (value) { - case "2": - return ConsoleModifiers.Shift; - case "3": - return ConsoleModifiers.Alt; - case "4": - return ConsoleModifiers.Shift | ConsoleModifiers.Alt; - case "5": - return ConsoleModifiers.Control; - case "6": - return ConsoleModifiers.Shift | ConsoleModifiers.Control; - case "7": - return ConsoleModifiers.Alt | ConsoleModifiers.Control; - case "8": - return ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control; - default: - return 0; - } - } - - /// - /// Gets the depending on terminating and value. - /// - /// The terminating. - /// The value. - /// The which may changes. - /// The and probably the . - public static ConsoleKey GetConsoleKey (char terminating, string value, ref ConsoleModifiers mod) - { - ConsoleKey key; - switch (terminating) { - case 'A': - key = ConsoleKey.UpArrow; - break; - case 'B': - key = ConsoleKey.DownArrow; - break; - case 'C': - key = ConsoleKey.RightArrow; - break; - case 'D': - key = ConsoleKey.LeftArrow; - break; - case 'F': - key = ConsoleKey.End; - break; - case 'H': - key = ConsoleKey.Home; - break; - case 'P': - key = ConsoleKey.F1; - break; - case 'Q': - key = ConsoleKey.F2; - break; - case 'R': - key = ConsoleKey.F3; - break; - case 'S': - key = ConsoleKey.F4; - break; - case 'Z': - key = ConsoleKey.Tab; - mod |= ConsoleModifiers.Shift; - break; - case '~': - switch (value) { - case "2": - key = ConsoleKey.Insert; - break; - case "3": - key = ConsoleKey.Delete; - break; - case "5": - key = ConsoleKey.PageUp; - break; - case "6": - key = ConsoleKey.PageDown; - break; - case "15": - key = ConsoleKey.F5; - break; - case "17": - key = ConsoleKey.F6; - break; - case "18": - key = ConsoleKey.F7; - break; - case "19": - key = ConsoleKey.F8; - break; - case "20": - key = ConsoleKey.F9; - break; - case "21": - key = ConsoleKey.F10; - break; - case "23": - key = ConsoleKey.F11; - break; - case "24": - key = ConsoleKey.F12; - break; - default: - key = 0; - break; - } - break; - default: - key = 0; - break; - } - - return key; - } - - /// - /// A helper to get only the from the array. - /// - /// - /// The char array of the escape sequence. - public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki) - { - char [] kChar = new char [] { }; - var length = 0; - foreach (var kc in cki) { - length++; - Array.Resize (ref kChar, length); - kChar [length - 1] = kc.KeyChar; - } - - return kChar; - } - - private static MouseFlags? lastMouseButtonPressed; - //private static MouseFlags? lastMouseButtonReleased; - private static bool isButtonPressed; - //private static bool isButtonReleased; - private static bool isButtonClicked; - private static bool isButtonDoubleClicked; - private static bool isButtonTripleClicked; - private static Point point; - - /// - /// Gets the mouse button flags and the position. - /// - /// The array. - /// The mouse button flags. - /// The mouse position. - /// The handler that will process the event. - public static void GetMouse (ConsoleKeyInfo [] cki, out List mouseFlags, out Point pos, Action continuousButtonPressedHandler) - { - MouseFlags buttonState = 0; - pos = new Point (); - int buttonCode = 0; - bool foundButtonCode = false; - int foundPoint = 0; - string value = ""; - var kChar = GetKeyCharArray (cki); - //System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}"); - for (int i = 0; i < kChar.Length; i++) { - var c = kChar [i]; - if (c == '<') { - foundButtonCode = true; - } else if (foundButtonCode && c != ';') { - value += c.ToString (); - } else if (c == ';') { - if (foundButtonCode) { - foundButtonCode = false; - buttonCode = int.Parse (value); - } - if (foundPoint == 1) { - pos.X = int.Parse (value) - 1; - } - value = ""; - foundPoint++; - } else if (foundPoint > 0 && c != 'm' && c != 'M') { - value += c.ToString (); - } else if (c == 'm' || c == 'M') { - //pos.Y = int.Parse (value) + Console.WindowTop - 1; - pos.Y = int.Parse (value) - 1; - - switch (buttonCode) { - case 0: - case 8: - case 16: - case 24: - case 32: - case 36: - case 40: - case 48: - case 56: - buttonState = c == 'M' ? MouseFlags.Button1Pressed - : MouseFlags.Button1Released; - break; - case 1: - case 9: - case 17: - case 25: - case 33: - case 37: - case 41: - case 45: - case 49: - case 53: - case 57: - case 61: - buttonState = c == 'M' ? MouseFlags.Button2Pressed - : MouseFlags.Button2Released; - break; - case 2: - case 10: - case 14: - case 18: - case 22: - case 26: - case 30: - case 34: - case 42: - case 46: - case 50: - case 54: - case 58: - case 62: - buttonState = c == 'M' ? MouseFlags.Button3Pressed - : MouseFlags.Button3Released; - break; - case 35: - //// Needed for Windows OS - //if (isButtonPressed && c == 'm' - // && (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed - // || lastMouseEvent.ButtonState == MouseFlags.Button2Pressed - // || lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) { - - // switch (lastMouseEvent.ButtonState) { - // case MouseFlags.Button1Pressed: - // buttonState = MouseFlags.Button1Released; - // break; - // case MouseFlags.Button2Pressed: - // buttonState = MouseFlags.Button2Released; - // break; - // case MouseFlags.Button3Pressed: - // buttonState = MouseFlags.Button3Released; - // break; - // } - //} else { - // buttonState = MouseFlags.ReportMousePosition; - //} - //break; - case 39: - case 43: - case 47: - case 51: - case 55: - case 59: - case 63: - buttonState = MouseFlags.ReportMousePosition; - break; - case 64: - buttonState = MouseFlags.WheeledUp; - break; - case 65: - buttonState = MouseFlags.WheeledDown; - break; - case 68: - case 72: - case 80: - buttonState = MouseFlags.WheeledLeft; // Shift/Ctrl+WheeledUp - break; - case 69: - case 73: - case 81: - buttonState = MouseFlags.WheeledRight; // Shift/Ctrl+WheeledDown - break; - } - // Modifiers. - switch (buttonCode) { - case 8: - case 9: - case 10: - case 43: - buttonState |= MouseFlags.ButtonAlt; - break; - case 14: - case 47: - buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift; - break; - case 16: - case 17: - case 18: - case 51: - buttonState |= MouseFlags.ButtonCtrl; - break; - case 22: - case 55: - buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift; - break; - case 24: - case 25: - case 26: - case 59: - buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt; - break; - case 30: - case 63: - buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt; - break; - case 32: - case 33: - case 34: - buttonState |= MouseFlags.ReportMousePosition; - break; - case 36: - case 37: - buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift; - break; - case 39: - case 68: - case 69: - buttonState |= MouseFlags.ButtonShift; - break; - case 40: - case 41: - case 42: - buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt; - break; - case 45: - case 46: - buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift; - break; - case 48: - case 49: - case 50: - buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl; - break; - case 53: - case 54: - buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift; - break; - case 56: - case 57: - case 58: - buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt; - break; - case 61: - case 62: - buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt; - break; - } - } - } - - mouseFlags = new List () { MouseFlags.AllEvents }; - - if (lastMouseButtonPressed != null && !isButtonPressed && !buttonState.HasFlag (MouseFlags.ReportMousePosition) - && !buttonState.HasFlag (MouseFlags.Button1Released) - && !buttonState.HasFlag (MouseFlags.Button2Released) - && !buttonState.HasFlag (MouseFlags.Button3Released) - && !buttonState.HasFlag (MouseFlags.Button4Released)) { - - lastMouseButtonPressed = null; - isButtonPressed = false; - } - - if (!isButtonClicked && !isButtonDoubleClicked && ((buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || - buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed) && lastMouseButtonPressed == null) || - isButtonPressed && lastMouseButtonPressed != null && buttonState.HasFlag (MouseFlags.ReportMousePosition)) { - - mouseFlags [0] = buttonState; - lastMouseButtonPressed = buttonState; - isButtonPressed = true; - - if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) { - point = new Point () { - X = pos.X, - Y = pos.Y - }; - - Application.MainLoop.AddIdle (() => { - Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler)); - return false; - }); - } else if (mouseFlags [0] == MouseFlags.ReportMousePosition) { - isButtonPressed = false; - } - - } else if (isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || - buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) { - - mouseFlags [0] = GetButtonTripleClicked (buttonState); - isButtonDoubleClicked = false; - isButtonTripleClicked = true; - - } else if (isButtonClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed || - buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) { - - mouseFlags [0] = GetButtonDoubleClicked (buttonState); - isButtonClicked = false; - isButtonDoubleClicked = true; - Application.MainLoop.AddIdle (() => { - Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); - return false; - }); - - } - //else if (isButtonReleased && !isButtonClicked && buttonState == MouseFlags.ReportMousePosition) { - // mouseFlag [0] = GetButtonClicked ((MouseFlags)lastMouseButtonReleased); - // lastMouseButtonReleased = null; - // isButtonReleased = false; - // isButtonClicked = true; - // Application.MainLoop.AddIdle (() => { - // Task.Run (async () => await ProcessButtonClickedAsync ()); - // return false; - // }); - - //} - else if (!isButtonClicked && !isButtonDoubleClicked && (buttonState == MouseFlags.Button1Released || buttonState == MouseFlags.Button2Released || - buttonState == MouseFlags.Button3Released || buttonState == MouseFlags.Button4Released)) { - - mouseFlags [0] = buttonState; - isButtonPressed = false; - - if (isButtonTripleClicked) { - isButtonTripleClicked = false; - } else if (pos.X == point.X && pos.Y == point.Y) { - mouseFlags.Add (GetButtonClicked (buttonState)); - isButtonClicked = true; - Application.MainLoop.AddIdle (() => { - Task.Run (async () => await ProcessButtonClickedAsync ()); - return false; - }); - } - - point = pos; - - //if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) { - // lastMouseButtonReleased = buttonState; - // isButtonPressed = false; - // isButtonReleased = true; - //} else { - // lastMouseButtonPressed = null; - // isButtonPressed = false; - //} - - } else if (buttonState == MouseFlags.WheeledUp) { - - mouseFlags [0] = MouseFlags.WheeledUp; - - } else if (buttonState == MouseFlags.WheeledDown) { - - mouseFlags [0] = MouseFlags.WheeledDown; - - } else if (buttonState == MouseFlags.WheeledLeft) { - - mouseFlags [0] = MouseFlags.WheeledLeft; - - } else if (buttonState == MouseFlags.WheeledRight) { - - mouseFlags [0] = MouseFlags.WheeledRight; - - } else if (buttonState == MouseFlags.ReportMousePosition) { - mouseFlags [0] = MouseFlags.ReportMousePosition; - - } else { - mouseFlags [0] = buttonState; - //foreach (var flag in buttonState.GetUniqueFlags()) { - // mouseFlag [0] |= flag; - //} - } - - mouseFlags [0] = SetControlKeyStates (buttonState, mouseFlags [0]); - //buttonState = mouseFlags; - - //System.Diagnostics.Debug.WriteLine ($"buttonState: {buttonState} X: {pos.X} Y: {pos.Y}"); - //foreach (var mf in mouseFlags) { - // System.Diagnostics.Debug.WriteLine ($"mouseFlags: {mf} X: {pos.X} Y: {pos.Y}"); - //} - } - - private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action continuousButtonPressedHandler) - { - while (isButtonPressed) { - await Task.Delay (100); - //var me = new MouseEvent () { - // X = point.X, - // Y = point.Y, - // Flags = mouseFlag - //}; - - var view = Application.WantContinuousButtonPressedView; - if (view == null) - break; - if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point)); - } - } - } - - private static async Task ProcessButtonClickedAsync () - { - await Task.Delay (300); - isButtonClicked = false; - } - - private static async Task ProcessButtonDoubleClickedAsync () - { - await Task.Delay (300); - isButtonDoubleClicked = false; - } - - private static MouseFlags GetButtonClicked (MouseFlags mouseFlag) - { - MouseFlags mf = default; - switch (mouseFlag) { - case MouseFlags.Button1Released: - mf = MouseFlags.Button1Clicked; - break; - - case MouseFlags.Button2Released: - mf = MouseFlags.Button2Clicked; - break; - - case MouseFlags.Button3Released: - mf = MouseFlags.Button3Clicked; - break; - } - return mf; - } - - private static MouseFlags GetButtonDoubleClicked (MouseFlags mouseFlag) - { - MouseFlags mf = default; - switch (mouseFlag) { - case MouseFlags.Button1Pressed: - mf = MouseFlags.Button1DoubleClicked; - break; - - case MouseFlags.Button2Pressed: - mf = MouseFlags.Button2DoubleClicked; - break; - - case MouseFlags.Button3Pressed: - mf = MouseFlags.Button3DoubleClicked; - break; - } - return mf; - } - - private static MouseFlags GetButtonTripleClicked (MouseFlags mouseFlag) - { - MouseFlags mf = default; - switch (mouseFlag) { - case MouseFlags.Button1Pressed: - mf = MouseFlags.Button1TripleClicked; - break; - - case MouseFlags.Button2Pressed: - mf = MouseFlags.Button2TripleClicked; - break; - - case MouseFlags.Button3Pressed: - mf = MouseFlags.Button3TripleClicked; - break; - } - return mf; - } - - private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag) - { - if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0) - mouseFlag |= MouseFlags.ButtonCtrl; - - if ((buttonState & MouseFlags.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0) - mouseFlag |= MouseFlags.ButtonShift; - - if ((buttonState & MouseFlags.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0) - mouseFlag |= MouseFlags.ButtonAlt; - return mouseFlag; - } - - /// - /// Get the terminal that holds the console driver. - /// - /// The process. - /// If supported the executable console process, null otherwise. - public static Process GetParentProcess (Process process) - { - if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { - return null; - } - - string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + process.Id; - using (ManagementObjectSearcher mos = new ManagementObjectSearcher (query)) { - foreach (ManagementObject mo in mos.Get ()) { - if (mo ["ParentProcessId"] != null) { - try { - var id = Convert.ToInt32 (mo ["ParentProcessId"]); - return Process.GetProcessById (id); - } catch { - } - } - } - } - return null; - } - } -} \ No newline at end of file diff --git a/Terminal.Gui/MainLoop.cs b/Terminal.Gui/MainLoop.cs index 05179fbfbb..0934e15c49 100644 --- a/Terminal.Gui/MainLoop.cs +++ b/Terminal.Gui/MainLoop.cs @@ -77,8 +77,8 @@ public ReadOnlyCollection> IdleHandlers { /// /// The current in use. /// - /// The driver. - public IMainLoopDriver Driver { get; } + /// The main loop driver. + public IMainLoopDriver MainLoopDriver { get; } /// /// Invoked when a new timeout is added. To be used in the case @@ -93,7 +93,7 @@ public ReadOnlyCollection> IdleHandlers { /// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop). public MainLoop (IMainLoopDriver driver) { - Driver = driver; + MainLoopDriver = driver; driver.Setup (this); } @@ -128,7 +128,7 @@ public Func AddIdle (Func idleHandler) idleHandlers.Add (idleHandler); } - Driver.Wakeup (); + MainLoopDriver.Wakeup (); return idleHandler; } @@ -140,8 +140,9 @@ public Func AddIdle (Func idleHandler) /// This method also returns false if the idle handler is not found. public bool RemoveIdle (Func token) { - lock (_idleHandlersLock) + lock (_idleHandlersLock) { return idleHandlers.Remove (token); + } } void AddTimeout (TimeSpan time, Timeout timeout) @@ -149,7 +150,7 @@ void AddTimeout (TimeSpan time, Timeout timeout) lock (_timeoutsLockToken) { var k = (DateTime.UtcNow + time).Ticks; timeouts.Add (NudgeToUniqueKey (k), timeout); - TimeoutAdded?.Invoke (this, new TimeoutEventArgs(timeout, k)); + TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k)); } } @@ -166,8 +167,9 @@ void AddTimeout (TimeSpan time, Timeout timeout) /// public object AddTimeout (TimeSpan time, Func callback) { - if (callback == null) + if (callback == null) { throw new ArgumentNullException (nameof (callback)); + } var timeout = new Timeout () { Span = time, Callback = callback @@ -188,8 +190,9 @@ public bool RemoveTimeout (object token) { lock (_timeoutsLockToken) { var idx = timeouts.IndexOfValue (token as Timeout); - if (idx == -1) + if (idx == -1) { return false; + } timeouts.RemoveAt (idx); } return true; @@ -213,8 +216,9 @@ void RunTimers () var k = t.Key; var timeout = t.Value; if (k < now) { - if (timeout.Callback (this)) + if (timeout.Callback (this)) { AddTimeout (timeout.Span, timeout); + } } else { lock (_timeoutsLockToken) { timeouts.Add (NudgeToUniqueKey (k), timeout); @@ -249,21 +253,25 @@ void RunIdle () } foreach (var idle in iterate) { - if (idle ()) - lock (_idleHandlersLock) + if (idle ()) { + lock (_idleHandlersLock) { idleHandlers.Add (idle); + } + } } } bool _running; + // BUGBUG: Stop is only called from MainLoopUnitTests.cs. As a result, the mainloop + // will never exit during other unit tests or normal execution. /// /// Stops the mainloop. /// public void Stop () { _running = false; - Driver.Wakeup (); + MainLoopDriver.Wakeup (); } /// @@ -276,7 +284,7 @@ public void Stop () /// public bool EventsPending (bool wait = false) { - return Driver.EventsPending (wait); + return MainLoopDriver.EventsPending (wait); } /// @@ -291,10 +299,11 @@ public bool EventsPending (bool wait = false) /// public void RunIteration () { - if (timeouts.Count > 0) + if (timeouts.Count > 0) { RunTimers (); + } - Driver.Iteration (); + MainLoopDriver.Iteration (); bool runIdle = false; lock (_idleHandlersLock) { diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index 49e669d53c..d1c69247d8 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -28,7 +28,6 @@ "Ctrl" ] }, - "Application.EnableConsoleScrolling": false, "Application.QuitKey": { "Key": "Q", "Modifiers": [ diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index f487887ce6..30592f23d0 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -27,16 +27,12 @@ - - - + + + + - - - - - diff --git a/Terminal.Gui/Text/RuneExtensions.cs b/Terminal.Gui/Text/RuneExtensions.cs index 86781ff77a..8fc1e2abd3 100644 --- a/Terminal.Gui/Text/RuneExtensions.cs +++ b/Terminal.Gui/Text/RuneExtensions.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Text; +using Wcwidth; namespace Terminal.Gui; @@ -26,21 +27,7 @@ public static class RuneExtensions { /// public static int GetColumns (this Rune rune) { - // TODO: I believe there is a way to do this without using our own tables, using Rune. - var codePoint = rune.Value; - switch (codePoint) { - case < 0x20: - case >= 0x7f and < 0xa0: - return -1; - case < 0x7f: - return 1; - } - /* binary search in table of non-spacing characters */ - if (BiSearch (codePoint, _combining, _combining.GetLength (0) - 1) != 0) { - return 0; - } - /* if we arrive here, ucs is not a combining or C0/C1 control character */ - return 1 + (BiSearch (codePoint, _combiningWideChars, _combiningWideChars.GetLength (0) - 1) != 0 ? 1 : 0); + return UnicodeCalculator.GetWidth (rune); } /// @@ -179,133 +166,4 @@ public static bool CanBeEncodedAsRune (byte [] buffer) } return true; } - - // ---------------- implementation details ------------------ - // TODO: Can this be handled by the new .NET 8 Rune type? - static readonly int [,] _combining = new int [,] { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, - { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x2E9A, 0x2E9A }, - { 0x2EF4, 0x2EFF }, { 0x2FD6, 0x2FEF }, { 0x2FFC, 0x2FFF }, - { 0x31E4, 0x31EF }, { 0x321F, 0x321F }, { 0xA48D, 0xA48F }, - { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, - { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE1A, 0xFE1F }, - { 0xFE20, 0xFE23 }, { 0xFE53, 0xFE53 }, { 0xFE67, 0xFE67 }, - { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, - { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF } - }; - - static readonly int [,] _combiningWideChars = new int [,] { - /* Hangul Jamo init. consonants - 0x1100, 0x11ff */ - /* Miscellaneous Technical - 0x2300, 0x23ff */ - /* Hangul Syllables - 0x11a8, 0x11c2 */ - /* CJK Compatibility Ideographs - f900, fad9 */ - /* Vertical forms - fe10, fe19 */ - /* CJK Compatibility Forms - fe30, fe4f */ - /* Fullwidth Forms - ff01, ffee */ - /* Alphabetic Presentation Forms - 0xFB00, 0xFb4f */ - /* Chess Symbols - 0x1FA00, 0x1FA0f */ - - { 0x1100, 0x115f }, { 0x231a, 0x231b }, { 0x2329, 0x232a }, - { 0x23e9, 0x23ec }, { 0x23f0, 0x23f0 }, { 0x23f3, 0x23f3 }, - { 0x25fd, 0x25fe }, { 0x2614, 0x2615 }, { 0x2648, 0x2653 }, - { 0x267f, 0x267f }, { 0x2693, 0x2693 }, { 0x26a1, 0x26a1 }, - { 0x26aa, 0x26ab }, { 0x26bd, 0x26be }, { 0x26c4, 0x26c5 }, - { 0x26ce, 0x26ce }, { 0x26d4, 0x26d4 }, { 0x26ea, 0x26ea }, - { 0x26f2, 0x26f3 }, { 0x26f5, 0x26f5 }, { 0x26fa, 0x26fa }, - { 0x26fd, 0x26fd }, { 0x2705, 0x2705 }, { 0x270a, 0x270b }, - { 0x2728, 0x2728 }, { 0x274c, 0x274c }, { 0x274e, 0x274e }, - { 0x2753, 0x2755 }, { 0x2757, 0x2757 }, { 0x2795, 0x2797 }, - { 0x27b0, 0x27b0 }, { 0x27bf, 0x27bf }, { 0x2b1b, 0x2b1c }, - { 0x2b50, 0x2b50 }, { 0x2b55, 0x2b55 }, { 0x2e80, 0x303e }, - { 0x3041, 0x3096 }, { 0x3099, 0x30ff }, { 0x3105, 0x312f }, - { 0x3131, 0x318e }, { 0x3190, 0x3247 }, { 0x3250, 0x4dbf }, - { 0x4e00, 0xa4c6 }, { 0xa960, 0xa97c }, { 0xac00, 0xd7a3 }, - { 0xf900, 0xfaff }, { 0xfe10, 0xfe1f }, { 0xfe30, 0xfe6b }, - { 0xff01, 0xff60 }, { 0xffe0, 0xffe6 }, - { 0x16fe0, 0x16fe4 }, { 0x16ff0, 0x16ff1 }, { 0x17000, 0x187f7 }, - { 0x18800, 0x18cd5 }, { 0x18d00, 0x18d08 }, { 0x1aff0, 0x1affc }, - { 0x1b000, 0x1b122 }, { 0x1b150, 0x1b152 }, { 0x1b164, 0x1b167 }, { 0x1b170, 0x1b2fb }, { 0x1d538, 0x1d550 }, - { 0x1f004, 0x1f004 }, { 0x1f0cf, 0x1f0cf }, /*{ 0x1f100, 0x1f10a },*/ - //{ 0x1f110, 0x1f12d }, { 0x1f130, 0x1f169 }, { 0x1f170, 0x1f1ac }, - { 0x1f18f, 0x1f199 }, - { 0x1f1e6, 0x1f1ff }, { 0x1f200, 0x1f202 }, { 0x1f210, 0x1f23b }, - { 0x1f240, 0x1f248 }, { 0x1f250, 0x1f251 }, { 0x1f260, 0x1f265 }, - { 0x1f300, 0x1f320 }, { 0x1f32d, 0x1f33e }, { 0x1f340, 0x1f37e }, - { 0x1f380, 0x1f393 }, { 0x1f3a0, 0x1f3ca }, { 0x1f3cf, 0x1f3d3 }, - { 0x1f3e0, 0x1f3f0 }, { 0x1f3f4, 0x1f3f4 }, { 0x1f3f8, 0x1f43e }, - { 0x1f440, 0x1f44e }, { 0x1f450, 0x1f4fc }, { 0x1f4ff, 0x1f53d }, - { 0x1f54b, 0x1f54e }, { 0x1f550, 0x1f567 }, { 0x1f57a, 0x1f57a }, - { 0x1f595, 0x1f596 }, { 0x1f5a4, 0x1f5a4 }, { 0x1f5fb, 0x1f606 }, - { 0x1f607, 0x1f64f }, { 0x1f680, 0x1f6c5 }, { 0x1f6cc, 0x1f6cc }, - { 0x1f6d0, 0x1f6d2 }, { 0x1f6d5, 0x1f6d7 }, { 0x1f6dd, 0x1f6df }, { 0x1f6eb, 0x1f6ec }, - { 0x1f6f4, 0x1f6fc }, { 0x1f7e0, 0x1f7eb }, { 0x1f7f0, 0x1f7f0 }, { 0x1f90c, 0x1f93a }, - { 0x1f93c, 0x1f945 }, { 0x1f947, 0x1f97f }, { 0x1f980, 0x1f9cc }, - { 0x1f9cd, 0x1f9ff }, { 0x1fa70, 0x1fa74 }, { 0x1fa78, 0x1fa7c }, { 0x1fa80, 0x1fa86 }, - { 0x1fa90, 0x1faac }, { 0x1fab0, 0x1faba }, { 0x1fac0, 0x1fac5 }, - { 0x1fad0, 0x1fad9 }, { 0x1fae0, 0x1fae7 }, { 0x1faf0, 0x1faf6 }, { 0x20000, 0x2fffd }, { 0x30000, 0x3fffd }, - //{ 0xe0100, 0xe01ef }, { 0xf0000, 0xffffd }, { 0x100000, 0x10fffd } - }; - - static int BiSearch (int rune, int [,] table, int max) - { - var min = 0; - - if (rune < table [0, 0] || rune > table [max, 1]) { - return 0; - } - while (max >= min) { - var mid = (min + max) / 2; - if (rune > table [mid, 1]) { - min = mid + 1; - } else if (rune < table [mid, 0]) { - max = mid - 1; - } else { - return 1; - } - } - - return 0; - } } \ No newline at end of file diff --git a/Terminal.Gui/View/Frame.cs b/Terminal.Gui/View/Frame.cs index 83ecb68923..6183819b29 100644 --- a/Terminal.Gui/View/Frame.cs +++ b/Terminal.Gui/View/Frame.cs @@ -126,7 +126,9 @@ public virtual void OnDrawSubViews (Rect clipRect) /// public override void OnDrawContent (Rect contentArea) { - if (Thickness == Thickness.Empty) return; + if (Thickness == Thickness.Empty) { + return; + } if (ColorScheme != null) { Driver.SetAttribute (GetNormalColor ()); @@ -139,9 +141,6 @@ public override void OnDrawContent (Rect contentArea) } //Driver.SetAttribute (Colors.Error.Normal); - - var prevClip = SetClip (Frame); - var screenBounds = ViewToScreen (Frame); // This just draws/clears the thickness, not the insides. @@ -305,7 +304,7 @@ public override void OnDrawContent (Rect contentArea) } } - Driver.Clip = prevClip; + ClearNeedsDisplay (); } // TODO: v2 - Frame.BorderStyle is temporary - Eventually the border will be drawn by a "BorderView" that is a subview of the Frame. diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 7413616098..17a725e16b 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -9,7 +9,7 @@ public partial class View { /// /// The color scheme for this view, if it is not defined, it returns the 's - /// color scheme. + /// color scheme. /// public virtual ColorScheme ColorScheme { get { @@ -34,7 +34,11 @@ public virtual ColorScheme ColorScheme { /// If it's overridden can return other values. public virtual Attribute GetNormalColor () { - return Enabled ? ColorScheme.Normal : ColorScheme.Disabled; + ColorScheme cs = ColorScheme; + if (ColorScheme == null) { + cs = new ColorScheme (); + } + return Enabled ? cs.Normal : cs.Disabled; } /// @@ -76,106 +80,126 @@ public void AddRune (int col, int row, Rune ch) } /// - /// Removes the and the setting on this view. + /// Clears and . /// protected void ClearNeedsDisplay () { - _needsDisplay = Rect.Empty; + _needsDisplayRect = Rect.Empty; _subViewNeedsDisplay = false; } - // The view-relative region that needs to be redrawn - internal Rect _needsDisplay { get; private set; } = Rect.Empty; + // The view-relative region that needs to be redrawn. Marked internal for unit tests. + internal Rect _needsDisplayRect = Rect.Empty; + + /// + /// Gets or sets whether the view needs to be redrawn. + /// + public bool NeedsDisplay { + get => _needsDisplayRect != Rect.Empty; + set { + if (value) { + SetNeedsDisplay (); + } else { + ClearNeedsDisplay (); + } + } + } /// - /// Sets a flag indicating this view needs to be redisplayed because its state has changed. + /// Sets the area of this view needing to be redrawn to . /// public void SetNeedsDisplay () { - if (!IsInitialized) return; + if (!IsInitialized) { + return; + } SetNeedsDisplay (Bounds); } /// - /// Flags the view-relative region on this View as needing to be redrawn. + /// Expands the area of this view needing to be redrawn to include . /// /// The view-relative region that needs to be redrawn. public void SetNeedsDisplay (Rect region) { - if (_needsDisplay.IsEmpty) { - _needsDisplay = region; + if (_needsDisplayRect.IsEmpty) { + _needsDisplayRect = region; } else { - var x = Math.Min (_needsDisplay.X, region.X); - var y = Math.Min (_needsDisplay.Y, region.Y); - var w = Math.Max (_needsDisplay.Width, region.Width); - var h = Math.Max (_needsDisplay.Height, region.Height); - _needsDisplay = new Rect (x, y, w, h); + var x = Math.Min (_needsDisplayRect.X, region.X); + var y = Math.Min (_needsDisplayRect.Y, region.Y); + var w = Math.Max (_needsDisplayRect.Width, region.Width); + var h = Math.Max (_needsDisplayRect.Height, region.Height); + _needsDisplayRect = new Rect (x, y, w, h); } _superView?.SetSubViewNeedsDisplay (); - if (_subviews == null) + if (_needsDisplayRect.X < Bounds.X || + _needsDisplayRect.Y < Bounds.Y || + _needsDisplayRect.Width > Bounds.Width || + _needsDisplayRect.Height > Bounds.Height) { + Margin?.SetNeedsDisplay (Margin.Bounds); + Border?.SetNeedsDisplay (Border.Bounds); + Padding?.SetNeedsDisplay (Padding.Bounds); + } + + if (_subviews == null) { return; + } - foreach (var view in _subviews) - if (view.Frame.IntersectsWith (region)) { - var childRegion = Rect.Intersect (view.Frame, region); - childRegion.X -= view.Frame.X; - childRegion.Y -= view.Frame.Y; - view.SetNeedsDisplay (childRegion); + foreach (var subview in _subviews) { + if (subview.Frame.IntersectsWith (region)) { + var subviewRegion = Rect.Intersect (subview.Frame, region); + subviewRegion.X -= subview.Frame.X; + subviewRegion.Y -= subview.Frame.Y; + subview.SetNeedsDisplay (subviewRegion); } + } + } + + /// + /// Gets whether any Subviews need to be redrawn. + /// + public bool SubViewNeedsDisplay { + get => _subViewNeedsDisplay; } - internal bool _subViewNeedsDisplay { get; private set; } + bool _subViewNeedsDisplay; /// /// Indicates that any Subviews (in the list) need to be repainted. /// public void SetSubViewNeedsDisplay () { - if (_subViewNeedsDisplay) { - return; - } _subViewNeedsDisplay = true; - if (_superView != null && !_superView._subViewNeedsDisplay) + if (_superView != null && !_superView._subViewNeedsDisplay) { _superView.SetSubViewNeedsDisplay (); + } } /// - /// Clears the view region with the current color. + /// Clears the with the normal background color. /// /// /// - /// This clears the entire region used by this view. + /// This clears the Bounds used by this view. /// /// - public void Clear () - { - var h = Frame.Height; - var w = Frame.Width; - for (var line = 0; line < h; line++) { - Move (0, line); - for (var col = 0; col < w; col++) - Driver.AddRune ((Rune)' '); - } - } + public void Clear () => Clear (ViewToScreen(Bounds)); - // BUGBUG: Stupid that this takes screen-relative. We should have a tenet that says - // "View APIs only deal with View-relative coords". + // BUGBUG: This version of the Clear API should be removed. We should have a tenet that says + // "View APIs only deal with View-relative coords". This is only used by ComboBox which can + // be refactored to use the View-relative version. /// - /// Clears the specified region with the current color. + /// Clears the specified screen-relative rectangle with the normal background. /// /// /// - /// The screen-relative region to clear. + /// The screen-relative rectangle to clear. public void Clear (Rect regionScreen) { - var h = regionScreen.Height; - var w = regionScreen.Width; - for (var line = regionScreen.Y; line < regionScreen.Y + h; line++) { - Driver.Move (regionScreen.X, line); - for (var col = 0; col < w; col++) - Driver.AddRune ((Rune)' '); - } + var prev = Driver.SetAttribute (GetNormalColor ()); + Driver.FillRect (regionScreen); + Driver.SetAttribute (prev); } // Clips a rectangle in screen coordinates to the dimensions currently available on the screen @@ -190,35 +214,18 @@ internal Rect ScreenClip (Rect regionScreen) } /// - /// Sets the 's clip region to . + /// Expands the 's clip region to include . /// /// The current screen-relative clip region, which can be then re-applied by setting . /// /// - /// is View-relative. - /// - /// /// If and do not intersect, the clip region will be set to . /// /// public Rect ClipToBounds () - { - return SetClip (Bounds); - } - - // BUGBUG: v2 - SetClip should return VIEW-relative so that it can be used to reset it; using Driver.Clip directly should not be necessary. - /// - /// Sets the clip region to the specified view-relative region. - /// - /// The current screen-relative clip region, which can be then re-applied by setting . - /// View-relative clip region. - /// - /// If and do not intersect, the clip region will be set to . - /// - public Rect SetClip (Rect region) { var previous = Driver.Clip; - Driver.Clip = Rect.Intersect (previous, ViewToScreen (region)); + Driver.Clip = Rect.Intersect (previous, ViewToScreen (Bounds)); return previous; } @@ -304,22 +311,11 @@ public virtual bool OnDrawFrames () return false; } - var prevClip = Driver.Clip; - Driver.Clip = ViewToScreen (Frame); - - // TODO: Figure out what we should do if we have no superview - //if (SuperView != null) { - // TODO: Clipping is disabled for now to ensure we see errors - Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);// screenBounds;// SuperView.ClipToBounds (); - //} - // Each of these renders lines to either this View's LineCanvas // Those lines will be finally rendered in OnRenderLineCanvas - Margin?.OnDrawContent (Bounds); - Border?.OnDrawContent (Bounds); - Padding?.OnDrawContent (Bounds); - - Driver.Clip = prevClip; + Margin?.OnDrawContent (Margin.Bounds); + Border?.OnDrawContent (Border.Bounds); + Padding?.OnDrawContent (Padding.Bounds); return true; } @@ -346,7 +342,6 @@ public void Draw () if (!CanBeVisible (this)) { return; } - OnDrawFrames (); var prevClip = ClipToBounds (); @@ -367,7 +362,6 @@ public void Draw () Driver.Clip = prevClip; OnRenderLineCanvas (); - // Invoke DrawContentCompleteEvent OnDrawContentComplete (Bounds); @@ -389,29 +383,29 @@ public virtual bool OnRenderLineCanvas () return false; } - //Driver.SetAttribute (new Attribute(Color.White, Color.Black)); - // If we have a SuperView, it'll render our frames. if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) { foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map - Driver.SetAttribute (p.Value.Attribute?.Value ?? ColorScheme.Normal); + Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); Driver.Move (p.Key.X, p.Key.Y); - Driver.AddRune (p.Value.Rune.Value); + // TODO: #2616 - Support combining sequences that don't normalize + Driver.AddRune (p.Value.Runes [0]); } LineCanvas.Clear (); } - if (Subviews.Where (s => s.SuperViewRendersLineCanvas).Count () > 0) { + if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) { foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) { - // Combine the LineCavas' + // Combine the LineCanvas' LineCanvas.Merge (subview.LineCanvas); subview.LineCanvas.Clear (); } foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map - Driver.SetAttribute (p.Value.Attribute?.Value ?? ColorScheme.Normal); + Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal); Driver.Move (p.Key.X, p.Key.Y); - Driver.AddRune (p.Value.Rune.Value); + // TODO: #2616 - Support combining sequences that don't normalize + Driver.AddRune (p.Value.Runes [0]); } LineCanvas.Clear (); } @@ -441,38 +435,45 @@ public virtual bool OnRenderLineCanvas () /// public virtual void OnDrawContent (Rect contentArea) { - if (SuperView != null) { - Clear (ViewToScreen (Bounds)); - } + if (NeedsDisplay) { + if (SuperView != null) { + Clear (ViewToScreen (Bounds)); + } - if (!string.IsNullOrEmpty (TextFormatter.Text)) { - if (TextFormatter != null) { - TextFormatter.NeedsFormat = true; + if (!string.IsNullOrEmpty (TextFormatter.Text)) { + if (TextFormatter != null) { + TextFormatter.NeedsFormat = true; + } } // This should NOT clear TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (), - HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), - Rect.Empty, false); + HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), + Rect.Empty, false); SetSubViewNeedsDisplay (); } // Draw subviews // TODO: Implement OnDrawSubviews (cancelable); - if (_subviews != null) { - foreach (var view in _subviews) { - if (view.Visible) { //!view._needsDisplay.IsEmpty || view._childNeedsDisplay || view.LayoutNeeded) { - if (true) { //view.Frame.IntersectsWith (bounds)) { // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { - if (view.LayoutNeeded) { - view.LayoutSubviews (); - } - - // Draw the subview - // Use the view's bounds (view-relative; Location will always be (0,0) - //if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { - view.Draw (); - //} - } + if (_subviews != null && SubViewNeedsDisplay) { + var subviewsNeedingDraw = _subviews.Where ( + view => view.Visible && + (view.NeedsDisplay || + view.SubViewNeedsDisplay || + view.LayoutNeeded) + ); + + foreach (var view in subviewsNeedingDraw) { + //view.Frame.IntersectsWith (bounds)) { + // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { + if (view.LayoutNeeded) { + view.LayoutSubviews (); } + + // Draw the subview + // Use the view's bounds (view-relative; Location will always be (0,0) + //if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { + view.Draw (); + //} } } } diff --git a/Terminal.Gui/View/ViewLayout.cs b/Terminal.Gui/View/ViewLayout.cs index a8fed3a265..3b720339c6 100644 --- a/Terminal.Gui/View/ViewLayout.cs +++ b/Terminal.Gui/View/ViewLayout.cs @@ -470,16 +470,15 @@ protected virtual void OnResizeNeeded () internal void SetNeedsLayout () { - if (LayoutNeeded) + if (LayoutNeeded) { return; + } LayoutNeeded = true; foreach (var view in Subviews) { view.SetNeedsLayout (); } TextFormatter.NeedsFormat = true; - if (SuperView == null) - return; - SuperView.SetNeedsLayout (); + SuperView?.SetNeedsLayout (); } /// @@ -541,7 +540,7 @@ public virtual void ViewToScreen (int col, int row, out int rcol, out int rrow, /// /// Converts a region in view-relative coordinates to screen-relative coordinates. /// - internal Rect ViewToScreen (Rect region) + public Rect ViewToScreen (Rect region) { ViewToScreen (region.X, region.Y, out var x, out var y, clamped: false); return new Rect (x, y, region.Width, region.Height); diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 01226fd809..1657e09ed0 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -5,7 +5,7 @@ using System.Text; namespace Terminal.Gui { - public partial class View { + public partial class View { static readonly IList _empty = new List (0).AsReadOnly (); View _superView = null; @@ -128,7 +128,7 @@ public virtual void OnAdded (SuperViewChangedEventArgs e) } /// - /// Gets information if the view was already added to the . + /// Indicates whether the view was added to . /// public bool IsAdded { get; private set; } @@ -420,10 +420,12 @@ public override bool OnEnter (View view) { var args = new FocusEventArgs (view); Enter?.Invoke (this, args); - if (args.Handled) + if (args.Handled) { return true; - if (base.OnEnter (view)) + } + if (base.OnEnter (view)) { return true; + } return false; } @@ -433,11 +435,14 @@ public override bool OnLeave (View view) { var args = new FocusEventArgs (view); Leave?.Invoke (this, args); - if (args.Handled) + if (args.Handled) { return true; - if (base.OnLeave (view)) + } + if (base.OnLeave (view)) { return true; + } + Driver?.SetCursorVisibility (CursorVisibility.Invisible); return false; } diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index 0968b1d337..e8eaf8088f 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -119,11 +119,11 @@ protected override void UpdateTextFormatterText () Rune GetCheckedState () { - switch (Checked) { - case true: return charChecked; - case false: return charUnChecked; - default: return charNullChecked; - } + return Checked switch { + true => charChecked, + false => charUnChecked, + var _ => charNullChecked + }; } string GetFormatterText () @@ -157,9 +157,7 @@ public bool AllowNullChecked { get => allowNullChecked; set { allowNullChecked = value; - if (Checked == null) { - Checked = false; - } + Checked ??= false; } } diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index c9d0cb9b4b..36bcf3b636 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -68,14 +68,7 @@ public int BoxHeight { } } } - private int _boxHeight = 2; - - // Cursor runes. - private static readonly Rune [] _cursorRunes = new Rune [] - { - (Rune)0x250C, (Rune) 0x2500, (Rune) 0x2500, (Rune) 0x2510, - (Rune) 0x2514, (Rune) 0x2500, (Rune) 0x2500, (Rune) 0x2518 - }; + int _boxHeight = 2; /// /// Cursor for the selected color. diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index e01a41fd52..b425e5f073 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -213,8 +213,8 @@ public override void OnDrawContent (Rect contentArea) Source.Position = displayStart; var n = source.Read (data, 0, data.Length); - int activeColor = ColorScheme.HotNormal; - int trackingColor = ColorScheme.HotFocus; + var activeColor = ColorScheme.HotNormal; + var trackingColor = ColorScheme.HotFocus; for (int line = 0; line < frame.Height; line++) { var lineRect = new Rect (0, line, frame.Width, 1); diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 5e18a55927..74af49e30e 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -573,7 +573,6 @@ public override void OnDrawContent (Rect contentArea) } var savedClip = Driver.Clip; Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows); - Driver.SetAttribute (GetNormalColor ()); OnDrawFrames (); diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index f8cd3a4739..bb83466360 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -14,597 +14,596 @@ using System; using System.Linq; -namespace Terminal.Gui { - /// - /// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. - /// - /// - /// - /// The subviews that are added to this are offset by the - /// property. The view itself is a window into the - /// space represented by the . - /// - /// - /// Use the - /// - /// - public class ScrollView : View { - - // The ContentView is the view that contains the subviews and content that are being scrolled - // The ContentView is the size of the ContentSize and is offset by the ContentOffset - private class ContentView : View { - public ContentView (Rect frame) : base (frame) - { - Id = "ScrollView.ContentView"; - CanFocus = true; - } +namespace Terminal.Gui; +/// +/// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. +/// +/// +/// +/// The subviews that are added to this are offset by the +/// property. The view itself is a window into the +/// space represented by the . +/// +/// +/// Use the +/// +/// +public class ScrollView : View { + + // The ContentView is the view that contains the subviews and content that are being scrolled + // The ContentView is the size of the ContentSize and is offset by the ContentOffset + private class ContentView : View { + public ContentView (Rect frame) : base (frame) + { + Id = "ScrollView.ContentView"; + CanFocus = true; } + } - ContentView _contentView; - ScrollBarView _vertical, _horizontal; + ContentView _contentView; + ScrollBarView _vertical, _horizontal; - /// - /// Initializes a new instance of the class using positioning. - /// - /// - public ScrollView (Rect frame) : base (frame) - { - SetInitialProperties (frame); - } + /// + /// Initializes a new instance of the class using positioning. + /// + /// + public ScrollView (Rect frame) : base (frame) + { + SetInitialProperties (frame); + } - /// - /// Initializes a new instance of the class using positioning. - /// - public ScrollView () : base () - { - SetInitialProperties (Rect.Empty); - } + /// + /// Initializes a new instance of the class using positioning. + /// + public ScrollView () : base () + { + SetInitialProperties (Rect.Empty); + } - void SetInitialProperties (Rect frame) - { - _contentView = new ContentView (frame); - _vertical = new ScrollBarView (1, 0, isVertical: true) { - X = Pos.AnchorEnd (1), - Y = 0, - Width = 1, - Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0), - Host = this + void SetInitialProperties (Rect frame) + { + _contentView = new ContentView (frame); + _vertical = new ScrollBarView (1, 0, isVertical: true) { + X = Pos.AnchorEnd (1), + Y = 0, + Width = 1, + Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0), + Host = this + }; + + _horizontal = new ScrollBarView (1, 0, isVertical: false) { + X = 0, + Y = Pos.AnchorEnd (1), + Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0), + Height = 1, + Host = this + }; + + _vertical.OtherScrollBarView = _horizontal; + _horizontal.OtherScrollBarView = _vertical; + base.Add (_contentView); + CanFocus = true; + + MouseEnter += View_MouseEnter; + MouseLeave += View_MouseLeave; + _contentView.MouseEnter += View_MouseEnter; + _contentView.MouseLeave += View_MouseLeave; + + // Things this view knows how to do + AddCommand (Command.ScrollUp, () => ScrollUp (1)); + AddCommand (Command.ScrollDown, () => ScrollDown (1)); + AddCommand (Command.ScrollLeft, () => ScrollLeft (1)); + AddCommand (Command.ScrollRight, () => ScrollRight (1)); + AddCommand (Command.PageUp, () => ScrollUp (Bounds.Height)); + AddCommand (Command.PageDown, () => ScrollDown (Bounds.Height)); + AddCommand (Command.PageLeft, () => ScrollLeft (Bounds.Width)); + AddCommand (Command.PageRight, () => ScrollRight (Bounds.Width)); + AddCommand (Command.TopHome, () => ScrollUp (_contentSize.Height)); + AddCommand (Command.BottomEnd, () => ScrollDown (_contentSize.Height)); + AddCommand (Command.LeftHome, () => ScrollLeft (_contentSize.Width)); + AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width)); + + // Default keybindings for this view + AddKeyBinding (Key.CursorUp, Command.ScrollUp); + AddKeyBinding (Key.CursorDown, Command.ScrollDown); + AddKeyBinding (Key.CursorLeft, Command.ScrollLeft); + AddKeyBinding (Key.CursorRight, Command.ScrollRight); + + AddKeyBinding (Key.PageUp, Command.PageUp); + AddKeyBinding ((Key)'v' | Key.AltMask, Command.PageUp); + + AddKeyBinding (Key.PageDown, Command.PageDown); + AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown); + + AddKeyBinding (Key.PageUp | Key.CtrlMask, Command.PageLeft); + AddKeyBinding (Key.PageDown | Key.CtrlMask, Command.PageRight); + AddKeyBinding (Key.Home, Command.TopHome); + AddKeyBinding (Key.End, Command.BottomEnd); + AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome); + AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd); + + Initialized += (s, e) => { + if (!_vertical.IsInitialized) { + _vertical.BeginInit (); + _vertical.EndInit (); + } + if (!_horizontal.IsInitialized) { + _horizontal.BeginInit (); + _horizontal.EndInit (); + } + SetContentOffset (_contentOffset); + _contentView.Frame = new Rect (ContentOffset, ContentSize); + _vertical.ChangedPosition += delegate { + ContentOffset = new Point (ContentOffset.X, _vertical.Position); }; - - _horizontal = new ScrollBarView (1, 0, isVertical: false) { - X = 0, - Y = Pos.AnchorEnd (1), - Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0), - Height = 1, - Host = this + _horizontal.ChangedPosition += delegate { + ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); }; + }; + } - _vertical.OtherScrollBarView = _horizontal; - _horizontal.OtherScrollBarView = _vertical; - base.Add (_contentView); - CanFocus = true; + //public override void BeginInit () + //{ + // SetContentOffset (contentOffset); + // base.BeginInit (); + //} - MouseEnter += View_MouseEnter; - MouseLeave += View_MouseLeave; - _contentView.MouseEnter += View_MouseEnter; - _contentView.MouseLeave += View_MouseLeave; - - // Things this view knows how to do - AddCommand (Command.ScrollUp, () => ScrollUp (1)); - AddCommand (Command.ScrollDown, () => ScrollDown (1)); - AddCommand (Command.ScrollLeft, () => ScrollLeft (1)); - AddCommand (Command.ScrollRight, () => ScrollRight (1)); - AddCommand (Command.PageUp, () => ScrollUp (Bounds.Height)); - AddCommand (Command.PageDown, () => ScrollDown (Bounds.Height)); - AddCommand (Command.PageLeft, () => ScrollLeft (Bounds.Width)); - AddCommand (Command.PageRight, () => ScrollRight (Bounds.Width)); - AddCommand (Command.TopHome, () => ScrollUp (_contentSize.Height)); - AddCommand (Command.BottomEnd, () => ScrollDown (_contentSize.Height)); - AddCommand (Command.LeftHome, () => ScrollLeft (_contentSize.Width)); - AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width)); - - // Default keybindings for this view - AddKeyBinding (Key.CursorUp, Command.ScrollUp); - AddKeyBinding (Key.CursorDown, Command.ScrollDown); - AddKeyBinding (Key.CursorLeft, Command.ScrollLeft); - AddKeyBinding (Key.CursorRight, Command.ScrollRight); - - AddKeyBinding (Key.PageUp, Command.PageUp); - AddKeyBinding ((Key)'v' | Key.AltMask, Command.PageUp); - - AddKeyBinding (Key.PageDown, Command.PageDown); - AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown); - - AddKeyBinding (Key.PageUp | Key.CtrlMask, Command.PageLeft); - AddKeyBinding (Key.PageDown | Key.CtrlMask, Command.PageRight); - AddKeyBinding (Key.Home, Command.TopHome); - AddKeyBinding (Key.End, Command.BottomEnd); - AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome); - AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd); - - Initialized += (s, e) => { - if (!_vertical.IsInitialized) { - _vertical.BeginInit (); - _vertical.EndInit (); - } - if (!_horizontal.IsInitialized) { - _horizontal.BeginInit (); - _horizontal.EndInit (); - } - SetContentOffset (_contentOffset); - _contentView.Frame = new Rect (ContentOffset, ContentSize); - _vertical.ChangedPosition += delegate { - ContentOffset = new Point (ContentOffset.X, _vertical.Position); - }; - _horizontal.ChangedPosition += delegate { - ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); - }; - }; - } + Size _contentSize; + Point _contentOffset; + bool _showHorizontalScrollIndicator; + bool _showVerticalScrollIndicator; + bool _keepContentAlwaysInViewport = true; + bool _autoHideScrollBars = true; - //public override void BeginInit () - //{ - // SetContentOffset (contentOffset); - // base.BeginInit (); - //} - - Size _contentSize; - Point _contentOffset; - bool _showHorizontalScrollIndicator; - bool _showVerticalScrollIndicator; - bool _keepContentAlwaysInViewport = true; - bool _autoHideScrollBars = true; - - /// - /// Represents the contents of the data shown inside the scrollview - /// - /// The size of the content. - public Size ContentSize { - get { - return _contentSize; - } - set { - if (_contentSize != value) { - _contentSize = value; - _contentView.Frame = new Rect (_contentOffset, value); - _vertical.Size = _contentSize.Height; - _horizontal.Size = _contentSize.Width; - SetNeedsDisplay (); - } + /// + /// Represents the contents of the data shown inside the scrollview + /// + /// The size of the content. + public Size ContentSize { + get { + return _contentSize; + } + set { + if (_contentSize != value) { + _contentSize = value; + _contentView.Frame = new Rect (_contentOffset, value); + _vertical.Size = _contentSize.Height; + _horizontal.Size = _contentSize.Width; + SetNeedsDisplay (); } } + } - /// - /// Represents the top left corner coordinate that is displayed by the scrollview - /// - /// The content offset. - public Point ContentOffset { - get { - return _contentOffset; + /// + /// Represents the top left corner coordinate that is displayed by the scrollview + /// + /// The content offset. + public Point ContentOffset { + get { + return _contentOffset; + } + set { + if (!IsInitialized) { + // We're not initialized so we can't do anything fancy. Just cache value. + _contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); ; + return; } - set { - if (!IsInitialized) { - // We're not initialized so we can't do anything fancy. Just cache value. - _contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); ; - return; - } - SetContentOffset (value); - } + SetContentOffset (value); } + } - private void SetContentOffset (Point offset) - { - var co = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y)); - _contentOffset = co; - _contentView.Frame = new Rect (_contentOffset, _contentSize); - var p = Math.Max (0, -_contentOffset.Y); - if (_vertical.Position != p) { - _vertical.Position = Math.Max (0, -_contentOffset.Y); - } - p = Math.Max (0, -_contentOffset.X); - if (_horizontal.Position != p) { - _horizontal.Position = Math.Max (0, -_contentOffset.X); - } - SetNeedsDisplay (); - } - - /// - /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. - /// - public bool AutoHideScrollBars { - get => _autoHideScrollBars; - set { - if (_autoHideScrollBars != value) { - _autoHideScrollBars = value; - if (Subviews.Contains (_vertical)) { - _vertical.AutoHideScrollBars = value; - } - if (Subviews.Contains (_horizontal)) { - _horizontal.AutoHideScrollBars = value; - } - SetNeedsDisplay (); + private void SetContentOffset (Point offset) + { + var co = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y)); + _contentOffset = co; + _contentView.Frame = new Rect (_contentOffset, _contentSize); + var p = Math.Max (0, -_contentOffset.Y); + if (_vertical.Position != p) { + _vertical.Position = Math.Max (0, -_contentOffset.Y); + } + p = Math.Max (0, -_contentOffset.X); + if (_horizontal.Position != p) { + _horizontal.Position = Math.Max (0, -_contentOffset.X); + } + SetNeedsDisplay (); + } + + /// + /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. + /// + public bool AutoHideScrollBars { + get => _autoHideScrollBars; + set { + if (_autoHideScrollBars != value) { + _autoHideScrollBars = value; + if (Subviews.Contains (_vertical)) { + _vertical.AutoHideScrollBars = value; + } + if (Subviews.Contains (_horizontal)) { + _horizontal.AutoHideScrollBars = value; } + SetNeedsDisplay (); } } + } - /// - /// Get or sets if the view-port is kept always visible in the area of this - /// - public bool KeepContentAlwaysInViewport { - get { return _keepContentAlwaysInViewport; } - set { - if (_keepContentAlwaysInViewport != value) { - _keepContentAlwaysInViewport = value; - _vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value; - _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value; - Point p = default; - if (value && -_contentOffset.X + Bounds.Width > _contentSize.Width) { - p = new Point (_contentSize.Width - Bounds.Width + (_showVerticalScrollIndicator ? 1 : 0), -_contentOffset.Y); - } - if (value && -_contentOffset.Y + Bounds.Height > _contentSize.Height) { - if (p == default) { - p = new Point (-_contentOffset.X, _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0)); - } else { - p.Y = _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0); - } - } - if (p != default) { - ContentOffset = p; + /// + /// Get or sets if the view-port is kept always visible in the area of this + /// + public bool KeepContentAlwaysInViewport { + get { return _keepContentAlwaysInViewport; } + set { + if (_keepContentAlwaysInViewport != value) { + _keepContentAlwaysInViewport = value; + _vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value; + _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value; + Point p = default; + if (value && -_contentOffset.X + Bounds.Width > _contentSize.Width) { + p = new Point (_contentSize.Width - Bounds.Width + (_showVerticalScrollIndicator ? 1 : 0), -_contentOffset.Y); + } + if (value && -_contentOffset.Y + Bounds.Height > _contentSize.Height) { + if (p == default) { + p = new Point (-_contentOffset.X, _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0)); + } else { + p.Y = _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0); } } + if (p != default) { + ContentOffset = p; + } } } + } - View _contentBottomRightCorner; + View _contentBottomRightCorner; - /// - /// Adds the view to the scrollview. - /// - /// The view to add to the scrollview. - public override void Add (View view) - { - if (view.Id == "contentBottomRightCorner") { - _contentBottomRightCorner = view; - base.Add (view); - } else { - if (!IsOverridden (view, "MouseEvent")) { - view.MouseEnter += View_MouseEnter; - view.MouseLeave += View_MouseLeave; - } - _contentView.Add (view); - } - SetNeedsLayout (); + /// + /// Adds the view to the scrollview. + /// + /// The view to add to the scrollview. + public override void Add (View view) + { + if (view.Id == "contentBottomRightCorner") { + _contentBottomRightCorner = view; + base.Add (view); + } else { + if (!IsOverridden (view, "MouseEvent")) { + view.MouseEnter += View_MouseEnter; + view.MouseLeave += View_MouseLeave; + } + _contentView.Add (view); } + SetNeedsLayout (); + } - void View_MouseLeave (object sender, MouseEventEventArgs e) - { - if (Application.MouseGrabView != null && Application.MouseGrabView != _vertical && Application.MouseGrabView != _horizontal) { - Application.UngrabMouse (); - } + void View_MouseLeave (object sender, MouseEventEventArgs e) + { + if (Application.MouseGrabView != null && Application.MouseGrabView != _vertical && Application.MouseGrabView != _horizontal) { + Application.UngrabMouse (); } + } - void View_MouseEnter (object sender, MouseEventEventArgs e) - { - Application.GrabMouse (this); - } - - /// - /// Gets or sets the visibility for the horizontal scroll indicator. - /// - /// true if show horizontal scroll indicator; otherwise, false. - public bool ShowHorizontalScrollIndicator { - get => _showHorizontalScrollIndicator; - set { - if (value != _showHorizontalScrollIndicator) { - _showHorizontalScrollIndicator = value; - SetNeedsLayout (); - if (value) { - _horizontal.OtherScrollBarView = _vertical; - base.Add (_horizontal); - _horizontal.ShowScrollIndicator = value; - _horizontal.AutoHideScrollBars = _autoHideScrollBars; - _horizontal.OtherScrollBarView.ShowScrollIndicator = value; - _horizontal.MouseEnter += View_MouseEnter; - _horizontal.MouseLeave += View_MouseLeave; - } else { - base.Remove (_horizontal); - _horizontal.OtherScrollBarView = null; - _horizontal.MouseEnter -= View_MouseEnter; - _horizontal.MouseLeave -= View_MouseLeave; - } + void View_MouseEnter (object sender, MouseEventEventArgs e) + { + Application.GrabMouse (this); + } + + /// + /// Gets or sets the visibility for the horizontal scroll indicator. + /// + /// true if show horizontal scroll indicator; otherwise, false. + public bool ShowHorizontalScrollIndicator { + get => _showHorizontalScrollIndicator; + set { + if (value != _showHorizontalScrollIndicator) { + _showHorizontalScrollIndicator = value; + SetNeedsLayout (); + if (value) { + _horizontal.OtherScrollBarView = _vertical; + base.Add (_horizontal); + _horizontal.ShowScrollIndicator = value; + _horizontal.AutoHideScrollBars = _autoHideScrollBars; + _horizontal.OtherScrollBarView.ShowScrollIndicator = value; + _horizontal.MouseEnter += View_MouseEnter; + _horizontal.MouseLeave += View_MouseLeave; + } else { + base.Remove (_horizontal); + _horizontal.OtherScrollBarView = null; + _horizontal.MouseEnter -= View_MouseEnter; + _horizontal.MouseLeave -= View_MouseLeave; } - _vertical.Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0); } + _vertical.Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0); } + } - /// - /// Removes all widgets from this container. - /// - /// - /// - public override void RemoveAll () - { - _contentView.RemoveAll (); - } - - /// - /// Gets or sets the visibility for the vertical scroll indicator. - /// - /// true if show vertical scroll indicator; otherwise, false. - public bool ShowVerticalScrollIndicator { - get => _showVerticalScrollIndicator; - set { - if (value != _showVerticalScrollIndicator) { - _showVerticalScrollIndicator = value; - SetNeedsLayout (); - if (value) { - _vertical.OtherScrollBarView = _horizontal; - base.Add (_vertical); - _vertical.ShowScrollIndicator = value; - _vertical.AutoHideScrollBars = _autoHideScrollBars; - _vertical.OtherScrollBarView.ShowScrollIndicator = value; - _vertical.MouseEnter += View_MouseEnter; - _vertical.MouseLeave += View_MouseLeave; - } else { - Remove (_vertical); - _vertical.OtherScrollBarView = null; - _vertical.MouseEnter -= View_MouseEnter; - _vertical.MouseLeave -= View_MouseLeave; - } + /// + /// Removes all widgets from this container. + /// + /// + /// + public override void RemoveAll () + { + _contentView.RemoveAll (); + } + + /// + /// Gets or sets the visibility for the vertical scroll indicator. + /// + /// true if show vertical scroll indicator; otherwise, false. + public bool ShowVerticalScrollIndicator { + get => _showVerticalScrollIndicator; + set { + if (value != _showVerticalScrollIndicator) { + _showVerticalScrollIndicator = value; + SetNeedsLayout (); + if (value) { + _vertical.OtherScrollBarView = _horizontal; + base.Add (_vertical); + _vertical.ShowScrollIndicator = value; + _vertical.AutoHideScrollBars = _autoHideScrollBars; + _vertical.OtherScrollBarView.ShowScrollIndicator = value; + _vertical.MouseEnter += View_MouseEnter; + _vertical.MouseLeave += View_MouseLeave; + } else { + Remove (_vertical); + _vertical.OtherScrollBarView = null; + _vertical.MouseEnter -= View_MouseEnter; + _vertical.MouseLeave -= View_MouseLeave; } - _horizontal.Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0); } + _horizontal.Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0); } + } - /// - public override void OnDrawContent (Rect contentArea) - { - SetViewsNeedsDisplay (); + /// + public override void OnDrawContent (Rect contentArea) + { + SetViewsNeedsDisplay (); - var savedClip = ClipToBounds (); - Driver.SetAttribute (GetNormalColor ()); - Clear (); + var savedClip = ClipToBounds (); + // TODO: It's bad practice for views to always clear a view. It negates clipping. + Clear (); - if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) { - _contentView.Draw (); - } + if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) { + _contentView.Draw (); + } - DrawScrollBars (); + DrawScrollBars (); - Driver.Clip = savedClip; - } + Driver.Clip = savedClip; + } - private void DrawScrollBars () - { - if (_autoHideScrollBars) { - ShowHideScrollBars (); - } else { - if (ShowVerticalScrollIndicator) { - _vertical.Draw (); - } - if (ShowHorizontalScrollIndicator) { - _horizontal.Draw (); - } - if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) { - SetContentBottomRightCornerVisibility (); - _contentBottomRightCorner.Draw (); - } + private void DrawScrollBars () + { + if (_autoHideScrollBars) { + ShowHideScrollBars (); + } else { + if (ShowVerticalScrollIndicator) { + _vertical.Draw (); + } + if (ShowHorizontalScrollIndicator) { + _horizontal.Draw (); + } + if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) { + SetContentBottomRightCornerVisibility (); + _contentBottomRightCorner.Draw (); } } + } - private void SetContentBottomRightCornerVisibility () - { - if (_showHorizontalScrollIndicator && _showVerticalScrollIndicator) { - _contentBottomRightCorner.Visible = true; - } else if (_horizontal.IsAdded || _vertical.IsAdded) { - _contentBottomRightCorner.Visible = false; - } + private void SetContentBottomRightCornerVisibility () + { + if (_showHorizontalScrollIndicator && _showVerticalScrollIndicator) { + _contentBottomRightCorner.Visible = true; + } else if (_horizontal.IsAdded || _vertical.IsAdded) { + _contentBottomRightCorner.Visible = false; } + } - void ShowHideScrollBars () - { - bool v = false, h = false; bool p = false; + void ShowHideScrollBars () + { + bool v = false, h = false; bool p = false; - if (Bounds.Height == 0 || Bounds.Height > _contentSize.Height) { - if (ShowVerticalScrollIndicator) { - ShowVerticalScrollIndicator = false; - } - v = false; - } else if (Bounds.Height > 0 && Bounds.Height == _contentSize.Height) { - p = true; - } else { + if (Bounds.Height == 0 || Bounds.Height > _contentSize.Height) { + if (ShowVerticalScrollIndicator) { + ShowVerticalScrollIndicator = false; + } + v = false; + } else if (Bounds.Height > 0 && Bounds.Height == _contentSize.Height) { + p = true; + } else { + if (!ShowVerticalScrollIndicator) { + ShowVerticalScrollIndicator = true; + } + v = true; + } + if (Bounds.Width == 0 || Bounds.Width > _contentSize.Width) { + if (ShowHorizontalScrollIndicator) { + ShowHorizontalScrollIndicator = false; + } + h = false; + } else if (Bounds.Width > 0 && Bounds.Width == _contentSize.Width && p) { + if (ShowHorizontalScrollIndicator) { + ShowHorizontalScrollIndicator = false; + } + h = false; + if (ShowVerticalScrollIndicator) { + ShowVerticalScrollIndicator = false; + } + v = false; + } else { + if (p) { if (!ShowVerticalScrollIndicator) { ShowVerticalScrollIndicator = true; } v = true; } - if (Bounds.Width == 0 || Bounds.Width > _contentSize.Width) { - if (ShowHorizontalScrollIndicator) { - ShowHorizontalScrollIndicator = false; - } - h = false; - } else if (Bounds.Width > 0 && Bounds.Width == _contentSize.Width && p) { - if (ShowHorizontalScrollIndicator) { - ShowHorizontalScrollIndicator = false; - } - h = false; - if (ShowVerticalScrollIndicator) { - ShowVerticalScrollIndicator = false; - } - v = false; - } else { - if (p) { - if (!ShowVerticalScrollIndicator) { - ShowVerticalScrollIndicator = true; - } - v = true; - } - if (!ShowHorizontalScrollIndicator) { - ShowHorizontalScrollIndicator = true; - } - h = true; - } - var dim = Dim.Fill (h ? 1 : 0); - if (!_vertical.Height.Equals (dim)) { - _vertical.Height = dim; - } - dim = Dim.Fill (v ? 1 : 0); - if (!_horizontal.Width.Equals (dim)) { - _horizontal.Width = dim; - } - - if (v) { - _vertical.SetRelativeLayout (Bounds); - _vertical.Draw (); - } - if (h) { - _horizontal.SetRelativeLayout (Bounds); - _horizontal.Draw (); - } - SetContentBottomRightCornerVisibility (); - if (v && h) { - _contentBottomRightCorner.SetRelativeLayout (Bounds); - _contentBottomRightCorner.Draw (); + if (!ShowHorizontalScrollIndicator) { + ShowHorizontalScrollIndicator = true; } + h = true; + } + var dim = Dim.Fill (h ? 1 : 0); + if (!_vertical.Height.Equals (dim)) { + _vertical.Height = dim; + } + dim = Dim.Fill (v ? 1 : 0); + if (!_horizontal.Width.Equals (dim)) { + _horizontal.Width = dim; } - void SetViewsNeedsDisplay () - { - foreach (View view in _contentView.Subviews) { - view.SetNeedsDisplay (); - } + if (v) { + _vertical.SetRelativeLayout (Bounds); + _vertical.Draw (); + } + if (h) { + _horizontal.SetRelativeLayout (Bounds); + _horizontal.Draw (); + } + SetContentBottomRightCornerVisibility (); + if (v && h) { + _contentBottomRightCorner.SetRelativeLayout (Bounds); + _contentBottomRightCorner.Draw (); } + } - /// - public override void PositionCursor () - { - if (InternalSubviews.Count == 0) - Move (0, 0); - else - base.PositionCursor (); - } - - /// - /// Scrolls the view up. - /// - /// true, if left was scrolled, false otherwise. - /// Number of lines to scroll. - public bool ScrollUp (int lines) - { - if (_contentOffset.Y < 0) { - ContentOffset = new Point (_contentOffset.X, Math.Min (_contentOffset.Y + lines, 0)); - return true; - } - return false; + void SetViewsNeedsDisplay () + { + foreach (View view in _contentView.Subviews) { + view.SetNeedsDisplay (); } + } - /// - /// Scrolls the view to the left - /// - /// true, if left was scrolled, false otherwise. - /// Number of columns to scroll by. - public bool ScrollLeft (int cols) - { - if (_contentOffset.X < 0) { - ContentOffset = new Point (Math.Min (_contentOffset.X + cols, 0), _contentOffset.Y); - return true; - } - return false; + /// + public override void PositionCursor () + { + if (InternalSubviews.Count == 0) + Move (0, 0); + else + base.PositionCursor (); + } + + /// + /// Scrolls the view up. + /// + /// true, if left was scrolled, false otherwise. + /// Number of lines to scroll. + public bool ScrollUp (int lines) + { + if (_contentOffset.Y < 0) { + ContentOffset = new Point (_contentOffset.X, Math.Min (_contentOffset.Y + lines, 0)); + return true; } + return false; + } - /// - /// Scrolls the view down. - /// - /// true, if left was scrolled, false otherwise. - /// Number of lines to scroll. - public bool ScrollDown (int lines) - { - if (_vertical.CanScroll (lines, out _, true)) { - ContentOffset = new Point (_contentOffset.X, _contentOffset.Y - lines); - return true; - } - return false; + /// + /// Scrolls the view to the left + /// + /// true, if left was scrolled, false otherwise. + /// Number of columns to scroll by. + public bool ScrollLeft (int cols) + { + if (_contentOffset.X < 0) { + ContentOffset = new Point (Math.Min (_contentOffset.X + cols, 0), _contentOffset.Y); + return true; } + return false; + } - /// - /// Scrolls the view to the right. - /// - /// true, if right was scrolled, false otherwise. - /// Number of columns to scroll by. - public bool ScrollRight (int cols) - { - if (_horizontal.CanScroll (cols, out _)) { - ContentOffset = new Point (_contentOffset.X - cols, _contentOffset.Y); - return true; - } - return false; + /// + /// Scrolls the view down. + /// + /// true, if left was scrolled, false otherwise. + /// Number of lines to scroll. + public bool ScrollDown (int lines) + { + if (_vertical.CanScroll (lines, out _, true)) { + ContentOffset = new Point (_contentOffset.X, _contentOffset.Y - lines); + return true; } + return false; + } - /// - public override bool ProcessKey (KeyEvent kb) - { - if (base.ProcessKey (kb)) - return true; + /// + /// Scrolls the view to the right. + /// + /// true, if right was scrolled, false otherwise. + /// Number of columns to scroll by. + public bool ScrollRight (int cols) + { + if (_horizontal.CanScroll (cols, out _)) { + ContentOffset = new Point (_contentOffset.X - cols, _contentOffset.Y); + return true; + } + return false; + } - var result = InvokeKeybindings (kb); - if (result != null) - return (bool)result; + /// + public override bool ProcessKey (KeyEvent kb) + { + if (base.ProcessKey (kb)) + return true; - return false; - } + var result = InvokeKeybindings (kb); + if (result != null) + return (bool)result; - /// - public override bool MouseEvent (MouseEvent me) - { - if (me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp && - me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft && - // me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && - !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + return false; + } - return false; - } + /// + public override bool MouseEvent (MouseEvent me) + { + if (me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp && + me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft && + // me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && + !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator) { - ScrollDown (1); - } else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator) { - ScrollUp (1); - } else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator) { - ScrollRight (1); - } else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator) { - ScrollLeft (1); - } else if (me.X == _vertical.Frame.X && ShowVerticalScrollIndicator) { - _vertical.MouseEvent (me); - } else if (me.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator) { - _horizontal.MouseEvent (me); - } else if (IsOverridden (me.View, "MouseEvent")) { - Application.UngrabMouse (); - } - return true; + return false; } - /// - protected override void Dispose (bool disposing) - { - if (!_showVerticalScrollIndicator) { - // It was not added to SuperView, so it won't get disposed automatically - _vertical?.Dispose (); - } - if (!_showHorizontalScrollIndicator) { - // It was not added to SuperView, so it won't get disposed automatically - _horizontal?.Dispose (); - } - base.Dispose (disposing); + if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator) { + ScrollDown (1); + } else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator) { + ScrollUp (1); + } else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator) { + ScrollRight (1); + } else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator) { + ScrollLeft (1); + } else if (me.X == _vertical.Frame.X && ShowVerticalScrollIndicator) { + _vertical.MouseEvent (me); + } else if (me.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator) { + _horizontal.MouseEvent (me); + } else if (IsOverridden (me.View, "MouseEvent")) { + Application.UngrabMouse (); } + return true; + } - /// - public override bool OnEnter (View view) - { - if (Subviews.Count == 0 || !Subviews.Any (subview => subview.CanFocus)) { - Application.Driver?.SetCursorVisibility (CursorVisibility.Invisible); - } + /// + protected override void Dispose (bool disposing) + { + if (!_showVerticalScrollIndicator) { + // It was not added to SuperView, so it won't get disposed automatically + _vertical?.Dispose (); + } + if (!_showHorizontalScrollIndicator) { + // It was not added to SuperView, so it won't get disposed automatically + _horizontal?.Dispose (); + } + base.Dispose (disposing); + } - return base.OnEnter (view); + /// + public override bool OnEnter (View view) + { + if (Subviews.Count == 0 || !Subviews.Any (subview => subview.CanFocus)) { + Application.Driver?.SetCursorVisibility (CursorVisibility.Invisible); } + + return base.OnEnter (view); } } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 4e4fd77325..8fb9a21569 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -121,8 +121,9 @@ public override void OnDrawContent (Rect contentArea) { Move (0, 0); Driver.SetAttribute (GetNormalColor ()); - for (int i = 0; i < Frame.Width; i++) + for (int i = 0; i < Frame.Width; i++) { Driver.AddRune ((Rune)' '); + } Move (1, 0); var scheme = GetNormalColor (); diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index b4ae68ab4c..86a558b82d 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -171,7 +171,7 @@ public void ApplyStyleChanges () // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0 tabsBar.Y = Pos.Percent (0); } - + LayoutSubviews (); SetNeedsDisplay (); } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 9907d9480f..d9f3d29f1d 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -461,7 +461,7 @@ public override void OnDrawContent (Rect contentArea) var roc = GetReadOnlyColor (); for (int idx = p; idx < tcount; idx++) { var rune = _text [idx]; - var cols = ((Rune)rune).GetColumns (); + var cols = rune.GetColumns (); if (idx == _point && HasFocus && !Used && _length == 0 && !ReadOnly) { Driver.SetAttribute (selColor); } else if (ReadOnly) { @@ -474,7 +474,7 @@ public override void OnDrawContent (Rect contentArea) Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : ColorScheme.Focus); } if (col + cols <= width) { - Driver.AddRune ((Rune)(Secret ? CM.Glyphs.Dot : rune)); + Driver.AddRune ((Secret ? CM.Glyphs.Dot : rune)); } if (!TextModel.SetCol (ref col, width, cols)) { break; @@ -567,7 +567,7 @@ void Adjust () return; int offB = OffSetBackground (); - bool need = !_needsDisplay.IsEmpty || !Used; + bool need = NeedsDisplay || !Used; if (_point < _first) { _first = _point; need = true; @@ -1233,14 +1233,11 @@ public virtual void Cut () List DeleteSelectedText () { - string actualText = Text; SetSelectedStartSelectedLength (); int selStart = SelectedStart > -1 ? _start : _point; - (var size, var _) = TextModel.DisplaySize (_text, 0, selStart, false); - (var size2, var _) = TextModel.DisplaySize (_text, selStart, selStart + _length, false); - (var size3, var _) = TextModel.DisplaySize (_text, selStart + _length, actualText.GetRuneCount (), false); - var newText = actualText [..size] + - actualText.Substring (size + size2, size3); + var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) + + StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length))); + ClearAllSelection (); _point = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart; return newText.ToRuneList (); @@ -1257,14 +1254,11 @@ public virtual void Paste () SetSelectedStartSelectedLength (); int selStart = _start == -1 ? CursorPosition : _start; - string actualText = Text; - (int size, int _) = TextModel.DisplaySize (_text, 0, selStart, false); - (var size2, var _) = TextModel.DisplaySize (_text, selStart, selStart + _length, false); - (var size3, var _) = TextModel.DisplaySize (_text, selStart + _length, actualText.GetRuneCount (), false); string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? ""; - Text = actualText [..size] + + Text = StringExtensions.ToString (_text.GetRange (0, selStart)) + cbTxt + - actualText.Substring (size + size2, size3); + StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length))); + _point = selStart + cbTxt.GetRuneCount (); ClearAllSelection (); SetNeedsDisplay (); @@ -1375,4 +1369,4 @@ protected override void SetCursorPosition (int column) ((TextField)HostControl).CursorPosition = column; } } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index c6f111c6cd..5a1f49293b 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -2407,7 +2407,11 @@ void ClearRegion (int left, int top, int right, int bottom) /// public override Attribute GetNormalColor () { - return Enabled ? ColorScheme.Focus : ColorScheme.Disabled; + ColorScheme cs = ColorScheme; + if (ColorScheme == null) { + cs = new ColorScheme (); + } + return Enabled ? cs.Focus : cs.Disabled; } /// @@ -3063,10 +3067,10 @@ public void InsertText (string toAdd) InsertText (new KeyEvent () { Key = key }); } - if (_needsDisplay.IsEmpty) { - PositionCursor (); - } else { + if (NeedsDisplay) { Adjust (); + } else { + PositionCursor (); } } @@ -3235,7 +3239,7 @@ void Adjust () { var offB = OffSetBackground (); var line = GetCurrentLine (); - bool need = !_needsDisplay.IsEmpty || _wrapNeeded || !Used; + bool need = NeedsDisplay || _wrapNeeded || !Used; var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth); var dSize = TextModel.DisplaySize (line, _leftColumn, _currentColumn, true, TabWidth); if (!_wordWrap && _currentColumn < _leftColumn) { @@ -4395,10 +4399,10 @@ public override bool OnKeyUp (KeyEvent kb) void DoNeededAction () { - if (_needsDisplay.IsEmpty) { - PositionCursor (); - } else { + if (NeedsDisplay) { Adjust (); + } else { + PositionCursor (); } } diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 01ffc6f0ac..aa660203bc 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -898,7 +898,7 @@ public override bool MouseEvent (MouseEvent mouseEvent) // End Drag Application.UngrabMouse (); - Driver.UncookMouse (); + //Driver.UncookMouse (); FinalisePosition ( dragOrignalPos, Orientation == Orientation.Horizontal ? Y : X); diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index d567f25786..159774984d 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -757,9 +757,10 @@ public override void OnDrawContent (Rect contentArea) return; } - if (!_needsDisplay.IsEmpty || _subViewNeedsDisplay || LayoutNeeded) { - Driver.SetAttribute (GetNormalColor ()); - Clear (ViewToScreen (Bounds)); + 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 (); @@ -776,9 +777,10 @@ public override void OnDrawContent (Rect contentArea) } } + // This should not be here, but in base foreach (var view in Subviews) { if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) { - view.SetNeedsLayout (); + //view.SetNeedsLayout (); view.SetNeedsDisplay (view.Bounds); view.SetSubViewNeedsDisplay (); } diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs index c28f4d7c79..1c95d66477 100644 --- a/Terminal.Gui/Views/ToplevelOverlapped.cs +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -97,7 +97,7 @@ static bool OverlappedChildNeedsDisplay () } foreach (var top in _toplevels) { - if (top != Current && top.Visible && (!top._needsDisplay.IsEmpty || top._subViewNeedsDisplay || top.LayoutNeeded)) { + if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) { OverlappedTop.SetSubViewNeedsDisplay (); return true; } diff --git a/Terminal.sln b/Terminal.sln index cd242ab364..b0837d7501 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -14,15 +14,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E143FB1F-0B88-48CB-9086-72CDCECFCD22}" ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig .gitignore = .gitignore + .github\CODEOWNERS = .github\CODEOWNERS CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md CONTRIBUTING.md = CONTRIBUTING.md .github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml - .github\GitVersion.yml = .github\GitVersion.yml .github\workflows\publish.yml = .github\workflows\publish.yml README.md = README.md - Release.ps1 = Release.ps1 testenvironments.json = testenvironments.json EndProjectSection EndProject diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index 4fe02b923b..00e9d9e71f 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -50,6 +50,9 @@ "Windows & FrameViews": { "commandName": "Project", "commandLineArgs": "\"Windows & FrameViews\"" + }, + "Profile 1": { + "commandName": "Executable" } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs index a8fe04e4c2..9284659f74 100644 --- a/UICatalog/Scenarios/BasicColors.cs +++ b/UICatalog/Scenarios/BasicColors.cs @@ -87,8 +87,8 @@ public override void Setup () Application.RootMouseEvent = (e) => { if (e.View != null) { - var colorValue = e.View.GetNormalColor ().Value; - Application.Driver.GetColors (colorValue, out Color fore, out Color back); + var fore = e.View.GetNormalColor ().Foreground; + var back = e.View.GetNormalColor ().Background; lblForeground.Text = fore.ToString (); viewForeground.ColorScheme.Normal = new Attribute (fore, fore); lblBackground.Text = back.ToString (); diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index ca1d792342..7c220312d2 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Data; using System.Globalization; using System.Linq; using System.Net.Http; @@ -11,6 +12,7 @@ using System.Text.Unicode; using System.Threading.Tasks; using Terminal.Gui; +using static Terminal.Gui.SpinnerStyle; using static Terminal.Gui.TableView; namespace UICatalog.Scenarios; @@ -27,24 +29,31 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("ScrollView")] public class CharacterMap : Scenario { CharMap _charMap; - Label _errorLabel; + public Label _errorLabel; TableView _categoryList; + // Don't create a Window, just return the top-level view + public override void Init () + { + Application.Init (); + Application.Top.ColorScheme = Colors.Base; + } + public override void Setup () { _charMap = new CharMap () { X = 0, - Y = 0, + Y = 1, Height = Dim.Fill () }; - Win.Add (_charMap); + Application.Top.Add (_charMap); var jumpLabel = new Label ("Jump To Code Point:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) }; - Win.Add (jumpLabel); + Application.Top.Add (jumpLabel); var jumpEdit = new TextField () { X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3" }; - Win.Add (jumpEdit); - _errorLabel = new Label ("") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] }; - Win.Add (_errorLabel); + Application.Top.Add (jumpEdit); + _errorLabel = new Label ("err") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] }; + Application.Top.Add (_errorLabel); jumpEdit.TextChanged += JumpEdit_TextChanged; @@ -96,15 +105,42 @@ public override void Setup () _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start; }; - Win.Add (_categoryList); + Application.Top.Add (_categoryList); _charMap.SelectedCodePoint = 0; //jumpList.Refresh (); _charMap.SetFocus (); _charMap.Width = Dim.Fill () - _categoryList.Width; + + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Quit", $"{Application.QuitKey}", () => Application.RequestStop ()), + }), + new MenuBarItem ("_Options", new MenuItem [] { + CreateMenuShowWidth (), + }) + }); + Application.Top.Add (menu); + + //_charMap.Hover += (s, a) => { + // _errorLabel.Text = $"U+{a.Item:x5} {(Rune)a.Item}"; + //}; } + MenuItem CreateMenuShowWidth () + { + var item = new MenuItem { + Title = "_Show Glyph Width", + }; + item.CheckType |= MenuItemCheckStyle.Checked; + item.Checked = _charMap?.ShowGlyphWidths; + item.Action += () => { + _charMap.ShowGlyphWidths = (bool)(item.Checked = !item.Checked); + }; + + return item; + } EnumerableTableSource CreateCategoryTable (int sortByColumn, bool descending) { @@ -177,7 +213,7 @@ private void JumpEdit_TextChanged (object sender, TextChangedEventArgs e) _errorLabel.Text = $"Beyond maximum codepoint"; return; } - _errorLabel.Text = $"U+{result:x4}"; + _errorLabel.Text = $"U+{result:x5}"; var table = (EnumerableTableSource)_categoryList.Table; _categoryList.SelectedRow = table.Data @@ -200,8 +236,7 @@ public int StartCodePoint { get => _start; set { _start = value; - _selected = value; - ContentOffset = new Point (0, (int)(_start / 16)); + SelectedCodePoint = value; SetNeedsDisplay (); } } @@ -216,15 +251,16 @@ public int SelectedCodePoint { get => _selected; set { _selected = value; - var col = Cursor.X; - var row = Cursor.Y; - var height = (Bounds.Height / ROW_HEIGHT) - (ShowHorizontalScrollIndicator ? 2 : 1); + var row = (SelectedCodePoint / 16 * _rowHeight); + var col = (SelectedCodePoint % 16 * COLUMN_WIDTH); + + var height = (Bounds.Height) - (ShowHorizontalScrollIndicator ? 2 : 1); if (row + ContentOffset.Y < 0) { // Moving up. ContentOffset = new Point (ContentOffset.X, row); } else if (row + ContentOffset.Y >= height) { // Moving down. - ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + ROW_HEIGHT)); + ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight)); } var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth); if (col + ContentOffset.X < 0) { @@ -239,10 +275,15 @@ public int SelectedCodePoint { } } + public event EventHandler Hover; + + /// + /// Gets the coordinates of the Cursor based on the SelectedCodePoint in screen coordinates + /// public Point Cursor { get { - var row = SelectedCodePoint / 16; - var col = (SelectedCodePoint - row * 16) * COLUMN_WIDTH; + var row = (SelectedCodePoint / 16 * _rowHeight) + ContentOffset.Y + 1; + var col = (SelectedCodePoint % 16 * COLUMN_WIDTH) + ContentOffset.X + RowLabelWidth + 1; // + 1 for padding return new Point (col, row); } set => throw new NotImplementedException (); @@ -250,35 +291,42 @@ public Point Cursor { public override void PositionCursor () { - if (HasFocus && Cursor.X + ContentOffset.X + RowLabelWidth + 1 >= RowLabelWidth && - Cursor.X + ContentOffset.X + RowLabelWidth + 1 < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) && - Cursor.Y + ContentOffset.Y + 1 > 0 && - Cursor.Y + ContentOffset.Y + 1 < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) { - + if (HasFocus && + Cursor.X >= RowLabelWidth && + Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) && + Cursor.Y > 0 && + Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) { Driver.SetCursorVisibility (CursorVisibility.Default); - Move (Cursor.X + ContentOffset.X + RowLabelWidth + 1, Cursor.Y + ContentOffset.Y + 1); + Move (Cursor.X, Cursor.Y); } else { Driver.SetCursorVisibility (CursorVisibility.Invisible); } } + public bool ShowGlyphWidths { + get => _rowHeight == 2; + set { + _rowHeight = value ? 2 : 1; + SetNeedsDisplay (); + } + } int _start = 0; int _selected = 0; - public const int COLUMN_WIDTH = 3; - public const int ROW_HEIGHT = 1; + const int COLUMN_WIDTH = 3; + int _rowHeight = 1; public static int MaxCodePoint => 0x10FFFF; - public static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1; - public static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16); + static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1; + static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16); public CharMap () { ColorScheme = Colors.Dialog; CanFocus = true; - ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1))); + ContentSize = new Size (CharMap.RowWidth, (int)((MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight)); AddCommand (Command.ScrollUp, () => { if (SelectedCodePoint >= 16) { @@ -305,12 +353,12 @@ public CharMap () return true; }); AddCommand (Command.PageUp, () => { - var page = (Bounds.Height / ROW_HEIGHT - 1) * 16; + var page = (Bounds.Height / _rowHeight - 1) * 16; SelectedCodePoint -= Math.Min (page, SelectedCodePoint); return true; }); AddCommand (Command.PageDown, () => { - var page = (Bounds.Height / ROW_HEIGHT - 1) * 16; + var page = (Bounds.Height / _rowHeight - 1) * 16; SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint); return true; }); @@ -336,26 +384,34 @@ public CharMap () public override void OnDrawContent (Rect contentArea) { - if (ShowHorizontalScrollIndicator && ContentSize.Height < (int)(MaxCodePoint / 16 + 2)) { - ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 2)); - var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth); - if (Cursor.X + ContentOffset.X >= width) { - // Snap to the selected glyph. - ContentOffset = new Point (Math.Min (Cursor.X, Cursor.X - width + COLUMN_WIDTH), ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y); - } else { - ContentOffset = new Point (ContentOffset.X - Cursor.X, ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y); - } - } else if (!ShowHorizontalScrollIndicator && ContentSize.Height > (int)(MaxCodePoint / 16 + 1)) { - ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 1)); - // Snap 1st column into view if it's been scrolled horizontally - ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y); - } + //if (ShowHorizontalScrollIndicator && ContentSize.Height < (int)(MaxCodePoint / 16 + 2)) { + // //ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 2)); + // //ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16) * _rowHeight + 2); + // var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth); + // if (Cursor.X + ContentOffset.X >= width) { + // // Snap to the selected glyph. + // ContentOffset = new Point ( + // Math.Min (Cursor.X, Cursor.X - width + COLUMN_WIDTH), + // ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y); + // } else { + // ContentOffset = new Point ( + // ContentOffset.X - Cursor.X, + // ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y); + // } + //} else if (!ShowHorizontalScrollIndicator && ContentSize.Height > (int)(MaxCodePoint / 16 + 1)) { + // //ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 1)); + // // Snap 1st column into view if it's been scrolled horizontally + // ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y); + //} base.OnDrawContent (contentArea); } //public void CharMap_DrawContent (object s, DrawEventArgs a) public override void OnDrawContentComplete (Rect contentArea) { + if (contentArea.Height == 0 || contentArea.Width == 0) { + return; + } Rect viewport = new Rect (ContentOffset, new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0), Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0))); @@ -370,8 +426,8 @@ public override void OnDrawContentComplete (Rect contentArea) Driver.Clip = new Rect (Driver.Clip.Location, new Size (Driver.Clip.Width - 1, Driver.Clip.Height)); } - var cursorCol = Cursor.X; - var cursorRow = Cursor.Y; + var cursorCol = Cursor.X - ContentOffset.X - RowLabelWidth - 1; + var cursorRow = Cursor.Y - ContentOffset.Y - 1; Driver.SetAttribute (GetHotNormalColor ()); Move (0, 0); @@ -382,7 +438,7 @@ public override void OnDrawContentComplete (Rect contentArea) Move (x, 0); Driver.SetAttribute (GetHotNormalColor ()); Driver.AddStr (" "); - Driver.SetAttribute (HasFocus && (cursorCol + RowLabelWidth == x) ? ColorScheme.HotFocus : GetHotNormalColor ()); + Driver.SetAttribute (HasFocus && (cursorCol + ContentOffset.X + RowLabelWidth == x) ? ColorScheme.HotFocus : GetHotNormalColor ()); Driver.AddStr ($"{hexDigit:x}"); Driver.SetAttribute (GetHotNormalColor ()); Driver.AddStr (" "); @@ -390,7 +446,10 @@ public override void OnDrawContentComplete (Rect contentArea) } var firstColumnX = viewport.X + RowLabelWidth; - for (int row = -ContentOffset.Y, y = 1; row <= (-ContentOffset.Y) + (Bounds.Height / ROW_HEIGHT); row++, y += ROW_HEIGHT) { + for (int y = 1; y < Bounds.Height; y++) { + // What row is this? + var row = (y - ContentOffset.Y - 1) / _rowHeight; + var val = (row) * 16; if (val > MaxCodePoint) { continue; @@ -398,16 +457,26 @@ public override void OnDrawContentComplete (Rect contentArea) Move (firstColumnX + COLUMN_WIDTH, y); Driver.SetAttribute (GetNormalColor ()); for (int col = 0; col < 16; col++) { + var x = firstColumnX + COLUMN_WIDTH * col + 1; + Move (x, y); if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) { Driver.SetAttribute (GetFocusColor ()); } + var scalar = val + col; + Rune rune = (Rune)'?'; + if (Rune.IsValid (scalar)) { + rune = new Rune (scalar); + } + var width = rune.GetColumns (); - if (char.IsSurrogate ((char)(val + col))) { - Driver.AddRune (Rune.ReplacementChar); + // are we at first row of the row? + if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) { + Driver.AddRune (rune); } else { - Driver.AddRune (new Rune (val + col)); + Driver.SetAttribute (ColorScheme.HotNormal); + Driver.AddStr ($"{width}"); } if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) { @@ -416,8 +485,11 @@ public override void OnDrawContentComplete (Rect contentArea) } Move (0, y); Driver.SetAttribute (HasFocus && (cursorRow + ContentOffset.Y + 1 == y) ? ColorScheme.HotFocus : ColorScheme.HotNormal); - var rowLabel = $"U+{val / 16:x5}_ "; - Driver.AddStr (rowLabel); + if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) { + Driver.AddStr ($"U+{val / 16:x5}_ "); + } else { + Driver.AddStr (new string (' ', RowLabelWidth)); + } } Driver.Clip = oldClip; } @@ -426,30 +498,38 @@ public override void OnDrawContentComplete (Rect contentArea) void Handle_MouseClick (object sender, MouseEventEventArgs args) { var me = args.MouseEvent; - if (me.Flags == MouseFlags.ReportMousePosition || (me.Flags != MouseFlags.Button1Clicked && - me.Flags != MouseFlags.Button1DoubleClicked)) { // && me.Flags != _contextMenu.MouseFlags)) { + if (me.Flags != MouseFlags.ReportMousePosition && me.Flags != MouseFlags.Button1Clicked && + me.Flags != MouseFlags.Button1DoubleClicked) { return; } - if (me.X < RowLabelWidth) { - return; + if (me.Y == 0) { + me.Y = Cursor.Y; } - if (me.Y < 1) { - return; + if (me.Y > 0) { + } + + if (me.X < RowLabelWidth || me.X > RowLabelWidth + (16 * COLUMN_WIDTH) - 1) { + me.X = Cursor.X; } - var row = me.Y - 1; + var row = (me.Y - 1 - ContentOffset.Y) / _rowHeight; // -1 for header var col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH; - if (row < 0 || row > Bounds.Height || col < 0 || col > 15) { - return; + + if (col > 15) { + col = 15; } - var val = (row - ContentOffset.Y) * 16 + col; + var val = (row) * 16 + col; if (val > MaxCodePoint) { return; } + if (me.Flags == MouseFlags.ReportMousePosition) { + Hover?.Invoke (this, new ListViewItemEventArgs (val, null)); + } + if (me.Flags == MouseFlags.Button1Clicked) { SelectedCodePoint = val; return; @@ -535,6 +615,7 @@ void ShowDetails () }; Application.Run (waitIndicator); + if (!string.IsNullOrEmpty (decResponse)) { string name = string.Empty; @@ -551,25 +632,156 @@ void ShowDetails () //&& property3Element.TryGetProperty ("nestedProperty", out JsonElement nestedPropertyElement)) { // Console.WriteLine (nestedPropertyElement.GetString ()); //} + decResponse = JsonSerializer.Serialize (document.RootElement, new + JsonSerializerOptions { + WriteIndented = true + }); } - var title = $"{ToCamelCase (name)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x4}"; - switch (MessageBox.Query (title, decResponse, "Copy _Glyph", "Copy Code _Point", "Cancel")) { - case 0: + var title = $"{ToCamelCase (name)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}"; + + var copyGlyph = new Button ("Copy _Glyph"); + var copyCP = new Button ("Copy Code _Point"); + var cancel = new Button ("Cancel"); + + var dlg = new Dialog (copyGlyph, copyCP, cancel) { + Title = title + }; + + copyGlyph.Clicked += (s, a) => { CopyGlyph (); - break; - case 1: + dlg.RequestStop (); + }; + copyCP.Clicked += (s, a) => { CopyCodePoint (); - break; - } + dlg.RequestStop (); + }; + cancel.Clicked += (s, a) => dlg.RequestStop (); + + var rune = (Rune)SelectedCodePoint; + var label = new Label () { + Text = "IsAscii: ", + X = 0, + Y = 0 + }; + dlg.Add (label); + + label = new Label () { + Text = $"{rune.IsAscii}", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = ", Bmp: ", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = $"{rune.IsBmp}", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = ", CombiningMark: ", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = $"{rune.IsCombiningMark ()}", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = ", SurrogatePair: ", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = $"{rune.IsSurrogatePair ()}", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = ", Plane: ", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = $"{rune.Plane}", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = "Columns: ", + X = 0, + Y = Pos.Bottom (label) + }; + dlg.Add (label); + + label = new Label () { + Text = $"{rune.GetColumns ()}", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = ", Utf16SequenceLength: ", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + + label = new Label () { + Text = $"{rune.Utf16SequenceLength}", + X = Pos.Right (label), + Y = Pos.Top (label) + }; + dlg.Add (label); + label = new Label () { + Text = $"Code Point Information from {UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint}:", + X = 0, + Y = Pos.Bottom (label) + }; + dlg.Add (label); + + var json = new TextView () { + X = 0, + Y = Pos.Bottom (label), + Width = Dim.Fill (), + Height = Dim.Fill (2), + ReadOnly = true, + Text = decResponse + }; + dlg.Add (json); + + Application.Run (dlg); + } else { - MessageBox.ErrorQuery ("Code Point API", $"{UcdApiClient.BaseUrl} did not return a result.", "Ok"); + MessageBox.ErrorQuery ("Code Point API", $"{UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint} did not return a result for\r\n {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}.", "Ok"); } // BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug Application.GrabMouse (this); } - public override bool OnEnter (View view) { if (IsInitialized) { @@ -577,6 +789,12 @@ public override bool OnEnter (View view) } return base.OnEnter (view); } + + public override bool OnLeave (View view) + { + Driver.SetCursorVisibility (CursorVisibility.Invisible); + return base.OnLeave (view); + } } public class UcdApiClient { @@ -637,7 +855,6 @@ public static List GetRanges () new UnicodeRange (0x1F130, 0x1F149 ,"Squared Latin Capital Letters"), new UnicodeRange (0x12400, 0x1240f ,"Cuneiform Numbers and Punctuation"), - new UnicodeRange (0x1FA00, 0x1FA0f ,"Chess Symbols"), new UnicodeRange (0x10000, 0x1007F ,"Linear B Syllabary"), new UnicodeRange (0x10080, 0x100FF ,"Linear B Ideograms"), new UnicodeRange (0x10100, 0x1013F ,"Aegean Numbers"), diff --git a/UICatalog/Scenarios/Generic.cs b/UICatalog/Scenarios/Generic.cs index f2719bccb5..9d4ce4e32b 100644 --- a/UICatalog/Scenarios/Generic.cs +++ b/UICatalog/Scenarios/Generic.cs @@ -12,12 +12,12 @@ public override void Init () // that reads "Press to Quit". Access this Window with `this.Win`. // - Sets the Theme & the ColorScheme property of `this.Win` to `colorScheme`. // To override this, implement an override of `Init`. - + //base.Init (); - + // A common, alternate, implementation where `this.Win` is not used is below. This code // leverages ConfigurationManager to borrow the color scheme settings from UICatalog: - + Application.Init (); ConfigurationManager.Themes.Theme = Theme; ConfigurationManager.Apply (); @@ -37,7 +37,6 @@ public override void Setup () Y = Pos.Center (), }; Application.Top.Add (button); - } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/Images.cs b/UICatalog/Scenarios/Images.cs new file mode 100644 index 0000000000..ea74bea58c --- /dev/null +++ b/UICatalog/Scenarios/Images.cs @@ -0,0 +1,136 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System; +using System.Collections.Concurrent; +using System.IO; +using Terminal.Gui; +using Attribute = Terminal.Gui.Attribute; + + + +namespace UICatalog.Scenarios { + [ScenarioMetadata (Name: "Images", Description: "Demonstration of how to render an image with/without true color support.")] + [ScenarioCategory ("Colors")] + public class Images : Scenario { + public override void Setup () + { + base.Setup (); + + var x = 0; + var y = 0; + + var canTrueColor = Application.Driver.SupportsTrueColor; + + var lblDriverName = new Label ($"Current driver is {Application.Driver.GetType ().Name}") { + X = x, + Y = y++ + }; + Win.Add (lblDriverName); + y++; + + var cbSupportsTrueColor = new CheckBox ("Driver supports true color ") { + X = x, + Y = y++, + Checked = canTrueColor, + CanFocus = false + }; + Win.Add (cbSupportsTrueColor); + + var cbUseTrueColor = new CheckBox ("Use true color") { + X = x, + Y = y++, + Checked = Application.Driver.UseTrueColor, + Enabled = canTrueColor, + }; + cbUseTrueColor.Toggled += (_, evt) => Application.Driver.UseTrueColor = evt.NewValue ?? false; + Win.Add (cbUseTrueColor); + + var btnOpenImage = new Button ("Open Image") { + X = x, + Y = y++ + }; + Win.Add (btnOpenImage); + + var imageView = new ImageView () { + X = x, + Y = y++, + Width = Dim.Fill (), + Height = Dim.Fill (), + }; + Win.Add (imageView); + + + btnOpenImage.Clicked += (_, _) => { + var ofd = new OpenDialog ("Open Image") { AllowsMultipleSelection = false }; + Application.Run (ofd); + + if (ofd.Canceled) + return; + + var path = ofd.FilePaths [0]; + + if (string.IsNullOrWhiteSpace (path)) { + return; + } + + if (!File.Exists (path)) { + return; + } + + Image img; + + try { + img = Image.Load (File.ReadAllBytes (path)); + } catch (Exception ex) { + + MessageBox.ErrorQuery ("Could not open file", ex.Message, "Ok"); + return; + } + + imageView.SetImage (img); + }; + } + + class ImageView : View { + + private Image fullResImage; + private Image matchSize; + + ConcurrentDictionary cache = new ConcurrentDictionary (); + + internal void SetImage (Image image) + { + fullResImage = image; + this.SetNeedsDisplay (); + } + + public override void OnDrawContent(Rect bounds) + { + base.OnDrawContent (bounds); + + if (fullResImage == null) { + return; + } + + // if we have not got a cached resized image of this size + if (matchSize == null || bounds.Width != matchSize.Width || bounds.Height != matchSize.Height) { + + // generate one + matchSize = fullResImage.Clone (x => x.Resize (bounds.Width, bounds.Height)); + } + + for (int y = 0; y < bounds.Height; y++) { + for (int x = 0; x < bounds.Width; x++) { + var rgb = matchSize [x, y]; + + var attr = cache.GetOrAdd (rgb, (rgb) => new Attribute (new TrueColor (), new TrueColor (rgb.R, rgb.G, rgb.B))); + + Driver.SetAttribute (attr); + AddRune (x, y, (System.Text.Rune)' '); + } + } + } + } + } +} diff --git a/UICatalog/Scenarios/LineDrawing.cs b/UICatalog/Scenarios/LineDrawing.cs index 9946a8c084..8682dce2e2 100644 --- a/UICatalog/Scenarios/LineDrawing.cs +++ b/UICatalog/Scenarios/LineDrawing.cs @@ -142,8 +142,9 @@ public override void OnDrawContentComplete (Rect contentArea) foreach (var canvas in _layers) { foreach (var c in canvas.GetCellMap ()) { - Driver.SetAttribute (c.Value.Attribute?.Value ?? ColorScheme.Normal); - this.AddRune (c.Key.X, c.Key.Y, c.Value.Rune.Value); + Driver.SetAttribute (c.Value.Attribute ?? ColorScheme.Normal); + // TODO: #2616 - Support combining sequences that don't normalize + this.AddRune (c.Key.X, c.Key.Y, c.Value.Runes [0]); } } } diff --git a/UICatalog/Scenarios/ProgressBarStyles.cs b/UICatalog/Scenarios/ProgressBarStyles.cs index 9ff533fa83..022d0adba5 100644 --- a/UICatalog/Scenarios/ProgressBarStyles.cs +++ b/UICatalog/Scenarios/ProgressBarStyles.cs @@ -79,7 +79,7 @@ public override void Setup () _fractionTimer = null; button.Enabled = true; } - Application.MainLoop.Driver.Wakeup (); + Application.MainLoop.MainLoopDriver.Wakeup (); }, null, 0, _timerTick); } }; @@ -128,7 +128,7 @@ public override void Setup () marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString (); marqueesBlocksPB.Pulse (); marqueesContinuousPB.Pulse (); - Application.MainLoop.Driver.Wakeup (); + Application.MainLoop.MainLoopDriver.Wakeup (); }, null, 0, 300); Application.Top.Unloaded += Top_Unloaded; diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index b392a72f73..d172d59b7a 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -786,7 +786,6 @@ public UnicodeRange (uint start, uint end, string category) new UnicodeRange(0x2b00, 0x2bff,"Miscellaneous Symbols and Arrows"), new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"), new UnicodeRange(0x12400, 0x1240f,"Cuneiform Numbers and Punctuation"), - new UnicodeRange(0x1FA00, 0x1FA0f,"Chess Symbols"), new UnicodeRange((uint)(CharMap.MaxCodePoint - 16), (uint)CharMap.MaxCodePoint,"End"), new UnicodeRange (0x0020 ,0x007F ,"Basic Latin"), diff --git a/UICatalog/Scenarios/TrueColors.cs b/UICatalog/Scenarios/TrueColors.cs new file mode 100644 index 0000000000..e4c7df5005 --- /dev/null +++ b/UICatalog/Scenarios/TrueColors.cs @@ -0,0 +1,116 @@ +using System; +using Terminal.Gui; + +namespace UICatalog.Scenarios { + + [ScenarioMetadata (Name: "True Colors", Description: "Demonstration of true color support.")] + [ScenarioCategory ("Colors")] + public class TrueColors : Scenario { + + public override void Setup () + { + var x = 2; + var y = 1; + + var canTrueColor = Application.Driver.SupportsTrueColor; + + var lblDriverName = new Label ($"Current driver is {Application.Driver.GetType ().Name}") { + X = x, + Y = y++ + }; + Win.Add (lblDriverName); + y++; + + var cbSupportsTrueColor = new CheckBox ("Driver supports true color ") { + X = x, + Y = y++, + Checked = canTrueColor, + CanFocus = false + }; + Win.Add (cbSupportsTrueColor); + + var cbUseTrueColor = new CheckBox ("Use true color") { + X = x, + Y = y++, + Checked = Application.Driver.UseTrueColor, + Enabled = canTrueColor, + }; + cbUseTrueColor.Toggled += (_, evt) => Application.Driver.UseTrueColor = evt.NewValue ?? false; + Win.Add (cbUseTrueColor); + + y += 2; + SetupGradient ("Red gradient", x, ref y, (i) => new TrueColor (i, 0, 0)); + SetupGradient ("Green gradient", x, ref y, (i) => new TrueColor (0, i, 0)); + SetupGradient ("Blue gradient", x, ref y, (i) => new TrueColor (0, 0, i)); + SetupGradient ("Yellow gradient", x, ref y, (i) => new TrueColor (i, i, 0)); + SetupGradient ("Magenta gradient", x, ref y, (i) => new TrueColor (i, 0, i)); + SetupGradient ("Cyan gradient", x, ref y, (i) => new TrueColor (0, i, i)); + SetupGradient ("Gray gradient", x, ref y, (i) => new TrueColor (i, i, i)); + + Win.Add (new Label ("Mouse over to get the gradient view color:") { + X = Pos.AnchorEnd (44), + Y = 2 + }); + Win.Add (new Label ("Red:") { + X = Pos.AnchorEnd (44), + Y = 4 + }); + Win.Add (new Label ("Green:") { + X = Pos.AnchorEnd (44), + Y = 5 + }); + Win.Add (new Label ("Blue:") { + X = Pos.AnchorEnd (44), + Y = 6 + }); + + var lblRed = new Label ("na") { + X = Pos.AnchorEnd (32), + Y = 4 + }; + Win.Add (lblRed); + var lblGreen = new Label ("na") { + X = Pos.AnchorEnd (32), + Y = 5 + }; + Win.Add (lblGreen); + var lblBlue = new Label ("na") { + X = Pos.AnchorEnd (32), + Y = 6 + }; + Win.Add (lblBlue); + + Application.RootMouseEvent = (e) => { + var normal = e.View.GetNormalColor (); + if (e.View != null) { + lblRed.Text = normal.TrueColorForeground.Value.Red.ToString (); + lblGreen.Text = normal.TrueColorForeground.Value.Green.ToString (); + lblBlue.Text = normal.TrueColorForeground.Value.Blue.ToString (); + } + }; + } + + private void SetupGradient (string name, int x, ref int y, Func colorFunc) + { + var gradient = new Label (name) { + X = x, + Y = y++, + }; + Win.Add (gradient); + for (int dx = x, i = 0; i <= 256; i += 4) { + var l = new Label (" ") { + X = dx++, + Y = y, + ColorScheme = new ColorScheme () { + Normal = new Terminal.Gui.Attribute ( + colorFunc (i > 255 ? 255 : i), + colorFunc (i > 255 ? 255 : i) + ) + } + }; + Win.Add (l); + } + y += 2; + } + } +} diff --git a/UICatalog/Scenarios/Unicode.cs b/UICatalog/Scenarios/Unicode.cs index 866679a615..e5e53095c8 100644 --- a/UICatalog/Scenarios/Unicode.cs +++ b/UICatalog/Scenarios/Unicode.cs @@ -9,16 +9,9 @@ namespace UICatalog.Scenarios { public class UnicodeInMenu : Scenario { public override void Setup () { - const string IdenticalSign = "\u2261"; - const string ArrowUpSign = "\u2191"; - const string ArrowDownSign = "\u2193"; - const string EllipsesSign = "\u2026"; - const string StashSign = "\u205E"; - - //string text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line."; string unicode = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ\nτὸ σπίτι φτωχικὸ στὶς ἀμμουδιὲς τοῦ Ὁμήρου.\nΜονάχη ἔγνοια ἡ γλῶσσα μου στὶς ἀμμουδιὲς τοῦ Ὁμήρου."; - string gitString = $"gui.cs master {IdenticalSign} {ArrowDownSign}18 {ArrowUpSign}10 {StashSign}1 {EllipsesSign}"; + string gitString = $"gui.cs 糊 (hú) {ConfigurationManager.Glyphs.IdenticalTo} {ConfigurationManager.Glyphs.DownArrow}18 {ConfigurationManager.Glyphs.UpArrow}10 {ConfigurationManager.Glyphs.VerticalFourDots}1 {ConfigurationManager.Glyphs.HorizontalEllipsis}"; var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_Файл", new MenuItem [] { @@ -30,7 +23,7 @@ public override void Setup () new MenuBarItem ("_Edit", new MenuItem [] { new MenuItem ("_Copy", "", null), new MenuItem ("C_ut", "", null), - new MenuItem ("_Paste", "", null) + new MenuItem ("_糊", "hú (Paste)", null) }) }); Application.Top.Add (menu); @@ -60,11 +53,10 @@ public override void Setup () label = new Label ("CheckBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; Win.Add (label); var checkBox = new CheckBox (gitString) { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) }; - var ckbAllowNull = new CheckBox ("Allow null checked") { X = Pos.Right (checkBox) + 1, Y = Pos.Y (label) }; - ckbAllowNull.Toggled += (s,e) => checkBox.AllowNullChecked = (bool)!e.OldValue; - Win.Add (checkBox, ckbAllowNull); + var checkBoxRight = new CheckBox ($"Align Right - {gitString}") { X = 20, Y = Pos.Bottom (checkBox), Width = Dim.Percent (50), TextAlignment = TextAlignment.Right}; + Win.Add (checkBox, checkBoxRight); - label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; + label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1 }; Win.Add (label); var comboBox = new ComboBox () { X = 20, diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 7e4bf420dc..141015d398 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -214,8 +214,6 @@ static Scenario RunUICatalogTopLevel () CM.Apply (); } - //Application.EnableConsoleScrolling = _enableConsoleScrolling; - Application.Run (); Application.Shutdown (); @@ -239,7 +237,6 @@ static Scenario RunUICatalogTopLevel () static bool _useSystemConsole = false; static ConsoleDriver.DiagnosticFlags _diagnosticFlags; - //static bool _enableConsoleScrolling = false; static bool _isFirstRunning = true; static string _topLevelColorScheme = string.Empty; @@ -254,7 +251,6 @@ public class UICatalogTopLevel : Toplevel { public MenuItem? miUseSubMenusSingleFrame; public MenuItem? miIsMenuBorderDisabled; public MenuItem? miIsMouseDisabled; - public MenuItem? miEnableConsoleScrolling; public ListView CategoryList; @@ -383,8 +379,8 @@ public UICatalogTopLevel () ScenarioList.KeyDown += (s, a) => { if (CollectionNavigator.IsCompatibleKey (a.KeyEvent)) { var newItem = _scenarioCollectionNav?.GetNextMatchingItem (ScenarioList.SelectedRow, (char)a.KeyEvent.KeyValue); - if (newItem is int && newItem != -1) { - ScenarioList.SelectedRow = (int)newItem; + if (newItem is int v && newItem != -1) { + ScenarioList.SelectedRow = v; ScenarioList.EnsureSelectedCellIsVisible (); ScenarioList.SetNeedsDisplay (); a.Handled = true; @@ -426,8 +422,7 @@ void LoadedHandler (object? sender, EventArgs? args) ConfigChanged (); miIsMouseDisabled!.Checked = Application.IsMouseDisabled; - miEnableConsoleScrolling!.Checked = Application.EnableConsoleScrolling; - DriverName.Title = $"Driver: {Driver.GetType ().Name}"; + DriverName.Title = $"Driver: {Driver.GetVersionInfo()}"; OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}"; if (_selectedScenario != null) { @@ -489,8 +484,7 @@ List CreateDiagnosticMenuItems () { List menuItems = new List { CreateDiagnosticFlagsMenuItems (), - new MenuItem [] { }, - CreateEnableConsoleScrollingMenuItems (), + Array.Empty (), CreateDisabledEnabledMouseItems (), CreateDisabledEnabledMenuBorder (), CreateDisabledEnableUseSubMenusSingleFrame (), @@ -567,22 +561,6 @@ MenuItem [] CreateKeybindingsMenuItems () return menuItems.ToArray (); } - MenuItem [] CreateEnableConsoleScrollingMenuItems () - { - List menuItems = new List (); - miEnableConsoleScrolling = new MenuItem (); - miEnableConsoleScrolling.Title = "_Enable Console Scrolling"; - miEnableConsoleScrolling.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miEnableConsoleScrolling.Title!.Substring (1, 1) [0]; - miEnableConsoleScrolling.CheckType |= MenuItemCheckStyle.Checked; - miEnableConsoleScrolling.Action += () => { - miEnableConsoleScrolling.Checked = !miEnableConsoleScrolling.Checked; - Application.EnableConsoleScrolling = (bool)miEnableConsoleScrolling.Checked!; - }; - menuItems.Add (miEnableConsoleScrolling); - - return menuItems.ToArray (); - } - MenuItem [] CreateDiagnosticFlagsMenuItems () { const string OFF = "Diagnostics: _Off"; @@ -759,7 +737,6 @@ public void ConfigChanged () StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit"; miIsMouseDisabled!.Checked = Application.IsMouseDisabled; - miEnableConsoleScrolling!.Checked = Application.EnableConsoleScrolling; var height = (UICatalogApp.ShowStatusBar ? 1 : 0);// + (MenuBar.Visible ? 1 : 0); //ContentPane.Height = Dim.Fill (height); diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index e44534723e..8ece3526fe 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -24,7 +24,6 @@ void Pre_Init_State () Assert.Null (Application.Driver); Assert.Null (Application.Top); Assert.Null (Application.Current); - Assert.False (Application.EnableConsoleScrolling); Assert.Null (Application.MainLoop); Assert.Null (Application.Iteration); Assert.Null (Application.RootMouseEvent); @@ -36,11 +35,14 @@ void Post_Init_State () Assert.NotNull (Application.Driver); Assert.NotNull (Application.Top); Assert.NotNull (Application.Current); - Assert.False (Application.EnableConsoleScrolling); Assert.NotNull (Application.MainLoop); Assert.Null (Application.Iteration); Assert.Null (Application.RootMouseEvent); Assert.Null (Application.TerminalResized); + // FakeDriver is always 80x25 + Assert.Equal (80, Application.Driver.Cols); + Assert.Equal (25, Application.Driver.Rows); + } void Init () @@ -60,28 +62,24 @@ void Shutdown () public void Init_Shutdown_Cleans_Up () { // Verify initial state is per spec - Pre_Init_State (); + //Pre_Init_State (); Application.Init (new FakeDriver ()); // Verify post-Init state is correct - Post_Init_State (); - - // MockDriver is always 80x25 - Assert.Equal (80, Application.Driver.Cols); - Assert.Equal (25, Application.Driver.Rows); + //Post_Init_State (); Application.Shutdown (); // Verify state is back to initial - Pre_Init_State (); + //Pre_Init_State (); #if DEBUG_IDISPOSABLE // Validate there are no outstanding Responder-based instances // after a scenario was selected to run. This proves the main UI Catalog // 'app' closed cleanly. - foreach (var inst in Responder.Instances) { - Assert.True (inst.WasDisposed); - } + //foreach (var inst in Responder.Instances) { + //Assert.True (inst.WasDisposed); + //} #endif } @@ -100,7 +98,7 @@ public void Init_Shutdown_Toplevel_Not_Disposed () } [Fact] - public void Init_Unbalanced_Throwss () + public void Init_Unbalanced_Throws () { Application.Init (new FakeDriver ()); @@ -131,6 +129,16 @@ public TestToplevel () } } + [Fact] + public void Init_Null_Driver_Should_Pick_A_Driver () + { + Application.Init (null); + + Assert.NotNull (Application.Driver); + + Shutdown (); + } + [Fact] public void Init_Begin_End_Cleans_Up () { diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index e5b694f870..4633833258 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -25,7 +25,7 @@ public class MainLoopTests { public void Constructor_Setups_Driver () { var ml = new MainLoop (new FakeMainLoop ()); - Assert.NotNull (ml.Driver); + Assert.NotNull (ml.MainLoopDriver); } // Idle Handler tests @@ -525,6 +525,10 @@ public void Iteration () { throw new NotImplementedException (); } + public void TearDown () + { + throw new NotImplementedException (); + } public void Setup (MainLoop mainLoop) { @@ -659,7 +663,9 @@ public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions (Action Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null))); Assert.Equal (cancel, btn.Text); Assert.Equal (one, total); - } else if (taskCompleted) Application.RequestStop (); + } else if (taskCompleted) { + Application.RequestStop (); + } }; Application.Run (); diff --git a/UnitTests/AssemblyInfo.cs b/UnitTests/AssemblyInfo.cs index 8739e61a25..97d54f7cfc 100644 --- a/UnitTests/AssemblyInfo.cs +++ b/UnitTests/AssemblyInfo.cs @@ -1,11 +1 @@ global using CM = Terminal.Gui.ConfigurationManager; - -using System; -using System.Diagnostics; -using System.Reflection; -using Terminal.Gui; -using Xunit; - -// Since Application is a singleton we can't run tests in parallel -[assembly: CollectionBehavior (DisableTestParallelization = true)] - diff --git a/UnitTests/Clipboard/ClipboardTests.cs b/UnitTests/Clipboard/ClipboardTests.cs index 048bdb0b21..7ea0543452 100644 --- a/UnitTests/Clipboard/ClipboardTests.cs +++ b/UnitTests/Clipboard/ClipboardTests.cs @@ -1,10 +1,6 @@ using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using Terminal.Gui; using Xunit; using Xunit.Abstractions; -using static AutoInitShutdownAttribute; namespace Terminal.Gui.ClipboardTests { public class ClipboardTests { diff --git a/UnitTests/Configuration/ConfigurationMangerTests.cs b/UnitTests/Configuration/ConfigurationMangerTests.cs index 3a2689d026..662cd61f73 100644 --- a/UnitTests/Configuration/ConfigurationMangerTests.cs +++ b/UnitTests/Configuration/ConfigurationMangerTests.cs @@ -13,7 +13,7 @@ namespace Terminal.Gui.ConfigurationTests { public class ConfigurationManagerTests { - public static readonly JsonSerializerOptions _jsonOptions = new() { + public static readonly JsonSerializerOptions _jsonOptions = new () { Converters = { new AttributeJsonConverter (), new ColorJsonConverter (), @@ -65,44 +65,43 @@ public void DeepMemberwiseCopyTest () Assert.Equal (boolSrc, boolCopy); // Structs - var attrDest = new Attribute (1); - var attrSrc = new Attribute (2); + var attrDest = new Attribute (Color.Black); + var attrSrc = new Attribute (Color.White); var attrCopy = DeepMemberwiseCopy (attrSrc, attrDest); Assert.Equal (attrSrc, attrCopy); // Classes - var colorschemeDest = new ColorScheme () { Disabled = new Attribute (1) }; - var colorschemeSrc = new ColorScheme () { Disabled = new Attribute (2) }; + var colorschemeDest = new ColorScheme () { Disabled = new Attribute (Color.Black) }; + var colorschemeSrc = new ColorScheme () { Disabled = new Attribute (Color.White) }; var colorschemeCopy = DeepMemberwiseCopy (colorschemeSrc, colorschemeDest); Assert.Equal (colorschemeSrc, colorschemeCopy); // Dictionaries - var dictDest = new Dictionary () { { "Disabled", new Attribute (1) } }; - var dictSrc = new Dictionary () { { "Disabled", new Attribute (2) } }; + var dictDest = new Dictionary () { { "Disabled", new Attribute (Color.Black) } }; + var dictSrc = new Dictionary () { { "Disabled", new Attribute (Color.White) } }; var dictCopy = (Dictionary)DeepMemberwiseCopy (dictSrc, dictDest); Assert.Equal (dictSrc, dictCopy); - dictDest = new Dictionary () { { "Disabled", new Attribute (1) } }; - dictSrc = new Dictionary () { { "Disabled", new Attribute (2) }, { "Normal", new Attribute (3) } }; + dictDest = new Dictionary () { { "Disabled", new Attribute (Color.Black) } }; + dictSrc = new Dictionary () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } }; dictCopy = (Dictionary)DeepMemberwiseCopy (dictSrc, dictDest); Assert.Equal (dictSrc, dictCopy); // src adds an item - dictDest = new Dictionary () { { "Disabled", new Attribute (1) } }; - dictSrc = new Dictionary () { { "Disabled", new Attribute (2) }, { "Normal", new Attribute (3) } }; + dictDest = new Dictionary () { { "Disabled", new Attribute (Color.Black) } }; + dictSrc = new Dictionary () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } }; dictCopy = (Dictionary)DeepMemberwiseCopy (dictSrc, dictDest); Assert.Equal (2, dictCopy.Count); Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]); Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]); // src updates only one item - dictDest = new Dictionary () { { "Disabled", new Attribute (1) }, { "Normal", new Attribute (2) } }; - dictSrc = new Dictionary () { { "Disabled", new Attribute (3) } }; + dictDest = new Dictionary () { { "Disabled", new Attribute (Color.Black) }, { "Normal", new Attribute (Color.White) } }; + dictSrc = new Dictionary () { { "Disabled", new Attribute (Color.White) } }; dictCopy = (Dictionary)DeepMemberwiseCopy (dictSrc, dictDest); Assert.Equal (2, dictCopy.Count); Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]); Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]); - } //[Fact ()] @@ -207,7 +206,7 @@ public void UseWithoutResetAsserts () } [Fact] - public void Reset_Resets() + public void Reset_Resets () { ConfigurationManager.Locations = ConfigLocations.DefaultOnly; ConfigurationManager.Reset (); @@ -225,7 +224,6 @@ public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same () ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F; ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B; ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true; - ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true; ConfigurationManager.Settings.Apply (); // assert apply worked @@ -233,7 +231,6 @@ public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same () Assert.Equal (Key.F, Application.AlternateForwardKey); Assert.Equal (Key.B, Application.AlternateBackwardKey); Assert.True (Application.IsMouseDisabled); - Assert.True (Application.EnableConsoleScrolling); //act ConfigurationManager.Reset (); @@ -245,14 +242,12 @@ public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same () Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey); Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey); Assert.False (Application.IsMouseDisabled); - Assert.False (Application.EnableConsoleScrolling); // arrange ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q; ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F; ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B; ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true; - ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true; ConfigurationManager.Settings.Apply (); ConfigurationManager.Locations = ConfigLocations.DefaultOnly; @@ -268,7 +263,6 @@ public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same () Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey); Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey); Assert.False (Application.IsMouseDisabled); - Assert.False (Application.EnableConsoleScrolling); } @@ -312,7 +306,7 @@ public void TestConfigurationManagerToJson () ConfigurationManager.Reset (); ConfigurationManager.GetHardCodedDefaults (); var stream = ConfigurationManager.ToStream (); - + ConfigurationManager.Settings.Update (stream, "TestConfigurationManagerToJson"); } @@ -320,7 +314,7 @@ public void TestConfigurationManagerToJson () public void TestConfigurationManagerInitDriver_NoLocations () { - + } [Fact, AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)] @@ -336,7 +330,7 @@ public void TestConfigurationManagerInitDriver () // Change Base var json = ConfigurationManager.ToStream (); - + ConfigurationManager.Settings.Update (json, "TestConfigurationManagerInitDriver"); var colorSchemes = ((Dictionary)ConfigurationManager.Themes [ConfigurationManager.Themes.Theme] ["ColorSchemes"].PropertyValue); @@ -507,7 +501,7 @@ public void TestConfigurationManagerUpdateFromJson () ConfigurationManager.Reset (); ConfigurationManager.ThrowOnJsonErrors = true; - + ConfigurationManager.Settings.Update (json, "TestConfigurationManagerUpdateFromJson"); Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey); @@ -518,7 +512,7 @@ public void TestConfigurationManagerUpdateFromJson () Assert.Equal (Color.White, Colors.ColorSchemes ["Base"].Normal.Foreground); Assert.Equal (Color.Blue, Colors.ColorSchemes ["Base"].Normal.Background); - var colorSchemes = (Dictionary)Themes.First().Value ["ColorSchemes"].PropertyValue; + var colorSchemes = (Dictionary)Themes.First ().Value ["ColorSchemes"].PropertyValue; Assert.Equal (Color.White, colorSchemes ["Base"].Normal.Foreground); Assert.Equal (Color.Blue, colorSchemes ["Base"].Normal.Background); @@ -571,7 +565,7 @@ public void TestConfigurationManagerInvalidJsonThrows () ""UserDefined"": { ""AbNormal"": { ""foreground"": ""green"", - ""background"": ""1234"" + ""background"": ""black"" } } } @@ -615,7 +609,7 @@ public void TestConfigurationManagerInvalidJsonThrows () jsonException = Assert.Throws (() => ConfigurationManager.Settings.Update (json, "test")); Assert.StartsWith ("Unknown property", jsonException.Message); - + Assert.Equal (0, ConfigurationManager.jsonErrors.Length); ConfigurationManager.ThrowOnJsonErrors = false; @@ -661,7 +655,7 @@ public void TestConfigurationManagerInvalidJsonLogs () ""UserDefined"": { ""AbNormal"": { ""foreground"": ""green"", - ""background"": ""1234"" + ""background"": ""black"" } } } @@ -696,7 +690,7 @@ public void TestConfigurationManagerInvalidJsonLogs () ConfigurationManager.Settings.Update (json, "test"); ConfigurationManager.Settings.Update ("{}}", "test"); - + Assert.NotEqual (0, ConfigurationManager.jsonErrors.Length); Application.Shutdown (); @@ -743,12 +737,11 @@ public void LoadConfigurationFromAllSources_ShouldLoadSettingsFromAllSources () public void Load_FiresUpdated () { ConfigurationManager.Reset (); - + ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q; ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F; ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B; ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true; - ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true; ConfigurationManager.Updated += ConfigurationManager_Updated; bool fired = false; @@ -760,7 +753,6 @@ void ConfigurationManager_Updated (object sender, ConfigurationManagerEventArgs Assert.Equal (Key.PageDown | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue); Assert.Equal (Key.PageUp | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue); Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue); - Assert.False ((bool)ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue); } ConfigurationManager.Load (true); @@ -785,7 +777,6 @@ void ConfigurationManager_Applied (object sender, ConfigurationManagerEventArgs Assert.Equal (Key.F, Application.AlternateForwardKey); Assert.Equal (Key.B, Application.AlternateBackwardKey); Assert.True (Application.IsMouseDisabled); - Assert.True (Application.EnableConsoleScrolling); } // act @@ -793,7 +784,6 @@ void ConfigurationManager_Applied (object sender, ConfigurationManagerEventArgs ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F; ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B; ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true; - ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true; ConfigurationManager.Apply (); diff --git a/UnitTests/Configuration/JsonConverterTests.cs b/UnitTests/Configuration/JsonConverterTests.cs index 51bf898cd8..0b0178ac90 100644 --- a/UnitTests/Configuration/JsonConverterTests.cs +++ b/UnitTests/Configuration/JsonConverterTests.cs @@ -1,12 +1,5 @@ -using Xunit; -using Terminal.Gui; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Text.Json; -using Attribute = Terminal.Gui.Attribute; +using System.Text.Json; +using Xunit; namespace Terminal.Gui.ConfigurationTests { public class ColorJsonConverterTests { @@ -132,6 +125,56 @@ public void TestDeserializeColor_BrightRed () } } + public class TrueColorJsonConverterTests { + [Theory] + [InlineData (0,0,0, "\"#000000\"")] + public void SerializesToHexCode (int r, int g, int b, string expected) + { + // Arrange + + // Act + var actual = JsonSerializer.Serialize (new TrueColor (r, g, b), new JsonSerializerOptions { + Converters = { new TrueColorJsonConverter () } + }); + + //Assert + Assert.Equal (expected, actual); + + } + + [Theory] + [InlineData ("\"#000000\"", 0, 0, 0)] + public void DeserializesFromHexCode (string hexCode, int r, int g, int b) + { + // Arrange + TrueColor expected = new TrueColor (r, g, b); + + // Act + var actual = JsonSerializer.Deserialize (hexCode, new JsonSerializerOptions { + Converters = { new TrueColorJsonConverter () } + }); + + //Assert + Assert.Equal (expected, actual); + } + + [Theory] + [InlineData ("\"rgb(0,0,0)\"", 0, 0, 0)] + public void DeserializesFromRgb (string rgb, int r, int g, int b) + { + // Arrange + TrueColor expected = new TrueColor (r, g, b); + + // Act + var actual = JsonSerializer.Deserialize (rgb, new JsonSerializerOptions { + Converters = { new TrueColorJsonConverter () } + }); + + //Assert + Assert.Equal (expected, actual); + } + } + public class AttributeJsonConverterTests { [Fact, AutoInitShutdown] public void TestDeserialize () @@ -155,7 +198,7 @@ public void TestSerialize () // Test serializing to human-readable color names var attribute = new Attribute (Color.Blue, Color.Green); var json = JsonSerializer.Serialize (attribute, ConfigurationManagerTests._jsonOptions); - Assert.Equal ("{\"Foreground\":\"Blue\",\"Background\":\"Green\"}", json); + Assert.Equal ("{\"Foreground\":\"Blue\",\"Background\":\"Green\",\"TrueColorForeground\":\"#000080\",\"TrueColorBackground\":\"#008000\"}", json); } } diff --git a/UnitTests/Configuration/SettingsScopeTests.cs b/UnitTests/Configuration/SettingsScopeTests.cs index 4e1fc28f18..b7d89f258d 100644 --- a/UnitTests/Configuration/SettingsScopeTests.cs +++ b/UnitTests/Configuration/SettingsScopeTests.cs @@ -25,7 +25,6 @@ public void GetHardCodedDefaults_ShouldSetProperties () Assert.True (ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue is Key); Assert.True (ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue is Key); Assert.True (ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue is bool); - Assert.True (ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue is bool); Assert.True (ConfigurationManager.Settings ["Theme"].PropertyValue is string); Assert.Equal ("Default", ConfigurationManager.Settings ["Theme"].PropertyValue as string); @@ -43,14 +42,12 @@ public void Apply_ShouldApplyProperties () Assert.Equal (Key.PageDown | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue); Assert.Equal (Key.PageUp | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue); Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue); - Assert.False ((bool)ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue); // act ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q; ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F; ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B; ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true; - ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true; ConfigurationManager.Settings.Apply (); @@ -59,7 +56,6 @@ public void Apply_ShouldApplyProperties () Assert.Equal (Key.F, Application.AlternateForwardKey); Assert.Equal (Key.B, Application.AlternateBackwardKey); Assert.True (Application.IsMouseDisabled); - Assert.True (Application.EnableConsoleScrolling); } [Fact, AutoInitShutdown] @@ -73,14 +69,12 @@ public void CopyUpdatedProperitesFrom_ShouldCopyChangedPropertiesOnly () updatedSettings["Application.AlternateForwardKey"].PropertyValue = Key.F; updatedSettings["Application.AlternateBackwardKey"].PropertyValue = Key.B; updatedSettings["Application.IsMouseDisabled"].PropertyValue = true; - updatedSettings["Application.EnableConsoleScrolling"].PropertyValue = true; ConfigurationManager.Settings.Update (updatedSettings); Assert.Equal (Key.End, ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue); Assert.Equal (Key.F, updatedSettings ["Application.AlternateForwardKey"].PropertyValue); Assert.Equal (Key.B, updatedSettings ["Application.AlternateBackwardKey"].PropertyValue); Assert.True ((bool)updatedSettings ["Application.IsMouseDisabled"].PropertyValue); - Assert.True ((bool)updatedSettings ["Application.EnableConsoleScrolling"].PropertyValue); } } } \ No newline at end of file diff --git a/UnitTests/ConsoleDrivers/AddRuneTests.cs b/UnitTests/ConsoleDrivers/AddRuneTests.cs new file mode 100644 index 0000000000..33a4036b4f --- /dev/null +++ b/UnitTests/ConsoleDrivers/AddRuneTests.cs @@ -0,0 +1,196 @@ +using Microsoft.VisualStudio.TestPlatform.Utilities; +using System.Buffers; +using System.Text; +using Xunit; +using Xunit.Abstractions; +using static Terminal.Gui.SpinnerStyle; + +// Alias Console to MockConsole so we don't accidentally use Console + +namespace Terminal.Gui.DriverTests; +public class AddRuneTests { + readonly ITestOutputHelper _output; + + public AddRuneTests (ITestOutputHelper output) + { + this._output = output; + } + + [Fact] + public void AddRune () + { + + var driver = new FakeDriver (); + Application.Init (driver); + driver.Init (() => { }); + + driver.AddRune (new Rune ('a')); + Assert.Equal ((Rune)'a', driver.Contents [0, 0].Runes [0]); + + driver.End (); + Application.Shutdown (); + } + + [Fact] + public void AddRune_InvalidLocation_DoesNothing () + { + var driver = new FakeDriver (); + Application.Init (driver); + driver.Init (() => { }); + + driver.Move (driver.Cols, driver.Rows); + driver.AddRune ('a'); + + for (var col = 0; col < driver.Cols; col++) { + for (var row = 0; row < driver.Rows; row++) { + Assert.Equal ((Rune)' ', driver.Contents [row, col].Runes [0]); + } + } + + driver.End (); + Application.Shutdown (); + } + + [Fact] + public void AddRune_MovesToNextColumn () + { + var driver = new FakeDriver (); + Application.Init (driver); + driver.Init (() => { }); + + driver.AddRune ('a'); + Assert.Equal ((Rune)'a', driver.Contents [0, 0].Runes [0]); + Assert.Equal (0, driver.Row); + Assert.Equal (1, driver.Col); + + driver.AddRune ('b'); + Assert.Equal ((Rune)'b', driver.Contents [0, 1].Runes [0]); + Assert.Equal (0, driver.Row); + Assert.Equal (2, driver.Col); + + // Move to the last column of the first row + var lastCol = driver.Cols - 1; + driver.Move (lastCol, 0); + Assert.Equal (0, driver.Row); + Assert.Equal (lastCol, driver.Col); + + // Add a rune to the last column of the first row; should increment the row or col even though it's now invalid + driver.AddRune ('c'); + Assert.Equal ((Rune)'c', driver.Contents [0, lastCol].Runes [0]); + Assert.Equal (lastCol + 1, driver.Col); + + // Add a rune; should succeed but do nothing as it's outside of Contents + driver.AddRune ('d'); + Assert.Equal (lastCol + 2, driver.Col); + for (var col = 0; col < driver.Cols; col++) { + for (var row = 0; row < driver.Rows; row++) { + Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Runes [0]); + } + } + + driver.End (); + Application.Shutdown (); + } + + [Fact] + public void AddRune_MovesToNextColumn_Wide () + { + var driver = new FakeDriver (); + Application.Init (driver); + driver.Init (() => { }); + + // 🍕 Slice of Pizza "\U0001F355" + var operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out Rune rune, out int charsConsumed); + Assert.Equal (OperationStatus.Done, operationStatus); + Assert.Equal (charsConsumed, rune.Utf16SequenceLength); + Assert.Equal (2, rune.GetColumns ()); + + driver.AddRune (rune); + Assert.Equal (rune, driver.Contents [0, 0].Runes [0]); + Assert.Equal (0, driver.Row); + Assert.Equal (2, driver.Col); + + //driver.AddRune ('b'); + //Assert.Equal ((Rune)'b', driver.Contents [0, 1].Runes [0]); + //Assert.Equal (0, driver.Row); + //Assert.Equal (2, driver.Col); + + //// Move to the last column of the first row + //var lastCol = driver.Cols - 1; + //driver.Move (lastCol, 0); + //Assert.Equal (0, driver.Row); + //Assert.Equal (lastCol, driver.Col); + + //// Add a rune to the last column of the first row; should increment the row or col even though it's now invalid + //driver.AddRune ('c'); + //Assert.Equal ((Rune)'c', driver.Contents [0, lastCol].Runes [0]); + //Assert.Equal (lastCol + 1, driver.Col); + + //// Add a rune; should succeed but do nothing as it's outside of Contents + //driver.AddRune ('d'); + //Assert.Equal (lastCol + 2, driver.Col); + //for (var col = 0; col < driver.Cols; col++) { + // for (var row = 0; row < driver.Rows; row++) { + // Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Runes [0]); + // } + //} + + driver.End (); + Application.Shutdown (); + } + + + [Fact] + public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars () + { + var driver = new FakeDriver (); + Application.Init (driver); + driver.Init (() => { }); + + var expected = new Rune ('ắ'); + + var text = "\u1eaf"; + driver.AddStr (text); + Assert.Equal (expected, driver.Contents [0, 0].Runes [0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes [0]); + + driver.ClearContents (); + driver.Move (0, 0); + + text = "\u0103\u0301"; + driver.AddStr (text); + Assert.Equal (expected, driver.Contents [0, 0].Runes [0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes [0]); + + driver.ClearContents (); + driver.Move (0, 0); + + text = "\u0061\u0306\u0301"; + driver.AddStr (text); + Assert.Equal (expected, driver.Contents [0, 0].Runes [0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes [0]); + + // var s = "a\u0301\u0300\u0306"; + + + // TestHelpers.AssertDriverContentsWithFrameAre (@" + //ắ", output); + + // tf.Text = "\u1eaf"; + // Application.Refresh (); + // TestHelpers.AssertDriverContentsWithFrameAre (@" + //ắ", output); + + // tf.Text = "\u0103\u0301"; + // Application.Refresh (); + // TestHelpers.AssertDriverContentsWithFrameAre (@" + //ắ", output); + + // tf.Text = "\u0061\u0306\u0301"; + // Application.Refresh (); + // TestHelpers.AssertDriverContentsWithFrameAre (@" + //ắ", output); + driver.End (); + Application.Shutdown (); + } +} diff --git a/UnitTests/ConsoleDrivers/AttributeTests.cs b/UnitTests/ConsoleDrivers/AttributeTests.cs index f2130f220d..4f46c1a84c 100644 --- a/UnitTests/ConsoleDrivers/AttributeTests.cs +++ b/UnitTests/ConsoleDrivers/AttributeTests.cs @@ -23,31 +23,29 @@ public void Constuctors_Constuct () Assert.Equal (default (Color), attr.Foreground); Assert.Equal (default (Color), attr.Background); - // Test value, foreground, background - var value = 42; + // Test foreground, background var fg = new Color (); fg = Color.Red; var bg = new Color (); bg = Color.Blue; - attr = new Attribute (value, fg, bg); - - Assert.Equal (value, attr.Value); - Assert.Equal (fg, attr.Foreground); - Assert.Equal (bg, attr.Background); - - // value, foreground, background attr = new Attribute (fg, bg); + Assert.True (attr.Initialized); + Assert.True (attr.HasValidColors); Assert.Equal (fg, attr.Foreground); Assert.Equal (bg, attr.Background); attr = new Attribute (fg); + Assert.True (attr.Initialized); + Assert.True (attr.HasValidColors); Assert.Equal (fg, attr.Foreground); Assert.Equal (fg, attr.Background); attr = new Attribute (bg); + Assert.True (attr.Initialized); + Assert.True (attr.HasValidColors); Assert.Equal (bg, attr.Foreground); Assert.Equal (bg, attr.Background); @@ -73,44 +71,15 @@ public void Implicit_Assign () // Test conversion to int attr = new Attribute (value, fg, bg); - int value_implicit = (int)attr.Value; + int value_implicit = attr.Value; Assert.Equal (value, value_implicit); - // Test conversion from int - attr = value; Assert.Equal (value, attr.Value); driver.End (); Application.Shutdown (); } - [Fact] - public void Implicit_Assign_NoDriver () - { - - var attr = new Attribute (); - - var fg = new Color (); - fg = Color.Red; - - var bg = new Color (); - bg = Color.Blue; - - // Test conversion to int - attr = new Attribute (fg, bg); - int value_implicit = (int)attr.Value; - Assert.False (attr.Initialized); - - Assert.Equal (-1, value_implicit); - Assert.False (attr.Initialized); - - // Test conversion from int - attr = -1; - Assert.Equal (-1, attr.Value); - Assert.False (attr.Initialized); - - } - [Fact] public void Make_SetsNotInitialized_NoDriver () { @@ -227,5 +196,47 @@ public void IsValid_Tests () attr = new Attribute ((Color)(-1), (Color)(-1)); Assert.False (attr.HasValidColors); } + + [Fact] + public void Equals_NotInitialized() + { + var attr1 = new Attribute (Color.Red, Color.Green); + var attr2 = new Attribute (Color.Red, Color.Green); + + Assert.True (attr1.Equals (attr2)); + Assert.True (attr2.Equals (attr1)); + } + + [Fact] + public void NotEquals_NotInitialized () + { + var attr1 = new Attribute (Color.Red, Color.Green); + var attr2 = new Attribute (Color.Green, Color.Red); + + Assert.False (attr1.Equals (attr2)); + Assert.False (attr2.Equals (attr1)); + } + + [Fact, AutoInitShutdown] + public void Equals_Initialized () + { + Assert.NotNull(Application.Driver); + + var attr1 = new Attribute (Color.Red, Color.Green); + var attr2 = new Attribute (Color.Red, Color.Green); + + Assert.True (attr1.Equals (attr2)); + Assert.True (attr2.Equals (attr1)); + } + + [Fact,AutoInitShutdown] + public void NotEquals_Initialized () + { + var attr1 = new Attribute (Color.Red, Color.Green); + var attr2 = new Attribute (Color.Green, Color.Red); + + Assert.False (attr1.Equals (attr2)); + Assert.False (attr2.Equals (attr1)); + } } } diff --git a/UnitTests/ConsoleDrivers/ClipRegionTests.cs b/UnitTests/ConsoleDrivers/ClipRegionTests.cs new file mode 100644 index 0000000000..9afaa05611 --- /dev/null +++ b/UnitTests/ConsoleDrivers/ClipRegionTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using Xunit.Abstractions; + +// Alias Console to MockConsole so we don't accidentally use Console +using Console = Terminal.Gui.FakeConsole; + +namespace Terminal.Gui.DriverTests { + public class ClipRegionTests { + readonly ITestOutputHelper output; + + public ClipRegionTests (ITestOutputHelper output) + { + this.output = output; + } + + [Theory] + [InlineData (typeof (FakeDriver))] + public void IsValidLocation (Type driverType) + { + var driver = (FakeDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + + // positive + Assert.True (driver.IsValidLocation (0, 0)); + Assert.True (driver.IsValidLocation (1, 1)); + Assert.True (driver.IsValidLocation (driver.Cols - 1, driver.Rows - 1)); + // negative + Assert.False (driver.IsValidLocation (-1, 0)); + Assert.False (driver.IsValidLocation (0, -1)); + Assert.False (driver.IsValidLocation (-1, -1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows)); + + // Define a clip rectangle + driver.Clip = new Rect (5, 5, 5, 5); + // positive + Assert.True (driver.IsValidLocation (5, 5)); + Assert.True (driver.IsValidLocation (9, 9)); + // negative + Assert.False (driver.IsValidLocation (4, 5)); + Assert.False (driver.IsValidLocation (5, 4)); + Assert.False (driver.IsValidLocation (10, 9)); + Assert.False (driver.IsValidLocation (9, 10)); + Assert.False (driver.IsValidLocation (-1, 0)); + Assert.False (driver.IsValidLocation (0, -1)); + Assert.False (driver.IsValidLocation (-1, -1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows)); + + Application.Shutdown (); + } + + [Theory] + [InlineData (typeof (FakeDriver))] + public void Clip_Set_To_Empty_AllInvalid (Type driverType) + { + var driver = (FakeDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + + // Define a clip rectangle + driver.Clip = Rect.Empty; + + // negative + Assert.False (driver.IsValidLocation (4, 5)); + Assert.False (driver.IsValidLocation (5, 4)); + Assert.False (driver.IsValidLocation (10, 9)); + Assert.False (driver.IsValidLocation (9, 10)); + Assert.False (driver.IsValidLocation (-1, 0)); + Assert.False (driver.IsValidLocation (0, -1)); + Assert.False (driver.IsValidLocation (-1, -1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1)); + Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows)); + + Application.Shutdown (); + } + + [Theory] + [InlineData (typeof (FakeDriver))] + public void AddRune_Is_Clipped (Type driverType) + { + var driver = (FakeDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + + driver.Move (0, 0); + driver.AddRune ('x'); + Assert.Equal ((Rune)'x', driver.Contents [0, 0].Runes [0]); + + driver.Move (5, 5); + driver.AddRune ('x'); + Assert.Equal ((Rune)'x', driver.Contents [5, 5].Runes [0]); + + // Clear the contents + driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), ' '); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes [0]); + + // Setup the region with a single rectangle, fill screen with 'x' + driver.Clip = new Rect (5, 5, 5, 5); + driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), 'x'); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes [0]); + Assert.Equal ((Rune)' ', driver.Contents [4, 9].Runes [0]); + Assert.Equal ((Rune)'x', driver.Contents [5, 5].Runes [0]); + Assert.Equal ((Rune)'x', driver.Contents [9, 9].Runes [0]); + Assert.Equal ((Rune)' ', driver.Contents [10, 10].Runes [0]); + + Application.Shutdown (); + } + } +} diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs index 03858df3e8..9779c79b4f 100644 --- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs @@ -57,8 +57,6 @@ public void End_Cleans_Up (Type driverType) Console.BackgroundColor = ConsoleColor.Green; Assert.Equal (ConsoleColor.Green, Console.BackgroundColor); driver.Move (2, 3); - Assert.Equal (2, Console.CursorLeft); - Assert.Equal (3, Console.CursorTop); driver.End (); Assert.Equal (0, Console.CursorLeft); @@ -225,17 +223,6 @@ public void TerminalResized_Simulation (Type driverType) Assert.Equal (40, Application.Driver.Rows); Assert.True (wasTerminalResized); - // MockDriver will still be 120x40 - wasTerminalResized = false; - Application.EnableConsoleScrolling = true; - driver.SetWindowSize (40, 20); - Assert.Equal (120, Application.Driver.Cols); - Assert.Equal (40, Application.Driver.Rows); - Assert.Equal (120, Console.BufferWidth); - Assert.Equal (40, Console.BufferHeight); - Assert.Equal (40, Console.WindowWidth); - Assert.Equal (20, Console.WindowHeight); - Assert.True (wasTerminalResized); Application.Shutdown (); } diff --git a/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs b/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs index c84b8bd25e..9bd550ca77 100644 --- a/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs @@ -19,12 +19,11 @@ public ConsoleScrollingTests (ITestOutputHelper output) [Theory] [InlineData (typeof (FakeDriver))] - public void EnableConsoleScrolling_Is_False_Left_And_Top_Is_Always_Zero (Type driverType) + public void Left_And_Top_Is_Always_Zero (Type driverType) { var driver = (FakeDriver)Activator.CreateInstance (driverType); Application.Init (driver); - Assert.False (Application.EnableConsoleScrolling); Assert.Equal (0, Console.WindowLeft); Assert.Equal (0, Console.WindowTop); @@ -34,123 +33,6 @@ public void EnableConsoleScrolling_Is_False_Left_And_Top_Is_Always_Zero (Type dr Application.Shutdown (); } - - [Theory] - [InlineData (typeof (FakeDriver))] - public void EnableConsoleScrolling_Is_True_Left_Cannot_Be_Greater_Than_WindowWidth (Type driverType) - { - var driver = (FakeDriver)Activator.CreateInstance (driverType); - Application.Init (driver); - - Application.EnableConsoleScrolling = true; - Assert.True (Application.EnableConsoleScrolling); - - driver.SetWindowPosition (81, 25); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - Application.Shutdown (); - } - - [Theory] - [InlineData (typeof (FakeDriver))] - public void EnableConsoleScrolling_Is_True_Left_Cannot_Be_Greater_Than_BufferWidth_Minus_WindowWidth (Type driverType) - { - var driver = (FakeDriver)Activator.CreateInstance (driverType); - Application.Init (driver); - - Application.EnableConsoleScrolling = true; - Assert.True (Application.EnableConsoleScrolling); - - driver.SetWindowPosition (81, 25); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - // MockDriver will now be sets to 120x25 - driver.SetBufferSize (120, 25); - Assert.Equal (120, Application.Driver.Cols); - Assert.Equal (25, Application.Driver.Rows); - Assert.Equal (120, Console.BufferWidth); - Assert.Equal (25, Console.BufferHeight); - Assert.Equal (80, Console.WindowWidth); - Assert.Equal (25, Console.WindowHeight); - driver.SetWindowPosition (121, 25); - Assert.Equal (40, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - driver.SetWindowSize (90, 25); - Assert.Equal (120, Application.Driver.Cols); - Assert.Equal (25, Application.Driver.Rows); - Assert.Equal (120, Console.BufferWidth); - Assert.Equal (25, Console.BufferHeight); - Assert.Equal (90, Console.WindowWidth); - Assert.Equal (25, Console.WindowHeight); - driver.SetWindowPosition (121, 25); - Assert.Equal (30, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - Application.Shutdown (); - } - - [Theory] - [InlineData (typeof (FakeDriver))] - public void EnableConsoleScrolling_Is_True_Top_Cannot_Be_Greater_Than_WindowHeight (Type driverType) - { - var driver = (FakeDriver)Activator.CreateInstance (driverType); - Application.Init (driver); - - Application.EnableConsoleScrolling = true; - Assert.True (Application.EnableConsoleScrolling); - - driver.SetWindowPosition (80, 26); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - Application.Shutdown (); - } - - [Theory] - [InlineData (typeof (FakeDriver))] - public void EnableConsoleScrolling_Is_True_Top_Cannot_Be_Greater_Than_BufferHeight_Minus_WindowHeight (Type driverType) - { - var driver = (FakeDriver)Activator.CreateInstance (driverType); - Application.Init (driver); - - Application.EnableConsoleScrolling = true; - Assert.True (Application.EnableConsoleScrolling); - - driver.SetWindowPosition (80, 26); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - // MockDriver will now be sets to 80x40 - driver.SetBufferSize (80, 40); - Assert.Equal (80, Application.Driver.Cols); - Assert.Equal (40, Application.Driver.Rows); - Assert.Equal (80, Console.BufferWidth); - Assert.Equal (40, Console.BufferHeight); - Assert.Equal (80, Console.WindowWidth); - Assert.Equal (25, Console.WindowHeight); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - driver.SetWindowPosition (80, 40); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (15, Console.WindowTop); - - driver.SetWindowSize (80, 20); - Assert.Equal (80, Application.Driver.Cols); - Assert.Equal (40, Application.Driver.Rows); - Assert.Equal (80, Console.BufferWidth); - Assert.Equal (40, Console.BufferHeight); - Assert.Equal (80, Console.WindowWidth); - Assert.Equal (20, Console.WindowHeight); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (15, Console.WindowTop); - driver.SetWindowPosition (80, 41); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (20, Console.WindowTop); - - Application.Shutdown (); - } + } } diff --git a/UnitTests/ConsoleDrivers/ContentsTests.cs b/UnitTests/ConsoleDrivers/ContentsTests.cs new file mode 100644 index 0000000000..2bd0a10b45 --- /dev/null +++ b/UnitTests/ConsoleDrivers/ContentsTests.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +// Alias Console to MockConsole so we don't accidentally use Console +using Console = Terminal.Gui.FakeConsole; + +namespace Terminal.Gui.DriverTests; +public class ContentsTests { + readonly ITestOutputHelper output; + + public ContentsTests (ITestOutputHelper output) + { + this.output = output; + } + + [Theory] + [InlineData (typeof (FakeDriver))] + //[InlineData (typeof (NetDriver))] + //[InlineData (typeof (CursesDriver))] + //[InlineData (typeof (WindowsDriver))] + public void AddStr_With_Combining_Characters (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + // driver.Init (null); + + var acuteaccent = new System.Text.Rune (0x0301); // Combining acute accent (é) + var combined = "e" + acuteaccent; + var expected = "é"; + + driver.AddStr (combined); + TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + +#if false // Disabled Until #2616 is fixed + + // 3 char combine + // a + ogonek + acute = ( ǫ́ ) + var ogonek = new System.Text.Rune (0x0328); // Combining ogonek (a small hook or comma shape) + combined = "a" + ogonek + acuteaccent; + expected = "ǫ́"; + + driver.Move (0, 0); + driver.AddStr (combined); + TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + +#endif + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } + + [Theory] + [InlineData (typeof (FakeDriver))] + //[InlineData (typeof (NetDriver))] + //[InlineData (typeof (CursesDriver))] + //[InlineData (typeof (WindowsDriver))] + public void Move_Bad_Coordinates (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + + Assert.Equal (0, driver.Col); + Assert.Equal (0, driver.Row); + + driver.Move (-1, 0); + Assert.Equal (-1, driver.Col); + Assert.Equal (0, driver.Row); + + driver.Move (0, -1); + Assert.Equal (0, driver.Col); + Assert.Equal (-1, driver.Row); + + driver.Move (driver.Cols, 0); + Assert.Equal (driver.Cols, driver.Col); + Assert.Equal (0, driver.Row); + + driver.Move (0, driver.Rows); + Assert.Equal (0, driver.Col); + Assert.Equal (driver.Rows, driver.Row); + + driver.Move (500, 500); + Assert.Equal (500, driver.Col); + Assert.Equal (500, driver.Row); + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } + + // TODO: Add these unit tests + + // AddRune moves correctly + + // AddRune with wide characters are handled correctly + + // AddRune with wide characters and Col < 0 are handled correctly + + // AddRune with wide characters and Col == Cols - 1 are handled correctly + + // AddRune with wide characters and Col == Cols are handled correctly + + // AddStr moves correctly + + // AddStr with wide characters moves correctly + + // AddStr where Col is negative works + + // AddStr where Col is negative and characters include wide / combining characters works + + // AddStr where Col is near Cols and characters include wide / combining characters works + + // Clipping works correctly + + // Clipping works correctly with wide characters + + // Clipping works correctly with combining characters + + // Clipping works correctly with combining characters and wide characters + + // ResizeScreen works correctly + + // Refresh works correctly + + // IsDirty tests +} + diff --git a/UnitTests/ConsoleDrivers/KeyTests.cs b/UnitTests/ConsoleDrivers/KeyTests.cs index 4f40707ae3..56e6d3c1f3 100644 --- a/UnitTests/ConsoleDrivers/KeyTests.cs +++ b/UnitTests/ConsoleDrivers/KeyTests.cs @@ -184,41 +184,43 @@ public void Key_ToString () [ClassData (typeof (PacketTest))] public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode) { - var modifiers = new ConsoleModifiers (); - if (shift) modifiers |= ConsoleModifiers.Shift; - if (alt) modifiers |= ConsoleModifiers.Alt; - if (control) modifiers |= ConsoleModifiers.Control; - var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar); - - if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) Assert.Equal (mappedConsoleKey, initialVirtualKey); - else Assert.Equal (mappedConsoleKey, outputChar < 0xff ? outputChar & 0xff | 0xff << 8 : outputChar); - Assert.Equal (scanCode, initialScanCode); - - var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode); - - //if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) { - if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) Assert.Equal (0, (double)keyChar); - else Assert.Equal (keyChar, unicodeCharacter); - Assert.Equal (consoleKey, expectedVirtualKey); - Assert.Equal (scanCode, expectedScanCode); - - var top = Application.Top; - - top.KeyPress += (s, e) => { - var after = ShortcutHelper.GetModifiersKey (e.KeyEvent); - Assert.Equal (expectedRemapping, after); - e.Handled = true; - Application.RequestStop (); - }; - - var iterations = -1; - - Application.Iteration += () => { - iterations++; - if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control); - }; - lock (packetLock) { + Application._forceFakeConsole = true; + Application.Init (); + + var modifiers = new ConsoleModifiers (); + if (shift) modifiers |= ConsoleModifiers.Shift; + if (alt) modifiers |= ConsoleModifiers.Alt; + if (control) modifiers |= ConsoleModifiers.Control; + var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar); + + if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) Assert.Equal (mappedConsoleKey, initialVirtualKey); + else Assert.Equal (mappedConsoleKey, outputChar < 0xff ? outputChar & 0xff | 0xff << 8 : outputChar); + Assert.Equal (scanCode, initialScanCode); + + var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode); + + //if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) { + if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) Assert.Equal (0, (double)keyChar); + else Assert.Equal (keyChar, unicodeCharacter); + Assert.Equal (consoleKey, expectedVirtualKey); + Assert.Equal (scanCode, expectedScanCode); + + var top = Application.Top; + + top.KeyPress += (s, e) => { + var after = ShortcutHelper.GetModifiersKey (e.KeyEvent); + Assert.Equal (expectedRemapping, after); + e.Handled = true; + Application.RequestStop (); + }; + + var iterations = -1; + + Application.Iteration += () => { + iterations++; + if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control); + }; Application.Run (); Application.Shutdown (); } diff --git a/UnitTests/FileServices/FileDialogTests.cs b/UnitTests/FileServices/FileDialogTests.cs index 5d1b1f282a..96e3012339 100644 --- a/UnitTests/FileServices/FileDialogTests.cs +++ b/UnitTests/FileServices/FileDialogTests.cs @@ -217,14 +217,14 @@ public void MultiSelectDirectory_CannotToggleDotDot (bool acceptWithEnter) Assert.Multiple ( () => { // Only the subfolder should be selected - Assert.Equal (1, dlg.MultiSelected.Count); + Assert.Single (dlg.MultiSelected); AssertIsTheSubfolder (dlg.Path); AssertIsTheSubfolder (dlg.MultiSelected.Single ()); }, () => { // Event should also agree with the final state Assert.NotNull (eventMultiSelected); - Assert.Equal (1, eventMultiSelected.Count); + Assert.Single (eventMultiSelected); AssertIsTheSubfolder (eventMultiSelected.Single ()); } ); @@ -330,14 +330,14 @@ public void MultiSelectDirectory_CanToggleThenAccept (bool acceptWithEnter) Assert.Multiple ( () => { // Only the subfolder should be selected - Assert.Equal (1, dlg.MultiSelected.Count); + Assert.Single (dlg.MultiSelected); AssertIsTheSubfolder (dlg.Path); AssertIsTheSubfolder (dlg.MultiSelected.Single ()); }, () => { // Event should also agree with the final state Assert.NotNull (eventMultiSelected); - Assert.Equal (1, eventMultiSelected.Count); + Assert.Single (eventMultiSelected); AssertIsTheSubfolder (eventMultiSelected.Single ()); } ); @@ -382,7 +382,7 @@ public void TestDirectoryContents_Linux () fd.Style.Culture = new CultureInfo ("en-US"); fd.Draw (); - + string expected = @$" ┌──────────────────────────────────────────────────────────────────┐ @@ -467,7 +467,7 @@ public void TestDirectoryContents_Windows_Colors () fd.Draw (); - TestHelpers.AssertDriverUsedColors (other,dir,img,exe); + TestHelpers.AssertDriverUsedColors (other, dir, img, exe); } private ColorScheme GetColorScheme (Attribute a) @@ -487,7 +487,7 @@ private ColorScheme GetColorScheme (Attribute a) [InlineData (".csv", "c:\\MyFile.csv", true)] [InlineData (".csv", "c:\\MyFile.CSV", true)] [InlineData (".csv", "c:\\MyFile.csv.bak", false)] - public void TestAllowedType_Basic(string allowed, string candidate, bool expected) + public void TestAllowedType_Basic (string allowed, string candidate, bool expected) { Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate)); } @@ -496,7 +496,7 @@ public void TestAllowedType_Basic(string allowed, string candidate, bool expecte [InlineData ("Dockerfile", "c:\\temp\\Dockerfile", true)] [InlineData ("Dockerfile", "Dockerfile", true)] [InlineData ("Dockerfile", "someimg.Dockerfile", true)] - public void TestAllowedType_SpecificFile(string allowed, string candidate, bool expected) + public void TestAllowedType_SpecificFile (string allowed, string candidate, bool expected) { Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate)); } @@ -504,7 +504,7 @@ public void TestAllowedType_SpecificFile(string allowed, string candidate, bool [Theory] [InlineData (".Designer.cs", "c:\\MyView.Designer.cs", true)] [InlineData (".Designer.cs", "c:\\temp/MyView.Designer.cs", true)] - [InlineData(".Designer.cs","MyView.Designer.cs",true)] + [InlineData (".Designer.cs", "MyView.Designer.cs", true)] [InlineData (".Designer.cs", "c:\\MyView.DESIGNER.CS", true)] [InlineData (".Designer.cs", "MyView.cs", false)] public void TestAllowedType_DoubleBarreled (string allowed, string candidate, bool expected) diff --git a/UnitTests/Input/EscSeqReqTests.cs b/UnitTests/Input/EscSeqReqTests.cs index 53032e2667..35a50cc2e9 100644 --- a/UnitTests/Input/EscSeqReqTests.cs +++ b/UnitTests/Input/EscSeqReqTests.cs @@ -10,69 +10,69 @@ public class EscSeqReqTests { [Fact] public void Constructor_Defaults () { - var escSeqReq = new EscSeqReqProc (); - Assert.NotNull (escSeqReq.EscSeqReqStats); - Assert.Empty (escSeqReq.EscSeqReqStats); + var escSeqReq = new EscSeqRequests (); + Assert.NotNull (escSeqReq.Statuses); + Assert.Empty (escSeqReq.Statuses); } [Fact] public void Add_Tests () { - var escSeqReq = new EscSeqReqProc (); + var escSeqReq = new EscSeqRequests (); escSeqReq.Add ("t"); - Assert.Single (escSeqReq.EscSeqReqStats); - Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating); - Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumRequests); - Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding); + Assert.Single (escSeqReq.Statuses); + Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal (1, escSeqReq.Statuses [^1].NumRequests); + Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); escSeqReq.Add ("t", 2); - Assert.Single (escSeqReq.EscSeqReqStats); - Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating); - Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumRequests); - Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding); + Assert.Single (escSeqReq.Statuses); + Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal (1, escSeqReq.Statuses [^1].NumRequests); + Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); - escSeqReq = new EscSeqReqProc (); + escSeqReq = new EscSeqRequests (); escSeqReq.Add ("t", 2); - Assert.Single (escSeqReq.EscSeqReqStats); - Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating); - Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests); - Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumOutstanding); + Assert.Single (escSeqReq.Statuses); + Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); + Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding); escSeqReq.Add ("t", 3); - Assert.Single (escSeqReq.EscSeqReqStats); - Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating); - Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests); - Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumOutstanding); + Assert.Single (escSeqReq.Statuses); + Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); + Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding); } [Fact] public void Remove_Tests () { - var escSeqReq = new EscSeqReqProc (); + var escSeqReq = new EscSeqRequests (); escSeqReq.Add ("t"); escSeqReq.Remove ("t"); - Assert.Empty (escSeqReq.EscSeqReqStats); + Assert.Empty (escSeqReq.Statuses); escSeqReq.Add ("t", 2); escSeqReq.Remove ("t"); - Assert.Single (escSeqReq.EscSeqReqStats); - Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating); - Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests); - Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding); + Assert.Single (escSeqReq.Statuses); + Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator); + Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests); + Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding); escSeqReq.Remove ("t"); - Assert.Empty (escSeqReq.EscSeqReqStats); + Assert.Empty (escSeqReq.Statuses); } [Fact] public void Requested_Tests () { - var escSeqReq = new EscSeqReqProc (); - Assert.False (escSeqReq.Requested ("t")); + var escSeqReq = new EscSeqRequests (); + Assert.False (escSeqReq.HasResponse ("t")); escSeqReq.Add ("t"); - Assert.False (escSeqReq.Requested ("r")); - Assert.True (escSeqReq.Requested ("t")); + Assert.False (escSeqReq.HasResponse ("r")); + Assert.True (escSeqReq.HasResponse ("t")); } } } diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index 39c34db622..9afb3d903f 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -10,15 +10,15 @@ public class EscSeqUtilsTests { public void Defaults_Values () { Assert.Equal ('\x1b', EscSeqUtils.KeyEsc); - Assert.Equal ("\x1b[", EscSeqUtils.KeyCSI); + Assert.Equal ("\x1b[", EscSeqUtils.CSI); Assert.Equal ("\x1b[?1003h", EscSeqUtils.CSI_EnableAnyEventMouse); Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse); Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse); Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse); Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse); Assert.Equal ("\x1b[?1015l", EscSeqUtils.CSI_DisableUrxvtExtModeMouse); - Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.EnableMouseEvents); - Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.DisableMouseEvents); + Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.CSI_EnableMouseEvents); + Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.CSI_DisableMouseEvents); } [Fact] @@ -26,51 +26,51 @@ public void GetConsoleInputKey_ConsoleKeyInfo () { var cki = new ConsoleKeyInfo ('r', 0, false, false, false); var expectedCki = new ConsoleKeyInfo ('r', 0, false, false, false); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('r', 0, true, false, false); expectedCki = new ConsoleKeyInfo ('r', 0, true, false, false); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('r', 0, false, true, false); expectedCki = new ConsoleKeyInfo ('r', 0, false, true, false); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('r', 0, false, false, true); expectedCki = new ConsoleKeyInfo ('r', 0, false, false, true); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('r', 0, true, true, false); expectedCki = new ConsoleKeyInfo ('r', 0, true, true, false); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('r', 0, false, true, true); expectedCki = new ConsoleKeyInfo ('r', 0, false, true, true); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('r', 0, true, true, true); expectedCki = new ConsoleKeyInfo ('r', 0, true, true, true); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('\u0012', 0, false, false, false); expectedCki = new ConsoleKeyInfo ('R', ConsoleKey.R, false, false, true); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('\0', (ConsoleKey)64, false, false, true); expectedCki = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar, false, false, true); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('\r', 0, false, false, false); expectedCki = new ConsoleKeyInfo ('\r', ConsoleKey.Enter, false, false, false); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('\u007f', 0, false, false, false); expectedCki = new ConsoleKeyInfo ('\u007f', ConsoleKey.Backspace, false, false, false); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); cki = new ConsoleKeyInfo ('R', 0, false, false, false); expectedCki = new ConsoleKeyInfo ('R', 0, false, false, false); - Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki)); + Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki)); } [Fact] @@ -83,7 +83,7 @@ public void ResizeArray_ConsoleKeyInfo () Assert.Equal (cki, expectedCkInfos [0]); } - private EscSeqReqProc escSeqReqProc; + private EscSeqRequests escSeqReqProc; private ConsoleKeyInfo newConsoleKeyInfo; private ConsoleKey key; private ConsoleKeyInfo [] cki; @@ -617,7 +617,7 @@ public void DecodeEscSeq_Tests () ClearAll (); Assert.Null (escSeqReqProc); - escSeqReqProc = new EscSeqReqProc (); + escSeqReqProc = new EscSeqRequests (); escSeqReqProc.Add ("t"); cki = new ConsoleKeyInfo [] { @@ -633,10 +633,10 @@ public void DecodeEscSeq_Tests () new ConsoleKeyInfo ('t', 0, false, false, false) }; expectedCki = default; - Assert.Single (escSeqReqProc.EscSeqReqStats); - Assert.Equal ("t", escSeqReqProc.EscSeqReqStats [^1].Terminating); + Assert.Single (escSeqReqProc.Statuses); + Assert.Equal ("t", escSeqReqProc.Statuses [^1].Terminator); EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed); - Assert.Empty (escSeqReqProc.EscSeqReqStats); + Assert.Empty (escSeqReqProc.Statuses); Assert.Equal (expectedCki, newConsoleKeyInfo); Assert.Equal (0, (int)key); Assert.Equal (0, (int)mod); diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 82f03ce71f..535b3bcd3c 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -4,7 +4,6 @@ using System.Text; using Xunit.Abstractions; using Xunit; -using Terminal.Gui; using System.Text.RegularExpressions; using System.Reflection; using System.Diagnostics; @@ -12,7 +11,9 @@ using Attribute = Terminal.Gui.Attribute; using Microsoft.VisualStudio.TestPlatform.Utilities; using Xunit.Sdk; +using System.Globalization; +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. @@ -31,54 +32,45 @@ public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute { /// CursesDriver, NetDriver) will be used when Application.Init is called. If null FakeDriver will be used. /// Only valid if is true. /// If true, will force the use of . - /// Only valid if == and is true. + /// Only valid if == and is true. /// Only valid if is true. - /// Only valid if == and is true. + /// Only valid if == and is true. /// Only valid if is true. - /// Only valid if == and is true. + /// Only valid if == and is true. /// Determines what config file locations will /// load from. - public AutoInitShutdownAttribute (bool autoInit = true, bool autoShutdown = true, + public AutoInitShutdownAttribute (bool autoInit = true, Type consoleDriverType = null, bool useFakeClipboard = true, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false, ConfigurationManager.ConfigLocations configLocation = ConfigurationManager.ConfigLocations.DefaultOnly) { - //Assert.True (autoInit == false && consoleDriverType == null); - AutoInit = autoInit; - AutoShutdown = autoShutdown; - DriverType = consoleDriverType ?? typeof (FakeDriver); + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); + _driverType = consoleDriverType ?? typeof (FakeDriver); FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard; FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException; FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue; ConfigurationManager.Locations = configLocation; } - static bool _init = false; bool AutoInit { get; } - bool AutoShutdown { get; } - Type DriverType; + Type _driverType; public override void Before (MethodInfo methodUnderTest) { Debug.WriteLine ($"Before: {methodUnderTest.Name}"); - if (AutoShutdown && _init) { - throw new InvalidOperationException ("After did not run when AutoShutdown was specified."); - } if (AutoInit) { - Application.Init ((ConsoleDriver)Activator.CreateInstance (DriverType)); - _init = true; + Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType)); } } - + public override void After (MethodInfo methodUnderTest) { Debug.WriteLine ($"After: {methodUnderTest.Name}"); - if (AutoShutdown) { + if (AutoInit) { Application.Shutdown (); - _init = false; } } } @@ -107,20 +99,26 @@ public override void After (MethodInfo methodUnderTest) } } -class TestHelpers { +partial class TestHelpers { + [GeneratedRegex ("\\s+$", RegexOptions.Multiline)] + private static partial Regex TrailingWhiteSpaceRegEx (); + [GeneratedRegex ("^\\s+", RegexOptions.Multiline)] + private static partial Regex LeadingWhitespaceRegEx (); + #pragma warning disable xUnit1013 // Public method should be marked as test public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output, bool ignoreLeadingWhitespace = false) { #pragma warning restore xUnit1013 // Public method should be marked as test var sb = new StringBuilder (); - var driver = ((FakeDriver)Application.Driver); + var driver = (FakeDriver)Application.Driver; var contents = driver.Contents; for (int r = 0; r < driver.Rows; r++) { for (int c = 0; c < driver.Cols; c++) { - Rune rune = (Rune)contents [r, c, 0]; + // TODO: Remove hard-coded [0] once combining pairs is supported + Rune rune = contents [r, c].Runes[0]; if (rune.DecodeSurrogatePair (out char [] spair)) { sb.Append (spair); } else { @@ -135,51 +133,47 @@ public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelp var actualLook = sb.ToString (); - if (!string.Equals (expectedLook, actualLook)) { - - // ignore trailing whitespace on each line - var trailingWhitespace = new Regex (@"\s+$", RegexOptions.Multiline); - var leadingWhitespace = new Regex (@"^\s+", RegexOptions.Multiline); - - // get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string) - expectedLook = trailingWhitespace.Replace (expectedLook, "").Trim (); - actualLook = trailingWhitespace.Replace (actualLook, "").Trim (); - - if (ignoreLeadingWhitespace) { - expectedLook = leadingWhitespace.Replace (expectedLook, "").Trim (); - actualLook = leadingWhitespace.Replace (actualLook, "").Trim (); - } + if (string.Equals (expectedLook, actualLook)) return; + + // get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string) + expectedLook = TrailingWhiteSpaceRegEx ().Replace (expectedLook, "").Trim (); + actualLook = TrailingWhiteSpaceRegEx ().Replace (actualLook, "").Trim (); - // standardize line endings for the comparison - expectedLook = expectedLook.Replace ("\r\n", "\n"); - actualLook = actualLook.Replace ("\r\n", "\n"); + if (ignoreLeadingWhitespace) { + expectedLook = LeadingWhitespaceRegEx().Replace (expectedLook, "").Trim (); + actualLook = LeadingWhitespaceRegEx().Replace (actualLook, "").Trim (); + } - // 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); - } + // standardize line endings for the comparison + expectedLook = expectedLook.Replace ("\r\n", "\n"); + actualLook = actualLook.Replace ("\r\n", "\n"); - Assert.Equal (expectedLook, actualLook); + // 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); } public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output) { var lines = new List> (); var sb = new StringBuilder (); - var driver = ((FakeDriver)Application.Driver); + var driver = Application.Driver; var x = -1; var y = -1; - int w = -1; - int h = -1; - + var w = -1; + var h = -1; + var contents = driver.Contents; - for (int r = 0; r < driver.Rows; r++) { + for (var r = 0; r < driver.Rows; r++) { var runes = new List (); - for (int c = 0; c < driver.Cols; c++) { - var rune = (Rune)contents [r, c, 0]; + for (var c = 0; c < driver.Cols; c++) { + // TODO: Remove hard-coded [0] once combining pairs is supported + Rune rune = contents [r, c].Runes [0]; if (rune != (Rune)' ') { if (x == -1) { x = c; @@ -196,26 +190,19 @@ public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestO } h = r - y + 1; } - if (x > -1) { - runes.Add (rune); - } - } - if (runes.Count > 0) { - lines.Add (runes); + if (x > -1) runes.Add (rune); } + if (runes.Count > 0) lines.Add (runes); } // Remove unnecessary empty lines if (lines.Count > 0) { - for (int r = lines.Count - 1; r > h - 1; r--) { - lines.RemoveAt (r); - } + for (var r = lines.Count - 1; r > h - 1; r--) lines.RemoveAt (r); } // Remove trailing whitespace on each line - for (int r = 0; r < lines.Count; r++) { - List row = lines [r]; - for (int c = row.Count - 1; c >= 0; c--) { + foreach (var row in lines) { + for (var c = row.Count - 1; c >= 0; c--) { var rune = row [c]; if (rune != (Rune)' ' || (row.Sum (x => x.GetColumns ()) == w)) { break; @@ -236,25 +223,22 @@ public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestO var actualLook = sb.ToString (); - if (!string.Equals (expectedLook, actualLook)) { - - // standardize line endings for the comparison - expectedLook = expectedLook.Replace ("\r\n", "\n"); - actualLook = actualLook.Replace ("\r\n", "\n"); + if (string.Equals (expectedLook, actualLook)) { + return new Rect (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); + } + + // standardize line endings for the comparison + expectedLook = expectedLook.Replace ("\r\n", "\n"); + actualLook = actualLook.Replace ("\r\n", "\n"); - // Remove the first and the last line ending from the expectedLook - if (expectedLook.StartsWith ("\n")) { - expectedLook = expectedLook [1..]; - } - if (expectedLook.EndsWith ("\n")) { - expectedLook = expectedLook [..^1]; - } + // Remove the first and the last line ending from the expectedLook + if (expectedLook.StartsWith ("\n")) expectedLook = expectedLook [1..]; + if (expectedLook.EndsWith ("\n")) expectedLook = expectedLook [..^1]; - output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook); - output?.WriteLine ("But Was:" + Environment.NewLine + actualLook); + output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook); + output?.WriteLine ("But Was:" + Environment.NewLine + actualLook); - Assert.Equal (expectedLook, actualLook); - } + Assert.Equal (expectedLook, actualLook); return new Rect (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); } @@ -270,35 +254,32 @@ public static void AssertDriverColorsAre (string expectedLook, params Attribute { #pragma warning restore xUnit1013 // Public method should be marked as test - if (expectedColors.Length > 10) { - throw new ArgumentException ("This method only works for UIs that use at most 10 colors"); - } + if (expectedColors.Length > 10) throw new ArgumentException ("This method only works for UIs that use at most 10 colors"); expectedLook = expectedLook.Trim (); - var driver = ((FakeDriver)Application.Driver); - + var driver = (FakeDriver)Application.Driver; + var contents = driver.Contents; - int r = 0; + var r = 0; foreach (var line in expectedLook.Split ('\n').Select (l => l.Trim ())) { - for (int c = 0; c < line.Length; c++) { + for (var c = 0; c < line.Length; c++) { - int val = contents [r, c, 1]; + var val = contents [r, c].Attribute; - var match = expectedColors.Where (e => e.Value == val).ToList (); - if (match.Count == 0) { - throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0). Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => DescribeColor (c.Value)))})"); - } else if (match.Count > 1) { + var match = expectedColors.Where (e => e == val).ToList (); + switch (match.Count) { + case 0: + throw new Exception ($"Unexpected color {val} was used at row {r} and col {c} (indexes start at 0). Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value.ToString()))})"); + case > 1: throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value"); } var colorUsed = Array.IndexOf (expectedColors, match [0]).ToString () [0]; var userExpected = line [c]; - if (colorUsed != userExpected) { - throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0). Color index used was {colorUsed} ({DescribeColor (val)}) but test expected {userExpected} ({DescribeColor (expectedColors [int.Parse (userExpected.ToString ())].Value)}) (these are indexes into the expectedColors array)"); - } + if (colorUsed != userExpected) throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0). Color index used was {colorUsed} ({val}) but test expected {userExpected} ({expectedColors [int.Parse (userExpected.ToString ())].Value}) (these are indexes into the expectedColors array)"); } r++; @@ -312,40 +293,37 @@ public static void AssertDriverColorsAre (string expectedLook, params Attribute /// internal static void AssertDriverUsedColors (params Attribute [] expectedColors) { - var driver = ((FakeDriver)Application.Driver); + var driver = (FakeDriver)Application.Driver; var contents = driver.Contents; var toFind = expectedColors.ToList (); - var colorsUsed = new HashSet (); + // Contents 3rd column is an Attribute + var colorsUsed = new HashSet (); - for (int r = 0; r < driver.Rows; r++) { - for (int c = 0; c < driver.Cols; c++) { - int val = contents [r, c, 1]; - - colorsUsed.Add (val); + for (var r = 0; r < driver.Rows; r++) { + for (var c = 0; c < driver.Cols; c++) { + var val = contents [r, c].Attribute; + if (val.HasValue) { + colorsUsed.Add (val.Value); - var match = toFind.FirstOrDefault (e => e.Value == val); + var match = toFind.FirstOrDefault (e => e == val); - // need to check twice because Attribute is a struct and therefore cannot be null - if (toFind.Any (e => e.Value == val)) { - toFind.Remove (match); + // need to check twice because Attribute is a struct and therefore cannot be null + if (toFind.Any (e => e == val)) { + toFind.Remove (match); + } } - } - } + }} - if(toFind.Any()) { - var sb = new StringBuilder (); - sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => DescribeColor (a)))); - sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (DescribeColor))); - throw new Exception (sb.ToString()); + if (!toFind.Any ()) { + return; } - } - private static object DescribeColor (int userExpected) - { - var a = new Attribute (userExpected); - return $"{a.Foreground},{a.Background}"; + var sb = new StringBuilder (); + sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString()))); + sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString()))); + throw new Exception (sb.ToString ()); } #pragma warning disable xUnit1013 // Public method should be marked as test @@ -376,11 +354,11 @@ private static string ReplaceNewLinesToPlatformSpecific (string toReplace) { var replaced = toReplace; - if (Environment.NewLine.Length == 2 && !replaced.Contains ("\r\n")) { - replaced = replaced.Replace ("\n", Environment.NewLine); - } else if (Environment.NewLine.Length == 1) { - replaced = replaced.Replace ("\r\n", Environment.NewLine); - } + replaced = Environment.NewLine.Length switch { + 2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine), + 1 => replaced.Replace ("\r\n", Environment.NewLine), + var _ => replaced + }; return replaced; } diff --git a/UnitTests/Text/RuneTests.cs b/UnitTests/Text/RuneTests.cs index 9c0d686bf9..699a3ba225 100644 --- a/UnitTests/Text/RuneTests.cs +++ b/UnitTests/Text/RuneTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using Xunit; +using static Terminal.Gui.SpinnerStyle; namespace Terminal.Gui.TextTests; @@ -19,7 +20,10 @@ public class RuneTests { [InlineData (0x0328, true)] // Combining ogonek (a small hook or comma shape) U+0328 [InlineData (0x00E9, false)] // Latin Small Letter E with Acute, Unicode U+00E9 é [InlineData (0x0061, false)] // Latin Small Letter A is U+0061. - public void TestIsCombiningMark (int codepoint, bool expected) + [InlineData ('\uFE20', true)] // Combining Ligature Left Half - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml + [InlineData ('\uFE21', true)] // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml + + public void IsCombiningMark (int codepoint, bool expected) { var rune = new Rune (codepoint); Assert.Equal (expected, rune.IsCombiningMark ()); @@ -53,6 +57,8 @@ public void MakePrintable_Does_Not_Convert_Ansi_Chars_To_Unicode (int code) [InlineData (0x0301)] // Combining acute accent (é) [InlineData (0x0302)] // Combining Circumflex Accent [InlineData (0x0061)] // Combining ogonek (a small hook or comma shape) + [InlineData ('\uFE20')] // Combining Ligature Left Half - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml + [InlineData ('\uFE21')] // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml public void MakePrintable_Combining_Character_Is_Not_Printable (int code) { var rune = new Rune (code); @@ -61,30 +67,65 @@ public void MakePrintable_Combining_Character_Is_Not_Printable (int code) } [Theory] + [InlineData (0, "\0", 0, 1, 1)] + [InlineData ('\u1dc0', "᷀", 0, 1, 3)] // ◌᷀ Combining Dotted Grave Accent + [InlineData ('\u20D0', "⃐", 0, 1, 3)] // ◌⃐ Combining Left Harpoon Above + + [InlineData (1, "\u0001", -1, 1, 1)] + [InlineData (2, "\u0002", -1, 1, 1)] + [InlineData (31, "\u001f", -1, 1, 1)] // non printable character - Information Separator One + [InlineData (127, "\u007f", -1, 1, 1)] // non printable character - Delete + + [InlineData (32, " ", 1, 1, 1)] // space [InlineData ('a', "a", 1, 1, 1)] [InlineData ('b', "b", 1, 1, 1)] [InlineData (123, "{", 1, 1, 1)] // { Left Curly Bracket - [InlineData ('\u1150', "ᅐ", 2, 1, 3)] // ᅐ Hangul Choseong Ceongchieumcieuc - [InlineData ('\u1161', "ᅡ", 0, 1, 3)] // ᅡ Hangul Jungseong A - Unicode Hangul Jamo for join with column width equal to 0 alone. - [InlineData (31, "\u001f", -1, 1, 1)] // non printable character - Information Separator One - [InlineData (127, "\u007f", -1, 1, 1)] // non printable character - Delete - [InlineData ('\u20D0', "⃐", 0, 1, 3)] // ◌⃐ Combining Left Harpoon Above + [InlineData ('\u231c', "⌜", 1, 1, 3)] // ⌜ Top Left Corner + + // BUGBUG: These are CLEARLY wide glyphs, but GetColumns() returns 1 + // However, most terminals treat these as narrow and they overlap the next cell when drawn (including Windows Terminal) + [InlineData ('\u1161', "ᅡ", 1, 1, 3)] // ᅡ Hangul Jungseong A - Unicode Hangul Jamo for join with column width equal to 0 alone. + [InlineData ('\u2103', "℃", 1, 1, 3)] // ℃ Degree Celsius + [InlineData ('\u2501', "━", 1, 1, 3)] // ━ Box Drawings Heavy Horizontal [InlineData ('\u25a0', "■", 1, 1, 3)] // ■ Black Square [InlineData ('\u25a1', "□", 1, 1, 3)] // □ White Square + [InlineData ('\u277f', "❿", 1, 1, 3)] //Dingbat Negative Circled Number Ten - ❿ U+277f + [InlineData ('\u4dc0', "䷀", 1, 1, 3)] // ䷀Hexagram For The Creative Heaven - U+4dc0 - https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml + [InlineData ('\ud7b0', "ힰ", 1, 1, 3)] // ힰ ┤Hangul Jungseong O-Yeo - ힰ U+d7b0')] [InlineData ('\uf61e', "", 1, 1, 3)] // Private Use Area - [InlineData ('\u2103', "℃", 1, 1, 3)] // ℃ Degree Celsius + + [InlineData ('\u23f0', "⏰", 2, 1, 3)] // Alarm Clock - ⏰ U+23f0 [InlineData ('\u1100', "ᄀ", 2, 1, 3)] // ᄀ Hangul Choseong Kiyeok - [InlineData ('\u2501', "━", 1, 1, 3)] // ━ Box Drawings Heavy Horizontal + [InlineData ('\u1150', "ᅐ", 2, 1, 3)] // ᅐ Hangul Choseong Ceongchieumcieuc [InlineData ('\u2615', "☕", 2, 1, 3)] // ☕ Hot Beverage [InlineData ('\u231a', "⌚", 2, 1, 3)] // ⌚ Watch [InlineData ('\u231b', "⌛", 2, 1, 3)] // ⌛ Hourglass - [InlineData ('\u231c', "⌜", 1, 1, 3)] // ⌜ Top Left Corner - [InlineData ('\u1dc0', "᷀", 0, 1, 3)] // ◌᷀ Combining Dotted Grave Accent - public void GetColumns_With_Single_Code (int code, string str, int runeLength, int stringLength, int utf8Length) + + // From WindowsTerminal's CodepointWidthDetector tests (https://github.com/microsoft/terminal/blob/main/src/types/CodepointWidthDetector.cpp) + //static constexpr std::wstring_view emoji = L"\xD83E\xDD22"; // U+1F922 nauseated face + //static constexpr std::wstring_view ambiguous = L"\x414"; // U+0414 cyrillic capital de + + //{ 0x414, L"\x414", CodepointWidth::Narrow }, // U+0414 cyrillic capital de + [InlineData ('\u0414', "Д", 1, 1, 2)] // U+0414 cyrillic capital de + + //{ 0x1104, L"\x1104", CodepointWidth::Wide }, // U+1104 hangul choseong ssangtikeut + [InlineData ('\u1104', "ᄄ", 2, 1, 3)] + + //{ 0x306A, L"\x306A", CodepointWidth::Wide }, // U+306A hiragana na な + [InlineData (0x306A, "な", 2, 1, 3)] + + //{ 0x30CA, L"\x30CA", CodepointWidth::Wide }, // U+30CA katakana na ナ + [InlineData (0x30CA, "ナ", 2, 1, 3)] + + //{ 0x72D7, L"\x72D7", CodepointWidth::Wide }, // U+72D7 + [InlineData (0x72D7, "狗", 2, 1, 3)] + + + public void GetColumns_With_Single_Code (int code, string str, int columns, int stringLength, int utf8Length) { var rune = new Rune (code); Assert.Equal (str, rune.ToString ()); - Assert.Equal (runeLength, rune.GetColumns ()); + Assert.Equal (columns, rune.GetColumns ()); Assert.Equal (stringLength, rune.ToString ().Length); Assert.Equal (utf8Length, rune.Utf8SequenceLength); Assert.True (Rune.IsValid (rune.Value)); @@ -97,12 +138,12 @@ public void GetColumns_With_Single_Code (int code, string str, int runeLength, i [InlineData (new byte [] { 0xf0, 0x9f, 0xa4, 0x96 }, "🤖", 2, 2)] // 🤖 Robot Face [InlineData (new byte [] { 0xf0, 0x90, 0x90, 0xa1 }, "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er [InlineData (new byte [] { 0xf0, 0x9f, 0x8c, 0xb9 }, "🌹", 2, 2)] // 🌹 Rose - public void GetColumns_Utf8_Encode (byte [] code, string str, int runeLength, int stringLength) + public void GetColumns_Utf8_Encode (byte [] code, string str, int columns, int stringLength) { var operationStatus = Rune.DecodeFromUtf8 (code, out Rune rune, out int bytesConsumed); Assert.Equal (OperationStatus.Done, operationStatus); Assert.Equal (str, rune.ToString ()); - Assert.Equal (runeLength, rune.GetColumns ()); + Assert.Equal (columns, rune.GetColumns ()); Assert.Equal (stringLength, rune.ToString ().Length); Assert.Equal (bytesConsumed, rune.Utf8SequenceLength); Assert.True (Rune.IsValid (rune.Value)); @@ -116,11 +157,13 @@ public void GetColumns_Utf8_Encode (byte [] code, string str, int runeLength, in [InlineData (new char [] { '\ud83e', '\udde0' }, "🧠", 2, 2, 4)] // 🧠 Brain [InlineData (new char [] { '\ud801', '\udc21' }, "𐐡", 1, 2, 4)] // 𐐡 Deseret Capital Letter Er [InlineData (new char [] { '\ud83c', '\udf39' }, "🌹", 2, 2, 4)] // 🌹 Rose - public void GetColumns_Utf16_Encode (char [] code, string str, int runeLength, int stringLength, int utf8Length) + [InlineData (new char [] { '\uD83D', '\uDC7E' }, "👾", 2, 2, 4)] // U+1F47E alien monster (CodepointWidth::Wide) + [InlineData (new char [] { '\uD83D', '\uDD1C' }, "🔜", 2, 2, 4)] // 🔜 Soon With Rightwards Arrow Above (CodepointWidth::Wide) + public void GetColumns_Utf16_Encode (char [] code, string str, int columns, int stringLength, int utf8Length) { var rune = new Rune (code [0], code [1]); Assert.Equal (str, rune.ToString ()); - Assert.Equal (runeLength, rune.GetColumns ()); + Assert.Equal (columns, rune.GetColumns ()); Assert.Equal (stringLength, rune.ToString ().Length); Assert.Equal (utf8Length, rune.Utf8SequenceLength); Assert.True (Rune.IsValid (rune.Value)); @@ -134,12 +177,14 @@ public void GetColumns_Utf16_Encode (char [] code, string str, int runeLength, i [InlineData ("\U0001f9e0", "🧠", 2, 2)] // 🧠 Brain [InlineData ("\U00010421", "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er [InlineData ("\U0001f339", "🌹", 2, 2)] // 🌹 Rose - public void GetColumns_Utf32_Encode (string code, string str, int runeLength, int stringLength) + //[InlineData ("\uFE20FE21", "", 1, 1)] // Combining Ligature Left Half - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml + // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml + public void GetColumns_Utf32_Encode (string code, string str, int columns, int stringLength) { var operationStatus = Rune.DecodeFromUtf16 (code, out Rune rune, out int charsConsumed); Assert.Equal (OperationStatus.Done, operationStatus); Assert.Equal (str, rune.ToString ()); - Assert.Equal (runeLength, rune.GetColumns ()); + Assert.Equal (columns, rune.GetColumns ()); Assert.Equal (stringLength, rune.ToString ().Length); Assert.Equal (charsConsumed, rune.Utf16SequenceLength); Assert.True (Rune.IsValid (rune.Value)); @@ -147,7 +192,7 @@ public void GetColumns_Utf32_Encode (string code, string str, int runeLength, in // with DecodeRune (var nrune, var size) = code.DecodeRune (); Assert.Equal (str, nrune.ToString ()); - Assert.Equal (runeLength, nrune.GetColumns ()); + Assert.Equal (columns, nrune.GetColumns ()); Assert.Equal (stringLength, nrune.ToString ().Length); Assert.Equal (size, nrune.Utf8SequenceLength); for (int x = 0; x < code.Length - 1; x++) { @@ -165,12 +210,12 @@ public void GetColumns_Utf32_Encode (string code, string str, int runeLength, in [InlineData ("\u0915\u093f", "कि", 2, 2, 2)] // Hindi कि with DEVANAGARI LETTER KA and DEVANAGARI VOWEL SIGN I [InlineData ("\u0e4d\u0e32", "ํา", 2, 1, 2)] // Decomposition: ํ (U+0E4D) - า (U+0E32) = U+0E33 ำ Thai Character Sara Am [InlineData ("\u0e33", "ำ", 1, 1, 1)] // Decomposition: ํ (U+0E4D) - า (U+0E32) = U+0E33 ำ Thai Character Sara Am - public void GetColumns_String_Without_SurrogatePair (string code, string str, int codeLength, int runesLength, int stringLength) + public void GetColumns_String_Without_SurrogatePair (string code, string str, int codeLength, int columns, int stringLength) { Assert.Equal (str, code.Normalize ()); Assert.Equal (codeLength, code.Length); - Assert.Equal (runesLength, code.EnumerateRunes ().Sum (x => x.GetColumns ())); - Assert.Equal (runesLength, str.GetColumns ()); + //Assert.Equal (columns, code.EnumerateRunes ().Sum (x => x.GetColumns ())); + Assert.Equal (columns, str.GetColumns ()); Assert.Equal (stringLength, str.Length); } @@ -539,7 +584,7 @@ public void Test_All_Surrogate_Pairs_Range () [Theory] [InlineData ("Hello, 世界", 13, 11, 9)] // Without Surrogate Pairs - [InlineData ("Hello, 𝔹𝕆𝔹", 19, 13, 13)] // With Surrogate Pairs + [InlineData ("Hello, 𝔹𝕆𝔹", 19, 10, 13)] // With Surrogate Pairs public void Test_DecodeRune_Extension (string text, int bytesLength, int colsLength, int textLength) { List runes = new List (); @@ -558,7 +603,7 @@ public void Test_DecodeRune_Extension (string text, int bytesLength, int colsLen [Theory] [InlineData ("Hello, 世界", 13, 11, 9, "界世 ,olleH")] // Without Surrogate Pairs - [InlineData ("Hello, 𝔹𝕆𝔹", 19, 13, 13, "𝔹𝕆𝔹 ,olleH")] // With Surrogate Pairs + [InlineData ("Hello, 𝔹𝕆𝔹", 19, 10, 13, "𝔹𝕆𝔹 ,olleH")] // With Surrogate Pairs public void Test_DecodeLastRune_Extension (string text, int bytesLength, int colsLength, int textLength, string encoded) { List runes = new List (); @@ -641,17 +686,64 @@ public void Equals_ToRuneList () Assert.False (d.SequenceEqual (b [1])); } + /// + /// Shows the difference between using Wcwidth.UnicodeCalculator and our + /// own port of wcwidth. Specifically, the UnicodeCalculator is more accurate to spec + /// where null has a width of 0, and our port says it's -1. + /// + /// + /// + [Theory] + [InlineData (0, 0)] + [InlineData (-1, 1)] + [InlineData (-1, 2)] + [InlineData (-1, 3)] + [InlineData (-1, 4)] + [InlineData (-1, 5)] + [InlineData (-1, 6)] + [InlineData (-1, 7)] + [InlineData (-1, 8)] + [InlineData (-1, 9)] + [InlineData (-1, 10)] + [InlineData (-1, 11)] + [InlineData (-1, 12)] + [InlineData (-1, 13)] + [InlineData (-1, 14)] + [InlineData (-1, 15)] + [InlineData (-1, 16)] + [InlineData (-1, 17)] + [InlineData (-1, 18)] + [InlineData (-1, 19)] + [InlineData (-1, 20)] + [InlineData (-1, 21)] + [InlineData (-1, 22)] + [InlineData (-1, 23)] + [InlineData (-1, 24)] + [InlineData (-1, 25)] + [InlineData (-1, 26)] + [InlineData (-1, 27)] + [InlineData (-1, 28)] + [InlineData (-1, 29)] + [InlineData (-1, 30)] + [InlineData (-1, 31)] + public void Rune_GetColumns_Non_Printable (int expectedColumns, int scalar) + { + var rune = new Rune (scalar); + Assert.Equal (expectedColumns, rune.GetColumns()); + Assert.Equal (0, rune.ToString().GetColumns()); + } + [Fact] public void Rune_GetColumns_Versus_String_GetColumns_With_Non_Printable_Characters () { int sumRuneWidth = 0; int sumConsoleWidth = 0; for (uint i = 0; i < 32; i++) { - sumRuneWidth += ((Rune)i).GetColumns (); - sumConsoleWidth += ((Rune)i).ToString ().GetColumns (); + sumRuneWidth += ((Rune)(i)).GetColumns (); + sumConsoleWidth += ((Rune)(i)).ToString ().GetColumns (); } - Assert.Equal (-32, sumRuneWidth); + Assert.Equal (-31, sumRuneWidth); Assert.Equal (0, sumConsoleWidth); } diff --git a/UnitTests/Text/StringTests.cs b/UnitTests/Text/StringTests.cs index 4b708034e9..9ed93dc2f4 100644 --- a/UnitTests/Text/StringTests.cs +++ b/UnitTests/Text/StringTests.cs @@ -16,7 +16,7 @@ public void TestGetColumns_Empty () public void TestGetColumns_Null () { string? str = null; - Assert.Equal (0, str.GetColumns ()); + Assert.Equal (0, str!.GetColumns ()); } [Fact] @@ -51,6 +51,8 @@ public void TestGetColumns_MultiRune (string str, int expected) [InlineData ("👨‍👩‍👦‍👦👨‍👩‍👦‍👦", 16)] [InlineData ("山", 2)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71 [InlineData ("山🙂", 4)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71 + //[InlineData ("\ufe20\ufe21", 2)] // Combining Ligature Left Half ︠ - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml + // // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml public void TestGetColumns_MultiRune_WideBMP (string str, int expected) { Assert.Equal (expected, str.GetColumns ()); diff --git a/UnitTests/Text/UnicodeTests.cs b/UnitTests/Text/UnicodeTests.cs index 6d616ac6fa..12f8c9f4e6 100644 --- a/UnitTests/Text/UnicodeTests.cs +++ b/UnitTests/Text/UnicodeTests.cs @@ -1,4 +1,5 @@ -using Xunit; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Xunit; using Xunit.Abstractions; // Alias Console to MockConsole so we don't accidentally use Console @@ -41,10 +42,10 @@ public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_Wit ┌────────────────────────────┐ │これは広いルーンラインです。│ │これは広いルーンラインです。│ -│これは ┌────────────┐ です。│ -│これは │ワイドルーン│ です。│ -│これは │ {CM.Glyphs.LeftBracket} 選ぶ {CM.Glyphs.RightBracket} │ です。│ -│これは └────────────┘ です。│ +│これは�┌────────────┐�です。│ +│これは�│ワイドルーン│�です。│ +│これは�│ {CM.Glyphs.LeftBracket} 選ぶ {CM.Glyphs.RightBracket} │�です。│ +│これは�└────────────┘�です。│ │これは広いルーンラインです。│ │これは広いルーンラインです。│ └────────────────────────────┘ diff --git a/UnitTests/UnitTests - Backup.csproj b/UnitTests/UnitTests - Backup.csproj new file mode 100644 index 0000000000..bac4003278 --- /dev/null +++ b/UnitTests/UnitTests - Backup.csproj @@ -0,0 +1,63 @@ + + + net7.0 + + + Preview + false + + + + + 2.0 + 2.0 + 2.0 + 2.0 + + + TRACE + + + TRACE;DEBUG_IDISPOSABLE + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + False + + + [UICatalog]* + + + + + + + + + + False + + C:\Users\charlie\s\gui-cs\Terminal.Gui\UnitTests\bin\Debug\net7.0\fine-code-coverage\coverage-tool-output\UnitTests-fcc-mscodecoverage-generated.runsettings + + \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 57747a3ba0..58015ee29a 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -21,12 +21,12 @@ TRACE;DEBUG_IDISPOSABLE - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -39,6 +39,11 @@ + + + PreserveNewest + + False diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index 3f700c2d7f..6a5ea02432 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -23,8 +23,8 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () Assert.Equal ("𝔹", r.ToString ()); Assert.Equal (us, r.ToString ()); - Assert.Equal (2, us.GetColumns ()); - Assert.Equal (2, r.GetColumns ()); + Assert.Equal (1, us.GetColumns ()); + Assert.Equal (1, r.GetColumns ()); var win = new Window () { Title = us }; var label = new Label (r.ToString ()); @@ -37,9 +37,9 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () ((FakeDriver)Application.Driver).SetBufferSize (10, 4); var expected = @" -┌┤𝔹├────┐ -│𝔹 │ -│𝔹 │ +┌┤𝔹├─────┐ +│𝔹 │ +│𝔹 │ └────────┘"; TestHelpers.AssertDriverContentsWithFrameAre (expected, output); @@ -55,7 +55,7 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () }; TestHelpers.AssertDriverColorsAre (@" -0022000000 +0020000000 0000000000 0111000000 0000000000", expectedColors); diff --git a/UnitTests/View/KeyboardTests.cs b/UnitTests/View/KeyboardTests.cs index 6ddd755aff..7b35d9c80a 100644 --- a/UnitTests/View/KeyboardTests.cs +++ b/UnitTests/View/KeyboardTests.cs @@ -160,7 +160,7 @@ public void KeyDown_And_KeyUp_Events_With_Only_Key_Modifiers (bool shift, bool a Application.Top.Add (view); - Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', shift, alt, control)); + Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\0', 0, shift, alt, control)); Application.Iteration += () => Application.RequestStop (); diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs index 2734bf3130..5b6c4492fe 100644 --- a/UnitTests/View/Layout/LayoutTests.cs +++ b/UnitTests/View/Layout/LayoutTests.cs @@ -540,8 +540,8 @@ public void Excess_Text_Is_Erased_When_The_Width_Is_Reduced () // that was set on the OnAdded method with the text length of 3 // and height 1 because wasn't set and the text has 1 line Assert.Equal (new Rect (0, 0, 3, 1), lbl.Frame); - Assert.Equal (new Rect (0, 0, 3, 1), lbl._needsDisplay); - Assert.Equal (new Rect (0, 0, 0, 0), lbl.SuperView._needsDisplay); + Assert.Equal (new Rect (0, 0, 3, 1), lbl._needsDisplayRect); + Assert.Equal (new Rect (0, 0, 0, 0), lbl.SuperView._needsDisplayRect); Assert.True (lbl.SuperView.LayoutNeeded); lbl.SuperView.Draw (); Assert.Equal ("12 ", GetContents ()); @@ -550,7 +550,7 @@ string GetContents () { var text = ""; for (int i = 0; i < 4; i++) { - text += (char)Application.Driver.Contents [0, i, 0]; + text += Application.Driver.Contents [0, i].Runes[0]; } return text; } diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 7c3a862dc1..7273c5e8f6 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -534,11 +534,11 @@ public void LabelChangeText_RendersCorrectly_Constructors (int choice) Application.Begin (Application.Top); // should have the initial text - Assert.Equal ('t', driver.Contents [0, 0, 0]); - Assert.Equal ('e', driver.Contents [0, 1, 0]); - Assert.Equal ('s', driver.Contents [0, 2, 0]); - Assert.Equal ('t', driver.Contents [0, 3, 0]); - Assert.Equal (' ', driver.Contents [0, 4, 0]); + Assert.Equal ((Rune)'t', driver.Contents [0, 0].Runes [0]); + Assert.Equal ((Rune)'e', driver.Contents [0, 1].Runes [0]); + Assert.Equal ((Rune)'s', driver.Contents [0, 2].Runes [0]); + Assert.Equal ((Rune)'t', driver.Contents [0, 3].Runes [0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes [0]); } finally { Application.Shutdown (); } @@ -563,7 +563,7 @@ public void Internal_Tests () // BUGBUG: v2 - _needsDisplay needs debugging - test disabled for now. //Assert.Equal (new Rect (new Point (0, 0), rect.Size), view._needsDisplay); Assert.True (view.LayoutNeeded); - Assert.False (view._subViewNeedsDisplay); + Assert.False (view.SubViewNeedsDisplay); Assert.False (view._addingView); view._addingView = true; Assert.True (view._addingView); @@ -680,7 +680,7 @@ int RunesCount () for (int i = 0; i < Application.Driver.Rows; i++) { for (int j = 0; j < Application.Driver.Cols; j++) { - if (contents [i, j, 0] != ' ') { + if (contents [i, j].Runes[0] != (Rune)' ') { runesCount++; } } @@ -888,7 +888,7 @@ public void Visible_Clear_The_View_Output () win.Add (label); var top = Application.Top; top.Add (win); - Application.Begin (top); + var rs = Application.Begin (top); Assert.True (label.Visible); ((FakeDriver)Application.Driver).SetBufferSize (30, 5); @@ -901,6 +901,9 @@ public void Visible_Clear_The_View_Output () ", output); label.Visible = false; + + bool firstIteration = false; + Application.RunMainLoopIteration (ref rs, true, ref firstIteration); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────────────────────────┐ │ │ @@ -1092,7 +1095,7 @@ A text with some long width Assert.Equal (LayoutStyle.Computed, view.LayoutStyle); view.LayoutStyle = LayoutStyle.Absolute; Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect); top.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1127,7 +1130,7 @@ A text with some long width view.Height = 1; Assert.Equal (new Rect (1, 1, 10, 1), view.Frame); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect); top.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1161,7 +1164,7 @@ A text with some long width Assert.Equal (LayoutStyle.Computed, view.LayoutStyle); view.LayoutStyle = LayoutStyle.Absolute; Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect); view.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1198,7 +1201,7 @@ A text with some long width view.Height = 1; Assert.Equal (new Rect (1, 1, 10, 1), view.Frame); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect); view.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1233,7 +1236,7 @@ A text with some long width Assert.Equal (LayoutStyle.Computed, view.LayoutStyle); view.LayoutStyle = LayoutStyle.Absolute; Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect); top.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1270,7 +1273,7 @@ A text with some long width view.Height = 1; Assert.Equal (new Rect (3, 3, 10, 1), view.Frame); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect); top.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1303,7 +1306,7 @@ A text with some long width view.Frame = new Rect (3, 3, 10, 1); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect); view.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1340,7 +1343,7 @@ A text with some long width view.Height = 1; Assert.Equal (new Rect (3, 3, 10, 1), view.Frame); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); - Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect); view.Draw (); TestHelpers.AssertDriverContentsWithFrameAre (@" At 0,0 @@ -1363,6 +1366,8 @@ public void Test_Nested_Views_With_Height_Equal_To_One () bottom.Add (new Label ("222")); v.Add (bottom); + v.BeginInit (); + v.EndInit (); v.LayoutSubviews (); v.Draw (); @@ -1407,19 +1412,19 @@ public void Frame_Set_After_Initialize_Update_NeededDisplay () Application.Begin (top); top.LayoutComplete += (s, e) => { - Assert.Equal (new Rect (0, 0, 80, 25), top._needsDisplay); + Assert.Equal (new Rect (0, 0, 80, 25), top._needsDisplayRect); }; frame.LayoutComplete += (s, e) => { - Assert.Equal (new Rect (0, 0, 40, 8), frame._needsDisplay); + Assert.Equal (new Rect (0, 0, 40, 8), frame._needsDisplayRect); }; label.LayoutComplete += (s, e) => { - Assert.Equal (new Rect (0, 0, 38, 1), label._needsDisplay); + Assert.Equal (new Rect (0, 0, 38, 1), label._needsDisplayRect); }; button.LayoutComplete += (s, e) => { - Assert.Equal (new Rect (0, 0, 13, 1), button._needsDisplay); + Assert.Equal (new Rect (0, 0, 13, 1), button._needsDisplayRect); }; Assert.True (label.AutoSize); diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs index ff011910f4..d96d869ccd 100644 --- a/UnitTests/Views/ListViewTests.cs +++ b/UnitTests/Views/ListViewTests.cs @@ -240,7 +240,7 @@ string GetContents (int line) { var item = ""; for (int i = 0; i < 7; i++) { - item += (char)Application.Driver.Contents [line, i, 0]; + item += Application.Driver.Contents [line, i].Runes[0]; } return item; } diff --git a/UnitTests/Views/ProgressBarTests.cs b/UnitTests/Views/ProgressBarTests.cs index 841ffafac8..e04d4566b8 100644 --- a/UnitTests/Views/ProgressBarTests.cs +++ b/UnitTests/Views/ProgressBarTests.cs @@ -178,613 +178,613 @@ public void Pulse_Redraw_BidirectionalMarquee_True_Default () pb.Pulse (); pb.Draw (); if (i == 0) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 1) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 2) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 3) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 4) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 5) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 6) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 7) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 8) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 9) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 10) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 11) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 12) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 13) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 14) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 15) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 16) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 17) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 18) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 19) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 20) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 21) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 22) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 23) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 24) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 25) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 26) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 27) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 28) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 29) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 30) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 31) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 32) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 33) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 34) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 35) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 36) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 37) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } } } @@ -808,613 +808,613 @@ public void Pulse_Redraw_BidirectionalMarquee_False () pb.Pulse (); pb.Draw (); if (i == 0) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 1) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 2) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 3) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 4) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 5) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 6) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 7) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 8) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 9) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 10) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 11) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 12) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 13) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 14) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 15) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 16) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 17) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 18) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 19) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 20) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 21) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 22) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 23) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 24) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 25) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 26) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 27) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 28) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 29) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 30) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 31) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 32) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 14].Runes[0]); } else if (i == 33) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 34) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 35) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 36) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } else if (i == 37) { - Assert.Equal (' ', (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 6, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 7, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 8, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 9, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 10, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 11, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 12, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 13, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 14, 0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 6].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 7].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 8].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 9].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 10].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 11].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 12].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 13].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 14].Runes[0]); } } } @@ -1436,47 +1436,47 @@ public void Fraction_Redraw () pb.Fraction += 0.2F; pb.Draw (); if (i == 0) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); } else if (i == 1) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); } else if (i == 2) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); } else if (i == 3) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); } else if (i == 4) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); } else if (i == 5) { - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 0, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 1, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 2, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 3, 0]); - Assert.Equal (CM.Glyphs.BlocksMeterSegment.Value, (double)driver.Contents [0, 4, 0]); - Assert.Equal (' ', (double)driver.Contents [0, 5, 0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 0].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 1].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 2].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 3].Runes[0]); + Assert.Equal (CM.Glyphs.BlocksMeterSegment, driver.Contents [0, 4].Runes[0]); + Assert.Equal ((Rune)' ', driver.Contents [0, 5].Runes[0]); } } } diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs index 38c021609e..571a90d6ad 100644 --- a/UnitTests/Views/TabViewTests.cs +++ b/UnitTests/Views/TabViewTests.cs @@ -25,10 +25,13 @@ private TabView GetTabView () private TabView GetTabView (out Tab tab1, out Tab tab2, bool initFakeDriver = true) { - if (initFakeDriver) + if (initFakeDriver) { InitFakeDriver (); + } var tv = new TabView (); + tv.BeginInit (); + tv.EndInit (); tv.ColorScheme = new ColorScheme (); tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi")), false); tv.AddTab (tab2 = new Tab ("Tab2", new Label ("hi2")), false); diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index d179adef69..f5f8f3b763 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -1901,7 +1901,7 @@ public void LongColumnTest () // Now test making the width too small for the MinAcceptableWidth // the Column won't fit so should not be rendered var driver = ((FakeDriver)Application.Driver); - driver.UpdateOffScreen (); + driver.ClearContents (); tableView.Bounds = new Rect (0, 0, 9, 5); diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index 2dba686584..5a4582dc1d 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -1137,7 +1137,7 @@ string GetContents () { var item = ""; for (int i = 0; i < 16; i++) { - item += (char)Application.Driver.Contents [0, i, 0]; + item += Application.Driver.Contents [0, i].Runes [0]; } return item; } diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 689fd122ae..b33de39750 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -1012,7 +1012,7 @@ public void IsLoaded_With_Sub_Toplevel_Application_Begin_NeedDisplay () void view_LayoutStarted (object sender, LayoutEventArgs e) { - Assert.Equal (new Rect (0, 0, 20, 10), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 20, 10), view._needsDisplayRect); view.LayoutStarted -= view_LayoutStarted; } @@ -1024,12 +1024,12 @@ void view_LayoutStarted (object sender, LayoutEventArgs e) view.Frame = new Rect (1, 3, 10, 5); Assert.Equal (new Rect (1, 3, 10, 5), view.Frame); - Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplayRect); view.OnDrawContent (view.Bounds); view.Frame = new Rect (1, 3, 10, 5); Assert.Equal (new Rect (1, 3, 10, 5), view.Frame); - Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplay); + Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplayRect); } // BUGBUG: Broke this test with #2483 - @bdisp I need your help figuring out why @@ -1284,7 +1284,7 @@ public void Dialog_Bounds_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_Lef } [Fact, AutoInitShutdown] - public void Single_Smaller_Top_Will_Have_Cleaning_Trails_Chunk_On_Move () + public void Modal_As_Top_Will_Drag_Cleanly () { var dialog = new Dialog () { Width = 30, Height = 10 }; dialog.Add (new Label ( diff --git a/UnitTests/Views/TreeViewTests.cs b/UnitTests/Views/TreeViewTests.cs index cf6f5e2122..991f63fe52 100644 --- a/UnitTests/Views/TreeViewTests.cs +++ b/UnitTests/Views/TreeViewTests.cs @@ -848,13 +848,15 @@ public void TestTreeViewColor () tv.AddObject (n2); tv.Expand (n1); - var pink = new Attribute (Color.Magenta, Color.Black); - var hotpink = new Attribute (Color.BrightMagenta, Color.Black); - tv.ColorScheme = new ColorScheme (); tv.LayoutSubviews (); tv.Draw (); + // create a new color scheme + var pink = new Attribute (Color.Magenta, Color.Black); + var hotpink = new Attribute (Color.BrightMagenta, Color.Black); + + // Normal drawing of the tree view TestHelpers.AssertDriverContentsAre ( @"├-normal @@ -871,7 +873,6 @@ public void TestTreeViewColor () ", new [] { tv.ColorScheme.Normal, pink }); - // create a new color scheme var pinkScheme = new ColorScheme { Normal = pink, Focus = hotpink diff --git a/UnitTests/xunit.runner.json b/UnitTests/xunit.runner.json index e810a97254..c096d4186e 100644 --- a/UnitTests/xunit.runner.json +++ b/UnitTests/xunit.runner.json @@ -1,5 +1,6 @@ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "parallelizeTestCollections": false, - "parallelizeAssembly": false -} + "parallelizeAssembly": false, + "stopOnFail": false +} \ No newline at end of file