Merge pull request #1021 from BDisp/netdriver-unicode

Fixes #1020. NetDriver does not deal well with unicode characters yet.
This commit is contained in:
Charlie Kindel
2020-11-30 11:56:00 -08:00
committed by GitHub
11 changed files with 701 additions and 390 deletions

View File

@@ -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;

View File

@@ -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<MouseEvent> mouseHandler;
MainLoop mainLoop;
public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> 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;
});

View File

@@ -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;

View File

@@ -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<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> 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 {
/// <remarks>
/// This implementation is used for NetDriver.
/// </remarks>
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<ConsoleKeyInfo?> keyResult = new Queue<ConsoleKeyInfo?> ();
MainLoop mainLoop;
ConsoleDriver consoleDriver;
bool winChanged;
int newTop;
CancellationTokenSource tokenSource = new CancellationTokenSource ();
/// <summary>
@@ -439,6 +539,11 @@ namespace Terminal.Gui {
/// </summary>
public Action<ConsoleKeyInfo> KeyPressed;
/// <summary>
/// Invoked when the window is changed.
/// </summary>
public Action<int> WinChanged;
/// <summary>
/// Initializes the class with the console driver.
/// </summary>
@@ -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);
}
}
}

View File

@@ -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<InputRecord> (pRecord)};
: new [] { Marshal.PtrToStructure<InputRecord> (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<KeyEvent> keyHandler;
@@ -663,20 +578,51 @@ namespace Terminal.Gui {
Action<KeyEvent> keyUpHandler;
Action<MouseEvent> mouseHandler;
public WindowsDriver ()
{
winConsole = new WindowsConsole ();
}
bool winChanging;
public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> 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
}
/// <summary>
/// Mainloop intended to be used with the <see cref="WindowsDriver"/>, and can
/// only be used on Windows.
/// </summary>
/// <remarks>
/// This implementation is used for WindowsDriver.
/// </remarks>
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];
/// <summary>
/// Invoked when a Key is pressed or released.
/// </summary>
public Action<WindowsConsole.InputRecord> ProcessInput;
/// <summary>
/// Invoked when the window is changed.
/// </summary>
public Action<Size> 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);
}
}
}
}

View File

@@ -59,7 +59,7 @@ namespace Terminal.Gui {
/// The current <see cref="ConsoleDriver"/> in use.
/// </summary>
public static ConsoleDriver Driver;
/// <summary>
/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
/// </summary>
@@ -73,11 +73,32 @@ namespace Terminal.Gui {
public static Toplevel Current { get; private set; }
/// <summary>
/// TThe current <see cref="View"/> object being redrawn.
/// The current <see cref="View"/> object being redrawn.
/// </summary>
/// /// <value>The current.</value>
public static View CurrentView { get; set; }
/// <summary>
/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
/// </summary>
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 ();
}
}
}
/// <summary>
/// The <see cref="MainLoop"/> driver for the application
/// </summary>
@@ -167,7 +188,7 @@ namespace Terminal.Gui {
static void Init (Func<Toplevel> 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);

View File

@@ -546,6 +546,12 @@ namespace Terminal.Gui {
/// </summary>
public abstract int Top { get; }
/// <summary>
/// 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.
/// </summary>
public abstract bool HeightAsBuffer { get; set; }
/// <summary>
/// Initializes the driver
/// </summary>
@@ -570,12 +576,10 @@ namespace Terminal.Gui {
/// <returns></returns>
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;
}

View File

@@ -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;
}
}

View File

@@ -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 ();
}

View File

@@ -1415,7 +1415,7 @@ namespace Terminal.Gui {
CloseMenu ();
if (openedByAltKey) {
openedByAltKey = false;
LastFocused.SetFocus ();
LastFocused?.SetFocus ();
}
break;

View File

@@ -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