From 91f47a20664fe6b46abbf7c82499b19cb6027c3e Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 4 Jan 2024 22:35:18 -0700 Subject: [PATCH] Robustness improvements in prep for implementing Virtual Terminal Sequences (#3094) * Fixes #2616. Support combining sequences that don't normalize * Decouples Application from ConsoleDriver in TestHelpers * Updates driver tests to match new arch * Start on making all driver tests test all drivers * Improves handling if combining marks. * Fix unit tests fails. * Fix unit tests fails. * Handling combining mask. * Tying to fix this unit test that sometimes fail. * Add support for combining mask on NetDriver. * Enable CombiningMarks as List. * Prevents combining marks on invalid runes default and space. * Formatting for CI tests. * Fix non-normalized combining mark to add 1 to Col. * Reformatting for retest the CI. * Forces non-normalized CMs to be ignored. * Initial experiment * Created ANSiDriver. Updated UI Catalog command line handling * Fixed ForceDriver logic * Fixed ForceDriver logic * Updating P/Invoke * Force16 colors WIP * Fixed 16 colo mode * Updated unit tests * UI catalog tweak * Added chinese scenario from bdisp * Disabled AnsiDriver unit tests for now. * Code cleanup * Initial commit (fork from v2_fixes_2610_WT_VTS) * Code cleanup * Removed nativemethods.txt * Removed not needed native stuff * Code cleanup * Ensures command line handler doesn't eat exceptions --------- Co-authored-by: BDisp --- Terminal.Gui/Application.cs | 99 +- .../Configuration/ConfigurationManager.cs | 6 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 32 +- .../ConsoleDrivers/ConsoleKeyMapping.cs | 4 + .../CursesDriver/CursesDriver.cs | 10 +- .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 24 +- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 7 +- Terminal.Gui/Drawing/Color.cs | 418 ++-- Terminal.Gui/Views/Toplevel.cs | 1685 +++++++++-------- UICatalog/Properties/launchSettings.json | 19 +- UICatalog/Scenarios/CharacterMap.cs | 13 +- UICatalog/Scenarios/ChineseUI.cs | 53 + UICatalog/UICatalog.cs | 194 +- UICatalog/UICatalog.csproj | 1 + UnitTests/Application/ApplicationTests.cs | 64 +- UnitTests/ConsoleDrivers/AddRuneTests.cs | 22 +- UnitTests/ConsoleDrivers/ClipRegionTests.cs | 225 ++- .../ConsoleDrivers/ConsoleDriverTests.cs | 501 ++--- .../ConsoleDrivers/ConsoleScrolllingTests.cs | 58 +- UnitTests/ConsoleDrivers/ContentsTests.cs | 21 +- UnitTests/ConsoleDrivers/DriverColorTests.cs | 128 +- .../ConsoleDrivers/MainLoopDriverTests.cs | 51 +- 22 files changed, 1941 insertions(+), 1694 deletions(-) create mode 100644 UICatalog/Scenarios/ChineseUI.cs diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 1ec014d5f3..ed140fed6d 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -33,15 +33,20 @@ namespace Terminal.Gui; /// public static partial class Application { /// - /// Gets the that has been selected. See also . + /// Gets the that has been selected. See also . /// public static ConsoleDriver Driver { get; internal set; } /// - /// If , forces the use of the System.Console-based (see ) driver. The default is . + /// Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If + /// not specified, the driver is selected based on the platform. /// + /// + /// Note, will override this configuration setting if + /// called with either `driver` or `driverName` specified. + /// [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool UseSystemConsole { get; set; } = false; + public static string ForceDriver { get; set; } = string.Empty; /// /// Gets or sets whether will be forced to output only the 16 colors defined in . @@ -98,14 +103,13 @@ static List GetSupportedCultures () /// /// /// The function - /// combines and + /// combines and /// into a single call. An application cam use - /// without explicitly calling . + /// without explicitly calling . /// - /// - /// The to use. If not specified the default driver for the - /// platform will be used (see , , and ). - public static void Init (ConsoleDriver driver = null) => InternalInit (() => Toplevel.Create (), driver); + /// The to use. If neither or are specified the default driver for the platform will be used. + /// The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the to use. If neither or are specified the default driver for the platform will be used. + public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (Toplevel.Create, driver, driverName); internal static bool _initialized = false; internal static int _mainThreadId = -1; @@ -119,7 +123,7 @@ static List GetSupportedCultures () // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset. // // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset. - internal static void InternalInit (Func topLevelFactory, ConsoleDriver driver = null, bool calledViaRunT = false) + internal static void InternalInit (Func topLevelFactory, ConsoleDriver driver = null, string driverName = null, bool calledViaRunT = false) { if (_initialized && driver == null) { return; @@ -147,15 +151,28 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver Load (true); Apply (); - Driver ??= Environment.OSVersion.Platform switch { - _ when _forceFakeConsole => new FakeDriver (), // for unit testing only - _ when UseSystemConsole => new NetDriver (), - PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows => new WindowsDriver (), - _ => new CursesDriver () - }; + // Ignore Configuration for ForceDriver if driverName is specified + if (!string.IsNullOrEmpty (driverName)) { + ForceDriver = driverName; + } if (Driver == null) { - throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use."); + var p = Environment.OSVersion.Platform; + if (string.IsNullOrEmpty (ForceDriver)) { + if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { + Driver = new WindowsDriver (); + } else { + Driver = new CursesDriver (); + } + } else { + var drivers = GetDriverTypes (); + var driverType = drivers.FirstOrDefault (t => t.Name.ToLower () == ForceDriver.ToLower ()); + if (driverType != null) { + Driver = (ConsoleDriver)Activator.CreateInstance (driverType); + } else { + throw new ArgumentException ($"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}"); + } + } } try { @@ -168,10 +185,10 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex); } - Driver.SizeChanged += Driver_SizeChanged; - Driver.KeyDown += Driver_KeyDown; - Driver.KeyUp += Driver_KeyUp; - Driver.MouseEvent += Driver_MouseEvent; + Driver.SizeChanged += (s, args) => OnSizeChanging (args); + Driver.KeyDown += (s, args) => OnKeyDown (args); + Driver.KeyUp += (s, args) => OnKeyUp (args); + Driver.MouseEvent += (s, args) => OnMouseEvent (args); SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); @@ -190,12 +207,29 @@ internal static void InternalInit (Func topLevelFactory, ConsoleDriver static void Driver_MouseEvent (object sender, MouseEventEventArgs e) => OnMouseEvent (e); + /// + /// Gets of list of types that are available. + /// + /// + public static List GetDriverTypes () + { + // use reflection to get the list of drivers + var driverTypes = new List (); + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies ()) { + foreach (var type in asm.GetTypes ()) { + if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) { + driverTypes.Add (type); + } + } + } + return driverTypes; + } /// - /// Shutdown an application initialized with . + /// Shutdown an application initialized with . /// /// - /// Shutdown must be called for every call to or + /// Shutdown must be called for every call to or /// to ensure all resources are cleaned up (Disposed) and terminal settings are restored. /// public static void Shutdown () @@ -394,7 +428,7 @@ public static RunState Begin (Toplevel Toplevel) /// Runs the application by calling /// with a new instance of the specified -derived class. /// - /// Calling first is not needed as this function will initialize the application. + /// Calling first is not needed as this function will initialize the application. /// /// /// must be called when the application is closing (typically after Run> has @@ -407,7 +441,7 @@ public static RunState Begin (Toplevel Toplevel) /// /// The to use. If not specified the default driver for the /// platform will be used (, , or ). - /// Must be if has already been called. + /// Must be if has already been called. /// public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new () { @@ -429,7 +463,7 @@ public static void Run (Func errorHandler = null, ConsoleDri } } else { // Init() has NOT been called. - InternalInit (() => new T (), driver, true); + InternalInit (() => new T (), driver, null, true); Run (Top, errorHandler); } } @@ -838,13 +872,12 @@ public static void End (RunState runState) #endregion Run (Begin, Run, End) #region Toplevel handling - /// /// Holds the stack of TopLevel views. /// // BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What // about TopLevels that are just a SubView of another View? - static readonly Stack _topLevels = new (); + static readonly Stack _topLevels = new Stack (); /// /// The object used for the application on startup () @@ -1296,7 +1329,7 @@ bool FrameHandledMouseEvent (Frame frame) #endregion Mouse handling #region Keyboard handling - static Key _alternateForwardKey = new (KeyCode.PageDown | KeyCode.CtrlMask); + static Key _alternateForwardKey = new Key (KeyCode.PageDown | KeyCode.CtrlMask); /// /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. @@ -1320,7 +1353,7 @@ static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) } } - static Key _alternateBackwardKey = new (KeyCode.PageUp | KeyCode.CtrlMask); + static Key _alternateBackwardKey = new Key (KeyCode.PageUp | KeyCode.CtrlMask); /// /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. @@ -1344,7 +1377,7 @@ static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) } } - static Key _quitKey = new (KeyCode.Q | KeyCode.CtrlMask); + static Key _quitKey = new Key (KeyCode.Q | KeyCode.CtrlMask); /// /// Gets or sets the key to quit the application. @@ -1481,8 +1514,8 @@ public static bool OnKeyUp (Key a) } #endregion Keyboard handling } - /// /// Event arguments for the event. /// -public class IterationEventArgs { } \ No newline at end of file +public class IterationEventArgs { +} \ No newline at end of file diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs index 75b024612f..9510848111 100644 --- a/Terminal.Gui/Configuration/ConfigurationManager.cs +++ b/Terminal.Gui/Configuration/ConfigurationManager.cs @@ -257,7 +257,7 @@ public static void OnUpdated () /// /// Resets the state of . Should be called whenever a new app session - /// (e.g. in starts. Called by + /// (e.g. in starts. Called by /// if the reset parameter is . /// /// @@ -412,7 +412,9 @@ public static void Load (bool reset = false) { Debug.WriteLine ($"ConfigurationManager.Load()"); - if (reset) Reset (); + if (reset) { + Reset (); + } // LibraryResources is always loaded by Reset if (Locations == ConfigLocations.All) { diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 5e67ebb517..4bd0143e16 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -58,12 +58,24 @@ public abstract class ConsoleDriver { /// /// The number of columns visible in the terminal. /// - public virtual int Cols { get; internal set; } + public virtual int Cols { + get => _cols; + internal set { + _cols = value; + ClearContents(); + } + } /// /// The number of rows visible in the terminal. /// - public virtual int Rows { get; internal set; } + public virtual int Rows { + get => _rows; + internal set { + _rows = value; + ClearContents(); + } + } /// /// The leftmost column in the terminal. @@ -152,11 +164,19 @@ public void AddRune (Rune rune) rune = rune.MakePrintable (); runeWidth = rune.GetColumns (); if (runeWidth == 0 && rune.IsCombiningMark ()) { + // AtlasEngine does not support NON-NORMALIZED combining marks in a way + // compatible with the driver architecture. Any CMs (except in the first col) + // are correctly combined with the base char, but are ALSO treated as 1 column + // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[Γ© ]`. + // + // Until this is addressed (see Issue #), we do our best by + // a) Attempting to normalize any CM with the base char to it's left + // b) Ignoring any CMs that don't normalize if (Col > 0) { if (Contents [Row, Col - 1].CombiningMarks.Count > 0) { // Just add this mark to the list Contents [Row, Col - 1].CombiningMarks.Add (rune); - // Don't move to next column (let the driver figure out what to do). + // Ignore. Don't move to next column (let the driver figure out what to do). } else { // Attempt to normalize the cell to our left combined with this mark string combined = Contents [Row, Col - 1].Rune + rune.ToString (); @@ -167,11 +187,11 @@ public void AddRune (Rune rune) // It normalized! We can just set the Cell to the left with the // normalized codepoint Contents [Row, Col - 1].Rune = (Rune)normalized [0]; - // Don't move to next column because we're already there + // Ignore. Don't move to next column because we're already there } else { // It didn't normalize. Add it to the Cell to left's CM list Contents [Row, Col - 1].CombiningMarks.Add (rune); - // Don't move to next column (let the driver figure out what to do). + // Ignore. Don't move to next column (let the driver figure out what to do). } } Contents [Row, Col - 1].Attribute = CurrentAttribute; @@ -398,6 +418,8 @@ public void ClearContents () } Attribute _currentAttribute; + int _cols; + int _rows; /// /// The that will be used for the next or call. diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index f18420c424..648836f976 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -90,6 +90,10 @@ public static uint MapVKtoChar (VK vk) [DllImport ("user32.dll")] extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID); + /// + /// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread. + /// + /// public static string GetKeyboardLayoutName () { if (Environment.OSVersion.Platform != PlatformID.Win32NT) { diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index c0b3db7daa..3f251b8311 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -17,12 +17,18 @@ namespace Terminal.Gui; class CursesDriver : ConsoleDriver { public override int Cols { get => Curses.Cols; - internal set => Curses.Cols = value; + internal set { + Curses.Cols = value; + ClearContents(); + } } public override int Rows { get => Curses.Lines; - internal set => Curses.Lines = value; + internal set { + Curses.Lines = value; + ClearContents(); + } } CursorVisibility? _initialCursorVisibility = null; diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 7468832c15..79ce0ffe65 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -170,10 +170,10 @@ public enum ClearScreenOptions { /// /// 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 /// - /// - /// + /// Origin is (1,1). + /// Origin is (1,1). /// - public static string CSI_SetCursorPosition (int y, int x) => $"{CSI}{y};{x}H"; + public static string CSI_SetCursorPosition (int row, int col) => $"{CSI}{row};{col}H"; //ESC [ ; f - HVP Horizontal Vertical Position* Cursor moves to; coordinate within the viewport, where is the column of the line @@ -248,15 +248,29 @@ public enum DECSCUSR_Style { /// public static string CSI_SetGraphicsRendition (params int [] parameters) => $"{CSI}{string.Join (";", parameters)}m"; + /// + /// ESC [ (n) m - Uses to set the foreground color. + /// + /// One of the 16 color codes. + /// + public static string CSI_SetForegroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code); + + /// + /// ESC [ (n) m - Uses to set the background color. + /// + /// One of the 16 color codes. + /// + public static string CSI_SetBackgroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code+10); + /// /// ESC[38;5;{id}m - Set foreground color (256 colors) /// - public static string CSI_SetForegroundColor (int id) => $"{CSI}38;5;{id}m"; + public static string CSI_SetForegroundColor256 (int color) => $"{CSI}38;5;{color}m"; /// /// ESC[48;5;{id}m - Set background color (256 colors) /// - public static string CSI_SetBackgroundColor (int id) => $"{CSI}48;5;{id}m"; + public static string CSI_SetBackgroundColor256 (int color) => $"{CSI}48;5;{color}m"; /// /// ESC[38;2;{r};{g};{b}m Set foreground color as RGB. diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 58eae6264f..4ed7eb952e 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -837,9 +837,8 @@ public override void UpdateScreen () } else if (lastCol == -1) { lastCol = col; } - if (lastCol + 1 < cols) { + if (lastCol + 1 < cols) lastCol++; - } continue; } @@ -1167,10 +1166,6 @@ KeyCode MapKey (ConsoleKeyInfo keyInfo) // and passing on Shift would be redundant. return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar); } - break; - - - return (KeyCode)(uint)keyInfo.KeyChar; } var key = keyInfo.Key; diff --git a/Terminal.Gui/Drawing/Color.cs b/Terminal.Gui/Drawing/Color.cs index 3d2f5d35b5..bcab0b27f0 100644 --- a/Terminal.Gui/Drawing/Color.cs +++ b/Terminal.Gui/Drawing/Color.cs @@ -9,6 +9,7 @@ using System.Text.RegularExpressions; namespace Terminal.Gui; + /// /// Defines the 16 legacy color names and values that can be used to set the /// foreground and background colors in Terminal.Gui apps. Used with . @@ -88,14 +89,82 @@ public enum ColorName { /// White } +/// +/// The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for background color. +/// +public enum AnsiColorCode { + /// + /// The ANSI color code for Black. + /// + BLACK = 30, + /// + /// The ANSI color code for Red. + /// + RED = 31, + /// + /// The ANSI color code for Green. + /// + GREEN = 32, + /// + /// The ANSI color code for Yellow. + /// + YELLOW = 33, + /// + /// The ANSI color code for Blue. + /// + BLUE = 34, + /// + /// The ANSI color code for Magenta. + /// + MAGENTA = 35, + /// + /// The ANSI color code for Cyan. + /// + CYAN = 36, + /// + /// The ANSI color code for White. + /// + WHITE = 37, + /// + /// The ANSI color code for Bright Black. + /// + BRIGHT_BLACK = 90, + /// + /// The ANSI color code for Bright Red. + /// + BRIGHT_RED = 91, + /// + /// The ANSI color code for Bright Green. + /// + BRIGHT_GREEN = 92, + /// + /// The ANSI color code for Bright Yellow. + /// + BRIGHT_YELLOW = 93, + /// + /// The ANSI color code for Bright Blue. + /// + BRIGHT_BLUE = 94, + /// + /// The ANSI color code for Bright Magenta. + /// + BRIGHT_MAGENTA = 95, + /// + /// The ANSI color code for Bright Cyan. + /// + BRIGHT_CYAN = 96, + /// + /// The ANSI color code for Bright White. + /// + BRIGHT_WHITE = 97 +} /// /// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see ). /// Used with . /// [JsonConverter (typeof (ColorJsonConverter))] public class Color : IEquatable { - /// /// Initializes a new instance of the class. /// @@ -115,10 +184,7 @@ public Color (int red, int green, int blue, int alpha = 0xFF) /// Initializes a new instance of the class with an encoded 24-bit color value. /// /// The encoded 24-bit color value (see ). - public Color (int rgba) - { - Rgba = rgba; - } + public Color (int rgba) => Rgba = rgba; /// /// Initializes a new instance of the color from a legacy 16-color value. @@ -126,7 +192,7 @@ public Color (int rgba) /// The 16-color value. public Color (ColorName colorName) { - var c = Color.FromColorName (colorName); + var c = FromColorName (colorName); R = c.R; G = c.G; B = c.B; @@ -188,11 +254,11 @@ public Color () /// /// public int Rgba { - get => (A << 24) | (R << 16) | (G << 8) | B; + get => A << 24 | R << 16 | G << 8 | B; set { - A = (byte)((value >> 24) & 0xFF); - R = (byte)((value >> 16) & 0xFF); - G = (byte)((value >> 8) & 0xFF); + A = (byte)(value >> 24 & 0xFF); + R = (byte)(value >> 16 & 0xFF); + G = (byte)(value >> 8 & 0xFF); B = (byte)(value & 0xFF); } } @@ -203,39 +269,61 @@ public Color () /// Maps legacy 16-color values to the corresponding 24-bit RGB value. /// internal static ImmutableDictionary _colorToNameMap = new Dictionary () { - // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png - // See also: https://en.wikipedia.org/wiki/ANSI_escape_code - { new Color (12, 12, 12),Gui.ColorName.Black }, - { new Color (0, 55, 218),Gui.ColorName.Blue }, - { new Color (19, 161, 14),Gui.ColorName.Green}, - { new Color (58, 150, 221),Gui.ColorName.Cyan}, - { new Color (197, 15, 31),Gui.ColorName.Red}, - { new Color (136, 23, 152),Gui.ColorName.Magenta}, - { new Color (128, 64, 32),Gui.ColorName.Yellow}, - { new Color (204, 204, 204),Gui.ColorName.Gray}, - { new Color (118, 118, 118),Gui.ColorName.DarkGray}, - { new Color (59, 120, 255),Gui.ColorName.BrightBlue}, - { new Color (22, 198, 12),Gui.ColorName.BrightGreen}, - { new Color (97, 214, 214),Gui.ColorName.BrightCyan}, - { new Color (231, 72, 86),Gui.ColorName.BrightRed}, - { new Color (180, 0, 158),Gui.ColorName.BrightMagenta }, - { new Color (249, 241, 165),Gui.ColorName.BrightYellow}, - { new Color (242, 242, 242),Gui.ColorName.White}, - }.ToImmutableDictionary (); + // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png + // See also: https://en.wikipedia.org/wiki/ANSI_escape_code + { new Color (12, 12, 12), ColorName.Black }, + { new Color (0, 55, 218), ColorName.Blue }, + { new Color (19, 161, 14), ColorName.Green }, + { new Color (58, 150, 221), ColorName.Cyan }, + { new Color (197, 15, 31), ColorName.Red }, + { new Color (136, 23, 152), ColorName.Magenta }, + { new Color (128, 64, 32), ColorName.Yellow }, + { new Color (204, 204, 204), ColorName.Gray }, + { new Color (118, 118, 118), ColorName.DarkGray }, + { new Color (59, 120, 255), ColorName.BrightBlue }, + { new Color (22, 198, 12), ColorName.BrightGreen }, + { new Color (97, 214, 214), ColorName.BrightCyan }, + { new Color (231, 72, 86), ColorName.BrightRed }, + { new Color (180, 0, 158), ColorName.BrightMagenta }, + { new Color (249, 241, 165), ColorName.BrightYellow }, + { new Color (242, 242, 242), ColorName.White } + }.ToImmutableDictionary (); + + + /// + /// Defines the 16 legacy color names and values that can be used to set the + /// + internal static ImmutableDictionary _colorNameToAnsiColorMap = new Dictionary { + { ColorName.Black, AnsiColorCode.BLACK }, + { ColorName.Blue, AnsiColorCode.BLUE }, + { ColorName.Green, AnsiColorCode.GREEN }, + { ColorName.Cyan, AnsiColorCode.CYAN }, + { ColorName.Red, AnsiColorCode.RED }, + { ColorName.Magenta, AnsiColorCode.MAGENTA }, + { ColorName.Yellow, AnsiColorCode.YELLOW }, + { ColorName.Gray, AnsiColorCode.WHITE }, + { ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK }, + { ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE }, + { ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN }, + { ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN }, + { ColorName.BrightRed, AnsiColorCode.BRIGHT_RED }, + { ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA }, + { ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW }, + { ColorName.White, AnsiColorCode.BRIGHT_WHITE } + }.ToImmutableDictionary (); /// /// Gets or sets the 24-bit color value for each of the legacy 16-color values. /// [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] public static Dictionary Colors { - get { + get => // Transform _colorToNameMap into a Dictionary - return _colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}"); - } + _colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}"); set { // Transform Dictionary into _colorToNameMap var newMap = value.ToDictionary (kvp => new Color (kvp.Value), kvp => { - if (Enum.TryParse (kvp.Key.ToString (), ignoreCase: true, out ColorName colorName)) { + if (Enum.TryParse (kvp.Key.ToString (), true, out var colorName)) { return colorName; } throw new ArgumentException ($"Invalid color name: {kvp.Key}"); @@ -247,9 +335,9 @@ public Color () /// /// Converts a legacy to a 24-bit . /// - /// The to convert. + /// The to convert. /// - private static Color FromColorName (ColorName consoleColor) => _colorToNameMap.FirstOrDefault (x => x.Value == consoleColor).Key; + static Color FromColorName (ColorName colorName) => _colorToNameMap.FirstOrDefault (x => x.Value == colorName).Key; // Iterates through the entries in the _colorNames dictionary, calculates the // Euclidean distance between the input color and each dictionary color in RGB space, @@ -257,11 +345,11 @@ public Color () // representing the closest color entry and its associated color name. internal static ColorName FindClosestColor (Color inputColor) { - ColorName closestColor = Gui.ColorName.Black; // Default to Black + var closestColor = ColorName.Black; // Default to Black double closestDistance = double.MaxValue; foreach (var colorEntry in _colorToNameMap) { - var distance = CalculateColorDistance (inputColor, colorEntry.Key); + double distance = CalculateColorDistance (inputColor, colorEntry.Key); if (distance < closestDistance) { closestDistance = distance; closestColor = colorEntry.Value; @@ -271,12 +359,12 @@ internal static ColorName FindClosestColor (Color inputColor) return closestColor; } - private static double CalculateColorDistance (Color color1, Color color2) + static double CalculateColorDistance (Color color1, Color color2) { // Calculate the Euclidean distance between two colors - var deltaR = (double)color1.R - (double)color2.R; - var deltaG = (double)color1.G - (double)color2.G; - var deltaB = (double)color1.B - (double)color2.B; + double deltaR = (double)color1.R - (double)color2.R; + double deltaG = (double)color1.G - (double)color2.G; + double deltaB = (double)color1.B - (double)color2.B; return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB); } @@ -300,6 +388,16 @@ private static double CalculateColorDistance (Color color1, Color color2) } } + /// + /// Gets or sets the using a legacy 16-color value. + /// will return the closest 16 color match to the true color when no exact value is found. + /// + /// + /// Get returns the of the closest 24-bit color value. Set sets the RGB value using a hard-coded map. + /// + [JsonIgnore] + public AnsiColorCode AnsiColorCode => _colorNameToAnsiColorMap [ColorName]; + #region Legacy Color Names /// /// The black color. @@ -382,49 +480,49 @@ private static double CalculateColorDistance (Color color1, Color color2) public static bool TryParse (string text, [NotNullWhen (true)] out Color color) { // empty color - if ((text == null) || (text.Length == 0)) { + if (text == null || text.Length == 0) { color = null; return false; } // #RRGGBB, #RGB - if ((text [0] == '#') && text.Length is 7 or 4) { + if (text [0] == '#' && text.Length is 7 or 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); + int r = Convert.ToInt32 (text.Substring (1, 2), 16); + int g = Convert.ToInt32 (text.Substring (3, 2), 16); + int b = Convert.ToInt32 (text.Substring (5, 2), 16); color = new Color (r, g, b); } else { - var rText = char.ToString (text [1]); - var gText = char.ToString (text [2]); - var bText = char.ToString (text [3]); + string rText = char.ToString (text [1]); + string gText = char.ToString (text [2]); + string 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); + int r = Convert.ToInt32 (rText + rText, 16); + int g = Convert.ToInt32 (gText + gText, 16); + int b = Convert.ToInt32 (bText + bText, 16); color = new Color (r, g, b); } return true; } // #RRGGBB, #RGBA - if ((text [0] == '#') && text.Length is 8 or 5) { + if (text [0] == '#' && text.Length is 8 or 5) { 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); - var a = Convert.ToInt32 (text.Substring (7, 2), 16); + int r = Convert.ToInt32 (text.Substring (1, 2), 16); + int g = Convert.ToInt32 (text.Substring (3, 2), 16); + int b = Convert.ToInt32 (text.Substring (5, 2), 16); + int a = Convert.ToInt32 (text.Substring (7, 2), 16); color = new Color (a, r, g, b); } else { - var rText = char.ToString (text [1]); - var gText = char.ToString (text [2]); - var bText = char.ToString (text [3]); - var aText = char.ToString (text [4]); - - var r = Convert.ToInt32 (aText + aText, 16); - var g = Convert.ToInt32 (rText + rText, 16); - var b = Convert.ToInt32 (gText + gText, 16); - var a = Convert.ToInt32 (bText + bText, 16); + string rText = char.ToString (text [1]); + string gText = char.ToString (text [2]); + string bText = char.ToString (text [3]); + string aText = char.ToString (text [4]); + + int r = Convert.ToInt32 (aText + aText, 16); + int g = Convert.ToInt32 (rText + rText, 16); + int b = Convert.ToInt32 (gText + gText, 16); + int a = Convert.ToInt32 (bText + bText, 16); color = new Color (r, g, b, a); } return true; @@ -433,9 +531,9 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) // rgb(r,g,b) 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); + int r = int.Parse (match.Groups [1].Value); + int g = int.Parse (match.Groups [2].Value); + int b = int.Parse (match.Groups [3].Value); color = new Color (r, g, b); return true; } @@ -443,15 +541,15 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) // rgb(r,g,b,a) match = Regex.Match (text, @"rgb\((\d+),(\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); - var a = int.Parse (match.Groups [4].Value); + int r = int.Parse (match.Groups [1].Value); + int g = int.Parse (match.Groups [2].Value); + int b = int.Parse (match.Groups [3].Value); + int a = int.Parse (match.Groups [4].Value); color = new Color (r, g, b, a); return true; } - if (Enum.TryParse (text, ignoreCase: true, out ColorName colorName)) { + if (Enum.TryParse (text, true, out var colorName)) { color = new Color (colorName); return true; } @@ -465,37 +563,25 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) /// Cast from int. /// /// - public static implicit operator Color (int rgba) - { - return new Color (rgba); - } + public static implicit operator Color (int rgba) => new Color (rgba); /// /// Cast to int. /// /// - public static explicit operator int (Color color) - { - return color.Rgba; - } + public static explicit operator int (Color color) => color.Rgba; /// /// Cast from . /// /// - public static explicit operator Color (ColorName colorName) - { - return new Color (colorName); - } + public static explicit operator Color (ColorName colorName) => new Color (colorName); /// /// Cast to . /// /// - public static explicit operator ColorName (Color color) - { - return color.ColorName; - } + public static explicit operator ColorName (Color color) => color.ColorName; /// @@ -506,11 +592,13 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) /// public static bool operator == (Color left, Color right) { - if (left is null && right is null) + if (left is null && right is null) { return true; + } - if (left is null || right is null) + if (left is null || right is null) { return false; + } return left.Equals (right); } @@ -524,11 +612,13 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) /// public static bool operator != (Color left, Color right) { - if (left is null && right is null) + if (left is null && right is null) { return false; + } - if (left is null || right is null) + if (left is null || right is null) { return true; + } return !left.Equals (right); } @@ -539,10 +629,7 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) /// /// /// - public static bool operator == (ColorName left, Color right) - { - return left == right.ColorName; - } + public static bool operator == (ColorName left, Color right) => left == right.ColorName; /// /// Inequality operator for and objects. @@ -550,10 +637,7 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) /// /// /// - public static bool operator != (ColorName left, Color right) - { - return left != right.ColorName; - } + public static bool operator != (ColorName left, Color right) => left != right.ColorName; /// /// Equality operator for and objects. @@ -561,10 +645,7 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) /// /// /// - public static bool operator == (Color left, ColorName right) - { - return left.ColorName == right; - } + public static bool operator == (Color left, ColorName right) => left.ColorName == right; /// /// Inequality operator for and objects. @@ -572,33 +653,20 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color) /// /// /// - public static bool operator != (Color left, ColorName right) - { - return left.ColorName != right; - } + public static bool operator != (Color left, ColorName right) => left.ColorName != right; /// - public override bool Equals (object obj) - { - return obj is Color other && Equals (other); - } + public override bool Equals (object obj) => obj is Color other && Equals (other); /// - public bool Equals (Color other) - { - return - R == other.R && - G == other.G && - B == other.B && - A == other.A; - } + public bool Equals (Color other) => R == other.R && + G == other.G && + B == other.B && + A == other.A; /// - public override int GetHashCode () - { - return HashCode.Combine (R, G, B, A); - } + public override int GetHashCode () => HashCode.Combine (R, G, B, A); #endregion /// @@ -616,14 +684,13 @@ public override int GetHashCode () public override string ToString () { // If Values has an exact match with a named color (in _colorNames), use that. - if (_colorToNameMap.TryGetValue (this, out ColorName colorName)) { + if (_colorToNameMap.TryGetValue (this, out var colorName)) { return Enum.GetName (typeof (ColorName), colorName); } // Otherwise return as an RGB hex value. return $"#{R:X2}{G:X2}{B:X2}"; } } - /// /// Attributes represent how text is styled when displayed in the terminal. /// @@ -634,7 +701,6 @@ public override string ToString () /// [JsonConverter (typeof (AttributeJsonConverter))] public readonly struct Attribute : IEquatable { - /// /// Default empty attribute. /// @@ -665,19 +731,20 @@ public Attribute () { PlatformColor = -1; var d = Default; - Foreground = new (d.Foreground.ColorName); - Background = new (d.Background.ColorName); + Foreground = new Color (d.Foreground.ColorName); + Background = new Color (d.Background.ColorName); } /// /// Initializes a new instance with platform specific color value. /// /// Value. - internal Attribute (int platformColor) { + internal Attribute (int platformColor) + { PlatformColor = platformColor; var d = Default; - Foreground = new (d.Foreground.ColorName); - Background = new (d.Background.ColorName); + Foreground = new Color (d.Foreground.ColorName); + Background = new Color (d.Background.ColorName); } /// @@ -775,30 +842,21 @@ public Attribute (Color foreground, Color background) public static bool operator != (Attribute left, Attribute right) => !(left == right); /// - public override bool Equals (object obj) - { - return obj is Attribute other && Equals (other); - } + public override bool Equals (object obj) => obj is Attribute other && Equals (other); /// - public bool Equals (Attribute other) - { - return PlatformColor == other.PlatformColor && - Foreground == other.Foreground && - Background == other.Background; - } + public bool Equals (Attribute other) => PlatformColor == other.PlatformColor && + Foreground == other.Foreground && + Background == other.Background; /// public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background); /// - public override string ToString () - { + public override string ToString () => // Note, Unit tests are dependent on this format - return $"{Foreground},{Background}"; - } + $"{Foreground},{Background}"; } - /// /// Defines the s for common visible elements in a . /// Containers such as and use to determine @@ -899,25 +957,19 @@ public ColorScheme (Attribute attribute) /// /// /// true if the two objects are equal - public override bool Equals (object obj) - { - return Equals (obj as ColorScheme); - } + public override bool Equals (object obj) => 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); - } + public bool Equals (ColorScheme other) => 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. @@ -940,10 +992,7 @@ public override int GetHashCode () /// /// /// true if the two objects are equivalent - public static bool operator == (ColorScheme left, ColorScheme right) - { - return EqualityComparer.Default.Equals (left, right); - } + public static bool operator == (ColorScheme left, ColorScheme right) => EqualityComparer.Default.Equals (left, right); /// /// Compares two objects for inequality. @@ -951,12 +1000,8 @@ public override int GetHashCode () /// /// /// true if the two objects are not equivalent - public static bool operator != (ColorScheme left, ColorScheme right) - { - return !(left == right); - } + public static bool operator != (ColorScheme left, ColorScheme right) => !(left == right); } - /// /// The default s for the application. /// @@ -964,7 +1009,7 @@ public override int GetHashCode () /// This property can be set in a Theme to change the default for the application. /// public static class Colors { - private class SchemeNameComparerIgnoreCase : IEqualityComparer { + class SchemeNameComparerIgnoreCase : IEqualityComparer { public bool Equals (string x, string y) { if (x != null && y != null) { @@ -973,29 +1018,21 @@ public bool Equals (string x, string y) return false; } - public int GetHashCode (string obj) - { - return obj.ToLowerInvariant ().GetHashCode (); - } + public int GetHashCode (string obj) => obj.ToLowerInvariant ().GetHashCode (); } - static Colors () - { - ColorSchemes = Create (); - } + static Colors () => ColorSchemes = Create (); /// /// Creates a new dictionary of new objects. /// - public static Dictionary Create () - { + 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 ()); - } + typeof (Colors).GetProperties () + .Where (p => p.PropertyType == typeof (ColorScheme)) + .Select (p => new KeyValuePair (p.Name, new ColorScheme ())) + .ToDictionary (t => t.Key, t => t.Value, new SchemeNameComparerIgnoreCase ()); /// /// The application Toplevel color scheme, for the default Toplevel views. @@ -1047,10 +1084,7 @@ static Colors () /// public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); } - static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) - { - return ColorSchemes [schemeBeingSet]; - } + static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) => ColorSchemes [schemeBeingSet]; static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null) { @@ -1064,4 +1098,4 @@ static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string s [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)] [JsonConverter (typeof (DictionaryJsonConverter))] public static Dictionary ColorSchemes { get; private set; } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index c780213d01..81b73c8018 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -3,237 +3,252 @@ using System.ComponentModel; using System.Linq; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and +/// for pop-up views such as , , and . +/// +/// +/// +/// Toplevels can be modally executing views, started by calling . +/// They return control to the caller when has +/// been called (which sets the property to false). +/// +/// +/// A Toplevel is created when an application initializes Terminal.Gui by calling . +/// The application Toplevel can be accessed via . Additional Toplevels can be created +/// and run (e.g. s. To run a Toplevel, create the and +/// call . +/// +/// +public partial class Toplevel : View { /// - /// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and - /// for pop-up views such as , , and . + /// Gets or sets whether the main loop for this is running or not. /// /// - /// - /// Toplevels can be modally executing views, started by calling . - /// They return control to the caller when has - /// been called (which sets the property to false). - /// - /// - /// A Toplevel is created when an application initializes Terminal.Gui by calling . - /// The application Toplevel can be accessed via . Additional Toplevels can be created - /// and run (e.g. s. To run a Toplevel, create the and - /// call . - /// + /// Setting this property directly is discouraged. Use instead. /// - public partial class Toplevel : View { - /// - /// Gets or sets whether the main loop for this is running or not. - /// - /// - /// Setting this property directly is discouraged. Use instead. - /// - public bool Running { get; set; } - - /// - /// Invoked when the has begun to be loaded. - /// A Loaded event handler is a good place to finalize initialization before calling - /// . - /// - public event EventHandler Loaded; - - /// - /// Invoked when the main loop has started it's first iteration. - /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. - /// changes. - /// A Ready event handler is a good place to finalize initialization after calling - /// on this . - /// - public event EventHandler Ready; - - /// - /// Invoked when the Toplevel has been unloaded. - /// A Unloaded event handler is a good place to dispose objects after calling . - /// - public event EventHandler Unloaded; - - /// - /// Invoked when the Toplevel becomes the Toplevel. - /// - public event EventHandler Activate; - - /// - /// Invoked when the Toplevel ceases to be the Toplevel. - /// - public event EventHandler Deactivate; - - /// - /// Invoked when a child of the Toplevel is closed by - /// . - /// - public event EventHandler ChildClosed; - - /// - /// Invoked when the last child of the Toplevel is closed from - /// by . - /// - public event EventHandler AllChildClosed; - - /// - /// Invoked when the Toplevel's is being closed by - /// . - /// - public event EventHandler Closing; - - /// - /// Invoked when the Toplevel's is closed by . - /// - public event EventHandler Closed; - - /// - /// Invoked when a child Toplevel's has been loaded. - /// - public event EventHandler ChildLoaded; - - /// - /// Invoked when a cjhild Toplevel's has been unloaded. - /// - public event EventHandler ChildUnloaded; - - /// - /// Invoked when the terminal has been resized. The new of the terminal is provided. - /// - public event EventHandler SizeChanging; - - // TODO: Make cancelable? - internal virtual void OnSizeChanging (SizeChangedEventArgs size) => SizeChanging?.Invoke (this, size); - - internal virtual void OnChildUnloaded (Toplevel top) => ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top)); - - internal virtual void OnChildLoaded (Toplevel top) => ChildLoaded?.Invoke (this, new ToplevelEventArgs (top)); - - internal virtual void OnClosed (Toplevel top) => Closed?.Invoke (this, new ToplevelEventArgs (top)); - - internal virtual bool OnClosing (ToplevelClosingEventArgs ev) - { - Closing?.Invoke (this, ev); - return ev.Cancel; - } - - internal virtual void OnAllChildClosed () => AllChildClosed?.Invoke (this, EventArgs.Empty); - - internal virtual void OnChildClosed (Toplevel top) - { - if (IsOverlappedContainer) { - SetSubViewNeedsDisplay (); - } - ChildClosed?.Invoke (this, new ToplevelEventArgs (top)); - } + public bool Running { get; set; } + + /// + /// Invoked when the has begun to be loaded. + /// A Loaded event handler is a good place to finalize initialization before calling + /// . + /// + public event EventHandler Loaded; + + /// + /// Invoked when the main loop has started it's first iteration. + /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. + /// changes. + /// A Ready event handler is a good place to finalize initialization after calling + /// on this . + /// + public event EventHandler Ready; + + /// + /// Invoked when the Toplevel has been unloaded. + /// A Unloaded event handler is a good place to dispose objects after calling . + /// + public event EventHandler Unloaded; + + /// + /// Invoked when the Toplevel becomes the Toplevel. + /// + public event EventHandler Activate; + + /// + /// Invoked when the Toplevel ceases to be the Toplevel. + /// + public event EventHandler Deactivate; - internal virtual void OnDeactivate (Toplevel activated) - { - Deactivate?.Invoke (this, new ToplevelEventArgs (activated)); + /// + /// Invoked when a child of the Toplevel is closed by + /// . + /// + public event EventHandler ChildClosed; + + /// + /// Invoked when the last child of the Toplevel is closed from + /// by . + /// + public event EventHandler AllChildClosed; + + /// + /// Invoked when the Toplevel's is being closed by + /// . + /// + public event EventHandler Closing; + + /// + /// Invoked when the Toplevel's is closed by . + /// + public event EventHandler Closed; + + /// + /// Invoked when a child Toplevel's has been loaded. + /// + public event EventHandler ChildLoaded; + + /// + /// Invoked when a cjhild Toplevel's has been unloaded. + /// + public event EventHandler ChildUnloaded; + + /// + /// Invoked when the terminal has been resized. The new of the terminal is provided. + /// + public event EventHandler SizeChanging; + + // TODO: Make cancelable? + internal virtual void OnSizeChanging (SizeChangedEventArgs size) => SizeChanging?.Invoke (this, size); + + internal virtual void OnChildUnloaded (Toplevel top) => ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top)); + + internal virtual void OnChildLoaded (Toplevel top) => ChildLoaded?.Invoke (this, new ToplevelEventArgs (top)); + + internal virtual void OnClosed (Toplevel top) => Closed?.Invoke (this, new ToplevelEventArgs (top)); + + internal virtual bool OnClosing (ToplevelClosingEventArgs ev) + { + Closing?.Invoke (this, ev); + return ev.Cancel; + } + + internal virtual void OnAllChildClosed () => AllChildClosed?.Invoke (this, EventArgs.Empty); + + internal virtual void OnChildClosed (Toplevel top) + { + if (IsOverlappedContainer) { + SetSubViewNeedsDisplay (); } + ChildClosed?.Invoke (this, new ToplevelEventArgs (top)); + } + + internal virtual void OnDeactivate (Toplevel activated) => Deactivate?.Invoke (this, new ToplevelEventArgs (activated)); - internal virtual void OnActivate (Toplevel deactivated) - { - Activate?.Invoke (this, new ToplevelEventArgs (deactivated)); + internal virtual void OnActivate (Toplevel deactivated) => Activate?.Invoke (this, new ToplevelEventArgs (deactivated)); + + /// + /// Called from before the redraws for the first time. + /// + public virtual void OnLoaded () + { + IsLoaded = true; + foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { + tl.OnLoaded (); } + Loaded?.Invoke (this, EventArgs.Empty); + } - /// - /// Called from before the redraws for the first time. - /// - public virtual void OnLoaded () - { - IsLoaded = true; - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { - tl.OnLoaded (); - } - Loaded?.Invoke (this, EventArgs.Empty); + /// + /// Called from after the has entered the + /// first iteration of the loop. + /// + internal virtual void OnReady () + { + foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { + tl.OnReady (); } + Ready?.Invoke (this, EventArgs.Empty); + } - /// - /// Called from after the has entered the - /// first iteration of the loop. - /// - internal virtual void OnReady () - { - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { - tl.OnReady (); - } - Ready?.Invoke (this, EventArgs.Empty); + /// + /// Called from before the is disposed. + /// + internal virtual void OnUnloaded () + { + foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { + tl.OnUnloaded (); } + Unloaded?.Invoke (this, EventArgs.Empty); + } + + /// + /// Initializes a new instance of the class with the specified layout. + /// + /// A Superview-relative rectangle specifying the location and size for the new Toplevel + public Toplevel (Rect frame) : base (frame) => SetInitialProperties (); - /// - /// Called from before the is disposed. - /// - internal virtual void OnUnloaded () - { - foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) { - tl.OnUnloaded (); + /// + /// Initializes a new instance of the class with layout, + /// defaulting to full screen. + /// + public Toplevel () : base () + { + SetInitialProperties (); + Width = Dim.Fill (); + Height = Dim.Fill (); + } + + void SetInitialProperties () + { + ColorScheme = Colors.TopLevel; + + Application.GrabbingMouse += Application_GrabbingMouse; + Application.UnGrabbingMouse += Application_UnGrabbingMouse; + + // TODO: v2 - ALL Views (Responders??!?!) should support the commands related to + // - Focus + // Move the appropriate AddCommand calls to `Responder` + + // Things this view knows how to do + AddCommand (Command.QuitToplevel, () => { + QuitToplevel (); + return true; + }); + AddCommand (Command.Suspend, () => { + Driver.Suspend (); + ; + return true; + }); + AddCommand (Command.NextView, () => { + MoveNextView (); + return true; + }); + AddCommand (Command.PreviousView, () => { + MovePreviousView (); + return true; + }); + AddCommand (Command.NextViewOrTop, () => { + MoveNextViewOrTop (); + return true; + }); + AddCommand (Command.PreviousViewOrTop, () => { + MovePreviousViewOrTop (); + return true; + }); + AddCommand (Command.Refresh, () => { + Application.Refresh (); + return true; + }); + AddCommand (Command.Accept, () => { + // TODO: Perhaps all views should support the concept of being default? + // TODO: It's bad that Toplevel is tightly coupled with Button + if (Subviews.FirstOrDefault (v => v is Button && ((Button)v).IsDefault && ((Button)v).Enabled) is Button defaultBtn) { + defaultBtn.InvokeCommand (Command.Accept); + return true; } - Unloaded?.Invoke (this, EventArgs.Empty); - } - - /// - /// Initializes a new instance of the class with the specified layout. - /// - /// A Superview-relative rectangle specifying the location and size for the new Toplevel - public Toplevel (Rect frame) : base (frame) - { - SetInitialProperties (); - } - - /// - /// Initializes a new instance of the class with layout, - /// defaulting to full screen. - /// - public Toplevel () : base () - { - SetInitialProperties (); - Width = Dim.Fill (); - Height = Dim.Fill (); - } - - void SetInitialProperties () - { - ColorScheme = Colors.TopLevel; - Application.GrabbingMouse += Application_GrabbingMouse; - Application.UnGrabbingMouse += Application_UnGrabbingMouse; - - // TODO: v2 - ALL Views (Responders??!?!) should support the commands related to - // - Focus - // Move the appropriate AddCommand calls to `Responder` - - // Things this view knows how to do - AddCommand (Command.QuitToplevel, () => { QuitToplevel (); return true; }); - AddCommand (Command.Suspend, () => { Driver.Suspend (); ; return true; }); - AddCommand (Command.NextView, () => { MoveNextView (); return true; }); - AddCommand (Command.PreviousView, () => { MovePreviousView (); return true; }); - AddCommand (Command.NextViewOrTop, () => { MoveNextViewOrTop (); return true; }); - AddCommand (Command.PreviousViewOrTop, () => { MovePreviousViewOrTop (); return true; }); - AddCommand (Command.Refresh, () => { Application.Refresh (); return true; }); - AddCommand (Command.Accept, () => { - // TODO: Perhaps all views should support the concept of being default? - // TODO: It's bad that Toplevel is tightly coupled with Button - if (Subviews.FirstOrDefault(v => v is Button && ((Button)v).IsDefault && ((Button)v).Enabled) is Button defaultBtn) { - defaultBtn.InvokeCommand (Command.Accept); - return true; - } - return false; - }); + return false; + }); - // Default keybindings for this view - KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel); + // Default keybindings for this view + KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel); - KeyBindings.Add (KeyCode.CursorRight, Command.NextView); - KeyBindings.Add (KeyCode.CursorDown, Command.NextView); - KeyBindings.Add (KeyCode.CursorLeft, Command.PreviousView); - KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView); + KeyBindings.Add (KeyCode.CursorRight, Command.NextView); + KeyBindings.Add (KeyCode.CursorDown, Command.NextView); + KeyBindings.Add (KeyCode.CursorLeft, Command.PreviousView); + KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView); - KeyBindings.Add (KeyCode.Tab, Command.NextView); - KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.PreviousView); - KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextViewOrTop); - KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop); + KeyBindings.Add (KeyCode.Tab, Command.NextView); + KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.PreviousView); + KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextViewOrTop); + KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop); - KeyBindings.Add (KeyCode.F5, Command.Refresh); - KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix - KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix + KeyBindings.Add (KeyCode.F5, Command.Refresh); + KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix + KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix #if UNIX_KEY_BINDINGS KeyBindings.Add (Key.Z | Key.CtrlMask, Command.Suspend); @@ -242,726 +257,714 @@ void SetInitialProperties () KeyBindings.Add (Key.I | Key.CtrlMask, Command.NextView); // Unix KeyBindings.Add (Key.B | Key.CtrlMask, Command.PreviousView);// Unix #endif - // This enables the default button to be activated by the Enter key. - KeyBindings.Add (KeyCode.Enter, Command.Accept); - } + // This enables the default button to be activated by the Enter key. + KeyBindings.Add (KeyCode.Enter, Command.Accept); + } - private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) - { - if (Application.MouseGrabView == this && _dragPosition.HasValue) { - e.Cancel = true; - } + void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) + { + if (Application.MouseGrabView == this && _dragPosition.HasValue) { + e.Cancel = true; } + } - private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e) - { - if (Application.MouseGrabView == this && _dragPosition.HasValue) { - e.Cancel = true; - } + void Application_GrabbingMouse (object sender, GrabMouseEventArgs e) + { + if (Application.MouseGrabView == this && _dragPosition.HasValue) { + e.Cancel = true; } + } + + /// + /// Invoked when the is changed. + /// + public event EventHandler AlternateForwardKeyChanged; + + /// + /// Virtual method to invoke the event. + /// + /// + public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) + { + KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); + AlternateForwardKeyChanged?.Invoke (this, e); + } + + /// + /// Invoked when the is changed. + /// + public event EventHandler AlternateBackwardKeyChanged; + + /// + /// Virtual method to invoke the event. + /// + /// + public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) + { + KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); + AlternateBackwardKeyChanged?.Invoke (this, e); + } + + /// + /// Invoked when the is changed. + /// + public event EventHandler QuitKeyChanged; + + /// + /// Virtual method to invoke the event. + /// + /// + public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) + { + KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); + QuitKeyChanged?.Invoke (this, e); + } + + /// + /// Convenience factory method that creates a new Toplevel with the current terminal dimensions. + /// + /// The created Toplevel. + public static Toplevel Create () => new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows)); + + /// + /// Gets or sets a value indicating whether this can focus. + /// + /// true if can focus; otherwise, false. + public override bool CanFocus => SuperView == null ? true : base.CanFocus; + + /// + /// Determines whether the is modal or not. + /// If set to false (the default): + /// + /// + /// + /// events will propagate keys upwards. + /// + /// + /// The Toplevel will act as an embedded view (not a modal/pop-up). + /// + /// + /// + /// If set to true: + /// + /// + /// + /// events will NOT propagate keys upwards. + /// + /// + /// The Toplevel will and look like a modal (pop-up) (e.g. see . + /// + /// + /// + public bool Modal { get; set; } - /// - /// Invoked when the is changed. - /// - public event EventHandler AlternateForwardKeyChanged; - - /// - /// Virtual method to invoke the event. - /// - /// - public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - AlternateForwardKeyChanged?.Invoke (this, e); - } - - /// - /// Invoked when the is changed. - /// - public event EventHandler AlternateBackwardKeyChanged; - - /// - /// Virtual method to invoke the event. - /// - /// - public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - AlternateBackwardKeyChanged?.Invoke (this, e); - } - - /// - /// Invoked when the is changed. - /// - public event EventHandler QuitKeyChanged; - - /// - /// Virtual method to invoke the event. - /// - /// - public virtual void OnQuitKeyChanged (KeyChangedEventArgs e) - { - KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey); - QuitKeyChanged?.Invoke (this, e); - } - - /// - /// Convenience factory method that creates a new Toplevel with the current terminal dimensions. - /// - /// The created Toplevel. - public static Toplevel Create () - { - return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows)); - } - - /// - /// Gets or sets a value indicating whether this can focus. - /// - /// true if can focus; otherwise, false. - public override bool CanFocus { - get => SuperView == null ? true : base.CanFocus; - } - - /// - /// Determines whether the is modal or not. - /// If set to false (the default): - /// - /// - /// - /// events will propagate keys upwards. - /// - /// - /// The Toplevel will act as an embedded view (not a modal/pop-up). - /// - /// - /// - /// If set to true: - /// - /// - /// - /// events will NOT propagate keys upwards. - /// - /// - /// The Toplevel will and look like a modal (pop-up) (e.g. see . - /// - /// - /// - public bool Modal { get; set; } - - /// - /// Gets or sets the menu for this Toplevel. - /// - public virtual MenuBar MenuBar { get; set; } - - /// - /// Gets or sets the status bar for this Toplevel. - /// - public virtual StatusBar StatusBar { get; set; } - - /// - /// if was already loaded by the - /// , otherwise. - /// - public bool IsLoaded { get; private set; } - - private void MovePreviousViewOrTop () - { - if (Application.OverlappedTop == null) { - var top = Modal ? this : Application.Top; + /// + /// Gets or sets the menu for this Toplevel. + /// + public virtual MenuBar MenuBar { get; set; } + + /// + /// Gets or sets the status bar for this Toplevel. + /// + public virtual StatusBar StatusBar { get; set; } + + /// + /// if was already loaded by the + /// , otherwise. + /// + public bool IsLoaded { get; private set; } + + void MovePreviousViewOrTop () + { + if (Application.OverlappedTop == null) { + var top = Modal ? this : Application.Top; + top.FocusPrev (); + if (top.Focused == null) { top.FocusPrev (); - if (top.Focused == null) { - top.FocusPrev (); - } - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); - } else { - Application.OverlappedMovePrevious (); } + top.SetNeedsDisplay (); + Application.BringOverlappedTopToFront (); + } else { + Application.OverlappedMovePrevious (); } + } - private void MoveNextViewOrTop () - { - if (Application.OverlappedTop == null) { - var top = Modal ? this : Application.Top; + void MoveNextViewOrTop () + { + if (Application.OverlappedTop == null) { + var top = Modal ? this : Application.Top; + top.FocusNext (); + if (top.Focused == null) { top.FocusNext (); - if (top.Focused == null) { - top.FocusNext (); - } - top.SetNeedsDisplay (); - Application.BringOverlappedTopToFront (); - } else { - Application.OverlappedMoveNext (); } + top.SetNeedsDisplay (); + Application.BringOverlappedTopToFront (); + } else { + Application.OverlappedMoveNext (); } + } - private void MovePreviousView () - { - var old = GetDeepestFocusedSubview (Focused); - if (!FocusPrev ()) - FocusPrev (); - if (old != Focused && old != Focused?.Focused) { - old?.SetNeedsDisplay (); - Focused?.SetNeedsDisplay (); - } else { - FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward); - } + void MovePreviousView () + { + var old = GetDeepestFocusedSubview (Focused); + if (!FocusPrev ()) { + FocusPrev (); + } + if (old != Focused && old != Focused?.Focused) { + old?.SetNeedsDisplay (); + Focused?.SetNeedsDisplay (); + } else { + FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward); } + } - private void MoveNextView () - { - var old = GetDeepestFocusedSubview (Focused); - if (!FocusNext ()) - FocusNext (); - if (old != Focused && old != Focused?.Focused) { - old?.SetNeedsDisplay (); - Focused?.SetNeedsDisplay (); - } else { - FocusNearestView (SuperView?.TabIndexes, Direction.Forward); - } + void MoveNextView () + { + var old = GetDeepestFocusedSubview (Focused); + if (!FocusNext ()) { + FocusNext (); } + if (old != Focused && old != Focused?.Focused) { + old?.SetNeedsDisplay (); + Focused?.SetNeedsDisplay (); + } else { + FocusNearestView (SuperView?.TabIndexes, Direction.Forward); + } + } - private void QuitToplevel () - { - if (Application.OverlappedTop != null) { - Application.OverlappedTop.RequestStop (); - } else { - Application.RequestStop (); - } + void QuitToplevel () + { + if (Application.OverlappedTop != null) { + Application.OverlappedTop.RequestStop (); + } else { + Application.RequestStop (); } + } - View GetDeepestFocusedSubview (View view) - { - if (view == null) { - return null; - } + View GetDeepestFocusedSubview (View view) + { + if (view == null) { + return null; + } - foreach (var v in view.Subviews) { - if (v.HasFocus) { - return GetDeepestFocusedSubview (v); - } + foreach (var v in view.Subviews) { + if (v.HasFocus) { + return GetDeepestFocusedSubview (v); } - return view; } + return view; + } - void FocusNearestView (IEnumerable views, Direction direction) - { - if (views == null) { - return; - } + void FocusNearestView (IEnumerable views, Direction direction) + { + if (views == null) { + return; + } - bool found = false; - bool focusProcessed = false; - int idx = 0; + bool found = false; + bool focusProcessed = false; + int idx = 0; - foreach (var v in views) { - if (v == this) { - found = true; + foreach (var v in views) { + if (v == this) { + found = true; + } + if (found && v != this) { + if (direction == Direction.Forward) { + SuperView?.FocusNext (); + } else { + SuperView?.FocusPrev (); } - if (found && v != this) { - if (direction == Direction.Forward) { - SuperView?.FocusNext (); - } else { - SuperView?.FocusPrev (); - } - focusProcessed = true; - if (SuperView.Focused != null && SuperView.Focused != this) { - return; - } - } else if (found && !focusProcessed && idx == views.Count () - 1) { - views.ToList () [0].SetFocus (); + focusProcessed = true; + if (SuperView.Focused != null && SuperView.Focused != this) { + return; } - idx++; + } else if (found && !focusProcessed && idx == views.Count () - 1) { + views.ToList () [0].SetFocus (); } + idx++; } + } - /// - public override void Add (View view) - { - CanFocus = true; - AddMenuStatusBar (view); - base.Add (view); - } + /// + public override void Add (View view) + { + CanFocus = true; + AddMenuStatusBar (view); + base.Add (view); + } - internal void AddMenuStatusBar (View view) - { - if (view is MenuBar) { - MenuBar = view as MenuBar; - } - if (view is StatusBar) { - StatusBar = view as StatusBar; - } + internal void AddMenuStatusBar (View view) + { + if (view is MenuBar) { + MenuBar = view as MenuBar; } + if (view is StatusBar) { + StatusBar = view as StatusBar; + } + } - /// - public override void Remove (View view) - { - if (this is Toplevel Toplevel && Toplevel.MenuBar != null) { - RemoveMenuStatusBar (view); - } - base.Remove (view); + /// + public override void Remove (View view) + { + if (this is Toplevel Toplevel && Toplevel.MenuBar != null) { + RemoveMenuStatusBar (view); } + base.Remove (view); + } - /// - public override void RemoveAll () - { - if (this == Application.Top) { - MenuBar?.Dispose (); - MenuBar = null; - StatusBar?.Dispose (); - StatusBar = null; - } - base.RemoveAll (); + /// + public override void RemoveAll () + { + if (this == Application.Top) { + MenuBar?.Dispose (); + MenuBar = null; + StatusBar?.Dispose (); + StatusBar = null; } + base.RemoveAll (); + } - internal void RemoveMenuStatusBar (View view) - { - if (view is MenuBar) { - MenuBar?.Dispose (); - MenuBar = null; - } - if (view is StatusBar) { - StatusBar?.Dispose (); - StatusBar = null; - } + internal void RemoveMenuStatusBar (View view) + { + if (view is MenuBar) { + MenuBar?.Dispose (); + MenuBar = null; } + if (view is StatusBar) { + StatusBar?.Dispose (); + StatusBar = null; + } + } - /// - /// Gets a new location of the that is within the Bounds of the 's - /// (e.g. for dragging a Window). - /// The `out` parameters are the new X and Y coordinates. - /// - /// - /// If does not have a or it's SuperView is not - /// the position will be bound by the and . - /// - /// The Toplevel that is to be moved. - /// The target x location. - /// The target y location. - /// The x location that will ensure will be visible. - /// The y location that will ensure will be visible. - /// The new top most menuBar - /// The new top most statusBar - /// - /// Either (if does not have a Super View) or - /// 's SuperView. This can be used to ensure LayoutSubviews is called on the correct View. - /// - internal View GetLocationThatFits (Toplevel top, int targetX, int targetY, + /// + /// Gets a new location of the that is within the Bounds of the 's + /// (e.g. for dragging a Window). + /// The `out` parameters are the new X and Y coordinates. + /// + /// + /// If does not have a or it's SuperView is not + /// the position will be bound by the and . + /// + /// The Toplevel that is to be moved. + /// The target x location. + /// The target y location. + /// The x location that will ensure will be visible. + /// The y location that will ensure will be visible. + /// The new top most menuBar + /// The new top most statusBar + /// + /// Either (if does not have a Super View) or + /// 's SuperView. This can be used to ensure LayoutSubviews is called on the correct View. + /// + internal View GetLocationThatFits (Toplevel top, int targetX, int targetY, out int nx, out int ny, out MenuBar menuBar, out StatusBar statusBar) - { - int maxWidth; - View superView; - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - maxWidth = Driver.Cols; - superView = Application.Top; - } else { - // Use the SuperView's Bounds, not Frame - maxWidth = top.SuperView.Bounds.Width; - superView = top.SuperView; - } - if (superView.Margin != null && superView == top.SuperView) { - maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right; - } - if (top.Frame.Width <= maxWidth) { - nx = Math.Max (targetX, 0); - nx = nx + top.Frame.Width > maxWidth ? Math.Max (maxWidth - top.Frame.Width, 0) : nx; - if (nx > top.Frame.X + top.Frame.Width) { - nx = Math.Max (top.Frame.Right, 0); - } - } else { - nx = targetX; - } - //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); - bool menuVisible, statusVisible; - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - menuVisible = Application.Top.MenuBar?.Visible == true; - menuBar = Application.Top.MenuBar; - } else { - var t = top.SuperView; - while (t is not Toplevel) { - t = t.SuperView; - } - menuVisible = ((Toplevel)t).MenuBar?.Visible == true; - menuBar = ((Toplevel)t).MenuBar; - } - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - maxWidth = menuVisible ? 1 : 0; - } else { - maxWidth = 0; - } - ny = Math.Max (targetY, maxWidth); - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - statusVisible = Application.Top.StatusBar?.Visible == true; - statusBar = Application.Top.StatusBar; - } else { - var t = top.SuperView; - while (t is not Toplevel) { - t = t.SuperView; - } - statusVisible = ((Toplevel)t).StatusBar?.Visible == true; - statusBar = ((Toplevel)t).StatusBar; - } - if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { - maxWidth = statusVisible ? Driver.Rows - 1 : Driver.Rows; - } else { - maxWidth = statusVisible ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height; - } - if (superView.Margin != null && superView == top.SuperView) { - maxWidth -= superView.GetFramesThickness ().Top + superView.GetFramesThickness ().Bottom; - } - ny = Math.Min (ny, maxWidth); - if (top.Frame.Height <= maxWidth) { - ny = ny + top.Frame.Height > maxWidth ? Math.Max (maxWidth - top.Frame.Height, menuVisible ? 1 : 0) : ny; - if (ny > top.Frame.Y + top.Frame.Height) { - ny = Math.Max (top.Frame.Bottom, 0); - } - } - //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); - - return superView; - } + { + int maxWidth; + View superView; + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { + maxWidth = Driver.Cols; + superView = Application.Top; + } else { + // Use the SuperView's Bounds, not Frame + maxWidth = top.SuperView.Bounds.Width; + superView = top.SuperView; + } + if (superView.Margin != null && superView == top.SuperView) { + maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right; + } + if (top.Frame.Width <= maxWidth) { + nx = Math.Max (targetX, 0); + nx = nx + top.Frame.Width > maxWidth ? Math.Max (maxWidth - top.Frame.Width, 0) : nx; + if (nx > top.Frame.X + top.Frame.Width) { + nx = Math.Max (top.Frame.Right, 0); + } + } else { + nx = targetX; + } + //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); + bool menuVisible, statusVisible; + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { + menuVisible = Application.Top.MenuBar?.Visible == true; + menuBar = Application.Top.MenuBar; + } else { + var t = top.SuperView; + while (t is not Toplevel) { + t = t.SuperView; + } + menuVisible = ((Toplevel)t).MenuBar?.Visible == true; + menuBar = ((Toplevel)t).MenuBar; + } + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { + maxWidth = menuVisible ? 1 : 0; + } else { + maxWidth = 0; + } + ny = Math.Max (targetY, maxWidth); + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { + statusVisible = Application.Top.StatusBar?.Visible == true; + statusBar = Application.Top.StatusBar; + } else { + var t = top.SuperView; + while (t is not Toplevel) { + t = t.SuperView; + } + statusVisible = ((Toplevel)t).StatusBar?.Visible == true; + statusBar = ((Toplevel)t).StatusBar; + } + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { + maxWidth = statusVisible ? Driver.Rows - 1 : Driver.Rows; + } else { + maxWidth = statusVisible ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height; + } + if (superView.Margin != null && superView == top.SuperView) { + maxWidth -= superView.GetFramesThickness ().Top + superView.GetFramesThickness ().Bottom; + } + ny = Math.Min (ny, maxWidth); + if (top.Frame.Height <= maxWidth) { + ny = ny + top.Frame.Height > maxWidth ? Math.Max (maxWidth - top.Frame.Height, menuVisible ? 1 : 0) : ny; + if (ny > top.Frame.Y + top.Frame.Height) { + ny = Math.Max (top.Frame.Bottom, 0); + } + } + //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); + + return superView; + } - // TODO: v2 - Not sure this is needed anymore. - internal void PositionToplevels () - { - PositionToplevel (this); - foreach (var top in Subviews) { - if (top is Toplevel) { - PositionToplevel ((Toplevel)top); - } + // TODO: v2 - Not sure this is needed anymore. + internal void PositionToplevels () + { + PositionToplevel (this); + foreach (var top in Subviews) { + if (top is Toplevel) { + PositionToplevel ((Toplevel)top); } } + } - /// - /// Adjusts the location and size of within this Toplevel. - /// Virtual method enabling implementation of specific positions for inherited views. - /// - /// The Toplevel to adjust. - public virtual void PositionToplevel (Toplevel top) - { - var superView = GetLocationThatFits (top, top.Frame.X, top.Frame.Y, - out int nx, out int ny, out _, out StatusBar sb); - bool layoutSubviews = false; - var maxWidth = 0; - if (superView.Margin != null && superView == top.SuperView) { - maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right; - } - if ((superView != top || top?.SuperView != null || (top != Application.Top && top.Modal) - || (top?.SuperView == null && top.IsOverlapped)) - && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { - - if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) { - top.X = nx; - layoutSubviews = true; - } - if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) { - top.Y = ny; - layoutSubviews = true; - } + /// + /// Adjusts the location and size of within this Toplevel. + /// Virtual method enabling implementation of specific positions for inherited views. + /// + /// The Toplevel to adjust. + public virtual void PositionToplevel (Toplevel top) + { + var superView = GetLocationThatFits (top, top.Frame.X, top.Frame.Y, + out int nx, out int ny, out _, out var sb); + bool layoutSubviews = false; + int maxWidth = 0; + if (superView.Margin != null && superView == top.SuperView) { + maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right; + } + if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal + || top?.SuperView == null && top.IsOverlapped) + && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { + + if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) { + top.X = nx; + layoutSubviews = true; } - - // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. - if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) - && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) { - - top.Height = Dim.Fill (sb.Visible ? 1 : 0); + if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) { + top.Y = ny; layoutSubviews = true; } + } - if (superView.LayoutNeeded || layoutSubviews) { - superView.LayoutSubviews (); - } - if (LayoutNeeded) { - LayoutSubviews (); - } + // TODO: v2 - This is a hack to get the StatusBar to be positioned correctly. + if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) + && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) { + + top.Height = Dim.Fill (sb.Visible ? 1 : 0); + layoutSubviews = true; } - /// - public override void OnDrawContent (Rect contentArea) - { - if (!Visible) { - return; - } + if (superView.LayoutNeeded || layoutSubviews) { + superView.LayoutSubviews (); + } + if (LayoutNeeded) { + LayoutSubviews (); + } + } - if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) { - //Driver.SetAttribute (GetNormalColor ()); - // TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc... - Clear (); - LayoutSubviews (); - PositionToplevels (); - - if (this == Application.OverlappedTop) { - foreach (var top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) { - if (top.Frame.IntersectsWith (Bounds)) { - if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) { - top.SetNeedsLayout (); - top.SetNeedsDisplay (top.Bounds); - top.Draw (); - top.OnRenderLineCanvas (); - } + /// + public override void OnDrawContent (Rect contentArea) + { + if (!Visible) { + return; + } + + if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) { + //Driver.SetAttribute (GetNormalColor ()); + // TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc... + Clear (); + LayoutSubviews (); + PositionToplevels (); + + if (this == Application.OverlappedTop) { + foreach (var top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) { + if (top.Frame.IntersectsWith (Bounds)) { + if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) { + top.SetNeedsLayout (); + top.SetNeedsDisplay (top.Bounds); + top.Draw (); + top.OnRenderLineCanvas (); } } } + } - // This should not be here, but in base - foreach (var view in Subviews) { - if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) { - //view.SetNeedsLayout (); - view.SetNeedsDisplay (view.Bounds); - view.SetSubViewNeedsDisplay (); - } + // This should not be here, but in base + foreach (var view in Subviews) { + if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) { + //view.SetNeedsLayout (); + view.SetNeedsDisplay (view.Bounds); + view.SetSubViewNeedsDisplay (); } + } - base.OnDrawContent (contentArea); + base.OnDrawContent (contentArea); - // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true - //if (this.MenuBar != null && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu != null) { - // // TODO: Hack until we can get compositing working right. - // this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds); - //} - } + // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true + //if (this.MenuBar != null && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu != null) { + // // TODO: Hack until we can get compositing working right. + // this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds); + //} } + } - bool OutsideTopFrame (Toplevel top) - { - if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) { - return true; - } - return false; + bool OutsideTopFrame (Toplevel top) + { + if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) { + return true; } + return false; + } - internal static Point? _dragPosition; - Point _startGrabPoint; - - /// - public override bool MouseEvent (MouseEvent mouseEvent) - { - if (!CanFocus) { - return true; - } - - //System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}"); - - int nx, ny; - if (!_dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed - || mouseEvent.Flags == MouseFlags.Button2Pressed - || mouseEvent.Flags == MouseFlags.Button3Pressed)) { - - SetFocus (); - Application.BringOverlappedTopToFront (); - - // Only start grabbing if the user clicks on the title bar. - // BUGBUG: Assumes Frame == Border and Title is always at Y == 0 - if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) { - _startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y); - _dragPosition = new Point (); - nx = mouseEvent.X - mouseEvent.OfX; - ny = mouseEvent.Y - mouseEvent.OfY; - _dragPosition = new Point (nx, ny); - Application.GrabMouse (this); + internal static Point? _dragPosition; + Point _startGrabPoint; + + /// + public override bool MouseEvent (MouseEvent mouseEvent) + { + if (!CanFocus) { + return true; + } + + //System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}"); + + int nx, ny; + if (!_dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed + || mouseEvent.Flags == MouseFlags.Button2Pressed + || mouseEvent.Flags == MouseFlags.Button3Pressed)) { + + SetFocus (); + Application.BringOverlappedTopToFront (); + + // Only start grabbing if the user clicks on the title bar. + // BUGBUG: Assumes Frame == Border and Title is always at Y == 0 + if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) { + _startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y); + _dragPosition = new Point (); + nx = mouseEvent.X - mouseEvent.OfX; + ny = mouseEvent.Y - mouseEvent.OfY; + _dragPosition = new Point (nx, ny); + Application.GrabMouse (this); + } + + //System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}"); + return true; + } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || + mouseEvent.Flags == MouseFlags.Button3Pressed) { + if (_dragPosition.HasValue) { + if (SuperView == null) { + // Redraw the entire app window using just our Frame. Since we are + // Application.Top, and our Frame always == our Bounds (Location is always (0,0)) + // our Frame is actually view-relative (which is what Redraw takes). + // We need to pass all the view bounds because since the windows was + // moved around, we don't know exactly what was the affected region. + Application.Top.SetNeedsDisplay (); + } else { + SuperView.SetNeedsDisplay (); } + // BUGBUG: Assumes Frame == Border? + GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X), + mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y), + out nx, out ny, out _, out _); - //System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}"); - return true; - } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || - mouseEvent.Flags == MouseFlags.Button3Pressed) { - if (_dragPosition.HasValue) { - if (SuperView == null) { - // Redraw the entire app window using just our Frame. Since we are - // Application.Top, and our Frame always == our Bounds (Location is always (0,0)) - // our Frame is actually view-relative (which is what Redraw takes). - // We need to pass all the view bounds because since the windows was - // moved around, we don't know exactly what was the affected region. - Application.Top.SetNeedsDisplay (); - } else { - SuperView.SetNeedsDisplay (); - } - // BUGBUG: Assumes Frame == Border? - GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X), - mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y), - out nx, out ny, out _, out _); - - _dragPosition = new Point (nx, ny); - X = nx; - Y = ny; - //System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}"); - - SetNeedsDisplay (); - return true; - } - } + _dragPosition = new Point (nx, ny); + X = nx; + Y = ny; + //System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}"); - if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) { - _dragPosition = null; - Application.UngrabMouse (); + SetNeedsDisplay (); + return true; } + } - //System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}"); - //System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}"); - return false; + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) { + _dragPosition = null; + Application.UngrabMouse (); } - /// - /// Stops and closes this . If this Toplevel is the top-most Toplevel, - /// will be called, causing the application to exit. - /// - public virtual void RequestStop () - { - if (IsOverlappedContainer && Running - && (Application.Current == this - || Application.Current?.Modal == false - || Application.Current?.Modal == true && Application.Current?.Running == false)) { - - foreach (var child in Application.OverlappedChildren) { - var ev = new ToplevelClosingEventArgs (this); - if (child.OnClosing (ev)) { - return; - } - child.Running = false; - Application.RequestStop (child); - } - Running = false; - Application.RequestStop (this); - } else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) { - var ev = new ToplevelClosingEventArgs (Application.Current); - if (OnClosing (ev)) { - return; - } - Application.RequestStop (Application.Current); - } else if (!IsOverlappedContainer && Running && (!Modal || (Modal && Application.Current != this))) { + //System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}"); + //System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}"); + return false; + } + + /// + /// Stops and closes this . If this Toplevel is the top-most Toplevel, + /// will be called, causing the application to exit. + /// + public virtual void RequestStop () + { + if (IsOverlappedContainer && Running + && (Application.Current == this + || Application.Current?.Modal == false + || Application.Current?.Modal == true && Application.Current?.Running == false)) { + + foreach (var child in Application.OverlappedChildren) { var ev = new ToplevelClosingEventArgs (this); - if (OnClosing (ev)) { + if (child.OnClosing (ev)) { return; } - Running = false; - Application.RequestStop (this); - } else { - Application.RequestStop (Application.Current); + child.Running = false; + Application.RequestStop (child); + } + Running = false; + Application.RequestStop (this); + } else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) { + var ev = new ToplevelClosingEventArgs (Application.Current); + if (OnClosing (ev)) { + return; } + Application.RequestStop (Application.Current); + } else if (!IsOverlappedContainer && Running && (!Modal || Modal && Application.Current != this)) { + var ev = new ToplevelClosingEventArgs (this); + if (OnClosing (ev)) { + return; + } + Running = false; + Application.RequestStop (this); + } else { + Application.RequestStop (Application.Current); } + } - /// - /// Stops and closes the specified by . If is the top-most Toplevel, - /// will be called, causing the application to exit. - /// - /// The Toplevel to request stop. - public virtual void RequestStop (Toplevel top) - { - top.RequestStop (); - } + /// + /// Stops and closes the specified by . If is the top-most Toplevel, + /// will be called, causing the application to exit. + /// + /// The Toplevel to request stop. + public virtual void RequestStop (Toplevel top) => top.RequestStop (); - /// - public override void PositionCursor () - { - if (!IsOverlappedContainer) { - base.PositionCursor (); + /// + public override void PositionCursor () + { + if (!IsOverlappedContainer) { + base.PositionCursor (); + if (Focused == null) { + EnsureFocus (); if (Focused == null) { - EnsureFocus (); - if (Focused == null) { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - } + Driver.SetCursorVisibility (CursorVisibility.Invisible); } - return; } + return; + } - if (Focused == null) { - foreach (var top in Application.OverlappedChildren) { - if (top != this && top.Visible) { - top.SetFocus (); - return; - } + if (Focused == null) { + foreach (var top in Application.OverlappedChildren) { + if (top != this && top.Visible) { + top.SetFocus (); + return; } } - base.PositionCursor (); - if (Focused == null) { - Driver.SetCursorVisibility (CursorVisibility.Invisible); - } } - - /// - public override bool OnEnter (View view) - { - return MostFocused?.OnEnter (view) ?? base.OnEnter (view); + base.PositionCursor (); + if (Focused == null) { + Driver.SetCursorVisibility (CursorVisibility.Invisible); } + } - /// - public override bool OnLeave (View view) - { - return MostFocused?.OnLeave (view) ?? base.OnLeave (view); - } + /// + public override bool OnEnter (View view) => MostFocused?.OnEnter (view) ?? base.OnEnter (view); - /// - protected override void Dispose (bool disposing) - { - Application.GrabbingMouse -= Application_GrabbingMouse; - Application.UnGrabbingMouse -= Application_UnGrabbingMouse; + /// + public override bool OnLeave (View view) => MostFocused?.OnLeave (view) ?? base.OnLeave (view); - _dragPosition = null; - base.Dispose (disposing); + /// + protected override void Dispose (bool disposing) + { + Application.GrabbingMouse -= Application_GrabbingMouse; + Application.UnGrabbingMouse -= Application_UnGrabbingMouse; + + _dragPosition = null; + base.Dispose (disposing); + } +} +/// +/// Implements the for comparing two s +/// used by . +/// +public class ToplevelEqualityComparer : IEqualityComparer { + /// Determines whether the specified objects are equal. + /// The first object of type to compare. + /// The second object of type to compare. + /// + /// if the specified objects are equal; otherwise, . + public bool Equals (Toplevel x, Toplevel y) + { + if (y == null && x == null) { + return true; + } else if (x == null || y == null) { + return false; + } else if (x.Id == y.Id) { + return true; + } else { + return false; } } - /// - /// Implements the for comparing two s - /// used by . - /// - public class ToplevelEqualityComparer : IEqualityComparer { - /// Determines whether the specified objects are equal. - /// The first object of type to compare. - /// The second object of type to compare. - /// - /// if the specified objects are equal; otherwise, . - public bool Equals (Toplevel x, Toplevel y) - { - if (y == null && x == null) - return true; - else if (x == null || y == null) - return false; - else if (x.Id == y.Id) - return true; - else - return false; - } - - /// Returns a hash code for the specified object. - /// The for which a hash code is to be returned. - /// A hash code for the specified object. - /// The type of - /// is a reference type and is . - public int GetHashCode (Toplevel obj) - { - if (obj == null) - throw new ArgumentNullException (); - - int hCode = 0; - if (int.TryParse (obj.Id, out int result)) { - hCode = result; - } - return hCode.GetHashCode (); + /// Returns a hash code for the specified object. + /// The for which a hash code is to be returned. + /// A hash code for the specified object. + /// The type of + /// is a reference type and is . + public int GetHashCode (Toplevel obj) + { + if (obj == null) { + throw new ArgumentNullException (); } - } - /// - /// Implements the to sort the - /// from the if needed. - /// - public sealed class ToplevelComparer : IComparer { - /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. - /// The first object to compare. - /// The second object to compare. - /// A signed integer that indicates the relative values of and , as shown in the following table.Value Meaning Less than zero - /// is less than .Zero - /// equals .Greater than zero - /// is greater than . - public int Compare (Toplevel x, Toplevel y) - { - if (ReferenceEquals (x, y)) - return 0; - else if (x == null) - return -1; - else if (y == null) - return 1; - else - return string.Compare (x.Id, y.Id); + int hCode = 0; + if (int.TryParse (obj.Id, out int result)) { + hCode = result; } + return hCode.GetHashCode (); } } +/// +/// Implements the to sort the +/// from the if needed. +/// +public sealed class ToplevelComparer : IComparer { + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// The first object to compare. + /// The second object to compare. + /// A signed integer that indicates the relative values of and , as shown in the following table.Value Meaning Less than zero + /// is less than .Zero + /// equals .Greater than zero + /// is greater than . + public int Compare (Toplevel x, Toplevel y) + { + if (ReferenceEquals (x, y)) { + return 0; + } else if (x == null) { + return -1; + } else if (y == null) { + return 1; + } else { + return string.Compare (x.Id, y.Id); + } + } +} \ No newline at end of file diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index 4b3e5f0273..48c729999d 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -1,10 +1,15 @@ { "profiles": { "UICatalog": { + "commandName": "Project" + }, + "UICatalog --driver NetDriver": { + "commandName": "Project", + "commandLineArgs": "--driver NetDriver" + }, + "UICatalog --driver WindowsDriver": { "commandName": "Project", - "environmentVariables": { - "WT_SESSION": "yes" - } + "commandLineArgs": "--driver WindowsDriver" }, "WSL : UICatalog": { "commandName": "Executable", @@ -12,14 +17,10 @@ "commandLineArgs": "dotnet UICatalog.dll", "distributionName": "" }, - "UICatalog -usc": { - "commandName": "Project", - "commandLineArgs": "-usc" - }, - "WSL: UICatalog -usc": { + "WSL: UICatalog --driver NetDriver": { "commandName": "Executable", "executablePath": "wsl", - "commandLineArgs": "dotnet UICatalog.dll -usc", + "commandLineArgs": "dotnet UICatalog.dll --driver NetDriver", "distributionName": "" }, "Sliders": { diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 7dc7574a34..de4204b7e7 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -230,6 +230,7 @@ void JumpEdit_TextChanged (object sender, TextChangedEventArgs e) } class CharMap : ScrollView { + const CursorVisibility _cursor = CursorVisibility.Default; /// /// Specifies the starting offset for the character map. The default is 0x2500 /// which is the Box Drawing characters. @@ -296,11 +297,11 @@ class CharMap : ScrollView { public override void PositionCursor () { 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); + Cursor.X >= RowLabelWidth && + Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) && + Cursor.Y > 0 && + Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) { + Driver.SetCursorVisibility (_cursor); Move (Cursor.X, Cursor.Y); } else { Driver.SetCursorVisibility (CursorVisibility.Invisible); @@ -807,7 +808,7 @@ void ShowDetails () public override bool OnEnter (View view) { if (IsInitialized) { - Application.Driver.SetCursorVisibility (CursorVisibility.Default); + Application.Driver.SetCursorVisibility (_cursor); } return base.OnEnter (view); } diff --git a/UICatalog/Scenarios/ChineseUI.cs b/UICatalog/Scenarios/ChineseUI.cs new file mode 100644 index 0000000000..b2ebd9c756 --- /dev/null +++ b/UICatalog/Scenarios/ChineseUI.cs @@ -0,0 +1,53 @@ +ο»Ώusing Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("ChineseUI", "Chinese UI")] +[ScenarioCategory ("Unicode")] +public class ChineseUI : Scenario { + public override void Init () + { + Application.Init (); + var top = Application.Top; + + var win = new Window () { + Title = "Test", + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + top.Add (win); + + var buttonPanel = new FrameView () { + Title = "Command", + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = 5 + }; + win.Add (buttonPanel); + + var btn = new Button (1, 1, "δ½ ", true); // v1: A + btn.Clicked += (s, e) => { + int result = MessageBox.Query ("Confirm", + "Are you sure you want to quit ui?", 0, + "Yes", "No"); + if (result == 0) { + RequestStop (); + } + }; + + buttonPanel.Add ( + btn, + new Button (12, 1, "ε₯½"), // v1: B + new Button (22, 1, "ε‘€") // v1: C + ); + + Application.Run (); + } + + public override void Run () + { + } +} \ No newline at end of file diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 6f715ee93d..31f3aec37f 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -1,24 +1,23 @@ -global using CM = Terminal.Gui.ConfigurationManager; global using Attribute = Terminal.Gui.Attribute; +global using CM = Terminal.Gui.ConfigurationManager; using System; using System.Collections.Generic; +using System.CommandLine; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; using Terminal.Gui; -using System.IO; -using System.Reflection; -using System.Threading; using static Terminal.Gui.ConfigurationManager; -using System.Text.Json.Serialization; -using static Terminal.Gui.TableView; - #nullable enable -namespace UICatalog; +namespace UICatalog; /// /// UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the catalog of scenarios. @@ -51,13 +50,20 @@ namespace UICatalog; /// /// class UICatalogApp { - [SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true)] [JsonPropertyName ("UICatalog.StatusBar")] + [SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true)] + [JsonPropertyName ("UICatalog.StatusBar")] public static bool ShowStatusBar { get; set; } = true; - static readonly FileSystemWatcher _currentDirWatcher = new (); - static readonly FileSystemWatcher _homeDirWatcher = new (); + static readonly FileSystemWatcher _currentDirWatcher = new FileSystemWatcher (); + static readonly FileSystemWatcher _homeDirWatcher = new FileSystemWatcher (); + + struct Options { + public string Driver; + public string Scenario; + /* etc. */ + } - static void Main (string [] args) + static int Main (string [] args) { Console.OutputEncoding = Encoding.Default; @@ -68,22 +74,68 @@ static void Main (string [] args) _scenarios = Scenario.GetScenarios (); _categories = Scenario.GetAllCategories (); - if (args.Length > 0 && args.Contains ("-usc")) { - _useSystemConsole = true; - args = args.Where (val => val != "-usc").ToArray (); + // Process command line args + // "UICatalog [-driver ] [scenario name]" + // If no driver is provided, the default driver is used. + var driverOption = new Option ( + "--driver", + "The ConsoleDriver to use." + ).FromAmong (Application.GetDriverTypes ().Select (d => d.Name).ToArray ()); + driverOption.AddAlias ("-d"); + driverOption.AddAlias ("--d"); + + var scenarioArgument = new Argument ( + "scenario", + description: "The name of the scenario to run.", + getDefaultValue: () => "none" + ).FromAmong (_scenarios.Select (s => s.GetName ()).Append ("none").ToArray ()); + + var rootCommand = new RootCommand (description: "A comprehensive sample library for Terminal.Gui") { + scenarioArgument, + driverOption + }; + + rootCommand.SetHandler ((context) => { + Options options = new Options () { + Driver = context.ParseResult.GetValueForOption (driverOption), + Scenario = context.ParseResult.GetValueForArgument (scenarioArgument), + /* etc. */ + }; + context.ExitCode = CommandWrapper (options); + }); + + return rootCommand.Invoke (args); + } + + // See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery + static int CommandWrapper (Options options) + { + try { + UICatalogMain (options); + } catch (Exception e) { + Console.WriteLine (e.ToString()); + return 1; } + return 0; + } + static void UICatalogMain (Options options) + { StartConfigFileWatcher (); + // By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used + // regardless of what's in a config file. + Application.ForceDriver = _forceDriver = options.Driver; + // If a Scenario name has been provided on the commandline // run it and exit when done. - if (args.Length > 0) { + if (options.Scenario != "none") { _topLevelColorScheme = "Base"; - int item = _scenarios.FindIndex (s => s.GetName ().Equals (args [0], StringComparison.OrdinalIgnoreCase)); + int item = _scenarios!.FindIndex (s => s.GetName ().Equals (options.Scenario, StringComparison.OrdinalIgnoreCase)); _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!; - Application.UseSystemConsole = _useSystemConsole; - Application.Init (); + + Application.Init (driverName: _forceDriver); _selectedScenario.Theme = _cachedTheme; _selectedScenario.TopLevelColorScheme = _topLevelColorScheme; _selectedScenario.Init (); @@ -148,14 +200,14 @@ static void StartConfigFileWatcher () // Setup a file system watcher for `./.tui/` _currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite; - var assemblyLocation = Assembly.GetExecutingAssembly ().Location; + string assemblyLocation = Assembly.GetExecutingAssembly ().Location; string tuiDir; if (!string.IsNullOrEmpty (assemblyLocation)) { var assemblyFile = new FileInfo (assemblyLocation); tuiDir = Path.Combine (assemblyFile.Directory!.FullName, ".tui"); } else { - tuiDir = Path.Combine (System.AppContext.BaseDirectory, ".tui"); + tuiDir = Path.Combine (AppContext.BaseDirectory, ".tui"); } @@ -206,11 +258,14 @@ static void ConfigFileChanged (object sender, FileSystemEventArgs e) /// static Scenario RunUICatalogTopLevel () { - Application.UseSystemConsole = _useSystemConsole; // Run UI Catalog UI. When it exits, if _selectedScenario is != null then // a Scenario was selected. Otherwise, the user wants to quit UI Catalog. - Application.Init (); + + // If the user specified a driver on the command line then use it, + // ignoring Config files. + + Application.Init (driverName: _forceDriver); if (_cachedTheme is null) { _cachedTheme = Themes?.Theme; @@ -240,7 +295,7 @@ static Scenario RunUICatalogTopLevel () // If set, holds the scenario the user selected static Scenario? _selectedScenario = null; - static bool _useSystemConsole = false; + static string _forceDriver = string.Empty; static ConsoleDriver.DiagnosticFlags _diagnosticFlags; static bool _isFirstRunning = true; static string _topLevelColorScheme = string.Empty; @@ -264,7 +319,7 @@ public class UICatalogTopLevel : Toplevel { // TableView works. There's no real reason not to use ListView. Because we use TableView, and TableView // doesn't (currently) have CollectionNavigator support built in, we implement it here, within the app. public TableView ScenarioList; - CollectionNavigator _scenarioCollectionNav = new (); + CollectionNavigator _scenarioCollectionNav = new CollectionNavigator (); public StatusItem DriverName; public StatusItem OS; @@ -274,16 +329,15 @@ public UICatalogTopLevel () _themeMenuItems = CreateThemeMenuItems (); _themeMenuBarItem = new MenuBarItem ("_Themes", _themeMenuItems); MenuBar = new MenuBar (new MenuBarItem [] { - new ("_File", new MenuItem [] { - new ("_Quit", "Quit UI Catalog", RequestStop, null, null) + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Quit", "Quit UI Catalog", RequestStop, null, null) }), _themeMenuBarItem, - new ("Diag_nostics", CreateDiagnosticMenuItems ()), - new ("_Help", new MenuItem [] { - new ("_Documentation", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.GuiV2Docs"), null, null, (KeyCode)Key.F1), - new ("_README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, (KeyCode)Key.F2), - new ("_About...", - "About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage!.ToString (), 0, false, "_Ok"), null, null, (KeyCode)Key.A.WithCtrl) + new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems ()), + new MenuBarItem ("_Help", new MenuItem [] { + new MenuItem ("_Documentation", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.GuiV2Docs"), null, null, (KeyCode)Key.F1), + new MenuItem ("_README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, (KeyCode)Key.F2), + new MenuItem ("_About...", "About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage!.ToString (), 0, false, "_Ok"), null, null, (KeyCode)Key.A.WithCtrl) }) }); @@ -295,7 +349,7 @@ public UICatalogTopLevel () }; StatusBar.Items = new StatusItem [] { - new (Application.QuitKey, $"~{Application.QuitKey} to quit", () => { + new StatusItem (Application.QuitKey, $"~{Application.QuitKey} to quit", () => { if (_selectedScenario is null) { // This causes GetScenarioToRun to return null _selectedScenario = null; @@ -304,7 +358,7 @@ public UICatalogTopLevel () _selectedScenario.RequestStop (); } }), - new (Key.F10, "~F10~ Status Bar", () => { + new StatusItem (Key.F10, "~F10~ Status Bar", () => { StatusBar.Visible = !StatusBar.Visible; //ContentPane!.Height = Dim.Fill(StatusBar.Visible ? 1 : 0); LayoutSubviews (); @@ -390,8 +444,8 @@ public UICatalogTopLevel () ScenarioList.CellActivated += ScenarioView_OpenSelectedItem; // TableView typically is a grid where nav keys are biased for moving left/right. - ScenarioList.KeyBindings.Add (Key.Home, Command.TopHome); - ScenarioList.KeyBindings.Add (Key.End, Command.BottomEnd); + ScenarioList.KeyBindings.Add (Key.Home, Terminal.Gui.Command.TopHome); + ScenarioList.KeyBindings.Add (Key.End, Terminal.Gui.Command.BottomEnd); // Ideally, TableView.MultiSelect = false would turn off any keybindings for // multi-select options. But it currently does not. UI Catalog uses Ctrl-A for @@ -453,10 +507,7 @@ void UnloadedHandler (object? sender, EventArgs? args) Unloaded -= UnloadedHandler; } - void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) - { - ConfigChanged (); - } + void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) => ConfigChanged (); /// /// Launches the selected scenario, setting the global _selectedScenario @@ -516,7 +567,7 @@ void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e) miIsMenuBorderDisabled = new MenuItem { Title = "Disable Menu _Border" }; - miIsMenuBorderDisabled.Shortcut = (KeyCode)(new Key (miIsMenuBorderDisabled!.Title!.Substring (14, 1) [0])).WithAlt.WithCtrl; + miIsMenuBorderDisabled.Shortcut = (KeyCode)new Key (miIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl; miIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked; miIsMenuBorderDisabled.Action += () => { miIsMenuBorderDisabled.Checked = (bool)!miIsMenuBorderDisabled.Checked!; @@ -553,7 +604,7 @@ void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e) miIsMouseDisabled = new MenuItem { Title = "_Disable Mouse" }; - miIsMouseDisabled.Shortcut = (KeyCode)(new Key (miIsMouseDisabled!.Title!.Substring (1, 1) [0])).WithAlt.WithCtrl; + miIsMouseDisabled.Shortcut = (KeyCode)new Key (miIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl; miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked; miIsMouseDisabled.Action += () => { miIsMouseDisabled.Checked = Application.IsMouseDisabled = (bool)!miIsMouseDisabled.Checked!; @@ -592,7 +643,7 @@ void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e) foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) { var item = new MenuItem { Title = GetDiagnosticsTitle (diag), - Shortcut = (KeyCode)(new Key(index.ToString () [0])).WithAlt + Shortcut = (KeyCode)new Key (index.ToString () [0]).WithAlt }; index++; item.CheckType |= MenuItemCheckStyle.Checked; @@ -633,24 +684,18 @@ void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e) } return menuItems.ToArray (); - string GetDiagnosticsTitle (Enum diag) - { - return Enum.GetName (_diagnosticFlags.GetType (), diag) switch { - "Off" => OFF, - "FrameRuler" => FRAME_RULER, - "FramePadding" => FRAME_PADDING, - _ => "" - }; - } + string GetDiagnosticsTitle (Enum diag) => Enum.GetName (_diagnosticFlags.GetType (), diag) switch { + "Off" => OFF, + "FrameRuler" => FRAME_RULER, + "FramePadding" => FRAME_PADDING, + _ => "" + }; - Enum GetDiagnosticsEnumValue (string title) - { - return title switch { - FRAME_RULER => ConsoleDriver.DiagnosticFlags.FrameRuler, - FRAME_PADDING => ConsoleDriver.DiagnosticFlags.FramePadding, - _ => null! - }; - } + Enum GetDiagnosticsEnumValue (string title) => title switch { + FRAME_RULER => ConsoleDriver.DiagnosticFlags.FrameRuler, + FRAME_PADDING => ConsoleDriver.DiagnosticFlags.FramePadding, + _ => null! + }; void SetDiagnosticsFlag (Enum diag, bool add) { @@ -685,7 +730,7 @@ void SetDiagnosticsFlag (Enum diag, bool add) foreach (var theme in Themes!) { var item = new MenuItem { Title = $"_{theme.Key}", - Shortcut = (KeyCode)(new Key ((KeyCode)((uint)KeyCode.D1 + (schemeCount++))).WithCtrl) + Shortcut = (KeyCode)new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++)).WithCtrl }; item.CheckType |= MenuItemCheckStyle.Checked; item.Checked = theme.Key == _cachedTheme; // CM.Themes.Theme; @@ -723,13 +768,13 @@ void SetDiagnosticsFlag (Enum diag, bool add) public void ConfigChanged () { - miForce16Colors!.Checked = Application.Force16Colors; - if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme)) { _topLevelColorScheme = "Base"; } - _themeMenuItems = ((UICatalogTopLevel)Application.Top).CreateThemeMenuItems (); + _cachedTheme = Themes?.Theme; + + _themeMenuItems = CreateThemeMenuItems (); _themeMenuBarItem!.Children = _themeMenuItems; foreach (var mi in _themeMenuItems!) { if (mi is { Parent: null }) { @@ -737,25 +782,8 @@ public void ConfigChanged () } } - var checkedThemeMenu = _themeMenuItems?.Where (m => m?.Checked ?? false).FirstOrDefault (); - if (checkedThemeMenu != null) { - checkedThemeMenu.Checked = false; - } - checkedThemeMenu = _themeMenuItems?.Where (m => m != null && m.Title == Themes?.Theme).FirstOrDefault (); - if (checkedThemeMenu != null) { - Themes!.Theme = checkedThemeMenu.Title!; - checkedThemeMenu.Checked = true; - } - - var schemeMenuItems = ((MenuBarItem)_themeMenuItems?.Where (i => i is MenuBarItem)!.FirstOrDefault ()!)!.Children; - foreach (var schemeMenuItem in schemeMenuItems) { - schemeMenuItem.Checked = (string)schemeMenuItem.Data == _topLevelColorScheme; - } - ColorScheme = Colors.ColorSchemes [_topLevelColorScheme]; - //ContentPane.LineStyle = FrameView.DefaultBorderStyle; - MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey; StatusBar.Items [0].Shortcut = Application.QuitKey; StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit"; @@ -763,7 +791,7 @@ public void ConfigChanged () miIsMouseDisabled!.Checked = Application.IsMouseDisabled; int height = ShowStatusBar ? 1 : 0; // + (MenuBar.Visible ? 1 : 0); - //ContentPane.Height = Dim.Fill (height); + //ContentPane.Height = Dim.Fill (height); StatusBar.Visible = ShowStatusBar; diff --git a/UICatalog/UICatalog.csproj b/UICatalog/UICatalog.csproj index d0a9e63bb3..a717e41bcd 100644 --- a/UICatalog/UICatalog.csproj +++ b/UICatalog/UICatalog.csproj @@ -32,6 +32,7 @@ + diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 3fa25cc4c4..0b49293fb9 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -1,5 +1,6 @@ ο»Ώusing System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; @@ -17,7 +18,9 @@ public class ApplicationTests { public ApplicationTests (ITestOutputHelper output) { - this._output = output; + _output = output; + ConsoleDriver.RunningUnitTests = true; + #if DEBUG_IDISPOSABLE Responder.Instances.Clear (); RunState.Instances.Clear (); @@ -52,10 +55,7 @@ void Init () Assert.NotNull (SynchronizationContext.Current); } - void Shutdown () - { - Application.Shutdown (); - } + void Shutdown () => Application.Shutdown (); [Fact] public void Init_Shutdown_Cleans_Up () @@ -73,10 +73,10 @@ public void Init_Shutdown_Cleans_Up () // Verify state is back to initial //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. - Assert.Empty (Responder.Instances); + // Validate there are no outstanding Responder-based instances + // after a scenario was selected to run. This proves the main UI Catalog + // 'app' closed cleanly. + Assert.Empty (Responder.Instances); #endif } @@ -106,10 +106,7 @@ public void Init_Unbalanced_Throws () } class TestToplevel : Toplevel { - public TestToplevel () - { - IsOverlappedContainer = false; - } + public TestToplevel () => IsOverlappedContainer = false; } [Fact] @@ -122,6 +119,21 @@ public void Init_Null_Driver_Should_Pick_A_Driver () Shutdown (); } + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Application.Init (driverName: driverType.Name); + Assert.NotNull (Application.Driver); + Assert.Equal (driverType, Application.Driver.GetType ()); + Shutdown (); + } + [Fact] public void Init_Begin_End_Cleans_Up () { @@ -140,7 +152,7 @@ public void Init_Begin_End_Cleans_Up () }; Application.NotifyNewRunState += NewRunStateFn; - Toplevel topLevel = new Toplevel (); + var topLevel = new Toplevel (); var rs = Application.Begin (topLevel); Assert.NotNull (rs); Assert.NotNull (runstate); @@ -225,7 +237,6 @@ public void Begin_Null_Toplevel_Throws () } #region RunTests - [Fact] public void Run_T_After_InitWithDriver_with_TopLevel_Throws () { @@ -249,7 +260,7 @@ public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Throws () Init (); // Run when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel) - Assert.Throws (() => Application.Run (errorHandler: null, new FakeDriver ())); + Assert.Throws (() => Application.Run (null, new FakeDriver ())); Shutdown (); @@ -281,7 +292,7 @@ public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow () [Fact] public void Run_T_After_InitNullDriver_with_TestTopLevel_Throws () { - Application._forceFakeConsole = true; + Application.ForceDriver = "FakeDriver"; Application.Init (null); Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ()); @@ -324,7 +335,7 @@ public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws () [Fact] public void Run_T_NoInit_DoesNotThrow () { - Application._forceFakeConsole = true; + Application.ForceDriver = "FakeDriver"; Application.Iteration += (s, a) => { Application.RequestStop (); @@ -348,7 +359,7 @@ public void Run_T_NoInit_WithDriver_DoesNotThrow () }; // Init has NOT been called and we're passing a valid driver to Run. This is ok. - Application.Run (errorHandler: null, new FakeDriver ()); + Application.Run (null, new FakeDriver ()); Shutdown (); @@ -410,7 +421,7 @@ public void Run_Loaded_Ready_Unlodaded_Events () { Init (); var top = Application.Top; - var count = 0; + int count = 0; top.Loaded += (s, e) => count++; top.Ready += (s, e) => count++; top.Unloaded += (s, e) => count++; @@ -425,12 +436,12 @@ public void Run_Loaded_Ready_Unlodaded_Events () public void Run_Toplevel_With_Modal_View_Does_Not_Refresh_If_Not_Dirty () { Init (); - var count = 0; + int count = 0; // Don't use Dialog here as it has more layout logic. Use Window instead. Dialog d = null; var top = Application.Top; top.DrawContent += (s, a) => count++; - var iteration = -1; + int iteration = -1; Application.Iteration += (s, a) => { iteration++; if (iteration == 0) { @@ -439,7 +450,7 @@ public void Run_Toplevel_With_Modal_View_Does_Not_Refresh_If_Not_Dirty () d.DrawContent += (s, a) => count++; Application.Run (d); } else if (iteration < 3) { - Application.OnMouseEvent (new (new () { X = 0, Y = 0, Flags = MouseFlags.ReportMousePosition })); + Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent { X = 0, Y = 0, Flags = MouseFlags.ReportMousePosition })); Assert.False (top.NeedsDisplay); Assert.False (top.SubViewNeedsDisplay); Assert.False (top.LayoutNeeded); @@ -521,7 +532,6 @@ public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving () } // TODO: Add tests for Run that test errorHandler - #endregion #region ShutdownTests @@ -556,7 +566,7 @@ public void Shutdown_Resets_SyncContext () } #endregion - [Fact, AutoInitShutdown] + [Fact] [AutoInitShutdown] public void Begin_Sets_Application_Top_To_Console_Size () { Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame); @@ -580,7 +590,7 @@ public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Applica var t4 = new Toplevel (); // t1, t2, t3, d, t4 - var iterations = 5; + int iterations = 5; t1.Ready += (s, e) => { Assert.Equal (t1, Application.Top); @@ -667,7 +677,7 @@ public void Invoke_Adds_Idle () var rs = Application.Begin (top); bool firstIteration = false; - var actionCalled = 0; + int actionCalled = 0; Application.Invoke (() => { actionCalled++; }); Application.MainLoop.Running = true; Application.RunIteration (ref rs, ref firstIteration); diff --git a/UnitTests/ConsoleDrivers/AddRuneTests.cs b/UnitTests/ConsoleDrivers/AddRuneTests.cs index 5381de84cd..7650ccedb2 100644 --- a/UnitTests/ConsoleDrivers/AddRuneTests.cs +++ b/UnitTests/ConsoleDrivers/AddRuneTests.cs @@ -7,20 +7,22 @@ // 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) { ConsoleDriver.RunningUnitTests = true; - this._output = output; + _output = output; } [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] - [InlineData (typeof (CursesDriver))] + //[InlineData (typeof (ANSIDriver))] [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] public void AddRune (Type driverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); @@ -44,8 +46,8 @@ public void AddRune_InvalidLocation_DoesNothing () 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++) { + for (int col = 0; col < driver.Cols; col++) { + for (int row = 0; row < driver.Rows; row++) { Assert.Equal ((Rune)' ', driver.Contents [row, col].Rune); } } @@ -70,7 +72,7 @@ public void AddRune_MovesToNextColumn () Assert.Equal (2, driver.Col); // Move to the last column of the first row - var lastCol = driver.Cols - 1; + int lastCol = driver.Cols - 1; driver.Move (lastCol, 0); Assert.Equal (0, driver.Row); Assert.Equal (lastCol, driver.Col); @@ -83,8 +85,8 @@ public void AddRune_MovesToNextColumn () // 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++) { + for (int col = 0; col < driver.Cols; col++) { + for (int row = 0; row < driver.Rows; row++) { Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Rune); } } @@ -99,7 +101,7 @@ public void AddRune_MovesToNextColumn_Wide () driver.Init (); // πŸ• Slice of Pizza "\U0001F355" - var operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out Rune rune, out int charsConsumed); + var operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out var rune, out int charsConsumed); Assert.Equal (OperationStatus.Done, operationStatus); Assert.Equal (charsConsumed, rune.Utf16SequenceLength); Assert.Equal (2, rune.GetColumns ()); @@ -145,7 +147,7 @@ public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars () var expected = new Rune ('αΊ―'); - var text = "\u1eaf"; + string text = "\u1eaf"; driver.AddStr (text); Assert.Equal (expected, driver.Contents [0, 0].Rune); Assert.Equal ((Rune)' ', driver.Contents [0, 1].Rune); @@ -188,4 +190,4 @@ public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars () //αΊ―", output); driver.End (); } -} +} \ No newline at end of file diff --git a/UnitTests/ConsoleDrivers/ClipRegionTests.cs b/UnitTests/ConsoleDrivers/ClipRegionTests.cs index 3f229e2365..2247e08665 100644 --- a/UnitTests/ConsoleDrivers/ClipRegionTests.cs +++ b/UnitTests/ConsoleDrivers/ClipRegionTests.cs @@ -9,108 +9,125 @@ // 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].Rune); - - driver.Move (5, 5); - driver.AddRune ('x'); - Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune); - - // Clear the contents - driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), ' '); - Assert.Equal ((Rune)' ', driver.Contents [0, 0].Rune); - - // 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].Rune); - Assert.Equal ((Rune)' ', driver.Contents [4, 9].Rune); - Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune); - Assert.Equal ((Rune)'x', driver.Contents [9, 9].Rune); - Assert.Equal ((Rune)' ', driver.Contents [10, 10].Rune); - - Application.Shutdown (); - } +namespace Terminal.Gui.DriverTests; + +public class ClipRegionTests { + readonly ITestOutputHelper output; + + public ClipRegionTests (ITestOutputHelper output) + { + ConsoleDriver.RunningUnitTests = true; + this.output = output; + } + + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void IsValidLocation (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + Application.Driver.Rows = 10; + Application.Driver.Cols = 10; + + // 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))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void Clip_Set_To_Empty_AllInvalid (Type driverType) + { + var driver = (ConsoleDriver)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))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void AddRune_Is_Clipped (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + Application.Driver.Rows = 25; + Application.Driver.Cols = 80; + + driver.Move (0, 0); + driver.AddRune ('x'); + Assert.Equal ((Rune)'x', driver.Contents [0, 0].Rune); + + driver.Move (5, 5); + driver.AddRune ('x'); + Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune); + + // Clear the contents + driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), ' '); + Assert.Equal ((Rune)' ', driver.Contents [0, 0].Rune); + + // 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].Rune); + Assert.Equal ((Rune)' ', driver.Contents [4, 9].Rune); + Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune); + Assert.Equal ((Rune)'x', driver.Contents [9, 9].Rune); + Assert.Equal ((Rune)' ', driver.Contents [10, 10].Rune); + + Application.Shutdown (); } -} +} \ No newline at end of file diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs index d3c09b80e2..d0281c4f4f 100644 --- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs @@ -8,264 +8,271 @@ // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -namespace Terminal.Gui.DriverTests { - public class ConsoleDriverTests { - readonly ITestOutputHelper output; - - public ConsoleDriverTests (ITestOutputHelper output) - { - ConsoleDriver.RunningUnitTests = true; - this.output = output; - } +namespace Terminal.Gui.DriverTests; - [Theory] - [InlineData (typeof (FakeDriver))] - [InlineData (typeof (NetDriver))] - [InlineData (typeof (CursesDriver))] - [InlineData (typeof (WindowsDriver))] - public void Init_Inits (Type driverType) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - var ml = driver.Init (); - Assert.NotNull (ml); - Assert.NotNull (driver.Clipboard); - Console.ForegroundColor = ConsoleColor.Red; - Assert.Equal (ConsoleColor.Red, Console.ForegroundColor); - Console.BackgroundColor = ConsoleColor.Green; - Assert.Equal (ConsoleColor.Green, Console.BackgroundColor); - - driver.End (); - } +public class ConsoleDriverTests { + readonly ITestOutputHelper output; - [Theory] - [InlineData (typeof (FakeDriver))] - [InlineData (typeof (NetDriver))] - [InlineData (typeof (CursesDriver))] - [InlineData (typeof (WindowsDriver))] - public void End_Cleans_Up (Type driverType) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - driver.Init (); - driver.End (); - } + public ConsoleDriverTests (ITestOutputHelper output) + { + ConsoleDriver.RunningUnitTests = true; + this.output = output; + } - [Theory] - [InlineData (typeof (FakeDriver))] - public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - Application.Init (driver); + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void Init_Inits (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + var ml = driver.Init (); + Assert.NotNull (ml); + Assert.NotNull (driver.Clipboard); + Console.ForegroundColor = ConsoleColor.Red; + Assert.Equal (ConsoleColor.Red, Console.ForegroundColor); + Console.BackgroundColor = ConsoleColor.Green; + Assert.Equal (ConsoleColor.Green, Console.BackgroundColor); + + driver.End (); + } - var top = Application.Top; - var view = new View () { - CanFocus = true - }; - var count = 0; - var wasKeyPressed = false; + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void End_Cleans_Up (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + driver.Init (); + driver.End (); + } - view.KeyDown += (s, e) => { - wasKeyPressed = true; - }; - top.Add (view); + [Theory] + [InlineData (typeof (FakeDriver))] + public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + + var top = Application.Top; + var view = new View () { + CanFocus = true + }; + int count = 0; + bool wasKeyPressed = false; + + view.KeyDown += (s, e) => { + wasKeyPressed = true; + }; + top.Add (view); + + Application.Iteration += (s, a) => { + count++; + if (count == 10) { + Application.RequestStop (); + } + }; - Application.Iteration += (s, a) => { - count++; - if (count == 10) Application.RequestStop (); - }; + Application.Run (); - Application.Run (); + Assert.False (wasKeyPressed); - Assert.False (wasKeyPressed); + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); + [Theory] + [InlineData (typeof (FakeDriver))] + public void FakeDriver_MockKeyPresses (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + + string text = "MockKeyPresses"; + var mKeys = new Stack (); + foreach (char r in text.Reverse ()) { + var ck = char.IsLetter (r) ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r; + var cki = new ConsoleKeyInfo (r, ck, false, false, false); + mKeys.Push (cki); } - - [Theory] - [InlineData (typeof (FakeDriver))] - public void FakeDriver_MockKeyPresses (Type driverType) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - Application.Init (driver); - - var text = "MockKeyPresses"; - var mKeys = new Stack (); - foreach (var r in text.Reverse ()) { - var ck = char.IsLetter (r) ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r; - var cki = new ConsoleKeyInfo (r, ck, false, false, false); - mKeys.Push (cki); + Console.MockKeyPresses = mKeys; + + var top = Application.Top; + var view = new View () { + CanFocus = true + }; + string rText = ""; + int idx = 0; + + view.KeyDown += (s, e) => { + Assert.Equal (text [idx], (char)e.KeyCode); + rText += (char)e.KeyCode; + Assert.Equal (rText, text.Substring (0, idx + 1)); + e.Handled = true; + idx++; + }; + top.Add (view); + + Application.Iteration += (s, a) => { + if (mKeys.Count == 0) { + Application.RequestStop (); } - Console.MockKeyPresses = mKeys; - - var top = Application.Top; - var view = new View () { - CanFocus = true - }; - var rText = ""; - var idx = 0; - - view.KeyDown += (s, e) => { - Assert.Equal (text [idx], (char)e.KeyCode); - rText += (char)e.KeyCode; - Assert.Equal (rText, text.Substring (0, idx + 1)); - e.Handled = true; - idx++; - }; - top.Add (view); - - Application.Iteration += (s, a) => { - if (mKeys.Count == 0) Application.RequestStop (); - }; - - Application.Run (); - - Assert.Equal ("MockKeyPresses", rText); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } + }; - //[Theory] - //[InlineData (typeof (FakeDriver))] - //public void FakeDriver_MockKeyPresses_Press_AfterTimeOut (Type driverType) - //{ - // var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - // Application.Init (driver); - - // // Simulating pressing of QuitKey after a short period of time - // uint quitTime = 100; - // Func closeCallback = (MainLoop loop) => { - // // Prove the scenario is using Application.QuitKey correctly - // output.WriteLine ($" {quitTime}ms elapsed; Simulating keypresses..."); - // FakeConsole.PushMockKeyPress (Key.F); - // FakeConsole.PushMockKeyPress (Key.U); - // FakeConsole.PushMockKeyPress (Key.C); - // FakeConsole.PushMockKeyPress (Key.K); - // return false; - // }; - // output.WriteLine ($"Add timeout to simulate key presses after {quitTime}ms"); - // _ = Application.AddTimeout (TimeSpan.FromMilliseconds (quitTime), closeCallback); - - // // If Top doesn't quit within abortTime * 5 (500ms), this will force it - // uint abortTime = quitTime * 5; - // Func forceCloseCallback = (MainLoop loop) => { - // Application.RequestStop (); - // Assert.Fail ($" failed to Quit after {abortTime}ms. Force quit."); - // return false; - // }; - // output.WriteLine ($"Add timeout to force quit after {abortTime}ms"); - // _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); - - // Key key = Key.Unknown; - - // Application.Top.KeyPress += (e) => { - // key = e.Key; - // output.WriteLine ($" Application.Top.KeyPress: {key}"); - // e.Handled = true; - - // }; - - // int iterations = 0; - // Application.Iteration += (s, a) => { - // output.WriteLine ($" iteration {++iterations}"); - - // if (Console.MockKeyPresses.Count == 0) { - // output.WriteLine ($" No more MockKeyPresses; RequestStop"); - // Application.RequestStop (); - // } - // }; - - // Application.Run (); - - // // 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 TerminalResized_Simulation (Type driverType) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - driver?.Init (); - driver.Cols = 80; - driver.Rows = 25; - - var wasTerminalResized = false; - driver.SizeChanged += (s, e) => { - wasTerminalResized = true; - Assert.Equal (120, e.Size.Width); - Assert.Equal (40, e.Size.Height); - }; - - Assert.Equal (80, driver.Cols); - Assert.Equal (25, driver.Rows); - Assert.False (wasTerminalResized); - - driver.Cols = 120; - driver.Rows = 40; - driver.OnSizeChanged (new SizeChangedEventArgs(new Size(driver.Cols, driver.Rows))); - Assert.Equal (120, driver.Cols); - Assert.Equal (40, driver.Rows); - Assert.True (wasTerminalResized); - driver.End (); - } + Application.Run (); + + Assert.Equal ("MockKeyPresses", rText); - // Disabled due to test error - Change Task.Delay to an await - // [Fact, AutoInitShutdown] - // public void Write_Do_Not_Change_On_ProcessKey () - // { - // var win = new Window (); - // Application.Begin (win); - // ((FakeDriver)Application.Driver).SetBufferSize (20, 8); - - // System.Threading.Tasks.Task.Run (() => { - // System.Threading.Tasks.Task.Delay (500).Wait (); - // Application.Invoke (() => { - // var lbl = new Label ("Hello World") { X = Pos.Center () }; - // var dlg = new Dialog (); - // dlg.Add (lbl); - // Application.Begin (dlg); - - // var expected = @" - //β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - //β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ - //β”‚β”‚ Hello World β”‚ β”‚ - //β”‚β”‚ β”‚ β”‚ - //β”‚β”‚ β”‚ β”‚ - //β”‚β”‚ β”‚ β”‚ - //β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - //β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - //"; - - // var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - // Assert.Equal (new Rect (0, 0, 20, 8), pos); - - // Assert.True (dlg.ProcessKey (new (Key.Tab))); - // dlg.Draw (); - - // expected = @" - //β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - //β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ - //β”‚β”‚ Hello World β”‚ β”‚ - //β”‚β”‚ β”‚ β”‚ - //β”‚β”‚ β”‚ β”‚ - //β”‚β”‚ β”‚ β”‚ - //β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - //β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - //"; - - // pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - // Assert.Equal (new Rect (0, 0, 20, 8), pos); - - // win.RequestStop (); - // }); - // }); - - // Application.Run (win); - // Application.Shutdown (); - // } + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); } -} + + //[Theory] + //[InlineData (typeof (FakeDriver))] + //public void FakeDriver_MockKeyPresses_Press_AfterTimeOut (Type driverType) + //{ + // var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + // Application.Init (driver); + + // // Simulating pressing of QuitKey after a short period of time + // uint quitTime = 100; + // Func closeCallback = (MainLoop loop) => { + // // Prove the scenario is using Application.QuitKey correctly + // output.WriteLine ($" {quitTime}ms elapsed; Simulating keypresses..."); + // FakeConsole.PushMockKeyPress (Key.F); + // FakeConsole.PushMockKeyPress (Key.U); + // FakeConsole.PushMockKeyPress (Key.C); + // FakeConsole.PushMockKeyPress (Key.K); + // return false; + // }; + // output.WriteLine ($"Add timeout to simulate key presses after {quitTime}ms"); + // _ = Application.AddTimeout (TimeSpan.FromMilliseconds (quitTime), closeCallback); + + // // If Top doesn't quit within abortTime * 5 (500ms), this will force it + // uint abortTime = quitTime * 5; + // Func forceCloseCallback = (MainLoop loop) => { + // Application.RequestStop (); + // Assert.Fail ($" failed to Quit after {abortTime}ms. Force quit."); + // return false; + // }; + // output.WriteLine ($"Add timeout to force quit after {abortTime}ms"); + // _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback); + + // Key key = Key.Unknown; + + // Application.Top.KeyPress += (e) => { + // key = e.Key; + // output.WriteLine ($" Application.Top.KeyPress: {key}"); + // e.Handled = true; + + // }; + + // int iterations = 0; + // Application.Iteration += (s, a) => { + // output.WriteLine ($" iteration {++iterations}"); + + // if (Console.MockKeyPresses.Count == 0) { + // output.WriteLine ($" No more MockKeyPresses; RequestStop"); + // Application.RequestStop (); + // } + // }; + + // Application.Run (); + + // // 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 (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void TerminalResized_Simulation (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + driver?.Init (); + driver.Cols = 80; + driver.Rows = 25; + + bool wasTerminalResized = false; + driver.SizeChanged += (s, e) => { + wasTerminalResized = true; + Assert.Equal (120, e.Size.Width); + Assert.Equal (40, e.Size.Height); + }; + + Assert.Equal (80, driver.Cols); + Assert.Equal (25, driver.Rows); + Assert.False (wasTerminalResized); + + driver.Cols = 120; + driver.Rows = 40; + driver.OnSizeChanged (new SizeChangedEventArgs (new Size (driver.Cols, driver.Rows))); + Assert.Equal (120, driver.Cols); + Assert.Equal (40, driver.Rows); + Assert.True (wasTerminalResized); + driver.End (); + } + + // Disabled due to test error - Change Task.Delay to an await + // [Fact, AutoInitShutdown] + // public void Write_Do_Not_Change_On_ProcessKey () + // { + // var win = new Window (); + // Application.Begin (win); + // ((FakeDriver)Application.Driver).SetBufferSize (20, 8); + + // System.Threading.Tasks.Task.Run (() => { + // System.Threading.Tasks.Task.Delay (500).Wait (); + // Application.Invoke (() => { + // var lbl = new Label ("Hello World") { X = Pos.Center () }; + // var dlg = new Dialog (); + // dlg.Add (lbl); + // Application.Begin (dlg); + + // var expected = @" + //β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + //β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + //β”‚β”‚ Hello World β”‚ β”‚ + //β”‚β”‚ β”‚ β”‚ + //β”‚β”‚ β”‚ β”‚ + //β”‚β”‚ β”‚ β”‚ + //β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + //β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + //"; + + // var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + // Assert.Equal (new Rect (0, 0, 20, 8), pos); + + // Assert.True (dlg.ProcessKey (new (Key.Tab))); + // dlg.Draw (); + + // expected = @" + //β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + //β”‚β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + //β”‚β”‚ Hello World β”‚ β”‚ + //β”‚β”‚ β”‚ β”‚ + //β”‚β”‚ β”‚ β”‚ + //β”‚β”‚ β”‚ β”‚ + //β”‚β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + //β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + //"; + + // pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + // Assert.Equal (new Rect (0, 0, 20, 8), pos); + + // win.RequestStop (); + // }); + // }); + + // Application.Run (win); + // Application.Shutdown (); + // } +} \ No newline at end of file diff --git a/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs b/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs index 9bd550ca77..abbf5eea75 100644 --- a/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs +++ b/UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs @@ -8,31 +8,35 @@ // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -namespace Terminal.Gui.DriverTests { - public class ConsoleScrollingTests { - readonly ITestOutputHelper output; - - public ConsoleScrollingTests (ITestOutputHelper output) - { - this.output = output; - } - - [Theory] - [InlineData (typeof (FakeDriver))] - public void Left_And_Top_Is_Always_Zero (Type driverType) - { - var driver = (FakeDriver)Activator.CreateInstance (driverType); - Application.Init (driver); - - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - driver.SetWindowPosition (5, 5); - Assert.Equal (0, Console.WindowLeft); - Assert.Equal (0, Console.WindowTop); - - Application.Shutdown (); - } - +namespace Terminal.Gui.DriverTests; + +public class ConsoleScrollingTests { + readonly ITestOutputHelper output; + + public ConsoleScrollingTests (ITestOutputHelper output) + { + ConsoleDriver.RunningUnitTests = true; + this.output = output; + } + + [Theory] + [InlineData (typeof (FakeDriver))] + //[InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + //[InlineData (typeof (WindowsDriver))] + //[InlineData (typeof (CursesDriver))] + public void Left_And_Top_Is_Always_Zero (Type driverType) + { + var driver = (FakeDriver)Activator.CreateInstance (driverType); + Application.Init (driver); + + Assert.Equal (0, Console.WindowLeft); + Assert.Equal (0, Console.WindowTop); + + driver.SetWindowPosition (5, 5); + Assert.Equal (0, Console.WindowLeft); + Assert.Equal (0, Console.WindowTop); + + Application.Shutdown (); } -} +} \ No newline at end of file diff --git a/UnitTests/ConsoleDrivers/ContentsTests.cs b/UnitTests/ConsoleDrivers/ContentsTests.cs index 235fcff152..dfd9a98d79 100644 --- a/UnitTests/ConsoleDrivers/ContentsTests.cs +++ b/UnitTests/ConsoleDrivers/ContentsTests.cs @@ -10,6 +10,7 @@ using Console = Terminal.Gui.FakeConsole; namespace Terminal.Gui.DriverTests; + public class ContentsTests { readonly ITestOutputHelper output; @@ -23,13 +24,14 @@ public ContentsTests (ITestOutputHelper output) [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] //[InlineData (typeof (CursesDriver))] // TODO: Uncomment when #2796 and #2615 are fixed //[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed public void AddStr_Combining_Character_1st_Column (Type driverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); - var expected = "\u0301!"; + string expected = "\u0301!"; driver.AddStr ("\u0301!"); // acute accent + exclamation mark TestHelpers.AssertDriverContentsAre (expected, output, driver); @@ -39,6 +41,7 @@ public void AddStr_Combining_Character_1st_Column (Type driverType) [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] //[InlineData (typeof (CursesDriver))] // TODO: Uncomment when #2796 and #2615 are fixed //[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed public void AddStr_With_Combining_Characters (Type driverType) @@ -46,18 +49,18 @@ public void AddStr_With_Combining_Characters (Type driverType) var driver = (ConsoleDriver)Activator.CreateInstance (driverType); driver.Init (); - var acuteaccent = new System.Text.Rune (0x0301); // Combining acute accent (Γ©) - var combined = "e" + acuteaccent; - var expected = "Γ©"; + var acuteaccent = new Rune (0x0301); // Combining acute accent (Γ©) + string combined = "e" + acuteaccent; + string expected = "Γ©"; driver.AddStr (combined); TestHelpers.AssertDriverContentsAre (expected, output, driver); // 3 char combine // a + ogonek + acute = ( ą́ ) - var ogonek = new System.Text.Rune (0x0328); // Combining ogonek (a small hook or comma shape) + var ogonek = new Rune (0x0328); // Combining ogonek (a small hook or comma shape) combined = "a" + ogonek + acuteaccent; - expected = ("a" + ogonek).Normalize(NormalizationForm.FormC); // See Issue #2616 + expected = ("a" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616 driver.Move (0, 0); driver.AddStr (combined); @@ -93,8 +96,9 @@ public void AddStr_With_Combining_Characters (Type driverType) [Theory] [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] - [InlineData (typeof (CursesDriver))] + //[InlineData (typeof (ANSIDriver))] [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] public void Move_Bad_Coordinates (Type driverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); @@ -160,5 +164,4 @@ public void Move_Bad_Coordinates (Type driverType) // Refresh works correctly // IsDirty tests -} - +} \ No newline at end of file diff --git a/UnitTests/ConsoleDrivers/DriverColorTests.cs b/UnitTests/ConsoleDrivers/DriverColorTests.cs index 5a44ba6f7d..299aae7c11 100644 --- a/UnitTests/ConsoleDrivers/DriverColorTests.cs +++ b/UnitTests/ConsoleDrivers/DriverColorTests.cs @@ -4,69 +4,69 @@ // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -namespace Terminal.Gui.DriverTests { - public class DriverColorTests { - public DriverColorTests () - { - ConsoleDriver.RunningUnitTests = true; - } - - [Theory] - [InlineData (typeof (FakeDriver))] - [InlineData (typeof (NetDriver))] - [InlineData (typeof (CursesDriver))] - [InlineData (typeof (WindowsDriver))] - public void SetColors_Changes_Colors (Type driverType) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - driver.Init (); - - Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor); - Assert.Equal (ConsoleColor.Black, Console.BackgroundColor); - - Console.ForegroundColor = ConsoleColor.Red; - Assert.Equal (ConsoleColor.Red, Console.ForegroundColor); - - Console.BackgroundColor = ConsoleColor.Green; - Assert.Equal (ConsoleColor.Green, Console.BackgroundColor); - - Console.ResetColor (); - Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor); - Assert.Equal (ConsoleColor.Black, Console.BackgroundColor); - - driver.End (); - } - - - [Theory] - [InlineData (typeof (FakeDriver), false)] - [InlineData (typeof (NetDriver), true)] - [InlineData (typeof (CursesDriver), false)] - [InlineData (typeof (WindowsDriver), true)] // Because we're not Windows Terminal - public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - driver.Init (); - - Assert.Equal (expectedSetting, driver.SupportsTrueColor); - - driver.End (); - } - - [Theory] - [InlineData (typeof (FakeDriver))] - [InlineData (typeof (NetDriver))] - [InlineData (typeof (CursesDriver))] - [InlineData (typeof (WindowsDriver))] - public void Force16Colors_Sets (Type driverType) - { - var driver = (ConsoleDriver)Activator.CreateInstance (driverType); - driver.Init (); - - driver.Force16Colors = true; - Assert.True (driver.Force16Colors); - - driver.End (); - } +namespace Terminal.Gui.DriverTests; + +public class DriverColorTests { + public DriverColorTests () => ConsoleDriver.RunningUnitTests = true; + + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void SetColors_Changes_Colors (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + driver.Init (); + + Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor); + Assert.Equal (ConsoleColor.Black, Console.BackgroundColor); + + Console.ForegroundColor = ConsoleColor.Red; + Assert.Equal (ConsoleColor.Red, Console.ForegroundColor); + + Console.BackgroundColor = ConsoleColor.Green; + Assert.Equal (ConsoleColor.Green, Console.BackgroundColor); + + Console.ResetColor (); + Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor); + Assert.Equal (ConsoleColor.Black, Console.BackgroundColor); + + driver.End (); + } + + + [Theory] + [InlineData (typeof (FakeDriver), false)] + [InlineData (typeof (NetDriver), true)] + //[InlineData (typeof (ANSIDriver), true)] + [InlineData (typeof (WindowsDriver), true)] + [InlineData (typeof (CursesDriver), false)] + public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + driver.Init (); + + Assert.Equal (expectedSetting, driver.SupportsTrueColor); + + driver.End (); + } + + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + //[InlineData (typeof (ANSIDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void Force16Colors_Sets (Type driverType) + { + var driver = (ConsoleDriver)Activator.CreateInstance (driverType); + driver.Init (); + + driver.Force16Colors = true; + Assert.True (driver.Force16Colors); + + driver.End (); } } \ No newline at end of file diff --git a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs index 01bea55f69..c5fc8d0901 100644 --- a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs +++ b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs @@ -12,17 +12,14 @@ namespace Terminal.Gui.DriverTests; public class MainLoopDriverTests { - - public MainLoopDriverTests (ITestOutputHelper output) - { - ConsoleDriver.RunningUnitTests = true; - } + public MainLoopDriverTests (ITestOutputHelper output) => ConsoleDriver.RunningUnitTests = true; [Theory] [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); @@ -45,20 +42,21 @@ public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverTy Assert.Empty (mainLoop.Timeouts); Assert.False (mainLoop.Running); } - + [Theory] [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))] [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); var mainLoop = new MainLoop (mainLoopDriver); - var callbackInvoked = false; + bool callbackInvoked = false; - var token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => { + object token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => { callbackInvoked = true; return false; }); @@ -77,14 +75,15 @@ public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, T [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); var mainLoop = new MainLoop (mainLoopDriver); - var token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); - var result = mainLoop.RemoveTimeout (token); + object token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); + bool result = mainLoop.RemoveTimeout (token); Assert.True (result); mainLoop.Dispose (); @@ -95,13 +94,14 @@ public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); var mainLoop = new MainLoop (mainLoopDriver); - var result = mainLoop.RemoveTimeout (new object ()); + bool result = mainLoop.RemoveTimeout (new object ()); Assert.False (result); } @@ -111,12 +111,13 @@ public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, T [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_AddIdle_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); var mainLoop = new MainLoop (mainLoopDriver); - var idleHandlerInvoked = false; + bool idleHandlerInvoked = false; bool IdleHandler () { @@ -124,7 +125,7 @@ bool IdleHandler () return false; } - Func token = mainLoop.AddIdle (IdleHandler); + var token = mainLoop.AddIdle (IdleHandler); Assert.NotNull (token); Assert.False (idleHandlerInvoked); // Idle handler should not be invoked immediately @@ -138,6 +139,7 @@ bool IdleHandler () [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); @@ -145,8 +147,8 @@ public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type ma var mainLoop = new MainLoop (mainLoopDriver); bool IdleHandler () => false; - Func token = mainLoop.AddIdle (IdleHandler); - var result = mainLoop.RemoveIdle (token); + var token = mainLoop.AddIdle (IdleHandler); + bool result = mainLoop.RemoveIdle (token); Assert.True (result); mainLoop.Dispose (); @@ -157,13 +159,14 @@ public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type ma [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); var mainLoop = new MainLoop (mainLoopDriver); - var result = mainLoop.RemoveIdle (() => false); + bool result = mainLoop.RemoveIdle (() => false); Assert.False (result); mainLoop.Dispose (); @@ -174,14 +177,15 @@ public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); var mainLoop = new MainLoop (mainLoopDriver); - var idleHandlerInvoked = false; + bool idleHandlerInvoked = false; - Func idleHandler = () => { + var idleHandler = () => { idleHandlerInvoked = true; return false; }; @@ -198,13 +202,14 @@ public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driver [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_CheckTimersAndIdleHandlers_NoTimersOrIdleHandlers_ReturnsFalse (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver }); var mainLoop = new MainLoop (mainLoopDriver); - var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout); + bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout); Assert.False (result); Assert.Equal (-1, waitTimeout); @@ -216,6 +221,7 @@ public void MainLoop_CheckTimersAndIdleHandlers_NoTimersOrIdleHandlers_ReturnsFa [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_CheckTimersAndIdleHandlers_TimersActive_ReturnsTrue (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); @@ -223,7 +229,7 @@ public void MainLoop_CheckTimersAndIdleHandlers_TimersActive_ReturnsTrue (Type d var mainLoop = new MainLoop (mainLoopDriver); mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); - var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout); + bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout); Assert.True (result); Assert.True (waitTimeout >= 0); @@ -235,6 +241,7 @@ public void MainLoop_CheckTimersAndIdleHandlers_TimersActive_ReturnsTrue (Type d [InlineData (typeof (NetDriver), typeof (NetMainLoop))] [InlineData (typeof (CursesDriver), typeof (UnixMainLoop))] [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] + //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] public void MainLoop_CheckTimersAndIdleHandlers_IdleHandlersActive_ReturnsTrue (Type driverType, Type mainLoopDriverType) { var driver = (ConsoleDriver)Activator.CreateInstance (driverType); @@ -242,7 +249,7 @@ public void MainLoop_CheckTimersAndIdleHandlers_IdleHandlersActive_ReturnsTrue ( var mainLoop = new MainLoop (mainLoopDriver); mainLoop.AddIdle (() => false); - var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout); + bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout); Assert.True (result); Assert.Equal (-1, waitTimeout); @@ -267,4 +274,4 @@ public void MainLoop_CheckTimersAndIdleHandlers_IdleHandlersActive_ReturnsTrue ( // Assert.True (actionInvoked); // mainLoop.Dispose (); //} -} +} \ No newline at end of file