diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f854f34f1..5ca55ca34 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,25 +1,29 @@ { "name": "Terminal.Gui Codespace", - "image": "mcr.microsoft.com/vscode/devcontainers/dotnet:6.0", - "settings": { - "terminal.integrated.defaultProfile.linux": "pwsh" - }, - "extensions": [ - "eamodio.gitlens", - "ms-dotnettools.csharp", - "VisualStudioExptTeam.vscodeintellicode", - "ms-vscode.powershell", - "cschleiden.vscode-github-actions", - "redhat.vscode-yaml", - "bierner.markdown-preview-github-styles", - "ban.spellright", - "jmrog.vscode-nuget-package-manager", - "coenraads.bracket-pair-colorizer", - "vscode-icons-team.vscode-icons", - "editorconfig.editorconfig", - "formulahendry.dotnet-test-explorer" - ], - "postCreateCommand": "dotnet restore && dotnet clean && dotnet build --configuration Release --no-restore && dotnet test --configuration Debug --no-restore --verbosity normal --collect:'XPlat Code Coverage' --settings UnitTests/coverlet.runsettings" + "image": "mcr.microsoft.com/vscode/devcontainers/dotnet:7.0", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "pwsh" + }, + "extensions": [ + "eamodio.gitlens", + "ms-dotnettools.csharp", + "VisualStudioExptTeam.vscodeintellicode", + "ms-vscode.powershell", + "cschleiden.vscode-github-actions", + "redhat.vscode-yaml", + "bierner.markdown-preview-github-styles", + "ban.spellright", + "jmrog.vscode-nuget-package-manager", + "coenraads.bracket-pair-colorizer", + "vscode-icons-team.vscode-icons", + "editorconfig.editorconfig", + "formulahendry.dotnet-test-explorer" + ], + "postCreateCommand": "dotnet restore && dotnet clean && dotnet build --configuration Release --no-restore && dotnet test --configuration Debug --no-restore --verbosity normal --collect:'XPlat Code Coverage' --settings UnitTests/coverlet.runsettings" + } + } } // Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) diff --git a/ReactiveExample/ReactiveExample.csproj b/ReactiveExample/ReactiveExample.csproj index 0c174f748..c2ce6640c 100644 --- a/ReactiveExample/ReactiveExample.csproj +++ b/ReactiveExample/ReactiveExample.csproj @@ -3,7 +3,7 @@ Exe net6.0 - + 1.0 1.0 diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 0a61f3bbb..c915db6e4 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -74,14 +74,14 @@ namespace Terminal.Gui { var c = sn [0]; Curses.mvaddch (crow, ccol - 1, (int)(uint)c); contents [crow, ccol - 1, 0] = c; - contents [crow, ccol - 1, 1] = currentAttribute; + contents [crow, ccol - 1, 1] = CurrentAttribute; contents [crow, ccol - 1, 2] = 1; } else { if (runeWidth < 2 && ccol > 0 && Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) { - var curAtttib = currentAttribute; + var curAtttib = CurrentAttribute; Curses.attrset (contents [crow, ccol - 1, 1]); Curses.mvaddch (crow, ccol - 1, (int)(uint)' '); contents [crow, ccol - 1, 0] = (int)(uint)' '; @@ -91,7 +91,7 @@ namespace Terminal.Gui { } else if (runeWidth < 2 && ccol <= Clip.Right - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) { - var curAtttib = currentAttribute; + var curAtttib = CurrentAttribute; Curses.attrset (contents [crow, ccol + 1, 1]); Curses.mvaddch (crow, ccol + 1, (int)(uint)' '); contents [crow, ccol + 1, 0] = (int)(uint)' '; @@ -106,25 +106,28 @@ namespace Terminal.Gui { Curses.addch ((int)(uint)rune); contents [crow, ccol, 0] = (int)(uint)rune; } - contents [crow, ccol, 1] = currentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 1; } - } else + } else { needMove = true; + } if (runeWidth < 0 || runeWidth > 0) { ccol++; } + if (runeWidth > 1) { if (validClip && ccol < Clip.Right) { - contents [crow, ccol, 1] = currentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 0; } ccol++; } - if (sync) + if (sync) { UpdateScreen (); + } } public override void AddStr (ustring str) @@ -179,12 +182,10 @@ namespace Terminal.Gui { public override void UpdateScreen () => window.redrawwin (); - Attribute currentAttribute; - public override void SetAttribute (Attribute c) { - currentAttribute = c; - Curses.attrset (currentAttribute); + base.SetAttribute (c); + Curses.attrset (CurrentAttribute); } public Curses.Window window; @@ -220,6 +221,7 @@ namespace Terminal.Gui { public override void SetColors (ConsoleColor foreground, ConsoleColor background) { + // BUGBUG: This code is never called ?? See Issue #2300 int f = (short)foreground; int b = (short)background; var v = colorPairs [f, b]; @@ -237,6 +239,7 @@ namespace Terminal.Gui { Dictionary rawPairs = new Dictionary (); public override void SetColors (short foreColorId, short backgroundColorId) { + // BUGBUG: This code is never called ?? See Issue #2300 int key = ((ushort)foreColorId << 16) | (ushort)backgroundColorId; if (!rawPairs.TryGetValue (key, out var v)) { v = MakeColor (foreColorId, backgroundColorId); @@ -894,34 +897,18 @@ namespace Terminal.Gui { if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition)) StartReportingMouseMoves (); - ResizeScreen (); - UpdateOffScreen (); - - //HLine = Curses.ACS_HLINE; - //VLine = Curses.ACS_VLINE; - //Stipple = Curses.ACS_CKBOARD; - //Diamond = Curses.ACS_DIAMOND; - //ULCorner = Curses.ACS_ULCORNER; - //LLCorner = Curses.ACS_LLCORNER; - //URCorner = Curses.ACS_URCORNER; - //LRCorner = Curses.ACS_LRCORNER; - //LeftTee = Curses.ACS_LTEE; - //RightTee = Curses.ACS_RTEE; - //TopTee = Curses.ACS_TTEE; - //BottomTee = Curses.ACS_BTEE; - //RightArrow = Curses.ACS_RARROW; - //LeftArrow = Curses.ACS_LARROW; - //UpArrow = Curses.ACS_UARROW; - //DownArrow = Curses.ACS_DARROW; + CurrentAttribute = MakeColor (Color.White, Color.Black); if (Curses.HasColors) { Curses.StartColor (); Curses.UseDefaultColors (); - CreateColors (); + InitalizeColorSchemes (); } else { - CreateColors (false); + InitalizeColorSchemes (false); + // BUGBUG: This is a hack to make the colors work on the Mac? + // The new Theme support overwrites these colors, so this is not needed? Colors.TopLevel.Normal = Curses.COLOR_GREEN; Colors.TopLevel.Focus = Curses.COLOR_WHITE; Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW; @@ -948,6 +935,10 @@ namespace Terminal.Gui { Colors.Error.HotFocus = Curses.A_REVERSE; Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; } + + ResizeScreen (); + UpdateOffScreen (); + } public override void ResizeScreen () @@ -1022,6 +1013,8 @@ namespace Terminal.Gui { return Curses.COLOR_YELLOW | Curses.A_BOLD | Curses.COLOR_GRAY; case Color.White: return Curses.COLOR_WHITE | Curses.A_BOLD | Curses.COLOR_GRAY; + case Color.Invalid: + return Curses.COLOR_BLACK; } throw new ArgumentException ("Invalid color code"); } @@ -1112,11 +1105,6 @@ namespace Terminal.Gui { //Curses.mouseinterval (lastMouseInterval); } - public override Attribute GetAttribute () - { - return currentAttribute; - } - /// public override bool GetCursorVisibility (out CursorVisibility visibility) { diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 84ced2be2..661daaca4 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -142,7 +142,7 @@ namespace Terminal.Gui { } var c = sn [0]; contents [crow, ccol - 1, 0] = c; - contents [crow, ccol - 1, 1] = currentAttribute; + contents [crow, ccol - 1, 1] = CurrentAttribute; contents [crow, ccol - 1, 2] = 1; } else { @@ -163,20 +163,22 @@ namespace Terminal.Gui { } else { contents [crow, ccol, 0] = (int)(uint)rune; } - contents [crow, ccol, 1] = currentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 1; dirtyLine [crow] = true; } - } else + } else { needMove = true; + } if (runeWidth < 0 || runeWidth > 0) { ccol++; } + if (runeWidth > 1) { if (validClip && ccol < Clip.Right) { - contents [crow, ccol, 1] = currentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 0; } ccol++; @@ -187,8 +189,9 @@ namespace Terminal.Gui { // if (crow + 1 < Rows) // crow++; //} - if (sync) + if (sync) { UpdateScreen (); + } } public override void AddStr (ustring str) @@ -226,11 +229,10 @@ namespace Terminal.Gui { rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT; FakeConsole.Clear (); ResizeScreen (); + // Call InitalizeColorSchemes before UpdateOffScreen as it references Colors + CurrentAttribute = MakeColor (Color.White, Color.Black); + InitalizeColorSchemes (); UpdateOffScreen (); - - CreateColors (); - - //MockConsole.Clear (); } public override Attribute MakeAttribute (Color fore, Color back) @@ -301,10 +303,9 @@ namespace Terminal.Gui { UpdateCursor (); } - Attribute currentAttribute; public override void SetAttribute (Attribute c) { - currentAttribute = c; + base.SetAttribute (c); } public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) @@ -493,11 +494,6 @@ namespace Terminal.Gui { keyUpHandler (new KeyEvent (map, keyModifiers)); } - public override Attribute GetAttribute () - { - return currentAttribute; - } - /// public override bool GetCursorVisibility (out CursorVisibility visibility) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 0b1da2d2a..9ae4eb20e 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1252,7 +1252,7 @@ namespace Terminal.Gui { } var c = sn [0]; contents [crow, ccol - 1, 0] = c; - contents [crow, ccol - 1, 1] = currentAttribute; + contents [crow, ccol - 1, 1] = CurrentAttribute; contents [crow, ccol - 1, 2] = 1; } else { @@ -1273,7 +1273,7 @@ namespace Terminal.Gui { } else { contents [crow, ccol, 0] = (int)(uint)rune; } - contents [crow, ccol, 1] = currentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 1; } @@ -1283,9 +1283,10 @@ namespace Terminal.Gui { if (runeWidth < 0 || runeWidth > 0) { ccol++; } + if (runeWidth > 1) { if (validClip && ccol < Clip.Right) { - contents [crow, ccol, 1] = currentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 0; } ccol++; @@ -1358,12 +1359,14 @@ namespace Terminal.Gui { cols = Console.WindowWidth; rows = Console.WindowHeight; + CurrentAttribute = MakeColor (Color.White, Color.Black); + InitalizeColorSchemes (); + ResizeScreen (); UpdateOffScreen (); StartReportingMouseMoves (); - CreateColors (); Clear (); } @@ -1631,10 +1634,10 @@ namespace Terminal.Gui { { } - Attribute currentAttribute; + public override void SetAttribute (Attribute c) { - currentAttribute = c; + base.SetAttribute (c); } public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo) @@ -1952,11 +1955,6 @@ namespace Terminal.Gui { }; } - public override Attribute GetAttribute () - { - return currentAttribute; - } - /// public override bool GetCursorVisibility (out CursorVisibility visibility) { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 8d1b71646..21ea48300 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -116,6 +116,10 @@ namespace Terminal.Gui { public bool GetCursorVisibility (out CursorVisibility visibility) { + if (ScreenBuffer == IntPtr.Zero) { + visibility = CursorVisibility.Invisible; + return false; + } if (!GetConsoleCursorInfo (ScreenBuffer, out ConsoleCursorInfo info)) { var err = Marshal.GetLastWin32Error (); if (err != 0) { @@ -1455,13 +1459,13 @@ namespace Terminal.Gui { var winSize = WinConsole.GetConsoleOutputWindow (out Point pos); cols = winSize.Width; rows = winSize.Height; - WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); + CurrentAttribute = MakeColor (Color.White, Color.Black); + InitalizeColorSchemes (); + ResizeScreen (); UpdateOffScreen (); - - CreateColors (); } catch (Win32Exception e) { throw new InvalidOperationException ("The Windows Console output window is not available.", e); } @@ -1530,8 +1534,8 @@ namespace Terminal.Gui { var prevPosition = crow * Cols + (ccol - 1); OutputBuffer [prevPosition].Char.UnicodeChar = c; contents [crow, ccol - 1, 0] = c; - OutputBuffer [prevPosition].Attributes = (ushort)currentAttribute; - contents [crow, ccol - 1, 1] = currentAttribute; + OutputBuffer [prevPosition].Attributes = (ushort)CurrentAttribute; + contents [crow, ccol - 1, 1] = CurrentAttribute; contents [crow, ccol - 1, 2] = 1; WindowsConsole.SmallRect.Update (ref damageRegion, (short)(ccol - 1), (short)crow); } else { @@ -1557,8 +1561,8 @@ namespace Terminal.Gui { OutputBuffer [position].Char.UnicodeChar = (char)rune; contents [crow, ccol, 0] = (int)(uint)rune; } - OutputBuffer [position].Attributes = (ushort)currentAttribute; - contents [crow, ccol, 1] = currentAttribute; + OutputBuffer [position].Attributes = (ushort)CurrentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 1; WindowsConsole.SmallRect.Update (ref damageRegion, (short)ccol, (short)crow); } @@ -1567,20 +1571,22 @@ namespace Terminal.Gui { if (runeWidth < 0 || runeWidth > 0) { ccol++; } + if (runeWidth > 1) { if (validClip && ccol < Clip.Right) { position = GetOutputBufferPosition (); - OutputBuffer [position].Attributes = (ushort)currentAttribute; + OutputBuffer [position].Attributes = (ushort)CurrentAttribute; OutputBuffer [position].Char.UnicodeChar = (char)0x00; contents [crow, ccol, 0] = (int)(uint)0x00; - contents [crow, ccol, 1] = currentAttribute; + contents [crow, ccol, 1] = CurrentAttribute; contents [crow, ccol, 2] = 0; } ccol++; } - if (sync) + if (sync) { UpdateScreen (); + } } public override void AddStr (ustring str) @@ -1589,11 +1595,9 @@ namespace Terminal.Gui { AddRune (rune); } - Attribute currentAttribute; - public override void SetAttribute (Attribute c) { - currentAttribute = c; + base.SetAttribute (c); } public override Attribute MakeColor (Color foreground, Color background) @@ -1695,11 +1699,6 @@ namespace Terminal.Gui { WinConsole = null; } - public override Attribute GetAttribute () - { - return currentAttribute; - } - /// public override bool GetCursorVisibility (out CursorVisibility visibility) { diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index b5f6e5984..eb609e609 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -57,7 +57,7 @@ namespace Terminal.Gui { /// /// public static class Application { - static Stack toplevels = new Stack (); + static readonly Stack toplevels = new Stack (); /// /// The current in use. @@ -111,28 +111,33 @@ namespace Terminal.Gui { /// public static View WantContinuousButtonPressedView { get; private set; } + private static bool? _heightAsBuffer; + /// /// The current used in the terminal. /// + /// public static bool HeightAsBuffer { get { if (Driver == null) { - throw new ArgumentNullException ("The driver must be initialized first."); + return _heightAsBuffer.HasValue && _heightAsBuffer.Value; } return Driver.HeightAsBuffer; } set { + _heightAsBuffer = value; if (Driver == null) { - throw new ArgumentNullException ("The driver must be initialized first."); + return; } - Driver.HeightAsBuffer = value; + + Driver.HeightAsBuffer = _heightAsBuffer.Value; } } static Key alternateForwardKey = Key.PageDown | Key.CtrlMask; /// - /// Alternative key to navigate forwards through all views. Ctrl+Tab is always used. + /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. /// public static Key AlternateForwardKey { get => alternateForwardKey; @@ -147,7 +152,7 @@ namespace Terminal.Gui { static void OnAlternateForwardKeyChanged (Key oldKey) { - foreach (var top in toplevels) { + foreach (var top in toplevels.ToArray()) { top.OnAlternateForwardKeyChanged (oldKey); } } @@ -155,7 +160,7 @@ namespace Terminal.Gui { static Key alternateBackwardKey = Key.PageUp | Key.CtrlMask; /// - /// Alternative key to navigate backwards through all views. Shift+Ctrl+Tab is always used. + /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. /// public static Key AlternateBackwardKey { get => alternateBackwardKey; @@ -170,7 +175,7 @@ namespace Terminal.Gui { static void OnAlternateBackwardKeyChanged (Key oldKey) { - foreach (var top in toplevels) { + foreach (var top in toplevels.ToArray()) { top.OnAlternateBackwardKeyChanged (oldKey); } } @@ -200,7 +205,8 @@ namespace Terminal.Gui { static void OnQuitKeyChanged (Key oldKey) { - foreach (var top in toplevels) { + // Duplicate the list so if it changes during enumeration we're safe + foreach (var top in toplevels.ToArray()) { top.OnQuitKeyChanged (oldKey); } } @@ -212,7 +218,7 @@ namespace Terminal.Gui { public static MainLoop MainLoop { get; private set; } /// - /// Disable or enable the mouse in this + /// Disable or enable the mouse. The mouse is enabled by default. /// public static bool IsMouseDisabled { get; set; } @@ -266,7 +272,7 @@ namespace Terminal.Gui { // users use async/await on their code // class MainLoopSyncContext : SynchronizationContext { - MainLoop mainLoop; + readonly MainLoop mainLoop; public MainLoopSyncContext (MainLoop mainLoop) { @@ -305,9 +311,9 @@ namespace Terminal.Gui { } /// - /// If set, it forces the use of the System.Console-based driver. + /// If , forces the use of the System.Console-based (see ) driver. The default is . /// - public static bool UseSystemConsole; + public static bool UseSystemConsole { get; set; } = false; // For Unit testing - ignores UseSystemConsole internal static bool ForceFakeConsole; @@ -422,6 +428,7 @@ namespace Terminal.Gui { MainLoop = new MainLoop (mainLoopDriver); try { + Driver.HeightAsBuffer = HeightAsBuffer; Driver.Init (TerminalResized); } catch (InvalidOperationException ex) { // This is a case where the driver is unable to initialize the console. @@ -933,6 +940,8 @@ namespace Terminal.Gui { if (Top != null && toplevel != Top && !toplevels.Contains (Top)) { Top.Dispose (); Top = null; + } else if (Top != null && toplevel != Top && toplevels.Contains (Top)) { + Top.OnLeave (toplevel); } if (string.IsNullOrEmpty (toplevel.Id.ToString ())) { var count = 1; @@ -986,9 +995,7 @@ namespace Terminal.Gui { toplevel.PositionToplevels (); toplevel.WillPresent (); if (refreshDriver) { - if (MdiTop != null) { - MdiTop.OnChildLoaded (toplevel); - } + MdiTop?.OnChildLoaded (toplevel); toplevel.OnLoaded (); Redraw (toplevel); toplevel.PositionCursor (); @@ -1043,6 +1050,7 @@ namespace Terminal.Gui { MdiTop.OnAllChildClosed (); } else { SetCurrentAsTop (); + Current.OnEnter (Current); } Refresh (); } @@ -1111,12 +1119,6 @@ namespace Terminal.Gui { Driver.Refresh (); } - static void Refresh (View view) - { - view.Redraw (view.Bounds); - Driver.Refresh (); - } - /// /// Triggers a refresh of the entire display. /// diff --git a/Terminal.Gui/Core/Autocomplete/Autocomplete.cs b/Terminal.Gui/Core/Autocomplete/Autocomplete.cs index f351b8424..61ccc01ea 100644 --- a/Terminal.Gui/Core/Autocomplete/Autocomplete.cs +++ b/Terminal.Gui/Core/Autocomplete/Autocomplete.cs @@ -324,6 +324,7 @@ namespace Terminal.Gui { if (IsWordChar ((char)kb.Key)) { Visible = true; closed = false; + return false; } if (kb.Key == Reopen) { @@ -332,6 +333,9 @@ namespace Terminal.Gui { if (closed || Suggestions.Count == 0) { Visible = false; + if (!closed) { + Close (); + } return false; } @@ -345,6 +349,17 @@ namespace Terminal.Gui { return true; } + if (kb.Key == Key.CursorLeft || kb.Key == Key.CursorRight) { + GenerateSuggestions (kb.Key == Key.CursorLeft ? -1 : 1); + if (Suggestions.Count == 0) { + Visible = false; + if (!closed) { + Close (); + } + } + return false; + } + if (kb.Key == SelectionKey) { return Select (); } @@ -368,6 +383,9 @@ namespace Terminal.Gui { public virtual bool MouseEvent (MouseEvent me, bool fromHost = false) { if (fromHost) { + if (!Visible) { + return false; + } GenerateSuggestions (); if (Visible && Suggestions.Count == 0) { Visible = false; @@ -444,7 +462,8 @@ namespace Terminal.Gui { /// Populates with all strings in that /// match with the current cursor position/text in the /// - public virtual void GenerateSuggestions () + /// The column offset. + public virtual void GenerateSuggestions (int columnOffset = 0) { // if there is nothing to pick from if (AllSuggestions.Count == 0) { @@ -452,7 +471,7 @@ namespace Terminal.Gui { return; } - var currentWord = GetCurrentWord (); + var currentWord = GetCurrentWord (columnOffset); if (string.IsNullOrWhiteSpace (currentWord)) { ClearSuggestions (); @@ -524,11 +543,12 @@ namespace Terminal.Gui { /// /// Returns the currently selected word from the . /// - /// When overriding this method views can make use of + /// When overriding this method views can make use of /// /// + /// The column offset. /// - protected abstract string GetCurrentWord (); + protected abstract string GetCurrentWord (int columnOffset = 0); /// /// @@ -536,37 +556,40 @@ namespace Terminal.Gui { /// or null. Also returns null if the is positioned in the middle of a word. /// /// - /// Use this method to determine whether autocomplete should be shown when the cursor is at - /// a given point in a line and to get the word from which suggestions should be generated. + /// + /// Use this method to determine whether autocomplete should be shown when the cursor is at + /// a given point in a line and to get the word from which suggestions should be generated. + /// Use the to indicate if search the word at left (negative), + /// at right (positive) or at the current column (zero) which is the default. + /// /// /// /// + /// /// - protected virtual string IdxToWord (List line, int idx) + protected virtual string IdxToWord (List line, int idx, int columnOffset = 0) { StringBuilder sb = new StringBuilder (); + var endIdx = idx; - // do not generate suggestions if the cursor is positioned in the middle of a word - bool areMidWord; - - if (idx == line.Count) { - // the cursor positioned at the very end of the line - areMidWord = false; - } else { - // we are in the middle of a word if the cursor is over a letter/number - areMidWord = IsWordChar (line [idx]); + // get the ending word index + while (endIdx < line.Count) { + if (IsWordChar (line [endIdx])) { + endIdx++; + } else { + break; + } } - // if we are in the middle of a word then there is no way to autocomplete that word - if (areMidWord) { + // It isn't a word char then there is no way to autocomplete that word + if (endIdx == idx && columnOffset != 0) { return null; } // we are at the end of a word. Work out what has been typed so far - while (idx-- > 0) { - - if (IsWordChar (line [idx])) { - sb.Insert (0, (char)line [idx]); + while (endIdx-- > 0) { + if (IsWordChar (line [endIdx])) { + sb.Insert (0, (char)line [endIdx]); } else { break; } diff --git a/Terminal.Gui/Core/Autocomplete/IAutocomplete.cs b/Terminal.Gui/Core/Autocomplete/IAutocomplete.cs index 32e7046e7..2e3194eb3 100644 --- a/Terminal.Gui/Core/Autocomplete/IAutocomplete.cs +++ b/Terminal.Gui/Core/Autocomplete/IAutocomplete.cs @@ -109,6 +109,7 @@ namespace Terminal.Gui { /// Populates with all strings in that /// match with the current cursor position/text in the . /// - void GenerateSuggestions (); + /// The column offset. Current (zero - default), left (negative), right (positive). + void GenerateSuggestions (int columnOffset = 0); } } diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index 42d32ebfa..bc3856289 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/Terminal.Gui/Core/ConsoleDriver.cs @@ -1,23 +1,22 @@ // -// ConsoleDriver.cs: Definition for the Console Driver API +// ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations. // -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// -// Define this to enable diagnostics drawing for Window Frames using NStack; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using Unix.Terminal; namespace Terminal.Gui { /// - /// Basic colors that can be used to set the foreground and background colors in console applications. + /// Colors that can be used to set the foreground and background colors in console applications. /// + /// + /// The value indicates either no-color has been set or the color is invalid. + /// public enum Color { /// /// The black color. @@ -82,26 +81,112 @@ namespace Terminal.Gui { /// /// The White color. /// - White + White, + /// + /// Indicates an invalid or un-set color value. + /// + Invalid = -1 } /// - /// Attributes are used as elements that contain both a foreground and a background or platform specific features + /// + /// + public class TrueColor { + /// + /// Red color component. + /// + public int Red { get; } + /// + /// Green color component. + /// + public int Green { get; } + /// + /// Blue color component. + /// + public int Blue { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// + /// + public TrueColor (int red, int green, int blue) + { + Red = red; + Green = green; + Blue = blue; + } + + /// + /// + /// + /// + public Color ToConsoleColor () + { + var trueColorMap = new Dictionary () { + { new TrueColor (0,0,0),Color.Black}, + { new TrueColor (0, 0, 0x80),Color.Blue}, + { new TrueColor (0, 0x80, 0),Color.Green}, + { new TrueColor (0, 0x80, 0x80),Color.Cyan}, + { new TrueColor (0x80, 0, 0),Color.Red}, + { new TrueColor (0x80, 0, 0x80),Color.Magenta}, + { new TrueColor (0xC1, 0x9C, 0x00),Color.Brown}, // TODO confirm this + { new TrueColor (0xC0, 0xC0, 0xC0),Color.Gray}, + { new TrueColor (0x80, 0x80, 0x80),Color.DarkGray}, + { new TrueColor (0, 0, 0xFF),Color.BrightBlue}, + { new TrueColor (0, 0xFF, 0),Color.BrightGreen}, + { new TrueColor (0, 0xFF, 0xFF),Color.BrightCyan}, + { new TrueColor (0xFF, 0, 0),Color.BrightRed}, + { new TrueColor (0xFF, 0, 0xFF),Color.BrightMagenta }, + { new TrueColor (0xFF, 0xFF, 0),Color.BrightYellow}, + { new TrueColor (0xFF, 0xFF, 0xFF),Color.White}, + }; + // Iterate over all colors in the map + var distances = trueColorMap.Select ( + k => Tuple.Create ( + // the candidate we are considering matching against (RGB) + k.Key, + + CalculateDistance (k.Key, this) + )); + + // get the closest + var match = distances.OrderBy (t => t.Item2).First (); + return trueColorMap [match.Item1]; + } + + private float CalculateDistance (TrueColor color1, TrueColor color2) + { + // use RGB distance + return + Math.Abs (color1.Red - color2.Red) + + Math.Abs (color1.Green - color2.Green) + + Math.Abs (color1.Blue - color2.Blue); + } + } + + /// + /// Attributes are used as elements that contain both a foreground and a background or platform specific features. /// /// - /// s are needed to map colors to terminal capabilities that might lack colors, on color - /// scenarios, they encode both the foreground and the background color and are used in the - /// class to define color schemes that can be used in your application. + /// s are needed to map colors to terminal capabilities that might lack colors. + /// They encode both the foreground and the background color and are used in the + /// class to define color schemes that can be used in an application. /// public struct Attribute { /// - /// The color attribute value. + /// The -specific color attribute value. If is + /// the value of this property is invalid (typcially because the Attribute was created before a driver was loaded) + /// and the attribute should be re-made (see ) before it is used. /// public int Value { get; } + /// /// The foreground color. /// public Color Foreground { get; } + /// /// The background color. /// @@ -114,11 +199,13 @@ namespace Terminal.Gui { /// Value. public Attribute (int value) { - Color foreground = default; - Color background = default; + Color foreground = Color.Invalid; + Color background = Color.Invalid; + Initialized = false; if (Application.Driver != null) { Application.Driver.GetColors (value, out foreground, out background); + Initialized = true; } Value = value; Foreground = foreground; @@ -136,6 +223,7 @@ namespace Terminal.Gui { Value = value; Foreground = foreground; Background = background; + Initialized = true; } /// @@ -145,7 +233,9 @@ namespace Terminal.Gui { /// Background public Attribute (Color foreground = new Color (), Color background = new Color ()) { - Value = Make (foreground, background).Value; + var make = Make (foreground, background); + Initialized = make.Initialized; + Value = make.Value; Foreground = foreground; Background = background; } @@ -158,29 +248,42 @@ namespace Terminal.Gui { public Attribute (Color color) : this (color, color) { } /// - /// Implicit conversion from an to the underlying Int32 representation + /// Implicit conversion from an to the underlying, driver-specific, Int32 representation + /// of the color. /// - /// The integer value stored in the attribute. + /// The driver-specific color value stored in the attribute. /// The attribute to convert - public static implicit operator int (Attribute c) => c.Value; + public static implicit operator int (Attribute c) + { + if (!c.Initialized) throw new InvalidOperationException ("Attribute: Attributes must be initialized by a driver before use."); + return c.Value; + } /// - /// Implicitly convert an integer value into an + /// Implicitly convert an driver-specific color value into an /// - /// An attribute with the specified integer value. + /// An attribute with the specified driver-specific color value. /// value public static implicit operator Attribute (int v) => new Attribute (v); /// - /// Creates an from the specified foreground and background. + /// Creates an from the specified foreground and background colors. /// - /// The make. + /// + /// If a has not been loaded (Application.Driver == null) this + /// method will return an attribute with set to . + /// + /// The new attribute. /// Foreground color to use. /// Background color to use. public static Attribute Make (Color foreground, Color background) { - if (Application.Driver == null) - throw new InvalidOperationException ("The Application has not been initialized"); + if (Application.Driver == null) { + // Create the attribute, but show it's not been initialized + var a = new Attribute (-1, foreground, background); + a.Initialized = false; + return a; + } return Application.Driver.MakeAttribute (foreground, background); } @@ -194,45 +297,114 @@ namespace Terminal.Gui { throw new InvalidOperationException ("The Application has not been initialized"); return Application.Driver.GetAttribute (); } + + /// + /// If the attribute has been initialzed by a and + /// thus has that is valid for that driver. If the + /// and colors may have been set (see ) but + /// the attribute has not been mapped to a specific color value. + /// + /// + /// Attributes that have not been initialized must eventually be initialized before being passed to a driver. + /// + public bool Initialized { get; internal set; } + + /// + /// Returns if the Atrribute is valid (both foreground and background have valid color values). + /// + /// + public bool HasValidColors { + get { + return Foreground != Color.Invalid && Background != Color.Invalid; + } + } } /// - /// Color scheme definitions, they cover some common scenarios and are used - /// typically in containers such as and to set the scheme that is used by all the - /// views contained inside. + /// Defines the color s for common visible elements in a . + /// Containers such as and use to determine + /// the colors used by sub-views. /// + /// + /// See also: . + /// public class ColorScheme : IEquatable { - Attribute _normal; - Attribute _focus; - Attribute _hotNormal; - Attribute _hotFocus; - Attribute _disabled; - internal string caller = ""; + Attribute _normal = new Attribute(Color.White, Color.Black); + Attribute _focus = new Attribute (Color.White, Color.Black); + Attribute _hotNormal = new Attribute (Color.White, Color.Black); + Attribute _hotFocus = new Attribute (Color.White, Color.Black); + Attribute _disabled = new Attribute (Color.White, Color.Black); /// - /// The default color for text, when the view is not focused. + /// Used by and to track which ColorScheme + /// is being accessed. /// - public Attribute Normal { get { return _normal; } set { _normal = value; } } + internal string schemeBeingSet = ""; /// - /// The color for text when the view has the focus. + /// The foreground and background color for text when the view is not focused, hot, or disabled. /// - public Attribute Focus { get { return _focus; } set { _focus = value; } } + public Attribute Normal { + get { return _normal; } + set { + if (!value.HasValidColors) { + return; + } + _normal = value; + } + } /// - /// The color for the hotkey when a view is not focused + /// The foreground and background color for text when the view has the focus. /// - public Attribute HotNormal { get { return _hotNormal; } set { _hotNormal = value; } } + public Attribute Focus { + get { return _focus; } + set { + if (!value.HasValidColors) { + return; + } + _focus = value; + } + } /// - /// The color for the hotkey when the view is focused. + /// The foreground and background color for text when the view is highlighted (hot). /// - public Attribute HotFocus { get { return _hotFocus; } set { _hotFocus = value; } } + public Attribute HotNormal { + get { return _hotNormal; } + set { + if (!value.HasValidColors) { + return; + } + _hotNormal = value; + } + } /// - /// The default color for text, when the view is disabled. + /// The foreground and background color for text when the view is highlighted (hot) and has focus. /// - public Attribute Disabled { get { return _disabled; } set { _disabled = value; } } + public Attribute HotFocus { + get { return _hotFocus; } + set { + if (!value.HasValidColors) { + return; + } + _hotFocus = value; + } + } + + /// + /// The default foreground and background color for text, when the view is disabled. + /// + public Attribute Disabled { + get { return _disabled; } + set { + if (!value.HasValidColors) { + return; + } + _disabled = value; + } + } /// /// Compares two objects for equality. @@ -295,20 +467,67 @@ namespace Terminal.Gui { { return !(left == right); } + + internal void Initialize () + { + // If the new scheme was created before a driver was loaded, we need to re-make + // the attributes + if (!_normal.Initialized) { + _normal = new Attribute (_normal.Foreground, _normal.Background); + } + if (!_focus.Initialized) { + _focus = new Attribute (_focus.Foreground, _focus.Background); + } + if (!_hotNormal.Initialized) { + _hotNormal = new Attribute (_hotNormal.Foreground, _hotNormal.Background); + } + if (!_hotFocus.Initialized) { + _hotFocus = new Attribute (_hotFocus.Foreground, _hotFocus.Background); + } + if (!_disabled.Initialized) { + _disabled = new Attribute (_disabled.Foreground, _disabled.Background); + } + } } /// /// The default s for the application. /// + /// + /// This property can be set in a Theme to change the default for the application. + /// public static class Colors { + private class SchemeNameComparerIgnoreCase : IEqualityComparer { + public bool Equals (string x, string y) + { + if (x != null && y != null) { + return x.ToLowerInvariant () == y.ToLowerInvariant (); + } + return false; + } + + public int GetHashCode (string obj) + { + return obj.ToLowerInvariant ().GetHashCode (); + } + } + static Colors () + { + ColorSchemes = Create (); + } + + /// + /// Creates a new dictionary of new objects. + /// + public static Dictionary Create () { // Use reflection to dynamically create the default set of ColorSchemes from the list defined // by the class. - ColorSchemes = typeof (Colors).GetProperties () + return typeof (Colors).GetProperties () .Where (p => p.PropertyType == typeof (ColorScheme)) - .Select (p => new KeyValuePair (p.Name, new ColorScheme ())) // (ColorScheme)p.GetValue (p))) - .ToDictionary (t => t.Key, t => t.Value); + .Select (p => new KeyValuePair (p.Name, new ColorScheme())) + .ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ()); } /// @@ -361,21 +580,21 @@ namespace Terminal.Gui { /// public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); } - static ColorScheme GetColorScheme ([CallerMemberName] string callerMemberName = null) + static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) { - return ColorSchemes [callerMemberName]; + return ColorSchemes [schemeBeingSet]; } - static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string callerMemberName = null) + static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null) { - ColorSchemes [callerMemberName] = colorScheme; - colorScheme.caller = callerMemberName; + ColorSchemes [schemeBeingSet] = colorScheme; + colorScheme.schemeBeingSet = schemeBeingSet; } /// /// Provides the defined s. /// - public static Dictionary ColorSchemes { get; } + public static Dictionary ColorSchemes { get; private set; } } /// @@ -659,13 +878,35 @@ namespace Terminal.Gui { public abstract void UpdateScreen (); /// - /// Selects the specified attribute as the attribute to use for future calls to AddRune, AddString. + /// The current attribute the driver is using. /// - /// C. - public abstract void SetAttribute (Attribute c); + public virtual Attribute CurrentAttribute { + get => currentAttribute; + set { + if (!value.Initialized && value.HasValidColors && Application.Driver != null) { + CurrentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background); + return; + } + if (!value.Initialized) Debug.WriteLine ("ConsoleDriver.CurrentAttribute: Attributes must be initialized before use."); + + currentAttribute = value; + } + } /// - /// Set Colors from limit sets of colors. + /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. + /// + /// + /// Implementations should call base.SetAttribute(c). + /// + /// C. + public virtual void SetAttribute (Attribute c) + { + CurrentAttribute = c; + } + + /// + /// Set Colors from limit sets of colors. Not implemented by any driver: See Issue #2300. /// /// Foreground. /// Background. @@ -675,7 +916,7 @@ namespace Terminal.Gui { // that independently with the R, G, B values. /// /// Advanced uses - set colors to any pre-set pairs, you would need to init_color - /// that independently with the R, G, B values. + /// that independently with the R, G, B values. Not implemented by any driver: See Issue #2300. /// /// Foreground color identifier. /// Background color identifier. @@ -998,12 +1239,13 @@ namespace Terminal.Gui { public abstract void StopReportingMouseMoves (); /// - /// Disables the cooked event processing from the mouse driver. At startup, it is assumed mouse events are cooked. + /// Disables the cooked event processing from the mouse driver. + /// At startup, it is assumed mouse events are cooked. Not implemented by any driver: See Issue #2300. /// public abstract void UncookMouse (); /// - /// Enables the cooked event processing from the mouse driver + /// Enables the cooked event processing from the mouse driver. Not implemented by any driver: See Issue #2300. /// public abstract void CookMouse (); @@ -1196,6 +1438,7 @@ namespace Terminal.Gui { /// Lower right rounded corner /// public Rune LRRCorner = '\u256f'; + private Attribute currentAttribute; /// /// Make the attribute for the foreground and background colors. @@ -1209,7 +1452,7 @@ namespace Terminal.Gui { /// Gets the current . /// /// The current attribute. - public abstract Attribute GetAttribute (); + public Attribute GetAttribute () => CurrentAttribute; /// /// Make the for the . @@ -1220,21 +1463,24 @@ namespace Terminal.Gui { public abstract Attribute MakeColor (Color foreground, Color background); /// - /// Create all with the for the console driver. + /// Ensures all s in are correclty + /// initalized by the driver. /// - /// Flag indicating if colors are supported. - public void CreateColors (bool hasColors = true) + /// Flag indicating if colors are supported (not used). + public void InitalizeColorSchemes (bool supportsColors = true) { - Colors.TopLevel = new ColorScheme (); - Colors.Base = new ColorScheme (); - Colors.Dialog = new ColorScheme (); - Colors.Menu = new ColorScheme (); - Colors.Error = new ColorScheme (); + // Ensure all Attributes are initlaized by the driver + foreach (var s in Colors.ColorSchemes) { + s.Value.Initialize (); + } - if (!hasColors) { + if (!supportsColors) { return; } + + // Define the default color theme only if the user has not defined one. + Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black); Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan); Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black); diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index fddabb692..881b97426 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -821,7 +821,6 @@ namespace Terminal.Gui { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) { Application.UngrabMouse (); - Driver.UncookMouse (); dragPosition = null; } @@ -960,6 +959,18 @@ namespace Terminal.Gui { } return false; } + + /// + public override bool OnEnter (View view) + { + return MostFocused?.OnEnter (view) ?? base.OnEnter (view); + } + + /// + public override bool OnLeave (View view) + { + return MostFocused?.OnLeave (view) ?? base.OnLeave (view); + } } /// diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 2a02bf118..09d8ca3a1 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1447,8 +1447,9 @@ namespace Terminal.Gui { /// public virtual ColorScheme ColorScheme { get { - if (colorScheme == null) + if (colorScheme == null) { return SuperView?.ColorScheme; + } return colorScheme; } set { diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index ce22fd3cd..e92806346 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -10,9 +10,10 @@ - 1.9 - 1.9 - 1.9 + 1.0 + 1.0 + 1.0 + 1.0 diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 14fc56394..07389fb4f 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1346,12 +1346,12 @@ namespace Terminal.Gui { } /// - protected override string GetCurrentWord () + protected override string GetCurrentWord (int columnOffset = 0) { var host = (TextField)HostControl; var currentLine = host.Text.ToRuneList (); - var cursorPosition = Math.Min (host.CursorPosition, currentLine.Count); - return IdxToWord (currentLine, cursorPosition); + var cursorPosition = Math.Min (host.CursorPosition + columnOffset, currentLine.Count); + return IdxToWord (currentLine, cursorPosition, columnOffset); } /// diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 3c9e54b09..7cb797b11 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -2035,6 +2035,16 @@ namespace Terminal.Gui { return base.OnEnter (view); } + /// + public override bool OnLeave (View view) + { + if (Application.MouseGrabView != null && Application.MouseGrabView == this) { + Application.UngrabMouse (); + } + + return base.OnLeave (view); + } + // Returns an encoded region start..end (top 32 bits are the row, low32 the column) void GetEncodedRegionBounds (out long start, out long end, int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null) @@ -2437,6 +2447,10 @@ namespace Terminal.Gui { PositionCursor (); + if (clickWithSelecting) { + clickWithSelecting = false; + return; + } if (SelectedLength > 0) return; @@ -2667,8 +2681,10 @@ namespace Terminal.Gui { need = true; } else if ((wordWrap && leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width && tSize.size + RightOffset < Frame.Width + offB.width)) { - leftColumn = 0; - need = true; + if (leftColumn > 0) { + leftColumn = 0; + need = true; + } } if (currentRow < topRow) { @@ -4269,6 +4285,7 @@ namespace Terminal.Gui { } bool isButtonShift; + bool clickWithSelecting; /// public override bool MouseEvent (MouseEvent ev) @@ -4362,6 +4379,7 @@ namespace Terminal.Gui { columnTrack = currentColumn; } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) { if (shiftSelecting) { + clickWithSelecting = true; StopSelecting (); } ProcessMouseClick (ev, out _); @@ -4447,16 +4465,6 @@ namespace Terminal.Gui { line = r; } - /// - public override bool OnLeave (View view) - { - if (Application.MouseGrabView != null && Application.MouseGrabView == this) { - Application.UngrabMouse (); - } - - return base.OnLeave (view); - } - /// /// Allows clearing the items updating the original text. /// @@ -4475,12 +4483,12 @@ namespace Terminal.Gui { public class TextViewAutocomplete : Autocomplete { /// - protected override string GetCurrentWord () + protected override string GetCurrentWord (int columnOffset = 0) { var host = (TextView)HostControl; var currentLine = host.GetCurrentLine (); - var cursorPosition = Math.Min (host.CurrentColumn, currentLine.Count); - return IdxToWord (currentLine, cursorPosition); + var cursorPosition = Math.Min (host.CurrentColumn + columnOffset, currentLine.Count); + return IdxToWord (currentLine, cursorPosition, columnOffset); } /// diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 65d6eda20..6d49d9e06 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -163,7 +163,7 @@ namespace UICatalog { public UICatalogTopLevel () { - ColorScheme = _colorScheme; + ColorScheme = _colorScheme = Colors.Base; MenuBar = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null, Key.Q | Key.CtrlMask) diff --git a/UICatalog/UICatalog.csproj b/UICatalog/UICatalog.csproj index 40d346438..6320c1e99 100644 --- a/UICatalog/UICatalog.csproj +++ b/UICatalog/UICatalog.csproj @@ -5,7 +5,7 @@ 8.0 UICatalog.UICatalogApp - + 1.0 1.0 diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index b7ef1d8db..7228d8ad6 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -24,7 +24,6 @@ namespace Terminal.Gui.ApplicationTests { Assert.Null (Application.Driver); Assert.Null (Application.Top); Assert.Null (Application.Current); - Assert.Throws (() => Application.HeightAsBuffer == true); Assert.Null (Application.MainLoop); Assert.Null (Application.Iteration); Assert.Null (Application.RootMouseEvent); diff --git a/UnitTests/Drivers/AttributeTests.cs b/UnitTests/Drivers/AttributeTests.cs index ddc7b4695..fd563c051 100644 --- a/UnitTests/Drivers/AttributeTests.cs +++ b/UnitTests/Drivers/AttributeTests.cs @@ -85,7 +85,34 @@ namespace Terminal.Gui.DriverTests { } [Fact] - public void Make_Asserts_IfNotInit () + public void Implicit_Assign_NoDriver () + { + + var attr = new Attribute (); + + var fg = new Color (); + fg = Color.Red; + + var bg = new Color (); + bg = Color.Blue; + + // Test conversion to int + attr = new Attribute (fg, bg); + int value_implicit = (int)attr.Value; + Assert.False (attr.Initialized); + + Assert.Equal (-1, value_implicit); + Assert.False (attr.Initialized); + + // Test conversion from int + attr = -1; + Assert.Equal (-1, attr.Value); + Assert.False (attr.Initialized); + + } + + [Fact] + public void Make_SetsNotInitialized_NoDriver () { var fg = new Color (); fg = Color.Red; @@ -93,7 +120,9 @@ namespace Terminal.Gui.DriverTests { var bg = new Color (); bg = Color.Blue; - Assert.Throws (() => Attribute.Make (fg, bg)); + var a = Attribute.Make (fg, bg); + + Assert.False (a.Initialized); } [Fact] @@ -109,8 +138,8 @@ namespace Terminal.Gui.DriverTests { var bg = new Color (); bg = Color.Blue; - var attr = Attribute.Make (fg, bg); - + var attr = Attribute.Make (fg, bg); + Assert.True (attr.Initialized); Assert.Equal (fg, attr.Foreground); Assert.Equal (bg, attr.Background); @@ -119,7 +148,23 @@ namespace Terminal.Gui.DriverTests { } [Fact] - public void Get_Asserts_IfNotInit () + public void Make_Creates_NoDriver () + { + + var fg = new Color (); + fg = Color.Red; + + var bg = new Color (); + bg = Color.Blue; + + var attr = Attribute.Make (fg, bg); + Assert.False (attr.Initialized); + Assert.Equal (fg, attr.Foreground); + Assert.Equal (bg, attr.Background); + } + + [Fact] + public void Get_Asserts_NoDriver () { Assert.Throws (() => Attribute.Get ()); } @@ -163,5 +208,24 @@ namespace Terminal.Gui.DriverTests { Assert.Equal (Color.Red, fg); Assert.Equal (Color.Green, bg); } + + [Fact] + public void IsValid_Tests () + { + var attr = new Attribute (); + Assert.True (attr.HasValidColors); + + attr = new Attribute (Color.Red, Color.Green); + Assert.True (attr.HasValidColors); + + attr = new Attribute (Color.Red, Color.Invalid); + Assert.False (attr.HasValidColors); + + attr = new Attribute (Color.Invalid, Color.Green); + Assert.False (attr.HasValidColors); + + attr = new Attribute (Color.Invalid, Color.Invalid); + Assert.False (attr.HasValidColors); + } } } diff --git a/UnitTests/Drivers/ColorTests.cs b/UnitTests/Drivers/ColorTests.cs index f42463c81..3e8266e27 100644 --- a/UnitTests/Drivers/ColorTests.cs +++ b/UnitTests/Drivers/ColorTests.cs @@ -35,5 +35,14 @@ namespace Terminal.Gui.DriverTests { Application.Shutdown (); } + [Fact, AutoInitShutdown] + public void ColorScheme_New () + { + var scheme = new ColorScheme (); + var lbl = new Label (); + lbl.ColorScheme = scheme; + lbl.Redraw (lbl.Bounds); + } + } } \ No newline at end of file diff --git a/UnitTests/Drivers/ConsoleDriverTests.cs b/UnitTests/Drivers/ConsoleDriverTests.cs index a77d5f5a6..8289e9737 100644 --- a/UnitTests/Drivers/ConsoleDriverTests.cs +++ b/UnitTests/Drivers/ConsoleDriverTests.cs @@ -319,7 +319,7 @@ namespace Terminal.Gui.DriverTests { Application.Shutdown (); } - + [Fact, AutoInitShutdown] public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space () { @@ -441,7 +441,7 @@ namespace Terminal.Gui.DriverTests { } private static object packetLock = new object (); - + /// /// Sometimes when using remote tools EventKeyRecord sends 'virtual keystrokes'. /// These are indicated with the wVirtualKeyCode of 231. When we see this code @@ -487,6 +487,7 @@ namespace Terminal.Gui.DriverTests { if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control); }; + lock (packetLock) { Application.Run (); Application.Shutdown (); diff --git a/UnitTests/Drivers/KeyTests.cs b/UnitTests/Drivers/KeyTests.cs index 3d4d8606e..3116c8628 100644 --- a/UnitTests/Drivers/KeyTests.cs +++ b/UnitTests/Drivers/KeyTests.cs @@ -1,4 +1,5 @@ using System; +using Terminal.Gui; using Xunit; namespace Terminal.Gui.DriverTests { diff --git a/UnitTests/TopLevels/ToplevelTests.cs b/UnitTests/TopLevels/ToplevelTests.cs index 7cd2b1d06..88f9181dd 100644 --- a/UnitTests/TopLevels/ToplevelTests.cs +++ b/UnitTests/TopLevels/ToplevelTests.cs @@ -696,7 +696,7 @@ namespace Terminal.Gui.TopLevelTests { ((FakeDriver)Application.Driver).SetBufferSize (40, 15); MessageBox.Query ("About", "Hello Word", "Ok"); - } else if (iterations == 1) TestHelpers.AssertDriverContentsWithFrameAre (@" + } else if (iterations == 1) TestHelpers.AssertDriverContentsWithFrameAre (@" File ┌ Window ──────────────────────────────┐ │ │ @@ -712,7 +712,7 @@ namespace Terminal.Gui.TopLevelTests { │ │ └──────────────────────────────────────┘ CTRL-N New ", output); -else if (iterations == 2) { + else if (iterations == 2) { Assert.Null (Application.MouseGrabView); // Grab the mouse ReflectionTools.InvokePrivate ( @@ -815,8 +815,8 @@ else if (iterations == 2) { Assert.Null (Application.MouseGrabView); - } else if (iterations == 8) Application.RequestStop (); -else if (iterations == 9) Application.RequestStop (); + } else if (iterations == 8) Application.RequestStop (); + else if (iterations == 9) Application.RequestStop (); }; Application.Run (); @@ -956,7 +956,7 @@ else if (iterations == 9) Application.RequestStop (); Assert.Null (Application.MouseGrabView); - } else if (iterations == 8) Application.RequestStop (); + } else if (iterations == 8) Application.RequestStop (); }; Application.Run (); @@ -974,5 +974,42 @@ else if (iterations == 9) Application.RequestStop (); exception = Record.Exception (() => ((FakeDriver)Application.Driver).SetBufferSize (10, 0)); Assert.Null (exception); } + + [Fact, AutoInitShutdown] + public void OnEnter_OnLeave_Triggered_On_Application_Begin_End () + { + var isEnter = false; + var isLeave = false; + var v = new View (); + v.Enter += (_) => isEnter = true; + v.Leave += (_) => isLeave = true; + var top = Application.Top; + top.Add (v); + + Assert.False (v.CanFocus); + var exception = Record.Exception (() => top.OnEnter (top)); + Assert.Null (exception); + exception = Record.Exception (() => top.OnLeave (top)); + Assert.Null (exception); + + v.CanFocus = true; + Application.Begin (top); + + Assert.True (isEnter); + Assert.False (isLeave); + + isEnter = false; + var d = new Dialog (); + var rs = Application.Begin (d); + + Assert.False (isEnter); + Assert.True (isLeave); + + isLeave = false; + Application.End (rs); + + Assert.True (isEnter); + Assert.False (isLeave); + } } } \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 68da4e1d0..ae3766244 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -7,12 +7,12 @@ false - + - 1.0 - 1.0 - 1.0 - 1.0 + 2.0 + 2.0 + 2.0 + 2.0 TRACE diff --git a/UnitTests/Views/AutocompleteTests.cs b/UnitTests/Views/AutocompleteTests.cs index 51dd0076c..dca1fd81c 100644 --- a/UnitTests/Views/AutocompleteTests.cs +++ b/UnitTests/Views/AutocompleteTests.cs @@ -6,9 +6,16 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Terminal.Gui; using Xunit; +using Xunit.Abstractions; namespace Terminal.Gui.ViewTests { public class AutocompleteTests { + readonly ITestOutputHelper output; + + public AutocompleteTests (ITestOutputHelper output) + { + this.output = output; + } [Fact] public void Test_GenerateSuggestions_Simple () @@ -151,5 +158,84 @@ namespace Terminal.Gui.ViewTests { Assert.Empty (tv.Autocomplete.Suggestions); Assert.Equal (3, tv.Autocomplete.AllSuggestions.Count); } + + [Fact, AutoInitShutdown] + public void CursorLeft_CursorRight_Mouse_Button_Pressed_Does_Not_Show_Popup () + { + var tv = new TextView () { + Width = 50, + Height = 5, + Text = "This a long line and against TextView." + }; + tv.Autocomplete.AllSuggestions = Regex.Matches (tv.Text.ToString (), "\\w+") + .Select (s => s.Value) + .Distinct ().ToList (); + var top = Application.Top; + top.Add (tv); + Application.Begin (top); + + + for (int i = 0; i < 7; i++) { + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This a long line and against TextView.", output); + } + + Assert.True (tv.MouseEvent (new MouseEvent () { + X = 6, + Y = 0, + Flags = MouseFlags.Button1Pressed + })); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This a long line and against TextView.", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.g, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This ag long line and against TextView. + against ", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This ag long line and against TextView. + against ", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This ag long line and against TextView. + against ", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This ag long line and against TextView.", output); + + for (int i = 0; i < 3; i++) { + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This ag long line and against TextView.", output); + } + + Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This a long line and against TextView.", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.n, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This an long line and against TextView. + and ", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (@" +This an long line and against TextView.", output); + } } } \ No newline at end of file diff --git a/UnitTests/Views/ViewTests.cs b/UnitTests/Views/ViewTests.cs index 3d93e7d6c..01c1f19ad 100644 --- a/UnitTests/Views/ViewTests.cs +++ b/UnitTests/Views/ViewTests.cs @@ -1602,8 +1602,8 @@ Y // Calling the Text constructor. lbl = new Label (text); } - lbl.ColorScheme = new ColorScheme (); - lbl.Redraw (lbl.Bounds); + Application.Top.Add (lbl); + Application.Top.Redraw (Application.Top.Bounds); // should have the initial text Assert.Equal ('t', driver.Contents [0, 0, 0]);