From 72a4104ee584e595488f9474a202a14049f8bf86 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 16 Dec 2020 23:35:24 +0000 Subject: [PATCH 01/70] Fixes #1041. NetDriver events should stay all together. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 383 ++++++++++++++++------- Terminal.Gui/Core/Application.cs | 18 ++ UICatalog/UICatalog.cs | 25 +- 3 files changed, 304 insertions(+), 122 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 385c7fc09a..bd6f5fbf48 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1,4 +1,5 @@ -// +//#define PROCESS_REQUEST +// // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient. // // Authors: @@ -107,24 +108,40 @@ public void Cleanup () internal class NetEvents { ManualResetEventSlim inputReady = new ManualResetEventSlim (false); ManualResetEventSlim waitForStart = new ManualResetEventSlim (false); + ManualResetEventSlim winChange = new ManualResetEventSlim (false); Queue inputResultQueue = new Queue (); - + ConsoleDriver consoleDriver; + int lastWindowHeight; + int largestWindowHeight; +#if PROCESS_REQUEST + bool neededProcessRequest; +#endif public int NumberOfCSI { get; } - public NetEvents (int numberOfCSI = 1) + public NetEvents (ConsoleDriver consoleDriver, int numberOfCSI = 1) { + if (consoleDriver == null) { + throw new ArgumentNullException ("Console driver instance must be provided."); + } + this.consoleDriver = consoleDriver; NumberOfCSI = numberOfCSI; Task.Run (ProcessInputResultQueue); + Task.Run (CheckWinChange); } public InputResult? ReadConsoleInput () { while (true) { waitForStart.Set (); + winChange.Set (); + if (inputResultQueue.Count == 0) { inputReady.Wait (); inputReady.Reset (); } +#if PROCESS_REQUEST + neededProcessRequest = false; +#endif if (inputResultQueue.Count > 0) { return inputResultQueue.Dequeue (); } @@ -145,6 +162,65 @@ void ProcessInputResultQueue () } } + void CheckWinChange () + { + while (true) { + winChange.Wait (); + winChange.Reset (); + WaitWinChange (); + inputReady.Set (); + } + } + + void WaitWinChange () + { + while (true) { + if (!consoleDriver.HeightAsBuffer) { + if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) { + GetWindowSizeEvent (new Size (Console.WindowWidth, Console.WindowHeight)); + return; + } + } else { + largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight); + if (Console.BufferWidth != consoleDriver.Cols || largestWindowHeight != consoleDriver.Rows + || Console.WindowHeight != lastWindowHeight) { + lastWindowHeight = Console.WindowHeight; + GetWindowSizeEvent (new Size (Console.BufferWidth, lastWindowHeight)); + return; + } + if (Console.WindowTop != consoleDriver.Top) { + // Top only working on Windows. + var winPositionEv = new WindowPositionEvent () { + Top = Console.WindowTop + }; + inputResultQueue.Enqueue (new InputResult () { + EventType = EventType.WindowPosition, + WindowPositionEvent = winPositionEv + }); + return; + } +#if PROCESS_REQUEST + if (!neededProcessRequest) { + Console.Out.Write ("\x1b[6n"); + neededProcessRequest = true; + } +#endif + } + } + } + + void GetWindowSizeEvent (Size size) + { + WindowSizeEvent windowSizeEvent = new WindowSizeEvent () { + Size = size + }; + + inputResultQueue.Enqueue (new InputResult () { + EventType = EventType.WindowSize, + WindowSizeEvent = windowSizeEvent + }); + } + void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo) { InputResult inputResult = new InputResult { @@ -174,6 +250,7 @@ void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo) } break; case 27: + case 91: ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { consoleKeyInfo }; ConsoleModifiers mod = consoleKeyInfo.Modifiers; while (Console.KeyAvailable) { @@ -207,30 +284,67 @@ void SplitCSI (ConsoleKeyInfo [] cki, ref InputResult inputResult, ref ConsoleKe ConsoleKeyInfo [] splitedCki = new ConsoleKeyInfo [] { }; int length = 0; var kChar = GetKeyCharArray (cki); - var nCSI = kChar.Where (val => val == '\x1b').ToArray ().Length; + var nCSI = GetNumberOfCSI (kChar); int curCSI = 0; + char previousKChar = '\0'; if (nCSI > 1) { foreach (var ck in cki) { if (NumberOfCSI > 0 && nCSI - curCSI > NumberOfCSI) { - if (ck.KeyChar == '\x1b') { + if (ck.KeyChar == '\x1b' + || (ck.KeyChar == '[' && previousKChar != '\x1b')) { curCSI++; } + previousKChar = ck.KeyChar; continue; } - if (ck.KeyChar == '\x1b') { - if (splitedCki.Length > 0) { + if (ck.KeyChar == '\x1b' || (ck.KeyChar == '[' && previousKChar != '\x1b')) { + if (ck.KeyChar == 'R') { + ResizeArray (ck); + } + if (splitedCki.Length > 1) { DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, splitedCki, ref mod); } splitedCki = new ConsoleKeyInfo [] { }; length = 0; } - length++; - Array.Resize (ref splitedCki, length); - splitedCki [length - 1] = ck; + ResizeArray (ck); + previousKChar = ck.KeyChar; } } else { DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, cki, ref mod); } + + void ResizeArray (ConsoleKeyInfo ck) + { + length++; + Array.Resize (ref splitedCki, length); + splitedCki [length - 1] = ck; + } + } + + char [] GetKeyCharArray (ConsoleKeyInfo [] cki) + { + char [] kChar = new char [] { }; + var length = 0; + foreach (var kc in cki) { + length++; + Array.Resize (ref kChar, length); + kChar [length - 1] = kc.KeyChar; + } + + return kChar; + } + + int GetNumberOfCSI (char [] csi) + { + int nCSI = 0; + for (int i = 0; i < csi.Length; i++) { + if (csi [i] == '\x1b' || (csi [i] == '[' && (i == 0 || (i > 0 && csi [i - 1] != '\x1b')))) { + nCSI++; + } + } + + return nCSI; } void DecodeCSI (ref InputResult inputResult, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ref MouseEvent mouseEvent, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod) @@ -306,7 +420,8 @@ void DecodeCSI (ref InputResult inputResult, ref ConsoleKeyInfo newConsoleKeyInf (mod & ConsoleModifiers.Control) != 0); break; case 7: - throw new NotImplementedException ("Condition not yet detected!"); + GetRequestEvent (GetKeyCharArray (cki)); + return; case int n when n >= 8: GetMouseEvent (cki); return; @@ -320,6 +435,56 @@ void DecodeCSI (ref InputResult inputResult, ref ConsoleKeyInfo newConsoleKeyInf inputResultQueue.Enqueue (inputResult); } + Point lastCursorPosition; + + void GetRequestEvent (char [] kChar) + { + EventType eventType = new EventType (); + Point point = new Point (); + int foundPoint = 0; + string value = ""; + for (int i = 0; i < kChar.Length; i++) { + var c = kChar [i]; + if (c == '[') { + foundPoint++; + } else if (foundPoint == 1 && c != ';') { + value += c.ToString (); + } else if (c == ';') { + if (foundPoint == 1) { + point.Y = int.Parse (value) - 1; + } + value = ""; + foundPoint++; + } else if (foundPoint > 0 && i < kChar.Length - 1) { + value += c.ToString (); + } else if (i == kChar.Length - 1) { + point.X = int.Parse (value) + Console.WindowTop - 1; + + switch (c) { + case 'R': + if (lastCursorPosition.Y != point.Y) { + lastCursorPosition = point; + eventType = EventType.WindowPosition; + var winPositionEv = new WindowPositionEvent () { + CursorPosition = point + }; + inputResultQueue.Enqueue (new InputResult () { + EventType = eventType, + WindowPositionEvent = winPositionEv + }); + } else { + return; + } + break; + default: + throw new NotImplementedException (); + } + } + } + + inputReady.Set (); + } + MouseEvent lastMouseEvent; bool isButtonPressed; bool isButtonClicked; @@ -369,6 +534,7 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 8: case 16: case 32: + case 40: buttonState = c == 'M' ? MouseButtonState.Button1Pressed : MouseButtonState.Button1Released; break; @@ -376,6 +542,7 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 9: case 17: case 33: + case 41: buttonState = c == 'M' ? MouseButtonState.Button2Pressed : MouseButtonState.Button2Released; break; @@ -383,6 +550,7 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 10: case 18: case 34: + case 42: buttonState = c == 'M' ? MouseButtonState.Button3Pressed : MouseButtonState.Button3Released; break; @@ -585,7 +753,7 @@ async Task ProcessContinuousButtonPressedAsync () await Task.Delay (200); var view = Application.wantContinuousButtonPressedView; if (isButtonPressed && !Console.KeyAvailable - && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked + && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked && (view != null || view == null && lastMouseEvent.Position != point)) { point = lastMouseEvent.Position; inputResultQueue.Enqueue (new InputResult () { @@ -601,19 +769,6 @@ async Task ProcessContinuousButtonPressedAsync () isButtonPressed = false; } - char [] GetKeyCharArray (ConsoleKeyInfo [] cki) - { - char [] kChar = new char [] { }; - var length = 0; - foreach (var kc in cki) { - length++; - Array.Resize (ref kChar, length); - kChar [length - 1] = kc.KeyChar; - } - - return kChar; - } - ConsoleModifiers GetConsoleModifiers (uint keyChar) { switch (keyChar) { @@ -719,7 +874,9 @@ ConsoleKey GetConsoleKey (char keyChar, ref ConsoleModifiers mod, int length) public enum EventType { key = 1, - Mouse = 2 + Mouse = 2, + WindowSize = 3, + WindowPosition = 4 } [Flags] @@ -760,10 +917,21 @@ public struct MouseEvent { public MouseButtonState ButtonState; } + public struct WindowSizeEvent { + public Size Size; + } + + public struct WindowPositionEvent { + public int Top; + public Point CursorPosition; + } + public struct InputResult { public EventType EventType; public ConsoleKeyInfo ConsoleKeyInfo; public MouseEvent MouseEvent; + public WindowSizeEvent WindowSizeEvent; + public WindowPositionEvent WindowPositionEvent; } } @@ -775,16 +943,19 @@ internal class NetDriver : ConsoleDriver { public override bool HeightAsBuffer { get; set; } public NetWinVTConsole NetWinConsole { get; } + public bool IsWinPlatform { get; } + public bool AlwaysSetPosition { get; set; } - bool isWinPlatform; + int largestWindowHeight; public NetDriver () { var p = Environment.OSVersion.Platform; if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { - isWinPlatform = true; + IsWinPlatform = true; NetWinConsole = new NetWinVTConsole (); } + largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight); } // The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag @@ -848,7 +1019,7 @@ public override void AddStr (ustring str) public override void End () { - if (isWinPlatform) { + if (IsWinPlatform) { NetWinConsole.Cleanup (); } @@ -934,7 +1105,7 @@ void ResizeScreen () // Can raise an exception while is still resizing. try { // Not supported on Unix. - if (isWinPlatform) { + if (IsWinPlatform) { #pragma warning disable CA1416 Console.CursorTop = 0; Console.CursorLeft = 0; @@ -954,7 +1125,7 @@ void ResizeScreen () } } } else { - if (isWinPlatform && Console.WindowHeight > 0) { + if (IsWinPlatform && Console.WindowHeight > 0) { // Can raise an exception while is still resizing. try { #pragma warning disable CA1416 @@ -1012,11 +1183,16 @@ void SetColor (int color) } } + public override void Refresh () + { + UpdateScreen (); + } + public override void UpdateScreen () { if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3 || (!HeightAsBuffer && Rows != Console.WindowHeight) - || (HeightAsBuffer && Rows != Console.BufferHeight)) { + || (HeightAsBuffer && Rows != largestWindowHeight)) { return; } @@ -1024,6 +1200,7 @@ public override void UpdateScreen () int rows = Math.Min (Console.WindowHeight + top, Rows); int cols = Cols; + Console.CursorVisible = false; for (int row = top; row < rows; row++) { if (!dirtyLine [row]) { continue; @@ -1033,31 +1210,35 @@ public override void UpdateScreen () if (contents [row, col, 2] != 1) { continue; } - if (Console.WindowHeight > 0) { - // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth. - try { - Console.SetCursorPosition (col, row); - } catch (Exception) { - return; - } + if (Console.WindowHeight > 0 && !SetCursorPosition (col, row)) { + return; } for (; col < cols && contents [row, col, 2] == 1; col++) { var color = contents [row, col, 1]; if (color != redrawColor) { SetColor (color); } + if (AlwaysSetPosition && !SetCursorPosition (col, row)) { + return; + } Console.Write ((char)contents [row, col, 0]); contents [row, col, 2] = 0; } } } - + Console.CursorVisible = true; UpdateCursor (); } - public override void Refresh () + bool SetCursorPosition (int col, int row) { - UpdateScreen (); + // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth. + try { + Console.SetCursorPosition (col, row); + return true; + } catch (Exception) { + return false; + } } public override void UpdateCursor () @@ -1230,15 +1411,42 @@ public override void PrepareToRun (MainLoop mainLoop, Action keyHandle var mLoop = mainLoop.Driver as NetMainLoop; - // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - mLoop.KeyPressed = (e) => ProcessInput (e); + // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called. + mLoop.ProcessInput = (e) => ProcessInput (e); + } - mLoop.WinChanged = (e) => ChangeWin (e); + void ProcessInput (NetEvents.InputResult inputEvent) + { + switch (inputEvent.EventType) { + case NetEvents.EventType.key: + var map = MapKey (inputEvent.ConsoleKeyInfo); + if (map == (Key)0xffffffff) { + return; + } + keyDownHandler (new KeyEvent (map, keyModifiers)); + keyHandler (new KeyEvent (map, keyModifiers)); + keyUpHandler (new KeyEvent (map, keyModifiers)); + keyModifiers = new KeyModifiers (); + break; + case NetEvents.EventType.Mouse: + mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); + break; + case NetEvents.EventType.WindowSize: + ChangeWin (); + break; + case NetEvents.EventType.WindowPosition: + var newTop = inputEvent.WindowPositionEvent.Top; + if (HeightAsBuffer && top != newTop) { + top = newTop; + Refresh (); + } + break; + } } bool winChanging; - void ChangeWin (int newTop) + void ChangeWin () { winChanging = true; const int Min_WindowWidth = 14; @@ -1248,8 +1456,8 @@ void ChangeWin (int newTop) Console.WindowHeight); top = 0; } else { - size = new Size (Console.BufferWidth, Console.BufferHeight); - top = newTop; + largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight); + size = new Size (Console.BufferWidth, largestWindowHeight); } cols = size.Width; rows = size.Height; @@ -1260,25 +1468,6 @@ void ChangeWin (int newTop) } } - void ProcessInput (NetEvents.InputResult inputEvent) - { - switch (inputEvent.EventType) { - case NetEvents.EventType.key: - var map = MapKey (inputEvent.ConsoleKeyInfo); - if (map == (Key)0xffffffff) { - return; - } - keyDownHandler (new KeyEvent (map, keyModifiers)); - keyHandler (new KeyEvent (map, keyModifiers)); - keyUpHandler (new KeyEvent (map, keyModifiers)); - keyModifiers = new KeyModifiers (); - break; - case NetEvents.EventType.Mouse: - mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); - break; - } - } - MouseEvent ToDriverMouse (NetEvents.MouseEvent me) { MouseFlags mouseFlag = 0; @@ -1416,24 +1605,15 @@ public override void UncookMouse () internal class NetMainLoop : IMainLoopDriver { ManualResetEventSlim keyReady = new ManualResetEventSlim (false); ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); - ManualResetEventSlim winChange = new ManualResetEventSlim (false); Queue inputResult = new Queue (); MainLoop mainLoop; - ConsoleDriver consoleDriver; - bool winChanged; - int newTop; CancellationTokenSource tokenSource = new CancellationTokenSource (); NetEvents netEvents; /// /// Invoked when a Key is pressed. /// - public Action KeyPressed; - - /// - /// Invoked when the window is changed. - /// - public Action WinChanged; + public Action ProcessInput; /// /// Initializes the class with the console driver. @@ -1447,11 +1627,10 @@ public NetMainLoop (ConsoleDriver consoleDriver = null) if (consoleDriver == null) { throw new ArgumentNullException ("Console driver instance must be provided."); } - this.consoleDriver = consoleDriver; - netEvents = new NetEvents (); + netEvents = new NetEvents (consoleDriver); } - void KeyReader () + void NetInputHandler () { while (true) { waitForProbe.Wait (); @@ -1470,43 +1649,10 @@ void KeyReader () } } - void CheckWinChange () - { - while (true) { - winChange.Wait (); - winChange.Reset (); - WaitWinChange (); - winChanged = true; - keyReady.Set (); - } - } - - int lastWindowHeight; - void WaitWinChange () - { - while (true) { - 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; - } - } - } - } - void IMainLoopDriver.Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; - Task.Run (KeyReader); - Task.Run (CheckWinChange); + Task.Run (NetInputHandler); } void IMainLoopDriver.Wakeup () @@ -1517,7 +1663,6 @@ void IMainLoopDriver.Wakeup () bool IMainLoopDriver.EventsPending (bool wait) { waitForProbe.Set (); - winChange.Set (); if (CheckTimers (wait, out var waitTimeout)) { return true; @@ -1534,7 +1679,7 @@ bool IMainLoopDriver.EventsPending (bool wait) } if (!tokenSource.IsCancellationRequested) { - return inputResult.Count > 0 || CheckTimers (wait, out _) || winChanged; + return inputResult.Count > 0 || CheckTimers (wait, out _); } tokenSource.Dispose (); @@ -1568,11 +1713,7 @@ bool CheckTimers (bool wait, out int waitTimeout) void IMainLoopDriver.MainIteration () { if (inputResult.Count > 0) { - KeyPressed?.Invoke (inputResult.Dequeue ().Value); - } - if (winChanged) { - winChanged = false; - WinChanged?.Invoke (newTop); + ProcessInput?.Invoke (inputResult.Dequeue ().Value); } } } diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index e476e5e56a..2299ca6fa6 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -99,6 +99,24 @@ public static bool HeightAsBuffer { } } + /// + /// Used only by to forcing always setting the cursor position when writing to the screen. + /// + public static bool AlwaysSetPosition { + get { + if (Driver is NetDriver) { + return (Driver as NetDriver).AlwaysSetPosition; + } + return false; + } + set { + if (Driver is NetDriver) { + (Driver as NetDriver).AlwaysSetPosition = value; + Driver.Refresh (); + } + } + } + /// /// The driver for the application /// diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index e8577d3587..d71088b653 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -63,6 +63,8 @@ public class UICatalogApp { private static Scenario _runningScenario = null; private static bool _useSystemConsole = false; private static ConsoleDriver.DiagnosticFlags _diagnosticFlags; + private static bool _heightAsBuffer; + private static bool _alwaysSetPosition; static void Main (string [] args) { @@ -144,6 +146,8 @@ private static Scenario GetScenarioToRun () { Application.UseSystemConsole = _useSystemConsole; Application.Init (); + Application.HeightAsBuffer = _heightAsBuffer; + Application.AlwaysSetPosition = _alwaysSetPosition; // Set this here because not initialized until driver is loaded _baseColorScheme = Colors.Base; @@ -280,9 +284,27 @@ static List CreateDiagnosticMenuItems () menuItems.Add (CreateDiagnosticFlagsMenuItems ()); menuItems.Add (new MenuItem [] { null }); menuItems.Add (CreateSizeStyle ()); + menuItems.Add (CreateAlwaysSetPosition ()); return menuItems; } + static MenuItem [] CreateAlwaysSetPosition () + { + List menuItems = new List (); + var item = new MenuItem (); + item.Title = "_Always set position (NetDriver only)"; + item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0]; + item.CheckType |= MenuItemCheckStyle.Checked; + item.Checked = Application.AlwaysSetPosition; + item.Action += () => { + Application.AlwaysSetPosition = !item.Checked; + item.Checked = _alwaysSetPosition = Application.AlwaysSetPosition; + }; + menuItems.Add (item); + + return menuItems.ToArray (); + } + static MenuItem [] CreateSizeStyle () { List menuItems = new List (); @@ -293,7 +315,8 @@ static MenuItem [] CreateSizeStyle () item.Checked = Application.HeightAsBuffer; item.Action += () => { item.Checked = !item.Checked; - Application.HeightAsBuffer = item.Checked; + _heightAsBuffer = item.Checked; + Application.HeightAsBuffer = _heightAsBuffer; }; menuItems.Add (item); From 39c285686181883fb93014eedd44a52d69a9ef59 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 17 Dec 2020 12:29:41 +0000 Subject: [PATCH 02/70] Also clear screen in Unix and typo. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2 ++ Terminal.Gui/Core/Application.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index bd6f5fbf48..222d19d5ee 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1032,6 +1032,8 @@ void Clear () { if (Rows > 0) { Console.Clear (); + Console.Out.Write ("\x1b[3J"); + //Console.Out.Write ("\x1b[?25l"); } } diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 2299ca6fa6..176d1db3fb 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -100,7 +100,7 @@ public static bool HeightAsBuffer { } /// - /// Used only by to forcing always setting the cursor position when writing to the screen. + /// Used only by to forcing always moving the cursor position when writing to the screen. /// public static bool AlwaysSetPosition { get { From a4b19b05b8e8b263f61b30171ab440696d886f05 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 17 Dec 2020 14:20:36 +0000 Subject: [PATCH 03/70] Prevents negative values if the windows size change so quickly. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 222d19d5ee..a4608acd94 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -177,7 +177,9 @@ void WaitWinChange () while (true) { if (!consoleDriver.HeightAsBuffer) { if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) { - GetWindowSizeEvent (new Size (Console.WindowWidth, Console.WindowHeight)); + var w = Math.Max (Console.WindowWidth, 0); + var h = Math.Max (Console.WindowHeight, 0); + GetWindowSizeEvent (new Size (w, h)); return; } } else { From 8725c3f4612acc59a9e0dc37da22dcad55b3be73 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 17 Dec 2020 20:09:53 +0000 Subject: [PATCH 04/70] Fixes #1043. The menu separator is being printed in the wrong place. --- Terminal.Gui/Views/Menu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 3c8f6f5f37..f3a7f49549 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -453,7 +453,7 @@ public override void Redraw (Rect bounds) Driver.AddRune (' '); if (item == null) { - Move (Frame.Right - 1, i + 1); + Move (Frame.Width - 1, i + 1); Driver.AddRune (Driver.RightTee); continue; } From 045090999141b8ca6b0e40a5938932b798ab022e Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 22 Dec 2020 00:32:54 +0000 Subject: [PATCH 05/70] Fixes #93. Audit TextView just like we did TextField to ensure proper treatment of Unicode --- Terminal.Gui/Views/TextField.cs | 4 ++-- Terminal.Gui/Views/TextView.cs | 26 +++++++++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 2c77a87d44..9b902746c5 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -241,7 +241,7 @@ public override void Redraw (Rect bounds) PositionCursor (); } - static int SetCol (int col, int width, int cols) + internal static int SetCol (int col, int width, int cols) { if (col + cols <= width) { col += cols; @@ -805,7 +805,7 @@ int PositionCursor (int x, bool getX = true) return point; } - int GetPointFromX (List t, int start, int x) + internal static int GetPointFromX (List t, int start, int x) { if (x < 0) { return x; diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 76429274c1..3b9f2c4fab 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -421,11 +421,18 @@ public override void PositionCursor () } var line = model.GetLine (currentRow); var retreat = 0; + var col = 0; if (line.Count > 0) { retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)]) ? 1 : 0), 0); + for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) { + if (idx == CurrentColumn) + break; + var cols = Rune.ColumnWidth (line [idx]); + col += cols - 1; + } } - Move (CurrentColumn - leftColumn - retreat, CurrentRow - topRow); + Move (CurrentColumn - leftColumn - retreat + col, CurrentRow - topRow); } void ClearRegion (int left, int top, int right, int bottom) @@ -569,10 +576,12 @@ public override void Redraw (Rect bounds) } Move (bounds.Left, row); - for (int col = bounds.Left; col < right; col++) { - var lineCol = leftColumn + col; + var col = 0; + for (int idx = bounds.Left; idx < right; idx++) { + var lineCol = leftColumn + idx; var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol]; - if (selecting && PointInSelection (col, row)) { + var cols = Rune.ColumnWidth (rune); + if (selecting && PointInSelection (idx, row)) { ColorSelection (); } else { ColorNormal (); @@ -581,6 +590,7 @@ public override void Redraw (Rect bounds) if (!SpecialRune (rune)) { AddRune (col, row, rune); } + col = TextField.SetCol (col, bounds.Right, cols); } } PositionCursor (); @@ -1265,10 +1275,12 @@ public override bool MouseEvent (MouseEvent ev) currentRow = ev.Y + topRow; } var r = GetCurrentLine (); - if (ev.X - leftColumn >= r.Count) + var idx = TextField.GetPointFromX (r, leftColumn, ev.X); + if (idx - leftColumn >= r.Count) { currentColumn = r.Count - leftColumn; - else - currentColumn = ev.X - leftColumn; + } else { + currentColumn = idx - leftColumn; + } } PositionCursor (); } else if (ev.Flags == MouseFlags.WheeledDown) { From fe311ea54bcab83438f1c3dd3998f30d3ca34f51 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 22 Dec 2020 12:02:07 +0000 Subject: [PATCH 06/70] Moving common methods to the TextModel class. --- Terminal.Gui/Views/TextField.cs | 34 ++++--------------------------- Terminal.Gui/Views/TextView.cs | 36 +++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 9b902746c5..275eac4755 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -195,7 +195,7 @@ public override void PositionCursor () if (idx == point) break; var cols = Rune.ColumnWidth (text [idx]); - col = SetCol (col, Frame.Width - 1, cols); + col = TextModel.SetCol (col, Frame.Width - 1, cols); } Move (col, 0); } @@ -227,7 +227,7 @@ public override void Redraw (Rect bounds) if (col + cols <= width) { Driver.AddRune ((Rune)(Secret ? '*' : rune)); } - col = SetCol (col, width, cols); + col = TextModel.SetCol (col, width, cols); if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) { break; } @@ -241,15 +241,6 @@ public override void Redraw (Rect bounds) PositionCursor (); } - internal static int SetCol (int col, int width, int cols) - { - if (col + cols <= width) { - col += cols; - } - - return col; - } - // Returns the size and length in a range of the string. (int size, int length) DisplaySize (List t, int start = -1, int end = -1, bool checkNextRune = true) { @@ -779,7 +770,7 @@ int PositionCursor (MouseEvent ev) { // We could also set the cursor position. int x; - var pX = GetPointFromX (text, first, ev.X); + var pX = TextModel.GetColFromX (text, first, ev.X); if (text.Count == 0) { x = pX - ev.OfX; } else { @@ -792,7 +783,7 @@ int PositionCursor (int x, bool getX = true) { int pX = x; if (getX) { - pX = GetPointFromX (text, first, x); + pX = TextModel.GetColFromX (text, first, x); } if (first + pX > text.Count) { point = text.Count; @@ -805,23 +796,6 @@ int PositionCursor (int x, bool getX = true) return point; } - internal static int GetPointFromX (List t, int start, int x) - { - if (x < 0) { - return x; - } - int size = start; - var pX = x + start; - for (int i = start; i < t.Count; i++) { - var r = t [i]; - size += Rune.ColumnWidth (r); - if (i == pX || (size > pX)) { - return i - start; - } - } - return t.Count - start; - } - void PrepareSelection (int x, int direction = 0) { x = x + first < 0 ? 0 : x; diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 3b9f2c4fab..dc420b972f 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -171,6 +171,34 @@ public void RemoveLine (int pos) { lines.RemoveAt (pos); } + + internal static int SetCol (int col, int width, int cols) + { + if (col + cols <= width) { + col += cols; + } + + return col; + } + + internal static int GetColFromX (List t, int start, int x) + { + if (x < 0) { + return x; + } + int size = start; + var pX = x + start; + for (int i = start; i < t.Count; i++) { + var r = t [i]; + size += Rune.ColumnWidth (r); + if (i == pX || (size > pX)) { + return i - start; + } + } + return t.Count - start; + } + + } /// @@ -590,7 +618,7 @@ public override void Redraw (Rect bounds) if (!SpecialRune (rune)) { AddRune (col, row, rune); } - col = TextField.SetCol (col, bounds.Right, cols); + col = TextModel.SetCol (col, bounds.Right, cols); } } PositionCursor (); @@ -658,7 +686,7 @@ void InsertText (ustring text) var line = GetCurrentLine (); - // Optmize single line + // Optimize single line if (lines.Count == 1) { line.InsertRange (currentColumn, lines [0]); currentColumn += lines [0].Count; @@ -683,7 +711,7 @@ void InsertText (ustring text) var lastp = last.Count; last.InsertRange (last.Count, rest); - // Now adjjust column and row positions + // Now adjust column and row positions currentRow += lines.Count - 1; currentColumn = lastp; if (currentRow - topRow > Frame.Height) { @@ -1275,7 +1303,7 @@ public override bool MouseEvent (MouseEvent ev) currentRow = ev.Y + topRow; } var r = GetCurrentLine (); - var idx = TextField.GetPointFromX (r, leftColumn, ev.X); + var idx = TextModel.GetColFromX (r, leftColumn, ev.X); if (idx - leftColumn >= r.Count) { currentColumn = r.Count - leftColumn; } else { From e102a34897b4aad403c5ca1a5c6b58cd4b4d160c Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 24 Dec 2020 01:19:34 +0000 Subject: [PATCH 07/70] Fixes #1050. ScrollView take to long to scroll enormous content size. --- Terminal.Gui/Views/ScrollView.cs | 45 +++++++++++++++++++++++++---- UICatalog/Scenarios/CharacterMap.cs | 2 ++ UICatalog/Scenarios/Scrolling.cs | 1 + 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 2e507dc865..3a53bc5d45 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -277,6 +277,8 @@ public override void Redraw (Rect region) } } + int lastLocation = -1; + /// public override bool MouseEvent (MouseEvent me) { @@ -285,6 +287,10 @@ public override bool MouseEvent (MouseEvent me) return false; } + if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + lastLocation = -1; + } + int location = vertical ? me.Y : me.X; int barsize = vertical ? Bounds.Height : Bounds.Width; int posTopLeftTee = vertical ? posTopTee : posLeftTee; @@ -307,13 +313,40 @@ public override bool MouseEvent (MouseEvent me) b1 = Math.Max (b1 - 1, 0); } - if (location > b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) { - Host.CanScroll (location, out int nv, vertical); - if (nv > 0) { - SetPosition (Math.Min (pos + nv, Size)); + if (location > b1 && location <= b2 + 1) { + if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { + if (location == 1) { + SetPosition (0); + } else if (location == barsize) { + Host.CanScroll (Size - pos, out int nv, vertical); + if (nv > 0) { + SetPosition (Math.Min (pos + nv, Size)); + } + } + } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + var mb = (b2 - b1) / 2; + var ml = mb + b1 + (mb == 0 ? 1 : 0); + if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) { + lastLocation = location; + var np = b1 * Size / barsize; + SetPosition (np); + } else if (location > lastLocation) { + var np = location * Size / barsize; + Host.CanScroll (np - pos, out int nv, vertical); + if (nv > 0) { + SetPosition (pos + nv); + } + } + } + } else { + if (location >= b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) { + Host.CanScroll (location, out int nv, vertical); + if (nv > 0) { + SetPosition (Math.Min (pos + nv, Size)); + } + } else if (location <= b1) { + SetPosition (Math.Max (pos - barsize - location, 0)); } - } else if (location <= b1) { - SetPosition (Math.Max (pos - barsize - location, 0)); } } diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 923a61f60c..10736add22 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -137,6 +137,8 @@ private void CharMap_DrawContent (Rect viewport) // } //} + ContentSize = new Size (CharMap.RowWidth, MaxCodePointVal / 16 + Frame.Height - 1); + for (int header = 0; header < 16; header++) { Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0); Driver.AddStr ($" {header:x} "); diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index 48b51584f6..119ea77fee 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -129,6 +129,7 @@ public override void Setup () X = 0, Y = 0, Width = Dim.Fill (1), // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does. + Height = 1, ColorScheme = Colors.Error }; scrollView.Add (horizontalRuler); From 9c4b3e0c6bff503b6d1b5426bde1786f1a3f57d8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 24 Dec 2020 22:21:47 +0000 Subject: [PATCH 08/70] Fixing some keys and mouse navigation. Moving a few more methods from the TextField to the TextModel class. --- Terminal.Gui/Views/TextField.cs | 68 +++----------- Terminal.Gui/Views/TextView.cs | 152 ++++++++++++++++++-------------- 2 files changed, 97 insertions(+), 123 deletions(-) diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 275eac4755..613fc33928 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -157,7 +157,7 @@ public override Rect Frame { TextChanged?.Invoke (oldText); if (point > text.Count) { - point = Math.Max (DisplaySize (text, 0).size - 1, 0); + point = Math.Max (TextModel.DisplaySize (text, 0).size - 1, 0); } Adjust (); @@ -241,36 +241,15 @@ public override void Redraw (Rect bounds) PositionCursor (); } - // Returns the size and length in a range of the string. - (int size, int length) DisplaySize (List t, int start = -1, int end = -1, bool checkNextRune = true) - { - if (t == null || t.Count == 0) { - return (0, 0); - } - int size = 0; - int len = 0; - int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end; - int i = start == -1 ? 0 : start; - for (; i < tcount; i++) { - var rune = t [i]; - size += Rune.ColumnWidth (rune); - len += Rune.RuneLen (rune); - if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) { - size += Rune.ColumnWidth (t [i + 1]); - len += Rune.RuneLen (t [i + 1]); - } - } - return (size, len); - } - void Adjust () { int offB = OffSetBackground (); if (point < first) { first = point; } else if (first + point - (Frame.Width + offB) == 0 || - DisplaySize (text, first, point).size >= Frame.Width + offB) { - first = Math.Max (CalculateFirst (text, first, point, Frame.Width - 1 + offB), 0); + TextModel.DisplaySize (text, first, point).size >= Frame.Width + offB) { + first = Math.Max (TextModel.CalculateLeftColumn (text, first, + point, Frame.Width - 1 + offB, point), 0); } SetNeedsDisplay (); } @@ -285,33 +264,6 @@ int OffSetBackground () return offB; } - int CalculateFirst (List t, int start, int end, int width) - { - if (t == null) { - return 0; - } - (var dSize, _) = DisplaySize (t, start, end); - if (dSize < width) { - return start; - } - int size = 0; - int tcount = end > t.Count - 1 ? t.Count - 1 : end; - int col = 0; - for (int i = tcount; i > start; i--) { - var rune = t [i]; - var s = Rune.ColumnWidth (rune); - size += s; - if (size >= dSize - width) { - col = tcount - i + start; - if (start == 0 || col == start || (point == t.Count && (point - col > width))) { - col++; - } - break; - } - } - return col; - } - void SetText (List newText) { Text = ustring.Make (newText); @@ -871,9 +823,9 @@ void DeleteSelectedText () ustring actualText = Text; int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart; int selLength = Math.Abs (SelectedLength); - (var _, var len) = DisplaySize (text, 0, selStart, false); - (var _, var len2) = DisplaySize (text, selStart, selStart + selLength, false); - (var _, var len3) = DisplaySize (text, selStart + selLength, actualText.RuneCount, false); + (var _, var len) = TextModel.DisplaySize (text, 0, selStart, false); + (var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + selLength, false); + (var _, var len3) = TextModel.DisplaySize (text, selStart + selLength, actualText.RuneCount, false); Text = actualText[0, len] + actualText[len + len2, len + len2 + len3]; ClearAllSelection (); @@ -893,9 +845,9 @@ public virtual void Paste () SetSelectedStartSelectedLength (); int selStart = start == -1 ? CursorPosition : start; ustring actualText = Text; - (int _, int len) = DisplaySize (text, 0, selStart, false); - (var _, var len2) = DisplaySize (text, selStart, selStart + length, false); - (var _, var len3) = DisplaySize (text, selStart + length, actualText.RuneCount, false); + (int _, int len) = TextModel.DisplaySize (text, 0, selStart, false); + (var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + length, false); + (var _, var len3) = TextModel.DisplaySize (text, selStart + length, actualText.RuneCount, false); ustring cbTxt = Clipboard.Contents ?? ""; Text = actualText [0, len] + cbTxt + diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index dc420b972f..88b0002841 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -198,7 +198,55 @@ internal static int GetColFromX (List t, int start, int x) return t.Count - start; } + // Returns the size and length in a range of the string. + internal static (int size, int length) DisplaySize (List t, int start = -1, int end = -1, bool checkNextRune = true) + { + if (t == null || t.Count == 0) { + return (0, 0); + } + int size = 0; + int len = 0; + int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end; + int i = start == -1 ? 0 : start; + for (; i < tcount; i++) { + var rune = t [i]; + size += Rune.ColumnWidth (rune); + len += Rune.RuneLen (rune); + if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) { + size += Rune.ColumnWidth (t [i + 1]); + len += Rune.RuneLen (t [i + 1]); + } + } + return (size, len); + } + // Returns the left column in a range of the string. + internal static int CalculateLeftColumn (List t, int start, int end, int width, int currentColumn) + { + if (t == null) { + return 0; + } + (var dSize, _) = TextModel.DisplaySize (t, start, end); + if (dSize < width) { + return start; + } + int size = 0; + int tcount = end > t.Count - 1 ? t.Count - 1 : end; + int col = 0; + for (int i = tcount; i > start; i--) { + var rune = t [i]; + var s = Rune.ColumnWidth (rune); + size += s; + if (size >= dSize - width) { + col = tcount - i + start; + if (start == 0 || col == start || (currentColumn == t.Count && (currentColumn - col > width))) { + col++; + } + break; + } + } + return col; + } } /// @@ -679,10 +727,15 @@ ustring StringFromRunes (List runes) void InsertText (ustring text) { + if (ustring.IsNullOrEmpty (text)) { + return; + } + var lines = TextModel.StringToRunes (text); - if (lines.Count == 0) + if (lines.Count == 0) { return; + } var line = GetCurrentLine (); @@ -690,8 +743,9 @@ void InsertText (ustring text) if (lines.Count == 1) { line.InsertRange (currentColumn, lines [0]); currentColumn += lines [0].Count; - if (currentColumn - leftColumn > Frame.Width) + if (currentColumn - leftColumn > Frame.Width) { leftColumn = currentColumn - Frame.Width + 1; + } SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1)); return; } @@ -704,8 +758,9 @@ void InsertText (ustring text) // First line is inserted at the current location, the rest is appended line.InsertRange (currentColumn, lines [0]); - for (int i = 1; i < lines.Count; i++) + for (int i = 1; i < lines.Count; i++) { model.AddLine (currentRow + i, lines [i]); + } var last = model.GetLine (currentRow + lines.Count - 1); var lastp = last.Count; @@ -714,16 +769,7 @@ void InsertText (ustring text) // Now adjust column and row positions currentRow += lines.Count - 1; currentColumn = lastp; - if (currentRow - topRow > Frame.Height) { - topRow = currentRow - Frame.Height + 1; - if (topRow < 0) - topRow = 0; - } - if (currentColumn < leftColumn) - leftColumn = currentColumn; - if (currentColumn - leftColumn >= Frame.Width) - leftColumn = currentColumn - Frame.Width + 1; - SetNeedsDisplay (); + Adjust (); } // The column we are tracking, or -1 if we are not tracking any column @@ -745,27 +791,40 @@ void TrackColumn () void Adjust () { + int offB = OffSetBackground (); + var line = GetCurrentLine (); bool need = false; if (currentColumn < leftColumn) { - currentColumn = leftColumn; + leftColumn = currentColumn; need = true; - } - if (currentColumn - leftColumn > Frame.Width) { - leftColumn = currentColumn - Frame.Width + 1; + } else if (currentColumn - leftColumn > Frame.Width + offB || + TextModel.DisplaySize (line, leftColumn, currentColumn).size >= Frame.Width + offB) { + leftColumn = Math.Max (TextModel.CalculateLeftColumn (line, leftColumn, + currentColumn, Frame.Width - 1 + offB, currentColumn), 0); need = true; } if (currentRow < topRow) { topRow = currentRow; need = true; - } - if (currentRow - topRow > Frame.Height) { + } else if (currentRow - topRow > Frame.Height) { topRow = currentRow - Frame.Height + 1; need = true; } - if (need) + if (need) { SetNeedsDisplay (); - else + } else { PositionCursor (); + } + } + + int OffSetBackground () + { + int offB = 0; + if (SuperView?.Frame.Right - Frame.Right < 0) { + offB = SuperView.Frame.Right - Frame.Right - 1; + } + + return offB; } /// @@ -854,53 +913,33 @@ public override bool ProcessKey (KeyEvent kb) var currentLine = GetCurrentLine (); if (currentColumn < currentLine.Count) { currentColumn++; - if (currentColumn >= leftColumn + Frame.Width) { - leftColumn++; - SetNeedsDisplay (); - } - PositionCursor (); } else { if (currentRow + 1 < model.Count) { currentRow++; currentColumn = 0; - leftColumn = 0; if (currentRow >= topRow + Frame.Height) { topRow++; } - SetNeedsDisplay (); - PositionCursor (); } - break; } + Adjust (); break; case Key.B | Key.CtrlMask: case Key.CursorLeft: if (currentColumn > 0) { currentColumn--; - if (currentColumn < leftColumn) { - leftColumn--; - SetNeedsDisplay (); - } - PositionCursor (); } else { if (currentRow > 0) { currentRow--; if (currentRow < topRow) { topRow--; - SetNeedsDisplay (); } currentLine = GetCurrentLine (); currentColumn = currentLine.Count; - int prev = leftColumn; - leftColumn = currentColumn - Frame.Width + 1; - if (leftColumn < 0) - leftColumn = 0; - if (prev != leftColumn) - SetNeedsDisplay (); - PositionCursor (); } } + Adjust (); break; case Key.Delete: @@ -928,10 +967,7 @@ public override bool ProcessKey (KeyEvent kb) model.RemoveLine (currentRow); currentRow--; currentColumn = prevCount; - leftColumn = currentColumn - Frame.Width + 1; - if (leftColumn < 0) - leftColumn = 0; - SetNeedsDisplay (); + Adjust (); } break; @@ -939,11 +975,7 @@ public override bool ProcessKey (KeyEvent kb) case Key.Home: case Key.A | Key.CtrlMask: currentColumn = 0; - if (currentColumn < leftColumn) { - leftColumn = 0; - SetNeedsDisplay (); - } else - PositionCursor (); + Adjust (); break; case Key.DeleteChar: case Key.D | Key.CtrlMask: // Delete @@ -970,12 +1002,7 @@ public override bool ProcessKey (KeyEvent kb) currentLine = GetCurrentLine (); currentColumn = currentLine.Count; int pcol = leftColumn; - leftColumn = currentColumn - Frame.Width + 1; - if (leftColumn < 0) - leftColumn = 0; - if (pcol != leftColumn) - SetNeedsDisplay (); - PositionCursor (); + Adjust (); break; case Key.K | Key.CtrlMask: // kill-to-end @@ -1016,11 +1043,7 @@ public override bool ProcessKey (KeyEvent kb) selectionStartRow = currentRow; break; - case ((int)'w' + Key.AltMask): - SetClipboard (GetRegion ()); - selecting = false; - break; - + case ((int)'W' + Key.AltMask): case Key.W | Key.CtrlMask: SetClipboard (GetRegion ()); if (!isReadOnly) @@ -1050,7 +1073,6 @@ public override bool ProcessKey (KeyEvent kb) case Key.Enter: if (isReadOnly) break; - var orow = currentRow; currentLine = GetCurrentLine (); restCount = currentLine.Count - currentColumn; rest = currentLine.GetRange (currentColumn, restCount); @@ -1307,7 +1329,7 @@ public override bool MouseEvent (MouseEvent ev) if (idx - leftColumn >= r.Count) { currentColumn = r.Count - leftColumn; } else { - currentColumn = idx - leftColumn; + currentColumn = idx + leftColumn; } } PositionCursor (); From 56f2319227db6d955d20c4d5d4ea8430d3250fbd Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Dec 2020 22:24:01 +0000 Subject: [PATCH 09/70] Added column scrolling feature and some more other improvements. --- Terminal.Gui/Views/TextField.cs | 2 - Terminal.Gui/Views/TextView.cs | 194 ++++++++++++++++++++++---------- 2 files changed, 133 insertions(+), 63 deletions(-) diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 613fc33928..e0f85c27d3 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -115,8 +115,6 @@ public override Rect Frame { get => base.Frame; set { base.Frame = value; - var w = base.Frame.Width; - first = point > w ? point - w : 0; Adjust (); } } diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 88b0002841..27eba5b2ac 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -172,6 +172,25 @@ public void RemoveLine (int pos) lines.RemoveAt (pos); } + /// + /// Returns the maximum line length of the visible lines. + /// + /// The first line. + /// The last line. + public int GetMaxVisibleLine (int first, int last) + { + int maxLength = 0; + last = last < lines.Count ? last : lines.Count; + for (int i = first; i < last; i++) { + var l = GetLine (i).Count; + if (l > maxLength) { + maxLength = l; + } + } + + return maxLength; + } + internal static int SetCol (int col, int width, int cols) { if (col + cols <= width) { @@ -432,6 +451,15 @@ public override ustring Text { } } + /// + public override Rect Frame { + get => base.Frame; + set { + base.Frame = value; + Adjust (); + } + } + /// /// Loads the contents of the file into the . /// @@ -508,7 +536,11 @@ public override void PositionCursor () col += cols - 1; } } - Move (CurrentColumn - leftColumn - retreat + col, CurrentRow - topRow); + var ccol = CurrentColumn - leftColumn - retreat + col; + if (leftColumn <= CurrentColumn && ccol < Frame.Width + && topRow <= CurrentRow && CurrentRow - topRow < Frame.Height) { + Move (ccol, CurrentRow - topRow); + } } void ClearRegion (int left, int top, int right, int bottom) @@ -791,23 +823,23 @@ void TrackColumn () void Adjust () { - int offB = OffSetBackground (); + var offB = OffSetBackground (); var line = GetCurrentLine (); bool need = false; if (currentColumn < leftColumn) { leftColumn = currentColumn; need = true; - } else if (currentColumn - leftColumn > Frame.Width + offB || - TextModel.DisplaySize (line, leftColumn, currentColumn).size >= Frame.Width + offB) { + } else if (currentColumn - leftColumn > Frame.Width + offB.width || + TextModel.DisplaySize (line, leftColumn, currentColumn).size >= Frame.Width + offB.width) { leftColumn = Math.Max (TextModel.CalculateLeftColumn (line, leftColumn, - currentColumn, Frame.Width - 1 + offB, currentColumn), 0); + currentColumn, Frame.Width - 1 + offB.width, currentColumn), 0); need = true; } if (currentRow < topRow) { topRow = currentRow; need = true; - } else if (currentRow - topRow > Frame.Height) { - topRow = currentRow - Frame.Height + 1; + } else if (currentRow - topRow >= Frame.Height + offB.height) { + topRow = Math.Min (Math.Max (currentRow - Frame.Height + 1, 0), currentRow); need = true; } if (need) { @@ -817,25 +849,43 @@ void Adjust () } } - int OffSetBackground () + (int width, int height) OffSetBackground () { - int offB = 0; + int w = 0; + int h = 0; if (SuperView?.Frame.Right - Frame.Right < 0) { - offB = SuperView.Frame.Right - Frame.Right - 1; + w = SuperView.Frame.Right - Frame.Right - 1; } - - return offB; + if (SuperView?.Frame.Bottom - Frame.Bottom < 0) { + h = SuperView.Frame.Bottom - Frame.Bottom - 1; + } + return (w, h); } /// /// Will scroll the to display the specified row at the top /// /// Row that should be displayed at the top, if the value is negative it will be reset to zero - public void ScrollTo (int row) + public void ScrollToRow (int row) { - if (row < 0) + if (row < 0) { row = 0; - topRow = row > model.Count ? model.Count - 1 : row; + } + topRow = row > model.Count - 1 ? model.Count - 1 : row; + SetNeedsDisplay (); + } + + /// + /// Will scroll the to display the specified column at the left + /// + /// Column that should be displayed at the left, if the value is negative it will be reset to zero + public void ScrollToCol (int col) + { + if (col < 0) { + col = 0; + } + var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height); + leftColumn = col > maxlength - 1 ? maxlength - 1 : col; SetNeedsDisplay (); } @@ -883,7 +933,7 @@ public override bool ProcessKey (KeyEvent kb) break; case Key.PageUp: - case ((int)'v' + Key.AltMask): + case ((int)'V' + Key.AltMask): int nPageUpShift = Frame.Height - 1; if (currentRow > 0) { if (columnTrack == -1) @@ -1051,7 +1101,8 @@ public override bool ProcessKey (KeyEvent kb) selecting = false; break; - case (Key)((int)'b' + Key.AltMask): + case Key.CtrlMask | Key.CursorLeft: + case (Key)((int)'B' + Key.AltMask): var newPos = WordBackward (currentColumn, currentRow); if (newPos.HasValue) { currentColumn = newPos.Value.col; @@ -1061,7 +1112,8 @@ public override bool ProcessKey (KeyEvent kb) break; - case (Key)((int)'f' + Key.AltMask): + case Key.CtrlMask | Key.CursorRight: + case (Key)((int)'F' + Key.AltMask): newPos = WordForward (currentColumn, currentRow); if (newPos.HasValue) { currentColumn = newPos.Value.col; @@ -1123,11 +1175,12 @@ public override bool ProcessKey (KeyEvent kb) return true; } - private void MoveUp () + void MoveUp () { if (currentRow > 0) { - if (columnTrack == -1) + if (columnTrack == -1) { columnTrack = currentColumn; + } currentRow--; if (currentRow < topRow) { topRow--; @@ -1138,11 +1191,12 @@ private void MoveUp () } } - private void MoveDown () + void MoveDown () { if (currentRow + 1 < model.Count) { - if (columnTrack == -1) + if (columnTrack == -1) { columnTrack = currentColumn; + } currentRow++; if (currentRow >= topRow + Frame.Height) { topRow++; @@ -1150,6 +1204,8 @@ private void MoveDown () } TrackColumn (); PositionCursor (); + } else if (currentRow > Frame.Height) { + Adjust (); } } @@ -1246,28 +1302,31 @@ bool MovePrev (ref int col, ref int row, out Rune rune) { var col = fromCol; var row = fromRow; - var line = GetCurrentLine (); - var rune = RuneAt (col, row); + try { + var rune = RuneAt (col, row); - var srow = row; - if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) { - while (MoveNext (ref col, ref row, out rune)) { - if (Rune.IsLetterOrDigit (rune)) - break; - } - while (MoveNext (ref col, ref row, out rune)) { - if (!Rune.IsLetterOrDigit (rune)) - break; - } - } else { - while (MoveNext (ref col, ref row, out rune)) { - if (!Rune.IsLetterOrDigit (rune)) - break; + var srow = row; + if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) { + while (MoveNext (ref col, ref row, out rune)) { + if (Rune.IsLetterOrDigit (rune)) + break; + } + while (MoveNext (ref col, ref row, out rune)) { + if (!Rune.IsLetterOrDigit (rune)) + break; + } + } else { + while (MoveNext (ref col, ref row, out rune)) { + if (!Rune.IsLetterOrDigit (rune)) + break; + } } + if (fromCol != col || fromRow != row) + return (col, row); + return null; + } catch (Exception) { + return null; } - if (fromCol != col || fromRow != row) - return (col, row); - return null; } (int col, int row)? WordBackward (int fromCol, int fromRow) @@ -1277,27 +1336,30 @@ bool MovePrev (ref int col, ref int row, out Rune rune) var col = fromCol; var row = fromRow; - var line = GetCurrentLine (); - var rune = RuneAt (col, row); + try { + var rune = RuneAt (col, row); - if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) { - while (MovePrev (ref col, ref row, out rune)) { - if (Rune.IsLetterOrDigit (rune)) - break; - } - while (MovePrev (ref col, ref row, out rune)) { - if (!Rune.IsLetterOrDigit (rune)) - break; - } - } else { - while (MovePrev (ref col, ref row, out rune)) { - if (!Rune.IsLetterOrDigit (rune)) - break; + if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) { + while (MovePrev (ref col, ref row, out rune)) { + if (Rune.IsLetterOrDigit (rune)) + break; + } + while (MovePrev (ref col, ref row, out rune)) { + if (!Rune.IsLetterOrDigit (rune)) + break; + } + } else { + while (MovePrev (ref col, ref row, out rune)) { + if (!Rune.IsLetterOrDigit (rune)) + break; + } } + if (fromCol != col || fromRow != row) + return (col, row); + return null; + } catch (Exception) { + return null; } - if (fromCol != col || fromRow != row) - return (col, row); - return null; } /// @@ -1335,10 +1397,20 @@ public override bool MouseEvent (MouseEvent ev) PositionCursor (); } else if (ev.Flags == MouseFlags.WheeledDown) { lastWasKill = false; - MoveDown (); + columnTrack = currentColumn; + ScrollToRow (topRow + 1); } else if (ev.Flags == MouseFlags.WheeledUp) { lastWasKill = false; - MoveUp (); + columnTrack = currentColumn; + ScrollToRow (topRow - 1); + } else if (ev.Flags == MouseFlags.WheeledRight) { + lastWasKill = false; + columnTrack = currentColumn; + ScrollToCol (leftColumn + 1); + } else if (ev.Flags == MouseFlags.WheeledLeft) { + lastWasKill = false; + columnTrack = currentColumn; + ScrollToCol (leftColumn - 1); } return true; From 2d6e7cda99872d8e3dc0316b9e202b837de2fe14 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 26 Dec 2020 11:45:56 +0000 Subject: [PATCH 10/70] Fixes #1052. Application.CurrentView looks unused. --- Terminal.Gui/Core/Application.cs | 14 +------------- Terminal.Gui/Core/Toplevel.cs | 2 -- Terminal.Gui/Core/View.cs | 3 --- Terminal.Gui/Core/Window.cs | 1 - Terminal.Gui/Views/FrameView.cs | 1 - 5 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 176d1db3fb..e20dd170a0 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -72,12 +72,6 @@ public static class Application { /// The current. public static Toplevel Current { get; private set; } - /// - /// The current object being redrawn. - /// - /// /// The current. - public static View CurrentView { get; set; } - /// /// The current used in the terminal. /// @@ -193,7 +187,7 @@ public override void Send (SendOrPostCallback d, object state) /// Loads the right for the platform. /// /// - /// Creates a and assigns it to and + /// Creates a and assigns it to /// /// public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver); @@ -244,7 +238,6 @@ static void Init (Func topLevelFactory, ConsoleDriver driver = null, I } Top = topLevelFactory (); Current = Top; - CurrentView = Top; _initialized = true; } @@ -541,7 +534,6 @@ public static void Shutdown () } toplevels.Clear (); Current = null; - CurrentView = null; Top = null; MainLoop = null; @@ -552,8 +544,6 @@ public static void Shutdown () static void Redraw (View view) { - Application.CurrentView = view; - view.Redraw (view.Bounds); Driver.Refresh (); } @@ -588,10 +578,8 @@ internal static void End (View view) if (toplevels.Count == 0) { Current = null; - CurrentView = null; } else { Current = toplevels.Peek (); - CurrentView = Current; Refresh (); } } diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 9d38592cfe..373a3e8459 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -428,8 +428,6 @@ private void PositionToplevel (Toplevel top) /// public override void Redraw (Rect bounds) { - Application.CurrentView = this; - if (IsCurrentTop || this == Application.Top) { if (!NeedDisplay.IsEmpty || LayoutNeeded) { Driver.SetAttribute (Colors.TopLevel.Normal); diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index ac0a519d0e..b0b4724c2f 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1314,8 +1314,6 @@ public virtual void Redraw (Rect bounds) return; } - Application.CurrentView = this; - var clipRect = new Rect (Point.Empty, frame.Size); if (ColorScheme != null) { @@ -1340,7 +1338,6 @@ public virtual void Redraw (Rect bounds) 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) diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs index 00197567e4..29c7293fb4 100644 --- a/Terminal.Gui/Core/Window.cs +++ b/Terminal.Gui/Core/Window.cs @@ -169,7 +169,6 @@ public override void RemoveAll () public override void Redraw (Rect bounds) { //var padding = 0; - Application.CurrentView = this; var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height)); // BUGBUG: Why do we draw the frame twice? This call is here to clear the content area, I think. Why not just clear that area? diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index 947631eb6b..7ff5d2cf3e 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -147,7 +147,6 @@ public override void RemoveAll () public override void Redraw (Rect bounds) { var padding = 0; - Application.CurrentView = this; var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height)); if (!NeedDisplay.IsEmpty) { From 7583128cad2a5caa6c264099db14b63193dd8db9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 26 Dec 2020 11:50:25 +0000 Subject: [PATCH 11/70] Removed from the unit test too. --- UnitTests/ApplicationTests.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs index 110ebcdac5..5d5ce642c0 100644 --- a/UnitTests/ApplicationTests.cs +++ b/UnitTests/ApplicationTests.cs @@ -23,14 +23,12 @@ public ApplicationTests () public void Init_Shutdown_Cleans_Up () { Assert.Null (Application.Current); - Assert.Null (Application.CurrentView); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); Assert.NotNull (Application.Current); - Assert.NotNull (Application.CurrentView); Assert.NotNull (Application.Top); Assert.NotNull (Application.MainLoop); Assert.NotNull (Application.Driver); @@ -41,7 +39,6 @@ public void Init_Shutdown_Cleans_Up () Application.Shutdown (); Assert.Null (Application.Current); - Assert.Null (Application.CurrentView); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); @@ -92,7 +89,6 @@ public void Begin_End_Cleana_Up () Application.End (rs); Assert.Null (Application.Current); - Assert.Null (Application.CurrentView); Assert.NotNull (Application.Top); Assert.NotNull (Application.MainLoop); Assert.NotNull (Application.Driver); @@ -123,7 +119,6 @@ public void RequestStop_Stops () Application.Shutdown (); Assert.Null (Application.Current); - Assert.Null (Application.CurrentView); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); @@ -148,7 +143,6 @@ public void RunningFalse_Stops () Application.Shutdown (); Assert.Null (Application.Current); - Assert.Null (Application.CurrentView); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); @@ -207,7 +201,6 @@ public void KeyUp_Event () Application.Shutdown (); Assert.Null (Application.Current); - Assert.Null (Application.CurrentView); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); From 708fd8a8762ab39e5ab70b5f227f2f1e3c468c01 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 26 Dec 2020 12:03:06 +0000 Subject: [PATCH 12/70] Fixes #1053. ProcessMouseEvent seems to initialize MouseEvent incorrectly. --- Terminal.Gui/Core/Application.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 176d1db3fb..7350bcb712 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -438,8 +438,8 @@ static void ProcessMouseEvent (MouseEvent me) X = rx, Y = ry, Flags = me.Flags, - OfX = rx, - OfY = ry, + OfX = 0, + OfY = 0, View = view }; From 01c07ddc9ef35efbc78b437ccbe0707a9dd9620c Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 26 Dec 2020 13:56:30 +0000 Subject: [PATCH 13/70] Fixes #1056. Window doesn't redraw his SuperView properly. --- Terminal.Gui/Core/Window.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs index 00197567e4..8ced76cc97 100644 --- a/Terminal.Gui/Core/Window.cs +++ b/Terminal.Gui/Core/Window.cs @@ -196,12 +196,8 @@ public override void Redraw (Rect bounds) // Checks if there are any SuperView view which intersect with this window. if (SuperView != null) { - foreach (var view in SuperView.Subviews) { - if (view != this && view.Frame.IntersectsWith (Bounds)) { - view.SetNeedsLayout (); - view.SetNeedsDisplay (view.Bounds); - } - } + SuperView.SetNeedsLayout (); + SuperView.SetNeedsDisplay (); } } From 9d721e94f6b1b2debf217e91d01f5e544f30501c Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 26 Dec 2020 23:57:13 +0000 Subject: [PATCH 14/70] Ensures clipping always respecting the anchoring if it's the case. Adding SetWidth and SetHeight methods to the View class. --- Terminal.Gui/Core/Toplevel.cs | 19 +++++++++-- Terminal.Gui/Core/View.cs | 60 +++++++++++++++++++++++++++++++++++ Terminal.Gui/Core/Window.cs | 7 ++-- Terminal.Gui/Views/Button.cs | 15 ++------- 4 files changed, 84 insertions(+), 17 deletions(-) diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 9d38592cfe..e4b8522e18 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -363,14 +363,24 @@ internal void RemoveMenuStatusBar (View view) internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny) { nx = Math.Max (x, 0); - nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx; + int l; + if (SuperView == null || SuperView is Toplevel) { + l = Driver.Cols; + } else { + l = SuperView.Frame.Width; + } + nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx; + SetWidth (top.Frame.Width, out int rWidth); + if (rWidth < 0 && nx >= top.Frame.X) { + nx = Math.Max (top.Frame.Right - 2, 0); + } + //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); bool m, s; if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) { m = Application.Top.MenuBar != null; } else { m = ((Toplevel)SuperView).MenuBar != null; } - int l; if (SuperView == null || SuperView is Toplevel) { l = m ? 1 : 0; } else { @@ -389,6 +399,11 @@ internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out i } ny = Math.Min (ny, l); ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; + SetHeight (top.Frame.Height, out int rHeight); + if (rHeight < 0 && ny >= top.Frame.Y) { + ny = Math.Max (top.Frame.Bottom - 2, 0); + } + //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); } internal void PositionToplevels () diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index ac0a519d0e..fa8ad40452 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -2144,5 +2144,65 @@ bool CanBeVisible (View view) return true; } + + /// + /// Calculate the width based on the settings. + /// + /// The desired width. + /// The real result width. + /// True if the width can be directed assigned, false otherwise. + public bool SetWidth (int desiredWidth, out int resultWidth) + { + int w = desiredWidth; + bool canSetWidth; + if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) { + // It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored. + w = Width.Anchor (w); + canSetWidth = false; + } else if (Width is Dim.DimFactor factor) { + // Tries to get the SuperView width otherwise the view width. + var sw = SuperView != null ? SuperView.Frame.Width : w; + if (factor.IsFromRemaining ()) { + sw -= Frame.X; + } + w = Width.Anchor (sw); + canSetWidth = false; + } else { + canSetWidth = true; + } + resultWidth = w; + + return canSetWidth; + } + + /// + /// Calculate the height based on the settings. + /// + /// The desired height. + /// The real result height. + /// True if the height can be directed assigned, false otherwise. + public bool SetHeight (int desiredHeight, out int resultHeight) + { + int h = desiredHeight; + bool canSetHeight; + if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) { + // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. + h = Height.Anchor (h); + canSetHeight = false; + } else if (Height is Dim.DimFactor factor) { + // Tries to get the SuperView height otherwise the view height. + var sh = SuperView != null ? SuperView.Frame.Height : h; + if (factor.IsFromRemaining ()) { + sh -= Frame.Y; + } + h = Height.Anchor (sh); + canSetHeight = false; + } else { + canSetHeight = true; + } + resultHeight = h; + + return canSetHeight; + } } } diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs index 8ced76cc97..8a8f6e1079 100644 --- a/Terminal.Gui/Core/Window.cs +++ b/Terminal.Gui/Core/Window.cs @@ -227,7 +227,7 @@ public override bool MouseEvent (MouseEvent mouseEvent) Application.GrabMouse (this); } - //Demo.ml2.Text = $"Starting at {dragPosition}"; + //System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}"); return true; } else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || mouseEvent.Flags == MouseFlags.Button3Pressed) { @@ -245,10 +245,11 @@ public override bool MouseEvent (MouseEvent mouseEvent) mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny); dragPosition = new Point (nx, ny); + LayoutSubviews (); Frame = new Rect (nx, ny, Frame.Width, Frame.Height); X = nx; Y = ny; - //Demo.ml2.Text = $"{dx},{dy}"; + //System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}"); // FIXED: optimize, only SetNeedsDisplay on the before/after regions. SetNeedsDisplay (); @@ -262,7 +263,7 @@ public override bool MouseEvent (MouseEvent mouseEvent) dragPosition = null; } - //Demo.ml.Text = me.ToString (); + //System.Diagnostics.Debug.WriteLine (mouseEvent.ToString ()); return false; } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 9a9fb351b9..5b22615fd5 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -146,19 +146,10 @@ internal void Update () base.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket); int w = base.Text.RuneCount - (base.Text.Contains (HotKeySpecifier) ? 1 : 0); - if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) { - // It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored. - w = Width.Anchor (w); - } else if (Width is Dim.DimFactor) { - // Tries to get the SuperView width otherwise the button width. - var sw = SuperView != null ? SuperView.Frame.Width : w; - if (((Dim.DimFactor)Width).IsFromRemaining ()) { - sw -= Frame.X; - } - w = Width.Anchor (sw); - } else { - Width = w; + if (SetWidth (w, out int rWidth)) { + Width = rWidth; } + w = rWidth; var layout = LayoutStyle; bool layoutChanged = false; if (!(Height is Dim.DimAbsolute)) { From abab3f8c254243c3f9b8c70f5e33c3dab925fe95 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 27 Dec 2020 11:14:36 +0000 Subject: [PATCH 15/70] Typo fix. --- Terminal.Gui/Core/View.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index fa8ad40452..0e5ac7ce67 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -2150,7 +2150,7 @@ bool CanBeVisible (View view) /// /// The desired width. /// The real result width. - /// True if the width can be directed assigned, false otherwise. + /// True if the width can be directly assigned, false otherwise. public bool SetWidth (int desiredWidth, out int resultWidth) { int w = desiredWidth; @@ -2180,7 +2180,7 @@ public bool SetWidth (int desiredWidth, out int resultWidth) /// /// The desired height. /// The real result height. - /// True if the height can be directed assigned, false otherwise. + /// True if the height can be directly assigned, false otherwise. public bool SetHeight (int desiredHeight, out int resultHeight) { int h = desiredHeight; From 43ea03bc2923a57068af85235b71381f80084955 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 27 Dec 2020 17:12:25 +0000 Subject: [PATCH 16/70] In addition to the view visibility having to be true to be redrawn, it is also necessary that the width and height are greater than zero. --- Terminal.Gui/Core/View.cs | 10 +++++----- Terminal.Gui/Views/Menu.cs | 3 +++ UICatalog/Scenarios/Clipping.cs | 10 +++++----- UICatalog/Scenarios/Threading.cs | 8 +++++++- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 0e5ac7ce67..e23bb90e2d 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1125,10 +1125,10 @@ public virtual void PositionCursor () return; } - if (focused != null) { + if (focused?.Frame.Width > 0 && focused.Frame.Height > 0) { focused.PositionCursor (); } else { - if (CanFocus && HasFocus && Visible) { + if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { Move (textFormatter.HotKeyPos == -1 ? 0 : textFormatter.CursorPosition, 0); } else { Move (frame.X, frame.Y); @@ -1344,7 +1344,7 @@ public virtual void Redraw (Rect bounds) // Draw the subview // Use the view's bounds (view-relative; Location will always be (0,0) - if (view.Visible) { + if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { view.Redraw (view.Bounds); } } @@ -2133,11 +2133,11 @@ public void EndInit () bool CanBeVisible (View view) { - if (!view.Visible) { + if (!view.Visible || view.Frame.Width == 0 || view.Frame.Height == 0) { return false; } for (var c = view.SuperView; c != null; c = c.SuperView) { - if (!c.Visible) { + if (!c.Visible || c.Frame.Width == 0 || c.Frame.Height == 0) { return false; } } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index f3a7f49549..e3047fd32a 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -927,6 +927,9 @@ public override void Redraw (Rect bounds) /// public override void PositionCursor () { + if (selected == -1 && HasFocus && Menus.Length > 0) { + selected = 0; + } int pos = 0; for (int i = 0; i < Menus.Length; i++) { if (i == selected) { diff --git a/UICatalog/Scenarios/Clipping.cs b/UICatalog/Scenarios/Clipping.cs index 67496d1cb8..00cf263af8 100644 --- a/UICatalog/Scenarios/Clipping.cs +++ b/UICatalog/Scenarios/Clipping.cs @@ -32,7 +32,7 @@ public override void Setup () //Win.Y = 2; //Win.Width = Dim.Fill () - 4; //Win.Height = Dim.Fill () - 2; - var label = new Label ("ScrollView (new Rect (5, 5, 100, 60)) with a 200, 100 ContentSize...") { + var label = new Label ("ScrollView (new Rect (3, 3, 50, 20)) with a 200, 100 ContentSize...") { X = 0, Y = 0, //ColorScheme = Colors.Dialog }; @@ -40,10 +40,10 @@ public override void Setup () var scrollView = new ScrollView (new Rect (3, 3, 50, 20)); scrollView.ColorScheme = Colors.Menu; - scrollView.ContentSize = new Size (100, 60); + scrollView.ContentSize = new Size (200, 100); //ContentOffset = new Point (0, 0), - scrollView.ShowVerticalScrollIndicator = true; - scrollView.ShowHorizontalScrollIndicator = true; + //scrollView.ShowVerticalScrollIndicator = true; + //scrollView.ShowHorizontalScrollIndicator = true; var embedded1 = new Window ("1") { X = 3, @@ -78,7 +78,7 @@ public override void Setup () embedded2.Add (embedded3); scrollView.Add (embedded1); - + Top.Add (scrollView); } } diff --git a/UICatalog/Scenarios/Threading.cs b/UICatalog/Scenarios/Threading.cs index 68ebf92f9b..14ab8d4d4e 100644 --- a/UICatalog/Scenarios/Threading.cs +++ b/UICatalog/Scenarios/Threading.cs @@ -92,7 +92,13 @@ public override void Setup () _btnQuit.Clicked += Application.RequestStop; Win.Add (_itemsList, _btnActionCancel, _logJob, text, _btnAction, _btnLambda, _btnHandler, _btnSync, _btnMethod, _btnClearData, _btnQuit); - _btnActionCancel.SetFocus (); + + void Top_Loaded () + { + _btnActionCancel.SetFocus (); + Top.Loaded -= Top_Loaded; + } + Top.Loaded += Top_Loaded; } private async void LoadData () From 30e4439e7b684291c21fdfd603b5e4aa7f04d5fb Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 27 Dec 2020 18:16:44 +0000 Subject: [PATCH 17/70] Reverting the CanBeVisible method. --- Terminal.Gui/Core/View.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index e23bb90e2d..c0ec426a61 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -2133,11 +2133,11 @@ public void EndInit () bool CanBeVisible (View view) { - if (!view.Visible || view.Frame.Width == 0 || view.Frame.Height == 0) { + if (!view.Visible) { return false; } for (var c = view.SuperView; c != null; c = c.SuperView) { - if (!c.Visible || c.Frame.Width == 0 || c.Frame.Height == 0) { + if (!c.Visible) { return false; } } From fa57c20a48a457ed70ea412da81d0a25dcce90ff Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 27 Dec 2020 19:03:49 +0000 Subject: [PATCH 18/70] Allowing ScrollTo method to deal with rows and columns without breaking anything. --- Terminal.Gui/Views/TextView.cs | 40 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 27eba5b2ac..3284843f4c 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -863,29 +863,23 @@ void Adjust () } /// - /// Will scroll the to display the specified row at the top + /// Will scroll the to display the specified row at the top if is true or + /// will scroll the to display the specified column at the left if is false. /// - /// Row that should be displayed at the top, if the value is negative it will be reset to zero - public void ScrollToRow (int row) + /// Row that should be displayed at the top or Column that should be displayed at the left, + /// if the value is negative it will be reset to zero + /// If true (default) the is a row, column otherwise. + public void ScrollTo (int idx, bool isRow = true) { - if (row < 0) { - row = 0; + if (idx < 0) { + idx = 0; } - topRow = row > model.Count - 1 ? model.Count - 1 : row; - SetNeedsDisplay (); - } - - /// - /// Will scroll the to display the specified column at the left - /// - /// Column that should be displayed at the left, if the value is negative it will be reset to zero - public void ScrollToCol (int col) - { - if (col < 0) { - col = 0; + if (isRow) { + topRow = idx > model.Count - 1 ? model.Count - 1 : idx; + } else { + var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height); + leftColumn = idx > maxlength - 1 ? maxlength - 1 : idx; } - var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height); - leftColumn = col > maxlength - 1 ? maxlength - 1 : col; SetNeedsDisplay (); } @@ -1398,19 +1392,19 @@ public override bool MouseEvent (MouseEvent ev) } else if (ev.Flags == MouseFlags.WheeledDown) { lastWasKill = false; columnTrack = currentColumn; - ScrollToRow (topRow + 1); + ScrollTo (topRow + 1); } else if (ev.Flags == MouseFlags.WheeledUp) { lastWasKill = false; columnTrack = currentColumn; - ScrollToRow (topRow - 1); + ScrollTo (topRow - 1); } else if (ev.Flags == MouseFlags.WheeledRight) { lastWasKill = false; columnTrack = currentColumn; - ScrollToCol (leftColumn + 1); + ScrollTo (leftColumn + 1, false); } else if (ev.Flags == MouseFlags.WheeledLeft) { lastWasKill = false; columnTrack = currentColumn; - ScrollToCol (leftColumn - 1); + ScrollTo (leftColumn - 1, false); } return true; From ee0fc06b9a96b6eb536cba6d05cb90d6a1eb6353 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 28 Dec 2020 00:29:33 +0000 Subject: [PATCH 19/70] Fixes #1061. ComputerLayout scenario does not drawn correctly. --- Terminal.Gui/Core/Window.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs index aac079b574..5997f3f41f 100644 --- a/Terminal.Gui/Core/Window.cs +++ b/Terminal.Gui/Core/Window.cs @@ -184,6 +184,7 @@ public override void Redraw (Rect bounds) contentView.Redraw (contentView.Bounds); Driver.Clip = savedClip; + ClearLayoutNeeded (); ClearNeedsDisplay (); Driver.SetAttribute (ColorScheme.Normal); Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false); @@ -246,8 +247,12 @@ public override bool MouseEvent (MouseEvent mouseEvent) dragPosition = new Point (nx, ny); LayoutSubviews (); Frame = new Rect (nx, ny, Frame.Width, Frame.Height); - X = nx; - Y = ny; + if (X == null || X is Pos.PosAbsolute) { + X = nx; + } + if (Y == null || Y is Pos.PosAbsolute) { + Y = ny; + } //System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}"); // FIXED: optimize, only SetNeedsDisplay on the before/after regions. From 721c796c7f80d9979f1d3b8b16f0514220b79aab Mon Sep 17 00:00:00 2001 From: Herman Schoenfeld Date: Mon, 28 Dec 2020 14:09:17 +1000 Subject: [PATCH 20/70] Added unhandled exception handling in Application.Run --- Terminal.Gui/Core/Application.cs | 44 +++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index f8400455ab..399f7ac04e 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -645,18 +645,18 @@ static void DrawBounds (View v) /// /// Runs the application by calling with the value of /// - public static void Run () + public static void Run (Func errorHandler = null) { - Run (Top); + Run (Top, errorHandler); } /// - /// Runs the application by calling with a new instance of the specified -derived class + /// Runs the application by calling with a new instance of the specified -derived class /// - public static void Run () where T : Toplevel, new() + public static void Run (Func errorHandler = null) where T : Toplevel, new() { Init (() => new T ()); - Run (Top); + Run (Top, errorHandler); } /// @@ -669,10 +669,10 @@ public static void Run () /// run other modal s such as boxes. /// /// - /// To make a stop execution, call . + /// To make a stop execution, call . /// /// - /// Calling is equivalent to calling , followed by , + /// Calling is equivalent to calling , followed by , /// and then calling . /// /// @@ -682,13 +682,33 @@ public static void Run () /// the method will only process any pending events, timers, idle handlers and /// then return control immediately. /// + /// + /// When is null the exception is rethrown, when it returns true the application is resumed and when false method exits gracefully. + /// /// /// The tu run modally. - public static void Run (Toplevel view) + /// Handler for any unhandled exceptions (resumes when returns true, rethrows when null). + public static void Run (Toplevel view, Func errorHandler = null) { - var runToken = Begin (view); - RunLoop (runToken); - End (runToken); + while (true) + { + var resume = false; + try + { + var runToken = Begin (view); + RunLoop (runToken); + End (runToken); + } + catch (Exception error) + { + if (errorHandler == null) + throw; + resume = errorHandler (error); + } + if (resume) + continue; + break; + } } /// @@ -696,7 +716,7 @@ public static void Run (Toplevel view) /// /// /// - /// This will cause to return. + /// This will cause to return. /// /// /// Calling is equivalent to setting the property on the curently running to false. From 5e1e7c0ee4877c8171c385b07dd45b010048e74b Mon Sep 17 00:00:00 2001 From: Herman Schoenfeld Date: Mon, 28 Dec 2020 21:56:08 +1000 Subject: [PATCH 21/70] XMLDOC fixes for prior commit --- Terminal.Gui/Core/Application.cs | 4 ++-- Terminal.Gui/Core/Toplevel.cs | 8 ++++---- Terminal.Gui/Windows/Dialog.cs | 2 +- Terminal.Gui/Windows/FileDialog.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 399f7ac04e..8d39115108 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -67,7 +67,7 @@ public static class Application { public static Toplevel Top { get; private set; } /// - /// The current object. This is updated when enters and leaves to point to the current . + /// The current object. This is updated when enters and leaves to point to the current . /// /// The current. public static Toplevel Current { get; private set; } @@ -643,7 +643,7 @@ static void DrawBounds (View v) } /// - /// Runs the application by calling with the value of + /// Runs the application by calling with the value of /// public static void Run (Func errorHandler = null) { diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 0d42a89f8b..19b133ef98 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -15,7 +15,7 @@ namespace Terminal.Gui { /// /// /// - /// Toplevels can be modally executing views, started by calling . + /// Toplevels can be modally executing views, started by calling . /// They return control to the caller when has /// been called (which sets the property to false). /// @@ -23,7 +23,7 @@ namespace Terminal.Gui { /// A Toplevel is created when an application initialzies Terminal.Gui by callling . /// The application Toplevel can be accessed via . Additional Toplevels can be created /// and run (e.g. s. To run a Toplevel, create the and - /// call . + /// call . /// /// /// Toplevels can also opt-in to more sophisticated initialization @@ -57,7 +57,7 @@ public class Toplevel : View { /// /// Fired once the Toplevel's has started it's first iteration. /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. - /// changes. A Ready event handler is a good place to finalize initialization after calling `(topLevel)`. + /// changes. A Ready event handler is a good place to finalize initialization after calling `(topLevel)`. /// public event Action Ready; @@ -469,7 +469,7 @@ public override void Redraw (Rect bounds) } /// - /// Invoked by as part of the after + /// Invoked by as part of the after /// the views have been laid out, and before the views are drawn for the first time. /// public virtual void WillPresent () diff --git a/Terminal.Gui/Windows/Dialog.cs b/Terminal.Gui/Windows/Dialog.cs index bb3134e10e..4542f2664e 100644 --- a/Terminal.Gui/Windows/Dialog.cs +++ b/Terminal.Gui/Windows/Dialog.cs @@ -15,7 +15,7 @@ namespace Terminal.Gui { /// or more s. It defaults to the color scheme and has a 1 cell padding around the edges. /// /// - /// To run the modally, create the , and pass it to . + /// To run the modally, create the , and pass it to . /// This will execute the dialog until it terminates via the [ESC] or [CTRL-Q] key, or when one of the views /// or buttons added to the dialog calls . /// diff --git a/Terminal.Gui/Windows/FileDialog.cs b/Terminal.Gui/Windows/FileDialog.cs index a3e6c89dfa..b6981fbbeb 100644 --- a/Terminal.Gui/Windows/FileDialog.cs +++ b/Terminal.Gui/Windows/FileDialog.cs @@ -670,7 +670,7 @@ public ustring FilePath { /// /// /// To use, create an instance of , and pass it to - /// . This will run the dialog modally, + /// . This will run the dialog modally, /// and when this returns, the property will contain the selected file name or /// null if the user canceled. /// @@ -713,7 +713,7 @@ public ustring FileName { /// /// /// To use, create an instance of , and pass it to - /// . This will run the dialog modally, + /// . This will run the dialog modally, /// and when this returns, the list of filds will be available on the property. /// /// From 183be6c31f6f54a0a1b47446f13f4e83c2b9ddab Mon Sep 17 00:00:00 2001 From: Herman Schoenfeld Date: Tue, 29 Dec 2020 16:23:23 +1000 Subject: [PATCH 22/70] Refactor unhandled exception handling loop as per review --- Terminal.Gui/Core/Application.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 8d39115108..7bb1e94868 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -690,24 +690,24 @@ public static void Run (Func errorHandler = null) /// Handler for any unhandled exceptions (resumes when returns true, rethrows when null). public static void Run (Toplevel view, Func errorHandler = null) { - while (true) + var resume = true; + while (resume) { - var resume = false; try { + resume = false; var runToken = Begin (view); RunLoop (runToken); End (runToken); - } - catch (Exception error) + } + catch (Exception error) { if (errorHandler == null) + { throw; - resume = errorHandler (error); + } + resume = errorHandler(error); } - if (resume) - continue; - break; } } From 7febb7ac5c7cc0d990a012f09392635758c52648 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 29 Dec 2020 15:21:25 +0000 Subject: [PATCH 23/70] Fixes #1068. The ResizeView doesn't handle the AutoSize properly. --- Terminal.Gui/Core/View.cs | 7 ++++--- UICatalog/Scenarios/Scrolling.cs | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 122d09a6eb..0e7bd5bb6e 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1966,6 +1966,7 @@ public override string ToString () bool ResizeView (bool autoSize) { + var aSize = autoSize; if (textFormatter.Size != Bounds.Size && (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0 || autoSize && Bounds.Width != textFormatter.Size.Width)) || ((height == null || height is Dim.DimAbsolute) && (Bounds.Height == 0 @@ -1976,17 +1977,17 @@ bool ResizeView (bool autoSize) } else if (width is Dim.DimAbsolute) { width = Math.Max (Bounds.Width, height.Anchor (Bounds.Width)); } else { - return false; + aSize = false; } if (height == null) { height = Bounds.Height; } else if (height is Dim.DimAbsolute) { height = Math.Max (Bounds.Height, height.Anchor (Bounds.Height)); } else { - return false; + aSize = false; } } - return autoSize; + return aSize; } /// diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index 119ea77fee..79ee89ad50 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -128,8 +128,7 @@ public override void Setup () var horizontalRuler = new Label () { X = 0, Y = 0, - Width = Dim.Fill (1), // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does. - Height = 1, + Width = Dim.Fill (), // FIXED: I don't think this should be needed; DimFill() should respect container's frame. X does. ColorScheme = Colors.Error }; scrollView.Add (horizontalRuler); From 2674a3b21a46502679bb6868d33b897b41a66fcd Mon Sep 17 00:00:00 2001 From: mrboring Date: Sat, 2 Jan 2021 10:38:02 +0000 Subject: [PATCH 24/70] Fixed misspelling of scrollview --- Terminal.Gui/Views/ScrollView.cs | 2 +- docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html | 2 +- docs/index.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 3a53bc5d45..6e441b16cd 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -427,7 +427,7 @@ void Init (Rect frame) bool autoHideScrollBars = true; /// - /// Represents the contents of the data shown inside the scrolview + /// Represents the contents of the data shown inside the scrollview /// /// The size of the content. public Size ContentSize { diff --git a/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html b/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html index a49c193c42..5644581987 100644 --- a/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html +++ b/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html @@ -515,7 +515,7 @@
Property Value

ContentSize

-Represents the contents of the data shown inside the scrolview +Represents the contents of the data shown inside the scrollview
Declaration
diff --git a/docs/index.json b/docs/index.json index 4798b2c3fa..85104b0e53 100644 --- a/docs/index.json +++ b/docs/index.json @@ -267,7 +267,7 @@ "api/Terminal.Gui/Terminal.Gui.ScrollView.html": { "href": "api/Terminal.Gui/Terminal.Gui.ScrollView.html", "title": "Class ScrollView", - "keywords": "Class ScrollView Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. Inheritance System.Object Responder View ScrollView Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize Inherited Members View.Added View.Removed View.Enter View.Leave View.MouseEnter View.MouseLeave View.MouseClick View.HotKey View.HotKeySpecifier View.Shortcut View.ShortcutTag View.ShortcutAction View.Data View.Driver View.Subviews View.TabIndexes View.TabIndex View.TabStop View.CanFocus View.Id View.IsCurrentTop View.WantMousePositionReports View.WantContinuousButtonPressed View.Frame View.LayoutStyle View.Bounds View.X View.Y View.Width View.Height View.SuperView View.SetNeedsDisplay() View.SetNeedsDisplay(Rect) View.ChildNeedsDisplay() View.Add(View[]) View.Remove(View) View.BringSubviewToFront(View) View.SendSubviewToBack(View) View.SendSubviewBackwards(View) View.BringSubviewForward(View) View.Clear() View.Clear(Rect) View.ScreenToView(Int32, Int32) View.ClipToBounds() View.SetClip(Rect) View.DrawFrame(Rect, Int32, Boolean) View.DrawHotString(ustring, Attribute, Attribute) View.DrawHotString(ustring, Boolean, ColorScheme) View.Move(Int32, Int32) View.HasFocus View.OnAdded(View) View.OnRemoved(View) View.OnEnter(View) View.OnLeave(View) View.Focused View.MostFocused View.ColorScheme View.AddRune(Int32, Int32, Rune) View.ClearNeedsDisplay() View.DrawContent View.OnDrawContent(Rect) View.SetFocus() View.KeyPress View.ProcessHotKey(KeyEvent) View.ProcessColdKey(KeyEvent) View.KeyDown View.OnKeyDown(KeyEvent) View.KeyUp View.OnKeyUp(KeyEvent) View.EnsureFocus() View.FocusFirst() View.FocusLast() View.FocusPrev() View.FocusNext() View.LayoutStarted View.LayoutComplete View.Initialized View.LayoutSubviews() View.Text View.AutoSize View.TextAlignment View.IsInitialized View.ToString() View.OnMouseEnter(MouseEvent) View.OnMouseLeave(MouseEvent) View.OnMouseEvent(MouseEvent) View.OnMouseClick(View.MouseEventArgs) View.BeginInit() View.EndInit() View.Visible Responder.Dispose() System.Object.Equals(System.Object) System.Object.Equals(System.Object, System.Object) System.Object.GetHashCode() System.Object.GetType() System.Object.MemberwiseClone() System.Object.ReferenceEquals(System.Object, System.Object) Namespace : Terminal.Gui Assembly : Terminal.Gui.dll Syntax public class ScrollView : View, IDisposable, ISupportInitializeNotification, ISupportInitialize Remarks The subviews that are added to this ScrollView are offset by the ContentOffset property. The view itself is a window into the space represented by the ContentSize . Use the Constructors ScrollView() Initializes a new instance of the ScrollView class using Computed positioning. Declaration public ScrollView() ScrollView(Rect) Initializes a new instance of the ScrollView class using Absolute positioning. Declaration public ScrollView(Rect frame) Parameters Type Name Description Rect frame Properties AutoHideScrollBars If true the vertical/horizontal scroll bars won't be showed if it's not needed. Declaration public bool AutoHideScrollBars { get; set; } Property Value Type Description System.Boolean ContentOffset Represents the top left corner coordinate that is displayed by the scrollview Declaration public Point ContentOffset { get; set; } Property Value Type Description Point The content offset. ContentSize Represents the contents of the data shown inside the scrolview Declaration public Size ContentSize { get; set; } Property Value Type Description Size The size of the content. KeepContentAlwaysInViewport Get or sets if the view-port is kept always visible in the area of this ScrollView Declaration public bool KeepContentAlwaysInViewport { get; set; } Property Value Type Description System.Boolean ShowHorizontalScrollIndicator Gets or sets the visibility for the horizontal scroll indicator. Declaration public bool ShowHorizontalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show horizontal scroll indicator; otherwise, false . ShowVerticalScrollIndicator /// Gets or sets the visibility for the vertical scroll indicator. Declaration public bool ShowVerticalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show vertical scroll indicator; otherwise, false . Methods Add(View) Adds the view to the scrollview. Declaration public override void Add(View view) Parameters Type Name Description View view The view to add to the scrollview. Overrides View.Add(View) Dispose(Boolean) Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration protected override void Dispose(bool disposing) Parameters Type Name Description System.Boolean disposing Overrides View.Dispose(Boolean) Remarks If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed. MouseEvent(MouseEvent) Method invoked when a mouse event is generated Declaration public override bool MouseEvent(MouseEvent me) Parameters Type Name Description MouseEvent me Returns Type Description System.Boolean true , if the event was handled, false otherwise. Overrides Responder.MouseEvent(MouseEvent) PositionCursor() Positions the cursor in the right position based on the currently focused view in the chain. Declaration public override void PositionCursor() Overrides View.PositionCursor() ProcessKey(KeyEvent) If the view is focused, gives the view a chance to process the keystroke. Declaration public override bool ProcessKey(KeyEvent kb) Parameters Type Name Description KeyEvent kb Returns Type Description System.Boolean Overrides View.ProcessKey(KeyEvent) Remarks Views can override this method if they are interested in processing the given keystroke. If they consume the keystroke, they must return true to stop the keystroke from being processed by other widgets or consumed by the widget engine. If they return false, the keystroke will be passed using the ProcessColdKey method to other views to process. The View implementation does nothing but return false, so it is not necessary to call base.ProcessKey if you derive directly from View, but you should if you derive other View subclasses. Redraw(Rect) Redraws this view and its subviews; only redraws the views that have been flagged for a re-display. Declaration public override void Redraw(Rect region) Parameters Type Name Description Rect region Overrides View.Redraw(Rect) Remarks Always use Bounds (view-relative) when calling Redraw(Rect) , NOT Frame (superview-relative). Views should set the color that they want to use on entry, as otherwise this will inherit the last color that was set globally on the driver. Overrides of Redraw(Rect) must ensure they do not set Driver.Clip to a clip region larger than the region parameter. RemoveAll() Removes all widgets from this container. Declaration public override void RemoveAll() Overrides View.RemoveAll() Remarks ScrollDown(Int32) Scrolls the view down. Declaration public bool ScrollDown(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollLeft(Int32) Scrolls the view to the left Declaration public bool ScrollLeft(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollRight(Int32) Scrolls the view to the right. Declaration public bool ScrollRight(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if right was scrolled, false otherwise. ScrollUp(Int32) Scrolls the view up. Declaration public bool ScrollUp(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize" + "keywords": "Class ScrollView Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. Inheritance System.Object Responder View ScrollView Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize Inherited Members View.Added View.Removed View.Enter View.Leave View.MouseEnter View.MouseLeave View.MouseClick View.HotKey View.HotKeySpecifier View.Shortcut View.ShortcutTag View.ShortcutAction View.Data View.Driver View.Subviews View.TabIndexes View.TabIndex View.TabStop View.CanFocus View.Id View.IsCurrentTop View.WantMousePositionReports View.WantContinuousButtonPressed View.Frame View.LayoutStyle View.Bounds View.X View.Y View.Width View.Height View.SuperView View.SetNeedsDisplay() View.SetNeedsDisplay(Rect) View.ChildNeedsDisplay() View.Add(View[]) View.Remove(View) View.BringSubviewToFront(View) View.SendSubviewToBack(View) View.SendSubviewBackwards(View) View.BringSubviewForward(View) View.Clear() View.Clear(Rect) View.ScreenToView(Int32, Int32) View.ClipToBounds() View.SetClip(Rect) View.DrawFrame(Rect, Int32, Boolean) View.DrawHotString(ustring, Attribute, Attribute) View.DrawHotString(ustring, Boolean, ColorScheme) View.Move(Int32, Int32) View.HasFocus View.OnAdded(View) View.OnRemoved(View) View.OnEnter(View) View.OnLeave(View) View.Focused View.MostFocused View.ColorScheme View.AddRune(Int32, Int32, Rune) View.ClearNeedsDisplay() View.DrawContent View.OnDrawContent(Rect) View.SetFocus() View.KeyPress View.ProcessHotKey(KeyEvent) View.ProcessColdKey(KeyEvent) View.KeyDown View.OnKeyDown(KeyEvent) View.KeyUp View.OnKeyUp(KeyEvent) View.EnsureFocus() View.FocusFirst() View.FocusLast() View.FocusPrev() View.FocusNext() View.LayoutStarted View.LayoutComplete View.Initialized View.LayoutSubviews() View.Text View.AutoSize View.TextAlignment View.IsInitialized View.ToString() View.OnMouseEnter(MouseEvent) View.OnMouseLeave(MouseEvent) View.OnMouseEvent(MouseEvent) View.OnMouseClick(View.MouseEventArgs) View.BeginInit() View.EndInit() View.Visible Responder.Dispose() System.Object.Equals(System.Object) System.Object.Equals(System.Object, System.Object) System.Object.GetHashCode() System.Object.GetType() System.Object.MemberwiseClone() System.Object.ReferenceEquals(System.Object, System.Object) Namespace : Terminal.Gui Assembly : Terminal.Gui.dll Syntax public class ScrollView : View, IDisposable, ISupportInitializeNotification, ISupportInitialize Remarks The subviews that are added to this ScrollView are offset by the ContentOffset property. The view itself is a window into the space represented by the ContentSize . Use the Constructors ScrollView() Initializes a new instance of the ScrollView class using Computed positioning. Declaration public ScrollView() ScrollView(Rect) Initializes a new instance of the ScrollView class using Absolute positioning. Declaration public ScrollView(Rect frame) Parameters Type Name Description Rect frame Properties AutoHideScrollBars If true the vertical/horizontal scroll bars won't be showed if it's not needed. Declaration public bool AutoHideScrollBars { get; set; } Property Value Type Description System.Boolean ContentOffset Represents the top left corner coordinate that is displayed by the scrollview Declaration public Point ContentOffset { get; set; } Property Value Type Description Point The content offset. ContentSize Represents the contents of the data shown inside the scrollview Declaration public Size ContentSize { get; set; } Property Value Type Description Size The size of the content. KeepContentAlwaysInViewport Get or sets if the view-port is kept always visible in the area of this ScrollView Declaration public bool KeepContentAlwaysInViewport { get; set; } Property Value Type Description System.Boolean ShowHorizontalScrollIndicator Gets or sets the visibility for the horizontal scroll indicator. Declaration public bool ShowHorizontalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show horizontal scroll indicator; otherwise, false . ShowVerticalScrollIndicator /// Gets or sets the visibility for the vertical scroll indicator. Declaration public bool ShowVerticalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show vertical scroll indicator; otherwise, false . Methods Add(View) Adds the view to the scrollview. Declaration public override void Add(View view) Parameters Type Name Description View view The view to add to the scrollview. Overrides View.Add(View) Dispose(Boolean) Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration protected override void Dispose(bool disposing) Parameters Type Name Description System.Boolean disposing Overrides View.Dispose(Boolean) Remarks If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed. MouseEvent(MouseEvent) Method invoked when a mouse event is generated Declaration public override bool MouseEvent(MouseEvent me) Parameters Type Name Description MouseEvent me Returns Type Description System.Boolean true , if the event was handled, false otherwise. Overrides Responder.MouseEvent(MouseEvent) PositionCursor() Positions the cursor in the right position based on the currently focused view in the chain. Declaration public override void PositionCursor() Overrides View.PositionCursor() ProcessKey(KeyEvent) If the view is focused, gives the view a chance to process the keystroke. Declaration public override bool ProcessKey(KeyEvent kb) Parameters Type Name Description KeyEvent kb Returns Type Description System.Boolean Overrides View.ProcessKey(KeyEvent) Remarks Views can override this method if they are interested in processing the given keystroke. If they consume the keystroke, they must return true to stop the keystroke from being processed by other widgets or consumed by the widget engine. If they return false, the keystroke will be passed using the ProcessColdKey method to other views to process. The View implementation does nothing but return false, so it is not necessary to call base.ProcessKey if you derive directly from View, but you should if you derive other View subclasses. Redraw(Rect) Redraws this view and its subviews; only redraws the views that have been flagged for a re-display. Declaration public override void Redraw(Rect region) Parameters Type Name Description Rect region Overrides View.Redraw(Rect) Remarks Always use Bounds (view-relative) when calling Redraw(Rect) , NOT Frame (superview-relative). Views should set the color that they want to use on entry, as otherwise this will inherit the last color that was set globally on the driver. Overrides of Redraw(Rect) must ensure they do not set Driver.Clip to a clip region larger than the region parameter. RemoveAll() Removes all widgets from this container. Declaration public override void RemoveAll() Overrides View.RemoveAll() Remarks ScrollDown(Int32) Scrolls the view down. Declaration public bool ScrollDown(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollLeft(Int32) Scrolls the view to the left Declaration public bool ScrollLeft(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollRight(Int32) Scrolls the view to the right. Declaration public bool ScrollRight(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if right was scrolled, false otherwise. ScrollUp(Int32) Scrolls the view up. Declaration public bool ScrollUp(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize" }, "api/Terminal.Gui/Terminal.Gui.ShortcutHelper.html": { "href": "api/Terminal.Gui/Terminal.Gui.ShortcutHelper.html", From 8bc5a3f5f3a455207137b306db098be5b61eba68 Mon Sep 17 00:00:00 2001 From: mrboring Date: Tue, 5 Jan 2021 09:56:41 +0000 Subject: [PATCH 25/70] Removed files from pull request --- docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html | 2 +- docs/index.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html b/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html index 5644581987..a49c193c42 100644 --- a/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html +++ b/docs/api/Terminal.Gui/Terminal.Gui.ScrollView.html @@ -515,7 +515,7 @@
Property Value

ContentSize

-Represents the contents of the data shown inside the scrollview +Represents the contents of the data shown inside the scrolview
Declaration
diff --git a/docs/index.json b/docs/index.json index 85104b0e53..4798b2c3fa 100644 --- a/docs/index.json +++ b/docs/index.json @@ -267,7 +267,7 @@ "api/Terminal.Gui/Terminal.Gui.ScrollView.html": { "href": "api/Terminal.Gui/Terminal.Gui.ScrollView.html", "title": "Class ScrollView", - "keywords": "Class ScrollView Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. Inheritance System.Object Responder View ScrollView Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize Inherited Members View.Added View.Removed View.Enter View.Leave View.MouseEnter View.MouseLeave View.MouseClick View.HotKey View.HotKeySpecifier View.Shortcut View.ShortcutTag View.ShortcutAction View.Data View.Driver View.Subviews View.TabIndexes View.TabIndex View.TabStop View.CanFocus View.Id View.IsCurrentTop View.WantMousePositionReports View.WantContinuousButtonPressed View.Frame View.LayoutStyle View.Bounds View.X View.Y View.Width View.Height View.SuperView View.SetNeedsDisplay() View.SetNeedsDisplay(Rect) View.ChildNeedsDisplay() View.Add(View[]) View.Remove(View) View.BringSubviewToFront(View) View.SendSubviewToBack(View) View.SendSubviewBackwards(View) View.BringSubviewForward(View) View.Clear() View.Clear(Rect) View.ScreenToView(Int32, Int32) View.ClipToBounds() View.SetClip(Rect) View.DrawFrame(Rect, Int32, Boolean) View.DrawHotString(ustring, Attribute, Attribute) View.DrawHotString(ustring, Boolean, ColorScheme) View.Move(Int32, Int32) View.HasFocus View.OnAdded(View) View.OnRemoved(View) View.OnEnter(View) View.OnLeave(View) View.Focused View.MostFocused View.ColorScheme View.AddRune(Int32, Int32, Rune) View.ClearNeedsDisplay() View.DrawContent View.OnDrawContent(Rect) View.SetFocus() View.KeyPress View.ProcessHotKey(KeyEvent) View.ProcessColdKey(KeyEvent) View.KeyDown View.OnKeyDown(KeyEvent) View.KeyUp View.OnKeyUp(KeyEvent) View.EnsureFocus() View.FocusFirst() View.FocusLast() View.FocusPrev() View.FocusNext() View.LayoutStarted View.LayoutComplete View.Initialized View.LayoutSubviews() View.Text View.AutoSize View.TextAlignment View.IsInitialized View.ToString() View.OnMouseEnter(MouseEvent) View.OnMouseLeave(MouseEvent) View.OnMouseEvent(MouseEvent) View.OnMouseClick(View.MouseEventArgs) View.BeginInit() View.EndInit() View.Visible Responder.Dispose() System.Object.Equals(System.Object) System.Object.Equals(System.Object, System.Object) System.Object.GetHashCode() System.Object.GetType() System.Object.MemberwiseClone() System.Object.ReferenceEquals(System.Object, System.Object) Namespace : Terminal.Gui Assembly : Terminal.Gui.dll Syntax public class ScrollView : View, IDisposable, ISupportInitializeNotification, ISupportInitialize Remarks The subviews that are added to this ScrollView are offset by the ContentOffset property. The view itself is a window into the space represented by the ContentSize . Use the Constructors ScrollView() Initializes a new instance of the ScrollView class using Computed positioning. Declaration public ScrollView() ScrollView(Rect) Initializes a new instance of the ScrollView class using Absolute positioning. Declaration public ScrollView(Rect frame) Parameters Type Name Description Rect frame Properties AutoHideScrollBars If true the vertical/horizontal scroll bars won't be showed if it's not needed. Declaration public bool AutoHideScrollBars { get; set; } Property Value Type Description System.Boolean ContentOffset Represents the top left corner coordinate that is displayed by the scrollview Declaration public Point ContentOffset { get; set; } Property Value Type Description Point The content offset. ContentSize Represents the contents of the data shown inside the scrollview Declaration public Size ContentSize { get; set; } Property Value Type Description Size The size of the content. KeepContentAlwaysInViewport Get or sets if the view-port is kept always visible in the area of this ScrollView Declaration public bool KeepContentAlwaysInViewport { get; set; } Property Value Type Description System.Boolean ShowHorizontalScrollIndicator Gets or sets the visibility for the horizontal scroll indicator. Declaration public bool ShowHorizontalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show horizontal scroll indicator; otherwise, false . ShowVerticalScrollIndicator /// Gets or sets the visibility for the vertical scroll indicator. Declaration public bool ShowVerticalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show vertical scroll indicator; otherwise, false . Methods Add(View) Adds the view to the scrollview. Declaration public override void Add(View view) Parameters Type Name Description View view The view to add to the scrollview. Overrides View.Add(View) Dispose(Boolean) Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration protected override void Dispose(bool disposing) Parameters Type Name Description System.Boolean disposing Overrides View.Dispose(Boolean) Remarks If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed. MouseEvent(MouseEvent) Method invoked when a mouse event is generated Declaration public override bool MouseEvent(MouseEvent me) Parameters Type Name Description MouseEvent me Returns Type Description System.Boolean true , if the event was handled, false otherwise. Overrides Responder.MouseEvent(MouseEvent) PositionCursor() Positions the cursor in the right position based on the currently focused view in the chain. Declaration public override void PositionCursor() Overrides View.PositionCursor() ProcessKey(KeyEvent) If the view is focused, gives the view a chance to process the keystroke. Declaration public override bool ProcessKey(KeyEvent kb) Parameters Type Name Description KeyEvent kb Returns Type Description System.Boolean Overrides View.ProcessKey(KeyEvent) Remarks Views can override this method if they are interested in processing the given keystroke. If they consume the keystroke, they must return true to stop the keystroke from being processed by other widgets or consumed by the widget engine. If they return false, the keystroke will be passed using the ProcessColdKey method to other views to process. The View implementation does nothing but return false, so it is not necessary to call base.ProcessKey if you derive directly from View, but you should if you derive other View subclasses. Redraw(Rect) Redraws this view and its subviews; only redraws the views that have been flagged for a re-display. Declaration public override void Redraw(Rect region) Parameters Type Name Description Rect region Overrides View.Redraw(Rect) Remarks Always use Bounds (view-relative) when calling Redraw(Rect) , NOT Frame (superview-relative). Views should set the color that they want to use on entry, as otherwise this will inherit the last color that was set globally on the driver. Overrides of Redraw(Rect) must ensure they do not set Driver.Clip to a clip region larger than the region parameter. RemoveAll() Removes all widgets from this container. Declaration public override void RemoveAll() Overrides View.RemoveAll() Remarks ScrollDown(Int32) Scrolls the view down. Declaration public bool ScrollDown(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollLeft(Int32) Scrolls the view to the left Declaration public bool ScrollLeft(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollRight(Int32) Scrolls the view to the right. Declaration public bool ScrollRight(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if right was scrolled, false otherwise. ScrollUp(Int32) Scrolls the view up. Declaration public bool ScrollUp(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize" + "keywords": "Class ScrollView Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. Inheritance System.Object Responder View ScrollView Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize Inherited Members View.Added View.Removed View.Enter View.Leave View.MouseEnter View.MouseLeave View.MouseClick View.HotKey View.HotKeySpecifier View.Shortcut View.ShortcutTag View.ShortcutAction View.Data View.Driver View.Subviews View.TabIndexes View.TabIndex View.TabStop View.CanFocus View.Id View.IsCurrentTop View.WantMousePositionReports View.WantContinuousButtonPressed View.Frame View.LayoutStyle View.Bounds View.X View.Y View.Width View.Height View.SuperView View.SetNeedsDisplay() View.SetNeedsDisplay(Rect) View.ChildNeedsDisplay() View.Add(View[]) View.Remove(View) View.BringSubviewToFront(View) View.SendSubviewToBack(View) View.SendSubviewBackwards(View) View.BringSubviewForward(View) View.Clear() View.Clear(Rect) View.ScreenToView(Int32, Int32) View.ClipToBounds() View.SetClip(Rect) View.DrawFrame(Rect, Int32, Boolean) View.DrawHotString(ustring, Attribute, Attribute) View.DrawHotString(ustring, Boolean, ColorScheme) View.Move(Int32, Int32) View.HasFocus View.OnAdded(View) View.OnRemoved(View) View.OnEnter(View) View.OnLeave(View) View.Focused View.MostFocused View.ColorScheme View.AddRune(Int32, Int32, Rune) View.ClearNeedsDisplay() View.DrawContent View.OnDrawContent(Rect) View.SetFocus() View.KeyPress View.ProcessHotKey(KeyEvent) View.ProcessColdKey(KeyEvent) View.KeyDown View.OnKeyDown(KeyEvent) View.KeyUp View.OnKeyUp(KeyEvent) View.EnsureFocus() View.FocusFirst() View.FocusLast() View.FocusPrev() View.FocusNext() View.LayoutStarted View.LayoutComplete View.Initialized View.LayoutSubviews() View.Text View.AutoSize View.TextAlignment View.IsInitialized View.ToString() View.OnMouseEnter(MouseEvent) View.OnMouseLeave(MouseEvent) View.OnMouseEvent(MouseEvent) View.OnMouseClick(View.MouseEventArgs) View.BeginInit() View.EndInit() View.Visible Responder.Dispose() System.Object.Equals(System.Object) System.Object.Equals(System.Object, System.Object) System.Object.GetHashCode() System.Object.GetType() System.Object.MemberwiseClone() System.Object.ReferenceEquals(System.Object, System.Object) Namespace : Terminal.Gui Assembly : Terminal.Gui.dll Syntax public class ScrollView : View, IDisposable, ISupportInitializeNotification, ISupportInitialize Remarks The subviews that are added to this ScrollView are offset by the ContentOffset property. The view itself is a window into the space represented by the ContentSize . Use the Constructors ScrollView() Initializes a new instance of the ScrollView class using Computed positioning. Declaration public ScrollView() ScrollView(Rect) Initializes a new instance of the ScrollView class using Absolute positioning. Declaration public ScrollView(Rect frame) Parameters Type Name Description Rect frame Properties AutoHideScrollBars If true the vertical/horizontal scroll bars won't be showed if it's not needed. Declaration public bool AutoHideScrollBars { get; set; } Property Value Type Description System.Boolean ContentOffset Represents the top left corner coordinate that is displayed by the scrollview Declaration public Point ContentOffset { get; set; } Property Value Type Description Point The content offset. ContentSize Represents the contents of the data shown inside the scrolview Declaration public Size ContentSize { get; set; } Property Value Type Description Size The size of the content. KeepContentAlwaysInViewport Get or sets if the view-port is kept always visible in the area of this ScrollView Declaration public bool KeepContentAlwaysInViewport { get; set; } Property Value Type Description System.Boolean ShowHorizontalScrollIndicator Gets or sets the visibility for the horizontal scroll indicator. Declaration public bool ShowHorizontalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show horizontal scroll indicator; otherwise, false . ShowVerticalScrollIndicator /// Gets or sets the visibility for the vertical scroll indicator. Declaration public bool ShowVerticalScrollIndicator { get; set; } Property Value Type Description System.Boolean true if show vertical scroll indicator; otherwise, false . Methods Add(View) Adds the view to the scrollview. Declaration public override void Add(View view) Parameters Type Name Description View view The view to add to the scrollview. Overrides View.Add(View) Dispose(Boolean) Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration protected override void Dispose(bool disposing) Parameters Type Name Description System.Boolean disposing Overrides View.Dispose(Boolean) Remarks If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed. MouseEvent(MouseEvent) Method invoked when a mouse event is generated Declaration public override bool MouseEvent(MouseEvent me) Parameters Type Name Description MouseEvent me Returns Type Description System.Boolean true , if the event was handled, false otherwise. Overrides Responder.MouseEvent(MouseEvent) PositionCursor() Positions the cursor in the right position based on the currently focused view in the chain. Declaration public override void PositionCursor() Overrides View.PositionCursor() ProcessKey(KeyEvent) If the view is focused, gives the view a chance to process the keystroke. Declaration public override bool ProcessKey(KeyEvent kb) Parameters Type Name Description KeyEvent kb Returns Type Description System.Boolean Overrides View.ProcessKey(KeyEvent) Remarks Views can override this method if they are interested in processing the given keystroke. If they consume the keystroke, they must return true to stop the keystroke from being processed by other widgets or consumed by the widget engine. If they return false, the keystroke will be passed using the ProcessColdKey method to other views to process. The View implementation does nothing but return false, so it is not necessary to call base.ProcessKey if you derive directly from View, but you should if you derive other View subclasses. Redraw(Rect) Redraws this view and its subviews; only redraws the views that have been flagged for a re-display. Declaration public override void Redraw(Rect region) Parameters Type Name Description Rect region Overrides View.Redraw(Rect) Remarks Always use Bounds (view-relative) when calling Redraw(Rect) , NOT Frame (superview-relative). Views should set the color that they want to use on entry, as otherwise this will inherit the last color that was set globally on the driver. Overrides of Redraw(Rect) must ensure they do not set Driver.Clip to a clip region larger than the region parameter. RemoveAll() Removes all widgets from this container. Declaration public override void RemoveAll() Overrides View.RemoveAll() Remarks ScrollDown(Int32) Scrolls the view down. Declaration public bool ScrollDown(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollLeft(Int32) Scrolls the view to the left Declaration public bool ScrollLeft(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. ScrollRight(Int32) Scrolls the view to the right. Declaration public bool ScrollRight(int cols) Parameters Type Name Description System.Int32 cols Number of columns to scroll by. Returns Type Description System.Boolean true , if right was scrolled, false otherwise. ScrollUp(Int32) Scrolls the view up. Declaration public bool ScrollUp(int lines) Parameters Type Name Description System.Int32 lines Number of lines to scroll. Returns Type Description System.Boolean true , if left was scrolled, false otherwise. Implements System.IDisposable System.ComponentModel.ISupportInitializeNotification System.ComponentModel.ISupportInitialize" }, "api/Terminal.Gui/Terminal.Gui.ShortcutHelper.html": { "href": "api/Terminal.Gui/Terminal.Gui.ShortcutHelper.html", From 6d86e7f294cb770053d064ae415f5b6d159fc2d8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 6 Jan 2021 11:27:05 +0000 Subject: [PATCH 26/70] Fixes #1071. Toplevel.GetTopLevelSubviews (). Changed from HashSet to IList. --- Terminal.Gui/Core/Toplevel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 19b133ef98..a779accc6e 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -278,7 +278,7 @@ IEnumerable GetToplevelSubviews (bool isForward) return null; } - HashSet views = new HashSet (); + IList views = new List (); foreach (var v in SuperView.Subviews) { views.Add (v); From 950c7dd19ab198a3872548c847a37d04d9398759 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 7 Jan 2021 17:37:54 +0000 Subject: [PATCH 27/70] Adding unit test. --- UnitTests/ViewTests.cs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index dfb45df8bb..35712be123 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -6,7 +6,7 @@ using Terminal.Gui; using Xunit; -// Alais Console to MockConsole so we don't accidentally use Console +// Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; namespace Terminal.Gui { @@ -27,9 +27,9 @@ public void New_Initializes () Assert.Null (r.ColorScheme); Assert.Equal (Dim.Sized (0), r.Width); Assert.Equal (Dim.Sized (0), r.Height); - // BUGBUG: Pos needs eqality implemented - //Assert.Equal (Pos.At (0), r.X); - //Assert.Equal (Pos.At (0), r.Y); + // FIXED: Pos needs equality implemented + Assert.Equal (Pos.At (0), r.X); + Assert.Equal (Pos.At (0), r.Y); Assert.False (r.IsCurrentTop); Assert.Empty (r.Id); Assert.Empty (r.Subviews); @@ -1077,5 +1077,32 @@ public void View_Difference_Between_An_Object_Initializer_And_A_Constructor () Assert.True (view.Frame.IsEmpty); Assert.True (view.Bounds.IsEmpty); } + + [Fact] + public void GetToplevelSubviews_Ensure_Order_List () + { + var top = new Toplevel (); + + for (int i = 0; i < 6; i++) { + var view = new View ($"View{i}"); + top.Add (view); + } + + IList views = new List (); + + var idx = 0; + foreach (var v in top.Subviews) { + views.Add (v); + Assert.Equal ($"View{idx}", v.Text); + idx++; + } + + idx = top.Subviews.Count - 1; + foreach (var v in top.Subviews.Reverse ()) { + views.Add (v); + Assert.Equal ($"View{idx}", v.Text); + idx--; + } + } } } From ea8d84f185c6ba341dd41c259215d16c661df10c Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 8 Jan 2021 19:53:04 +0000 Subject: [PATCH 28/70] Fixing the FocusNearestView method. --- Terminal.Gui/Core/Toplevel.cs | 37 ++++++++--------- UICatalog/Scenarios/WindowsAndFrameViews.cs | 2 +- UnitTests/ViewTests.cs | 45 ++++++++++++--------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index a779accc6e..1699cd9d71 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -217,11 +217,11 @@ public override bool ProcessKey (KeyEvent keyEvent) var old = GetDeepestFocusedSubview (Focused); if (!FocusNext ()) FocusNext (); - if (old != Focused) { + if (old != Focused && old != Focused?.Focused) { old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { - FocusNearestView (GetToplevelSubviews (true)); + FocusNearestView (SuperView?.Subviews, Direction.Forward); } return true; case Key.CursorLeft: @@ -230,11 +230,11 @@ public override bool ProcessKey (KeyEvent keyEvent) old = GetDeepestFocusedSubview (Focused); if (!FocusPrev ()) FocusPrev (); - if (old != Focused) { + if (old != Focused && old != Focused?.Focused) { old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { - FocusNearestView (GetToplevelSubviews (false)); + FocusNearestView (SuperView?.Subviews?.Reverse(), Direction.Backward); } return true; @@ -272,39 +272,34 @@ View GetDeepestFocusedSubview (View view) return view; } - IEnumerable GetToplevelSubviews (bool isForward) - { - if (SuperView == null) { - return null; - } - - IList views = new List (); - - foreach (var v in SuperView.Subviews) { - views.Add (v); - } - - return isForward ? views : views.Reverse (); - } - - void FocusNearestView (IEnumerable views) + void FocusNearestView (IEnumerable views, Direction direction) { if (views == null) { return; } bool found = false; + bool focusProcessed = false; + int idx = 0; foreach (var v in views) { if (v == this) { found = true; } if (found && v != this) { - v.EnsureFocus (); + if (direction == Direction.Forward) { + SuperView?.FocusNext (); + } else { + SuperView?.FocusPrev (); + } + focusProcessed = true; if (SuperView.Focused != null && SuperView.Focused != this) { return; } + } else if (found && !focusProcessed && idx == views.Count () - 1) { + views.ToList () [0].SetFocus (); } + idx++; } } diff --git a/UICatalog/Scenarios/WindowsAndFrameViews.cs b/UICatalog/Scenarios/WindowsAndFrameViews.cs index 0d7f108c3c..df590328ca 100644 --- a/UICatalog/Scenarios/WindowsAndFrameViews.cs +++ b/UICatalog/Scenarios/WindowsAndFrameViews.cs @@ -108,7 +108,7 @@ static int About () var frameView = new FrameView ("This is a Sub-FrameView") { X = Pos.Percent (50), Y = 1, - Width = Dim.Percent (100), + Width = Dim.Percent (100, true), // Or Dim.Percent (50) Height = 5, ColorScheme = Colors.Base, Text = "The Text in the FrameView", diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index 35712be123..b8cc950bd4 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -1079,30 +1079,35 @@ public void View_Difference_Between_An_Object_Initializer_And_A_Constructor () } [Fact] - public void GetToplevelSubviews_Ensure_Order_List () + public void FocusNearestView_Ensure_Focus_Ordered () { var top = new Toplevel (); - for (int i = 0; i < 6; i++) { - var view = new View ($"View{i}"); - top.Add (view); - } - - IList views = new List (); - - var idx = 0; - foreach (var v in top.Subviews) { - views.Add (v); - Assert.Equal ($"View{idx}", v.Text); - idx++; - } + var win = new Window (); + var winSubview = new View ("WindowSubview") { + CanFocus = true + }; + win.Add (winSubview); + top.Add (win); - idx = top.Subviews.Count - 1; - foreach (var v in top.Subviews.Reverse ()) { - views.Add (v); - Assert.Equal ($"View{idx}", v.Text); - idx--; - } + var frm = new FrameView (); + var frmSubview = new View ("FrameSubview") { + CanFocus = true + }; + frm.Add (frmSubview); + top.Add (frm); + + top.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())); + Assert.Equal ($"WindowSubview", top.MostFocused.Text); + top.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())); + Assert.Equal ("FrameSubview", top.MostFocused.Text); + top.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())); + Assert.Equal ($"WindowSubview", top.MostFocused.Text); + + top.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ())); + Assert.Equal ("FrameSubview", top.MostFocused.Text); + top.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ())); + Assert.Equal ($"WindowSubview", top.MostFocused.Text); } } } From 3dd17bd82cf78cb607867bbca8a562abaef89561 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 8 Jan 2021 20:35:47 +0000 Subject: [PATCH 29/70] Added feature to navigate through all Application.Top's subviews. --- Terminal.Gui/Core/Toplevel.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 1699cd9d71..c83eee343d 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -195,7 +195,7 @@ public override bool ProcessKey (KeyEvent keyEvent) if (base.ProcessKey (keyEvent)) return true; - switch (keyEvent.Key) { + switch (ShortcutHelper.GetModifiersKey (keyEvent)) { case Key.Q | Key.CtrlMask: // FIXED: stop current execution of this container Application.RequestStop (); @@ -224,9 +224,9 @@ public override bool ProcessKey (KeyEvent keyEvent) FocusNearestView (SuperView?.Subviews, Direction.Forward); } return true; + case Key.BackTab | Key.ShiftMask: case Key.CursorLeft: case Key.CursorUp: - case Key.BackTab: old = GetDeepestFocusedSubview (Focused); if (!FocusPrev ()) FocusPrev (); @@ -237,7 +237,12 @@ public override bool ProcessKey (KeyEvent keyEvent) FocusNearestView (SuperView?.Subviews?.Reverse(), Direction.Backward); } return true; - + case Key.Tab | Key.CtrlMask: + Application.Top.FocusNext (); + return true; + case Key.Tab | Key.ShiftMask | Key.CtrlMask: + Application.Top.FocusPrev (); + return true; case Key.L | Key.CtrlMask: Application.Refresh (); return true; From b3850f7f61e40e43a005a90382ad1895685dabd8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 8 Jan 2021 21:33:07 +0000 Subject: [PATCH 30/70] Fixing FakeDriver and unit test. --- Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs | 1 + UnitTests/ViewTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 8a443133ec..d79f5007b5 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -391,6 +391,7 @@ public override void PrepareToRun (MainLoop mainLoop, Action keyHandle keyHandler (new KeyEvent (map, keyModifiers)); keyUpHandler (new KeyEvent (map, keyModifiers)); + keyModifiers = new KeyModifiers (); }; } diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index b8cc950bd4..990e3f9c96 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -1104,9 +1104,9 @@ public void FocusNearestView_Ensure_Focus_Ordered () top.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())); Assert.Equal ($"WindowSubview", top.MostFocused.Text); - top.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ())); + top.ProcessKey (new KeyEvent (Key.BackTab | Key.ShiftMask, new KeyModifiers ())); Assert.Equal ("FrameSubview", top.MostFocused.Text); - top.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ())); + top.ProcessKey (new KeyEvent (Key.BackTab | Key.ShiftMask, new KeyModifiers ())); Assert.Equal ($"WindowSubview", top.MostFocused.Text); } } From 845b9400788819781144088905639e339c3389c1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 8 Jan 2021 22:44:03 +0000 Subject: [PATCH 31/70] Fixes shift on Unix. --- Terminal.Gui/Core/Event.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Core/Event.cs b/Terminal.Gui/Core/Event.cs index 04ae3b961b..83ccffe618 100644 --- a/Terminal.Gui/Core/Event.cs +++ b/Terminal.Gui/Core/Event.cs @@ -392,7 +392,7 @@ public class KeyEvent { /// Gets a value indicating whether the Shift key was pressed. ///
/// true if is shift; otherwise, false. - public bool IsShift => keyModifiers.Shift; + public bool IsShift => keyModifiers.Shift || Key == Key.BackTab; /// /// Gets a value indicating whether the Alt key was pressed (real or synthesized) From 61e87e84b5205675b82275cd2b295c1fccb1a127 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Jan 2021 15:08:07 +0000 Subject: [PATCH 32/70] Fixes #1073, #1058, #480 #1049. Provides more automation to the ScrollBarView, allowing easily implement on any view. --- Terminal.Gui/Core/View.cs | 3 +- Terminal.Gui/Views/ListView.cs | 41 +- Terminal.Gui/Views/ScrollBarView.cs | 538 +++++++++++++++++++ Terminal.Gui/Views/ScrollView.cs | 376 +------------ Terminal.Gui/Views/TextView.cs | 26 +- UICatalog/Scenarios/Editor.cs | 51 ++ UICatalog/Scenarios/ListViewWithSelection.cs | 31 +- 7 files changed, 690 insertions(+), 376 deletions(-) create mode 100644 Terminal.Gui/Views/ScrollBarView.cs diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 0e7bd5bb6e..b4ccd5f0d1 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -741,7 +741,7 @@ public void SetNeedsDisplay (Rect region) public void SetChildNeedsDisplay () { ChildNeedsDisplay = true; - if (container != null) + if (container != null && !container.ChildNeedsDisplay) container.SetChildNeedsDisplay (); } @@ -1342,6 +1342,7 @@ public virtual void Redraw (Rect bounds) // Draw the subview // Use the view's bounds (view-relative; Location will always be (0,0) if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { + view.OnDrawContent (view.Bounds); view.Redraw (view.Bounds); } } diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 84404d71e0..41b1ae8562 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -298,11 +298,6 @@ public override void Redraw (Rect bounds) Driver.SetAttribute (current); Move (0, 0); var f = Frame; - if (selected < top) { - top = selected; - } else if (selected >= top + f.Height) { - top = selected; - } var item = top; bool focused = HasFocus; int col = allowsMarking ? 2 : 0; @@ -477,8 +472,11 @@ public virtual bool MoveDown () } else if (selected + 1 < source.Count) { //can move by down by one. selected++; - if (selected >= top + Frame.Height) + if (selected >= top + Frame.Height) { top++; + } else if (selected < top) { + top = selected; + } OnSelectedChanged (); SetNeedsDisplay (); } else if (selected == 0) { @@ -511,8 +509,11 @@ public virtual bool MoveUp () if (selected > Source.Count) { selected = Source.Count - 1; } - if (selected < top) + if (selected < top) { top = selected; + } else if (selected > top + Frame.Height) { + top = Math.Max (selected - Frame.Height + 1, 0); + } OnSelectedChanged (); SetNeedsDisplay (); } @@ -551,6 +552,26 @@ public virtual bool MoveHome () return true; } + /// + /// Scrolls the view down. + /// + /// Number of lines to scroll down. + public virtual void ScrollDown (int lines) + { + top = Math.Min (top + lines, source.Count - 1); + SetNeedsDisplay (); + } + + /// + /// Scrolls the view up. + /// + /// Number of lines to scroll up. + public virtual void ScrollUp (int lines) + { + top = Math.Max (top - lines, 0); + SetNeedsDisplay (); + } + int lastSelectedItem = -1; private bool allowsMultipleSelection = true; @@ -630,10 +651,10 @@ public override bool MouseEvent (MouseEvent me) } if (me.Flags == MouseFlags.WheeledDown) { - MoveDown (); + ScrollDown (1); return true; } else if (me.Flags == MouseFlags.WheeledUp) { - MoveUp (); + ScrollUp (1); return true; } @@ -655,6 +676,8 @@ public override bool MouseEvent (MouseEvent me) return true; } + + } /// diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs new file mode 100644 index 0000000000..985d31ef40 --- /dev/null +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -0,0 +1,538 @@ +// +// ScrollBarView.cs: ScrollBarView view. +// +// Authors: +// Miguel de Icaza (miguel@gnome.org) +// + +using System; + +namespace Terminal.Gui { + /// + /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical + /// + /// + /// + /// The scrollbar is drawn to be a representation of the Size, assuming that the + /// scroll position is set at Position. + /// + /// + /// If the region to display the scrollbar is larger than three characters, + /// arrow indicators are drawn. + /// + /// + public class ScrollBarView : View { + bool vertical; + int size, position, contentOffset; + bool showScrollIndicator; + bool keepContentAlwaysInViewport = true; + bool autoHideScrollBars = true; + Dim originalHostWidth, originalHostHeight; + bool hosted; + bool showBothScrollIndicator => OtherScrollBarView != null && OtherScrollBarView.showScrollIndicator; + + /// + /// Initializes a new instance of the class using layout. + /// + /// Frame for the scrollbar. + public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { } + + /// + /// Initializes a new instance of the class using layout. + /// + /// Frame for the scrollbar. + /// The size that this scrollbar represents. Sets the property. + /// The position within this scrollbar. Sets the property. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the property. + public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) + { + Init (size, position, isVertical); + } + + /// + /// Initializes a new instance of the class using layout. + /// + public ScrollBarView () : this (0, 0, false) { } + + /// + /// Initializes a new instance of the class using layout. + /// + /// The size that this scrollbar represents. + /// The position within this scrollbar. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + public ScrollBarView (int size, int position, bool isVertical) : base () + { + Init (size, position, isVertical); + } + + /// + /// Initializes a new instance of the class using layout. + /// + public ScrollBarView (View host, bool isVertical) : this (0, 0, isVertical) + { + hosted = true; + originalHostWidth = host.Width; + originalHostHeight = host.Height; + X = isVertical ? Pos.Right(host) : Pos.Left (host); + Y = isVertical ? Pos.Top (host) : Pos.Bottom (host); + Host = host; + Host.SuperView.Add (this); + ShowScrollIndicator = true; + AutoHideScrollBars = true; + } + + void Init (int size, int position, bool isVertical) + { + vertical = isVertical; + this.position = position; + this.size = size; + WantContinuousButtonPressed = true; + } + + /// + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + /// + public bool IsVertical { + get => vertical; + set { + vertical = value; + SetNeedsDisplay (); + } + } + + /// + /// The size of content the scrollbar represents. + /// + /// The size. + /// The is typically the size of the virtual content. E.g. when a Scrollbar is + /// part of a the Size is set to the appropriate dimension of . + public int Size { + get => size; + set { + size = value; + ShowHideScrollBars (); + SetNeedsDisplay (); + } + } + + /// + /// Represents the top left/top corner coordinate that is displayed by the scrollbar. + /// + /// The content offset. + public int ContentOffset { + get { + return contentOffset; + } + set { + var co = -Math.Abs (value); + if (contentOffset != co) { + if (CanScroll (contentOffset - co, out int max, vertical)) { + if (max == contentOffset - co) { + contentOffset = co; + } else { + contentOffset = co + max; + } + } + Position = Math.Max (0, -contentOffset); + OnChangedPosition (); + SetNeedsDisplay (); + } + } + } + + /// + /// This event is raised when the position on the scrollbar has changed. + /// + public event Action ChangedPosition; + + /// + /// The position, relative to , to set the scrollbar at. + /// + /// The position. + public int Position { + get => position; + set { + position = value; + SetNeedsDisplay (); + } + } + + /// + /// Get or sets the view that host this + /// + public View Host { get; internal set; } + + /// + /// Represent a vertical or horizontal ScrollBarView other than this. + /// + public ScrollBarView OtherScrollBarView { get; set; } + + /// + /// Gets or sets the visibility for the vertical or horizontal scroll indicator. + /// + /// true if show vertical or horizontal scroll indicator; otherwise, false. + public bool ShowScrollIndicator { + get => showScrollIndicator; + set { + if (value == showScrollIndicator) { + return; + } + + showScrollIndicator = value; + SetNeedsLayout (); + if (value) { + Visible = true; + } else { + Visible = false; + } + Width = vertical ? 1 : Dim.Width (Host); + Height = vertical ? Dim.Height (Host) : 1; + if (vertical) { + Host.Width = showScrollIndicator ? originalHostWidth - 1 : originalHostWidth; + } else { + Host.Height = showScrollIndicator ? originalHostHeight - 1 : originalHostHeight; + } + } + } + + /// + /// Get or sets if the view-port is kept always visible in the area of this + /// + public bool KeepContentAlwaysInViewport { + get { return keepContentAlwaysInViewport; } + set { + if (keepContentAlwaysInViewport != value) { + keepContentAlwaysInViewport = value; + int co = 0; + if (value && !vertical && -contentOffset + Host.Bounds.Width > size) { + co = size - Host.Bounds.Width + (showBothScrollIndicator ? 1 : 0); + } + if (value && vertical && -contentOffset + Host.Bounds.Height > size) { + co = size - Host.Bounds.Height + (showBothScrollIndicator ? 1 : 0); + } + if (co != 0) { + ContentOffset = co; + } + if (OtherScrollBarView != null && OtherScrollBarView.keepContentAlwaysInViewport != value) { + OtherScrollBarView.KeepContentAlwaysInViewport = value; + } + } + } + } + + /// + /// If true the vertical/horizontal scroll bars won't be showed if it's not needed. + /// + public bool AutoHideScrollBars { + get => autoHideScrollBars; + set { + if (autoHideScrollBars != value) { + autoHideScrollBars = value; + SetNeedsDisplay (); + } + } + } + + void SetPosition (int newPos) + { + Position = newPos; + ContentOffset = Position; + OnChangedPosition (); + } + + /// + /// Virtual method to invoke the action event. + /// + public virtual void OnChangedPosition () + { + ChangedPosition?.Invoke (); + } + + internal bool pending; + + void ShowHideScrollBars () + { + if (!hosted || !autoHideScrollBars) { + return; + } + + if (vertical) { + if (Host.Bounds.Height == 0 || Host.Bounds.Height > size) { + if (showScrollIndicator) { + ShowScrollIndicator = false; + } + } else if (Host.Bounds.Height > 0 && Host.Bounds.Height == size) { + pending = true; + } else if (!showScrollIndicator) { + ShowScrollIndicator = true; + } + } else { + if (Host.Bounds.Width == 0 || Host.Bounds.Width > size) { + if (showScrollIndicator) { + ShowScrollIndicator = false; + } + } else if (Host.Bounds.Width > 0 && Host.Bounds.Width == size && OtherScrollBarView.pending) { + if (showScrollIndicator) { + ShowScrollIndicator = false; + } + if (showBothScrollIndicator) { + OtherScrollBarView.showScrollIndicator = false; + } + } else { + if (OtherScrollBarView.pending) { + if (!showBothScrollIndicator) { + OtherScrollBarView.showScrollIndicator = true; + } + } + if (!showScrollIndicator) { + ShowScrollIndicator = true; + } + } + } + } + + int posTopTee; + int posLeftTee; + int posBottomTee; + int posRightTee; + + /// + public override void Redraw (Rect region) + { + if (ColorScheme == null || Size == 0) { + return; + } + + Driver.SetAttribute (ColorScheme.Normal); + + if ((vertical && Bounds.Height == 0) || (!vertical && Bounds.Width == 0)) { + return; + } + + if (vertical) { + if (region.Right < Bounds.Width - 1) { + return; + } + + var col = Bounds.Width - 1; + var bh = Bounds.Height; + Rune special; + + if (bh < 4) { + var by1 = position * bh / Size; + var by2 = (position + bh) * bh / Size; + + Move (col, 0); + if (Bounds.Height == 1) { + Driver.AddRune (Driver.Diamond); + } else { + Driver.AddRune (Driver.UpArrow); + } + if (Bounds.Height == 3) { + Move (col, 1); + Driver.AddRune (Driver.Diamond); + } + if (Bounds.Height > 1) { + Move (col, Bounds.Height - 1); + Driver.AddRune (Driver.DownArrow); + } + } else { + bh -= 2; + var by1 = position * bh / Size; + var by2 = KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / Size; + if (KeepContentAlwaysInViewport && by1 == by2) { + by1 = Math.Max (by1 - 1, 0); + } + + Move (col, 0); + Driver.AddRune (Driver.UpArrow); + Move (col, Bounds.Height - 1); + Driver.AddRune (Driver.DownArrow); + + bool hasTopTee = false; + bool hasDiamond = false; + bool hasBottomTee = false; + for (int y = 0; y < bh; y++) { + Move (col, y + 1); + if ((y < by1 || y > by2) && ((position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) { + special = Driver.Stipple; + } else { + if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) { + hasDiamond = true; + special = Driver.Diamond; + } else { + if (y == by1 && !hasTopTee) { + hasTopTee = true; + posTopTee = y; + special = Driver.TopTee; + } else if ((position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) { + hasBottomTee = true; + posBottomTee = y; + special = Driver.BottomTee; + } else { + special = Driver.VLine; + } + } + } + Driver.AddRune (special); + } + if (!hasTopTee) { + Move (col, Bounds.Height - 2); + Driver.AddRune (Driver.TopTee); + } + } + } else { + if (region.Bottom < Bounds.Height - 1) { + return; + } + + var row = Bounds.Height - 1; + var bw = Bounds.Width; + Rune special; + + if (bw < 4) { + var bx1 = position * bw / Size; + var bx2 = (position + bw) * bw / Size; + + Move (0, row); + Driver.AddRune (Driver.LeftArrow); + Driver.AddRune (Driver.RightArrow); + } else { + bw -= 2; + var bx1 = position * bw / Size; + var bx2 = KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / Size; + if (KeepContentAlwaysInViewport && bx1 == bx2) { + bx1 = Math.Max (bx1 - 1, 0); + } + + Move (0, row); + Driver.AddRune (Driver.LeftArrow); + + bool hasLeftTee = false; + bool hasDiamond = false; + bool hasRightTee = false; + for (int x = 0; x < bw; x++) { + if ((x < bx1 || x >= bx2 + 1) && ((position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) { + special = Driver.Stipple; + } else { + if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) { + hasDiamond = true; + special = Driver.Diamond; + } else { + if (x == bx1 && !hasLeftTee) { + hasLeftTee = true; + posLeftTee = x; + special = Driver.LeftTee; + } else if ((position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) { + hasRightTee = true; + posRightTee = x; + special = Driver.RightTee; + } else { + special = Driver.HLine; + } + } + } + Driver.AddRune (special); + } + if (!hasLeftTee) { + Move (Bounds.Width -2, row); + Driver.AddRune (Driver.LeftTee); + } + + Driver.AddRune (Driver.RightArrow); + } + } + } + + int lastLocation = -1; + + /// + public override bool MouseEvent (MouseEvent me) + { + if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && + !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + return false; + } + + if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + lastLocation = -1; + } + + int location = vertical ? me.Y : me.X; + int barsize = vertical ? Bounds.Height : Bounds.Width; + int posTopLeftTee = vertical ? posTopTee : posLeftTee; + int posBottomRightTee = vertical ? posBottomTee : posRightTee; + + barsize -= 2; + var pos = Position; + if (location == 0) { + if (pos > 0) { + SetPosition (pos - 1); + } + } else if (location == barsize + 1) { + if (CanScroll (1, out _, vertical)) { + SetPosition (pos + 1); + } + } else if (location > 0 && location < barsize + 1) { + var b1 = pos * barsize / Size; + var b2 = KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size; + if (KeepContentAlwaysInViewport && b1 == b2) { + b1 = Math.Max (b1 - 1, 0); + } + + if (location > b1 && location <= b2 + 1) { + if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { + if (location == 1) { + SetPosition (0); + } else if (location == barsize) { + CanScroll (Size - pos, out int nv, vertical); + if (nv > 0) { + SetPosition (Math.Min (pos + nv, Size)); + } + } + } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + var mb = (b2 - b1) / 2; + var ml = mb + b1 + (mb == 0 ? 1 : 0); + if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) { + lastLocation = location; + var np = b1 * Size / barsize; + SetPosition (np); + } else if (location > lastLocation) { + var np = location * Size / barsize; + CanScroll (np - pos, out int nv, vertical); + if (nv > 0) { + SetPosition (pos + nv); + } + } + } + } else { + if (location >= b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) { + CanScroll (location, out int nv, vertical); + if (nv > 0) { + SetPosition (Math.Min (pos + nv, Size)); + } + } else if (location <= b1) { + SetPosition (Math.Max (pos - barsize - location, 0)); + } + } + } + + return true; + } + + internal bool CanScroll (int n, out int max, bool isVertical = false) + { + var size = isVertical ? + (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) : + (KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0); + var cSize = -Size; + var cOffSet = contentOffset; + var newSize = Math.Max (cSize, cOffSet - n); + max = cSize < newSize - size ? n : -cSize + (cOffSet - size) - 1; + if (cSize < newSize - size) { + return true; + } + return false; + } + } +} diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 6e441b16cd..0a5b139bc2 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -1,5 +1,5 @@ // -// ScrollView.cs: ScrollView and ScrollBarView views. +// ScrollView.cs: ScrollView view. // // Authors: // Miguel de Icaza (miguel@gnome.org) @@ -15,345 +15,6 @@ using System.Reflection; namespace Terminal.Gui { - /// - /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical - /// - /// - /// - /// The scrollbar is drawn to be a representation of the Size, assuming that the - /// scroll position is set at Position. - /// - /// - /// If the region to display the scrollbar is larger than three characters, - /// arrow indicators are drawn. - /// - /// - public class ScrollBarView : View { - bool vertical = false; - int size = 0, position = 0; - - /// - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - /// - public bool IsVertical { - get => vertical; - set { - vertical = value; - SetNeedsDisplay (); - } - } - - /// - /// The size of content the scrollbar represents. - /// - /// The size. - /// The is typically the size of the virtual content. E.g. when a Scrollbar is - /// part of a the Size is set to the appropriate dimension of . - public int Size { - get => size; - set { - size = value; - SetNeedsDisplay (); - } - } - - /// - /// This event is raised when the position on the scrollbar has changed. - /// - public event Action ChangedPosition; - - /// - /// The position, relative to , to set the scrollbar at. - /// - /// The position. - public int Position { - get => position; - set { - position = value; - SetNeedsDisplay (); - } - } - - /// - /// Get or sets the view that host this - /// - public ScrollView Host { get; internal set; } - - void SetPosition (int newPos) - { - Position = newPos; - ChangedPosition?.Invoke (); - } - - /// - /// Initializes a new instance of the class using layout. - /// - /// Frame for the scrollbar. - public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { } - - /// - /// Initializes a new instance of the class using layout. - /// - /// Frame for the scrollbar. - /// The size that this scrollbar represents. Sets the property. - /// The position within this scrollbar. Sets the property. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the property. - public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) - { - Init (size, position, isVertical); - } - - /// - /// Initializes a new instance of the class using layout. - /// - public ScrollBarView () : this (0, 0, false) { } - - /// - /// Initializes a new instance of the class using layout. - /// - /// The size that this scrollbar represents. - /// The position within this scrollbar. - /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. - public ScrollBarView (int size, int position, bool isVertical) : base () - { - Init (size, position, isVertical); - } - - void Init (int size, int position, bool isVertical) - { - vertical = isVertical; - this.position = position; - this.size = size; - WantContinuousButtonPressed = true; - } - - int posTopTee; - int posLeftTee; - int posBottomTee; - int posRightTee; - - /// - public override void Redraw (Rect region) - { - if (ColorScheme == null || Size == 0) - return; - - Driver.SetAttribute (ColorScheme.Normal); - - if (Bounds.Height == 0) { - return; - } - - if (vertical) { - if (region.Right < Bounds.Width - 1) - return; - - var col = Bounds.Width - 1; - var bh = Bounds.Height; - Rune special; - - if (bh < 4) { - var by1 = position * bh / Size; - var by2 = (position + bh) * bh / Size; - - Move (col, 0); - if (Bounds.Height == 1) { - Driver.AddRune (Driver.Diamond); - } else { - Driver.AddRune (Driver.UpArrow); - } - if (Bounds.Height == 3) { - Move (col, 1); - Driver.AddRune (Driver.Diamond); - } - if (Bounds.Height > 1) { - Move (col, Bounds.Height - 1); - Driver.AddRune (Driver.DownArrow); - } - } else { - bh -= 2; - var by1 = position * bh / Size; - var by2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / Size; - if (Host.KeepContentAlwaysInViewport && by1 == by2) { - by1 = Math.Max (by1 - 1, 0); - } - - Move (col, 0); - Driver.AddRune (Driver.UpArrow); - Move (col, Bounds.Height - 1); - Driver.AddRune (Driver.DownArrow); - - bool hasTopTee = false; - bool hasDiamond = false; - bool hasBottomTee = false; - for (int y = 0; y < bh; y++) { - Move (col, y + 1); - if ((y < by1 || y > by2) && ((position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) { - special = Driver.Stipple; - } else { - if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) { - hasDiamond = true; - special = Driver.Diamond; - } else { - if (y == by1 && !hasTopTee) { - hasTopTee = true; - posTopTee = y; - special = Driver.TopTee; - } else if ((position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) { - hasBottomTee = true; - posBottomTee = y; - special = Driver.BottomTee; - } else { - special = Driver.VLine; - } - } - } - Driver.AddRune (special); - } - if (!hasTopTee) { - Move (col, Bounds.Height - 2); - Driver.AddRune (Driver.TopTee); - } - } - } else { - if (region.Bottom < Bounds.Height - 1) - return; - - var row = Bounds.Height - 1; - var bw = Bounds.Width; - Rune special; - - if (bw < 4) { - var bx1 = position * bw / Size; - var bx2 = (position + bw) * bw / Size; - - Move (0, row); - Driver.AddRune (Driver.LeftArrow); - Driver.AddRune (Driver.RightArrow); - } else { - bw -= 2; - var bx1 = position * bw / Size; - var bx2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / Size; - if (Host.KeepContentAlwaysInViewport && bx1 == bx2) { - bx1 = Math.Max (bx1 - 1, 0); - } - - Move (0, row); - Driver.AddRune (Driver.LeftArrow); - - bool hasLeftTee = false; - bool hasDiamond = false; - bool hasRightTee = false; - for (int x = 0; x < bw; x++) { - if ((x < bx1 || x >= bx2 + 1) && ((position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) { - special = Driver.Stipple; - } else { - if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) { - hasDiamond = true; - special = Driver.Diamond; - } else { - if (x == bx1 && !hasLeftTee) { - hasLeftTee = true; - posLeftTee = x; - special = Driver.LeftTee; - } else if ((position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) { - hasRightTee = true; - posRightTee = x; - special = Driver.RightTee; - } else { - special = Driver.HLine; - } - } - } - Driver.AddRune (special); - } - if (!hasLeftTee) { - Move (Bounds.Width -2, row); - Driver.AddRune (Driver.LeftTee); - } - - Driver.AddRune (Driver.RightArrow); - } - } - } - - int lastLocation = -1; - - /// - public override bool MouseEvent (MouseEvent me) - { - if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && - !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - return false; - } - - if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - lastLocation = -1; - } - - int location = vertical ? me.Y : me.X; - int barsize = vertical ? Bounds.Height : Bounds.Width; - int posTopLeftTee = vertical ? posTopTee : posLeftTee; - int posBottomRightTee = vertical ? posBottomTee : posRightTee; - - barsize -= 2; - var pos = Position; - if (location == 0) { - if (pos > 0) { - SetPosition (pos - 1); - } - } else if (location == barsize + 1) { - if (Host.CanScroll (1, out _, vertical)) { - SetPosition (pos + 1); - } - } else if (location > 0 && location < barsize + 1) { - var b1 = pos * barsize / Size; - var b2 = Host.KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size; - if (Host.KeepContentAlwaysInViewport && b1 == b2) { - b1 = Math.Max (b1 - 1, 0); - } - - if (location > b1 && location <= b2 + 1) { - if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { - if (location == 1) { - SetPosition (0); - } else if (location == barsize) { - Host.CanScroll (Size - pos, out int nv, vertical); - if (nv > 0) { - SetPosition (Math.Min (pos + nv, Size)); - } - } - } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - var mb = (b2 - b1) / 2; - var ml = mb + b1 + (mb == 0 ? 1 : 0); - if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) { - lastLocation = location; - var np = b1 * Size / barsize; - SetPosition (np); - } else if (location > lastLocation) { - var np = location * Size / barsize; - Host.CanScroll (np - pos, out int nv, vertical); - if (nv > 0) { - SetPosition (pos + nv); - } - } - } - } else { - if (location >= b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) { - Host.CanScroll (location, out int nv, vertical); - if (nv > 0) { - SetPosition (Math.Min (pos + nv, Size)); - } - } else if (location <= b1) { - SetPosition (Math.Max (pos - barsize - location, 0)); - } - } - } - - return true; - } - } - /// /// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView. /// @@ -483,6 +144,8 @@ public bool KeepContentAlwaysInViewport { set { if (keepContentAlwaysInViewport != value) { keepContentAlwaysInViewport = value; + vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value; + horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value; Point p = default; if (value && -contentOffset.X + Bounds.Width > contentSize.Width) { p = new Point (contentSize.Width - Bounds.Width + (showVerticalScrollIndicator ? 1 : 0), -contentOffset.Y); @@ -540,17 +203,21 @@ bool IsOverridden (View view) public bool ShowHorizontalScrollIndicator { get => showHorizontalScrollIndicator; set { - if (value == showHorizontalScrollIndicator) + if (value == showHorizontalScrollIndicator) { return; + } showHorizontalScrollIndicator = value; SetNeedsLayout (); if (value) { base.Add (horizontal); + horizontal.OtherScrollBarView = vertical; + horizontal.OtherScrollBarView.ShowScrollIndicator = value; horizontal.MouseEnter += View_MouseEnter; horizontal.MouseLeave += View_MouseLeave; } else { - Remove (horizontal); + base.Remove (horizontal); + horizontal.OtherScrollBarView = null; horizontal.MouseEnter -= View_MouseEnter; horizontal.MouseLeave -= View_MouseLeave; } @@ -575,17 +242,21 @@ public override void RemoveAll () public bool ShowVerticalScrollIndicator { get => showVerticalScrollIndicator; set { - if (value == showVerticalScrollIndicator) + if (value == showVerticalScrollIndicator) { return; + } showVerticalScrollIndicator = value; SetNeedsLayout (); if (value) { base.Add (vertical); + vertical.OtherScrollBarView = horizontal; + vertical.OtherScrollBarView.ShowScrollIndicator = value; vertical.MouseEnter += View_MouseEnter; vertical.MouseLeave += View_MouseLeave; } else { Remove (vertical); + vertical.OtherScrollBarView = null; vertical.MouseEnter -= View_MouseEnter; vertical.MouseLeave -= View_MouseLeave; } @@ -739,7 +410,7 @@ public bool ScrollLeft (int cols) /// Number of lines to scroll. public bool ScrollDown (int lines) { - if (CanScroll (lines, out _, true)) { + if (vertical.CanScroll (lines, out _, true)) { ContentOffset = new Point (contentOffset.X, contentOffset.Y - lines); return true; } @@ -753,28 +424,13 @@ public bool ScrollDown (int lines) /// Number of columns to scroll by. public bool ScrollRight (int cols) { - if (CanScroll (cols, out _)) { + if (horizontal.CanScroll (cols, out _)) { ContentOffset = new Point (contentOffset.X - cols, contentOffset.Y); return true; } return false; } - internal bool CanScroll (int n, out int max, bool isVertical = false) - { - var size = isVertical ? - (KeepContentAlwaysInViewport ? Bounds.Height + (showHorizontalScrollIndicator ? -2 : -1) : 0) : - (KeepContentAlwaysInViewport ? Bounds.Width + (showVerticalScrollIndicator ? -2 : -1) : 0); - var cSize = isVertical ? -contentSize.Height : -contentSize.Width; - var cOffSet = isVertical ? contentOffset.Y : contentOffset.X; - var newSize = Math.Max (cSize, cOffSet - n); - max = cSize < newSize - size ? n : -cSize + (cOffSet - size) - 1; - if (cSize < newSize - size) { - return true; - } - return false; - } - /// public override bool ProcessKey (KeyEvent kb) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 3284843f4c..50e9dc36f8 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -460,6 +460,26 @@ public override Rect Frame { } } + /// + /// Gets or sets the top row. + /// + public int TopRow { get => topRow; set => topRow = Math.Max (Math.Min (value, Lines - 1), 0); } + + /// + /// Gets or sets the left column. + /// + public int LeftColumn { get => leftColumn; set => leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); } + + /// + /// Gets the maximum visible length line. + /// + public int Maxlength => model.GetMaxVisibleLine (topRow, topRow + Frame.Height); + + /// + /// Gets the number of lines. + /// + public int Lines => model.Count; + /// /// Loads the contents of the file into the . /// @@ -875,10 +895,10 @@ public void ScrollTo (int idx, bool isRow = true) idx = 0; } if (isRow) { - topRow = idx > model.Count - 1 ? model.Count - 1 : idx; + topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0); } else { var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height); - leftColumn = idx > maxlength - 1 ? maxlength - 1 : idx; + leftColumn = Math.Max (idx > maxlength - 1 ? maxlength - 1 : idx, 0); } SetNeedsDisplay (); } @@ -1374,7 +1394,7 @@ public override bool MouseEvent (MouseEvent ev) if (ev.Flags == MouseFlags.Button1Clicked) { if (model.Count > 0) { - var maxCursorPositionableLine = (model.Count - 1) - topRow; + var maxCursorPositionableLine = Math.Max ((model.Count - 1) - topRow, 0); if (ev.Y > maxCursorPositionableLine) { currentRow = maxCursorPositionableLine; } else { diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 8c273f1b07..c76695d4dc 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -13,6 +13,7 @@ class Editor : Scenario { private string _fileName = "demo.txt"; private TextView _textView; private bool _saved = true; + private ScrollBarView _vertical; public override void Init (Toplevel top, ColorScheme colorScheme) { @@ -35,6 +36,7 @@ public override void Init (Toplevel top, ColorScheme colorScheme) new MenuItem ("C_ut", "", () => Cut()), new MenuItem ("_Paste", "", () => Paste()) }), + new MenuBarItem ("_ScrollBarView", CreateKeepChecked ()) }); Top.Add (menu); @@ -67,6 +69,41 @@ public override void Init (Toplevel top, ColorScheme colorScheme) LoadFile (); Win.Add (_textView); + + _vertical = new ScrollBarView (_textView, true); + var horizontal = new ScrollBarView (_textView, false); + _vertical.OtherScrollBarView = horizontal; + horizontal.OtherScrollBarView = _vertical; + + _vertical.ChangedPosition += () => { + _textView.TopRow = _vertical.Position; + if (_textView.TopRow != _vertical.Position) { + _vertical.Position = _textView.TopRow; + } + _textView.SetNeedsDisplay (); + }; + + horizontal.ChangedPosition += () => { + _textView.LeftColumn = horizontal.Position; + if (_textView.LeftColumn != horizontal.Position) { + horizontal.Position = _textView.LeftColumn; + } + _textView.SetNeedsDisplay (); + }; + + _textView.DrawContent += (e) => { + _vertical.Size = _textView.Lines - 1; + _vertical.ContentOffset = _textView.TopRow; + horizontal.Size = _textView.Maxlength - 1; + horizontal.ContentOffset = _textView.LeftColumn; + _vertical.ColorScheme = horizontal.ColorScheme = _textView.ColorScheme; + if (_vertical.ShowScrollIndicator) { + _vertical.Redraw (e); + } + if (horizontal.ShowScrollIndicator) { + horizontal.Redraw (e); + } + }; } public override void Setup () @@ -145,11 +182,25 @@ private void CreateDemoFile(string fileName) sb.Append ("Hello world.\n"); sb.Append ("This is a test of the Emergency Broadcast System.\n"); + for (int i = 0; i < 40; i++) { + sb.Append ("This is a test with a very long line and many lines to test the ScrollViewBar against the TextView.\n"); + } var sw = System.IO.File.CreateText (fileName); sw.Write (sb.ToString ()); sw.Close (); } + private MenuItem [] CreateKeepChecked () + { + var item = new MenuItem (); + item.Title = "Keep Content Always In Viewport"; + item.CheckType |= MenuItemCheckStyle.Checked; + item.Checked = true; + item.Action += () => _vertical.KeepContentAlwaysInViewport = item.Checked = !item.Checked; + + return new MenuItem [] { item }; + } + public override void Run () { base.Run (); diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index 5a0e03daa6..7ad3f95799 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -6,7 +6,7 @@ using Terminal.Gui; namespace UICatalog { - [ScenarioMetadata (Name: "List View With Selection", Description: "ListView with colunns and selection")] + [ScenarioMetadata (Name: "List View With Selection", Description: "ListView with columns and selection")] [ScenarioCategory ("Controls")] class ListViewWithSelection : Scenario { @@ -55,9 +55,34 @@ public override void Setup () }; Win.Add (_listView); - + var vertical = new ScrollBarView (_listView, true); + + vertical.ChangedPosition += () => { + _listView.TopItem = vertical.Position; + if (_listView.TopItem != vertical.Position) { + vertical.Position = _listView.TopItem; + } + _listView.SetNeedsDisplay (); + }; + + _listView.DrawContent += (e) => { + vertical.Size = _listView.Source.Count; + vertical.ContentOffset = _listView.TopItem; + vertical.ColorScheme = _listView.ColorScheme; + if (vertical.ShowScrollIndicator) { + vertical.Redraw (e); + } + }; + _listView.SetSource (_scenarios); + var k = "Keep Content Always In Viewport"; + var keepCheckBox = new CheckBox (k, vertical.AutoHideScrollBars) { + X = Pos.AnchorEnd (k.Length + 3), + Y = 0, + }; + keepCheckBox.Toggled += (_) => vertical.KeepContentAlwaysInViewport = keepCheckBox.Checked; + Win.Add (keepCheckBox); } private void _customRenderCB_Toggled (bool prev) @@ -84,7 +109,7 @@ private void AllowMultipleCB_Toggled (bool prev) Win.SetNeedsDisplay (); } - // This is basicaly the same implementation used by the UICatalog main window + // This is basically the same implementation used by the UICatalog main window internal class ScenarioListDataSource : IListDataSource { int _nameColumnWidth = 30; private List scenarios; From f7a8cf63d74aba7faefb93bcd58d9d528d06c81d Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 12 Jan 2021 00:18:16 +0000 Subject: [PATCH 33/70] Since FocusNearestView method deals with navigation and focus is better use TabIndexes instead. --- Terminal.Gui/Core/Toplevel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index c83eee343d..69f75e3a3e 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -221,7 +221,7 @@ public override bool ProcessKey (KeyEvent keyEvent) old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { - FocusNearestView (SuperView?.Subviews, Direction.Forward); + FocusNearestView (SuperView?.TabIndexes, Direction.Forward); } return true; case Key.BackTab | Key.ShiftMask: @@ -234,7 +234,7 @@ public override bool ProcessKey (KeyEvent keyEvent) old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { - FocusNearestView (SuperView?.Subviews?.Reverse(), Direction.Backward); + FocusNearestView (SuperView?.TabIndexes?.Reverse(), Direction.Backward); } return true; case Key.Tab | Key.CtrlMask: From 4fbb5be1653900c74951ae07921325278bd5b616 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 12 Jan 2021 00:53:26 +0000 Subject: [PATCH 34/70] Fixed some bugs. --- Terminal.Gui/Views/ScrollBarView.cs | 55 ++++++++++++++--------------- UICatalog/Scenarios/Editor.cs | 6 ++-- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 985d31ef40..01e895c51c 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -29,7 +29,7 @@ public class ScrollBarView : View { bool autoHideScrollBars = true; Dim originalHostWidth, originalHostHeight; bool hosted; - bool showBothScrollIndicator => OtherScrollBarView != null && OtherScrollBarView.showScrollIndicator; + bool showBothScrollIndicator => OtherScrollBarView != null && OtherScrollBarView.showScrollIndicator && showScrollIndicator; /// /// Initializes a new instance of the class using layout. @@ -256,38 +256,35 @@ void ShowHideScrollBars () return; } - if (vertical) { - if (Host.Bounds.Height == 0 || Host.Bounds.Height > size) { - if (showScrollIndicator) { - ShowScrollIndicator = false; - } - } else if (Host.Bounds.Height > 0 && Host.Bounds.Height == size) { - pending = true; - } else if (!showScrollIndicator) { - ShowScrollIndicator = true; + int barsize = vertical ? Bounds.Height : Bounds.Width; + + if (barsize == 0 || barsize > size) { + if (showScrollIndicator) { + ShowScrollIndicator = false; + } + } else if (barsize > 0 && barsize == size && OtherScrollBarView != null && OtherScrollBarView.pending) { + if (showScrollIndicator) { + ShowScrollIndicator = false; } + if (OtherScrollBarView != null && showBothScrollIndicator) { + OtherScrollBarView.ShowScrollIndicator = false; + } + } else if (barsize > 0 && barsize == size && OtherScrollBarView != null && !OtherScrollBarView.pending) { + pending = true; + OtherScrollBarView.Redraw (OtherScrollBarView.Bounds); } else { - if (Host.Bounds.Width == 0 || Host.Bounds.Width > size) { - if (showScrollIndicator) { - ShowScrollIndicator = false; - } - } else if (Host.Bounds.Width > 0 && Host.Bounds.Width == size && OtherScrollBarView.pending) { - if (showScrollIndicator) { - ShowScrollIndicator = false; - } - if (showBothScrollIndicator) { - OtherScrollBarView.showScrollIndicator = false; - } - } else { - if (OtherScrollBarView.pending) { - if (!showBothScrollIndicator) { - OtherScrollBarView.showScrollIndicator = true; - } - } - if (!showScrollIndicator) { - ShowScrollIndicator = true; + if (OtherScrollBarView != null && OtherScrollBarView.pending) { + if (!showBothScrollIndicator) { + OtherScrollBarView.ShowScrollIndicator = true; + OtherScrollBarView.Redraw (OtherScrollBarView.Bounds); } } + if (!showScrollIndicator) { + ShowScrollIndicator = true; + } + } + if (OtherScrollBarView != null) { + OtherScrollBarView.pending = false; } } diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index c76695d4dc..c48110efbf 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -94,7 +94,7 @@ public override void Init (Toplevel top, ColorScheme colorScheme) _textView.DrawContent += (e) => { _vertical.Size = _textView.Lines - 1; _vertical.ContentOffset = _textView.TopRow; - horizontal.Size = _textView.Maxlength - 1; + horizontal.Size = _textView.Maxlength; horizontal.ContentOffset = _textView.LeftColumn; _vertical.ColorScheme = horizontal.ColorScheme = _textView.ColorScheme; if (_vertical.ShowScrollIndicator) { @@ -182,8 +182,8 @@ private void CreateDemoFile(string fileName) sb.Append ("Hello world.\n"); sb.Append ("This is a test of the Emergency Broadcast System.\n"); - for (int i = 0; i < 40; i++) { - sb.Append ("This is a test with a very long line and many lines to test the ScrollViewBar against the TextView.\n"); + for (int i = 0; i < 30; i++) { + sb.Append ($"{i} - This is a test with a very long line and many lines to test the ScrollViewBar against the TextView. - {i}\n"); } var sw = System.IO.File.CreateText (fileName); sw.Write (sb.ToString ()); From 6f13c64747d3abfb931bf8302b715befe203c733 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 12 Jan 2021 01:37:28 +0000 Subject: [PATCH 35/70] ContentOffset fixed. --- Terminal.Gui/Views/ScrollBarView.cs | 4 ++-- UICatalog/Scenarios/Editor.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 01e895c51c..518da0ee77 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -126,11 +126,11 @@ public int ContentOffset { set { var co = -Math.Abs (value); if (contentOffset != co) { - if (CanScroll (contentOffset - co, out int max, vertical)) { + if (CanScroll (value + contentOffset, out int max, vertical) || max > 0) { if (max == contentOffset - co) { contentOffset = co; } else { - contentOffset = co + max; + contentOffset = -max; } } Position = Math.Max (0, -contentOffset); diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index c48110efbf..4b03554a44 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -94,7 +94,7 @@ public override void Init (Toplevel top, ColorScheme colorScheme) _textView.DrawContent += (e) => { _vertical.Size = _textView.Lines - 1; _vertical.ContentOffset = _textView.TopRow; - horizontal.Size = _textView.Maxlength; + horizontal.Size = _textView.Maxlength + 1; horizontal.ContentOffset = _textView.LeftColumn; _vertical.ColorScheme = horizontal.ColorScheme = _textView.ColorScheme; if (_vertical.ShowScrollIndicator) { From cb71674e7617fe9ffb145c2af7e0729e23040337 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 12 Jan 2021 23:24:24 +0000 Subject: [PATCH 36/70] Sets the vertical and horizontal ContentOffset property. --- Terminal.Gui/Views/ScrollView.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 0a5b139bc2..0ae8f9e9f1 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -117,7 +117,9 @@ public Point ContentOffset { set { contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); contentView.Frame = new Rect (contentOffset, contentSize); + vertical.ContentOffset = -contentOffset.Y; vertical.Position = Math.Max (0, -contentOffset.Y); + horizontal.ContentOffset = -contentOffset.X; horizontal.Position = Math.Max (0, -contentOffset.X); SetNeedsDisplay (); } From 4bbc2fc22dfe4fad93b852939256c1a1fa9f50f8 Mon Sep 17 00:00:00 2001 From: Angelo Breuer Date: Wed, 13 Jan 2021 16:24:12 +0100 Subject: [PATCH 37/70] Reset synchronization context on Application.Shutdown() - Fixes https://github.com/migueldeicaza/gui.cs/issues/1084 Reset synchronization context to allow the user to run async/await, as the main loop has been ended, the synchronization context from gui.cs does no longer process any callbacks. --- Terminal.Gui/Core/Application.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 7bb1e94868..15143df5f8 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -540,6 +540,12 @@ public static void Shutdown () Driver?.End (); Driver = null; _initialized = false; + + // Reset synchronization context to allow the user to run async/await, + // as the main loop has been ended, the synchronization context from + // gui.cs does no longer process any callbacks. See #1084 for more details: + // (https://github.com/migueldeicaza/gui.cs/issues/1084). + SynchronizationContext.SetSynchronizationContext (syncContext: null); } static void Redraw (View view) From 3ca84f838106c81e5bde309123aaeafccd7a1e86 Mon Sep 17 00:00:00 2001 From: Angelo Breuer Date: Wed, 13 Jan 2021 16:24:24 +0100 Subject: [PATCH 38/70] Add tests for #1084 --- UnitTests/ApplicationTests.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs index 5d5ce642c0..07d2ca9634 100644 --- a/UnitTests/ApplicationTests.cs +++ b/UnitTests/ApplicationTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Terminal.Gui; using Xunit; @@ -220,5 +222,30 @@ public void Loaded_Ready_Unlodaded_Events () Application.Shutdown (); Assert.Equal (3, count); } + + [Fact] + public void Shutdown_Allows_Async () + { + static async Task TaskWithAsyncContinuation () + { + await Task.Yield (); + await Task.Yield (); + } + + Init (); + Application.Shutdown (); + + var task = TaskWithAsyncContinuation (); + Thread.Sleep (20); + Assert.True (task.IsCompletedSuccessfully); + } + + [Fact] + public void Shutdown_Resets_SyncContext () + { + Init (); + Application.Shutdown (); + Assert.Null (SynchronizationContext.Current); + } } } From 5c2e037c854578d7231f945b55365359be9f09f2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 14 Jan 2021 00:48:02 +0000 Subject: [PATCH 39/70] Removed ContentOffset from ScrollBarView. --- Terminal.Gui/Views/ScrollBarView.cs | 72 ++++++++------------ Terminal.Gui/Views/ScrollView.cs | 2 - UICatalog/Scenarios/Editor.cs | 4 +- UICatalog/Scenarios/ListViewWithSelection.cs | 2 +- 4 files changed, 32 insertions(+), 48 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 518da0ee77..85548f899b 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -23,7 +23,7 @@ namespace Terminal.Gui { /// public class ScrollBarView : View { bool vertical; - int size, position, contentOffset; + int size, position; bool showScrollIndicator; bool keepContentAlwaysInViewport = true; bool autoHideScrollBars = true; @@ -70,6 +70,11 @@ public ScrollBarView (int size, int position, bool isVertical) : base () /// public ScrollBarView (View host, bool isVertical) : this (0, 0, isVertical) { + if (host == null) { + throw new ArgumentNullException ("The host parameter can't be null."); + } else if (host.SuperView == null) { + throw new ArgumentNullException ("The host SuperView parameter can't be null."); + } hosted = true; originalHostWidth = host.Width; originalHostHeight = host.Height; @@ -115,31 +120,6 @@ public int Size { } } - /// - /// Represents the top left/top corner coordinate that is displayed by the scrollbar. - /// - /// The content offset. - public int ContentOffset { - get { - return contentOffset; - } - set { - var co = -Math.Abs (value); - if (contentOffset != co) { - if (CanScroll (value + contentOffset, out int max, vertical) || max > 0) { - if (max == contentOffset - co) { - contentOffset = co; - } else { - contentOffset = -max; - } - } - Position = Math.Max (0, -contentOffset); - OnChangedPosition (); - SetNeedsDisplay (); - } - } - } - /// /// This event is raised when the position on the scrollbar has changed. /// @@ -152,8 +132,17 @@ public int ContentOffset { public int Position { get => position; set { - position = value; - SetNeedsDisplay (); + if (position != value) { + if (CanScroll (value - position, out int max, vertical) || max > 0) { + if (max > 0 && max == value - position) { + position = value; + } else { + position = Math.Max (max, 0); + } + } + OnChangedPosition (); + SetNeedsDisplay (); + } } } @@ -203,15 +192,15 @@ public bool KeepContentAlwaysInViewport { set { if (keepContentAlwaysInViewport != value) { keepContentAlwaysInViewport = value; - int co = 0; - if (value && !vertical && -contentOffset + Host.Bounds.Width > size) { - co = size - Host.Bounds.Width + (showBothScrollIndicator ? 1 : 0); + int pos = 0; + if (value && !vertical && position + Host.Bounds.Width > size) { + pos = size - Host.Bounds.Width + (showBothScrollIndicator ? 1 : 0); } - if (value && vertical && -contentOffset + Host.Bounds.Height > size) { - co = size - Host.Bounds.Height + (showBothScrollIndicator ? 1 : 0); + if (value && vertical && position + Host.Bounds.Height > size) { + pos = size - Host.Bounds.Height + (showBothScrollIndicator ? 1 : 0); } - if (co != 0) { - ContentOffset = co; + if (pos != 0) { + Position = pos; } if (OtherScrollBarView != null && OtherScrollBarView.keepContentAlwaysInViewport != value) { OtherScrollBarView.KeepContentAlwaysInViewport = value; @@ -236,7 +225,6 @@ public bool AutoHideScrollBars { void SetPosition (int newPos) { Position = newPos; - ContentOffset = Position; OnChangedPosition (); } @@ -432,7 +420,7 @@ public override void Redraw (Rect region) Driver.AddRune (special); } if (!hasLeftTee) { - Move (Bounds.Width -2, row); + Move (Bounds.Width - 2, row); Driver.AddRune (Driver.LeftTee); } @@ -519,14 +507,12 @@ public override bool MouseEvent (MouseEvent me) internal bool CanScroll (int n, out int max, bool isVertical = false) { - var size = isVertical ? + var s = isVertical ? (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) : (KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0); - var cSize = -Size; - var cOffSet = contentOffset; - var newSize = Math.Max (cSize, cOffSet - n); - max = cSize < newSize - size ? n : -cSize + (cOffSet - size) - 1; - if (cSize < newSize - size) { + var newSize = Math.Min (size, position + n); + max = size > s + newSize ? n : size - (s + position) - 1; + if (size > s + newSize) { return true; } return false; diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 0ae8f9e9f1..0a5b139bc2 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -117,9 +117,7 @@ public Point ContentOffset { set { contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); contentView.Frame = new Rect (contentOffset, contentSize); - vertical.ContentOffset = -contentOffset.Y; vertical.Position = Math.Max (0, -contentOffset.Y); - horizontal.ContentOffset = -contentOffset.X; horizontal.Position = Math.Max (0, -contentOffset.X); SetNeedsDisplay (); } diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 4b03554a44..296e202dc6 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -93,9 +93,9 @@ public override void Init (Toplevel top, ColorScheme colorScheme) _textView.DrawContent += (e) => { _vertical.Size = _textView.Lines - 1; - _vertical.ContentOffset = _textView.TopRow; + _vertical.Position = _textView.TopRow; horizontal.Size = _textView.Maxlength + 1; - horizontal.ContentOffset = _textView.LeftColumn; + horizontal.Position = _textView.LeftColumn; _vertical.ColorScheme = horizontal.ColorScheme = _textView.ColorScheme; if (_vertical.ShowScrollIndicator) { _vertical.Redraw (e); diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index 7ad3f95799..dac0f619e7 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -67,7 +67,7 @@ public override void Setup () _listView.DrawContent += (e) => { vertical.Size = _listView.Source.Count; - vertical.ContentOffset = _listView.TopItem; + vertical.Position = _listView.TopItem; vertical.ColorScheme = _listView.ColorScheme; if (vertical.ShowScrollIndicator) { vertical.Redraw (e); From 5b86c460e855c70da8c0cd51232a1f6bab13d7cb Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 14 Jan 2021 00:57:10 +0000 Subject: [PATCH 40/70] Added ScrollBarViewTests.cs file with unit tests for ScrollBarView. --- UnitTests/ScrollBarViewTests.cs | 258 ++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 UnitTests/ScrollBarViewTests.cs diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs new file mode 100644 index 0000000000..5dd4d36fe4 --- /dev/null +++ b/UnitTests/ScrollBarViewTests.cs @@ -0,0 +1,258 @@ +using System; +using Xunit; + +namespace Terminal.Gui { + public class ScrollBarViewTests { + public class HostView : View { + public int Top { get; set; } + public int Lines { get; set; } + public int Left { get; set; } + public int Cols { get; set; } + } + + private HostView _hostView; + private ScrollBarView _vertical; + private ScrollBarView _horizontal; + private bool _added; + + public ScrollBarViewTests () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + var top = Application.Top; + + _hostView = new HostView () { + Width = Dim.Fill (), + Height = Dim.Fill (), + Top = 0, + Lines = 30, + Left = 0, + Cols = 100 + }; + + top.Add (_hostView); + } + + private void AddHandlers () + { + if (!_added) { + _hostView.DrawContent += _hostView_DrawContent; + _vertical.ChangedPosition += _vertical_ChangedPosition; + _horizontal.ChangedPosition += _horizontal_ChangedPosition; + } + _added = true; + } + + private void RemoveHandlers () + { + if (_added) { + _hostView.DrawContent -= _hostView_DrawContent; + _vertical.ChangedPosition -= _vertical_ChangedPosition; + _horizontal.ChangedPosition -= _horizontal_ChangedPosition; + } + _added = false; + } + + private void _hostView_DrawContent (Rect obj) + { + _vertical.Size = _hostView.Lines; + _vertical.Position = _hostView.Top; + _horizontal.Size = _hostView.Cols; + _horizontal.Position = _hostView.Left; + _vertical.ColorScheme = _horizontal.ColorScheme = _hostView.ColorScheme; + if (_vertical.ShowScrollIndicator) { + _vertical.Redraw (obj); + } + if (_horizontal.ShowScrollIndicator) { + _horizontal.Redraw (obj); + } + } + + private void _vertical_ChangedPosition () + { + _hostView.Top = _vertical.Position; + if (_hostView.Top != _vertical.Position) { + _vertical.Position = _hostView.Top; + } + _hostView.SetNeedsDisplay (); + } + + private void _horizontal_ChangedPosition () + { + _hostView.Left = _horizontal.Position; + if (_hostView.Left != _horizontal.Position) { + _horizontal.Position = _hostView.Left; + } + _hostView.SetNeedsDisplay (); + } + + [Fact] + public void Hosting_A_Null_View_To_A_ScrollBarView_Throws_ArgumentNullException () + { + Assert.Throws ("The host parameter can't be null.", + () => new ScrollBarView (null, true)); + Assert.Throws ("The host parameter can't be null.", + () => new ScrollBarView (null, false)); + } + + [Fact] + public void Hosting_A_Null_SuperView_View_To_A_ScrollBarView_Throws_ArgumentNullException () + { + Assert.Throws ("The host SuperView parameter can't be null.", + () => new ScrollBarView (new View (), true)); + Assert.Throws ("The host SuperView parameter can't be null.", + () => new ScrollBarView (new View (), false)); + } + + [Fact] + public void Hosting_A_View_To_A_ScrollBarView () + { + RemoveHandlers (); + + _vertical = new ScrollBarView (_hostView, true); + _horizontal = new ScrollBarView (_hostView, false); + _vertical.OtherScrollBarView = _horizontal; + _horizontal.OtherScrollBarView = _vertical; + + Assert.True (_vertical.IsVertical); + Assert.False (_horizontal.IsVertical); + + Assert.Equal (_vertical.Position, _hostView.Top); + Assert.NotEqual (_vertical.Size, _hostView.Lines); + Assert.Equal (_horizontal.Position, _hostView.Left); + Assert.NotEqual (_horizontal.Size, _hostView.Cols); + + AddHandlers (); + _hostView.SuperView.LayoutSubviews (); + _hostView.Redraw (_hostView.Bounds); + + Assert.Equal (_vertical.Position, _hostView.Top); + Assert.Equal (_vertical.Size, _hostView.Lines); + Assert.Equal (_horizontal.Position, _hostView.Left); + Assert.Equal (_horizontal.Size, _hostView.Cols); + } + + [Fact] + public void ChangedPosition_Update_The_Hosted_View () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + _vertical.Position = 2; + Assert.Equal (_vertical.Position, _hostView.Top); + + _horizontal.Position = 5; + Assert.Equal (_horizontal.Position, _hostView.Left); + } + + [Fact] + public void ChangedPosition_Negative_Value () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + _vertical.Position = -20; + Assert.Equal (0, _vertical.Position); + Assert.Equal (_vertical.Position, _hostView.Top); + + _horizontal.Position = -50; + Assert.Equal (0, _horizontal.Position); + Assert.Equal (_horizontal.Position, _hostView.Left); + } + + [Fact] + public void DrawContent_Update_The_ScrollBarView_Position () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + _hostView.Top = 3; + _hostView.Redraw (_hostView.Bounds); + Assert.Equal (_vertical.Position, _hostView.Top); + + _hostView.Left = 6; + _hostView.Redraw (_hostView.Bounds); + Assert.Equal (_horizontal.Position, _hostView.Left); + } + + [Fact] + public void OtherScrollBarView_Not_Null () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + Assert.Equal (_vertical.OtherScrollBarView, _horizontal); + Assert.Equal (_horizontal.OtherScrollBarView, _vertical); + } + + [Fact] + public void ShowScrollIndicator_Check () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + Assert.True (_vertical.ShowScrollIndicator); + Assert.True (_horizontal.ShowScrollIndicator); + } + + [Fact] + public void KeepContentAlwaysInViewport_True () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + _vertical.Position = 50; + Assert.Equal (_vertical.Position, _vertical.Size - _vertical.Bounds.Height + 1); + Assert.Equal (_vertical.Position, _hostView.Top); + + _horizontal.Position = 150; + Assert.Equal (_horizontal.Position, _horizontal.Size - _horizontal.Bounds.Width + 1); + Assert.Equal (_horizontal.Position, _hostView.Left); + } + + [Fact] + public void KeepContentAlwaysInViewport_False () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + _vertical.KeepContentAlwaysInViewport = false; + _vertical.Position = 50; + Assert.Equal (_vertical.Position, _vertical.Size - 1); + Assert.Equal (_vertical.Position, _hostView.Top); + + _horizontal.Position = 150; + Assert.Equal (_horizontal.Position, _horizontal.Size - 1); + Assert.Equal (_horizontal.Position, _hostView.Left); + } + + [Fact] + public void AutoHideScrollBars_Check () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + _hostView.Lines = 10; + _hostView.Redraw (_hostView.Bounds); + Assert.False (_vertical.ShowScrollIndicator); + _hostView.Cols = 60; + _hostView.Redraw (_hostView.Bounds); + Assert.False (_horizontal.ShowScrollIndicator); + + _hostView.Lines = 40; + _hostView.Redraw (_hostView.Bounds); + Assert.True (_vertical.ShowScrollIndicator); + _hostView.Cols = 120; + _hostView.Redraw (_hostView.Bounds); + Assert.True (_horizontal.ShowScrollIndicator); + } + } +} From 351cb8133f815c38966ae229f5811626769b8609 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 14 Jan 2021 01:35:20 +0000 Subject: [PATCH 41/70] Fixed backward bug and added one more unit test to probe. --- Terminal.Gui/Core/View.cs | 2 +- Terminal.Gui/Views/ScrollBarView.cs | 2 +- UnitTests/ScrollBarViewTests.cs | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index b4ccd5f0d1..66d8377e8c 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1342,8 +1342,8 @@ public virtual void Redraw (Rect bounds) // Draw the subview // Use the view's bounds (view-relative; Location will always be (0,0) if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { - view.OnDrawContent (view.Bounds); view.Redraw (view.Bounds); + view.OnDrawContent (view.Bounds); } } view.NeedDisplay = Rect.Empty; diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 85548f899b..851db3ce09 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -137,7 +137,7 @@ public int Position { if (max > 0 && max == value - position) { position = value; } else { - position = Math.Max (max, 0); + position = Math.Max (position + max, 0); } } OnChangedPosition (); diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index 5dd4d36fe4..968074c1ec 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -146,6 +146,32 @@ public void ChangedPosition_Update_The_Hosted_View () Assert.Equal (_horizontal.Position, _hostView.Left); } + [Fact] + public void ChangedPosition_Scrolling () + { + Hosting_A_View_To_A_ScrollBarView (); + + AddHandlers (); + + for (int i = 0; i < _vertical.Size; i++) { + _vertical.Position += 1; + Assert.Equal (_vertical.Position, _hostView.Top); + } + for (int i = _vertical.Size - 1; i >= 0; i--) { + _vertical.Position -= 1; + Assert.Equal (_vertical.Position, _hostView.Top); + } + + for (int i = 0; i < _horizontal.Size; i++) { + _horizontal.Position += i; + Assert.Equal (_horizontal.Position, _hostView.Left); + } + for (int i = _horizontal.Size - 1; i >= 0; i--) { + _horizontal.Position -= 1; + Assert.Equal (_horizontal.Position, _hostView.Left); + } + } + [Fact] public void ChangedPosition_Negative_Value () { From bf6c4ec417147dc45a5a04d2614067c6384cd105 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 14 Jan 2021 17:57:31 +0000 Subject: [PATCH 42/70] Improves mouse functionality and fixed the CharacterMap scenario. --- Terminal.Gui/Views/ScrollBarView.cs | 48 +++++++++++++++++------------ Terminal.Gui/Views/ScrollView.cs | 25 +++++++++++---- UICatalog/Scenarios/CharacterMap.cs | 2 +- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 851db3ce09..6163fbe9c0 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -222,12 +222,6 @@ public bool AutoHideScrollBars { } } - void SetPosition (int newPos) - { - Position = newPos; - OnChangedPosition (); - } - /// /// Virtual method to invoke the action event. /// @@ -435,28 +429,42 @@ public override void Redraw (Rect region) public override bool MouseEvent (MouseEvent me) { if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && - !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && + me.Flags != MouseFlags.Button1Released && me.Flags != MouseFlags.WheeledDown && + me.Flags != MouseFlags.WheeledUp && me.Flags != MouseFlags.WheeledRight && + me.Flags != MouseFlags.WheeledLeft) { return false; } - if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - lastLocation = -1; - } - int location = vertical ? me.Y : me.X; int barsize = vertical ? Bounds.Height : Bounds.Width; int posTopLeftTee = vertical ? posTopTee : posLeftTee; int posBottomRightTee = vertical ? posBottomTee : posRightTee; - barsize -= 2; var pos = Position; + + if ((me.Flags.HasFlag (MouseFlags.Button1Pressed) || + me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) + && (Application.mouseGrabView == null || Application.mouseGrabView != this)) { + Application.GrabMouse (this); + } else if (me.Flags == MouseFlags.Button1Released && Application.mouseGrabView != null && Application.mouseGrabView == this) { + Application.UngrabMouse (); + return true; + } else if (showScrollIndicator && (me.Flags == MouseFlags.WheeledDown || me.Flags == MouseFlags.WheeledUp || + me.Flags == MouseFlags.WheeledRight || me.Flags == MouseFlags.WheeledLeft)) { + return Host.MouseEvent (me); + } + + if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + lastLocation = -1; + } if (location == 0) { if (pos > 0) { - SetPosition (pos - 1); + Position = pos - 1; } } else if (location == barsize + 1) { if (CanScroll (1, out _, vertical)) { - SetPosition (pos + 1); + Position = pos + 1; } } else if (location > 0 && location < barsize + 1) { var b1 = pos * barsize / Size; @@ -468,11 +476,11 @@ public override bool MouseEvent (MouseEvent me) if (location > b1 && location <= b2 + 1) { if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { if (location == 1) { - SetPosition (0); + Position = 0; } else if (location == barsize) { CanScroll (Size - pos, out int nv, vertical); if (nv > 0) { - SetPosition (Math.Min (pos + nv, Size)); + Position = Math.Min (pos + nv, Size); } } } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { @@ -481,12 +489,12 @@ public override bool MouseEvent (MouseEvent me) if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) { lastLocation = location; var np = b1 * Size / barsize; - SetPosition (np); + Position = np; } else if (location > lastLocation) { var np = location * Size / barsize; CanScroll (np - pos, out int nv, vertical); if (nv > 0) { - SetPosition (pos + nv); + Position = pos + nv; } } } @@ -494,10 +502,10 @@ public override bool MouseEvent (MouseEvent me) if (location >= b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) { CanScroll (location, out int nv, vertical); if (nv > 0) { - SetPosition (Math.Min (pos + nv, Size)); + Position = Math.Min (pos + nv, Size); } } else if (location <= b1) { - SetPosition (Math.Max (pos - barsize - location, 0)); + Position = Math.Max (pos - barsize - location, 0); } } } diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 0a5b139bc2..f6d5e72429 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -78,6 +78,8 @@ void Init (Rect frame) MouseEnter += View_MouseEnter; MouseLeave += View_MouseLeave; + contentView.MouseEnter += View_MouseEnter; + contentView.MouseLeave += View_MouseLeave; } Size contentSize; @@ -115,11 +117,20 @@ public Point ContentOffset { return contentOffset; } set { - contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); - contentView.Frame = new Rect (contentOffset, contentSize); - vertical.Position = Math.Max (0, -contentOffset.Y); - horizontal.Position = Math.Max (0, -contentOffset.X); - SetNeedsDisplay (); + var co = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); + if (contentOffset != co) { + contentOffset = co; + contentView.Frame = new Rect (contentOffset, contentSize); + var p = Math.Max (0, -contentOffset.Y); + if (vertical.Position != p) { + vertical.Position = Math.Max (0, -contentOffset.Y); + } + p = Math.Max (0, -contentOffset.X); + if (horizontal.Position != p) { + horizontal.Position = Math.Max (0, -contentOffset.X); + } + SetNeedsDisplay (); + } } } @@ -180,7 +191,9 @@ public override void Add (View view) void View_MouseLeave (MouseEventArgs e) { - Application.UngrabMouse (); + if (Application.mouseGrabView != null && Application.mouseGrabView != vertical && Application.mouseGrabView != horizontal) { + Application.UngrabMouse (); + } } void View_MouseEnter (MouseEventArgs e) diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 10736add22..f211a26d42 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -143,7 +143,7 @@ private void CharMap_DrawContent (Rect viewport) 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+= V_SPACE) { + for (int row = -ContentOffset.Y, y = 0; row < -ContentOffset.Y + viewport.Height / 2 - 1; row++, y+= V_SPACE) { int val = (-viewport.Y + row) * 16; if (val < MaxCodePointVal) { var rowLabel = $"U+{val / 16:x4}x"; From 7d71525a4a89ef1d384f2ddad07a8c91337026e6 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 14 Jan 2021 22:59:45 +0000 Subject: [PATCH 43/70] Fixes #1088. On WindowsDriver moving the mouse with a button pressed, when it is released, the button clicked event is fired, causing an unintentional event. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 41ad015352..fc97bcca6b 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -799,6 +799,11 @@ MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) IsButtonReleased = false; } + var p = new Point () { + X = mouseEvent.MousePosition.X, + Y = mouseEvent.MousePosition.Y + }; + if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null && !IsButtonDoubleClicked) || (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved && mouseEvent.ButtonState != 0 && !IsButtonReleased && !IsButtonDoubleClicked)) { @@ -854,12 +859,7 @@ MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) IsButtonPressed = false; IsButtonReleased = true; } else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) && - IsButtonReleased) { - var p = new Point () { - X = mouseEvent.MousePosition.X, - Y = mouseEvent.MousePosition.Y - }; - //if (p == point) { + IsButtonReleased && p == point) { switch (LastMouseButtonPressed) { case WindowsConsole.ButtonState.Button1Pressed: mouseFlag = MouseFlags.Button1Clicked; @@ -877,9 +877,6 @@ MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) X = mouseEvent.MousePosition.X, Y = mouseEvent.MousePosition.Y }; - //} else { - // mouseFlag = 0; - //} LastMouseButtonPressed = null; IsButtonReleased = false; } else if (mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.DoubleClick)) { From b88dbb672b43a46e83482ca9ff3b95a6d47be1f9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 01:45:40 +0000 Subject: [PATCH 44/70] Added right and left scrolling feature to the ListView. --- Terminal.Gui/Core/View.cs | 2 +- Terminal.Gui/Views/ListView.cs | 127 ++++++++++++++++--- Terminal.Gui/Views/ScrollBarView.cs | 3 + UICatalog/Scenarios/ListViewWithSelection.cs | 56 ++++++-- UICatalog/Scenarios/ListsAndCombos.cs | 35 +++++ UICatalog/UICatalog.cs | 37 +++++- 6 files changed, 226 insertions(+), 34 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 66d8377e8c..b4ccd5f0d1 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1342,8 +1342,8 @@ public virtual void Redraw (Rect bounds) // Draw the subview // Use the view's bounds (view-relative; Location will always be (0,0) if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { - view.Redraw (view.Bounds); view.OnDrawContent (view.Bounds); + view.Redraw (view.Bounds); } } view.NeedDisplay = Rect.Empty; diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 41b1ae8562..c009146dff 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -34,6 +34,11 @@ public interface IListDataSource { /// int Count { get; } + /// + /// Returns the maximum length of elements to display + /// + int Length { get; } + /// /// This method is invoked to render a specified item, the method should cover the entire provided width. /// @@ -45,10 +50,11 @@ public interface IListDataSource { /// The column where the rendering will start /// The line where the rendering will be done. /// The width that must be filled out. + /// The index of the string to be displayed. /// /// The default color will be set before this method is invoked, and will be based on whether the item is selected or not. /// - void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width); + void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0); /// /// Should return whether the specified item is currently marked. @@ -103,7 +109,7 @@ public interface IListDataSource { /// /// public class ListView : View { - int top; + int top, left; int selected; IListDataSource source; @@ -146,7 +152,7 @@ public void SetSource (IList source) /// /// An item implementing the IList interface. /// - /// Use the property to set a new source and use custome rendering. + /// Use the property to set a new source and use custom rendering. /// public Task SetSourceAsync (IList source) { @@ -211,6 +217,28 @@ public int TopItem { } } + /// + /// Gets or sets the left column where the item start to be displayed at on the . + /// + /// The left position. + public int LeftItem { + get => left; + set { + if (source == null) + return; + + if (left < 0 || top >= source.Count) + throw new ArgumentException ("value"); + left = value; + SetNeedsDisplay (); + } + } + + /// + /// Gets the widest item. + /// + public int Maxlength => (source?.Length) ?? 0; + /// /// Gets or sets the index of the currently selected item. /// @@ -229,7 +257,6 @@ public int SelectedItem { } } - static IListDataSource MakeWrapper (IList source) { return new ListWrapper (source); @@ -301,6 +328,7 @@ public override void Redraw (Rect bounds) var item = top; bool focused = HasFocus; int col = allowsMarking ? 2 : 0; + int start = left; for (int row = 0; row < f.Height; row++, item++) { bool isSelected = item == selected; @@ -320,7 +348,7 @@ public override void Redraw (Rect bounds) Driver.AddRune (source.IsMarked (item) ? (AllowsMultipleSelection ? Driver.Checked : Driver.Selected) : (AllowsMultipleSelection ? Driver.UnChecked : Driver.UnSelected)); Driver.AddRune (' '); } - Source.Render (this, Driver, isSelected, item, col, row, f.Width - col); + Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start); } } } @@ -572,6 +600,26 @@ public virtual void ScrollUp (int lines) SetNeedsDisplay (); } + /// + /// Scrolls the view right. + /// + /// Number of columns to scroll right. + public virtual void ScrollRight (int cols) + { + left = Math.Max (Math.Min (left + cols, Maxlength - 1), 0); + SetNeedsDisplay (); + } + + /// + /// Scrolls the view left. + /// + /// Number of columns to scroll left. + public virtual void ScrollLeft (int cols) + { + left = Math.Max (left - cols, 0); + SetNeedsDisplay (); + } + int lastSelectedItem = -1; private bool allowsMultipleSelection = true; @@ -639,7 +687,8 @@ public override void PositionCursor () public override bool MouseEvent (MouseEvent me) { if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && - me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp) + me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp && + me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft) return false; if (!HasFocus && CanFocus) { @@ -656,6 +705,12 @@ public override bool MouseEvent (MouseEvent me) } else if (me.Flags == MouseFlags.WheeledUp) { ScrollUp (1); return true; + } else if (me.Flags == MouseFlags.WheeledRight) { + ScrollRight (1); + return true; + } else if (me.Flags == MouseFlags.WheeledLeft) { + ScrollLeft (1); + return true; } if (me.Y + top >= source.Count) { @@ -687,7 +742,7 @@ public override bool MouseEvent (MouseEvent me) public class ListWrapper : IListDataSource { IList src; BitArray marks; - int count; + int count, len; /// /// Initializes a new instance of given an @@ -698,7 +753,8 @@ public ListWrapper (IList source) if (source != null) { count = source.Count; marks = new BitArray (count); - this.src = source; + src = source; + len = GetMaxLengthItem (); } } @@ -707,11 +763,42 @@ public ListWrapper (IList source) /// public int Count => src != null ? src.Count : 0; - void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width) + /// + /// Gets the maximum item length in the . + /// + public int Length => len; + + int GetMaxLengthItem () + { + if (src?.Count == 0) { + return 0; + } + + int maxLength = 0; + for (int i = 0; i < src.Count; i++) { + var t = src [i]; + int l; + if (t is ustring u) { + l = u.RuneCount; + } else if (t is string s) { + l = s.Length; + } else { + l = t.ToString ().Length; + } + + if (l > maxLength) { + maxLength = l; + } + } + + return maxLength; + } + + void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0) { int byteLen = ustr.Length; int used = 0; - for (int i = 0; i < byteLen;) { + for (int i = start; i < byteLen;) { (var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen); var count = Rune.ColumnWidth (rune); if (used + count > width) @@ -735,19 +822,21 @@ void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int widt /// The col where to move. /// The line where to move. /// The item width. - public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width) + /// The index of the string to be displayed. + public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width, int start = 0) { container.Move (col, line); var t = src [item]; if (t == null) { RenderUstr (driver, ustring.Make (""), col, line, width); } else { - if (t is ustring) { - RenderUstr (driver, (ustring)t, col, line, width); - } else if (t is string) { - RenderUstr (driver, (string)t, col, line, width); - } else - RenderUstr (driver, t.ToString (), col, line, width); + if (t is ustring u) { + RenderUstr (driver, u, col, line, width, start); + } else if (t is string s) { + RenderUstr (driver, s, col, line, width, start); + } else { + RenderUstr (driver, t.ToString (), col, line, width, start); + } } } @@ -793,14 +882,14 @@ public class ListViewItemEventArgs : EventArgs { /// public int Item { get; } /// - /// The the item. + /// The item. /// public object Value { get; } /// /// Initializes a new instance of /// - /// The index of the the item. + /// The index of the item. /// The item public ListViewItemEventArgs (int item, object value) { diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 6163fbe9c0..7cffd2a63d 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -139,6 +139,8 @@ public int Position { } else { position = Math.Max (position + max, 0); } + } else if (max < 0) { + position = Math.Max (position + max, 0); } OnChangedPosition (); SetNeedsDisplay (); @@ -173,6 +175,7 @@ public bool ShowScrollIndicator { Visible = true; } else { Visible = false; + Position = 0; } Width = vertical ? 1 : Dim.Width (Host); Height = vertical ? Dim.Height (Host) : 1; diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index dac0f619e7..b729ee0c3c 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -25,7 +25,7 @@ public override void Setup () Height = 1, }; Win.Add (_customRenderCB); - _customRenderCB.Toggled += _customRenderCB_Toggled; ; + _customRenderCB.Toggled += _customRenderCB_Toggled; _allowMarkingCB = new CheckBox ("Allow Marking") { X = Pos.Right (_customRenderCB) + 1, @@ -56,6 +56,9 @@ public override void Setup () Win.Add (_listView); var vertical = new ScrollBarView (_listView, true); + var horizontal = new ScrollBarView (_listView, false); + vertical.OtherScrollBarView = horizontal; + horizontal.OtherScrollBarView = vertical; vertical.ChangedPosition += () => { _listView.TopItem = vertical.Position; @@ -65,13 +68,26 @@ public override void Setup () _listView.SetNeedsDisplay (); }; + horizontal.ChangedPosition += () => { + _listView.LeftItem = horizontal.Position; + if (_listView.LeftItem != horizontal.Position) { + horizontal.Position = _listView.LeftItem; + } + _listView.SetNeedsDisplay (); + }; + _listView.DrawContent += (e) => { - vertical.Size = _listView.Source.Count; + vertical.Size = _listView.Source.Count - 1; vertical.Position = _listView.TopItem; - vertical.ColorScheme = _listView.ColorScheme; + horizontal.Size = _listView.Maxlength; + horizontal.Position = _listView.LeftItem; + vertical.ColorScheme = horizontal.ColorScheme = _listView.ColorScheme; if (vertical.ShowScrollIndicator) { vertical.Redraw (e); } + if (horizontal.ShowScrollIndicator) { + horizontal.Redraw (e); + } }; _listView.SetSource (_scenarios); @@ -114,15 +130,16 @@ internal class ScenarioListDataSource : IListDataSource { int _nameColumnWidth = 30; private List scenarios; BitArray marks; - int count; + int count, len; public List Scenarios { - get => scenarios; + get => scenarios; set { if (value != null) { count = value.Count; marks = new BitArray (count); scenarios = value; + len = GetMaxLengthItem (); } } } @@ -135,14 +152,16 @@ public bool IsMarked (int item) public int Count => Scenarios != null ? Scenarios.Count : 0; + public int Length => len; + public ScenarioListDataSource (List itemList) => Scenarios = itemList; - public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width) + public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0) { container.Move (col, line); // Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item])); - RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width); + RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start); } public void SetMark (int item, bool value) @@ -151,11 +170,30 @@ public void SetMark (int item, bool value) marks [item] = value; } + int GetMaxLengthItem () + { + if (scenarios?.Count == 0) { + return 0; + } + + int maxLength = 0; + for (int i = 0; i < scenarios.Count; i++) { + var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i])); + var sc = $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}"; + var l = sc.Length; + if (l > maxLength) { + maxLength = l; + } + } + + return maxLength; + } + // A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461 - private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width) + private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0) { int used = 0; - int index = 0; + int index = start; while (index < ustr.Length) { (var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length); var count = Rune.ColumnWidth (rune); diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs index 910d3496d0..dda51619b0 100644 --- a/UICatalog/Scenarios/ListsAndCombos.cs +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -39,6 +39,41 @@ public override void Setup () listview.SelectedItemChanged += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem]; Win.Add (lbListView, listview); + var vertical = new ScrollBarView (listview, true); + var horizontal = new ScrollBarView (listview, false); + vertical.OtherScrollBarView = horizontal; + horizontal.OtherScrollBarView = vertical; + + vertical.ChangedPosition += () => { + listview.TopItem = vertical.Position; + if (listview.TopItem != vertical.Position) { + vertical.Position = listview.TopItem; + } + listview.SetNeedsDisplay (); + }; + + horizontal.ChangedPosition += () => { + listview.LeftItem = horizontal.Position; + if (listview.LeftItem != horizontal.Position) { + horizontal.Position = listview.LeftItem; + } + listview.SetNeedsDisplay (); + }; + + listview.DrawContent += (e) => { + vertical.Size = listview.Source.Count - 1; + vertical.Position = listview.TopItem; + horizontal.Size = listview.Maxlength; + horizontal.Position = listview.LeftItem; + vertical.ColorScheme = horizontal.ColorScheme = listview.ColorScheme; + if (vertical.ShowScrollIndicator) { + vertical.Redraw (e); + } + if (horizontal.ShowScrollIndicator) { + horizontal.Redraw (e); + } + }; + // ComboBox var lbComboBox = new Label ("ComboBox") { ColorScheme = Colors.TopLevel, diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index d71088b653..12b37bf18a 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -498,31 +498,58 @@ private static void _scenarioListView_OpenSelectedItem (EventArgs e) } internal class ScenarioListDataSource : IListDataSource { + private readonly int len; + public List Scenarios { get; set; } public bool IsMarked (int item) => false; public int Count => Scenarios.Count; - public ScenarioListDataSource (List itemList) => Scenarios = itemList; + public int Length => len; + + public ScenarioListDataSource (List itemList) + { + Scenarios = itemList; + len = GetMaxLengthItem (); + } - public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width) + public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0) { container.Move (col, line); // Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item])); - RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width); + RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start); } public void SetMark (int item, bool value) { } + int GetMaxLengthItem () + { + if (Scenarios?.Count == 0) { + return 0; + } + + int maxLength = 0; + for (int i = 0; i < Scenarios.Count; i++) { + var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i])); + var sc = $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}"; + var l = sc.Length; + if (l > maxLength) { + maxLength = l; + } + } + + return maxLength; + } + // A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461 - private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width) + private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0) { int used = 0; - int index = 0; + int index = start; while (index < ustr.Length) { (var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length); var count = Rune.ColumnWidth (rune); From 146d23422b821c1e6992fee3799519672074887f Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 11:38:32 +0000 Subject: [PATCH 45/70] Ensures that the ScrollBarView don't have two vertical or horizontal ScrollBarView. --- Terminal.Gui/Views/ScrollBarView.cs | 11 ++++++++++- UnitTests/ScrollBarViewTests.cs | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 7cffd2a63d..a1b5f6b951 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -29,6 +29,7 @@ public class ScrollBarView : View { bool autoHideScrollBars = true; Dim originalHostWidth, originalHostHeight; bool hosted; + ScrollBarView otherScrollBarView; bool showBothScrollIndicator => OtherScrollBarView != null && OtherScrollBarView.showScrollIndicator && showScrollIndicator; /// @@ -156,7 +157,15 @@ public int Position { /// /// Represent a vertical or horizontal ScrollBarView other than this. /// - public ScrollBarView OtherScrollBarView { get; set; } + public ScrollBarView OtherScrollBarView { + get => otherScrollBarView; + set { + if (value.IsVertical && vertical || !value.IsVertical && !vertical) { + throw new ArgumentException ($"There is already a {(vertical ? "vertical" : "horizontal")} ScrollBarView."); + } + otherScrollBarView = value; + } + } /// /// Gets or sets the visibility for the vertical or horizontal scroll indicator. diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index 968074c1ec..44bff58482 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -104,6 +104,32 @@ public void Hosting_A_Null_SuperView_View_To_A_ScrollBarView_Throws_ArgumentNull () => new ScrollBarView (new View (), false)); } + [Fact] + public void Hosting_Two_Vertical_ScrollBarView_Throws_ArgumentException () + { + var top = new Toplevel (); + var host = new View (); + top.Add (host); + var v = new ScrollBarView (host, true); + var h = new ScrollBarView (host, true); + + Assert.Throws (null, () => v.OtherScrollBarView = h); + Assert.Throws (null, () => h.OtherScrollBarView = v); + } + + [Fact] + public void Hosting_Two_Horizontal_ScrollBarView_Throws_ArgumentException () + { + var top = new Toplevel (); + var host = new View (); + top.Add (host); + var v = new ScrollBarView (host, false); + var h = new ScrollBarView (host, false); + + Assert.Throws (null, () => v.OtherScrollBarView = h); + Assert.Throws (null, () => h.OtherScrollBarView = v); + } + [Fact] public void Hosting_A_View_To_A_ScrollBarView () { From 542c507b5964fbbc30d7d081650d860c126cb92a Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 17:06:39 +0000 Subject: [PATCH 46/70] Ensuring visibility to the SelectedItem of the ListView when getting focus. --- Terminal.Gui/Views/ListView.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index c009146dff..1c8dd781b4 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -586,7 +586,7 @@ public virtual bool MoveHome () /// Number of lines to scroll down. public virtual void ScrollDown (int lines) { - top = Math.Min (top + lines, source.Count - 1); + top = Math.Max (Math.Min (top + lines, source.Count - 1), 0); SetNeedsDisplay (); } @@ -632,7 +632,9 @@ public virtual bool OnSelectedChanged () if (selected != lastSelectedItem) { var value = source?.Count > 0 ? source.ToList () [selected] : null; SelectedItemChanged?.Invoke (new ListViewItemEventArgs (selected, value)); - lastSelectedItem = selected; + if (HasFocus) { + lastSelectedItem = selected; + } return true; } @@ -656,6 +658,7 @@ public virtual bool OnOpenSelectedItem () public override bool OnEnter (View view) { if (lastSelectedItem == -1) { + EnsuresVisibilitySelectedItem (); OnSelectedChanged (); return true; } @@ -674,6 +677,15 @@ public override bool OnLeave (View view) return false; } + void EnsuresVisibilitySelectedItem () + { + if (selected < top) { + top = selected; + } else if (selected >= top + Frame.Height) { + top = Math.Max (selected - Frame.Height + 1, 0); + } + } + /// public override void PositionCursor () { From 0466a6ebc365ce29e24b226a4e064c11433e2b9e Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 17:09:31 +0000 Subject: [PATCH 47/70] Avoiding throwing exception on the "All Views Tester" scenario. --- Terminal.Gui/Views/ScrollBarView.cs | 9 +++++++-- UnitTests/ScrollBarViewTests.cs | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index a1b5f6b951..b01a08088b 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -479,8 +479,10 @@ public override bool MouseEvent (MouseEvent me) Position = pos + 1; } } else if (location > 0 && location < barsize + 1) { - var b1 = pos * barsize / Size; - var b2 = KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size; + var b1 = pos * (Size > 0 ? barsize / Size : 0); + var b2 = Size > 0 + ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) + : 0; if (KeepContentAlwaysInViewport && b1 == b2) { b1 = Math.Max (b1 - 1, 0); } @@ -527,6 +529,9 @@ public override bool MouseEvent (MouseEvent me) internal bool CanScroll (int n, out int max, bool isVertical = false) { + if (Host == null) { + throw new ArgumentNullException ("The host can't be null."); + } var s = isVertical ? (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) : (KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0); diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index 44bff58482..db278609b3 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -117,6 +117,14 @@ public void Hosting_Two_Vertical_ScrollBarView_Throws_ArgumentException () Assert.Throws (null, () => h.OtherScrollBarView = v); } + [Fact] + public void Scrolling_With_Default_Constructor_Throws_ArgumentNullException () + { + var sbv = new ScrollBarView (); + + Assert.Throws ("The host can't be null.", () => sbv.Position = 1); + } + [Fact] public void Hosting_Two_Horizontal_ScrollBarView_Throws_ArgumentException () { From 85d445a5d6f2d7b4aeb9123571c95c576891e6cd Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 17:11:27 +0000 Subject: [PATCH 48/70] Reverting the changes as this is working right again. --- UICatalog/Scenarios/CharacterMap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index f211a26d42..10736add22 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -143,7 +143,7 @@ private void CharMap_DrawContent (Rect viewport) Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0); Driver.AddStr ($" {header:x} "); } - for (int row = -ContentOffset.Y, y = 0; row < -ContentOffset.Y + viewport.Height / 2 - 1; row++, y+= V_SPACE) { + 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"; From b3817d8ff1ad2bf061dbea5c3f627e5def479f13 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 22:23:14 +0000 Subject: [PATCH 49/70] Set focus to the ScrollBarView host if scrolling is started by the scroll bar. --- Terminal.Gui/Views/ScrollBarView.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index b01a08088b..b9e48d84fc 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -448,10 +448,14 @@ public override bool MouseEvent (MouseEvent me) return false; } + if (!Host.HasFocus) { + Host.SetFocus (); + } + int location = vertical ? me.Y : me.X; int barsize = vertical ? Bounds.Height : Bounds.Width; - int posTopLeftTee = vertical ? posTopTee : posLeftTee; - int posBottomRightTee = vertical ? posBottomTee : posRightTee; + int posTopLeftTee = vertical ? posTopTee + 1: posLeftTee + 1; + int posBottomRightTee = vertical ? posBottomTee + 1: posRightTee + 1; barsize -= 2; var pos = Position; @@ -489,21 +493,24 @@ public override bool MouseEvent (MouseEvent me) if (location > b1 && location <= b2 + 1) { if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { - if (location == 1) { + if (location == 1 && posTopLeftTee <= 2) { Position = 0; } else if (location == barsize) { CanScroll (Size - pos, out int nv, vertical); if (nv > 0) { Position = Math.Min (pos + nv, Size); } + } else if (location < posTopLeftTee) { + if (CanScroll (-barsize, out int nv, vertical)) { + Position = pos + nv; + } } } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { var mb = (b2 - b1) / 2; var ml = mb + b1 + (mb == 0 ? 1 : 0); if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) { lastLocation = location; - var np = b1 * Size / barsize; - Position = np; + Position = b1 * Size / barsize; } else if (location > lastLocation) { var np = location * Size / barsize; CanScroll (np - pos, out int nv, vertical); From bedeca61e6152c7f7612e150d0db8ff5d4ae4fbe Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 23:04:49 +0000 Subject: [PATCH 50/70] Fixing some bugs. --- Terminal.Gui/Core/View.cs | 2 +- Terminal.Gui/Views/ScrollBarView.cs | 5 +++-- UnitTests/ScrollBarViewTests.cs | 10 ++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index b4ccd5f0d1..cefddbe3a1 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -741,7 +741,7 @@ public void SetNeedsDisplay (Rect region) public void SetChildNeedsDisplay () { ChildNeedsDisplay = true; - if (container != null && !container.ChildNeedsDisplay) + if (container != null) container.SetChildNeedsDisplay (); } diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index b9e48d84fc..006dce1f8a 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -448,7 +448,7 @@ public override bool MouseEvent (MouseEvent me) return false; } - if (!Host.HasFocus) { + if (Host != null && !Host.HasFocus) { Host.SetFocus (); } @@ -537,7 +537,8 @@ public override bool MouseEvent (MouseEvent me) internal bool CanScroll (int n, out int max, bool isVertical = false) { if (Host == null) { - throw new ArgumentNullException ("The host can't be null."); + max = 0; + return false; } var s = isVertical ? (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) : diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index db278609b3..0d963cf05c 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -118,11 +118,13 @@ public void Hosting_Two_Vertical_ScrollBarView_Throws_ArgumentException () } [Fact] - public void Scrolling_With_Default_Constructor_Throws_ArgumentNullException () + public void Scrolling_With_Default_Constructor_Do_Not_Scroll () { - var sbv = new ScrollBarView (); - - Assert.Throws ("The host can't be null.", () => sbv.Position = 1); + var sbv = new ScrollBarView { + Position = 1 + }; + Assert.NotEqual (1, sbv.Position); + Assert.Equal (0, sbv.Position); } [Fact] From 2f488c55726015672960a7e9d02c24fc61a0b4a8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 15 Jan 2021 23:58:46 +0000 Subject: [PATCH 51/70] Fixing ListView TopItem and LeftItem. --- Terminal.Gui/Views/ListView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 1c8dd781b4..b913da46ce 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -210,7 +210,7 @@ public int TopItem { if (source == null) return; - if (top < 0 || top >= source.Count) + if (top < 0 || (source.Count > 0 && top >= source.Count)) throw new ArgumentException ("value"); top = value; SetNeedsDisplay (); @@ -227,7 +227,7 @@ public int LeftItem { if (source == null) return; - if (left < 0 || top >= source.Count) + if (left < 0 || (Maxlength > 0 && left >= Maxlength)) throw new ArgumentException ("value"); left = value; SetNeedsDisplay (); From 746a5c8c61ed3e526f2d31b63c7d3b73f7b8453d Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 16 Jan 2021 23:53:36 +0000 Subject: [PATCH 52/70] View not visible doesn't have to be handled. --- Terminal.Gui/Core/Application.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 7bb1e94868..ed5fdb25e6 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -351,7 +351,7 @@ static View FindDeepestView (View start, int x, int y, out int resx, out int res var ry = y - startFrame.Y; for (int i = count - 1; i >= 0; i--) { View v = start.InternalSubviews [i]; - if (v.Frame.Contains (rx, ry)) { + if (v.Visible && v.Frame.Contains (rx, ry)) { var deep = FindDeepestView (v, rx, ry, out resx, out resy); if (deep == null) return v; From 58c2509772c2d84d88c00769b52de95613bab6b9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Jan 2021 00:29:01 +0000 Subject: [PATCH 53/70] Offers more simplicity to iterate with the ScrollBarView and its hosts. --- Terminal.Gui/Views/ListView.cs | 2 +- Terminal.Gui/Views/ScrollBarView.cs | 170 +++++++++++---- UICatalog/Scenarios/Editor.cs | 42 ++-- UICatalog/Scenarios/ListViewWithSelection.cs | 43 ++-- UICatalog/Scenarios/ListsAndCombos.cs | 37 ++-- UnitTests/ScrollBarViewTests.cs | 214 +++++++++++-------- 6 files changed, 303 insertions(+), 205 deletions(-) diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index b913da46ce..004a618b0f 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -682,7 +682,7 @@ void EnsuresVisibilitySelectedItem () if (selected < top) { top = selected; } else if (selected >= top + Frame.Height) { - top = Math.Max (selected - Frame.Height + 1, 0); + top = Math.Max (selected - Frame.Height + 2, 0); } } diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 006dce1f8a..c3963a6027 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -27,9 +27,10 @@ public class ScrollBarView : View { bool showScrollIndicator; bool keepContentAlwaysInViewport = true; bool autoHideScrollBars = true; - Dim originalHostWidth, originalHostHeight; bool hosted; ScrollBarView otherScrollBarView; + View contentBottomRightCorner; + bool showBothScrollIndicator => OtherScrollBarView != null && OtherScrollBarView.showScrollIndicator && showScrollIndicator; /// @@ -69,7 +70,10 @@ public ScrollBarView (int size, int position, bool isVertical) : base () /// /// Initializes a new instance of the class using layout. /// - public ScrollBarView (View host, bool isVertical) : this (0, 0, isVertical) + /// The view that will host this scrollbar. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. + /// If set to true (default) will have the other scrollbar, otherwise will have only one. + public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical) { if (host == null) { throw new ArgumentNullException ("The host parameter can't be null."); @@ -77,14 +81,43 @@ public ScrollBarView (View host, bool isVertical) : this (0, 0, isVertical) throw new ArgumentNullException ("The host SuperView parameter can't be null."); } hosted = true; - originalHostWidth = host.Width; - originalHostHeight = host.Height; - X = isVertical ? Pos.Right(host) : Pos.Left (host); - Y = isVertical ? Pos.Top (host) : Pos.Bottom (host); + ColorScheme = host.ColorScheme; + X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host); + Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; Host = host; Host.SuperView.Add (this); - ShowScrollIndicator = true; AutoHideScrollBars = true; + if (showBothScrollIndicator) { + OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) { + ColorScheme = host.ColorScheme, + Host = host, + OtherScrollBarView = this, + }; + OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); + OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; + OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView); + OtherScrollBarView.showScrollIndicator = true; + } + ShowScrollIndicator = true; + contentBottomRightCorner = new View (" "); + Host.SuperView.Add (contentBottomRightCorner); + contentBottomRightCorner.X = Pos.Right (host) - 1; + contentBottomRightCorner.Y = Pos.Bottom (host) - 1; + contentBottomRightCorner.Width = 1; + contentBottomRightCorner.Height = 1; + contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; + } + + private void ContentBottomRightCorner_MouseClick (MouseEventArgs me) + { + if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp + || me.MouseEvent.Flags == MouseFlags.WheeledRight || me.MouseEvent.Flags == MouseFlags.WheeledLeft) { + me.Handled = true; + MouseEvent (me.MouseEvent); + } else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) { + me.Handled = true; + Host.SetFocus (); + } } void Init (int size, int position, bool isVertical) @@ -116,7 +149,6 @@ public int Size { get => size; set { size = value; - ShowHideScrollBars (); SetNeedsDisplay (); } } @@ -160,7 +192,7 @@ public int Position { public ScrollBarView OtherScrollBarView { get => otherScrollBarView; set { - if (value.IsVertical && vertical || !value.IsVertical && !vertical) { + if (value != null && (value.IsVertical && vertical || !value.IsVertical && !vertical)) { throw new ArgumentException ($"There is already a {(vertical ? "vertical" : "horizontal")} ScrollBarView."); } otherScrollBarView = value; @@ -186,13 +218,23 @@ public bool ShowScrollIndicator { Visible = false; Position = 0; } - Width = vertical ? 1 : Dim.Width (Host); - Height = vertical ? Dim.Height (Host) : 1; - if (vertical) { - Host.Width = showScrollIndicator ? originalHostWidth - 1 : originalHostWidth; - } else { - Host.Height = showScrollIndicator ? originalHostHeight - 1 : originalHostHeight; - } + SetWidthHeight (); + } + } + + void SetWidthHeight () + { + if (showBothScrollIndicator) { + Width = vertical ? 1 : Dim.Width (Host) - 1; + Height = vertical ? Dim.Height (Host) - 1 : 1; + otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 1; + otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 1 : 1; + } else if (showScrollIndicator) { + Width = vertical ? 1 : Dim.Width (Host) - 0; + Height = vertical ? Dim.Height (Host) - 0 : 1; + } else if (otherScrollBarView != null && otherScrollBarView.showScrollIndicator) { + otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 0; + otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 0 : 1; } } @@ -242,44 +284,69 @@ public virtual void OnChangedPosition () ChangedPosition?.Invoke (); } - internal bool pending; + /// + /// Only used for a hosted view that will update and redraw the scrollbars. + /// + public virtual void Refresh () + { + ShowHideScrollBars (); + } void ShowHideScrollBars () { - if (!hosted || !autoHideScrollBars) { + if (!hosted || (hosted && !autoHideScrollBars)) { return; } - int barsize = vertical ? Bounds.Height : Bounds.Width; + var pending = CheckBothScrollBars (this); + CheckBothScrollBars (otherScrollBarView, pending); + + SetWidthHeight (); + SetRelativeLayout (Bounds); + OtherScrollBarView.SetRelativeLayout (OtherScrollBarView.Bounds); - if (barsize == 0 || barsize > size) { - if (showScrollIndicator) { - ShowScrollIndicator = false; + if (showBothScrollIndicator) { + contentBottomRightCorner.Visible = true; + } else { + contentBottomRightCorner.Visible = false; + } + if (showBothScrollIndicator) { + Redraw (Bounds); + } + if (otherScrollBarView.showScrollIndicator) { + otherScrollBarView.Redraw (otherScrollBarView.Bounds); + } + } + + bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) + { + int barsize = scrollBarView.vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width; + + if (barsize == 0 || barsize > scrollBarView.size) { + if (scrollBarView.showScrollIndicator) { + scrollBarView.ShowScrollIndicator = false; } - } else if (barsize > 0 && barsize == size && OtherScrollBarView != null && OtherScrollBarView.pending) { - if (showScrollIndicator) { - ShowScrollIndicator = false; + } else if (barsize > 0 && barsize == scrollBarView.size && scrollBarView.OtherScrollBarView != null && pending) { + if (scrollBarView.showScrollIndicator) { + scrollBarView.ShowScrollIndicator = false; } - if (OtherScrollBarView != null && showBothScrollIndicator) { - OtherScrollBarView.ShowScrollIndicator = false; + if (scrollBarView.OtherScrollBarView != null && scrollBarView.showBothScrollIndicator) { + scrollBarView.OtherScrollBarView.ShowScrollIndicator = false; } - } else if (barsize > 0 && barsize == size && OtherScrollBarView != null && !OtherScrollBarView.pending) { + } else if (barsize > 0 && barsize == size && scrollBarView.OtherScrollBarView != null && !pending) { pending = true; - OtherScrollBarView.Redraw (OtherScrollBarView.Bounds); } else { - if (OtherScrollBarView != null && OtherScrollBarView.pending) { - if (!showBothScrollIndicator) { - OtherScrollBarView.ShowScrollIndicator = true; - OtherScrollBarView.Redraw (OtherScrollBarView.Bounds); + if (scrollBarView.OtherScrollBarView != null && pending) { + if (!scrollBarView.showBothScrollIndicator) { + scrollBarView.OtherScrollBarView.ShowScrollIndicator = true; } } - if (!showScrollIndicator) { - ShowScrollIndicator = true; + if (!scrollBarView.showScrollIndicator) { + scrollBarView.ShowScrollIndicator = true; } } - if (OtherScrollBarView != null) { - OtherScrollBarView.pending = false; - } + + return pending; } int posTopTee; @@ -433,6 +500,10 @@ public override void Redraw (Rect region) Driver.AddRune (Driver.RightArrow); } } + + if (hosted && showBothScrollIndicator) { + contentBottomRightCorner.Redraw (contentBottomRightCorner.Bounds); + } } int lastLocation = -1; @@ -454,8 +525,8 @@ public override bool MouseEvent (MouseEvent me) int location = vertical ? me.Y : me.X; int barsize = vertical ? Bounds.Height : Bounds.Width; - int posTopLeftTee = vertical ? posTopTee + 1: posLeftTee + 1; - int posBottomRightTee = vertical ? posBottomTee + 1: posRightTee + 1; + int posTopLeftTee = vertical ? posTopTee + 1 : posLeftTee + 1; + int posBottomRightTee = vertical ? posBottomTee + 1 : posRightTee + 1; barsize -= 2; var pos = Position; @@ -508,15 +579,24 @@ public override bool MouseEvent (MouseEvent me) } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { var mb = (b2 - b1) / 2; var ml = mb + b1 + (mb == 0 ? 1 : 0); - if ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1)) { + if ((location > 1 || (location == 1 && posTopLeftTee > 1)) && ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1))) { lastLocation = location; - Position = b1 * Size / barsize; - } else if (location > lastLocation) { var np = location * Size / barsize; - CanScroll (np - pos, out int nv, vertical); - if (nv > 0) { + if (CanScroll (np - pos, out int nv, vertical)) { + Position = pos + nv; + } + } else if (location != barsize && location > lastLocation) { + var np = location * Size / barsize; + if (CanScroll (np - pos, out int nv, vertical)) { Position = pos + nv; } + } else if (location == 1 && posTopLeftTee <= 2) { + Position = 0; + } else if (location == barsize) { + CanScroll (Size - pos, out int nv, vertical); + if (nv > 0) { + Position = Math.Min (pos + nv, Size); + } } } } else { diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 296e202dc6..e305aa5944 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -13,7 +13,7 @@ class Editor : Scenario { private string _fileName = "demo.txt"; private TextView _textView; private bool _saved = true; - private ScrollBarView _vertical; + private ScrollBarView _scrollBar; public override void Init (Toplevel top, ColorScheme colorScheme) { @@ -70,39 +70,31 @@ public override void Init (Toplevel top, ColorScheme colorScheme) Win.Add (_textView); - _vertical = new ScrollBarView (_textView, true); - var horizontal = new ScrollBarView (_textView, false); - _vertical.OtherScrollBarView = horizontal; - horizontal.OtherScrollBarView = _vertical; + _scrollBar = new ScrollBarView (_textView, true); - _vertical.ChangedPosition += () => { - _textView.TopRow = _vertical.Position; - if (_textView.TopRow != _vertical.Position) { - _vertical.Position = _textView.TopRow; + _scrollBar.ChangedPosition += () => { + _textView.TopRow = _scrollBar.Position; + if (_textView.TopRow != _scrollBar.Position) { + _scrollBar.Position = _textView.TopRow; } _textView.SetNeedsDisplay (); }; - horizontal.ChangedPosition += () => { - _textView.LeftColumn = horizontal.Position; - if (_textView.LeftColumn != horizontal.Position) { - horizontal.Position = _textView.LeftColumn; + _scrollBar.OtherScrollBarView.ChangedPosition += () => { + _textView.LeftColumn = _scrollBar.OtherScrollBarView.Position; + if (_textView.LeftColumn != _scrollBar.OtherScrollBarView.Position) { + _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn; } _textView.SetNeedsDisplay (); }; _textView.DrawContent += (e) => { - _vertical.Size = _textView.Lines - 1; - _vertical.Position = _textView.TopRow; - horizontal.Size = _textView.Maxlength + 1; - horizontal.Position = _textView.LeftColumn; - _vertical.ColorScheme = horizontal.ColorScheme = _textView.ColorScheme; - if (_vertical.ShowScrollIndicator) { - _vertical.Redraw (e); - } - if (horizontal.ShowScrollIndicator) { - horizontal.Redraw (e); - } + _scrollBar.Size = _textView.Lines; + _scrollBar.Position = _textView.TopRow; + _scrollBar.OtherScrollBarView.Size = _textView.Maxlength + 1; + _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn; + _scrollBar.LayoutSubviews (); + _scrollBar.Refresh (); }; } @@ -196,7 +188,7 @@ private MenuItem [] CreateKeepChecked () item.Title = "Keep Content Always In Viewport"; item.CheckType |= MenuItemCheckStyle.Checked; item.Checked = true; - item.Action += () => _vertical.KeepContentAlwaysInViewport = item.Checked = !item.Checked; + item.Action += () => _scrollBar.KeepContentAlwaysInViewport = item.Checked = !item.Checked; return new MenuItem [] { item }; } diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index b729ee0c3c..369b7c8cad 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -55,49 +55,40 @@ public override void Setup () }; Win.Add (_listView); - var vertical = new ScrollBarView (_listView, true); - var horizontal = new ScrollBarView (_listView, false); - vertical.OtherScrollBarView = horizontal; - horizontal.OtherScrollBarView = vertical; - - vertical.ChangedPosition += () => { - _listView.TopItem = vertical.Position; - if (_listView.TopItem != vertical.Position) { - vertical.Position = _listView.TopItem; + var _scrollBar = new ScrollBarView (_listView, true); + + _scrollBar.ChangedPosition += () => { + _listView.TopItem = _scrollBar.Position; + if (_listView.TopItem != _scrollBar.Position) { + _scrollBar.Position = _listView.TopItem; } _listView.SetNeedsDisplay (); }; - horizontal.ChangedPosition += () => { - _listView.LeftItem = horizontal.Position; - if (_listView.LeftItem != horizontal.Position) { - horizontal.Position = _listView.LeftItem; + _scrollBar.OtherScrollBarView.ChangedPosition += () => { + _listView.LeftItem = _scrollBar.OtherScrollBarView.Position; + if (_listView.LeftItem != _scrollBar.OtherScrollBarView.Position) { + _scrollBar.OtherScrollBarView.Position = _listView.LeftItem; } _listView.SetNeedsDisplay (); }; _listView.DrawContent += (e) => { - vertical.Size = _listView.Source.Count - 1; - vertical.Position = _listView.TopItem; - horizontal.Size = _listView.Maxlength; - horizontal.Position = _listView.LeftItem; - vertical.ColorScheme = horizontal.ColorScheme = _listView.ColorScheme; - if (vertical.ShowScrollIndicator) { - vertical.Redraw (e); - } - if (horizontal.ShowScrollIndicator) { - horizontal.Redraw (e); - } + _scrollBar.Size = _listView.Source.Count; + _scrollBar.Position = _listView.TopItem; + _scrollBar.OtherScrollBarView.Size = _listView.Maxlength; + _scrollBar.OtherScrollBarView.Position = _listView.LeftItem; + _scrollBar.Refresh (); }; _listView.SetSource (_scenarios); var k = "Keep Content Always In Viewport"; - var keepCheckBox = new CheckBox (k, vertical.AutoHideScrollBars) { + var keepCheckBox = new CheckBox (k, _scrollBar.AutoHideScrollBars) { X = Pos.AnchorEnd (k.Length + 3), Y = 0, }; - keepCheckBox.Toggled += (_) => vertical.KeepContentAlwaysInViewport = keepCheckBox.Checked; + keepCheckBox.Toggled += (_) => _scrollBar.KeepContentAlwaysInViewport = keepCheckBox.Checked; Win.Add (keepCheckBox); } diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs index dda51619b0..bd9b417b5e 100644 --- a/UICatalog/Scenarios/ListsAndCombos.cs +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -39,39 +39,30 @@ public override void Setup () listview.SelectedItemChanged += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem]; Win.Add (lbListView, listview); - var vertical = new ScrollBarView (listview, true); - var horizontal = new ScrollBarView (listview, false); - vertical.OtherScrollBarView = horizontal; - horizontal.OtherScrollBarView = vertical; + var _scrollBar = new ScrollBarView (listview, true); - vertical.ChangedPosition += () => { - listview.TopItem = vertical.Position; - if (listview.TopItem != vertical.Position) { - vertical.Position = listview.TopItem; + _scrollBar.ChangedPosition += () => { + listview.TopItem = _scrollBar.Position; + if (listview.TopItem != _scrollBar.Position) { + _scrollBar.Position = listview.TopItem; } listview.SetNeedsDisplay (); }; - horizontal.ChangedPosition += () => { - listview.LeftItem = horizontal.Position; - if (listview.LeftItem != horizontal.Position) { - horizontal.Position = listview.LeftItem; + _scrollBar.OtherScrollBarView.ChangedPosition += () => { + listview.LeftItem = _scrollBar.OtherScrollBarView.Position; + if (listview.LeftItem != _scrollBar.OtherScrollBarView.Position) { + _scrollBar.OtherScrollBarView.Position = listview.LeftItem; } listview.SetNeedsDisplay (); }; listview.DrawContent += (e) => { - vertical.Size = listview.Source.Count - 1; - vertical.Position = listview.TopItem; - horizontal.Size = listview.Maxlength; - horizontal.Position = listview.LeftItem; - vertical.ColorScheme = horizontal.ColorScheme = listview.ColorScheme; - if (vertical.ShowScrollIndicator) { - vertical.Redraw (e); - } - if (horizontal.ShowScrollIndicator) { - horizontal.Redraw (e); - } + _scrollBar.Size = listview.Source.Count; + _scrollBar.Position = listview.TopItem; + _scrollBar.OtherScrollBarView.Size = listview.Maxlength; + _scrollBar.OtherScrollBarView.Position = listview.LeftItem; + _scrollBar.Refresh (); }; // ComboBox diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index 0d963cf05c..8fcc73cc22 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -11,8 +11,7 @@ public class HostView : View { } private HostView _hostView; - private ScrollBarView _vertical; - private ScrollBarView _horizontal; + private ScrollBarView _scrollBar; private bool _added; public ScrollBarViewTests () @@ -37,8 +36,8 @@ private void AddHandlers () { if (!_added) { _hostView.DrawContent += _hostView_DrawContent; - _vertical.ChangedPosition += _vertical_ChangedPosition; - _horizontal.ChangedPosition += _horizontal_ChangedPosition; + _scrollBar.ChangedPosition += _scrollBar_ChangedPosition; + _scrollBar.OtherScrollBarView.ChangedPosition += _scrollBar_OtherScrollBarView_ChangedPosition; } _added = true; } @@ -47,41 +46,36 @@ private void RemoveHandlers () { if (_added) { _hostView.DrawContent -= _hostView_DrawContent; - _vertical.ChangedPosition -= _vertical_ChangedPosition; - _horizontal.ChangedPosition -= _horizontal_ChangedPosition; + _scrollBar.ChangedPosition -= _scrollBar_ChangedPosition; + _scrollBar.OtherScrollBarView.ChangedPosition -= _scrollBar_OtherScrollBarView_ChangedPosition; } _added = false; } private void _hostView_DrawContent (Rect obj) { - _vertical.Size = _hostView.Lines; - _vertical.Position = _hostView.Top; - _horizontal.Size = _hostView.Cols; - _horizontal.Position = _hostView.Left; - _vertical.ColorScheme = _horizontal.ColorScheme = _hostView.ColorScheme; - if (_vertical.ShowScrollIndicator) { - _vertical.Redraw (obj); - } - if (_horizontal.ShowScrollIndicator) { - _horizontal.Redraw (obj); - } + _scrollBar.Size = _hostView.Lines; + _scrollBar.Position = _hostView.Top; + _scrollBar.OtherScrollBarView.Size = _hostView.Cols; + _scrollBar.OtherScrollBarView.Position = _hostView.Left; + _scrollBar.ColorScheme = _scrollBar.OtherScrollBarView.ColorScheme = _hostView.ColorScheme; + _scrollBar.Refresh (); } - private void _vertical_ChangedPosition () + private void _scrollBar_ChangedPosition () { - _hostView.Top = _vertical.Position; - if (_hostView.Top != _vertical.Position) { - _vertical.Position = _hostView.Top; + _hostView.Top = _scrollBar.Position; + if (_hostView.Top != _scrollBar.Position) { + _scrollBar.Position = _hostView.Top; } _hostView.SetNeedsDisplay (); } - private void _horizontal_ChangedPosition () + private void _scrollBar_OtherScrollBarView_ChangedPosition () { - _hostView.Left = _horizontal.Position; - if (_hostView.Left != _horizontal.Position) { - _horizontal.Position = _hostView.Left; + _hostView.Left = _scrollBar.OtherScrollBarView.Position; + if (_hostView.Left != _scrollBar.OtherScrollBarView.Position) { + _scrollBar.OtherScrollBarView.Position = _hostView.Left; } _hostView.SetNeedsDisplay (); } @@ -145,27 +139,27 @@ public void Hosting_A_View_To_A_ScrollBarView () { RemoveHandlers (); - _vertical = new ScrollBarView (_hostView, true); - _horizontal = new ScrollBarView (_hostView, false); - _vertical.OtherScrollBarView = _horizontal; - _horizontal.OtherScrollBarView = _vertical; + _scrollBar = new ScrollBarView (_hostView, true); + _scrollBar.OtherScrollBarView = new ScrollBarView (_hostView, false); + _scrollBar.OtherScrollBarView = _scrollBar.OtherScrollBarView; + _scrollBar.OtherScrollBarView.OtherScrollBarView = _scrollBar; - Assert.True (_vertical.IsVertical); - Assert.False (_horizontal.IsVertical); + Assert.True (_scrollBar.IsVertical); + Assert.False (_scrollBar.OtherScrollBarView.IsVertical); - Assert.Equal (_vertical.Position, _hostView.Top); - Assert.NotEqual (_vertical.Size, _hostView.Lines); - Assert.Equal (_horizontal.Position, _hostView.Left); - Assert.NotEqual (_horizontal.Size, _hostView.Cols); + Assert.Equal (_scrollBar.Position, _hostView.Top); + Assert.NotEqual (_scrollBar.Size, _hostView.Lines); + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); + Assert.NotEqual (_scrollBar.OtherScrollBarView.Size, _hostView.Cols); AddHandlers (); _hostView.SuperView.LayoutSubviews (); _hostView.Redraw (_hostView.Bounds); - Assert.Equal (_vertical.Position, _hostView.Top); - Assert.Equal (_vertical.Size, _hostView.Lines); - Assert.Equal (_horizontal.Position, _hostView.Left); - Assert.Equal (_horizontal.Size, _hostView.Cols); + Assert.Equal (_scrollBar.Position, _hostView.Top); + Assert.Equal (_scrollBar.Size, _hostView.Lines); + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); + Assert.Equal (_scrollBar.OtherScrollBarView.Size, _hostView.Cols); } [Fact] @@ -175,11 +169,11 @@ public void ChangedPosition_Update_The_Hosted_View () AddHandlers (); - _vertical.Position = 2; - Assert.Equal (_vertical.Position, _hostView.Top); + _scrollBar.Position = 2; + Assert.Equal (_scrollBar.Position, _hostView.Top); - _horizontal.Position = 5; - Assert.Equal (_horizontal.Position, _hostView.Left); + _scrollBar.OtherScrollBarView.Position = 5; + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); } [Fact] @@ -189,22 +183,22 @@ public void ChangedPosition_Scrolling () AddHandlers (); - for (int i = 0; i < _vertical.Size; i++) { - _vertical.Position += 1; - Assert.Equal (_vertical.Position, _hostView.Top); + for (int i = 0; i < _scrollBar.Size; i++) { + _scrollBar.Position += 1; + Assert.Equal (_scrollBar.Position, _hostView.Top); } - for (int i = _vertical.Size - 1; i >= 0; i--) { - _vertical.Position -= 1; - Assert.Equal (_vertical.Position, _hostView.Top); + for (int i = _scrollBar.Size - 1; i >= 0; i--) { + _scrollBar.Position -= 1; + Assert.Equal (_scrollBar.Position, _hostView.Top); } - for (int i = 0; i < _horizontal.Size; i++) { - _horizontal.Position += i; - Assert.Equal (_horizontal.Position, _hostView.Left); + for (int i = 0; i < _scrollBar.OtherScrollBarView.Size; i++) { + _scrollBar.OtherScrollBarView.Position += i; + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); } - for (int i = _horizontal.Size - 1; i >= 0; i--) { - _horizontal.Position -= 1; - Assert.Equal (_horizontal.Position, _hostView.Left); + for (int i = _scrollBar.OtherScrollBarView.Size - 1; i >= 0; i--) { + _scrollBar.OtherScrollBarView.Position -= 1; + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); } } @@ -215,13 +209,13 @@ public void ChangedPosition_Negative_Value () AddHandlers (); - _vertical.Position = -20; - Assert.Equal (0, _vertical.Position); - Assert.Equal (_vertical.Position, _hostView.Top); + _scrollBar.Position = -20; + Assert.Equal (0, _scrollBar.Position); + Assert.Equal (_scrollBar.Position, _hostView.Top); - _horizontal.Position = -50; - Assert.Equal (0, _horizontal.Position); - Assert.Equal (_horizontal.Position, _hostView.Left); + _scrollBar.OtherScrollBarView.Position = -50; + Assert.Equal (0, _scrollBar.OtherScrollBarView.Position); + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); } [Fact] @@ -233,11 +227,11 @@ public void DrawContent_Update_The_ScrollBarView_Position () _hostView.Top = 3; _hostView.Redraw (_hostView.Bounds); - Assert.Equal (_vertical.Position, _hostView.Top); + Assert.Equal (_scrollBar.Position, _hostView.Top); _hostView.Left = 6; _hostView.Redraw (_hostView.Bounds); - Assert.Equal (_horizontal.Position, _hostView.Left); + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); } [Fact] @@ -247,8 +241,8 @@ public void OtherScrollBarView_Not_Null () AddHandlers (); - Assert.Equal (_vertical.OtherScrollBarView, _horizontal); - Assert.Equal (_horizontal.OtherScrollBarView, _vertical); + Assert.Equal (_scrollBar.OtherScrollBarView, _scrollBar.OtherScrollBarView); + Assert.Equal (_scrollBar.OtherScrollBarView.OtherScrollBarView, _scrollBar); } [Fact] @@ -258,8 +252,8 @@ public void ShowScrollIndicator_Check () AddHandlers (); - Assert.True (_vertical.ShowScrollIndicator); - Assert.True (_horizontal.ShowScrollIndicator); + Assert.True (_scrollBar.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); } [Fact] @@ -269,13 +263,28 @@ public void KeepContentAlwaysInViewport_True () AddHandlers (); - _vertical.Position = 50; - Assert.Equal (_vertical.Position, _vertical.Size - _vertical.Bounds.Height + 1); - Assert.Equal (_vertical.Position, _hostView.Top); - - _horizontal.Position = 150; - Assert.Equal (_horizontal.Position, _horizontal.Size - _horizontal.Bounds.Width + 1); - Assert.Equal (_horizontal.Position, _hostView.Left); + Assert.Equal (80, _hostView.Bounds.Width); + Assert.Equal (25, _hostView.Bounds.Height); + Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.Equal (24, _scrollBar.Bounds.Height); + Assert.Equal (30, _scrollBar.Size); + Assert.Equal (100, _scrollBar.OtherScrollBarView.Size); + Assert.True (_scrollBar.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.True (_scrollBar.Visible); + Assert.True (_scrollBar.OtherScrollBarView.Visible); + + _scrollBar.Position = 50; + Assert.Equal (_scrollBar.Position, _scrollBar.Size - _scrollBar.Bounds.Height); + Assert.Equal (_scrollBar.Position, _hostView.Top); + Assert.Equal (6, _scrollBar.Position); + Assert.Equal (6, _hostView.Top); + + _scrollBar.OtherScrollBarView.Position = 150; + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); + Assert.Equal (21, _scrollBar.OtherScrollBarView.Position); + Assert.Equal (21, _hostView.Left); } [Fact] @@ -285,14 +294,14 @@ public void KeepContentAlwaysInViewport_False () AddHandlers (); - _vertical.KeepContentAlwaysInViewport = false; - _vertical.Position = 50; - Assert.Equal (_vertical.Position, _vertical.Size - 1); - Assert.Equal (_vertical.Position, _hostView.Top); + _scrollBar.KeepContentAlwaysInViewport = false; + _scrollBar.Position = 50; + Assert.Equal (_scrollBar.Position, _scrollBar.Size - 1); + Assert.Equal (_scrollBar.Position, _hostView.Top); - _horizontal.Position = 150; - Assert.Equal (_horizontal.Position, _horizontal.Size - 1); - Assert.Equal (_horizontal.Position, _hostView.Left); + _scrollBar.OtherScrollBarView.Position = 150; + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - 1); + Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); } [Fact] @@ -302,19 +311,54 @@ public void AutoHideScrollBars_Check () AddHandlers (); + _hostView.Redraw (_hostView.Bounds); + Assert.True (_scrollBar.ShowScrollIndicator); + Assert.True (_scrollBar.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", + _scrollBar.Height.ToString ()); + Assert.Equal (24, _scrollBar.Bounds.Height); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", + _scrollBar.OtherScrollBarView.Width.ToString ()); + Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width); + _hostView.Lines = 10; _hostView.Redraw (_hostView.Bounds); - Assert.False (_vertical.ShowScrollIndicator); + Assert.False (_scrollBar.ShowScrollIndicator); + Assert.False (_scrollBar.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", + _scrollBar.Height.ToString ()); + Assert.Equal (24, _scrollBar.Bounds.Height); + _hostView.Cols = 60; _hostView.Redraw (_hostView.Bounds); - Assert.False (_horizontal.ShowScrollIndicator); + Assert.False (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.False (_scrollBar.OtherScrollBarView.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))", + _scrollBar.OtherScrollBarView.Width.ToString ()); + Assert.Equal (80, _scrollBar.OtherScrollBarView.Bounds.Width); _hostView.Lines = 40; _hostView.Redraw (_hostView.Bounds); - Assert.True (_vertical.ShowScrollIndicator); + Assert.True (_scrollBar.ShowScrollIndicator); + Assert.True (_scrollBar.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))", + _scrollBar.Height.ToString ()); + Assert.Equal (25, _scrollBar.Bounds.Height); + _hostView.Cols = 120; _hostView.Redraw (_hostView.Bounds); - Assert.True (_horizontal.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", + _scrollBar.OtherScrollBarView.Width.ToString ()); + Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.True (_scrollBar.ShowScrollIndicator); + Assert.True (_scrollBar.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", + _scrollBar.Height.ToString ()); + Assert.Equal (24, _scrollBar.Bounds.Height); } } } From 0f038c76e1c40d841b1899f45f92ad1af2472412 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Jan 2021 00:47:32 +0000 Subject: [PATCH 54/70] Also implementing the ScrollBarView in the ComboBox of the ListsAndCombos scenario. --- Terminal.Gui/Views/ListView.cs | 4 ++-- UICatalog/Scenarios/ListsAndCombos.cs | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 004a618b0f..3413dfbf1a 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -210,7 +210,7 @@ public int TopItem { if (source == null) return; - if (top < 0 || (source.Count > 0 && top >= source.Count)) + if (value < 0 || (source.Count > 0 && value >= source.Count)) throw new ArgumentException ("value"); top = value; SetNeedsDisplay (); @@ -227,7 +227,7 @@ public int LeftItem { if (source == null) return; - if (left < 0 || (Maxlength > 0 && left >= Maxlength)) + if (value < 0 || (Maxlength > 0 && value >= Maxlength)) throw new ArgumentException ("value"); left = value; SetNeedsDisplay (); diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs index bd9b417b5e..b0da52150a 100644 --- a/UICatalog/Scenarios/ListsAndCombos.cs +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -83,6 +83,33 @@ public override void Setup () comboBox.SelectedItemChanged += (ListViewItemEventArgs text) => lbComboBox.Text = items[comboBox.SelectedItem]; Win.Add (lbComboBox, comboBox); + var scrollBarCbx = new ScrollBarView (comboBox.Subviews [1], true); + + scrollBarCbx.ChangedPosition += () => { + ((ListView)comboBox.Subviews [1]).TopItem = scrollBarCbx.Position; + if (((ListView)comboBox.Subviews [1]).TopItem != scrollBarCbx.Position) { + scrollBarCbx.Position = ((ListView)comboBox.Subviews [1]).TopItem; + } + comboBox.SetNeedsDisplay (); + }; + + scrollBarCbx.OtherScrollBarView.ChangedPosition += () => { + ((ListView)comboBox.Subviews [1]).LeftItem = scrollBarCbx.OtherScrollBarView.Position; + if (((ListView)comboBox.Subviews [1]).LeftItem != scrollBarCbx.OtherScrollBarView.Position) { + scrollBarCbx.OtherScrollBarView.Position = ((ListView)comboBox.Subviews [1]).LeftItem; + } + comboBox.SetNeedsDisplay (); + }; + + comboBox.DrawContent += (e) => { + scrollBarCbx.Size = comboBox.Source.Count + 1; + scrollBarCbx.Position = ((ListView)comboBox.Subviews [1]).TopItem; + scrollBarCbx.OtherScrollBarView.Size = ((ListView)comboBox.Subviews [1]).Maxlength; + scrollBarCbx.OtherScrollBarView.Position = ((ListView)comboBox.Subviews [1]).LeftItem; + scrollBarCbx.Refresh (); + }; + + var btnMoveUp = new Button ("Move _Up") { X = 1, Y = Pos.Bottom(lbListView), From 8f4924e40f155bc5080af7c8c6a05ec7c3f2cc86 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Jan 2021 02:02:52 +0000 Subject: [PATCH 55/70] Fixing bug in EnsuresVisibilitySelectedItem on ListView. --- Terminal.Gui/Views/ListView.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 3413dfbf1a..de6a56aea8 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -679,9 +679,10 @@ public override bool OnLeave (View view) void EnsuresVisibilitySelectedItem () { + SuperView?.LayoutSubviews (); if (selected < top) { top = selected; - } else if (selected >= top + Frame.Height) { + } else if (Frame.Height > 0 && selected >= top + Frame.Height) { top = Math.Max (selected - Frame.Height + 2, 0); } } From 3d95a87f3e5d4a046609bf1c962971a6e8a6098d Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 17 Jan 2021 22:57:16 +0000 Subject: [PATCH 56/70] Allowing columnTrack on button click in TextView. --- Terminal.Gui/Views/TextView.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 50e9dc36f8..d3867486e4 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -1409,6 +1409,8 @@ public override bool MouseEvent (MouseEvent ev) } } PositionCursor (); + lastWasKill = false; + columnTrack = currentColumn; } else if (ev.Flags == MouseFlags.WheeledDown) { lastWasKill = false; columnTrack = currentColumn; From ea834b8e0e2c86919eec224ed1d788e8595e652c Mon Sep 17 00:00:00 2001 From: Trevor Sullivan Date: Mon, 18 Jan 2021 13:19:08 -0700 Subject: [PATCH 57/70] Add copyable instructions for .NET Core users Adding a simple copy/paste command to install Terminal.Gui into .NET Core projects --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 063559e6c9..9872ae7d22 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ You can force the use of `System.Console` on Unix as well; see `Core.cs`. ## Showcase & Examples * **[UI Catalog](https://github.com/migueldeicaza/gui.cs/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run` in the `UICatalog` directory to run the UI Catalog. -* **[Reactive Example](https://github.com/migueldeicaza/gui.cs/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) � a tool that converts all events in a NuGet package into observable wrappers. +* **[Reactive Example](https://github.com/migueldeicaza/gui.cs/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers. * **[Example (aka `demo.cs`)](https://github.com/migueldeicaza/gui.cs/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the simple demo. * **[Standalone Example](https://github.com/migueldeicaza/gui.cs/tree/master/StandaloneExample)** - A trivial .NET core sample application can be found in the `StandaloneExample` directory. Run `dotnet run` in directory to test. * **[F# Example](https://github.com/migueldeicaza/gui.cs/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#. @@ -185,6 +185,14 @@ The example above shows how to add views using both styles of layout supported b Use NuGet to install the `Terminal.Gui` NuGet package: https://www.nuget.org/packages/Terminal.Gui +### Installation in .NET Core Projects + +To install Terminal.Gui into a .NET Core project, use the `dotnet` CLI tool with following command. + +``` +dotnet package add Terminal.Gui +``` + ## Running and Building * Windows, Mac, and Linux - Build and run using the .NET SDK command line tools (`dotnet build` in the root directory). Run `UICatalog` with `dotnet ./UICatalog/bin/Debug/net5.0/UICatalog.dll` or by directly executing `./UICatalog/bin/Debug/net5.0/UICatalog.exe`. From e7f66f83508fd04dfe9ad23ad56b14f65d5d0063 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 19 Jan 2021 10:54:40 +0000 Subject: [PATCH 58/70] Simplifies the code more and makes it more understandable on the ScrollBarView. --- Terminal.Gui/Views/ScrollBarView.cs | 138 +++++++++++++++------------- UnitTests/ScrollBarViewTests.cs | 76 +++++++++++---- 2 files changed, 132 insertions(+), 82 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index c3963a6027..bc238b1add 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -166,8 +166,8 @@ public int Position { get => position; set { if (position != value) { - if (CanScroll (value - position, out int max, vertical) || max > 0) { - if (max > 0 && max == value - position) { + if (CanScroll (value - position, out int max, vertical)) { + if (max == value - position) { position = value; } else { position = Math.Max (position + max, 0); @@ -222,22 +222,6 @@ public bool ShowScrollIndicator { } } - void SetWidthHeight () - { - if (showBothScrollIndicator) { - Width = vertical ? 1 : Dim.Width (Host) - 1; - Height = vertical ? Dim.Height (Host) - 1 : 1; - otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 1; - otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 1 : 1; - } else if (showScrollIndicator) { - Width = vertical ? 1 : Dim.Width (Host) - 0; - Height = vertical ? Dim.Height (Host) - 0 : 1; - } else if (otherScrollBarView != null && otherScrollBarView.showScrollIndicator) { - otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 0; - otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 0 : 1; - } - } - /// /// Get or sets if the view-port is kept always visible in the area of this /// @@ -310,7 +294,7 @@ void ShowHideScrollBars () } else { contentBottomRightCorner.Visible = false; } - if (showBothScrollIndicator) { + if (showScrollIndicator) { Redraw (Bounds); } if (otherScrollBarView.showScrollIndicator) { @@ -349,6 +333,23 @@ bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) return pending; } + void SetWidthHeight () + { + if (showBothScrollIndicator) { + Width = vertical ? 1 : Dim.Width (Host) - 1; + Height = vertical ? Dim.Height (Host) - 1 : 1; + + otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 1; + otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 1 : 1; + } else if (showScrollIndicator) { + Width = vertical ? 1 : Dim.Width (Host) - 0; + Height = vertical ? Dim.Height (Host) - 0 : 1; + } else if (otherScrollBarView != null && otherScrollBarView.showScrollIndicator) { + otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 0; + otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 0 : 1; + } + } + int posTopTee; int posLeftTee; int posBottomTee; @@ -535,6 +536,7 @@ public override bool MouseEvent (MouseEvent me) && (Application.mouseGrabView == null || Application.mouseGrabView != this)) { Application.GrabMouse (this); } else if (me.Flags == MouseFlags.Button1Released && Application.mouseGrabView != null && Application.mouseGrabView == this) { + lastLocation = -1; Application.UngrabMouse (); return true; } else if (showScrollIndicator && (me.Flags == MouseFlags.WheeledDown || me.Flags == MouseFlags.WheeledUp || @@ -542,9 +544,6 @@ public override bool MouseEvent (MouseEvent me) return Host.MouseEvent (me); } - if (!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - lastLocation = -1; - } if (location == 0) { if (pos > 0) { Position = pos - 1; @@ -562,51 +561,66 @@ public override bool MouseEvent (MouseEvent me) b1 = Math.Max (b1 - 1, 0); } - if (location > b1 && location <= b2 + 1) { - if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { - if (location == 1 && posTopLeftTee <= 2) { + if (lastLocation == -1 && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { + if (location == 1) { + if (location < posTopLeftTee && CanScroll (-barsize, out int nv, vertical)) { + Position = pos + nv; + } + if (location == posTopLeftTee) { Position = 0; - } else if (location == barsize) { - CanScroll (Size - pos, out int nv, vertical); - if (nv > 0) { - Position = Math.Min (pos + nv, Size); - } - } else if (location < posTopLeftTee) { - if (CanScroll (-barsize, out int nv, vertical)) { - Position = pos + nv; - } } - } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - var mb = (b2 - b1) / 2; - var ml = mb + b1 + (mb == 0 ? 1 : 0); - if ((location > 1 || (location == 1 && posTopLeftTee > 1)) && ((location >= b1 && location <= ml) || (location < lastLocation && lastLocation > -1))) { + } else if (location == barsize) { + if (location > posBottomRightTee && CanScroll (barsize, out int nv, vertical)) { + Position = pos + nv; + } + if (location == posBottomRightTee && CanScroll (Size - pos, out nv, vertical)) { + Position = Math.Min (pos + nv, Size); + } + } else if (location < posTopLeftTee) { + if (CanScroll (-barsize, out int nv, vertical)) { + Position = pos + nv; + } + } else if (location > posBottomRightTee) { + if (CanScroll (barsize, out int nv, vertical)) { + Position = pos + nv; + } + } + } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { + var posBarLength = posBottomRightTee - posTopLeftTee; + + if (lastLocation > -1 || + location > 1 && location < barsize && location >= posTopLeftTee && location <= posBottomRightTee) { + if (lastLocation == -1) { lastLocation = location; - var np = location * Size / barsize; - if (CanScroll (np - pos, out int nv, vertical)) { - Position = pos + nv; - } - } else if (location != barsize && location > lastLocation) { - var np = location * Size / barsize; - if (CanScroll (np - pos, out int nv, vertical)) { - Position = pos + nv; - } - } else if (location == 1 && posTopLeftTee <= 2) { - Position = 0; - } else if (location == barsize) { - CanScroll (Size - pos, out int nv, vertical); - if (nv > 0) { + return true; + } + + var np = location * Size / barsize - lastLocation; + if (CanScroll (np - pos, out int nv, vertical)) { + Position = pos + nv; + } + + if (location > lastLocation && location == barsize) { + if (CanScroll (Size - pos, out nv, vertical)) { Position = Math.Min (pos + nv, Size); } + } else if (location < lastLocation && location == 1 && b2 <= 2) { + Position = 0; } - } - } else { - if (location >= b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) { - CanScroll (location, out int nv, vertical); - if (nv > 0) { + } else if (location > posBottomRightTee) { + if (CanScroll (barsize, out int nv, vertical)) { + Position = pos + nv; + } + } else if (location < posTopLeftTee) { + if (CanScroll (-barsize, out int nv, vertical)) { + Position = pos + nv; + } + } else if (location == 1 && b2 <= 2) { + Position = 0; + } else if (location == barsize) { + if (CanScroll (Size - pos, out int nv, vertical)) { Position = Math.Min (pos + nv, Size); } - } else if (location <= b1) { - Position = Math.Max (pos - barsize - location, 0); } } } @@ -623,9 +637,9 @@ internal bool CanScroll (int n, out int max, bool isVertical = false) var s = isVertical ? (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) : (KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0); - var newSize = Math.Min (size, position + n); - max = size > s + newSize ? n : size - (s + position) - 1; - if (size > s + newSize) { + var newSize = Math.Max (Math.Min (size - s, position + n), 0); + max = size > s + newSize ? (newSize == 0 ? -position : n) : size - (s + position) - 1; + if (size >= s + newSize && max != 0) { return true; } return false; diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index 8fcc73cc22..6532abae3a 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -58,7 +58,6 @@ private void _hostView_DrawContent (Rect obj) _scrollBar.Position = _hostView.Top; _scrollBar.OtherScrollBarView.Size = _hostView.Cols; _scrollBar.OtherScrollBarView.Position = _hostView.Left; - _scrollBar.ColorScheme = _scrollBar.OtherScrollBarView.ColorScheme = _hostView.ColorScheme; _scrollBar.Refresh (); } @@ -111,16 +110,6 @@ public void Hosting_Two_Vertical_ScrollBarView_Throws_ArgumentException () Assert.Throws (null, () => h.OtherScrollBarView = v); } - [Fact] - public void Scrolling_With_Default_Constructor_Do_Not_Scroll () - { - var sbv = new ScrollBarView { - Position = 1 - }; - Assert.NotEqual (1, sbv.Position); - Assert.Equal (0, sbv.Position); - } - [Fact] public void Hosting_Two_Horizontal_ScrollBarView_Throws_ArgumentException () { @@ -134,15 +123,22 @@ public void Hosting_Two_Horizontal_ScrollBarView_Throws_ArgumentException () Assert.Throws (null, () => h.OtherScrollBarView = v); } + [Fact] + public void Scrolling_With_Default_Constructor_Do_Not_Scroll () + { + var sbv = new ScrollBarView { + Position = 1 + }; + Assert.NotEqual (1, sbv.Position); + Assert.Equal (0, sbv.Position); + } + [Fact] public void Hosting_A_View_To_A_ScrollBarView () { RemoveHandlers (); _scrollBar = new ScrollBarView (_hostView, true); - _scrollBar.OtherScrollBarView = new ScrollBarView (_hostView, false); - _scrollBar.OtherScrollBarView = _scrollBar.OtherScrollBarView; - _scrollBar.OtherScrollBarView.OtherScrollBarView = _scrollBar; Assert.True (_scrollBar.IsVertical); Assert.False (_scrollBar.OtherScrollBarView.IsVertical); @@ -241,7 +237,8 @@ public void OtherScrollBarView_Not_Null () AddHandlers (); - Assert.Equal (_scrollBar.OtherScrollBarView, _scrollBar.OtherScrollBarView); + Assert.NotNull (_scrollBar.OtherScrollBarView); + Assert.NotEqual (_scrollBar, _scrollBar.OtherScrollBarView); Assert.Equal (_scrollBar.OtherScrollBarView.OtherScrollBarView, _scrollBar); } @@ -298,10 +295,14 @@ public void KeepContentAlwaysInViewport_False () _scrollBar.Position = 50; Assert.Equal (_scrollBar.Position, _scrollBar.Size - 1); Assert.Equal (_scrollBar.Position, _hostView.Top); + Assert.Equal (29, _scrollBar.Position); + Assert.Equal (29, _hostView.Top); _scrollBar.OtherScrollBarView.Position = 150; Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - 1); Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); + Assert.Equal (99, _scrollBar.OtherScrollBarView.Position); + Assert.Equal (99, _hostView.Left); } [Fact] @@ -314,6 +315,8 @@ public void AutoHideScrollBars_Check () _hostView.Redraw (_hostView.Bounds); Assert.True (_scrollBar.ShowScrollIndicator); Assert.True (_scrollBar.Visible); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ()); + Assert.Equal (1, _scrollBar.Bounds.Width); Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", _scrollBar.Height.ToString ()); Assert.Equal (24, _scrollBar.Bounds.Height); @@ -322,43 +325,76 @@ public void AutoHideScrollBars_Check () Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", _scrollBar.OtherScrollBarView.Width.ToString ()); Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ()); + Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height); _hostView.Lines = 10; _hostView.Redraw (_hostView.Bounds); Assert.False (_scrollBar.ShowScrollIndicator); Assert.False (_scrollBar.Visible); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ()); + Assert.Equal (1, _scrollBar.Bounds.Width); Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", _scrollBar.Height.ToString ()); Assert.Equal (24, _scrollBar.Bounds.Height); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))", + _scrollBar.OtherScrollBarView.Width.ToString ()); + Assert.Equal (80, _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ()); + Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height); _hostView.Cols = 60; _hostView.Redraw (_hostView.Bounds); + Assert.False (_scrollBar.ShowScrollIndicator); + Assert.False (_scrollBar.Visible); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ()); + Assert.Equal (1, _scrollBar.Bounds.Width); + Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", + _scrollBar.Height.ToString ()); + Assert.Equal (24, _scrollBar.Bounds.Height); Assert.False (_scrollBar.OtherScrollBarView.ShowScrollIndicator); Assert.False (_scrollBar.OtherScrollBarView.Visible); Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))", _scrollBar.OtherScrollBarView.Width.ToString ()); Assert.Equal (80, _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ()); + Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height); _hostView.Lines = 40; _hostView.Redraw (_hostView.Bounds); Assert.True (_scrollBar.ShowScrollIndicator); Assert.True (_scrollBar.Visible); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ()); + Assert.Equal (1, _scrollBar.Bounds.Width); Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))", _scrollBar.Height.ToString ()); Assert.Equal (25, _scrollBar.Bounds.Height); + Assert.False (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.False (_scrollBar.OtherScrollBarView.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))", + _scrollBar.OtherScrollBarView.Width.ToString ()); + Assert.Equal (80, _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ()); + Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height); _hostView.Cols = 120; _hostView.Redraw (_hostView.Bounds); - Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); - Assert.True (_scrollBar.OtherScrollBarView.Visible); - Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", - _scrollBar.OtherScrollBarView.Width.ToString ()); - Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width); Assert.True (_scrollBar.ShowScrollIndicator); Assert.True (_scrollBar.Visible); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ()); + Assert.Equal (1, _scrollBar.Bounds.Width); Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", _scrollBar.Height.ToString ()); Assert.Equal (24, _scrollBar.Bounds.Height); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.Visible); + Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))", + _scrollBar.OtherScrollBarView.Width.ToString ()); + Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width); + Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ()); + Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height); } } } From ce46fb3d3e39f164605f1b52569dfb6343d441c7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 19 Jan 2021 11:01:15 +0000 Subject: [PATCH 59/70] Don't need this. --- Terminal.Gui/Views/ScrollBarView.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index bc238b1add..ee22c826de 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -586,8 +586,6 @@ public override bool MouseEvent (MouseEvent me) } } } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - var posBarLength = posBottomRightTee - posTopLeftTee; - if (lastLocation > -1 || location > 1 && location < barsize && location >= posTopLeftTee && location <= posBottomRightTee) { if (lastLocation == -1) { From 6267d2742d8e1ea8cc61bb85071f43b803e66312 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 Jan 2021 02:20:48 +0000 Subject: [PATCH 60/70] KeepContentAlwaysInViewport is redrawing well now if it is false, showing always the left tee-right tee and top tee-bottom tee. --- Terminal.Gui/Views/ScrollBarView.cs | 90 ++++++++++++++--------------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index ee22c826de..35c4d97435 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -243,6 +243,9 @@ public bool KeepContentAlwaysInViewport { if (OtherScrollBarView != null && OtherScrollBarView.keepContentAlwaysInViewport != value) { OtherScrollBarView.KeepContentAlwaysInViewport = value; } + if (pos == 0) { + Refresh (); + } } } } @@ -397,8 +400,8 @@ public override void Redraw (Rect region) } } else { bh -= 2; - var by1 = position * bh / Size; - var by2 = KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / Size; + var by1 = KeepContentAlwaysInViewport ? position * bh / Size : position * bh / (Size + bh); + var by2 = KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / (Size + bh); if (KeepContentAlwaysInViewport && by1 == by2) { by1 = Math.Max (by1 - 1, 0); } @@ -458,8 +461,8 @@ public override void Redraw (Rect region) Driver.AddRune (Driver.RightArrow); } else { bw -= 2; - var bx1 = position * bw / Size; - var bx2 = KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / Size; + var bx1 = KeepContentAlwaysInViewport ? position * bw / Size : position * bw / (Size + bw); + var bx2 = KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / (Size + bw); if (KeepContentAlwaysInViewport && bx1 == bx2) { bx1 = Math.Max (bx1 - 1, 0); } @@ -508,6 +511,7 @@ public override void Redraw (Rect region) } int lastLocation = -1; + int posBarOffset; /// public override bool MouseEvent (MouseEvent me) @@ -553,56 +557,46 @@ public override bool MouseEvent (MouseEvent me) Position = pos + 1; } } else if (location > 0 && location < barsize + 1) { - var b1 = pos * (Size > 0 ? barsize / Size : 0); - var b2 = Size > 0 - ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) - : 0; - if (KeepContentAlwaysInViewport && b1 == b2) { - b1 = Math.Max (b1 - 1, 0); - } - - if (lastLocation == -1 && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1Clicked) { - if (location == 1) { - if (location < posTopLeftTee && CanScroll (-barsize, out int nv, vertical)) { - Position = pos + nv; - } - if (location == posTopLeftTee) { - Position = 0; - } - } else if (location == barsize) { - if (location > posBottomRightTee && CanScroll (barsize, out int nv, vertical)) { - Position = pos + nv; - } - if (location == posBottomRightTee && CanScroll (Size - pos, out nv, vertical)) { - Position = Math.Min (pos + nv, Size); - } - } else if (location < posTopLeftTee) { - if (CanScroll (-barsize, out int nv, vertical)) { - Position = pos + nv; - } - } else if (location > posBottomRightTee) { - if (CanScroll (barsize, out int nv, vertical)) { - Position = pos + nv; - } - } - } else if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - if (lastLocation > -1 || - location > 1 && location < barsize && location >= posTopLeftTee && location <= posBottomRightTee) { + //var b1 = pos * (Size > 0 ? barsize / Size : 0); + //var b2 = Size > 0 + // ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size) + // : 0; + //if (KeepContentAlwaysInViewport && b1 == b2) { + // b1 = Math.Max (b1 - 1, 0); + //} + + if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) + || me.Flags == MouseFlags.Button1Pressed) { + if (lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee)) { if (lastLocation == -1) { lastLocation = location; + posBarOffset = keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0; return true; } - var np = location * Size / barsize - lastLocation; - if (CanScroll (np - pos, out int nv, vertical)) { - Position = pos + nv; - } - - if (location > lastLocation && location == barsize) { - if (CanScroll (Size - pos, out nv, vertical)) { + if (location > lastLocation) { + if (location - posBarOffset < barsize) { + var np = ((location - posBarOffset) * Size / barsize) + (Size / barsize); + if (CanScroll (np - pos, out int nv, vertical)) { + Position = pos + nv; + } + } else if (CanScroll (Size - pos, out int nv, vertical)) { Position = Math.Min (pos + nv, Size); } - } else if (location < lastLocation && location == 1 && b2 <= 2) { + } else if (location < lastLocation) { + if (location - posBarOffset > 0) { + var np = ((location - posBarOffset) * Size / barsize) - (Size / barsize); + if (CanScroll (np - pos, out int nv, vertical)) { + Position = pos + nv; + } + } else { + Position = 0; + } + } else if (location - posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) { Position = 0; } } else if (location > posBottomRightTee) { @@ -613,7 +607,7 @@ public override bool MouseEvent (MouseEvent me) if (CanScroll (-barsize, out int nv, vertical)) { Position = pos + nv; } - } else if (location == 1 && b2 <= 2) { + } else if (location == 1 && posTopLeftTee <= 3) { Position = 0; } else if (location == barsize) { if (CanScroll (Size - pos, out int nv, vertical)) { From 3cdb1fe0e68f74098bd30e78220e284215610386 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 Jan 2021 13:37:31 +0000 Subject: [PATCH 61/70] Fixes the double scrolling when the arrows are clicked on the ScrollBarView. --- Terminal.Gui/Views/ScrollBarView.cs | 92 ++++++++++++++--------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 35c4d97435..665887997e 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -296,6 +296,9 @@ void ShowHideScrollBars () contentBottomRightCorner.Visible = true; } else { contentBottomRightCorner.Visible = false; + if (Application.mouseGrabView != null && Application.mouseGrabView == this) { + Application.UngrabMouse (); + } } if (showScrollIndicator) { Redraw (Bounds); @@ -516,11 +519,11 @@ public override void Redraw (Rect region) /// public override bool MouseEvent (MouseEvent me) { - if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && + if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1DoubleClicked && !me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && me.Flags != MouseFlags.Button1Released && me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp && me.Flags != MouseFlags.WheeledRight && - me.Flags != MouseFlags.WheeledLeft) { + me.Flags != MouseFlags.WheeledLeft && me.Flags != MouseFlags.Button1TripleClicked) { return false; } @@ -535,8 +538,7 @@ public override bool MouseEvent (MouseEvent me) barsize -= 2; var pos = Position; - if ((me.Flags.HasFlag (MouseFlags.Button1Pressed) || - me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) + if (me.Flags != MouseFlags.Button1Released && (Application.mouseGrabView == null || Application.mouseGrabView != this)) { Application.GrabMouse (this); } else if (me.Flags == MouseFlags.Button1Released && Application.mouseGrabView != null && Application.mouseGrabView == this) { @@ -565,54 +567,52 @@ public override bool MouseEvent (MouseEvent me) // b1 = Math.Max (b1 - 1, 0); //} - if (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) - || me.Flags == MouseFlags.Button1Pressed) { - if (lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee)) { - if (lastLocation == -1) { - lastLocation = location; - posBarOffset = keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0; - return true; - } + if (lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee + && me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) { + if (lastLocation == -1) { + lastLocation = location; + posBarOffset = keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0; + return true; + } - if (location > lastLocation) { - if (location - posBarOffset < barsize) { - var np = ((location - posBarOffset) * Size / barsize) + (Size / barsize); - if (CanScroll (np - pos, out int nv, vertical)) { - Position = pos + nv; - } - } else if (CanScroll (Size - pos, out int nv, vertical)) { - Position = Math.Min (pos + nv, Size); - } - } else if (location < lastLocation) { - if (location - posBarOffset > 0) { - var np = ((location - posBarOffset) * Size / barsize) - (Size / barsize); - if (CanScroll (np - pos, out int nv, vertical)) { - Position = pos + nv; - } - } else { - Position = 0; + if (location > lastLocation) { + if (location - posBarOffset < barsize) { + var np = ((location - posBarOffset) * Size / barsize) + (Size / barsize); + if (CanScroll (np - pos, out int nv, vertical)) { + Position = pos + nv; } - } else if (location - posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, vertical)) { - Position = Math.Min (pos + nv, Size); - } else if (location - posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, vertical)) { + } else if (CanScroll (Size - pos, out int nv, vertical)) { Position = Math.Min (pos + nv, Size); - } else if (location - posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) { - Position = 0; - } - } else if (location > posBottomRightTee) { - if (CanScroll (barsize, out int nv, vertical)) { - Position = pos + nv; } - } else if (location < posTopLeftTee) { - if (CanScroll (-barsize, out int nv, vertical)) { - Position = pos + nv; + } else if (location < lastLocation) { + if (location - posBarOffset > 0) { + var np = ((location - posBarOffset) * Size / barsize) - (Size / barsize); + if (CanScroll (np - pos, out int nv, vertical)) { + Position = pos + nv; + } + } else { + Position = 0; } - } else if (location == 1 && posTopLeftTee <= 3) { + } else if (location - posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, vertical)) { + Position = Math.Min (pos + nv, Size); + } else if (location - posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) { Position = 0; - } else if (location == barsize) { - if (CanScroll (Size - pos, out int nv, vertical)) { - Position = Math.Min (pos + nv, Size); - } + } + } else if (location > posBottomRightTee) { + if (CanScroll (barsize, out int nv, vertical)) { + Position = pos + nv; + } + } else if (location < posTopLeftTee) { + if (CanScroll (-barsize, out int nv, vertical)) { + Position = pos + nv; + } + } else if (location == 1 && posTopLeftTee <= 3) { + Position = 0; + } else if (location == barsize) { + if (CanScroll (Size - pos, out int nv, vertical)) { + Position = Math.Min (pos + nv, Size); } } } From f568166fb8becf7bc78bf57c1e8a7f7f1c53c7c8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 Jan 2021 14:54:34 +0000 Subject: [PATCH 62/70] Fixes #1091. Continuous button pressed not working properly on Linux. --- .../CursesDriver/CursesDriver.cs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 1bab57fb9a..53e5dd6d27 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -269,23 +269,9 @@ MouseEvent ToDriverMouse (Curses.MouseEvent cev) } if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Task.Run (async () => { - while (IsButtonPressed && LastMouseButtonPressed != null) { - await Task.Delay (100); - var me = new MouseEvent () { - X = cev.X, - Y = cev.Y, - Flags = mouseFlag - }; - - var view = Application.wantContinuousButtonPressedView; - if (view == null) - break; - if (IsButtonPressed && LastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { - mouseHandler (me); - //mainLoop.Driver.Wakeup (); - } - } + Application.MainLoop.AddIdle (() => { + ProcessContinuousButtonPressedAsync (cev, mouseFlag).ConfigureAwait (false); + return false; }); } @@ -342,7 +328,7 @@ MouseEvent ToDriverMouse (Curses.MouseEvent cev) }; } - private MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev) + MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev) { LastMouseButtonPressed = cev.ButtonState; var mf = GetButtonState (cev, true); @@ -358,7 +344,7 @@ private MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev) return mf; } - private MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev) + MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev) { var mf = MapCursesButton (cev.ButtonState); if (!cancelButtonClicked && LastMouseButtonPressed == null && !isReportMousePosition) { @@ -371,6 +357,26 @@ private MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev) return mf; } + async Task ProcessContinuousButtonPressedAsync (Curses.MouseEvent cev, MouseFlags mouseFlag) + { + while (IsButtonPressed && LastMouseButtonPressed != null) { + await Task.Delay (100); + var me = new MouseEvent () { + X = cev.X, + Y = cev.Y, + Flags = mouseFlag + }; + + var view = Application.wantContinuousButtonPressedView; + if (view == null) + break; + if (IsButtonPressed && LastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { + mouseHandler (me); + //mainLoop.Driver.Wakeup (); + } + } + } + MouseFlags GetButtonState (Curses.MouseEvent cev, bool pressed = false) { MouseFlags mf = default; From 1bf4db0bd01c147981502bf374fa69ab61c52990 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 Jan 2021 14:57:44 +0000 Subject: [PATCH 63/70] Reducing the ProcessContinuousButtonPressedAsync Task.Delay. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index fc97bcca6b..7ae30230b7 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -961,7 +961,7 @@ async Task ProcessButtonDoubleClickedAsync () async Task ProcessContinuousButtonPressedAsync (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag) { while (IsButtonPressed) { - await Task.Delay (200); + await Task.Delay (100); var me = new MouseEvent () { X = mouseEvent.MousePosition.X, Y = mouseEvent.MousePosition.Y, From 1c7e32e961a4477d520e908a4bf12cd8725edf59 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 Jan 2021 14:59:45 +0000 Subject: [PATCH 64/70] Removing private keyword. --- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 1bab57fb9a..7a57304146 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -342,7 +342,7 @@ MouseEvent ToDriverMouse (Curses.MouseEvent cev) }; } - private MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev) + MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev) { LastMouseButtonPressed = cev.ButtonState; var mf = GetButtonState (cev, true); @@ -358,7 +358,7 @@ private MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev) return mf; } - private MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev) + MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev) { var mf = MapCursesButton (cev.ButtonState); if (!cancelButtonClicked && LastMouseButtonPressed == null && !isReportMousePosition) { From 27a617abbac56bba86ff44f1a92cf52ed2175050 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 Jan 2021 17:44:46 +0000 Subject: [PATCH 65/70] There is no need use the Application.MainLoop.AddIdle as the async call seems to work properly now. --- Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 53e5dd6d27..cf4daa4ea2 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -269,10 +269,7 @@ MouseEvent ToDriverMouse (Curses.MouseEvent cev) } if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop.AddIdle (() => { - ProcessContinuousButtonPressedAsync (cev, mouseFlag).ConfigureAwait (false); - return false; - }); + ProcessContinuousButtonPressedAsync (cev, mouseFlag).ConfigureAwait (false); } From f0254f8b4d30646ae1673c31e627eaf67797f994 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 21 Jan 2021 00:48:32 +0000 Subject: [PATCH 66/70] Fixes #1093. NetDriver is reading badly sometimes the virtual terminal sequences. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index a4608acd94..1a50df0917 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -290,16 +290,18 @@ void SplitCSI (ConsoleKeyInfo [] cki, ref InputResult inputResult, ref ConsoleKe int curCSI = 0; char previousKChar = '\0'; if (nCSI > 1) { - foreach (var ck in cki) { + for (int i = 0; i < cki.Length; i++) { + var ck = cki [i]; if (NumberOfCSI > 0 && nCSI - curCSI > NumberOfCSI) { - if (ck.KeyChar == '\x1b' - || (ck.KeyChar == '[' && previousKChar != '\x1b')) { + if (cki [i + 1].KeyChar == '\x1b' && previousKChar != '\0') { curCSI++; + previousKChar = '\0'; + } else { + previousKChar = ck.KeyChar; } - previousKChar = ck.KeyChar; continue; } - if (ck.KeyChar == '\x1b' || (ck.KeyChar == '[' && previousKChar != '\x1b')) { + if (ck.KeyChar == '\x1b') { if (ck.KeyChar == 'R') { ResizeArray (ck); } @@ -310,7 +312,9 @@ void SplitCSI (ConsoleKeyInfo [] cki, ref InputResult inputResult, ref ConsoleKe length = 0; } ResizeArray (ck); - previousKChar = ck.KeyChar; + if (i == cki.Length - 1 && splitedCki.Length > 0) { + DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, splitedCki, ref mod); + } } } else { DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, cki, ref mod); From 020111342536a0251f67bb4bd403e2af8c05d37c Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 21 Jan 2021 02:14:41 +0000 Subject: [PATCH 67/70] Added more mouse buttons codes. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 1a50df0917..8459709dc8 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -539,6 +539,7 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 0: case 8: case 16: + case 24: case 32: case 40: buttonState = c == 'M' ? MouseButtonState.Button1Pressed @@ -547,6 +548,7 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 1: case 9: case 17: + case 25: case 33: case 41: buttonState = c == 'M' ? MouseButtonState.Button2Pressed @@ -555,6 +557,7 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 2: case 10: case 18: + case 26: case 34: case 42: buttonState = c == 'M' ? MouseButtonState.Button3Pressed @@ -570,10 +573,14 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 65: buttonState = MouseButtonState.ButtonWheeledDown; break; + case 68: case 72: + case 80: buttonState = MouseButtonState.ButtonWheeledLeft; // Ctrl+ButtonWheeledUp break; + case 69: case 73: + case 81: buttonState = MouseButtonState.ButtonWheeledRight; // Ctrl+ButtonWheeledDown break; } @@ -582,17 +589,28 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 8: case 9: case 10: + buttonState |= MouseButtonState.ButtonAlt; + break; case 16: case 17: case 18: case 43: buttonState |= MouseButtonState.ButtonCtrl; break; + case 24: + case 25: + case 26: + buttonState |= MouseButtonState.ButtonAlt | MouseButtonState.ButtonCtrl; + break; case 32: case 33: case 34: buttonState |= MouseButtonState.ReportMousePosition; break; + case 68: + case 69: + buttonState |= MouseButtonState.ButtonShift; + break; } } } From cc7e5878dccf2705eab99f2348f7f92afd3bc80e Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 21 Jan 2021 21:07:00 +0000 Subject: [PATCH 68/70] Fixes the button release issue after button pressed and moved. --- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 173 ++++++++++++++++++----- 1 file changed, 137 insertions(+), 36 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 8459709dc8..2dd23dbdd1 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -497,6 +497,7 @@ void GetRequestEvent (char [] kChar) bool isButtonDoubleClicked; bool isButtonTripleClicked; bool isProcContBtnPressedRuning; + bool isButtonReleased; void GetMouseEvent (ConsoleKeyInfo [] cki) { @@ -529,11 +530,11 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) } else if (c == 'm' || c == 'M') { point.Y = int.Parse (value) + Console.WindowTop - 1; - if (c == 'M') { - isButtonPressed = true; - } else if (c == 'm') { - isButtonPressed = false; - } + //if (c == 'M') { + // isButtonPressed = true; + //} else if (c == 'm') { + // isButtonPressed = false; + //} switch (buttonCode) { case 0: @@ -541,7 +542,10 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 16: case 24: case 32: + case 36: case 40: + case 48: + case 56: buttonState = c == 'M' ? MouseButtonState.Button1Pressed : MouseButtonState.Button1Released; break; @@ -550,21 +554,40 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 17: case 25: case 33: + case 37: case 41: + case 45: + case 49: + case 53: + case 57: + case 61: buttonState = c == 'M' ? MouseButtonState.Button2Pressed : MouseButtonState.Button2Released; break; case 2: case 10: + case 14: case 18: + case 22: case 26: + case 30: case 34: case 42: + case 46: + case 50: + case 54: + case 58: + case 62: buttonState = c == 'M' ? MouseButtonState.Button3Pressed : MouseButtonState.Button3Released; break; case 35: + case 39: case 43: + case 47: + case 55: + case 59: + case 63: buttonState = MouseButtonState.ReportMousePosition; break; case 64: @@ -576,12 +599,12 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 68: case 72: case 80: - buttonState = MouseButtonState.ButtonWheeledLeft; // Ctrl+ButtonWheeledUp + buttonState = MouseButtonState.ButtonWheeledLeft; // Shift/Ctrl+ButtonWheeledUp break; case 69: case 73: case 81: - buttonState = MouseButtonState.ButtonWheeledRight; // Ctrl+ButtonWheeledDown + buttonState = MouseButtonState.ButtonWheeledRight; // Shift/Ctrl+ButtonWheeledDown break; } // Modifiers. @@ -589,28 +612,74 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) case 8: case 9: case 10: + case 43: buttonState |= MouseButtonState.ButtonAlt; break; + case 14: + case 47: + buttonState |= MouseButtonState.ButtonAlt | MouseButtonState.ButtonShift; + break; case 16: case 17: case 18: - case 43: + case 51: buttonState |= MouseButtonState.ButtonCtrl; break; + case 22: + case 55: + buttonState |= MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift; + break; case 24: case 25: case 26: + case 59: buttonState |= MouseButtonState.ButtonAlt | MouseButtonState.ButtonCtrl; break; + case 30: + case 63: + buttonState |= MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift | MouseButtonState.ButtonAlt; + break; case 32: case 33: case 34: buttonState |= MouseButtonState.ReportMousePosition; break; + case 36: + case 37: + buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonShift; + break; + case 39: case 68: case 69: buttonState |= MouseButtonState.ButtonShift; break; + case 40: + case 41: + case 42: + buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonAlt; + break; + case 45: + case 46: + buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonAlt | MouseButtonState.ButtonShift; + break; + case 48: + case 49: + case 50: + buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl; + break; + case 53: + case 54: + buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift; + break; + case 56: + case 57: + case 58: + buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl | MouseButtonState.ButtonAlt; + break; + case 61: + case 62: + buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift | MouseButtonState.ButtonAlt; + break; } } } @@ -618,6 +687,14 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) mouseEvent.Position.Y = point.Y; mouseEvent.ButtonState = buttonState; + if ((buttonState & MouseButtonState.Button1Pressed) != 0 + || (buttonState & MouseButtonState.Button2Pressed) != 0 + || (buttonState & MouseButtonState.Button3Pressed) != 0) { + isButtonPressed = true; + } else { + isButtonPressed = false; + } + if ((isButtonClicked || isButtonDoubleClicked || isButtonTripleClicked) && ((buttonState & MouseButtonState.Button1Released) != 0 || (buttonState & MouseButtonState.Button2Released) != 0 @@ -652,6 +729,16 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) return; } + if (!isButtonPressed && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked + && !isButtonReleased + && ((buttonState & MouseButtonState.Button1Released) == 0 + && (buttonState & MouseButtonState.Button2Released) == 0 + && (buttonState & MouseButtonState.Button3Released) == 0)) { + ProcessButtonReleased (lastMouseEvent); + inputReady.Set (); + return; + } + inputResultQueue.Enqueue (new InputResult () { EventType = EventType.Mouse, MouseEvent = mouseEvent @@ -676,6 +763,7 @@ void GetMouseEvent (ConsoleKeyInfo [] cki) lastMouseEvent = mouseEvent; if (isButtonPressed && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked && !isProcContBtnPressedRuning) { + isButtonReleased = false; Application.MainLoop.AddIdle (() => { ProcessContinuousButtonPressedAsync ().ConfigureAwait (false); return false; @@ -691,22 +779,18 @@ void ProcessButtonClicked (MouseEvent mouseEvent) Position = mouseEvent.Position, ButtonState = mouseEvent.ButtonState }; - switch (mouseEvent.ButtonState) { - case MouseButtonState.Button1Released: + if ((mouseEvent.ButtonState & MouseButtonState.Button1Released) != 0) { me.ButtonState &= ~MouseButtonState.Button1Released; me.ButtonState |= MouseButtonState.Button1Clicked; - break; - case MouseButtonState.Button2Released: + } else if ((mouseEvent.ButtonState & MouseButtonState.Button2Released) != 0) { me.ButtonState &= ~MouseButtonState.Button2Released; me.ButtonState |= MouseButtonState.Button2Clicked; - break; - case MouseButtonState.Button3Released: + } else if ((mouseEvent.ButtonState & MouseButtonState.Button3Released) != 0) { me.ButtonState &= ~MouseButtonState.Button3Released; me.ButtonState |= MouseButtonState.Button3Clicked; - break; - default: - return; } + isButtonReleased = true; + inputResultQueue.Enqueue (new InputResult () { EventType = EventType.Mouse, MouseEvent = me @@ -719,22 +803,18 @@ void ProcessButtonDoubleClicked (MouseEvent mouseEvent) Position = mouseEvent.Position, ButtonState = mouseEvent.ButtonState }; - switch (mouseEvent.ButtonState) { - case MouseButtonState.Button1Pressed: + if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) { me.ButtonState &= ~MouseButtonState.Button1Pressed; me.ButtonState |= MouseButtonState.Button1DoubleClicked; - break; - case MouseButtonState.Button2Pressed: + } else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) { me.ButtonState &= ~MouseButtonState.Button2Pressed; me.ButtonState |= MouseButtonState.Button2DoubleClicked; - break; - case MouseButtonState.Button3Pressed: + } else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) { me.ButtonState &= ~MouseButtonState.Button3Pressed; me.ButtonState |= MouseButtonState.Button3DoubleClicked; - break; - default: - return; } + isButtonReleased = true; + inputResultQueue.Enqueue (new InputResult () { EventType = EventType.Mouse, MouseEvent = me @@ -747,22 +827,18 @@ void ProcessButtonTripleClicked (MouseEvent mouseEvent) Position = mouseEvent.Position, ButtonState = mouseEvent.ButtonState }; - switch (mouseEvent.ButtonState) { - case MouseButtonState.Button1Pressed: + if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) { me.ButtonState &= ~MouseButtonState.Button1Pressed; me.ButtonState |= MouseButtonState.Button1TripleClicked; - break; - case MouseButtonState.Button2Pressed: + } else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) { me.ButtonState &= ~MouseButtonState.Button2Pressed; me.ButtonState |= MouseButtonState.Button2TrippleClicked; - break; - case MouseButtonState.Button3Pressed: + } else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) { me.ButtonState &= ~MouseButtonState.Button3Pressed; me.ButtonState |= MouseButtonState.Button3TripleClicked; - break; - default: - return; } + isButtonReleased = true; + inputResultQueue.Enqueue (new InputResult () { EventType = EventType.Mouse, MouseEvent = me @@ -790,7 +866,32 @@ async Task ProcessContinuousButtonPressedAsync () } } isProcContBtnPressedRuning = false; - isButtonPressed = false; + //isButtonPressed = false; + } + + void ProcessButtonReleased (MouseEvent mouseEvent) + { + var me = new MouseEvent () { + Position = mouseEvent.Position, + ButtonState = mouseEvent.ButtonState + }; + if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) { + me.ButtonState &= ~(MouseButtonState.Button1Pressed | MouseButtonState.ReportMousePosition); + me.ButtonState |= MouseButtonState.Button1Released; + } else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) { + me.ButtonState &= ~(MouseButtonState.Button2Pressed | MouseButtonState.ReportMousePosition); + me.ButtonState |= MouseButtonState.Button2Released; + } else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) { + me.ButtonState &= ~(MouseButtonState.Button3Pressed | MouseButtonState.ReportMousePosition); + me.ButtonState |= MouseButtonState.Button3Released; + } + isButtonReleased = true; + lastMouseEvent = me; + + inputResultQueue.Enqueue (new InputResult () { + EventType = EventType.Mouse, + MouseEvent = me + }); } ConsoleModifiers GetConsoleModifiers (uint keyChar) From 0eb3b993abe53317f5474021839ac330c73bb907 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 22 Jan 2021 00:23:01 +0000 Subject: [PATCH 69/70] Host must deal with the mouse wheel. --- Terminal.Gui/Views/ScrollBarView.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 665887997e..271e6dcb2d 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -545,7 +545,8 @@ public override bool MouseEvent (MouseEvent me) lastLocation = -1; Application.UngrabMouse (); return true; - } else if (showScrollIndicator && (me.Flags == MouseFlags.WheeledDown || me.Flags == MouseFlags.WheeledUp || + } + if (showScrollIndicator && (me.Flags == MouseFlags.WheeledDown || me.Flags == MouseFlags.WheeledUp || me.Flags == MouseFlags.WheeledRight || me.Flags == MouseFlags.WheeledLeft)) { return Host.MouseEvent (me); } From f9f68048a2e1a9f8259674b80baebab35b25e354 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 22 Jan 2021 02:21:56 +0000 Subject: [PATCH 70/70] The host only need to pass the real sizes and the ScrollBarView automatically consider the extra line and column. --- Terminal.Gui/Views/ScrollBarView.cs | 39 +++++++++++++++----- UICatalog/Scenarios/Editor.cs | 4 +- UICatalog/Scenarios/ListViewWithSelection.cs | 4 +- UICatalog/Scenarios/ListsAndCombos.cs | 8 ++-- UnitTests/ScrollBarViewTests.cs | 32 ++++++++++------ 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index 271e6dcb2d..6f8e499ec0 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -93,6 +93,7 @@ public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = Host = host, OtherScrollBarView = this, }; + OtherScrollBarView.hosted = true; OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView); @@ -108,7 +109,7 @@ public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; } - private void ContentBottomRightCorner_MouseClick (MouseEventArgs me) + void ContentBottomRightCorner_MouseClick (MouseEventArgs me) { if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp || me.MouseEvent.Flags == MouseFlags.WheeledRight || me.MouseEvent.Flags == MouseFlags.WheeledLeft) { @@ -148,7 +149,11 @@ public bool IsVertical { public int Size { get => size; set { - size = value; + if (hosted || (otherScrollBarView != null && otherScrollBarView.hosted)) { + size = value + 1; + } else { + size = value; + } SetNeedsDisplay (); } } @@ -175,6 +180,10 @@ public int Position { } else if (max < 0) { position = Math.Max (position + max, 0); } + var s = GetBarsize (vertical); + if (position + s == size && (hosted || (otherScrollBarView != null && otherScrollBarView.hosted))) { + position++; + } OnChangedPosition (); SetNeedsDisplay (); } @@ -293,9 +302,13 @@ void ShowHideScrollBars () OtherScrollBarView.SetRelativeLayout (OtherScrollBarView.Bounds); if (showBothScrollIndicator) { - contentBottomRightCorner.Visible = true; - } else { - contentBottomRightCorner.Visible = false; + if (contentBottomRightCorner != null) { + contentBottomRightCorner.Visible = true; + } + } else if (!showScrollIndicator) { + if (contentBottomRightCorner != null) { + contentBottomRightCorner.Visible = false; + } if (Application.mouseGrabView != null && Application.mouseGrabView == this) { Application.UngrabMouse (); } @@ -508,7 +521,7 @@ public override void Redraw (Rect region) } } - if (hosted && showBothScrollIndicator) { + if (contentBottomRightCorner != null && hosted && showBothScrollIndicator) { contentBottomRightCorner.Redraw (contentBottomRightCorner.Bounds); } } @@ -627,9 +640,7 @@ internal bool CanScroll (int n, out int max, bool isVertical = false) max = 0; return false; } - var s = isVertical ? - (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) : - (KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0); + int s = GetBarsize (isVertical); var newSize = Math.Max (Math.Min (size - s, position + n), 0); max = size > s + newSize ? (newSize == 0 ? -position : n) : size - (s + position) - 1; if (size >= s + newSize && max != 0) { @@ -637,5 +648,15 @@ internal bool CanScroll (int n, out int max, bool isVertical = false) } return false; } + + int GetBarsize (bool isVertical) + { + if (Host == null) { + return 0; + } + return isVertical ? + (KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) : + (KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0); + } } } diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index e305aa5944..041ed15ca4 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -89,9 +89,9 @@ public override void Init (Toplevel top, ColorScheme colorScheme) }; _textView.DrawContent += (e) => { - _scrollBar.Size = _textView.Lines; + _scrollBar.Size = _textView.Lines - 1; _scrollBar.Position = _textView.TopRow; - _scrollBar.OtherScrollBarView.Size = _textView.Maxlength + 1; + _scrollBar.OtherScrollBarView.Size = _textView.Maxlength; _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn; _scrollBar.LayoutSubviews (); _scrollBar.Refresh (); diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index 369b7c8cad..9e746cf3c0 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -74,9 +74,9 @@ public override void Setup () }; _listView.DrawContent += (e) => { - _scrollBar.Size = _listView.Source.Count; + _scrollBar.Size = _listView.Source.Count - 1; _scrollBar.Position = _listView.TopItem; - _scrollBar.OtherScrollBarView.Size = _listView.Maxlength; + _scrollBar.OtherScrollBarView.Size = _listView.Maxlength - 1; _scrollBar.OtherScrollBarView.Position = _listView.LeftItem; _scrollBar.Refresh (); }; diff --git a/UICatalog/Scenarios/ListsAndCombos.cs b/UICatalog/Scenarios/ListsAndCombos.cs index b0da52150a..56fa9c0f25 100644 --- a/UICatalog/Scenarios/ListsAndCombos.cs +++ b/UICatalog/Scenarios/ListsAndCombos.cs @@ -58,9 +58,9 @@ public override void Setup () }; listview.DrawContent += (e) => { - _scrollBar.Size = listview.Source.Count; + _scrollBar.Size = listview.Source.Count - 1; _scrollBar.Position = listview.TopItem; - _scrollBar.OtherScrollBarView.Size = listview.Maxlength; + _scrollBar.OtherScrollBarView.Size = listview.Maxlength - 1; _scrollBar.OtherScrollBarView.Position = listview.LeftItem; _scrollBar.Refresh (); }; @@ -102,9 +102,9 @@ public override void Setup () }; comboBox.DrawContent += (e) => { - scrollBarCbx.Size = comboBox.Source.Count + 1; + scrollBarCbx.Size = comboBox.Source.Count; scrollBarCbx.Position = ((ListView)comboBox.Subviews [1]).TopItem; - scrollBarCbx.OtherScrollBarView.Size = ((ListView)comboBox.Subviews [1]).Maxlength; + scrollBarCbx.OtherScrollBarView.Size = ((ListView)comboBox.Subviews [1]).Maxlength - 1; scrollBarCbx.OtherScrollBarView.Position = ((ListView)comboBox.Subviews [1]).LeftItem; scrollBarCbx.Refresh (); }; diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index 6532abae3a..1c58f93638 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -153,9 +153,9 @@ public void Hosting_A_View_To_A_ScrollBarView () _hostView.Redraw (_hostView.Bounds); Assert.Equal (_scrollBar.Position, _hostView.Top); - Assert.Equal (_scrollBar.Size, _hostView.Lines); + Assert.Equal (_scrollBar.Size, _hostView.Lines + 1); Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); - Assert.Equal (_scrollBar.OtherScrollBarView.Size, _hostView.Cols); + Assert.Equal (_scrollBar.OtherScrollBarView.Size, _hostView.Cols + 1); } [Fact] @@ -264,8 +264,8 @@ public void KeepContentAlwaysInViewport_True () Assert.Equal (25, _hostView.Bounds.Height); Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width); Assert.Equal (24, _scrollBar.Bounds.Height); - Assert.Equal (30, _scrollBar.Size); - Assert.Equal (100, _scrollBar.OtherScrollBarView.Size); + Assert.Equal (31, _scrollBar.Size); + Assert.Equal (101, _scrollBar.OtherScrollBarView.Size); Assert.True (_scrollBar.ShowScrollIndicator); Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); Assert.True (_scrollBar.Visible); @@ -274,14 +274,22 @@ public void KeepContentAlwaysInViewport_True () _scrollBar.Position = 50; Assert.Equal (_scrollBar.Position, _scrollBar.Size - _scrollBar.Bounds.Height); Assert.Equal (_scrollBar.Position, _hostView.Top); - Assert.Equal (6, _scrollBar.Position); - Assert.Equal (6, _hostView.Top); + Assert.Equal (7, _scrollBar.Position); + Assert.Equal (7, _hostView.Top); + Assert.True (_scrollBar.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.True (_scrollBar.Visible); + Assert.True (_scrollBar.OtherScrollBarView.Visible); _scrollBar.OtherScrollBarView.Position = 150; Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - _scrollBar.OtherScrollBarView.Bounds.Width); Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); - Assert.Equal (21, _scrollBar.OtherScrollBarView.Position); - Assert.Equal (21, _hostView.Left); + Assert.Equal (22, _scrollBar.OtherScrollBarView.Position); + Assert.Equal (22, _hostView.Left); + Assert.True (_scrollBar.ShowScrollIndicator); + Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator); + Assert.True (_scrollBar.Visible); + Assert.True (_scrollBar.OtherScrollBarView.Visible); } [Fact] @@ -295,14 +303,14 @@ public void KeepContentAlwaysInViewport_False () _scrollBar.Position = 50; Assert.Equal (_scrollBar.Position, _scrollBar.Size - 1); Assert.Equal (_scrollBar.Position, _hostView.Top); - Assert.Equal (29, _scrollBar.Position); - Assert.Equal (29, _hostView.Top); + Assert.Equal (30, _scrollBar.Position); + Assert.Equal (30, _hostView.Top); _scrollBar.OtherScrollBarView.Position = 150; Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - 1); Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left); - Assert.Equal (99, _scrollBar.OtherScrollBarView.Position); - Assert.Equal (99, _hostView.Left); + Assert.Equal (100, _scrollBar.OtherScrollBarView.Position); + Assert.Equal (100, _hostView.Left); } [Fact]