diff --git a/Example/demo.cs b/Example/demo.cs index 1bcc64446..f31ecd101 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -562,9 +562,11 @@ static class Demo { if (Debugger.IsAttached) CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); - //Application.UseSystemConsole = true; + Application.UseSystemConsole = true; Application.Init(); + Application.HeightAsBuffer = true; + //ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler; var top = Application.Top; diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 3551b0699..0cf0c4497 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -20,6 +20,7 @@ namespace Terminal.Gui { public override int Cols => Curses.Cols; public override int Rows => Curses.Lines; public override int Top => 0; + public override bool HeightAsBuffer { get; set; } // Current row, and current col, tracked by Move/AddRune only int ccol, crow; @@ -70,6 +71,7 @@ namespace Terminal.Gui { public override void Refresh () { Curses.refresh (); if (Curses.CheckWinChange ()) { + Clip = new Rect (0, 0, Cols, Rows); TerminalResized?.Invoke (); } } @@ -645,16 +647,16 @@ namespace Terminal.Gui { } Action mouseHandler; - MainLoop mainLoop; public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) { // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called Curses.timeout (0); this.mouseHandler = mouseHandler; - this.mainLoop = mainLoop; - (mainLoop.Driver as UnixMainLoop).AddWatch (0, UnixMainLoop.Condition.PollIn, x => { + var mLoop = mainLoop.Driver as UnixMainLoop; + + mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => { ProcessInput (keyHandler, keyDownHandler, keyUpHandler, mouseHandler); return true; }); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 7566d6928..c5a611fe7 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -20,6 +20,7 @@ namespace Terminal.Gui { public override int Cols => cols; public override int Rows => rows; public override int Top => 0; + public override bool HeightAsBuffer { get; set; } // The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag int [,,] contents; diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index ab36cf957..53997351f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -18,83 +18,59 @@ namespace Terminal.Gui { public override int Cols => cols; public override int Rows => rows; public override int Top => top; + public override bool HeightAsBuffer { get; set; } // The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag int [,,] contents; bool [] dirtyLine; - public NetDriver () - { - ResizeScreen (); - UpdateOffscreen (); - } - - void UpdateOffscreen () - { - int cols = Cols; - int rows = Rows; - - contents = new int [rows, cols, 3]; - dirtyLine = new bool [rows]; - for (int row = 0; row < rows; row++) { - for (int c = 0; c < cols; c++) { - contents [row, c, 0] = ' '; - contents [row, c, 1] = (ushort)Colors.TopLevel.Normal; - contents [row, c, 2] = 0; - dirtyLine [row] = true; - } - } - } - static bool sync = false; - bool needMove; // Current row, and current col, tracked by Move/AddCh only int ccol, crow; public override void Move (int col, int row) { ccol = col; crow = row; - - if (Clip.Contains (col, row)) { - if (cols == Console.WindowWidth && rows == Console.WindowHeight) { - Console.SetCursorPosition (col, row); - needMove = false; - } - } else { - if (cols == Console.WindowWidth && rows == Console.WindowHeight) { - if (Console.WindowHeight > 0) { - Console.SetCursorPosition (Clip.X, Clip.Y); - } - needMove = true; - } - } } public override void AddRune (Rune rune) { + if (contents.Length != Rows * Cols * 3) { + return; + } rune = MakePrintable (rune); - if (Clip.Contains (ccol, crow)) { - if (needMove) { - if (cols == Console.WindowWidth && rows == Console.WindowHeight) { - Console.SetCursorPosition (ccol, crow); - } - needMove = false; - } + var runeWidth = Rune.ColumnWidth (rune); + if (Clip.Contains (ccol, crow) && ccol + Math.Max (runeWidth, 1) <= Cols) { contents [crow, ccol, 0] = (int)(uint)rune; contents [crow, ccol, 1] = currentAttribute; contents [crow, ccol, 2] = 1; dirtyLine [crow] = true; - } else - needMove = true; - ccol++; + + ccol++; + if (runeWidth > 1) { + for (int i = 1; i < runeWidth; i++) { + if (ccol < cols) { + contents [crow, ccol, 2] = 0; + } else { + break; + } + ccol++; + } + } + } else if (ccol < cols && crow < rows) { + contents [crow, ccol, 2] = 1; + dirtyLine [crow] = true; + } + //if (ccol == Cols) { // ccol = 0; // if (crow + 1 < Rows) // crow++; //} - if (sync) + if (sync) { UpdateScreen (); + } } public override void AddStr (ustring str) @@ -122,10 +98,24 @@ namespace Terminal.Gui { return new Attribute () { value = ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff) }; } + bool isWinPlatform; + public override void Init (Action terminalResized) { TerminalResized = terminalResized; + Console.TreatControlCAsInput = true; + var p = Environment.OSVersion.Platform; + if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { + isWinPlatform = true; + } + + cols = Console.WindowWidth; + rows = Console.WindowHeight; + + Clear (); + ResizeScreen (); + UpdateOffScreen (); Colors.TopLevel = new ColorScheme (); Colors.Base = new ColorScheme (); @@ -163,15 +153,71 @@ namespace Terminal.Gui { Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red); Colors.Error.HotFocus = Colors.Error.HotNormal; - Clear (); } void ResizeScreen () { - cols = Console.WindowWidth; - rows = Console.WindowHeight; + if (!HeightAsBuffer) { + if (Console.WindowHeight > 0) { + // Can raise an exception while is still resizing. + try { + // Not supported on Unix. + if (isWinPlatform) { +#pragma warning disable CA1416 + Console.CursorTop = 0; + Console.CursorLeft = 0; + Console.WindowTop = 0; + Console.WindowLeft = 0; + Console.SetBufferSize (Cols, Rows); +#pragma warning restore CA1416 + } else { + //Console.Out.Write ($"\x1b[8;{Console.WindowHeight};{Console.WindowWidth}t"); + Console.Out.Write ($"\x1b[0;0" + + $";{Rows};{Cols}w"); + } + } catch (System.IO.IOException) { + return; + } catch (ArgumentOutOfRangeException) { + return; + } + } + } else { + if (isWinPlatform && Console.WindowHeight > 0) { + // Can raise an exception while is still resizing. + try { +#pragma warning disable CA1416 + Console.WindowTop = Math.Max (Math.Min (top, Rows - Console.WindowHeight), 0); +#pragma warning restore CA1416 + } catch (Exception) { + return; + } + } else { + Console.Out.Write ($"\x1b[{top};{Console.WindowLeft}" + + $";{Rows};{Cols}w"); + } + } + Clip = new Rect (0, 0, Cols, Rows); - top = Console.WindowTop; + + contents = new int [Rows, Cols, 3]; + dirtyLine = new bool [Rows]; + } + + void UpdateOffScreen () + { + // Can raise an exception while is still resizing. + try { + for (int row = 0; row < rows; row++) { + for (int c = 0; c < cols; c++) { + contents [row, c, 0] = ' '; + contents [row, c, 1] = (ushort)Colors.TopLevel.Normal; + contents [row, c, 2] = 0; + dirtyLine [row] = true; + } + } + } catch (IndexOutOfRangeException) { } + + winChanging = false; } public override Attribute MakeAttribute (Color fore, Color back) @@ -196,29 +242,42 @@ namespace Terminal.Gui { public override void UpdateScreen () { - if (Rows == 0) { + if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3 + || (!HeightAsBuffer && Rows != Console.WindowHeight) + || (HeightAsBuffer && Rows != Console.BufferHeight)) { return; } - int rows = Rows; + int top = Top; + int rows = Math.Min (Console.WindowHeight + top, Rows); int cols = Cols; for (int row = top; row < rows; row++) { - if (!dirtyLine [row]) + if (!dirtyLine [row]) { continue; + } dirtyLine [row] = false; for (int col = 0; col < cols; col++) { - if (contents [row, col, 2] != 1) + if (contents [row, col, 2] != 1) { continue; - + } if (Console.WindowHeight > 0) { - Console.SetCursorPosition (col, row); + // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth. + try { + Console.SetCursorPosition (col, row); + } catch (Exception) { + return; + } } for (; col < cols && contents [row, col, 2] == 1; col++) { + // Needed for the .Net Framework. + if (row == rows - 1 && col == cols - 1) { + break; + } var color = contents [row, col, 1]; - if (color != redrawColor) + if (color != redrawColor) { SetColor (color); - + } Console.Write ((char)contents [row, col, 0]); contents [row, col, 2] = 0; } @@ -230,12 +289,6 @@ namespace Terminal.Gui { public override void Refresh () { - if (Console.WindowWidth != Cols || Console.WindowHeight != Rows || Console.WindowTop != Top) { - ResizeScreen (); - UpdateOffscreen (); - TerminalResized.Invoke (); - } - UpdateScreen (); } @@ -243,10 +296,12 @@ namespace Terminal.Gui { { // Prevents the exception of size changing during resizing. try { - if (ccol > 0 && ccol < Console.WindowWidth && crow > 0 && crow < Console.WindowHeight) { + if (ccol >= 0 && ccol <= cols && crow >= 0 && crow <= rows) { Console.SetCursorPosition (ccol, crow); } - } catch (ArgumentOutOfRangeException) { } + } catch (System.IO.IOException) { + } catch (ArgumentOutOfRangeException) { + } } public override void StartReportingMouseMoves () @@ -269,36 +324,39 @@ namespace Terminal.Gui { Key MapKey (ConsoleKeyInfo keyInfo) { - MapKeyModifiers (keyInfo); + MapKeyModifiers (keyInfo, (Key)keyInfo.Key); switch (keyInfo.Key) { case ConsoleKey.Escape: - return Key.Esc; + return MapKeyModifiers (keyInfo, Key.Esc); case ConsoleKey.Tab: return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab; case ConsoleKey.Home: - return Key.Home; + return MapKeyModifiers (keyInfo, Key.Home); case ConsoleKey.End: - return Key.End; + return MapKeyModifiers (keyInfo, Key.End); case ConsoleKey.LeftArrow: - return Key.CursorLeft; + return MapKeyModifiers (keyInfo, Key.CursorLeft); case ConsoleKey.RightArrow: - return Key.CursorRight; + return MapKeyModifiers (keyInfo, Key.CursorRight); case ConsoleKey.UpArrow: - return Key.CursorUp; + return MapKeyModifiers (keyInfo, Key.CursorUp); case ConsoleKey.DownArrow: - return Key.CursorDown; + return MapKeyModifiers (keyInfo, Key.CursorDown); case ConsoleKey.PageUp: - return Key.PageUp; + return MapKeyModifiers (keyInfo, Key.PageUp); case ConsoleKey.PageDown: - return Key.PageDown; + return MapKeyModifiers (keyInfo, Key.PageDown); case ConsoleKey.Enter: - return Key.Enter; + return MapKeyModifiers (keyInfo, Key.Enter); case ConsoleKey.Spacebar: - return Key.Space; + return MapKeyModifiers (keyInfo, Key.Space); case ConsoleKey.Backspace: - return Key.Backspace; + return MapKeyModifiers (keyInfo, Key.Backspace); case ConsoleKey.Delete: - return Key.Delete; + return MapKeyModifiers (keyInfo, Key.DeleteChar); + case ConsoleKey.Insert: + return MapKeyModifiers (keyInfo, Key.InsertChar); + case ConsoleKey.Oem1: case ConsoleKey.Oem2: @@ -327,7 +385,7 @@ namespace Terminal.Gui { } if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) { - return (Key)((uint)Key.A + delta); + return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta)); } } return (Key)((uint)keyInfo.KeyChar); @@ -342,7 +400,7 @@ namespace Terminal.Gui { } if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) { - return (Key)((uint)Key.D0 + delta); + return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta)); } } return (Key)((uint)keyInfo.KeyChar); @@ -350,7 +408,7 @@ namespace Terminal.Gui { if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) { var delta = key - ConsoleKey.F1; if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { - return (Key)((uint)Key.F1 + delta); + return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta)); } return (Key)((uint)Key.F1 + delta); @@ -364,41 +422,82 @@ namespace Terminal.Gui { KeyModifiers keyModifiers; - void MapKeyModifiers (ConsoleKeyInfo keyInfo) + Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) { - if (keyModifiers == null) + if (keyModifiers == null) { keyModifiers = new KeyModifiers (); - - if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) + } + Key keyMod = new Key (); + if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) { + keyMod = Key.ShiftMask; keyModifiers.Shift = true; - if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) + } + if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) { + keyMod |= Key.CtrlMask; keyModifiers.Ctrl = true; - if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) + } + if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) { + keyMod |= Key.AltMask; keyModifiers.Alt = true; + } + + return keyMod != Key.Null ? keyMod | key : key; } + bool winChanging; public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) { + var mLoop = mainLoop.Driver as NetMainLoop; + // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - (mainLoop.Driver as NetMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) { + mLoop.KeyPressed = (consoleKey) => { var map = MapKey (consoleKey); if (map == (Key)0xffffffff) { return; } - keyHandler (new KeyEvent (map, keyModifiers)); - keyUpHandler (new KeyEvent (map, keyModifiers)); - keyModifiers = null; + + if (map == (Key.Space | Key.CtrlMask) || map == (Key.Space | Key.AltMask)) { + map = Key.AltMask; + keyModifiers.Alt = true; + keyModifiers.Ctrl = false; + keyDownHandler (new KeyEvent (map, keyModifiers)); + keyUpHandler (new KeyEvent (map, keyModifiers)); + } else { + keyDownHandler (new KeyEvent (map, keyModifiers)); + keyHandler (new KeyEvent (map, keyModifiers)); + keyUpHandler (new KeyEvent (map, keyModifiers)); + } + keyModifiers = new KeyModifiers (); + }; + + mLoop.WinChanged = (e) => { + winChanging = true; + const int Min_WindowWidth = 14; + Size size = new Size (); + if (!HeightAsBuffer) { + size = new Size (Math.Max (Min_WindowWidth, Console.WindowWidth), + Console.WindowHeight); + top = 0; + } else { + size = new Size (Console.BufferWidth, Console.BufferHeight); + top = e; + } + cols = size.Width; + rows = size.Height; + ResizeScreen (); + UpdateOffScreen (); + if (!winChanging) { + TerminalResized.Invoke (); + } }; } public override void SetColors (ConsoleColor foreground, ConsoleColor background) { - throw new NotImplementedException (); } public override void SetColors (short foregroundColorId, short backgroundColorId) { - throw new NotImplementedException (); } public override void CookMouse () @@ -424,14 +523,15 @@ namespace Terminal.Gui { /// /// This implementation is used for NetDriver. /// - public class NetMainLoop : IMainLoopDriver { + internal class NetMainLoop : IMainLoopDriver { ManualResetEventSlim keyReady = new ManualResetEventSlim (false); ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); ManualResetEventSlim winChange = new ManualResetEventSlim (false); - ConsoleKeyInfo? keyResult = null; + Queue keyResult = new Queue (); MainLoop mainLoop; ConsoleDriver consoleDriver; bool winChanged; + int newTop; CancellationTokenSource tokenSource = new CancellationTokenSource (); /// @@ -439,6 +539,11 @@ namespace Terminal.Gui { /// public Action KeyPressed; + /// + /// Invoked when the window is changed. + /// + public Action WinChanged; + /// /// Initializes the class with the console driver. /// @@ -449,7 +554,7 @@ namespace Terminal.Gui { public NetMainLoop (ConsoleDriver consoleDriver = null) { if (consoleDriver == null) { - throw new ArgumentNullException ("console driver instance must be provided."); + throw new ArgumentNullException ("Console driver instance must be provided."); } this.consoleDriver = consoleDriver; } @@ -459,7 +564,9 @@ namespace Terminal.Gui { while (true) { waitForProbe.Wait (); waitForProbe.Reset (); - keyResult = Console.ReadKey (true); + if (keyResult.Count == 0) { + keyResult.Enqueue (Console.ReadKey (true)); + } keyReady.Set (); } } @@ -475,12 +582,23 @@ namespace Terminal.Gui { } } + int lastWindowHeight; void WaitWinChange () { while (true) { - if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows - || Console.WindowTop != consoleDriver.Top) { // Top only working on Windows. - return; + if (!consoleDriver.HeightAsBuffer) { + if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) { + return; + } + } else { + if (Console.BufferWidth != consoleDriver.Cols || Console.BufferHeight != consoleDriver.Rows + || Console.WindowTop != consoleDriver.Top + || Console.WindowHeight != lastWindowHeight) { + // Top only working on Windows. + newTop = Console.WindowTop; + lastWindowHeight = Console.WindowHeight; + return; + } } } } @@ -499,8 +617,6 @@ namespace Terminal.Gui { bool IMainLoopDriver.EventsPending (bool wait) { - long now = DateTime.UtcNow.Ticks; - waitForProbe.Set (); winChange.Set (); @@ -519,7 +635,7 @@ namespace Terminal.Gui { } if (!tokenSource.IsCancellationRequested) { - return keyResult.HasValue || CheckTimers (wait, out _) || winChanged; + return keyResult.Count > 0 || CheckTimers (wait, out _) || winChanged; } tokenSource.Dispose (); @@ -552,14 +668,12 @@ namespace Terminal.Gui { void IMainLoopDriver.MainIteration () { - if (keyResult.HasValue) { - var kr = keyResult; - keyResult = null; - KeyPressed?.Invoke (kr.Value); + if (keyResult.Count > 0) { + KeyPressed?.Invoke (keyResult.Dequeue ().Value); } if (winChanged) { winChanged = false; - consoleDriver.Refresh (); + WinChanged?.Invoke (newTop); } } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 714a6a76f..abc70c0a1 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -86,6 +86,32 @@ namespace Terminal.Gui { return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = window.Left, Y = window.Top }, ref window); } + public void ReadFromConsoleOutput (Size size, Coord coords) + { + ScreenBuffer = CreateConsoleScreenBuffer ( + DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + ShareMode.FileShareRead | ShareMode.FileShareWrite, + IntPtr.Zero, + 1, + IntPtr.Zero + ); + if (ScreenBuffer == INVALID_HANDLE_VALUE) { + var err = Marshal.GetLastWin32Error (); + + if (err != 0) + throw new System.ComponentModel.Win32Exception (err); + } + + if (!SetConsoleActiveScreenBuffer (ScreenBuffer)) { + var err = Marshal.GetLastWin32Error (); + throw new System.ComponentModel.Win32Exception (err); + } + + OriginalStdOutChars = new CharInfo [size.Height * size.Width]; + SmallRect window = new SmallRect (); + ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window); + } + public bool SetCursorPosition (Coord position) { return SetConsoleCursorPosition (ScreenBuffer, position); @@ -207,7 +233,7 @@ namespace Terminal.Gui { public override string ToString () => $"({X},{Y})"; }; - internal struct WindowBufferSizeRecord { + public struct WindowBufferSizeRecord { public Coordinate size; public WindowBufferSizeRecord (short x, short y) @@ -355,6 +381,20 @@ namespace Terminal.Gui { } } + [StructLayout (LayoutKind.Sequential)] + public struct ConsoleKeyInfoEx { + public ConsoleKeyInfo consoleKeyInfo; + public bool CapsLock; + public bool NumLock; + + public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock) + { + this.consoleKeyInfo = consoleKeyInfo; + CapsLock = capslock; + NumLock = numlock; + } + } + [DllImport ("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle (int nStdHandle); @@ -431,231 +471,106 @@ namespace Terminal.Gui { return numberEventsRead == 0 ? null - : new [] {Marshal.PtrToStructure (pRecord)}; + : new [] { Marshal.PtrToStructure (pRecord) }; } catch (Exception) { return null; } finally { Marshal.FreeHGlobal (pRecord); } } -#if false // Not needed on the constructor. Perhaps could be used on resizing. To study. + + // Not needed on the constructor. Perhaps could be used on resizing. To study. [DllImport ("kernel32.dll", ExactSpelling = true)] - private static extern IntPtr GetConsoleWindow (); + static extern IntPtr GetConsoleWindow (); - [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern bool ShowWindow (IntPtr hWnd, int nCmdShow); + [DllImport ("user32.dll")] + [return: MarshalAs (UnmanagedType.Bool)] + static extern bool GetWindowPlacement (IntPtr hWnd, ref WindowPlacement lpwndpl); + [DllImport ("user32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + static extern bool SetWindowPlacement (IntPtr hWnd, [In] ref WindowPlacement lpwndpl); + + internal struct WindowPlacement { + public int length; + public int flags; + public int showCmd; + public System.Drawing.Point ptMinPosition; + public System.Drawing.Point ptMaxPosition; + public System.Drawing.Rectangle rcNormalPosition; + public System.Drawing.Rectangle rcDevice; + } + + // flags + public const int WPF_SET_MIN_POSITION = 1; + public const int WPF_RESTORE_TO_MAXIMIZED = 2; + public const int WPF_ASYNC_WINDOWPLACEMENT = 4; + + // showCmd public const int HIDE = 0; - public const int MAXIMIZE = 3; + public const int NORMAL = 1; + public const int SHOW_MINIMIZED = 2; + public const int SHOW_MAXIMIZED = 3; + public const int SHOW_NOACTIVATE = 4; + public const int SHOW = 5; public const int MINIMIZE = 6; + public const int SHOW_MIN_NOACTIVE = 7; + public const int SHOW_NA = 8; public const int RESTORE = 9; + public const int SHOW_DEFAULT = 10; + public const int FORCE_MINIMIZE = 11; - internal void ShowWindow (int state) + internal WindowPlacement GetWindow () { IntPtr thisConsole = GetConsoleWindow (); - ShowWindow (thisConsole, state); - } -#endif -#if false // See: https://github.com/migueldeicaza/gui.cs/issues/357 - [StructLayout (LayoutKind.Sequential)] - public struct SMALL_RECT { - public short Left; - public short Top; - public short Right; - public short Bottom; + WindowPlacement placement = new WindowPlacement { + length = Marshal.SizeOf (typeof (WindowPlacement)) + }; + GetWindowPlacement (thisConsole, ref placement); - public SMALL_RECT (short Left, short Top, short Right, short Bottom) - { - this.Left = Left; - this.Top = Top; - this.Right = Right; - this.Bottom = Bottom; - } + return placement; } - [StructLayout (LayoutKind.Sequential)] - public struct CONSOLE_SCREEN_BUFFER_INFO { - public int dwSize; - public int dwCursorPosition; - public short wAttributes; - public SMALL_RECT srWindow; - public int dwMaximumWindowSize; - } - - [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool GetConsoleScreenBufferInfo (IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo); - - // Theoretically GetConsoleScreenBuffer height should give the console Windoww size - // It does not work, however, and always returns the size the window was initially created at - internal Size GetWindowSize () + internal void SetWindow (int showCmd) { - var consoleScreenBufferInfo = new CONSOLE_SCREEN_BUFFER_INFO (); - //consoleScreenBufferInfo.dwSize = Marshal.SizeOf (typeof (CONSOLE_SCREEN_BUFFER_INFO)); - GetConsoleScreenBufferInfo (OutputHandle, out consoleScreenBufferInfo); - return new Size (consoleScreenBufferInfo.srWindow.Right - consoleScreenBufferInfo.srWindow.Left, - consoleScreenBufferInfo.srWindow.Bottom - consoleScreenBufferInfo.srWindow.Top); + IntPtr thisConsole = GetConsoleWindow (); + WindowPlacement placement = new WindowPlacement { + length = Marshal.SizeOf (typeof (WindowPlacement)), + showCmd = showCmd + }; + SetWindowPlacement (thisConsole, ref placement); + } + +#if false + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool GetConsoleScreenBufferInfo (IntPtr hConsoleOutput, out ConsoleScreenBufferInfo ConsoleScreenBufferInfo); + + // Theoretically GetConsoleScreenBuffer height should give the console Window size, but the Top is always 0. + // It does not work, however, and always returns the size the window was initially created at + internal Size GetWindowSize (IntPtr handle) + { + GetConsoleScreenBufferInfo (handle, out ConsoleScreenBufferInfo consoleScreenBufferInfo); + return new Size (consoleScreenBufferInfo.srWindow.Right - consoleScreenBufferInfo.srWindow.Left + 1, + consoleScreenBufferInfo.srWindow.Bottom - consoleScreenBufferInfo.srWindow.Top + 1); } #endif } - internal class WindowsDriver : ConsoleDriver, IMainLoopDriver { + internal class WindowsDriver : ConsoleDriver { static bool sync = false; - ManualResetEventSlim eventReady = new ManualResetEventSlim (false); - ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); - MainLoop mainLoop; WindowsConsole.CharInfo [] OutputBuffer; - int cols, rows; + int cols, rows, top; WindowsConsole winConsole; WindowsConsole.SmallRect damageRegion; public override int Cols => cols; public override int Rows => rows; - public override int Top => 0; + public override int Top => top; + public override bool HeightAsBuffer { get; set; } - public WindowsDriver () - { - winConsole = new WindowsConsole (); - - SetupColorsAndBorders (); - - cols = Console.WindowWidth; - rows = Console.WindowHeight; -#if false - winConsole.ShowWindow (WindowsConsole.RESTORE); -#endif - WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); - - ResizeScreen (); - UpdateOffScreen (); - - Task.Run ((Action)WindowsInputHandler); - } - - private void SetupColorsAndBorders () - { - Colors.TopLevel = new ColorScheme (); - Colors.Base = new ColorScheme (); - Colors.Dialog = new ColorScheme (); - Colors.Menu = new ColorScheme (); - Colors.Error = new ColorScheme (); - - Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black); - Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); - Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); - Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan); - - Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue); - Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); - Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue); - Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); - - Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray); - Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black); - Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray); - Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black); - Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray); - - Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); - Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray); - Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray); - Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray); - - Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White); - Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed); - Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White); - Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed); - } - - [StructLayout (LayoutKind.Sequential)] - public struct ConsoleKeyInfoEx { - public ConsoleKeyInfo consoleKeyInfo; - public bool CapsLock; - public bool NumLock; - - public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock) - { - this.consoleKeyInfo = consoleKeyInfo; - CapsLock = capslock; - NumLock = numlock; - } - } - - // The records that we keep fetching - WindowsConsole.InputRecord [] result, records = new WindowsConsole.InputRecord [1]; - - void WindowsInputHandler () - { - while (true) { - waitForProbe.Wait (); - waitForProbe.Reset (); - - result = winConsole.ReadConsoleInput (); - - eventReady.Set (); - } - } - - void IMainLoopDriver.Setup (MainLoop mainLoop) - { - this.mainLoop = mainLoop; - } - - void IMainLoopDriver.Wakeup () - { - //tokenSource.Cancel (); - eventReady.Reset (); - eventReady.Set (); - } - - bool IMainLoopDriver.EventsPending (bool wait) - { - if (CheckTimers (wait, out var waitTimeout)) { - return true; - } - - result = null; - waitForProbe.Set (); - - try { - if (!tokenSource.IsCancellationRequested) { - eventReady.Wait (waitTimeout, tokenSource.Token); - } - } catch (OperationCanceledException) { - return true; - } finally { - eventReady.Reset (); - } - - if (!tokenSource.IsCancellationRequested) { - return result != null || CheckTimers (wait, out waitTimeout); - } - - tokenSource.Dispose (); - tokenSource = new CancellationTokenSource (); - return true; - } - - bool CheckTimers (bool wait, out int waitTimeout) - { - long now = DateTime.UtcNow.Ticks; - - if (mainLoop.timeouts.Count > 0) { - waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); - if (waitTimeout < 0) - return true; - } else { - waitTimeout = -1; - } - - if (!wait) - waitTimeout = 0; - - int ic; - lock (mainLoop.idleHandlers) { - ic = mainLoop.idleHandlers.Count; - } - - return ic > 0; + public WindowsConsole WinConsole { + get => winConsole; + private set => winConsole = value; } Action keyHandler; @@ -663,20 +578,51 @@ namespace Terminal.Gui { Action keyUpHandler; Action mouseHandler; + public WindowsDriver () + { + winConsole = new WindowsConsole (); + } + + bool winChanging; + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) { this.keyHandler = keyHandler; this.keyDownHandler = keyDownHandler; this.keyUpHandler = keyUpHandler; this.mouseHandler = mouseHandler; + + var mLoop = mainLoop.Driver as WindowsMainLoop; + + mLoop.ProcessInput = (e) => ProcessInput (e); + + mLoop.WinChanged = (e) => ChangeWin (e); } - void IMainLoopDriver.MainIteration () + void ChangeWin (Size size) { - if (result == null) - return; + if (!HeightAsBuffer) { + winChanging = true; + top = 0; + cols = size.Width; + rows = size.Height; + var bufferCoords = new WindowsConsole.Coord () { + X = (short)cols, + Y = (short)rows + }; + winConsole.ReadFromConsoleOutput (size, bufferCoords); + ResizeScreen (); + UpdateOffScreen (); + if (!winChanging) { + TerminalResized.Invoke (); + } else { + System.Diagnostics.Debugger.Break (); + } + } + } - var inputEvent = result [0]; + void ProcessInput (WindowsConsole.InputRecord inputEvent) + { switch (inputEvent.EventType) { case WindowsConsole.EventType.Key: var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent)); @@ -766,20 +712,18 @@ namespace Terminal.Gui { break; case WindowsConsole.EventType.WindowBufferSize: - cols = inputEvent.WindowBufferSizeEvent.size.X; - rows = inputEvent.WindowBufferSizeEvent.size.Y; - ResizeScreen (); - UpdateOffScreen (); - TerminalResized?.Invoke (); + if (HeightAsBuffer) { + cols = inputEvent.WindowBufferSizeEvent.size.X; + rows = inputEvent.WindowBufferSizeEvent.size.Y; + ResizeScreen (); + UpdateOffScreen (); + TerminalResized?.Invoke (); + } break; case WindowsConsole.EventType.Focus: break; - - default: - break; } - result = null; } WindowsConsole.ButtonState? LastMouseButtonPressed = null; @@ -871,23 +815,23 @@ namespace Terminal.Gui { Y = mouseEvent.MousePosition.Y }; //if (p == point) { - switch (LastMouseButtonPressed) { - case WindowsConsole.ButtonState.Button1Pressed: - mouseFlag = MouseFlags.Button1Clicked; - break; + switch (LastMouseButtonPressed) { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1Clicked; + break; - case WindowsConsole.ButtonState.Button2Pressed: - mouseFlag = MouseFlags.Button2Clicked; - break; + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2Clicked; + break; - case WindowsConsole.ButtonState.RightmostButtonPressed: - mouseFlag = MouseFlags.Button3Clicked; - break; - } - point = new Point () { - X = mouseEvent.MousePosition.X, - Y = mouseEvent.MousePosition.Y - }; + case WindowsConsole.ButtonState.RightmostButtonPressed: + mouseFlag = MouseFlags.Button3Clicked; + break; + } + point = new Point () { + X = mouseEvent.MousePosition.X, + Y = mouseEvent.MousePosition.Y + }; //} else { // mouseFlag = 0; //} @@ -1009,7 +953,7 @@ namespace Terminal.Gui { KeyModifiers keyModifiers; - public ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent) + public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent) { var state = keyEvent.dwControlKeyState; @@ -1036,10 +980,10 @@ namespace Terminal.Gui { keyModifiers.Scrolllock = scrolllock; var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control); - return new ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock); + return new WindowsConsole.ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock); } - public Key MapKey (ConsoleKeyInfoEx keyInfoEx) + public Key MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) { var keyInfo = keyInfoEx.consoleKeyInfo; switch (keyInfo.Key) { @@ -1163,7 +1107,7 @@ namespace Terminal.Gui { return (Key)(0xffffffff); } - private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) + Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key) { Key keyMod = new Key (); if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) @@ -1179,7 +1123,48 @@ namespace Terminal.Gui { public override void Init (Action terminalResized) { TerminalResized = terminalResized; - SetupColorsAndBorders (); + + cols = Console.WindowWidth; + rows = Console.WindowHeight; +#if false + winConsole.ShowWindow (WindowsConsole.RESTORE); +#endif + WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); + + ResizeScreen (); + UpdateOffScreen (); + + Colors.TopLevel = new ColorScheme (); + Colors.Base = new ColorScheme (); + Colors.Dialog = new ColorScheme (); + Colors.Menu = new ColorScheme (); + Colors.Error = new ColorScheme (); + + Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black); + Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); + Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); + Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan); + + Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue); + Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); + Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue); + Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); + + Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray); + Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black); + Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray); + Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black); + Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray); + + Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); + Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray); + Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray); + Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray); + + Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White); + Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed); + Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White); + Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed); } void ResizeScreen () @@ -1196,12 +1181,15 @@ namespace Terminal.Gui { void UpdateOffScreen () { - for (int row = 0; row < rows; row++) + for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { int position = row * cols + col; OutputBuffer [position].Attributes = (ushort)Colors.TopLevel.Normal; OutputBuffer [position].Char.UnicodeChar = ' '; } + } + + winChanging = false; } int ccol, crow; @@ -1245,7 +1233,6 @@ namespace Terminal.Gui { } int currentAttribute; - CancellationTokenSource tokenSource = new CancellationTokenSource (); public override void SetAttribute (Attribute c) { @@ -1298,16 +1285,16 @@ namespace Terminal.Gui { Y = (short)Clip.Height }; - var window = new WindowsConsole.SmallRect () { - Top = 0, - Left = 0, - Right = (short)Clip.Right, - Bottom = (short)Clip.Bottom - }; + //var window = new WindowsConsole.SmallRect () { + // Top = 0, + // Left = 0, + // Right = (short)Clip.Right, + // Bottom = (short)Clip.Bottom + //}; UpdateCursor (); winConsole.WriteToConsole (OutputBuffer, bufferCoords, damageRegion); - // System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n"); + //System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n"); WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); } @@ -1325,7 +1312,7 @@ namespace Terminal.Gui { winConsole.Cleanup (); } -#region Unused + #region Unused public override void SetColors (ConsoleColor foreground, ConsoleColor background) { } @@ -1353,8 +1340,172 @@ namespace Terminal.Gui { public override void CookMouse () { } -#endregion - + #endregion } + /// + /// Mainloop intended to be used with the , and can + /// only be used on Windows. + /// + /// + /// This implementation is used for WindowsDriver. + /// + internal class WindowsMainLoop : IMainLoopDriver { + ManualResetEventSlim eventReady = new ManualResetEventSlim (false); + ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); + ManualResetEventSlim winChange = new ManualResetEventSlim (false); + MainLoop mainLoop; + ConsoleDriver consoleDriver; + WindowsConsole winConsole; + bool winChanged; + WindowsConsole.WindowPlacement windowPlacement; + Size windowSize; + CancellationTokenSource tokenSource = new CancellationTokenSource (); + + // The records that we keep fetching + WindowsConsole.InputRecord [] result = new WindowsConsole.InputRecord [1]; + + /// + /// Invoked when a Key is pressed or released. + /// + public Action ProcessInput; + + /// + /// Invoked when the window is changed. + /// + public Action WinChanged; + + public WindowsMainLoop (ConsoleDriver consoleDriver = null) + { + if (consoleDriver == null) { + throw new ArgumentNullException ("Console driver instance must be provided."); + } + this.consoleDriver = consoleDriver; + winConsole = ((WindowsDriver)consoleDriver).WinConsole; + } + + void IMainLoopDriver.Setup (MainLoop mainLoop) + { + this.mainLoop = mainLoop; + Task.Run (WindowsInputHandler); + Task.Run (CheckWinChange); + } + + void WindowsInputHandler () + { + while (true) { + waitForProbe.Wait (); + waitForProbe.Reset (); + + result = winConsole.ReadConsoleInput (); + + eventReady.Set (); + } + } + + void CheckWinChange () + { + while (true) { + winChange.Wait (); + winChange.Reset (); + WaitWinChange (); + winChanged = true; + eventReady.Set (); + } + } + + void WaitWinChange () + { + while (true) { + if (!consoleDriver.HeightAsBuffer) { + windowPlacement = winConsole.GetWindow (); + if (windowPlacement.rcNormalPosition.Size.Height > -1) { + windowSize = new Size (Math.Max (((windowPlacement.rcNormalPosition.Size.Width - + windowPlacement.rcNormalPosition.X) / 8) - 2, 0), + Math.Max (((windowPlacement.rcNormalPosition.Size.Height - + windowPlacement.rcNormalPosition.Y) / 16) - 7, 0)); + if (windowPlacement.showCmd != WindowsConsole.SHOW_MAXIMIZED + && (windowSize.Width != consoleDriver.Cols || windowSize.Height != consoleDriver.Rows)) { + return; + } else if (windowPlacement.showCmd == WindowsConsole.SHOW_MAXIMIZED + && (Console.LargestWindowWidth != consoleDriver.Cols || Console.LargestWindowHeight != consoleDriver.Rows)) { + windowSize = new Size (Console.LargestWindowWidth, Console.LargestWindowHeight); + return; + } + } + } + } + } + + void IMainLoopDriver.Wakeup () + { + //tokenSource.Cancel (); + eventReady.Set (); + } + + bool IMainLoopDriver.EventsPending (bool wait) + { + if (CheckTimers (wait, out var waitTimeout)) { + return true; + } + + //result = null; + waitForProbe.Set (); + winChange.Set (); + + try { + if (!tokenSource.IsCancellationRequested) { + eventReady.Wait (waitTimeout, tokenSource.Token); + } + } catch (OperationCanceledException) { + return true; + } finally { + eventReady.Reset (); + } + + if (!tokenSource.IsCancellationRequested) { + return result != null || CheckTimers (wait, out waitTimeout) || winChanged; + } + + tokenSource.Dispose (); + tokenSource = new CancellationTokenSource (); + return true; + } + + bool CheckTimers (bool wait, out int waitTimeout) + { + long now = DateTime.UtcNow.Ticks; + + if (mainLoop.timeouts.Count > 0) { + waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond); + if (waitTimeout < 0) + return true; + } else { + waitTimeout = -1; + } + + if (!wait) + waitTimeout = 0; + + int ic; + lock (mainLoop.idleHandlers) { + ic = mainLoop.idleHandlers.Count; + } + + return ic > 0; + } + + void IMainLoopDriver.MainIteration () + { + if (result != null) { + var inputEvent = result [0]; + result = null; + ProcessInput?.Invoke (inputEvent); + } + if (winChanged) { + winChanged = false; + WinChanged?.Invoke (windowSize); + } + } + } } diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 51c4dc941..e476e5e56 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -59,7 +59,7 @@ namespace Terminal.Gui { /// The current in use. /// public static ConsoleDriver Driver; - + /// /// The object used for the application on startup () /// @@ -73,11 +73,32 @@ namespace Terminal.Gui { public static Toplevel Current { get; private set; } /// - /// TThe current object being redrawn. + /// The current object being redrawn. /// /// /// The current. public static View CurrentView { get; set; } + /// + /// The current used in the terminal. + /// + public static bool HeightAsBuffer { + get { + if (Driver == null) { + throw new ArgumentNullException ("The driver must be initialized first."); + } + return Driver.HeightAsBuffer; + } + set { + if (Driver == null) { + throw new ArgumentNullException ("The driver must be initialized first."); + } + if (Driver.HeightAsBuffer != value) { + Driver.HeightAsBuffer = value; + Driver.Refresh (); + } + } + } + /// /// The driver for the application /// @@ -167,7 +188,7 @@ namespace Terminal.Gui { static void Init (Func topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) { if (_initialized && driver == null) return; - + // Used only for start debugging on Unix. //#if DEBUG // while (!System.Diagnostics.Debugger.IsAttached) { @@ -193,9 +214,8 @@ namespace Terminal.Gui { Driver = new NetDriver (); mainLoopDriver = new NetMainLoop (Driver); } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { - var windowsDriver = new WindowsDriver (); - mainLoopDriver = windowsDriver; - Driver = windowsDriver; + Driver = new WindowsDriver (); + mainLoopDriver = new WindowsMainLoop (Driver); } else { mainLoopDriver = new UnixMainLoop (); Driver = new CursesDriver (); @@ -588,11 +608,11 @@ namespace Terminal.Gui { } else if (!wait) { return; } - if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay)) { + if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { Top.Redraw (Top.Bounds); state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); } - if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay) { + if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded) { state.Toplevel.Redraw (state.Toplevel.Bounds); if (DebugDrawBounds) { DrawBounds (state.Toplevel); diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index c0084e9ce..1f618e5d7 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/Terminal.Gui/Core/ConsoleDriver.cs @@ -546,6 +546,12 @@ namespace Terminal.Gui { /// public abstract int Top { get; } + /// + /// If false height is measured by the window height and thus no scrolling. + /// If true then height is measured by the buffer height, enabling scrolling. + /// + public abstract bool HeightAsBuffer { get; set; } + /// /// Initializes the driver /// @@ -570,12 +576,10 @@ namespace Terminal.Gui { /// public static Rune MakePrintable (Rune c) { - if (c <= 0x1F) { - // ASCII (C0) control characters. - return new Rune (c + 0x2400); - } else if (c >= 0x80 && c <= 0x9F) { + if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) { + // ASCII (C0) control characters. // C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1) - return new Rune (0x25a1); // U+25A1, WHITE SQUARE, □: + return new Rune (c + 0x2400); } else { return c; } diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 2e555a547..da4eb22b7 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -401,10 +401,10 @@ namespace Terminal.Gui { { EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny); if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { - if (top.X is Pos.PosAbsolute && top.Bounds.X != nx) { + if ((top.X == null || top.X is Pos.PosAbsolute) && top.Bounds.X != nx) { top.X = nx; } - if (top.Y is Pos.PosAbsolute && top.Bounds.Y != ny) { + if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Bounds.Y != ny) { top.Y = ny; } } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index e5f1177a3..ac0a519d0 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1314,10 +1314,13 @@ namespace Terminal.Gui { return; } + Application.CurrentView = this; + var clipRect = new Rect (Point.Empty, frame.Size); - if (ColorScheme != null) + if (ColorScheme != null) { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); + } if (!ustring.IsNullOrEmpty (Text)) { Clear (); @@ -1333,14 +1336,14 @@ namespace Terminal.Gui { if (subviews != null) { foreach (var view in subviews) { - if (!view.NeedDisplay.IsEmpty || view.ChildNeedsDisplay) { + if (!view.NeedDisplay.IsEmpty || view.ChildNeedsDisplay || view.LayoutNeeded) { if (view.Frame.IntersectsWith (clipRect) && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { if (view.LayoutNeeded) view.LayoutSubviews (); Application.CurrentView = view; // Draw the subview - // Use the view's bounds (view-relative; Location will always be (0,0) because + // Use the view's bounds (view-relative; Location will always be (0,0) if (view.Visible) { view.Redraw (view.Bounds); } @@ -1350,6 +1353,7 @@ namespace Terminal.Gui { } } } + ClearLayoutNeeded (); ClearNeedsDisplay (); } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 7a749dafd..e2a67beaa 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1415,7 +1415,7 @@ namespace Terminal.Gui { CloseMenu (); if (openedByAltKey) { openedByAltKey = false; - LastFocused.SetFocus (); + LastFocused?.SetFocus (); } break; diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 849978f26..923a61f60 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -1,4 +1,7 @@ -using NStack; +#define DRAW_CONTENT +//#define BASE_DRAW_CONTENT + +using NStack; using System.Collections.Generic; using System.Linq; using System.Text; @@ -58,15 +61,20 @@ namespace UICatalog { CreateRadio("End", CharMap.MaxCodePointVal - 16, CharMap.MaxCodePointVal), }; - var jumpList = new RadioGroup (radioItems.Select (t => t.radioLabel).ToArray ()); - jumpList.X = Pos.X (label); - jumpList.Y = Pos.Bottom (label); - jumpList.Width = Dim.Fill (); + var jumpList = new RadioGroup (radioItems.Select (t => t.radioLabel).ToArray ()) { + X = Pos.X (label), + Y = Pos.Bottom (label), + Width = Dim.Fill (), + SelectedItem = 8 + }; jumpList.SelectedItemChanged += (args) => { - _charMap.Start = radioItems[args.SelectedItem].start; + _charMap.Start = radioItems [args.SelectedItem].start; }; Win.Add (jumpList); + + jumpList.Refresh (); + jumpList.SetFocus (); } public override void Run () @@ -91,11 +99,14 @@ namespace UICatalog { } int _start = 0x2500; + public const int H_SPACE = 2; + public const int V_SPACE = 2; + public static int MaxCodePointVal => 0xE0FFF; // Row Header + space + (space + char + space) public static int RowHeaderWidth => $"U+{MaxCodePointVal:x5}".Length; - public static int RowWidth => RowHeaderWidth + (" c".Length * 16); + public static int RowWidth => RowHeaderWidth + (H_SPACE * 16); public CharMap () { @@ -109,10 +120,12 @@ namespace UICatalog { ShowHorizontalScrollIndicator = false; } }; +#if DRAW_CONTENT DrawContent += CharMap_DrawContent; +#endif } -#if true + private void CharMap_DrawContent (Rect viewport) { //Rune ReplaceNonPrintables (Rune c) @@ -125,10 +138,10 @@ namespace UICatalog { //} for (int header = 0; header < 16; header++) { - Move (viewport.X + RowHeaderWidth + (header * 2), 0); + Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0); Driver.AddStr ($" {header:x} "); } - for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y += 2) { + for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y+= V_SPACE) { int val = (-viewport.Y + row) * 16; if (val < MaxCodePointVal) { var rowLabel = $"U+{val / 16:x4}x"; @@ -137,17 +150,17 @@ namespace UICatalog { var prevColWasWide = false; for (int col = 0; col < 16; col++) { var rune = new Rune ((uint)((uint)(-viewport.Y + row) * 16 + col)); - Move (viewport.X + RowHeaderWidth + (col * 2) + (prevColWasWide ? 0 : 1), 0 + y + 1); + Move (viewport.X + RowHeaderWidth + (col * H_SPACE) + (prevColWasWide ? 0 : 1), y + 1); Driver.AddRune (rune); - //prevColWasWide = Rune.ColumnWidth(rune) > 1; + //prevColWasWide = Rune.ColumnWidth (rune) > 1; } } } } -#else +#if BASE_DRAW_CONTENT public override void OnDrawContent (Rect viewport) { - CharMap_DrawContent(this, viewport); + CharMap_DrawContent (viewport); base.OnDrawContent (viewport); } #endif