From 671fa7d2b81739ffb70dba0fb860a15a8d1a031b Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 21 Sep 2022 17:40:57 +0100 Subject: [PATCH 01/58] Added help menu from the UICatalog into the StandaloneExample. --- StandaloneExample/Program.cs | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/StandaloneExample/Program.cs b/StandaloneExample/Program.cs index 8a2103074..4f5925b8c 100644 --- a/StandaloneExample/Program.cs +++ b/StandaloneExample/Program.cs @@ -5,6 +5,8 @@ using NStack; using System.Text; using Rune = System.Rune; + using System.Runtime.InteropServices; + using System.Diagnostics; static class Demo { class Box10x : View { @@ -220,6 +222,19 @@ Width = Dim.Fill (), Height = Dim.Fill () - 1 }; + + StringBuilder aboutMessage = new StringBuilder (); + aboutMessage.AppendLine (@"A comprehensive sample library for"); + aboutMessage.AppendLine (@""); + aboutMessage.AppendLine (@" _______ _ _ _____ _ "); + aboutMessage.AppendLine (@" |__ __| (_) | | / ____| (_) "); + aboutMessage.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ "); + aboutMessage.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | "); + aboutMessage.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | "); + aboutMessage.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| "); + aboutMessage.AppendLine (@""); + aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); + var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_New", "Creates new file", NewFile), @@ -238,6 +253,12 @@ miScrollViewCheck = new MenuBarItem ("ScrollView", new MenuItem [] { new MenuItem ("Box10x", "", () => ScrollViewCheck()) {CheckType = MenuItemCheckStyle.Radio, Checked = true }, new MenuItem ("Filler", "", () => ScrollViewCheck()) {CheckType = MenuItemCheckStyle.Radio } + }), + new MenuBarItem ("_Help", new MenuItem [] { + new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1), + new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2), + new MenuItem ("_About...", "About UI Catalog", + () => MessageBox.Query ("About UI Catalog", aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A) }) }); @@ -261,5 +282,32 @@ Application.Shutdown (); } + + private static void OpenUrl (string url) + { + try { + if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { + url = url.Replace ("&", "^&"); + Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true }); + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) { + using (var process = new Process { + StartInfo = new ProcessStartInfo { + FileName = "xdg-open", + Arguments = url, + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true, + UseShellExecute = false + } + }) { + process.Start (); + } + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { + Process.Start ("open", url); + } + } catch { + throw; + } + } } } \ No newline at end of file From 92dc7f04d4535efad236500d7c621cdc87564a38 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 21 Sep 2022 18:54:45 +0100 Subject: [PATCH 02/58] Fixes #2053. MessageBox.Query still not wrapping with v1.8.0 on windows/net6.0. --- Terminal.Gui/Windows/MessageBox.cs | 6 +- UnitTests/MessageBoxTests.cs | 104 +++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Windows/MessageBox.cs b/Terminal.Gui/Windows/MessageBox.cs index c1cb32682..9d9c74864 100644 --- a/Terminal.Gui/Windows/MessageBox.cs +++ b/Terminal.Gui/Windows/MessageBox.cs @@ -248,9 +248,9 @@ namespace Terminal.Gui { } else { maxWidthLine = width; } - int textWidth = TextFormatter.MaxWidth (message, maxWidthLine); + int textWidth = Math.Min (TextFormatter.MaxWidth (message, maxWidthLine), Application.Driver.Cols); int textHeight = TextFormatter.MaxLines (message, textWidth); // message.Count (ustring.Make ('\n')) + 1; - int msgboxHeight = Math.Max (1, textHeight) + 4; // textHeight + (top + top padding + buttons + bottom) + int msgboxHeight = Math.Min (Math.Max (1, textHeight) + 4, Application.Driver.Rows); // textHeight + (top + top padding + buttons + bottom) // Create button array for Dialog int count = 0; @@ -300,7 +300,7 @@ namespace Terminal.Gui { if (width == 0 & height == 0) { // Dynamically size Width - d.Width = Math.Max (maxWidthLine, Math.Max (title.ConsoleWidth, Math.Max (textWidth + 2, d.GetButtonsWidth ()))); // textWidth + (left + padding + padding + right) + d.Width = Math.Min (Math.Max (maxWidthLine, Math.Max (title.ConsoleWidth, Math.Max (textWidth + 2, d.GetButtonsWidth ()))), Application.Driver.Cols); // textWidth + (left + padding + padding + right) } // Setup actions diff --git a/UnitTests/MessageBoxTests.cs b/UnitTests/MessageBoxTests.cs index 8bbc9fcf4..44518ba68 100644 --- a/UnitTests/MessageBoxTests.cs +++ b/UnitTests/MessageBoxTests.cs @@ -154,5 +154,109 @@ namespace Terminal.Gui.Views { Application.Run (); } + + [Fact, AutoInitShutdown] + public void MessageBox_With_A_Label_Without_Spaces () + { + var iterations = -1; + Application.Begin (Application.Top); + + Application.Iteration += () => { + iterations++; + + if (iterations == 0) { + MessageBox.Query ("mywindow", new string ('f', 2000), "ok"); + + Application.RequestStop (); + } else if (iterations == 1) { + Application.Top.Redraw (Application.Top.Bounds); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌ mywindow ────────────────────────────────────────────────────────────────────┐ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│ +│ [◦ ok ◦] │ +└──────────────────────────────────────────────────────────────────────────────┘ +", output); + + Application.RequestStop (); + } + }; + + Application.Run (); + } + + [Fact, AutoInitShutdown] + public void MessageBox_With_A_Label_With_Spaces () + { + var iterations = -1; + Application.Begin (Application.Top); + + Application.Iteration += () => { + iterations++; + + if (iterations == 0) { + var sb = new StringBuilder (); + for (int i = 0; i < 1000; i++) + sb.Append ("ff "); + + MessageBox.Query ("mywindow", sb.ToString (), "ok"); + + Application.RequestStop (); + } else if (iterations == 1) { + Application.Top.Redraw (Application.Top.Bounds); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌ mywindow ────────────────────────────────────────────────────────────────────┐ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │ +│ [◦ ok ◦] │ +└──────────────────────────────────────────────────────────────────────────────┘ +", output); + + Application.RequestStop (); + } + }; + + Application.Run (); + } } } \ No newline at end of file From 8443f2973f90a941575fa03964d793023dba4600 Mon Sep 17 00:00:00 2001 From: Tig Kindel Date: Wed, 21 Sep 2022 14:58:20 -0700 Subject: [PATCH 03/58] Removed packages.config (defunct). Relnotes for 1.8.1 --- Terminal.Gui/Terminal.Gui.csproj | 3 +++ packages.config | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 packages.config diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index aee4c52cb..39d2e1ff2 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -79,6 +79,9 @@ A toolkit for building rich console apps for .NET that works on Windows, Mac, and Linux/Unix. Terminal.Gui - Cross Platform Terminal user interface toolkit for .NET + Release v1.8.1 + * Fixes #2053. MessageBox.Query not wrapping correctly + Release v1.8.0 * Fixes #2043. Update to NStack v1.0.3 * Fixes #2045. TrySetClipboardData test must be enclosed with a lock. diff --git a/packages.config b/packages.config deleted file mode 100644 index 72fb5fb96..000000000 --- a/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 03500150a10738e97f7bdc15d892dc1ba7e306a8 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 23 Sep 2022 17:07:47 +0100 Subject: [PATCH 04/58] Fixes #2061. WindowsDriver sometimes returns badly mouse flag zero on mouse moved. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index e5f65fbfd..e25027050 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -1123,11 +1123,9 @@ namespace Terminal.Gui { } } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) { + mouseFlag = MouseFlags.ReportMousePosition; if (mouseEvent.MousePosition.X != pointMove.X || mouseEvent.MousePosition.Y != pointMove.Y) { - mouseFlag = MouseFlags.ReportMousePosition; pointMove = new Point (mouseEvent.MousePosition.X, mouseEvent.MousePosition.Y); - } else { - mouseFlag = 0; } } else if (mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0) { mouseFlag = 0; From 834a942302f4909af6602e9d0111c9c5545b30b0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 24 Sep 2022 23:11:09 +0100 Subject: [PATCH 05/58] Fixes #2064. mouseGrabView must have a track to allow the views who use it having some control. --- Terminal.Gui/Core/Application.cs | 37 ++++++++++++- Terminal.Gui/Views/Menu.cs | 12 ++--- Terminal.Gui/Views/ScrollBarView.cs | 6 +-- Terminal.Gui/Views/ScrollView.cs | 2 +- Terminal.Gui/Views/TextField.cs | 6 +-- Terminal.Gui/Views/TextView.cs | 4 +- UnitTests/ApplicationTests.cs | 81 +++++++++++++++++++++++++---- UnitTests/ContextMenuTests.cs | 16 +++--- 8 files changed, 130 insertions(+), 34 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index eb1200999..f0764905c 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -581,7 +581,22 @@ namespace Terminal.Gui { return top; } - internal static View mouseGrabView; + static View mouseGrabView; + + /// + /// The view that grabbed the mouse, to where will be routed all the mouse events. + /// + public static View MouseGrabView => mouseGrabView; + + /// + /// Event to be invoked when a view grab the mouse. + /// + public static event Action GrabbedMouse; + + /// + /// Event to be invoked when a view ungrab the mouse. + /// + public static event Action UnGrabbedMouse; /// /// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called. @@ -592,6 +607,7 @@ namespace Terminal.Gui { { if (view == null) return; + OnGrabbedMouse (view); mouseGrabView = view; Driver.UncookMouse (); } @@ -601,10 +617,27 @@ namespace Terminal.Gui { /// public static void UngrabMouse () { + if (mouseGrabView == null) + return; + OnUnGrabbedMouse (mouseGrabView); mouseGrabView = null; Driver.CookMouse (); } + static void OnGrabbedMouse (View view) + { + if (view == null) + return; + GrabbedMouse?.Invoke (view); + } + + static void OnUnGrabbedMouse (View view) + { + if (view == null) + return; + UnGrabbedMouse?.Invoke (view); + } + /// /// Merely a debugging aid to see the raw mouse events /// @@ -656,7 +689,7 @@ namespace Terminal.Gui { lastMouseOwnerView?.OnMouseLeave (me); } // System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); - if (mouseGrabView != null && mouseGrabView.OnMouseEvent (nme)) { + if (mouseGrabView?.OnMouseEvent (nme) == true) { return; } } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index e40d852b5..804ddb5ed 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1816,7 +1816,7 @@ namespace Terminal.Gui { internal bool HandleGrabView (MouseEvent me, View current) { - if (Application.mouseGrabView != null) { + if (Application.MouseGrabView != null) { if (me.View is MenuBar || me.View is Menu) { var mbar = GetMouseGrabViewInstance (me.View); if (mbar != null) { @@ -1890,7 +1890,7 @@ namespace Terminal.Gui { //if (!(me.View is MenuBar) && !(me.View is Menu) && me.Flags != MouseFlags.Button1Pressed)) // return false; - //if (Application.mouseGrabView != null) { + //if (Application.MouseGrabView != null) { // if (me.View is MenuBar || me.View is Menu) { // me.X -= me.OfX; // me.Y -= me.OfY; @@ -1905,8 +1905,8 @@ namespace Terminal.Gui { // return true; //} - //if (Application.mouseGrabView != null) { - // if (Application.mouseGrabView == me.View && me.View == current) { + //if (Application.MouseGrabView != null) { + // if (Application.MouseGrabView == me.View && me.View == current) { // me.X -= me.OfX; // me.Y -= me.OfY; // } else if (me.View != current && me.View is MenuBar && me.View is Menu) { @@ -1927,7 +1927,7 @@ namespace Terminal.Gui { MenuBar GetMouseGrabViewInstance (View view) { - if (view == null || Application.mouseGrabView == null) { + if (view == null || Application.MouseGrabView == null) { return null; } @@ -1938,7 +1938,7 @@ namespace Terminal.Gui { hostView = ((Menu)view).host; } - var grabView = Application.mouseGrabView; + var grabView = Application.MouseGrabView; MenuBar hostGrabView = null; if (grabView is MenuBar) { hostGrabView = (MenuBar)grabView; diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index f584a2ec7..80c5b6ee3 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -357,7 +357,7 @@ namespace Terminal.Gui { } else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null) { otherScrollBarView.contentBottomRightCorner.Visible = false; } - if (Application.mouseGrabView != null && Application.mouseGrabView == this) { + if (Application.MouseGrabView != null && Application.MouseGrabView == this) { Application.UngrabMouse (); } } else if (contentBottomRightCorner != null) { @@ -638,9 +638,9 @@ namespace Terminal.Gui { var pos = Position; if (me.Flags != MouseFlags.Button1Released - && (Application.mouseGrabView == null || Application.mouseGrabView != this)) { + && (Application.MouseGrabView == null || Application.MouseGrabView != this)) { Application.GrabMouse (this); - } else if (me.Flags == MouseFlags.Button1Released && Application.mouseGrabView != null && Application.mouseGrabView == this) { + } else if (me.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) { lastLocation = -1; Application.UngrabMouse (); return true; diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 306261dae..820275178 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -227,7 +227,7 @@ namespace Terminal.Gui { void View_MouseLeave (MouseEventArgs e) { - if (Application.mouseGrabView != null && Application.mouseGrabView != vertical && Application.mouseGrabView != horizontal) { + if (Application.MouseGrabView != null && Application.MouseGrabView != vertical && Application.MouseGrabView != horizontal) { Application.UngrabMouse (); } } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 3f9ae85f0..5852c7051 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -249,9 +249,9 @@ namespace Terminal.Gui { /// public override bool OnLeave (View view) { - if (Application.mouseGrabView != null && Application.mouseGrabView == this) + if (Application.MouseGrabView != null && Application.MouseGrabView == this) Application.UngrabMouse (); - //if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar)) + //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar)) // ClearAllSelection (); return base.OnLeave (view); @@ -1049,7 +1049,7 @@ namespace Terminal.Gui { int x = PositionCursor (ev); isButtonReleased = false; PrepareSelection (x); - if (Application.mouseGrabView == null) { + if (Application.MouseGrabView == null) { Application.GrabMouse (this); } } else if (ev.Flags == MouseFlags.Button1Released) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index f671a5264..edbdb8bd3 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -4328,7 +4328,7 @@ namespace Terminal.Gui { } lastWasKill = false; columnTrack = currentColumn; - if (Application.mouseGrabView == null) { + if (Application.MouseGrabView == null) { Application.GrabMouse (this); } } else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) { @@ -4407,7 +4407,7 @@ namespace Terminal.Gui { /// public override bool OnLeave (View view) { - if (Application.mouseGrabView != null && Application.mouseGrabView == this) { + if (Application.MouseGrabView != null && Application.MouseGrabView == this) { Application.UngrabMouse (); } diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs index 56fdb9223..e0f331e74 100644 --- a/UnitTests/ApplicationTests.cs +++ b/UnitTests/ApplicationTests.cs @@ -1144,7 +1144,7 @@ namespace Terminal.Gui.Core { Assert.NotNull (Application.Top); var rs = Application.Begin (Application.Top); Assert.Equal (Application.Top, rs.Toplevel); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); Assert.Null (Application.WantContinuousButtonPressedView); Assert.False (Application.DebugDrawBounds); Assert.False (Application.ShowChild (Application.Top)); @@ -1428,7 +1428,7 @@ namespace Terminal.Gui.Core { iterations++; if (iterations == 0) { Assert.True (tf.HasFocus); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); ReflectionTools.InvokePrivate ( typeof (Application), @@ -1439,13 +1439,13 @@ namespace Terminal.Gui.Core { Flags = MouseFlags.ReportMousePosition }); - Assert.Equal (sv, Application.mouseGrabView); + Assert.Equal (sv, Application.MouseGrabView); MessageBox.Query ("Title", "Test", "Ok"); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); } else if (iterations == 1) { - Assert.Equal (sv, Application.mouseGrabView); + Assert.Equal (sv, Application.MouseGrabView); ReflectionTools.InvokePrivate ( typeof (Application), @@ -1456,7 +1456,7 @@ namespace Terminal.Gui.Core { Flags = MouseFlags.ReportMousePosition }); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); ReflectionTools.InvokePrivate ( typeof (Application), @@ -1467,7 +1467,7 @@ namespace Terminal.Gui.Core { Flags = MouseFlags.ReportMousePosition }); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); ReflectionTools.InvokePrivate ( typeof (Application), @@ -1478,11 +1478,11 @@ namespace Terminal.Gui.Core { Flags = MouseFlags.Button1Pressed }); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); Application.RequestStop (); } else if (iterations == 2) { - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); Application.RequestStop (); } @@ -1490,5 +1490,68 @@ namespace Terminal.Gui.Core { Application.Run (); } + + [Fact, AutoInitShutdown] + public void MouseGrabView_GrabbedMouse_UnGrabbedMouse () + { + View grabView = null; + var count = 0; + + var view1 = new View (); + var view2 = new View (); + + Application.GrabbedMouse += Application_GrabbedMouse; + Application.UnGrabbedMouse += Application_UnGrabbedMouse; + + Application.GrabMouse (view1); + Assert.Equal (0, count); + Assert.Equal (grabView, view1); + Assert.Equal (view1, Application.MouseGrabView); + + Application.UngrabMouse (); + Assert.Equal (1, count); + Assert.Equal (grabView, view1); + Assert.Null (Application.MouseGrabView); + + Application.GrabbedMouse += Application_GrabbedMouse; + Application.UnGrabbedMouse += Application_UnGrabbedMouse; + + Application.GrabMouse (view2); + Assert.Equal (1, count); + Assert.Equal (grabView, view2); + Assert.Equal (view2, Application.MouseGrabView); + + Application.UngrabMouse (); + Assert.Equal (2, count); + Assert.Equal (grabView, view2); + Assert.Null (Application.MouseGrabView); + + void Application_GrabbedMouse (View obj) + { + if (count == 0) { + Assert.Equal (view1, obj); + grabView = view1; + } else { + Assert.Equal (view2, obj); + grabView = view2; + } + + Application.GrabbedMouse -= Application_GrabbedMouse; + } + + void Application_UnGrabbedMouse (View obj) + { + if (count == 0) { + Assert.Equal (view1, obj); + Assert.Equal (grabView, obj); + } else { + Assert.Equal (view2, obj); + Assert.Equal (grabView, obj); + } + count++; + + Application.UnGrabbedMouse -= Application_UnGrabbedMouse; + } + } } } diff --git a/UnitTests/ContextMenuTests.cs b/UnitTests/ContextMenuTests.cs index 5df3b07ce..a0c88168a 100644 --- a/UnitTests/ContextMenuTests.cs +++ b/UnitTests/ContextMenuTests.cs @@ -519,38 +519,38 @@ namespace Terminal.Gui.Core { Application.Top.Add (menu); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); cm.Show (); Assert.True (ContextMenu.IsShow); - Assert.Equal (cm.MenuBar, Application.mouseGrabView); + Assert.Equal (cm.MenuBar, Application.MouseGrabView); Assert.False (menu.IsMenuOpen); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); Assert.False (ContextMenu.IsShow); - Assert.Equal (menu, Application.mouseGrabView); + Assert.Equal (menu, Application.MouseGrabView); Assert.True (menu.IsMenuOpen); cm.Show (); Assert.True (ContextMenu.IsShow); - Assert.Equal (cm.MenuBar, Application.mouseGrabView); + Assert.Equal (cm.MenuBar, Application.MouseGrabView); Assert.False (menu.IsMenuOpen); Assert.False (menu.OnKeyDown (new KeyEvent (Key.Null, new KeyModifiers () { Alt = true }))); Assert.True (menu.OnKeyUp (new KeyEvent (Key.Null, new KeyModifiers () { Alt = true }))); Assert.False (ContextMenu.IsShow); - Assert.Equal (menu, Application.mouseGrabView); + Assert.Equal (menu, Application.MouseGrabView); Assert.True (menu.IsMenuOpen); cm.Show (); Assert.True (ContextMenu.IsShow); - Assert.Equal (cm.MenuBar, Application.mouseGrabView); + Assert.Equal (cm.MenuBar, Application.MouseGrabView); Assert.False (menu.IsMenuOpen); Assert.False (menu.MouseEvent (new MouseEvent () { X = 1, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (ContextMenu.IsShow); - Assert.Equal (cm.MenuBar, Application.mouseGrabView); + Assert.Equal (cm.MenuBar, Application.MouseGrabView); Assert.False (menu.IsMenuOpen); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Flags = MouseFlags.Button1Clicked, View = menu })); Assert.False (ContextMenu.IsShow); - Assert.Equal (menu, Application.mouseGrabView); + Assert.Equal (menu, Application.MouseGrabView); Assert.True (menu.IsMenuOpen); } From 10fb8cda63980525aa473d29abc3cbd9a5ed6a32 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 24 Sep 2022 23:44:28 +0100 Subject: [PATCH 06/58] Replacing to MouseGrabView. --- UnitTests/ScrollBarViewTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UnitTests/ScrollBarViewTests.cs b/UnitTests/ScrollBarViewTests.cs index 12215d154..0248674bf 100644 --- a/UnitTests/ScrollBarViewTests.cs +++ b/UnitTests/ScrollBarViewTests.cs @@ -947,7 +947,7 @@ This is a test Flags = MouseFlags.Button1Clicked }); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); Assert.True (clicked); clicked = false; @@ -974,7 +974,7 @@ This is a test Flags = MouseFlags.Button1Clicked }); - Assert.Null (Application.mouseGrabView); + Assert.Null (Application.MouseGrabView); Assert.True (clicked); Assert.Equal (5, sbv.Size); Assert.False (sbv.ShowScrollIndicator); From 91c49af97dda87e5b612025b4dc086ee78c8d10c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:34:58 +0000 Subject: [PATCH 07/58] Bump actions/setup-dotnet from 1 to 2.1.1 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 1 to 2.1.1. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v1...v2.1.1) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/api-docs.yml | 2 +- .github/workflows/dotnet-core.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index 93b171ef2..ec0043cf9 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v2 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v2.1.1 with: dotnet-version: 6.0.100 diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 4f5b06735..d975b6ad8 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup .NET Core - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v2.1.1 with: dotnet-version: 6.0.100 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9080c5df9..33d7a1df5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.CommitsSinceVersionSource }}" - name: Setup dotnet - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v2.1.1 with: dotnet-version: 6.0.100 From 41072af7a24ca45de1241102d5d230b31b72c09d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 15:03:58 +0000 Subject: [PATCH 08/58] Bump Microsoft.NET.Test.Sdk from 17.3.1 to 17.3.2 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.1 to 17.3.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.1...v17.3.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- UnitTests/UnitTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 1035b581b..5cc67ac22 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -18,7 +18,7 @@ TRACE;DEBUG_IDISPOSABLE - + From 8ff4631e9a524856c39e5886ca1edd9073f8c375 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 14:51:59 +0000 Subject: [PATCH 09/58] Bump ReactiveMarbles.ObservableEvents.SourceGenerator Bumps [ReactiveMarbles.ObservableEvents.SourceGenerator](https://github.com/reactivemarbles/ObservableEvents) from 1.1.4 to 1.2.3. - [Release notes](https://github.com/reactivemarbles/ObservableEvents/releases) - [Commits](https://github.com/reactivemarbles/ObservableEvents/compare/1.1.4...1.2.3) --- updated-dependencies: - dependency-name: ReactiveMarbles.ObservableEvents.SourceGenerator dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- ReactiveExample/ReactiveExample.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactiveExample/ReactiveExample.csproj b/ReactiveExample/ReactiveExample.csproj index 199bdf20d..274cbde40 100644 --- a/ReactiveExample/ReactiveExample.csproj +++ b/ReactiveExample/ReactiveExample.csproj @@ -13,7 +13,7 @@ - + From b6715863a32394d7e6ca94df81f6505ba99662d3 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 28 Sep 2022 17:16:48 +0100 Subject: [PATCH 10/58] Fixes #2071. DrawContentComplete event is never called from the base if it's overridden. --- Terminal.Gui/Core/View.cs | 1 + UnitTests/ViewTests.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 99afe4b07..67dfdf755 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1548,6 +1548,7 @@ namespace Terminal.Gui { }; view.OnDrawContent (rect); view.Redraw (rect); + view.OnDrawContentComplete (rect); } } view.NeedDisplay = Rect.Empty; diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index ef601b46b..3c03abe3f 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -3892,5 +3892,23 @@ This is a tes This is a tes ", output); } + + [Fact, AutoInitShutdown] + public void DrawContentComplete_Event_Is_Always_Called () + { + var viewCalled = false; + var tvCalled = false; + + var view = new View ("View") { Width = 10, Height = 10 }; + view.DrawContentComplete += (e) => viewCalled = true; + var tv = new TextView () { Y = 11, Width = 10, Height = 10 }; + tv.DrawContentComplete += (e) => tvCalled = true; + + Application.Top.Add (view, tv); + Application.Begin (Application.Top); + + Assert.True (viewCalled); + Assert.True (tvCalled); + } } } From 702ef467277e6e4b1e5ff81a423e9ea1dca5e9f9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 28 Sep 2022 00:07:55 +0100 Subject: [PATCH 11/58] Fixes #2069. KeyDown and KeyUp events must run before OnKeyDown and OnKeyUp. --- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 3 + Terminal.Gui/Core/View.cs | 20 ++++-- UnitTests/ViewTests.cs | 68 +++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 50662f364..d594eea8c 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -370,12 +370,14 @@ namespace Terminal.Gui { return keyMod != Key.Null ? keyMod | key : key; } + Action keyDownHandler; Action keyHandler; Action keyUpHandler; private CursorVisibility savedCursorVisibility; public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) { + this.keyDownHandler = keyDownHandler; this.keyHandler = keyHandler; this.keyUpHandler = keyUpHandler; @@ -400,6 +402,7 @@ namespace Terminal.Gui { keyModifiers.Ctrl = true; } + keyDownHandler (new KeyEvent (map, keyModifiers)); keyHandler (new KeyEvent (map, keyModifiers)); keyUpHandler (new KeyEvent (map, keyModifiers)); } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 67dfdf755..ea9d80885 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1932,8 +1932,14 @@ namespace Terminal.Gui { if (args.Handled) { return true; } - if (Focused?.Enabled == true && Focused?.OnKeyDown (keyEvent) == true) { - return true; + if (Focused?.Enabled == true) { + Focused.KeyDown?.Invoke (args); + if (args.Handled) { + return true; + } + if (Focused?.OnKeyDown (keyEvent) == true) { + return true; + } } return false; @@ -1956,8 +1962,14 @@ namespace Terminal.Gui { if (args.Handled) { return true; } - if (Focused?.Enabled == true && Focused?.OnKeyUp (keyEvent) == true) { - return true; + if (Focused?.Enabled == true) { + Focused.KeyUp?.Invoke (args); + if (args.Handled) { + return true; + } + if (Focused?.OnKeyUp (keyEvent) == true) { + return true; + } } return false; diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index 3c03abe3f..baf66c497 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -3910,5 +3910,73 @@ This is a tes Assert.True (viewCalled); Assert.True (tvCalled); } + + [Fact, AutoInitShutdown] + public void KeyDown_And_KeyUp_Events_Must_Called_Before_OnKeyDown_And_OnKeyUp () + { + var view = new DerivedView (); + view.KeyDown += (e) => { + Assert.Equal (Key.a, e.KeyEvent.Key); + Assert.False (view.IsKeyDown); + e.Handled = true; + }; + view.KeyPress += (e) => { + Assert.Equal (Key.a, e.KeyEvent.Key); + Assert.False (view.IsKeyPress); + e.Handled = true; + }; + view.KeyUp += (e) => { + Assert.Equal (Key.a, e.KeyEvent.Key); + Assert.False (view.IsKeyUp); + e.Handled = true; + }; + + var iterations = -1; + + Application.Iteration += () => { + iterations++; + if (iterations == 0) { + Console.MockKeyPresses.Push (new ConsoleKeyInfo ('a', ConsoleKey.A, false, false, false)); + } else if (iterations == 1) { + Application.RequestStop (); + } + }; + + Application.Top.Add (view); + + Assert.True (view.CanFocus); + + Application.Run (); + Application.Shutdown (); + } + + public class DerivedView : View { + public DerivedView () + { + CanFocus = true; + } + + public bool IsKeyDown { get; set; } + public bool IsKeyPress { get; set; } + public bool IsKeyUp { get; set; } + + public override bool OnKeyDown (KeyEvent keyEvent) + { + IsKeyDown = true; + return base.OnKeyDown (keyEvent); + } + + public override bool ProcessKey (KeyEvent keyEvent) + { + IsKeyPress = true; + return base.ProcessKey (keyEvent); + } + + public override bool OnKeyUp (KeyEvent keyEvent) + { + IsKeyUp = true; + return base.OnKeyUp (keyEvent); + } + } } } From eaaa6364972883d4f7da570db05d03a6b52a1b36 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 28 Sep 2022 10:35:10 +0100 Subject: [PATCH 12/58] Adding a more realistic key down/up event with null key. --- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 18 ++-- UnitTests/ViewTests.cs | 90 ++++++++++++++++--- 2 files changed, 87 insertions(+), 21 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index d594eea8c..8189207bc 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -388,19 +388,23 @@ namespace Terminal.Gui { void ProcessInput (ConsoleKeyInfo consoleKey) { keyModifiers = new KeyModifiers (); - var map = MapKey (consoleKey); - if (map == (Key)0xffffffff) - return; - - if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) { - keyModifiers.Alt = true; - } if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) { keyModifiers.Shift = true; } + if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) { + keyModifiers.Alt = true; + } if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) { keyModifiers.Ctrl = true; } + var map = MapKey (consoleKey); + if (map == (Key)0xffffffff) { + if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + keyDownHandler (new KeyEvent (map, keyModifiers)); + keyUpHandler (new KeyEvent (map, keyModifiers)); + } + return; + } keyDownHandler (new KeyEvent (map, keyModifiers)); keyHandler (new KeyEvent (map, keyModifiers)); diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index baf66c497..5218060ab 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -3914,40 +3914,50 @@ This is a tes [Fact, AutoInitShutdown] public void KeyDown_And_KeyUp_Events_Must_Called_Before_OnKeyDown_And_OnKeyUp () { + var keyDown = false; + var keyPress = false; + var keyUp = false; + var view = new DerivedView (); view.KeyDown += (e) => { Assert.Equal (Key.a, e.KeyEvent.Key); + Assert.False (keyDown); Assert.False (view.IsKeyDown); e.Handled = true; + keyDown = true; }; view.KeyPress += (e) => { Assert.Equal (Key.a, e.KeyEvent.Key); + Assert.False (keyPress); Assert.False (view.IsKeyPress); e.Handled = true; + keyPress = true; }; view.KeyUp += (e) => { Assert.Equal (Key.a, e.KeyEvent.Key); + Assert.False (keyUp); Assert.False (view.IsKeyUp); e.Handled = true; - }; - - var iterations = -1; - - Application.Iteration += () => { - iterations++; - if (iterations == 0) { - Console.MockKeyPresses.Push (new ConsoleKeyInfo ('a', ConsoleKey.A, false, false, false)); - } else if (iterations == 1) { - Application.RequestStop (); - } + keyUp = true; }; Application.Top.Add (view); + Console.MockKeyPresses.Push (new ConsoleKeyInfo ('a', ConsoleKey.A, false, false, false)); + + Application.Iteration += () => Application.RequestStop (); + Assert.True (view.CanFocus); Application.Run (); Application.Shutdown (); + + Assert.True (keyDown); + Assert.True (keyPress); + Assert.True (keyUp); + Assert.False (view.IsKeyDown); + Assert.False (view.IsKeyPress); + Assert.False (view.IsKeyUp); } public class DerivedView : View { @@ -3963,20 +3973,72 @@ This is a tes public override bool OnKeyDown (KeyEvent keyEvent) { IsKeyDown = true; - return base.OnKeyDown (keyEvent); + return true; } public override bool ProcessKey (KeyEvent keyEvent) { IsKeyPress = true; - return base.ProcessKey (keyEvent); + return true; } public override bool OnKeyUp (KeyEvent keyEvent) { IsKeyUp = true; - return base.OnKeyUp (keyEvent); + return true; } } + + [Theory, AutoInitShutdown] + [InlineData (true, false, false)] + [InlineData (true, true, false)] + [InlineData (true, true, true)] + public void KeyDown_And_KeyUp_Events_With_Only_Key_Modifiers (bool shift, bool alt, bool control) + { + var keyDown = false; + var keyPress = false; + var keyUp = false; + + var view = new DerivedView (); + view.KeyDown += (e) => { + Assert.Equal (-1, e.KeyEvent.KeyValue); + Assert.Equal (shift, e.KeyEvent.IsShift); + Assert.Equal (alt, e.KeyEvent.IsAlt); + Assert.Equal (control, e.KeyEvent.IsCtrl); + Assert.False (keyDown); + Assert.False (view.IsKeyDown); + keyDown = true; + }; + view.KeyPress += (e) => { + keyPress = true; + }; + view.KeyUp += (e) => { + Assert.Equal (-1, e.KeyEvent.KeyValue); + Assert.Equal (shift, e.KeyEvent.IsShift); + Assert.Equal (alt, e.KeyEvent.IsAlt); + Assert.Equal (control, e.KeyEvent.IsCtrl); + Assert.False (keyUp); + Assert.False (view.IsKeyUp); + keyUp = true; + }; + + Application.Top.Add (view); + + Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', shift, alt, control)); + + Application.Iteration += () => Application.RequestStop (); + + Assert.True (view.CanFocus); + + Application.Run (); + Application.Shutdown (); + + Assert.True (keyDown); + Assert.False (keyPress); + Assert.True (keyUp); + Assert.True (view.IsKeyDown); + Assert.False (view.IsKeyPress); + Assert.True (view.IsKeyUp); + } } } From 506a8d16926c93b552c2057c9869af4362aa4085 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 15:40:59 +0000 Subject: [PATCH 13/58] Bump actions/setup-dotnet from 2.1.1 to 3.0.1 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 2.1.1 to 3.0.1. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v2.1.1...v3.0.1) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/api-docs.yml | 2 +- .github/workflows/dotnet-core.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index ec0043cf9..e0d5def02 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v2 - name: Setup .NET Core - uses: actions/setup-dotnet@v2.1.1 + uses: actions/setup-dotnet@v3.0.1 with: dotnet-version: 6.0.100 diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index d975b6ad8..b1a1cf807 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup .NET Core - uses: actions/setup-dotnet@v2.1.1 + uses: actions/setup-dotnet@v3.0.1 with: dotnet-version: 6.0.100 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 33d7a1df5..b5adb13fd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.CommitsSinceVersionSource }}" - name: Setup dotnet - uses: actions/setup-dotnet@v2.1.1 + uses: actions/setup-dotnet@v3.0.1 with: dotnet-version: 6.0.100 From 49eefa617df3b890cd99f2a1c3aaf6509c909c3d Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 12 Oct 2022 23:46:37 +0100 Subject: [PATCH 14/58] Fixes #2081. Clipboard unit tests sometimes fail with WSL. --- .../ConsoleDrivers/CursesDriver/CursesDriver.cs | 1 + Terminal.Gui/Core/Clipboard/ClipboardBase.cs | 3 ++- Terminal.sln | 1 + testenvironments.json | 15 +++++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 testenvironments.json diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index b5db7847b..a0837bf35 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -1500,6 +1500,7 @@ namespace Terminal.Gui { } }) { powershell.Start (); + powershell.WaitForExit (); if (!powershell.DoubleWaitForExit ()) { var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}. Output: {powershell.StandardOutput.ReadToEnd ()} diff --git a/Terminal.Gui/Core/Clipboard/ClipboardBase.cs b/Terminal.Gui/Core/Clipboard/ClipboardBase.cs index db61af80f..9eb17e174 100644 --- a/Terminal.Gui/Core/Clipboard/ClipboardBase.cs +++ b/Terminal.Gui/Core/Clipboard/ClipboardBase.cs @@ -92,7 +92,8 @@ namespace Terminal.Gui { try { SetClipboardDataImpl (text); return true; - } catch (Exception) { + } catch (Exception ex) { + System.Diagnostics.Debug.WriteLine ($"TrySetClipboardData: {ex.Message}"); return false; } } diff --git a/Terminal.sln b/Terminal.sln index 03b0011e9..29fac1e30 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml .github\workflows\publish.yml = .github\workflows\publish.yml README.md = README.md + testenvironments.json = testenvironments.json EndProjectSection EndProject Global diff --git a/testenvironments.json b/testenvironments.json new file mode 100644 index 000000000..70dbd0b4c --- /dev/null +++ b/testenvironments.json @@ -0,0 +1,15 @@ +{ + "version": "1", + "environments": [ + { + "name": "WSL-Ubuntu", + "type": "wsl", + "wslDistribution": "Ubuntu" + }, + { + "name": "WSL-Debian", + "type": "wsl", + "wslDistribution": "Debian" + } + ] +} \ No newline at end of file From b87fd489a121b154304653dbf533623089020da7 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 13 Oct 2022 09:52:12 +0100 Subject: [PATCH 15/58] Fix locking for the entire RunIdle callback execution --- Terminal.Gui/Core/MainLoop.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/MainLoop.cs b/Terminal.Gui/Core/MainLoop.cs index 43789e228..e71367646 100644 --- a/Terminal.Gui/Core/MainLoop.cs +++ b/Terminal.Gui/Core/MainLoop.cs @@ -306,9 +306,12 @@ namespace Terminal.Gui { Driver.MainIteration (); + bool runIdle = false; lock (idleHandlersLock) { - if (idleHandlers.Count > 0) - RunIdle (); + runIdle = idleHandlers.Count > 0; + } + if (runIdle) { + RunIdle (); } } From 371147d691e03337f1842967cd195035243ffd3c Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 14 Oct 2022 12:45:58 -0600 Subject: [PATCH 16/58] Deleted demo.cs; changed to Example.cs from README --- Example/Example.cs | 76 +++ Example/Properties/launchSettings.json | 11 - Example/README.md | 8 +- Example/demo.cs | 758 ------------------------- README.md | 84 +-- 5 files changed, 123 insertions(+), 814 deletions(-) create mode 100644 Example/Example.cs delete mode 100644 Example/Properties/launchSettings.json delete mode 100644 Example/demo.cs diff --git a/Example/Example.cs b/Example/Example.cs new file mode 100644 index 000000000..97db1f3d3 --- /dev/null +++ b/Example/Example.cs @@ -0,0 +1,76 @@ +// A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements +// This is the same code found in the Termiminal Gui README.md file. + +using Terminal.Gui; +using NStack; + +Application.Init (); + +// Creates the top-level window to show +var win = new Window ("Example App") { + X = 0, + Y = 1, // Leave one row for the toplevel menu + + // By using Dim.Fill(), this Window will automatically resize without manual intervention + Width = Dim.Fill (), + Height = Dim.Fill () +}; + +Application.Top.Add (win); + +// Creates a menubar, the item "New" has a help menu. +var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_New", "Creates a new file", null), + new MenuItem ("_Close", "",null), + new MenuItem ("_Quit", "", () => { if (Quit ()) Application.Top.Running = false; }) + }), + new MenuBarItem ("_Edit", new MenuItem [] { + new MenuItem ("_Copy", "", null), + new MenuItem ("C_ut", "", null), + new MenuItem ("_Paste", "", null) + }) + }); +Application.Top.Add (menu); + +static bool Quit () +{ + var n = MessageBox.Query (50, 7, "Quit Example", "Are you sure you want to quit this example?", "Yes", "No"); + return n == 0; +} + +var login = new Label ("Login: ") { X = 3, Y = 2 }; +var password = new Label ("Password: ") { + X = Pos.Left (login), + Y = Pos.Top (login) + 1 +}; +var loginText = new TextField ("") { + X = Pos.Right (password), + Y = Pos.Top (login), + Width = 40 +}; +var passText = new TextField ("") { + Secret = true, + X = Pos.Left (loginText), + Y = Pos.Top (password), + Width = Dim.Width (loginText) +}; + +// Add the views to the main window, +win.Add ( + // Using Computed Layout: + login, password, loginText, passText, + + // Using Absolute Layout: + new CheckBox (3, 6, "Remember me"), + new RadioGroup (3, 8, new ustring [] { "_Personal", "_Company" }, 0), + new Button (3, 14, "Ok"), + new Button (10, 14, "Cancel"), + new Label (3, 18, "Press F9 or ESC plus 9 to activate the menubar") +); + +// Run blocks until the user quits the application +Application.Run (); + +// Always bracket Application.Init with .Shutdown. +Application.Shutdown (); \ No newline at end of file diff --git a/Example/Properties/launchSettings.json b/Example/Properties/launchSettings.json deleted file mode 100644 index 495d3d1da..000000000 --- a/Example/Properties/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "Example": { - "commandName": "Project" - }, - "Example:-usc": { - "commandName": "Project", - "commandLineArgs": "-usc" - } - } -} \ No newline at end of file diff --git a/Example/README.md b/Example/README.md index 6daac3cd7..b03d0af0a 100644 --- a/Example/README.md +++ b/Example/README.md @@ -1,5 +1,7 @@ -# demo.cs +# Terminal.Gui C# Example -This is the original Terminal.Gui sample app. Over time we will be simplifying this sample to show just the basics of creating a Terminal.Gui app in C#. +This example shows how to use the Terminal.Gui library to create a simple GUI application in C#. -See the Sample Code section [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all samples. \ No newline at end of file +This is the same code found in the Termina.Gui README.md file. + +See [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all Terminal.Gui samples. \ No newline at end of file diff --git a/Example/demo.cs b/Example/demo.cs deleted file mode 100644 index 2957c8dc2..000000000 --- a/Example/demo.cs +++ /dev/null @@ -1,758 +0,0 @@ -using NStack; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using Terminal.Gui; - -static class Demo { - class Box10x : View { - int w = 40; - int h = 50; - - public bool WantCursorPosition { get; set; } = false; - - public Box10x (int x, int y) : base (new Rect (x, y, 20, 10)) - { - } - - public Size GetContentSize () - { - return new Size (w, h); - } - - public void SetCursorPosition (Point pos) - { - throw new NotImplementedException (); - } - - public override void Redraw (Rect bounds) - { - //Point pos = new Point (region.X, region.Y); - Driver.SetAttribute (ColorScheme.Focus); - - for (int y = 0; y < h; y++) { - Move (0, y); - Driver.AddStr (y.ToString ()); - for (int x = 0; x < w - y.ToString ().Length; x++) { - //Driver.AddRune ((Rune)('0' + (x + y) % 10)); - if (y.ToString ().Length < w) - Driver.AddStr (" "); - } - } - //Move (pos.X, pos.Y); - } - } - - class Filler : View { - int w = 40; - int h = 50; - - public Filler (Rect rect) : base (rect) - { - w = rect.Width; - h = rect.Height; - } - - public Size GetContentSize () - { - return new Size (w, h); - } - - public override void Redraw (Rect bounds) - { - Driver.SetAttribute (ColorScheme.Focus); - var f = Frame; - w = 0; - h = 0; - - for (int y = 0; y < f.Width; y++) { - Move (0, y); - var nw = 0; - for (int x = 0; x < f.Height; x++) { - Rune r; - switch (x % 3) { - case 0: - var er = y.ToString ().ToCharArray (0, 1) [0]; - nw += er.ToString ().Length; - Driver.AddRune (er); - if (y > 9) { - er = y.ToString ().ToCharArray (1, 1) [0]; - nw += er.ToString ().Length; - Driver.AddRune (er); - } - r = '.'; - break; - case 1: - r = 'o'; - break; - default: - r = 'O'; - break; - } - Driver.AddRune (r); - nw += Rune.RuneLen (r); - } - if (nw > w) - w = nw; - h = y + 1; - } - } - } - - static void ShowTextAlignments () - { - var container = new Window ("Show Text Alignments - Press Esc to return") { - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill () - }; - container.KeyUp += (e) => { - if (e.KeyEvent.Key == Key.Esc) - container.Running = false; - }; - - int i = 0; - string txt = "Hello world, how are you doing today?"; - container.Add ( - new Label ($"{i + 1}-{txt}") { TextAlignment = TextAlignment.Left, Y = 3, Width = Dim.Fill () }, - new Label ($"{i + 2}-{txt}") { TextAlignment = TextAlignment.Right, Y = 5, Width = Dim.Fill () }, - new Label ($"{i + 3}-{txt}") { TextAlignment = TextAlignment.Centered, Y = 7, Width = Dim.Fill () }, - new Label ($"{i + 4}-{txt}") { TextAlignment = TextAlignment.Justified, Y = 9, Width = Dim.Fill () } - ); - - Application.Run (container); - } - - static void ShowEntries (View container) - { - var scrollView = new ScrollView (new Rect (50, 10, 20, 8)) { - ContentSize = new Size (20, 50), - //ContentOffset = new Point (0, 0), - ShowVerticalScrollIndicator = true, - ShowHorizontalScrollIndicator = true - }; -#if false - scrollView.Add (new Box10x (0, 0)); -#else - var filler = new Filler (new Rect (0, 0, 40, 40)); - scrollView.Add (filler); - scrollView.DrawContent += (r) => { - scrollView.ContentSize = filler.GetContentSize (); - }; -#endif - - // This is just to debug the visuals of the scrollview when small - var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) { - ContentSize = new Size (100, 100), - ShowVerticalScrollIndicator = true, - ShowHorizontalScrollIndicator = true - }; - scrollView2.Add (new Box10x (0, 0)); - var progress = new ProgressBar (new Rect (68, 1, 10, 1)); - bool timer (MainLoop caller) - { - progress.Pulse (); - return true; - } - - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer); - - - // A little convoluted, this is because I am using this to test the - // layout based on referencing elements of another view: - - var login = new Label ("Login: ") { X = 3, Y = 6 }; - var password = new Label ("Password: ") { - X = Pos.Left (login), - Y = Pos.Bottom (login) + 1 - }; - var loginText = new TextField ("") { - X = Pos.Right (password), - Y = Pos.Top (login), - Width = 40 - }; - - var passText = new TextField ("") { - Secret = true, - X = Pos.Left (loginText), - Y = Pos.Top (password), - Width = Dim.Width (loginText) - }; - - var tf = new Button (3, 19, "Ok"); - var frameView = new FrameView (new Rect (3, 10, 25, 6), "Options"); - frameView.Add (new CheckBox (1, 0, "Remember me")); - frameView.Add (new RadioGroup (1, 2, new ustring [] { "_Personal", "_Company" })); - // Add some content - container.Add ( - login, - loginText, - password, - passText, - frameView, - new ListView (new Rect (59, 6, 16, 4), new string [] { - "First row", - "<>", - "This is a very long row that should overflow what is shown", - "4th", - "There is an empty slot on the second row", - "Whoa", - "This is so cool" - }), - scrollView, - scrollView2, - tf, - new Button (10, 19, "Cancel"), - new TimeField (3, 20, DateTime.Now.TimeOfDay), - new TimeField (23, 20, DateTime.Now.TimeOfDay, true), - new DateField (3, 22, DateTime.Now), - new DateField (23, 22, DateTime.Now, true), - progress, - new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) or Ctrl+T to activate the menubar"), - menuKeysStyle, - menuAutoMouseNav - - ); - container.SendSubviewToBack (tf); - } - - public static Label ml2; - - static void NewFile () - { - var ok = new Button ("Ok", is_default: true); - ok.Clicked += () => { Application.RequestStop (); }; - var cancel = new Button ("Cancel"); - cancel.Clicked += () => { Application.RequestStop (); }; - var d = new Dialog ("New File", 50, 20, ok, cancel); - ml2 = new Label (1, 1, "Mouse Debug Line"); - d.Add (ml2); - Application.Run (d); - } - - // - static void Editor () - { - Application.Init (); - Application.HeightAsBuffer = heightAsBuffer; - - var ntop = Application.Top; - - var text = new TextView () { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; - - string fname = GetFileName (); - - var win = new Window (fname ?? "Untitled") { - X = 0, - Y = 1, - Width = Dim.Fill (), - Height = Dim.Fill () - }; - ntop.Add (win); - - if (fname != null) - text.Text = System.IO.File.ReadAllText (fname); - win.Add (text); - - void Paste () - { - if (text != null) { - text.Paste (); - } - } - - void Cut () - { - if (text != null) { - text.Cut (); - } - } - - void Copy () - { - if (text != null) { - text.Copy (); - } - } - - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Close", "", () => { if (Quit ()) { running = MainApp; Application.RequestStop (); } }, null, null, Key.AltMask | Key.Q), - }), - new MenuBarItem ("_Edit", new MenuItem [] { - new MenuItem ("_Copy", "", Copy, null, null, Key.C | Key.CtrlMask), - new MenuItem ("C_ut", "", Cut, null, null, Key.X | Key.CtrlMask), - new MenuItem ("_Paste", "", Paste, null, null, Key.Y | Key.CtrlMask) - }), - }); - ntop.Add (menu); - - Application.Run (ntop); - } - - private static string GetFileName () - { - string fname = null; - foreach (var s in new [] { "/etc/passwd", "c:\\windows\\win.ini" }) - if (System.IO.File.Exists (s)) { - fname = s; - break; - } - - return fname; - } - - static bool Quit () - { - var n = MessageBox.Query (50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No"); - return n == 0; - } - - static void Close () - { - MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok"); - } - - // Watch what happens when I try to introduce a newline after the first open brace - // it introduces a new brace instead, and does not indent. Then watch me fight - // the editor as more oddities happen. - - public static void Open () - { - var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true }; - Application.Run (d); - - if (!d.Canceled) - MessageBox.Query (50, 7, "Selected File", d.FilePaths.Count > 0 ? string.Join (", ", d.FilePaths) : d.FilePath, "Ok"); - } - - public static void ShowHex () - { - var ntop = Application.Top; - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Close", "", () => { running = MainApp; Application.RequestStop (); }, null, null, Key.AltMask | Key.Q), - }), - }); - ntop.Add (menu); - - string fname = GetFileName (); - var win = new Window (fname) { - X = 0, - Y = 1, - Width = Dim.Fill (), - Height = Dim.Fill () - }; - ntop.Add (win); - - var source = System.IO.File.OpenRead (fname); - var hex = new HexView (source) { - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill () - }; - win.Add (hex); - Application.Run (ntop); - } - - public class MenuItemDetails : MenuItem { - ustring title; - string help; - Action action; - - public MenuItemDetails (ustring title, string help, Action action) : base (title, help, action) - { - this.title = title; - this.help = help; - this.action = action; - } - - public static MenuItemDetails Instance (MenuItem mi) - { - return (MenuItemDetails)mi.GetMenuItem (); - } - } - - public delegate MenuItem MenuItemDelegate (MenuItemDetails menuItem); - - public static void ShowMenuItem (MenuItem mi) - { - BindingFlags flags = BindingFlags.Public | BindingFlags.Static; - MethodInfo minfo = typeof (MenuItemDetails).GetMethod ("Instance", flags); - MenuItemDelegate mid = (MenuItemDelegate)Delegate.CreateDelegate (typeof (MenuItemDelegate), minfo); - MessageBox.Query (70, 7, mi.Title.ToString (), - $"{mi.Title.ToString ()} selected. Is from submenu: {mi.GetMenuBarItem ()}", "Ok"); - } - - static void MenuKeysStyle_Toggled (bool e) - { - menu.UseKeysUpDownAsKeysLeftRight = menuKeysStyle.Checked; - } - - static void MenuAutoMouseNav_Toggled (bool e) - { - menu.WantMousePositionReports = menuAutoMouseNav.Checked; - } - - static void Copy () - { - TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField; - if (textField != null && textField.SelectedLength != 0) { - textField.Copy (); - } - } - - static void Cut () - { - TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField; - if (textField != null && textField.SelectedLength != 0) { - textField.Cut (); - } - } - - static void Paste () - { - TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField; - textField?.Paste (); - } - - static void Help () - { - MessageBox.Query (50, 7, "Help", "This is a small help\nBe kind.", "Ok"); - } - - static void Load () - { - MessageBox.Query (50, 7, "Load", "This is a small load\nBe kind.", "Ok"); - } - - static void Save () - { - MessageBox.Query (50, 7, "Save", "This is a small save\nBe kind.", "Ok"); - } - - - #region Selection Demo - - static void ListSelectionDemo (bool multiple) - { - var ok = new Button ("Ok", is_default: true); - ok.Clicked += () => { Application.RequestStop (); }; - var cancel = new Button ("Cancel"); - cancel.Clicked += () => { Application.RequestStop (); }; - var d = new Dialog ("Selection Demo", 60, 20, ok, cancel); - - var animals = new List () { "Alpaca", "Llama", "Lion", "Shark", "Goat" }; - var msg = new Label ("Use space bar or control-t to toggle selection") { - X = 1, - Y = 1, - Width = Dim.Fill () - 1, - Height = 1 - }; - - var list = new ListView (animals) { - X = 1, - Y = 3, - Width = Dim.Fill () - 4, - Height = Dim.Fill () - 4, - AllowsMarking = true, - AllowsMultipleSelection = multiple - }; - d.Add (msg, list); - Application.Run (d); - - var result = ""; - for (int i = 0; i < animals.Count; i++) { - if (list.Source.IsMarked (i)) { - result += animals [i] + " "; - } - } - MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok"); - } - - static void ComboBoxDemo () - { - //TODO: Duplicated code in ListsAndCombos.cs Consider moving to shared assembly - var items = new List (); - foreach (var dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" }) { - if (Directory.Exists (dir)) { - items = Directory.GetFiles (dir).Union (Directory.GetDirectories (dir)) - .Select (Path.GetFileName) - .Where (x => char.IsLetterOrDigit (x [0])) - .OrderBy (x => x).Select (x => ustring.Make (x)).ToList (); - } - } - var list = new ComboBox () { Width = Dim.Fill (), Height = Dim.Fill () }; - list.SetSource (items); - list.OpenSelectedItem += (ListViewItemEventArgs text) => { Application.RequestStop (); }; - - var d = new Dialog () { Title = "Select source file", Width = Dim.Percent (50), Height = Dim.Percent (50) }; - d.Add (list); - Application.Run (d); - - MessageBox.Query (60, 10, "Selected file", list.Text.ToString () == "" ? "Nothing selected" : list.Text.ToString (), "Ok"); - } - #endregion - - - #region KeyDown / KeyPress / KeyUp Demo - private static void OnKeyDownPressUpDemo () - { - var close = new Button ("Close"); - close.Clicked += () => { Application.RequestStop (); }; - var container = new Dialog ("KeyDown & KeyPress & KeyUp demo", 80, 20, close) { - Width = Dim.Fill (), - Height = Dim.Fill (), - }; - - var list = new List (); - var listView = new ListView (list) { - X = 0, - Y = 0, - Width = Dim.Fill () - 1, - Height = Dim.Fill () - 2, - }; - listView.ColorScheme = Colors.TopLevel; - container.Add (listView); - - void KeyDownPressUp (KeyEvent keyEvent, string updown) - { - const int ident = -5; - switch (updown) { - case "Down": - case "Up": - case "Press": - var msg = $"Key{updown,ident}: "; - if ((keyEvent.Key & Key.ShiftMask) != 0) - msg += "Shift "; - if ((keyEvent.Key & Key.CtrlMask) != 0) - msg += "Ctrl "; - if ((keyEvent.Key & Key.AltMask) != 0) - msg += "Alt "; - msg += $"{(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}"; - //list.Add (msg); - list.Add (keyEvent.ToString ()); - - break; - - default: - if ((keyEvent.Key & Key.ShiftMask) != 0) { - list.Add ($"Key{updown,ident}: Shift "); - } else if ((keyEvent.Key & Key.CtrlMask) != 0) { - list.Add ($"Key{updown,ident}: Ctrl "); - } else if ((keyEvent.Key & Key.AltMask) != 0) { - list.Add ($"Key{updown,ident}: Alt "); - } else { - list.Add ($"Key{updown,ident}: {(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}"); - } - - break; - } - listView.MoveDown (); - } - - container.KeyDown += (e) => KeyDownPressUp (e.KeyEvent, "Down"); - container.KeyPress += (e) => KeyDownPressUp (e.KeyEvent, "Press"); - container.KeyUp += (e) => KeyDownPressUp (e.KeyEvent, "Up"); - Application.Run (container); - } - #endregion - - public static Action running = MainApp; - static void Main (string [] args) - { - if (args.Length > 0 && args.Contains ("-usc")) { - Application.UseSystemConsole = true; - } - - Console.OutputEncoding = System.Text.Encoding.Default; - - while (running != null) { - running.Invoke (); - } - Application.Shutdown (); - } - - public static Label ml; - public static MenuBar menu; - public static CheckBox menuKeysStyle; - public static CheckBox menuAutoMouseNav; - private static bool heightAsBuffer = false; - static void MainApp () - { - if (Debugger.IsAttached) - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); - - Application.Init (); - Application.HeightAsBuffer = heightAsBuffer; - //ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler; - - var top = Application.Top; - - //Open (); -#if true - int margin = 3; - var win = new Window ("Hello") { - X = 1, - Y = 1, - - Width = Dim.Fill () - margin, - Height = Dim.Fill () - margin - }; -#else - var tframe = top.Frame; - - var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height - 1), "Hello"); -#endif - MenuItemDetails [] menuItems = { - new MenuItemDetails ("F_ind", "", null), - new MenuItemDetails ("_Replace", "", null), - new MenuItemDetails ("_Item1", "", null), - new MenuItemDetails ("_Also From Sub Menu", "", null) - }; - - menuItems [0].Action = () => ShowMenuItem (menuItems [0]); - menuItems [1].Action = () => ShowMenuItem (menuItems [1]); - menuItems [2].Action = () => ShowMenuItem (menuItems [2]); - menuItems [3].Action = () => ShowMenuItem (menuItems [3]); - - MenuItem miUseSubMenusSingleFrame = null; - var useSubMenusSingleFrame = false; - MenuItem miUseKeysUpDownAsKeysLeftRight = null; - var useKeysUpDownAsKeysLeftRight = false; - - MenuItem miHeightAsBuffer = null; - - menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("Text _Editor Demo", "", () => { running = Editor; Application.RequestStop (); }, null, null, Key.AltMask | Key.CtrlMask | Key.D), - new MenuItem ("_New", "Creates new file", NewFile, null, null, Key.AltMask | Key.CtrlMask| Key.N), - new MenuItem ("_Open", "", Open, null, null, Key.AltMask | Key.CtrlMask| Key.O), - new MenuItem ("_Hex", "", () => { running = ShowHex; Application.RequestStop (); }, null, null, Key.AltMask | Key.CtrlMask | Key.H), - new MenuItem ("_Close", "", Close, null, null, Key.AltMask | Key.Q), - new MenuItem ("_Disabled", "", () => { }, () => false), - new MenuBarItem ("_SubMenu Disabled", new MenuItem [] { - new MenuItem ("_Disabled", "", () => { }, () => false) - }), - null, - new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } }, null, null, Key.CtrlMask | Key.Q) - }), - new MenuBarItem ("_Edit", new MenuItem [] { - new MenuItem ("_Copy", "", Copy, null, null, Key.AltMask | Key.CtrlMask | Key.C), - new MenuItem ("C_ut", "", Cut, null, null, Key.AltMask | Key.CtrlMask| Key.X), - new MenuItem ("_Paste", "", Paste, null, null, Key.AltMask | Key.CtrlMask| Key.V), - new MenuBarItem ("_Find and Replace", - new MenuItem [] { menuItems [0], menuItems [1] }), - menuItems[3], - miUseKeysUpDownAsKeysLeftRight = new MenuItem ("Use_KeysUpDownAsKeysLeftRight", "", - () => { - menu.UseKeysUpDownAsKeysLeftRight = miUseKeysUpDownAsKeysLeftRight.Checked = useKeysUpDownAsKeysLeftRight = !useKeysUpDownAsKeysLeftRight; - miUseSubMenusSingleFrame.Checked = useSubMenusSingleFrame = menu.UseSubMenusSingleFrame; - }) { - CheckType = MenuItemCheckStyle.Checked, Checked = useKeysUpDownAsKeysLeftRight - }, - miUseSubMenusSingleFrame = new MenuItem ("Use_SubMenusSingleFrame", "", - () => { - menu.UseSubMenusSingleFrame = miUseSubMenusSingleFrame.Checked = useSubMenusSingleFrame = !useSubMenusSingleFrame; - miUseKeysUpDownAsKeysLeftRight.Checked = useKeysUpDownAsKeysLeftRight = menu.UseKeysUpDownAsKeysLeftRight; - }) { - CheckType = MenuItemCheckStyle.Checked, Checked = useSubMenusSingleFrame - }, - miHeightAsBuffer = new MenuItem ("_Height As Buffer", "", () => { - miHeightAsBuffer.Checked = heightAsBuffer = !heightAsBuffer; - Application.HeightAsBuffer = heightAsBuffer; - }) { CheckType = MenuItemCheckStyle.Checked, Checked = heightAsBuffer } - }), - new MenuBarItem ("_List Demos", new MenuItem [] { - new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true), null, null, Key.AltMask + 0.ToString () [0]), - new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false), null, null, Key.AltMask + 1.ToString () [0]), - new MenuItem ("Search Single Item", "", ComboBoxDemo, null, null, Key.AltMask + 2.ToString () [0]) - }), - new MenuBarItem ("A_ssorted", new MenuItem [] { - new MenuItem ("_Show text alignments", "", () => ShowTextAlignments (), null, null, Key.AltMask | Key.CtrlMask | Key.G), - new MenuItem ("_OnKeyDown/Press/Up", "", () => OnKeyDownPressUpDemo (), null, null, Key.AltMask | Key.CtrlMask | Key.K) - }), - new MenuBarItem ("_Test Menu and SubMenus", new MenuBarItem [] { - new MenuBarItem ("SubMenu1Item_1", new MenuBarItem [] { - new MenuBarItem ("SubMenu2Item_1", new MenuBarItem [] { - new MenuBarItem ("SubMenu3Item_1", - new MenuItem [] { menuItems [2] }) - }) - }) - }), - new MenuBarItem ("_About...", "Demonstrates top-level menu item", () => MessageBox.ErrorQuery (50, 7, "About Demo", "This is a demo app for gui.cs", "Ok")), - }); - - menuKeysStyle = new CheckBox (3, 25, "UseKeysUpDownAsKeysLeftRight", true); - menuKeysStyle.Toggled += MenuKeysStyle_Toggled; - menuAutoMouseNav = new CheckBox (40, 25, "UseMenuAutoNavigation", true); - menuAutoMouseNav.Toggled += MenuAutoMouseNav_Toggled; - - ShowEntries (win); - - int count = 0; - ml = new Label (new Rect (3, 17, 47, 1), "Mouse: "); - Application.RootMouseEvent += delegate (MouseEvent me) { - ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}"; - }; - - var test = new Label (3, 18, "Se iniciará el análisis"); - win.Add (test); - win.Add (ml); - - var drag = new Label ("Drag: ") { X = 70, Y = 22 }; - var dragText = new TextField ("") { - X = Pos.Right (drag), - Y = Pos.Top (drag), - Width = 40 - }; - - var statusBar = new StatusBar (new StatusItem [] { - new StatusItem(Key.F1, "~F1~ Help", () => Help()), - new StatusItem(Key.F2, "~F2~ Load", Load), - new StatusItem(Key.F3, "~F3~ Save", Save), - new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => { if (Quit ()) { running = null; top.Running = false; } }), - new StatusItem(Key.Null, Application.Driver.GetType().Name, null) - }); - - win.Add (drag, dragText); - - var bottom = new Label ("This should go on the bottom of the same top-level!"); - win.Add (bottom); - var bottom2 = new Label ("This should go on the bottom of another top-level!"); - top.Add (bottom2); - - top.LayoutComplete += (e) => { - bottom.X = win.X; - bottom.Y = Pos.Bottom (win) - Pos.Top (win) - margin; - bottom2.X = Pos.Left (win); - bottom2.Y = Pos.Bottom (win); - }; - - win.KeyPress += Win_KeyPress; - - top.Add (win); - //top.Add (menu); - top.Add (menu, statusBar); - Application.Run (top); - } - - private static void Win_KeyPress (View.KeyEventEventArgs e) - { - switch (ShortcutHelper.GetModifiersKey (e.KeyEvent)) { - case Key.CtrlMask | Key.T: - if (menu.IsMenuOpen) - menu.CloseMenu (); - else - menu.OpenMenu (); - e.Handled = true; - break; - } - } -} diff --git a/README.md b/README.md index 9517f1ebe..c9324b14e 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ descriptors. Most classes are safe for threading. ## Showcase & Examples +* **[C# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the C# Example. * **[UI Catalog](https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run --project UICatalog` to run the UI Catalog. * **[Reactive Example](https://github.com/gui-cs/Terminal.Gui/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/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the simple demo. * **[Standalone Example](https://github.com/gui-cs/Terminal.Gui/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/gui-cs/Terminal.Gui/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#. * **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools/blob/master/docs/Microsoft.PowerShell.ConsoleGuiTools/Out-ConsoleGridView.md)** - `OCGV` sends the output from a command to an interactive table. @@ -46,34 +46,34 @@ descriptors. Most classes are safe for threading. See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured. The [Conceptual Documentation](https://gui-cs.github.io/Terminal.Gui/articles/index.html) provides insight into core concepts. -## Sample Usage -(This code uses C# 9.0 [Top-level statements](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#top-level-statements).) +## Sample Usage in C# + ```csharp +// A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements + using Terminal.Gui; using NStack; -Application.Init(); -var top = Application.Top; +Application.Init (); // Creates the top-level window to show -var win = new Window("MyApp") -{ +var win = new Window ("Example App") { X = 0, Y = 1, // Leave one row for the toplevel menu - // By using Dim.Fill(), it will automatically resize without manual intervention - Width = Dim.Fill(), - Height = Dim.Fill() + // By using Dim.Fill(), this Window will automatically resize without manual intervention + Width = Dim.Fill (), + Height = Dim.Fill () }; -top.Add(win); +Application.Top.Add (win); // Creates a menubar, the item "New" has a help menu. -var menu = new MenuBar(new MenuBarItem[] { +var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_New", "Creates new file", null), + new MenuItem ("_New", "Creates a new file", null), new MenuItem ("_Close", "",null), - new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; }) + new MenuItem ("_Quit", "", () => { if (Quit ()) Application.Top.Running = false; }) }), new MenuBarItem ("_Edit", new MenuItem [] { new MenuItem ("_Copy", "", null), @@ -81,49 +81,49 @@ var menu = new MenuBar(new MenuBarItem[] { new MenuItem ("_Paste", "", null) }) }); -top.Add(menu); +Application.Top.Add (menu); -static bool Quit() +static bool Quit () { - var n = MessageBox.Query(50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No"); + var n = MessageBox.Query (50, 7, "Quit Example", "Are you sure you want to quit this example?", "Yes", "No"); return n == 0; } -var login = new Label("Login: ") { X = 3, Y = 2 }; -var password = new Label("Password: ") -{ - X = Pos.Left(login), - Y = Pos.Top(login) + 1 +var login = new Label ("Login: ") { X = 3, Y = 2 }; +var password = new Label ("Password: ") { + X = Pos.Left (login), + Y = Pos.Top (login) + 1 }; -var loginText = new TextField("") -{ - X = Pos.Right(password), - Y = Pos.Top(login), +var loginText = new TextField ("") { + X = Pos.Right (password), + Y = Pos.Top (login), Width = 40 }; -var passText = new TextField("") -{ +var passText = new TextField ("") { Secret = true, - X = Pos.Left(loginText), - Y = Pos.Top(password), - Width = Dim.Width(loginText) + X = Pos.Left (loginText), + Y = Pos.Top (password), + Width = Dim.Width (loginText) }; -// Add some controls, -win.Add( - // The ones with my favorite layout system, Computed +// Add the views to the main window, +win.Add ( + // Using Computed Layout: login, password, loginText, passText, - // The ones laid out like an australopithecus, with Absolute positions: - new CheckBox(3, 6, "Remember me"), - new RadioGroup(3, 8, new ustring[] { "_Personal", "_Company" }, 0), - new Button(3, 14, "Ok"), - new Button(10, 14, "Cancel"), - new Label(3, 18, "Press F9 or ESC plus 9 to activate the menubar") + // Using Absolute Layout: + new CheckBox (3, 6, "Remember me"), + new RadioGroup (3, 8, new ustring [] { "_Personal", "_Company" }, 0), + new Button (3, 14, "Ok"), + new Button (10, 14, "Cancel"), + new Label (3, 18, "Press F9 or ESC plus 9 to activate the menubar") ); -Application.Run(); -Application.Shutdown(); +// Run blocks until the user quits the application +Application.Run (); + +// Always bracket Application.Init with .Shutdown. +Application.Shutdown (); ``` The example above shows adding views using both styles of layout supported by **Terminal.Gui**: **Absolute layout** and **[Computed layout](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#layout)**. From 1a2387997c29bbbf691d143447483a57f7ad48ed Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 14 Oct 2022 12:47:25 -0600 Subject: [PATCH 17/58] Removed ancient StandaloneExample --- README.md | 5 +- StandaloneExample/Program.cs | 313 ------------------ .../Properties/launchSettings.json | 11 - StandaloneExample/README.md | 9 - StandaloneExample/StandaloneExample.csproj | 10 - StandaloneExample/StandaloneExample.sln | 25 -- 6 files changed, 2 insertions(+), 371 deletions(-) delete mode 100644 StandaloneExample/Program.cs delete mode 100644 StandaloneExample/Properties/launchSettings.json delete mode 100644 StandaloneExample/README.md delete mode 100644 StandaloneExample/StandaloneExample.csproj delete mode 100644 StandaloneExample/StandaloneExample.sln diff --git a/README.md b/README.md index c9324b14e..ec139f65c 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,10 @@ descriptors. Most classes are safe for threading. ## Showcase & Examples -* **[C# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the C# Example. * **[UI Catalog](https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run --project UICatalog` to run the UI Catalog. -* **[Reactive Example](https://github.com/gui-cs/Terminal.Gui/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. -* **[Standalone Example](https://github.com/gui-cs/Terminal.Gui/tree/master/StandaloneExample)** - A trivial .NET core sample application can be found in the `StandaloneExample` directory. Run `dotnet run` in directory to test. +* **[C# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the C# Example. * **[F# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#. +* **[Reactive Example](https://github.com/gui-cs/Terminal.Gui/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. * **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools/blob/master/docs/Microsoft.PowerShell.ConsoleGuiTools/Out-ConsoleGridView.md)** - `OCGV` sends the output from a command to an interactive table. * **[PoshRedisViewer](https://github.com/En3Tho/PoshRedisViewer)** - A compact Redis viewer module for PowerShell written in F# and Gui.cs * **[TerminalGuiDesigner](https://github.com/tznind/TerminalGuiDesigner)** - Cross platform view designer for building Terminal.Gui applications. diff --git a/StandaloneExample/Program.cs b/StandaloneExample/Program.cs deleted file mode 100644 index 4f5925b8c..000000000 --- a/StandaloneExample/Program.cs +++ /dev/null @@ -1,313 +0,0 @@ -namespace StandaloneExample { - using System.Linq; - using Terminal.Gui; - using System; - using NStack; - using System.Text; - using Rune = System.Rune; - using System.Runtime.InteropServices; - using System.Diagnostics; - - static class Demo { - class Box10x : View { - public Box10x (int x, int y) : base (new Rect (x, y, 10, 10)) - { - } - - public override void Redraw (Rect region) - { - Driver.SetAttribute (ColorScheme.Focus); - - for (int y = 0; y < 10; y++) { - Move (0, y); - for (int x = 0; x < 10; x++) { - Driver.AddRune ((Rune)('0' + ((x + y) % 10))); - } - } - } - } - - class Filler : View { - public Filler (Rect rect) : base (rect) - { - } - - public override void Redraw (Rect region) - { - Driver.SetAttribute (ColorScheme.Focus); - var f = Frame; - - for (int y = 0; y < f.Width; y++) { - Move (0, y); - for (int x = 0; x < f.Height; x++) { - var r = (x % 3) switch { - 0 => '.', - 1 => 'o', - _ => 'O', - }; - Driver.AddRune (r); - } - } - } - } - - static void ShowTextAlignments () - { - var container = new Window ("Show Text Alignments - Press Esc to return") { - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill () - }; - container.KeyUp += (e) => { - if (e.KeyEvent.Key == Key.Esc) - container.Running = false; - }; - - const int i = 0; - const string txt = "Hello world, how are you doing today?"; - container.Add ( - new Label ($"{i + 1}-{txt}") { TextAlignment = TextAlignment.Left, Y = 3, Width = Dim.Fill () }, - new Label ($"{i + 2}-{txt}") { TextAlignment = TextAlignment.Right, Y = 5, Width = Dim.Fill () }, - new Label ($"{i + 3}-{txt}") { TextAlignment = TextAlignment.Centered, Y = 7, Width = Dim.Fill () }, - new Label ($"{i + 4}-{txt}") { TextAlignment = TextAlignment.Justified, Y = 9, Width = Dim.Fill () } - ); - - Application.Run (container); - } - - static void ShowEntries (View container) - { - scrollView = new ScrollView (new Rect (50, 10, 20, 8)) { - ContentSize = new Size (100, 100), - ContentOffset = new Point (-1, -1), - ShowVerticalScrollIndicator = true, - ShowHorizontalScrollIndicator = true - }; - - AddScrollViewChild (); - - // This is just to debug the visuals of the scrollview when small - var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) { - ContentSize = new Size (100, 100), - ShowVerticalScrollIndicator = true, - ShowHorizontalScrollIndicator = true - }; - scrollView2.Add (new Box10x (0, 0)); - var progress = new ProgressBar (new Rect (68, 1, 10, 1)); - bool timer (MainLoop _) - { - progress.Pulse (); - return true; - } - - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer); - - // A little convoluted, this is because I am using this to test the - // layout based on referencing elements of another view: - - var login = new Label ("Login: ") { X = 3, Y = 6 }; - var password = new Label ("Password: ") { - X = Pos.Left (login), - Y = Pos.Bottom (login) + 1 - }; - var loginText = new TextField ("") { - X = Pos.Right (password), - Y = Pos.Top (login), - Width = 40 - }; - var passText = new TextField ("") { - Secret = true, - X = Pos.Left (loginText), - Y = Pos.Top (password), - Width = Dim.Width (loginText) - }; - - // Add some content - container.Add ( - login, - loginText, - password, - passText, - new FrameView (new Rect (3, 10, 25, 6), "Options", new View [] { - new CheckBox (1, 0, "Remember me"), - new RadioGroup (1, 2, new ustring [] { "_Personal", "_Company" }) } - ), - new ListView (new Rect (60, 6, 16, 4), new string [] { - "First row", - "<>", - "This is a very long row that should overflow what is shown", - "4th", - "There is an empty slot on the second row", - "Whoa", - "This is so cool" - }), - scrollView, - scrollView2, - new Button ("Ok") { X = 3, Y = 19 }, - new Button ("Cancel") { X = 10, Y = 19 }, - progress, - new Label ("Press F9 (on Unix ESC+9 is an alias) to activate the menubar") { X = 3, Y = 22 } - ); - } - - private static void AddScrollViewChild () - { - if (isBox10x) { - scrollView.Add (new Box10x (0, 0)); - } else { - scrollView.Add (new Filler (new Rect (0, 0, 40, 40))); - } - scrollView.ContentOffset = Point.Empty; - } - - static void NewFile () - { - var okButton = new Button ("Ok", is_default: true); - okButton.Clicked += () => Application.RequestStop (); - var cancelButton = new Button ("Cancel"); - cancelButton.Clicked += () => Application.RequestStop (); - - var d = new Dialog ( - "New File", 50, 20, - okButton, - cancelButton); - - var ml2 = new Label (1, 1, "Mouse Debug Line"); - d.Add (ml2); - Application.Run (d); - } - - static bool Quit () - { - var n = MessageBox.Query (50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No"); - return n == 0; - } - - static void Close () - { - MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok"); - } - - private static void ScrollViewCheck () - { - isBox10x = miScrollViewCheck.Children [0].Checked = !miScrollViewCheck.Children [0].Checked; - miScrollViewCheck.Children [1].Checked = !miScrollViewCheck.Children [1].Checked; - - scrollView.RemoveAll (); - AddScrollViewChild (); - } - - public static Label ml; - private static MenuBarItem miScrollViewCheck; - private static bool isBox10x = true; - private static Window win; - private static ScrollView scrollView; - - static void Main (string [] args) - { - if (args.Length > 0 && args.Contains ("-usc")) { - Application.UseSystemConsole = true; - } - - Console.OutputEncoding = Encoding.Default; - - Application.Init (); - - var top = Application.Top; - - win = new Window ("Hello") { - X = 0, - Y = 1, - Width = Dim.Fill (), - Height = Dim.Fill () - 1 - }; - - StringBuilder aboutMessage = new StringBuilder (); - aboutMessage.AppendLine (@"A comprehensive sample library for"); - aboutMessage.AppendLine (@""); - aboutMessage.AppendLine (@" _______ _ _ _____ _ "); - aboutMessage.AppendLine (@" |__ __| (_) | | / ____| (_) "); - aboutMessage.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ "); - aboutMessage.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | "); - aboutMessage.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | "); - aboutMessage.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| "); - aboutMessage.AppendLine (@""); - aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui"); - - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_New", "Creates new file", NewFile), - new MenuItem ("_Open", "", null), - new MenuItem ("_Close", "", () => Close ()), - new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; }) - }), - new MenuBarItem ("_Edit", new MenuItem [] { - new MenuItem ("_Copy", "", null), - new MenuItem ("C_ut", "", null), - new MenuItem ("_Paste", "", null) - }), - new MenuBarItem ("A_ssorted", new MenuItem [] { - new MenuItem ("_Show text alignments", "", () => ShowTextAlignments (), null, null, Key.AltMask | Key.CtrlMask | Key.G) - }), - miScrollViewCheck = new MenuBarItem ("ScrollView", new MenuItem [] { - new MenuItem ("Box10x", "", () => ScrollViewCheck()) {CheckType = MenuItemCheckStyle.Radio, Checked = true }, - new MenuItem ("Filler", "", () => ScrollViewCheck()) {CheckType = MenuItemCheckStyle.Radio } - }), - new MenuBarItem ("_Help", new MenuItem [] { - new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1), - new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2), - new MenuItem ("_About...", "About UI Catalog", - () => MessageBox.Query ("About UI Catalog", aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A) - }) - }); - - ShowEntries (win); - int count = 0; - ml = new Label (new Rect (3, 17, 47, 1), "Mouse: "); - Application.RootMouseEvent += (me) => ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}"; - - win.Add (ml); - - var statusBar = new StatusBar (new StatusItem [] { - new StatusItem(Key.F1, "~F1~ Help", () => MessageBox.Query (50, 7, "Help", "Helping", "Ok")), - new StatusItem(Key.F2, "~F2~ Load", () => MessageBox.Query (50, 7, "Load", "Loading", "Ok")), - new StatusItem(Key.F3, "~F3~ Save", () => MessageBox.Query (50, 7, "Save", "Saving", "Ok")), - new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => { if (Quit ()) top.Running = false; }), - new StatusItem(Key.Null, Application.Driver.GetType().Name, null) - }); - - top.Add (win, menu, statusBar); - Application.Run (); - - Application.Shutdown (); - } - - private static void OpenUrl (string url) - { - try { - if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { - url = url.Replace ("&", "^&"); - Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true }); - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) { - using (var process = new Process { - StartInfo = new ProcessStartInfo { - FileName = "xdg-open", - Arguments = url, - RedirectStandardError = true, - RedirectStandardOutput = true, - CreateNoWindow = true, - UseShellExecute = false - } - }) { - process.Start (); - } - } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { - Process.Start ("open", url); - } - } catch { - throw; - } - } - } -} \ No newline at end of file diff --git a/StandaloneExample/Properties/launchSettings.json b/StandaloneExample/Properties/launchSettings.json deleted file mode 100644 index 7fc5e118e..000000000 --- a/StandaloneExample/Properties/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "StandaloneExample": { - "commandName": "Project" - }, - "StandaloneExample-usc": { - "commandName": "Project", - "commandLineArgs": "-usc" - } - } -} \ No newline at end of file diff --git a/StandaloneExample/README.md b/StandaloneExample/README.md deleted file mode 100644 index d04ad1153..000000000 --- a/StandaloneExample/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This is just a simple standalone sample that shows how to consume Terinal.Gui from a NuGet package and .NET Core project. - -Simply run: - -``` -$ dotnet run -``` - -To launch the application. Or use Visual Studio, open the solution and run. diff --git a/StandaloneExample/StandaloneExample.csproj b/StandaloneExample/StandaloneExample.csproj deleted file mode 100644 index e9b60a7b2..000000000 --- a/StandaloneExample/StandaloneExample.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - latest - Exe - net6.0 - - - - - \ No newline at end of file diff --git a/StandaloneExample/StandaloneExample.sln b/StandaloneExample/StandaloneExample.sln deleted file mode 100644 index bc04acfca..000000000 --- a/StandaloneExample/StandaloneExample.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneExample", "StandaloneExample.csproj", "{8DC768EF-530D-4261-BD35-FC41E13B041E}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8DC768EF-530D-4261-BD35-FC41E13B041E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DC768EF-530D-4261-BD35-FC41E13B041E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DC768EF-530D-4261-BD35-FC41E13B041E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DC768EF-530D-4261-BD35-FC41E13B041E}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {C9C434AC-B7E4-43AB-834A-F9489766FDFF} - EndGlobalSection -EndGlobal From b811e33e34b016bf401899f357c73bc85bfe58c5 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 14 Oct 2022 12:51:12 -0600 Subject: [PATCH 18/58] readme tweaks --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ec139f65c..2f53d212b 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for instructions for downloading and fork ## 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 run --project UICatalog`. -* Windows - Open `Terminal.Gui.sln` with Visual Studio 2019. +* Windows - Open `Terminal.sln` with Visual Studio 2022. ## Contributing @@ -168,10 +168,4 @@ Debates on architecture and design can be found in Issues tagged with [design](h ## History -This is an updated version of [gui.cs](http://tirania.org/blog/archive/2007/Apr-16.html) that Miguel wrote for [mono-curses](https://github.com/mono/mono-curses) in 2007. - -The original **gui.cs** was a UI toolkit in a single file and tied to curses. This version tries to be console-agnostic and instead of having a container/widget model, only uses Views (which can contain subviews) and changes the rendering model to rely on damage regions instead of burdening each view with the details. - -A presentation of this was part of the [Retro.NET](https://channel9.msdn.com/Events/dotnetConf/2018/S313) talk at .NET Conf 2018 [Slides](https://tirania.org/Retro.pdf) - -The most recent release notes can be found in the [Terminal.Gui.csproj](https://github.com/gui-cs/Terminal.Gui/blob/master/Terminal.Gui/Terminal.Gui.csproj) file. +See [gui-cs](https://github.com/gui-cs/) for how this project came to be. \ No newline at end of file From 2cdc9ae9fc6fd7266c735437d5564a578d75198a Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 14 Oct 2022 12:53:07 -0600 Subject: [PATCH 19/58] fix typo --- Example/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/README.md b/Example/README.md index b03d0af0a..fc3238e1e 100644 --- a/Example/README.md +++ b/Example/README.md @@ -2,6 +2,6 @@ This example shows how to use the Terminal.Gui library to create a simple GUI application in C#. -This is the same code found in the Termina.Gui README.md file. +This is the same code found in the Terminal.Gui README.md file. See [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all Terminal.Gui samples. \ No newline at end of file From 503e1e8e22924ce165d306c2df7470c44afe9cd1 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 14 Oct 2022 12:54:52 -0600 Subject: [PATCH 20/58] fixes link to OCGV --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f53d212b..5269552cb 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ descriptors. Most classes are safe for threading. * **[C# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the C# Example. * **[F# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#. * **[Reactive Example](https://github.com/gui-cs/Terminal.Gui/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. -* **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools/blob/master/docs/Microsoft.PowerShell.ConsoleGuiTools/Out-ConsoleGridView.md)** - `OCGV` sends the output from a command to an interactive table. +* **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools)** - `OCGV` sends the output from a command to an interactive table. * **[PoshRedisViewer](https://github.com/En3Tho/PoshRedisViewer)** - A compact Redis viewer module for PowerShell written in F# and Gui.cs * **[TerminalGuiDesigner](https://github.com/tznind/TerminalGuiDesigner)** - Cross platform view designer for building Terminal.Gui applications. From 76d4e197db4bf06f3885c4c97807cd74e634bacb Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 14 Oct 2022 13:02:32 -0600 Subject: [PATCH 21/58] noted demo.cs deletion with link to history --- Example/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Example/README.md b/Example/README.md index fc3238e1e..7e9c80dd8 100644 --- a/Example/README.md +++ b/Example/README.md @@ -4,4 +4,6 @@ This example shows how to use the Terminal.Gui library to create a simple GUI ap This is the same code found in the Terminal.Gui README.md file. -See [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all Terminal.Gui samples. \ No newline at end of file +See [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all Terminal.Gui samples. + +Note, the old `demo.cs` example has been deleted because it was not a very good example. It can still be found in the [git history](https://github.com/gui-cs/Terminal.Gui/tree/v1.8.2). \ No newline at end of file From c505c5b770743155796aa0bff25783bfe46453b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 14:50:54 +0000 Subject: [PATCH 22/58] Bump actions/setup-dotnet from 3.0.1 to 3.0.2 Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3.0.1 to 3.0.2. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v3.0.1...v3.0.2) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/api-docs.yml | 2 +- .github/workflows/dotnet-core.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index e0d5def02..c82fc6520 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v2 - name: Setup .NET Core - uses: actions/setup-dotnet@v3.0.1 + uses: actions/setup-dotnet@v3.0.2 with: dotnet-version: 6.0.100 diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index b1a1cf807..200fe5984 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup .NET Core - uses: actions/setup-dotnet@v3.0.1 + uses: actions/setup-dotnet@v3.0.2 with: dotnet-version: 6.0.100 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b5adb13fd..c503897ae 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ jobs: echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.CommitsSinceVersionSource }}" - name: Setup dotnet - uses: actions/setup-dotnet@v3.0.1 + uses: actions/setup-dotnet@v3.0.2 with: dotnet-version: 6.0.100 From c5f8150de4381db314c0c8e57d03806a1e5629ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 14:50:57 +0000 Subject: [PATCH 23/58] Bump gittools/actions from 0.9.13 to 0.9.14 Bumps [gittools/actions](https://github.com/gittools/actions) from 0.9.13 to 0.9.14. - [Release notes](https://github.com/gittools/actions/releases) - [Commits](https://github.com/gittools/actions/compare/v0.9.13...v0.9.14) --- updated-dependencies: - dependency-name: gittools/actions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b5adb13fd..e69c452b9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,12 +16,12 @@ jobs: fetch-depth: 0 #fetch-depth is needed for GitVersion - name: Install and calculate the new version with GitVersion - uses: gittools/actions/gitversion/setup@v0.9.13 + uses: gittools/actions/gitversion/setup@v0.9.14 with: versionSpec: 5.x - name: Determine Version - uses: gittools/actions/gitversion/execute@v0.9.13 + uses: gittools/actions/gitversion/execute@v0.9.14 id: gitversion # step id used as reference for output values - name: Display GitVersion outputs From 43c47b5c22d873f8c881678812d41ec59f3d561d Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Mon, 17 Oct 2022 14:06:38 -0600 Subject: [PATCH 24/58] Added templates to readme --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5269552cb..4361b8419 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,20 @@ A toolkit for building rich console apps for .NET, .NET Core, and Mono that work ![Sample app](docfx/images/sample.gif) + +## Quick Start + +Paste these commands into your favorite terminal on Windows, Mac, or Linux. This will install the [Terminal.Gui.Templates](https://github.com/gui-cs/Terminal.Gui.templates), create a new "Hello World" TUI app, and run it. + +(Press `CTRL-Q` to exit the app) + +```powershell +dotnet new --install Terminal.Gui.templates +dotnet new tui -n myproj +cd myproj +dotnet run +``` + ## Documentation * [Documentation Home](https://gui-cs.github.io/Terminal.Gui/index.html) @@ -153,13 +167,15 @@ To install Terminal.Gui into a .NET Core project, use the `dotnet` CLI tool with dotnet add package Terminal.Gui ``` -See [CONTRIBUTING.md](CONTRIBUTING.md) for instructions for downloading and forking the source. +Or, you can use the [Terminal.Gui.Templates](https://github.com/gui-cs/Terminal.Gui.templates). -## Running and Building +## Building the Library and Running the Examples * Windows, Mac, and Linux - Build and run using the .NET SDK command line tools (`dotnet build` in the root directory). Run `UICatalog` with `dotnet run --project UICatalog`. * Windows - Open `Terminal.sln` with Visual Studio 2022. +See [CONTRIBUTING.md](CONTRIBUTING.md) for instructions for downloading and forking the source. + ## Contributing See [CONTRIBUTING.md](https://github.com/gui-cs/Terminal.Gui/blob/master/CONTRIBUTING.md). From 97c0f5349a7259a10025e8fe487faaa124296e66 Mon Sep 17 00:00:00 2001 From: Alexandru Ciobanu Date: Tue, 18 Oct 2022 21:11:20 +0100 Subject: [PATCH 25/58] cleanup and fix docs in View class --- Terminal.Gui/Core/View.cs | 423 +++++++++++++++++++------------------- 1 file changed, 214 insertions(+), 209 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index ea9d80885..668191451 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -107,8 +107,8 @@ namespace Terminal.Gui { /// /// The method is invoked when the size or layout of a view has /// changed. The default processing system will keep the size and dimensions - /// for views that use the , and will recompute the - /// frames for the vies that use . + /// for views that use the , and will recompute the + /// frames for the vies that use . /// /// public partial class View : Responder, ISupportInitializeNotification { @@ -248,9 +248,9 @@ namespace Terminal.Gui { /// Points to the current driver in use by the view, it is a convenience property /// for simplifying the development of new views. /// - public static ConsoleDriver Driver { get { return Application.Driver; } } + public static ConsoleDriver Driver => Application.Driver; - static IList empty = new List (0).AsReadOnly (); + static readonly IList empty = new List (0).AsReadOnly (); // This is null, and allocated on demand. List subviews; @@ -259,7 +259,7 @@ namespace Terminal.Gui { /// This returns a list of the subviews contained by this view. /// /// The subviews. - public IList Subviews => subviews == null ? empty : subviews.AsReadOnly (); + public IList Subviews => subviews?.AsReadOnly () ?? empty; // Internally, we use InternalSubviews rather than subviews, as we do not expect us // to make the same mistakes our users make when they poke at the Subviews. @@ -278,7 +278,7 @@ namespace Terminal.Gui { /// This returns a tab index list of the subviews contained by this view. /// /// The tabIndexes. - public IList TabIndexes => tabIndexes == null ? empty : tabIndexes.AsReadOnly (); + public IList TabIndexes => tabIndexes?.AsReadOnly () ?? empty; int tabIndex = -1; @@ -309,7 +309,7 @@ namespace Terminal.Gui { int GetTabIndex (int idx) { - int i = 0; + var i = 0; foreach (var v in SuperView.tabIndexes) { if (v.tabIndex == -1 || v == this) { continue; @@ -321,7 +321,7 @@ namespace Terminal.Gui { void SetTabIndex () { - int i = 0; + var i = 0; foreach (var v in SuperView.tabIndexes) { if (v.tabIndex == -1) { continue; @@ -337,7 +337,7 @@ namespace Terminal.Gui { /// This only be true if the is also true and the focus can be avoided by setting this to false /// public bool TabStop { - get { return tabStop; } + get => tabStop; set { if (tabStop == value) { return; @@ -358,12 +358,16 @@ namespace Terminal.Gui { } if (base.CanFocus != value) { base.CanFocus = value; - if (!value && tabIndex > -1) { + + switch (value) { + case false when tabIndex > -1: TabIndex = -1; + break; + case true when SuperView?.CanFocus == false && addingView: + SuperView.CanFocus = true; + break; } - if (value && SuperView?.CanFocus == false && addingView) { - SuperView.CanFocus = value; - } + if (value && tabIndex == -1) { TabIndex = SuperView != null ? SuperView.tabIndexes.IndexOf (this) : -1; } @@ -375,7 +379,7 @@ namespace Terminal.Gui { if (!value && HasFocus) { SetHasFocus (false, this); SuperView?.EnsureFocus (); - if (SuperView != null && SuperView?.Focused == null) { + if (SuperView != null && SuperView.Focused == null) { SuperView.FocusNext (); if (SuperView.Focused == null) { Application.Current.FocusNext (); @@ -389,7 +393,7 @@ namespace Terminal.Gui { if (!value) { view.oldCanFocus = view.CanFocus; view.oldTabIndex = view.tabIndex; - view.CanFocus = value; + view.CanFocus = false; view.tabIndex = -1; } else { if (addingView) { @@ -423,22 +427,18 @@ namespace Terminal.Gui { /// /// Returns a value indicating if this View is currently on Top (Active) /// - public bool IsCurrentTop { - get { - return Application.Current == this; - } - } + public bool IsCurrentTop => Application.Current == this; /// /// Gets or sets a value indicating whether this wants mouse position reports. /// /// true if want mouse position reports; otherwise, false. - public virtual bool WantMousePositionReports { get; set; } = false; + public virtual bool WantMousePositionReports { get; set; } /// /// Gets or sets a value indicating whether this want continuous button pressed event. /// - public virtual bool WantContinuousButtonPressed { get; set; } = false; + public virtual bool WantContinuousButtonPressed { get; set; } /// /// Gets or sets the frame for the view. The frame is relative to the view's container (). @@ -446,7 +446,7 @@ namespace Terminal.Gui { /// The frame. /// /// - /// Change the Frame when using the layout style to move or resize views. + /// Change the Frame when using the layout style to move or resize views. /// /// /// Altering the Frame of a view will trigger the redrawing of the @@ -480,8 +480,10 @@ namespace Terminal.Gui { LayoutStyle layoutStyle; /// - /// Controls how the View's is computed during the LayoutSubviews method, if the style is set to , - /// LayoutSubviews does not change the . If the style is the is updated using + /// Controls how the View's is computed during the LayoutSubviews method, if the style is set to + /// , + /// LayoutSubviews does not change the . If the style is + /// the is updated using /// the , , , and properties. /// /// The layout style. @@ -511,19 +513,17 @@ namespace Terminal.Gui { /// public Rect Bounds { get => new Rect (Point.Empty, Frame.Size); - set { - Frame = new Rect (frame.Location, value.Size); - } + set => Frame = new Rect (frame.Location, value.Size); } Pos x, y; /// - /// Gets or sets the X position for the view (the column). Only used the is . + /// Gets or sets the X position for the view (the column). Only used the is . /// /// The X Position. /// - /// If is changing this property has no effect and its value is indeterminate. + /// If is changing this property has no effect and its value is indeterminate. /// public Pos X { get => x; @@ -539,11 +539,11 @@ namespace Terminal.Gui { } /// - /// Gets or sets the Y position for the view (the row). Only used the is . + /// Gets or sets the Y position for the view (the row). Only used the is . /// /// The y position (line). /// - /// If is changing this property has no effect and its value is indeterminate. + /// If is changing this property has no effect and its value is indeterminate. /// public Pos Y { get => y; @@ -560,11 +560,11 @@ namespace Terminal.Gui { Dim width, height; /// - /// Gets or sets the width of the view. Only used the is . + /// Gets or sets the width of the view. Only used the is . /// /// The width. /// - /// If is changing this property has no effect and its value is indeterminate. + /// If is changing this property has no effect and its value is indeterminate. /// public Dim Width { get => width; @@ -587,10 +587,10 @@ namespace Terminal.Gui { } /// - /// Gets or sets the height of the view. Only used the is . + /// Gets or sets the height of the view. Only used the is . /// /// The height. - /// If is changing this property has no effect and its value is indeterminate. + /// If is changing this property has no effect and its value is indeterminate. public Dim Height { get => height; set { @@ -612,18 +612,18 @@ namespace Terminal.Gui { } /// - /// Forces validation with layout + /// Forces validation with layout /// to avoid breaking the and settings. /// public bool ForceValidatePosDim { get; set; } - bool ValidatePosDim (object oldvalue, object newValue) + bool ValidatePosDim (object oldValue, object newValue) { - if (!IsInitialized || layoutStyle == LayoutStyle.Absolute || oldvalue == null || oldvalue.GetType () == newValue.GetType () || this is Toplevel) { + if (!IsInitialized || layoutStyle == LayoutStyle.Absolute || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) { return true; } if (layoutStyle == LayoutStyle.Computed) { - if (oldvalue.GetType () != newValue.GetType () && !(newValue is Pos.PosAbsolute || newValue is Dim.DimAbsolute)) { + if (oldValue.GetType () != newValue.GetType () && !(newValue is Pos.PosAbsolute || newValue is Dim.DimAbsolute)) { return true; } } @@ -666,7 +666,7 @@ namespace Terminal.Gui { /// if the size can be set, otherwise. public bool SetMinWidthHeight () { - if (GetMinWidthHeight (out Size size)) { + if (GetMinWidthHeight (out var size)) { Bounds = new Rect (Bounds.Location, size); TextFormatter.Size = GetBoundsTextFormatterSize (); return true; @@ -686,13 +686,13 @@ namespace Terminal.Gui { public View SuperView => container; /// - /// Initializes a new instance of a class with the absolute + /// Initializes a new instance of a class with the absolute /// dimensions specified in the frame parameter. /// /// The region covered by this view. /// - /// This constructor initialize a View with a of . Use to - /// initialize a View with of + /// This constructor initialize a View with a of . + /// Use to initialize a View with of /// public View (Rect frame) { @@ -700,12 +700,12 @@ namespace Terminal.Gui { } /// - /// Initializes a new instance of using layout. + /// Initializes a new instance of using layout. /// /// /// /// Use , , , and properties to dynamically control the size and location of the view. - /// The will be created using + /// The will be created using /// coordinates. The initial size ( will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// @@ -713,14 +713,14 @@ namespace Terminal.Gui { /// If Height is greater than one, word wrapping is provided. /// /// - /// This constructor initialize a View with a of . + /// This constructor initialize a View with a of . /// Use , , , and properties to dynamically control the size and location of the view. /// /// public View () : this (text: string.Empty, direction: TextDirection.LeftRight_TopBottom) { } /// - /// Initializes a new instance of using layout. + /// Initializes a new instance of using layout. /// /// /// @@ -738,7 +738,7 @@ namespace Terminal.Gui { public View (int x, int y, ustring text) : this (TextFormatter.CalcRect (x, y, text), text) { } /// - /// Initializes a new instance of using layout. + /// Initializes a new instance of using layout. /// /// /// @@ -759,11 +759,11 @@ namespace Terminal.Gui { } /// - /// Initializes a new instance of using layout. + /// Initializes a new instance of using layout. /// /// /// - /// The will be created using + /// The will be created using /// coordinates with the given string. The initial size ( will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// @@ -795,12 +795,8 @@ namespace Terminal.Gui { TabStop = false; LayoutStyle = layoutStyle; // BUGBUG: CalcRect doesn't account for line wrapping - Rect r; - if (rect.IsEmpty) { - r = TextFormatter.CalcRect (0, 0, text, direction); - } else { - r = rect; - } + + var r = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect; Frame = r; Text = text; @@ -809,7 +805,7 @@ namespace Terminal.Gui { } /// - /// Can be overridden if the has + /// Can be overridden if the has /// different format than the default. /// protected virtual void UpdateTextFormatterText () @@ -823,18 +819,18 @@ namespace Terminal.Gui { /// protected virtual void ProcessResizeView () { - var _x = x is Pos.PosAbsolute ? x.Anchor (0) : frame.X; - var _y = y is Pos.PosAbsolute ? y.Anchor (0) : frame.Y; + var actX = x is Pos.PosAbsolute ? x.Anchor (0) : frame.X; + var actY = y is Pos.PosAbsolute ? y.Anchor (0) : frame.Y; if (AutoSize) { var s = GetAutoSize (); var w = width is Dim.DimAbsolute && width.Anchor (0) > s.Width ? width.Anchor (0) : s.Width; var h = height is Dim.DimAbsolute && height.Anchor (0) > s.Height ? height.Anchor (0) : s.Height; - frame = new Rect (new Point (_x, _y), new Size (w, h)); + frame = new Rect (new Point (actX, actY), new Size (w, h)); } else { var w = width is Dim.DimAbsolute ? width.Anchor (0) : frame.Width; var h = height is Dim.DimAbsolute ? height.Anchor (0) : frame.Height; - frame = new Rect (new Point (_x, _y), new Size (w, h)); + frame = new Rect (new Point (actX, actY), new Size (w, h)); SetMinWidthHeight (); } TextFormatter.Size = GetBoundsTextFormatterSize (); @@ -842,7 +838,7 @@ namespace Terminal.Gui { SetNeedsDisplay (); } - private void TextFormatter_HotKeyChanged (Key obj) + void TextFormatter_HotKeyChanged (Key obj) { HotKeyChanged?.Invoke (obj); } @@ -894,10 +890,11 @@ namespace Terminal.Gui { var h = Math.Max (NeedDisplay.Height, region.Height); NeedDisplay = new Rect (x, y, w, h); } - if (container != null) - container.SetChildNeedsDisplay (); + container?.SetChildNeedsDisplay (); + if (subviews == null) return; + foreach (var view in subviews) if (view.Frame.IntersectsWith (region)) { var childRegion = Rect.Intersect (view.Frame, region); @@ -919,7 +916,7 @@ namespace Terminal.Gui { container.SetChildNeedsDisplay (); } - internal bool addingView = false; + internal bool addingView; /// /// Adds a subview (child) to this view. @@ -1106,9 +1103,9 @@ namespace Terminal.Gui { { var h = Frame.Height; var w = Frame.Width; - for (int line = 0; line < h; line++) { + for (var line = 0; line < h; line++) { Move (0, line); - for (int col = 0; col < w; col++) + for (var col = 0; col < w; col++) Driver.AddRune (' '); } } @@ -1123,9 +1120,9 @@ namespace Terminal.Gui { { var h = regionScreen.Height; var w = regionScreen.Width; - for (int line = regionScreen.Y; line < regionScreen.Y + h; line++) { + for (var line = regionScreen.Y; line < regionScreen.Y + h; line++) { Driver.Move (regionScreen.X, line); - for (int col = 0; col < w; col++) + for (var col = 0; col < w; col++) Driver.AddRune (' '); } } @@ -1143,11 +1140,12 @@ namespace Terminal.Gui { // Computes the real row, col relative to the screen. rrow = row + frame.Y; rcol = col + frame.X; - var ccontainer = container; - while (ccontainer != null) { - rrow += ccontainer.frame.Y; - rcol += ccontainer.frame.X; - ccontainer = ccontainer.container; + + var curContainer = container; + while (curContainer != null) { + rrow += curContainer.frame.Y; + rcol += curContainer.frame.X; + curContainer = curContainer.container; } // The following ensures that the cursor is always in the screen boundaries. @@ -1283,8 +1281,8 @@ namespace Terminal.Gui { return; } - ViewToScreen (col, row, out var rcol, out var rrow, clipped); - Driver.Move (rcol, rrow); + ViewToScreen (col, row, out var rCol, out var rRow, clipped); + Driver.Move (rCol, rRow); } /// @@ -1313,12 +1311,9 @@ namespace Terminal.Gui { } bool hasFocus; + /// - public override bool HasFocus { - get { - return hasFocus; - } - } + public override bool HasFocus => hasFocus; void SetHasFocus (bool value, View view, bool force = false) { @@ -1388,7 +1383,7 @@ namespace Terminal.Gui { /// public override bool OnEnter (View view) { - FocusEventArgs args = new FocusEventArgs (view); + var args = new FocusEventArgs (view); Enter?.Invoke (args); if (args.Handled) return true; @@ -1401,7 +1396,7 @@ namespace Terminal.Gui { /// public override bool OnLeave (View view) { - FocusEventArgs args = new FocusEventArgs (view); + var args = new FocusEventArgs (view); Leave?.Invoke (args); if (args.Handled) return true; @@ -1695,7 +1690,7 @@ namespace Terminal.Gui { return false; } - KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + var args = new KeyEventEventArgs (keyEvent); KeyPress?.Invoke (args); if (args.Handled) return true; @@ -1704,10 +1699,8 @@ namespace Terminal.Gui { if (args.Handled) return true; } - if (Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true) - return true; - - return false; + + return Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true; } /// @@ -1737,7 +1730,7 @@ namespace Terminal.Gui { // if ever see a true then that's what we will return if (thisReturn ?? false) { - toReturn = thisReturn.Value; + toReturn = true; } } } @@ -1863,7 +1856,7 @@ namespace Terminal.Gui { /// The used by a public Key GetKeyFromCommand (params Command [] command) { - return KeyBindings.First (x => x.Value.SequenceEqual (command)).Key; + return KeyBindings.First (kb => kb.Value.SequenceEqual (command)).Key; } /// @@ -1873,7 +1866,7 @@ namespace Terminal.Gui { return false; } - KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + var args = new KeyEventEventArgs (keyEvent); if (MostFocused?.Enabled == true) { MostFocused?.KeyPress?.Invoke (args); if (args.Handled) @@ -1883,6 +1876,7 @@ namespace Terminal.Gui { return true; if (subviews == null || subviews.Count == 0) return false; + foreach (var view in subviews) if (view.Enabled && view.ProcessHotKey (keyEvent)) return true; @@ -1896,7 +1890,7 @@ namespace Terminal.Gui { return false; } - KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + var args = new KeyEventEventArgs (keyEvent); KeyPress?.Invoke (args); if (args.Handled) return true; @@ -1909,6 +1903,7 @@ namespace Terminal.Gui { return true; if (subviews == null || subviews.Count == 0) return false; + foreach (var view in subviews) if (view.Enabled && view.ProcessColdKey (keyEvent)) return true; @@ -1927,7 +1922,7 @@ namespace Terminal.Gui { return false; } - KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + var args = new KeyEventEventArgs (keyEvent); KeyDown?.Invoke (args); if (args.Handled) { return true; @@ -1957,7 +1952,7 @@ namespace Terminal.Gui { return false; } - KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + var args = new KeyEventEventArgs (keyEvent); KeyUp?.Invoke (args); if (args.Handled) { return true; @@ -2025,10 +2020,10 @@ namespace Terminal.Gui { return; } - for (int i = tabIndexes.Count; i > 0;) { + for (var i = tabIndexes.Count; i > 0;) { i--; - View v = tabIndexes [i]; + var v = tabIndexes [i]; if (v.CanFocus && v.tabStop && v.Visible && v.Enabled) { SetFocus (v); return; @@ -2054,21 +2049,22 @@ namespace Terminal.Gui { FocusLast (); return focused != null; } - int focused_idx = -1; - for (int i = tabIndexes.Count; i > 0;) { + + var focusedIdx = -1; + for (var i = tabIndexes.Count; i > 0;) { i--; - View w = tabIndexes [i]; + var w = tabIndexes [i]; if (w.HasFocus) { if (w.FocusPrev ()) return true; - focused_idx = i; + focusedIdx = i; continue; } - if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible && w.Enabled) { + if (w.CanFocus && focusedIdx != -1 && w.tabStop && w.Visible && w.Enabled) { focused.SetHasFocus (false, w); - if (w != null && w.CanFocus && w.tabStop && w.Visible && w.Enabled) + if (w.CanFocus && w.tabStop && w.Visible && w.Enabled) w.FocusLast (); SetFocus (w); @@ -2100,21 +2096,21 @@ namespace Terminal.Gui { FocusFirst (); return focused != null; } - int n = tabIndexes.Count; - int focused_idx = -1; - for (int i = 0; i < n; i++) { - View w = tabIndexes [i]; + var n = tabIndexes.Count; + var focusedIdx = -1; + for (var i = 0; i < n; i++) { + var w = tabIndexes [i]; if (w.HasFocus) { if (w.FocusNext ()) return true; - focused_idx = i; + focusedIdx = i; continue; } - if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible && w.Enabled) { + if (w.CanFocus && focusedIdx != -1 && w.tabStop && w.Visible && w.Enabled) { focused.SetHasFocus (false, w); - if (w != null && w.CanFocus && w.tabStop && w.Visible && w.Enabled) + if (w.CanFocus && w.tabStop && w.Visible && w.Enabled) w.FocusFirst (); SetFocus (w); @@ -2131,14 +2127,10 @@ namespace Terminal.Gui { View GetMostFocused (View view) { if (view == null) { - return view; + return null; } - if (view.focused != null) { - return GetMostFocused (view.focused); - } else { - return view; - } + return view.focused != null ? GetMostFocused (view.focused) : view; } /// @@ -2150,7 +2142,7 @@ namespace Terminal.Gui { /// internal void SetRelativeLayout (Rect hostFrame) { - int w, h, _x, _y; + int actW, actH, actX, actY; var s = Size.Empty; if (AutoSize) { @@ -2159,71 +2151,76 @@ namespace Terminal.Gui { if (x is Pos.PosCenter) { if (width == null) { - w = AutoSize ? s.Width : hostFrame.Width; + actW = AutoSize ? s.Width : hostFrame.Width; } else { - w = width.Anchor (hostFrame.Width); - w = AutoSize && s.Width > w ? s.Width : w; + actW = width.Anchor (hostFrame.Width); + actW = AutoSize && s.Width > actW ? s.Width : actW; } - _x = x.Anchor (hostFrame.Width - w); + actX = x.Anchor (hostFrame.Width - actW); } else { - if (x == null) - _x = 0; - else - _x = x.Anchor (hostFrame.Width); - if (width == null) { - w = AutoSize ? s.Width : hostFrame.Width; - } else if (width is Dim.DimFactor && !((Dim.DimFactor)width).IsFromRemaining ()) { - w = width.Anchor (hostFrame.Width); - w = AutoSize && s.Width > w ? s.Width : w; - } else { - w = Math.Max (width.Anchor (hostFrame.Width - _x), 0); - w = AutoSize && s.Width > w ? s.Width : w; + actX = x?.Anchor (hostFrame.Width) ?? 0; + + switch (width) { + case null: + actW = AutoSize ? s.Width : hostFrame.Width; + break; + case Dim.DimFactor factor when !factor.IsFromRemaining (): + actW = width.Anchor (hostFrame.Width); + actW = AutoSize && s.Width > actW ? s.Width : actW; + break; + default: + actW = Math.Max (width.Anchor (hostFrame.Width - actX), 0); + actW = AutoSize && s.Width > actW ? s.Width : actW; + break; } } if (y is Pos.PosCenter) { if (height == null) { - h = AutoSize ? s.Height : hostFrame.Height; + actH = AutoSize ? s.Height : hostFrame.Height; } else { - h = height.Anchor (hostFrame.Height); - h = AutoSize && s.Height > h ? s.Height : h; + actH = height.Anchor (hostFrame.Height); + actH = AutoSize && s.Height > actH ? s.Height : actH; } - _y = y.Anchor (hostFrame.Height - h); + actY = y.Anchor (hostFrame.Height - actH); } else { - if (y == null) - _y = 0; - else - _y = y.Anchor (hostFrame.Height); - if (height == null) { - h = AutoSize ? s.Height : hostFrame.Height; - } else if (height is Dim.DimFactor && !((Dim.DimFactor)height).IsFromRemaining ()) { - h = height.Anchor (hostFrame.Height); - h = AutoSize && s.Height > h ? s.Height : h; - } else { - h = Math.Max (height.Anchor (hostFrame.Height - _y), 0); - h = AutoSize && s.Height > h ? s.Height : h; + actY = y?.Anchor (hostFrame.Height) ?? 0; + + switch (height) { + case null: + actH = AutoSize ? s.Height : hostFrame.Height; + break; + case Dim.DimFactor factor when !factor.IsFromRemaining (): + actH = height.Anchor (hostFrame.Height); + actH = AutoSize && s.Height > actH ? s.Height : actH; + break; + default: + actH = Math.Max (height.Anchor (hostFrame.Height - actY), 0); + actH = AutoSize && s.Height > actH ? s.Height : actH; + break; } } - var r = new Rect (_x, _y, w, h); + + var r = new Rect (actX, actY, actW, actH); if (Frame != r) { - Frame = new Rect (_x, _y, w, h); + Frame = new Rect (actX, actY, actW, actH); if (!SetMinWidthHeight ()) TextFormatter.Size = GetBoundsTextFormatterSize (); } } // https://en.wikipedia.org/wiki/Topological_sorting - List TopologicalSort (HashSet nodes, HashSet<(View From, View To)> edges) + List TopologicalSort (IEnumerable nodes, ICollection<(View From, View To)> edges) { var result = new List (); // Set of all nodes with no incoming edges - var S = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n)))); + var noEdgeNodes = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n)))); - while (S.Any ()) { + while (noEdgeNodes.Any ()) { // remove a node n from S - var n = S.First (); - S.Remove (n); + var n = noEdgeNodes.First (); + noEdgeNodes.Remove (n); // add n to tail of L if (n != this?.SuperView) @@ -2239,13 +2236,13 @@ namespace Terminal.Gui { // if m has no other incoming edges then if (edges.All (me => !me.To.Equals (m)) && m != this?.SuperView) { // insert m into S - S.Add (m); + noEdgeNodes.Add (m); } } } if (edges.Any ()) { - var (from, to) = edges.First (); + (var from, var to) = edges.First (); if (from != Application.Top) { if (!ReferenceEquals (from, to)) { throw new InvalidOperationException ($"TopologicalSort (for Pos/Dim) cannot find {from} linked with {to}. Did you forget to add it to {this}?"); @@ -2270,7 +2267,7 @@ namespace Terminal.Gui { } /// - /// Fired after the Views's method has completed. + /// Fired after the View's method has completed. /// /// /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. @@ -2286,7 +2283,7 @@ namespace Terminal.Gui { } /// - /// Fired after the Views's method has completed. + /// Fired after the View's method has completed. /// /// /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. @@ -2321,7 +2318,7 @@ namespace Terminal.Gui { return; } - Rect oldBounds = Bounds; + var oldBounds = Bounds; OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds }); TextFormatter.Size = GetBoundsTextFormatterSize (); @@ -2333,7 +2330,9 @@ namespace Terminal.Gui { void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { - if (pos is Pos.PosView pv) { + switch (pos) { + case Pos.PosView pv: + { if (pv.Target != this) { nEdges.Add ((pv.Target, from)); } @@ -2342,17 +2341,22 @@ namespace Terminal.Gui { } return; } - if (pos is Pos.PosCombine pc) { + case Pos.PosCombine pc: + { foreach (var v in from.InternalSubviews) { CollectPos (pc.left, from, ref nNodes, ref nEdges); CollectPos (pc.right, from, ref nNodes, ref nEdges); } + break; + } } } void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) { - if (dim is Dim.DimView dv) { + switch (dim) { + case Dim.DimView dv: + { if (dv.Target != this) { nEdges.Add ((dv.Target, from)); } @@ -2361,11 +2365,14 @@ namespace Terminal.Gui { } return; } - if (dim is Dim.DimCombine dc) { + case Dim.DimCombine dc: + { foreach (var v in from.InternalSubviews) { CollectDim (dc.left, from, ref nNodes, ref nEdges); CollectDim (dc.right, from, ref nNodes, ref nEdges); } + break; + } } } @@ -2459,9 +2466,9 @@ namespace Terminal.Gui { } /// - /// Gets or sets a flag that determines whether will have trailing spaces preserved - /// or not when is enabled. If `true` any trailing spaces will be trimmed when - /// either the property is changed or when is set to `true`. + /// Gets or sets a flag that determines whether will have trailing spaces preserved + /// or not when is enabled. If `true` any trailing spaces will be trimmed when + /// either the property is changed or when is set to `true`. /// The default is `false`. /// public virtual bool PreserveTrailingSpaces { @@ -2488,7 +2495,7 @@ namespace Terminal.Gui { } /// - /// Gets or sets how the View's is aligned verticaly when drawn. Changing this property will redisplay the . + /// Gets or sets how the View's is aligned vertically when drawn. Changing this property will redisplay the . /// /// The text alignment. public virtual VerticalTextAlignment VerticalTextAlignment { @@ -2507,7 +2514,7 @@ namespace Terminal.Gui { get => TextFormatter.Direction; set { if (TextFormatter.Direction != value) { - var isValidOldAutSize = autoSize && IsValidAutoSize (out Size autSize); + var isValidOldAutSize = autoSize && IsValidAutoSize (out var _); var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (value); @@ -2558,7 +2565,7 @@ namespace Terminal.Gui { foreach (var view in subviews) { if (!value) { view.oldEnabled = view.Enabled; - view.Enabled = value; + view.Enabled = false; } else { view.Enabled = view.oldEnabled; view.addingView = false; @@ -2618,7 +2625,7 @@ namespace Terminal.Gui { void SetHotKey () { - TextFormatter.FindHotKey (text, HotKeySpecifier, true, out _, out Key hk); + TextFormatter.FindHotKey (text, HotKeySpecifier, true, out _, out var hk); if (hotKey != hk) { HotKey = hk; } @@ -2645,9 +2652,9 @@ namespace Terminal.Gui { bool SetWidthHeight (Size nBounds) { - bool aSize = false; - var canSizeW = SetWidth (nBounds.Width - GetHotKeySpecifierLength (), out int rW); - var canSizeH = SetHeight (nBounds.Height - GetHotKeySpecifierLength (false), out int rH); + var aSize = false; + var canSizeW = SetWidth (nBounds.Width - GetHotKeySpecifierLength (), out var rW); + var canSizeH = SetHeight (nBounds.Height - GetHotKeySpecifierLength (false), out var rH); if (canSizeW) { aSize = true; width = rW; @@ -2702,10 +2709,10 @@ namespace Terminal.Gui { } /// - /// Get the width or height of the length. + /// Get the width or height of the length. /// /// trueif is the width (default)falseif is the height. - /// The length of the . + /// The length of the . public int GetHotKeySpecifierLength (bool isWidth = true) { if (isWidth) { @@ -2720,9 +2727,9 @@ namespace Terminal.Gui { } /// - /// Gets the bounds size from a . + /// Gets the bounds size from a . /// - /// The bounds size minus the length. + /// The bounds size minus the length. public Size GetTextFormatterBoundsSize () { return new Size (TextFormatter.Size.Width - GetHotKeySpecifierLength (), @@ -2732,7 +2739,7 @@ namespace Terminal.Gui { /// /// Gets the text formatter size from a size. /// - /// The text formatter size more the length. + /// The text formatter size more the length. public Size GetBoundsTextFormatterSize () { if (ustring.IsNullOrEmpty (TextFormatter.Text)) @@ -2773,14 +2780,10 @@ namespace Terminal.Gui { return false; } - MouseEventArgs args = new MouseEventArgs (mouseEvent); + var args = new MouseEventArgs (mouseEvent); MouseEnter?.Invoke (args); - if (args.Handled) - return true; - if (base.OnMouseEnter (mouseEvent)) - return true; - - return false; + + return args.Handled || base.OnMouseEnter (mouseEvent); } /// @@ -2794,14 +2797,10 @@ namespace Terminal.Gui { return false; } - MouseEventArgs args = new MouseEventArgs (mouseEvent); + var args = new MouseEventArgs (mouseEvent); MouseLeave?.Invoke (args); - if (args.Handled) - return true; - if (base.OnMouseLeave (mouseEvent)) - return true; - - return false; + + return args.Handled || base.OnMouseLeave (mouseEvent); } /// @@ -2819,7 +2818,7 @@ namespace Terminal.Gui { return false; } - MouseEventArgs args = new MouseEventArgs (mouseEvent); + var args = new MouseEventArgs (mouseEvent); if (OnMouseClick (args)) return true; if (MouseEvent (mouseEvent)) @@ -2861,8 +2860,8 @@ namespace Terminal.Gui { /// protected override void Dispose (bool disposing) { - for (int i = InternalSubviews.Count - 1; i >= 0; i--) { - View subview = InternalSubviews [i]; + for (var i = InternalSubviews.Count - 1; i >= 0; i--) { + var subview = InternalSubviews [i]; Remove (subview); subview.Dispose (); } @@ -2919,7 +2918,7 @@ namespace Terminal.Gui { bool CanSetWidth (int desiredWidth, out int resultWidth) { - int w = desiredWidth; + var 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. @@ -2943,13 +2942,18 @@ namespace Terminal.Gui { bool CanSetHeight (int desiredHeight, out int resultHeight) { - int h = desiredHeight; + var h = desiredHeight; bool canSetHeight; - if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) { + switch (Height) { + case Dim.DimCombine _: + case Dim.DimView _: + case 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 = !ForceValidatePosDim; - } else if (Height is Dim.DimFactor factor) { + break; + case Dim.DimFactor factor: + { // Tries to get the SuperView height otherwise the view height. var sh = SuperView != null ? SuperView.Frame.Height : h; if (factor.IsFromRemaining ()) { @@ -2957,8 +2961,11 @@ namespace Terminal.Gui { } h = Height.Anchor (sh); canSetHeight = !ForceValidatePosDim; - } else { + break; + } + default: canSetHeight = true; + break; } resultHeight = h; @@ -2994,7 +3001,7 @@ namespace Terminal.Gui { /// true if the width can be directly assigned, false otherwise. public bool GetCurrentWidth (out int currentWidth) { - SetRelativeLayout (SuperView == null ? frame : SuperView.frame); + SetRelativeLayout (SuperView?.frame ?? frame); currentWidth = frame.Width; return CanSetWidth (0, out _); @@ -3007,7 +3014,7 @@ namespace Terminal.Gui { /// true if the height can be directly assigned, false otherwise. public bool GetCurrentHeight (out int currentHeight) { - SetRelativeLayout (SuperView == null ? frame : SuperView.frame); + SetRelativeLayout (SuperView?.frame ?? frame); currentHeight = frame.Height; return CanSetHeight (0, out _); @@ -3016,8 +3023,8 @@ namespace Terminal.Gui { /// /// Determines the current based on the value. /// - /// if is - /// or if is . + /// if is + /// or if is . /// If it's overridden can return other values. public virtual Attribute GetNormalColor () { @@ -3032,9 +3039,7 @@ namespace Terminal.Gui { { View top = Application.Top; for (var v = this?.SuperView; v != null; v = v.SuperView) { - if (v != null) { - top = v; - } + top = v; } return top; From 43d89b119a3b6804422f159569907e46cb72ef7a Mon Sep 17 00:00:00 2001 From: Alexandru Ciobanu Date: Tue, 18 Oct 2022 21:16:02 +0100 Subject: [PATCH 26/58] Added Rider/Resharper settings file that enforces the formatting and code rules. --- Terminal.sln.DotSettings | 128 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 Terminal.sln.DotSettings diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings new file mode 100644 index 000000000..222edc098 --- /dev/null +++ b/Terminal.sln.DotSettings @@ -0,0 +1,128 @@ + + WARNING + DO_NOT_SHOW + HINT + ExpressionBody + True + NotRequired + NotRequired + NotRequired + RequiredForMultiline + RequiredForMultiline + False + BlockBody + Explicit + Implicit + Separate + BlockBody + BlockBody + internal volatile public private new static async protected extern sealed override virtual unsafe abstract readonly + FileScoped + ExplicitlyTyped + ExplicitlyTyped + Conditional + Shift, Bitwise, Conditional + Remove + True + 0 + END_OF_LINE + END_OF_LINE + False + False + USE_TABS_ONLY + END_OF_LINE + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + False + OUTSIDE + USUAL_INDENT + 8 + OUTSIDE + Tab + False + END_OF_LINE + False + 100 + 100 + True + True + True + True + True + True + True + 2 + SIMPLE_WRAP + END_OF_LINE + NEVER + ALWAYS + ALWAYS + ALWAYS + NEVER + NEVER + False + False + True + False + False + NEVER + False + False + False + True + True + True + True + True + True + True + True + True + True + True + True + END_OF_LINE + CHOP_ALWAYS + True + WRAP_IF_LONG + WRAP_IF_LONG + WRAP_IF_LONG + 527 + CHOP_ALWAYS + WRAP_IF_LONG + CHOP_ALWAYS + WRAP_IF_LONG + False + True + True + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + True + Terminal.sln.DotSettings + + True + /Users/alex/Development/Terminal.Gui/Terminal.sln.DotSettings + + True + 1 + True + 2 + True + True + True + True From 20856f0ce0b5fbd2badd64ccf479f1653c56ca82 Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 19 Oct 2022 07:46:52 +0100 Subject: [PATCH 27/58] Non breaking implementation of cancellable root mouse events --- Terminal.Gui/Core/Application.cs | 17 ++++++++++++ UnitTests/TextFieldTests.cs | 44 +++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index f0764905c..608b4c991 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -638,6 +638,18 @@ namespace Terminal.Gui { UnGrabbedMouse?.Invoke (view); } + /// + /// + /// Cancellable overload of . + /// + /// + /// Called for new MouseEvent events before any processing is performed or + /// views evaluate. Use for global mouse handling and/or debugging. + /// + /// Return true to suppress the MouseEvent event + /// + public static Func RootMouseEventCancellable; + /// /// Merely a debugging aid to see the raw mouse events /// @@ -670,6 +682,11 @@ namespace Terminal.Gui { me.View = view; } RootMouseEvent?.Invoke (me); + + if (RootMouseEventCancellable?.Invoke (me) ?? false) { + return; + } + if (mouseGrabView != null) { if (view == null) { UngrabMouse (); diff --git a/UnitTests/TextFieldTests.cs b/UnitTests/TextFieldTests.cs index 3fcd92f44..6f36e55f7 100644 --- a/UnitTests/TextFieldTests.cs +++ b/UnitTests/TextFieldTests.cs @@ -1203,10 +1203,52 @@ namespace Terminal.Gui.Views { Application.Driver.SendKeys ('j', ConsoleKey.A, false, false, false); Assert.Equal ("aj", tf.Text.ToString ()); } + [Fact] + [AutoInitShutdown] + public void Test_RootMouseKeyEvent_Cancel () + { + Application.RootMouseEventCancellable += SuppressRightClick; + + var tf = new TextField () { Width = 10 }; + int clickCounter = 0; + tf.MouseClick += (m) => { clickCounter++; }; + + Application.Top.Add (tf); + Application.Begin (Application.Top); + + var processMouseEventMethod = typeof (Application).GetMethod ("ProcessMouseEvent", BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new Exception ("Expected private method not found 'ProcessMouseEvent', this method was used for testing mouse behaviours"); + + var mouseEvent = new MouseEvent { + Flags = MouseFlags.Button1Clicked, + View = tf + }; + + processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); + Assert.Equal (1, clickCounter); + + mouseEvent.Flags = MouseFlags.Button3Clicked; + + // should be ignored because of SuppressRightClick callback + processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); + Assert.Equal (1, clickCounter); + + Application.RootMouseEventCancellable -= SuppressRightClick; + + // should no longer be ignored as the callback was removed + processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); + Assert.Equal (2, clickCounter); + } + private bool SuppressKey (KeyEvent arg) { - if (arg.KeyValue == 'j') + return false; + } + + private bool SuppressRightClick (MouseEvent arg) + { + if (arg.Flags.HasFlag (MouseFlags.Button3Clicked)) return true; return false; From 67839d108a065087c1f8ea6742a0909b350d9914 Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 19 Oct 2022 07:52:13 +0100 Subject: [PATCH 28/58] Fix copy/paste error in SuppressKey test --- UnitTests/TextFieldTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UnitTests/TextFieldTests.cs b/UnitTests/TextFieldTests.cs index 6f36e55f7..bd317447f 100644 --- a/UnitTests/TextFieldTests.cs +++ b/UnitTests/TextFieldTests.cs @@ -1243,6 +1243,9 @@ namespace Terminal.Gui.Views { private bool SuppressKey (KeyEvent arg) { + if (arg.KeyValue == 'j') + return true; + return false; } From 76dab4ad0b8c475784c558526555961c2e703be3 Mon Sep 17 00:00:00 2001 From: Alexandru Ciobanu Date: Wed, 19 Oct 2022 09:37:52 +0100 Subject: [PATCH 29/58] Simplify MakePrintable by using AND and avoiding converting to string and back. --- Terminal.Gui/Core/ConsoleDriver.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index 873d8398b..cd180af1c 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/Terminal.Gui/Core/ConsoleDriver.cs @@ -681,11 +681,13 @@ namespace Terminal.Gui { /// Column to move the cursor to. /// Row to move the cursor to. public abstract void Move (int col, int row); + /// /// Adds the specified rune to the display at the current cursor position /// /// Rune to add. public abstract void AddRune (Rune rune); + /// /// Ensures a Rune is not a control character and can be displayed by translating characters below 0x20 /// to equivalent, printable, Unicode chars. @@ -694,21 +696,14 @@ namespace Terminal.Gui { /// public static Rune MakePrintable (Rune c) { - var controlChars = gethexaformat (c, 4); - if (controlChars <= 0x1F || (controlChars >= 0X7F && controlChars <= 0x9F)) { + var controlChars = c & 0xFFFF; + if (controlChars <= 0x1F || controlChars >= 0X7F && controlChars <= 0x9F) { // ASCII (C0) control characters. // C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1) return new Rune (controlChars + 0x2400); - } else { - return c; } - } - static uint gethexaformat (uint rune, int length) - { - var hex = rune.ToString ($"x{length}"); - var hexstr = hex.Substring (hex.Length - length, length); - return (uint)int.Parse (hexstr, System.Globalization.NumberStyles.HexNumber); + return c; } /// From 441960e25ee486f3858d02f5916c591462fefa6e Mon Sep 17 00:00:00 2001 From: Alexandru Ciobanu Date: Wed, 19 Oct 2022 13:41:02 +0100 Subject: [PATCH 30/58] Added two unit tests to check for the MakePrintable cases. --- UnitTests/ConsoleDriverTests.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/UnitTests/ConsoleDriverTests.cs b/UnitTests/ConsoleDriverTests.cs index cb4bc1dcf..e9688c638 100644 --- a/UnitTests/ConsoleDriverTests.cs +++ b/UnitTests/ConsoleDriverTests.cs @@ -608,5 +608,29 @@ namespace Terminal.Gui.ConsoleDrivers { Application.Run (win); Application.Shutdown (); } + + [Theory] + [InlineData(0x0000001F, 0x241F)] + [InlineData(0x0000007F, 0x247F)] + [InlineData(0x0000009F, 0x249F)] + [InlineData(0x0001001A, 0x241A)] + public void MakePrintable_Converts_Control_Chars_To_Proper_Unicode (uint code, uint expected) + { + var actual = ConsoleDriver.MakePrintable(code); + + Assert.Equal (expected, actual.Value); + } + + [Theory] + [InlineData(0x20)] + [InlineData(0x7E)] + [InlineData(0xA0)] + [InlineData(0x010020)] + public void MakePrintable_Does_Not_Convert_Ansi_Chars_To_Unicode (uint code) + { + var actual = ConsoleDriver.MakePrintable(code); + + Assert.Equal (code, actual.Value); + } } } From 3e19bf70edc82b873cc0a6b71a6e70b4530693a3 Mon Sep 17 00:00:00 2001 From: Alexandru Ciobanu Date: Wed, 19 Oct 2022 13:55:34 +0100 Subject: [PATCH 31/58] Correct for the comments on the PR and removed two unused imports. --- Terminal.Gui/Core/View.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 668191451..3496f82b3 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -6,15 +6,13 @@ // - Check for NeedDisplay on the hierarchy and repaint // - Layout support // - "Colors" type or "Attributes" type? -// - What to surface as "BackgroundCOlor" when clearing a window, an attribute or colors? +// - What to surface as "BackgroundColor" when clearing a window, an attribute or colors? // // Optimizations // - Add rendering limitation to the exposed area using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Linq; using NStack; @@ -666,7 +664,7 @@ namespace Terminal.Gui { /// if the size can be set, otherwise. public bool SetMinWidthHeight () { - if (GetMinWidthHeight (out var size)) { + if (GetMinWidthHeight (out Size size)) { Bounds = new Rect (Bounds.Location, size); TextFormatter.Size = GetBoundsTextFormatterSize (); return true; From b20483cc683cf037f8f07c9bd47aa1b0d7f22940 Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 19 Oct 2022 13:57:53 +0100 Subject: [PATCH 32/58] Change `MouseEvent` to a mutable class (previously struct) and added Handled --- Terminal.Gui/Core/Application.cs | 14 +------------- Terminal.Gui/Core/Event.cs | 8 +++++++- Terminal.Gui/Core/View.cs | 6 +++++- UnitTests/TextFieldTests.cs | 27 +++++++++++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 608b4c991..ea47e6b51 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -638,18 +638,6 @@ namespace Terminal.Gui { UnGrabbedMouse?.Invoke (view); } - /// - /// - /// Cancellable overload of . - /// - /// - /// Called for new MouseEvent events before any processing is performed or - /// views evaluate. Use for global mouse handling and/or debugging. - /// - /// Return true to suppress the MouseEvent event - /// - public static Func RootMouseEventCancellable; - /// /// Merely a debugging aid to see the raw mouse events /// @@ -683,7 +671,7 @@ namespace Terminal.Gui { } RootMouseEvent?.Invoke (me); - if (RootMouseEventCancellable?.Invoke (me) ?? false) { + if (me.Handled) { return; } diff --git a/Terminal.Gui/Core/Event.cs b/Terminal.Gui/Core/Event.cs index 181724b1c..35212ed62 100644 --- a/Terminal.Gui/Core/Event.cs +++ b/Terminal.Gui/Core/Event.cs @@ -708,7 +708,7 @@ namespace Terminal.Gui { /// /// Describes a mouse event /// - public struct MouseEvent { + public class MouseEvent { /// /// The X (column) location for the mouse event. /// @@ -739,6 +739,12 @@ namespace Terminal.Gui { /// public View View; + /// + /// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber. + /// Its important to set this value to true specially when updating any View's layout from inside the subscriber method. + /// + public bool Handled { get; set; } + /// /// Returns a that represents the current . /// diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index ea9d80885..0b7591d5a 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -2755,11 +2755,15 @@ namespace Terminal.Gui { /// The for the event. /// public MouseEvent MouseEvent { get; set; } + /// /// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber. /// Its important to set this value to true specially when updating any View's layout from inside the subscriber method. /// - public bool Handled { get; set; } + public bool Handled { + get => MouseEvent.Handled; + set => MouseEvent.Handled = value; + } } /// diff --git a/UnitTests/TextFieldTests.cs b/UnitTests/TextFieldTests.cs index bd317447f..c3bf8961e 100644 --- a/UnitTests/TextFieldTests.cs +++ b/UnitTests/TextFieldTests.cs @@ -1207,7 +1207,7 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void Test_RootMouseKeyEvent_Cancel () { - Application.RootMouseEventCancellable += SuppressRightClick; + Application.RootMouseEvent += SuppressRightClick; var tf = new TextField () { Width = 10 }; int clickCounter = 0; @@ -1227,15 +1227,24 @@ namespace Terminal.Gui.Views { processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); Assert.Equal (1, clickCounter); - mouseEvent.Flags = MouseFlags.Button3Clicked; - - // should be ignored because of SuppressRightClick callback + // Get a fresh instance that represents a right click. + // Should be ignored because of SuppressRightClick callback + mouseEvent = new MouseEvent { + Flags = MouseFlags.Button3Clicked, + View = tf + }; processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); Assert.Equal (1, clickCounter); - Application.RootMouseEventCancellable -= SuppressRightClick; + Application.RootMouseEvent -= SuppressRightClick; + + // Get a fresh instance that represents a right click. + // Should no longer be ignored as the callback was removed + mouseEvent = new MouseEvent { + Flags = MouseFlags.Button3Clicked, + View = tf + }; - // should no longer be ignored as the callback was removed processMouseEventMethod.Invoke (null, new object [] { mouseEvent }); Assert.Equal (2, clickCounter); } @@ -1249,12 +1258,10 @@ namespace Terminal.Gui.Views { return false; } - private bool SuppressRightClick (MouseEvent arg) + private void SuppressRightClick (MouseEvent arg) { if (arg.Flags.HasFlag (MouseFlags.Button3Clicked)) - return true; - - return false; + arg.Handled = true; } [Fact, AutoInitShutdown] From 711d4d739ea16f9163e6cd4d9ed6ae091a73a68c Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 19 Oct 2022 15:47:57 +0100 Subject: [PATCH 33/58] Updated docs and made MouseEvent fields into properties for futureproofing and consistency --- Terminal.Gui/Core/Event.cs | 18 +++++++++++------- Terminal.Gui/Core/View.cs | 6 +++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Terminal.Gui/Core/Event.cs b/Terminal.Gui/Core/Event.cs index 35212ed62..dd25b18a4 100644 --- a/Terminal.Gui/Core/Event.cs +++ b/Terminal.Gui/Core/Event.cs @@ -706,38 +706,42 @@ namespace Terminal.Gui { } /// - /// Describes a mouse event + /// Low-level construct that conveys the details of mouse events, such + /// as coordinates and button state, from ConsoleDrivers up to and + /// Views. /// + /// The class includes the + /// Action which takes a MouseEvent argument. public class MouseEvent { /// /// The X (column) location for the mouse event. /// - public int X; + public int X { get; set; } /// /// The Y (column) location for the mouse event. /// - public int Y; + public int Y { get; set; } /// /// Flags indicating the kind of mouse event that is being posted. /// - public MouseFlags Flags; + public MouseFlags Flags { get; set; } /// /// The offset X (column) location for the mouse event. /// - public int OfX; + public int OfX { get; set; } /// /// The offset Y (column) location for the mouse event. /// - public int OfY; + public int OfY { get; set; } /// /// The current view at the location for the mouse event. /// - public View View; + public View View { get; set; } /// /// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber. diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 0b7591d5a..a6cdb4fc8 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -2743,7 +2743,9 @@ namespace Terminal.Gui { } /// - /// Specifies the event arguments for + /// Specifies the event arguments for . This is a higher-level construct + /// than the wrapped class and is used for the events defined on + /// and subclasses of View (e.g. and ). /// public class MouseEventArgs : EventArgs { /// @@ -2760,6 +2762,8 @@ namespace Terminal.Gui { /// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber. /// Its important to set this value to true specially when updating any View's layout from inside the subscriber method. /// + /// This property forwards to the property and is provided as a convenience and for + /// backwards compatibility public bool Handled { get => MouseEvent.Handled; set => MouseEvent.Handled = value; From 516e7e25de8c32686928769516b05c1908e19d79 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 19 Oct 2022 20:24:03 +0100 Subject: [PATCH 34/58] Fixes 2094. View does not clear it's background. --- Terminal.Gui/Core/View.cs | 24 ++++++++++++++++++++---- Terminal.Gui/Views/ScrollView.cs | 13 ++----------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index a6cdb4fc8..2690f3659 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -11,11 +11,10 @@ // Optimizations // - Add rendering limitation to the exposed area using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Linq; +using System.Reflection; using NStack; namespace Terminal.Gui { @@ -1502,16 +1501,19 @@ namespace Terminal.Gui { var clipRect = new Rect (Point.Empty, frame.Size); - //if (ColorScheme != null && !(this is Toplevel)) { if (ColorScheme != null) { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); } if (Border != null) { Border.DrawContent (this); + } else if ((GetType ().IsPublic || GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") && + (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) { + + Clear (ViewToScreen (bounds)); } - if (!ustring.IsNullOrEmpty (TextFormatter.Text) || (this is Label && !AutoSize)) { + if (!ustring.IsNullOrEmpty (TextFormatter.Text)) { Clear (); // Draw any Text if (TextFormatter != null) { @@ -3047,5 +3049,19 @@ namespace Terminal.Gui { return top; } + + /// + /// Check if the is overridden in the . + /// + /// The view. + /// The method name. + /// if it's overridden, otherwise. + public bool IsOverridden (View view, string method) + { + Type t = view.GetType (); + MethodInfo m = t.GetMethod (method); + + return (m.DeclaringType == t || m.ReflectedType == t) && m.GetBaseDefinition ().DeclaringType == typeof (Responder); + } } } diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 820275178..fc186bc0e 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -13,7 +13,6 @@ using System; using System.Linq; -using System.Reflection; namespace Terminal.Gui { /// @@ -217,7 +216,7 @@ namespace Terminal.Gui { /// The view to add to the scrollview. public override void Add (View view) { - if (!IsOverridden (view)) { + if (!IsOverridden (view, "MouseEvent")) { view.MouseEnter += View_MouseEnter; view.MouseLeave += View_MouseLeave; } @@ -237,14 +236,6 @@ namespace Terminal.Gui { Application.GrabMouse (this); } - bool IsOverridden (View view) - { - Type t = view.GetType (); - MethodInfo m = t.GetMethod ("MouseEvent"); - - return (m.DeclaringType == t || m.ReflectedType == t) && m.GetBaseDefinition ().DeclaringType == typeof (Responder); - } - /// /// Gets or sets the visibility for the horizontal scroll indicator. /// @@ -515,7 +506,7 @@ namespace Terminal.Gui { vertical.MouseEvent (me); } else if (me.Y == horizontal.Frame.Y && ShowHorizontalScrollIndicator) { horizontal.MouseEvent (me); - } else if (IsOverridden (me.View)) { + } else if (IsOverridden (me.View, "MouseEvent")) { Application.UngrabMouse (); } return true; From 46ff335836391d3fd6029e75f6fc93397a6e533c Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 19 Oct 2022 20:46:12 +0100 Subject: [PATCH 35/58] Fix unit tests. --- UnitTests/ViewTests.cs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index 5218060ab..eeb606d65 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -2123,6 +2123,28 @@ Y Assert.Equal (new Rect (0, 0, 8, 4), pos); } + [Fact, AutoInitShutdown] + public void DrawFrame_With_Minimum_Size () + { + var view = new View (new Rect (0, 0, 2, 2)); + + view.DrawContent += (_) => view.DrawFrame (view.Bounds, 0, true); + + Assert.Equal (Point.Empty, new Point (view.Frame.X, view.Frame.Y)); + Assert.Equal (new Size (2, 2), new Size (view.Frame.Width, view.Frame.Height)); + + Application.Top.Add (view); + Application.Begin (Application.Top); + + var expected = @" +┌┐ +└┘ +"; + + var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); + Assert.Equal (new Rect (0, 0, 2, 2), pos); + } + [Fact, AutoInitShutdown] public void DrawFrame_With_Negative_Positions () { @@ -2452,7 +2474,7 @@ Y Width = Dim.Fill (), Height = Dim.Fill () }; - view.LayoutComplete += e => { + view.DrawContent += e => { view.DrawFrame (view.Bounds); var savedClip = Application.Driver.Clip; Application.Driver.Clip = new Rect (1, 1, view.Bounds.Width - 2, view.Bounds.Height - 2); @@ -2500,7 +2522,7 @@ Y Width = Dim.Fill (), Height = Dim.Fill () }; - view.LayoutComplete += e => { + view.DrawContent += e => { view.DrawFrame (view.Bounds); var savedClip = Application.Driver.Clip; Application.Driver.Clip = new Rect (1, 1, view.Bounds.Width - 2, view.Bounds.Height - 2); From 769f5c8091c5800d9bf0d40b84d948af0fec7ae2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 19 Oct 2022 20:59:10 +0100 Subject: [PATCH 36/58] Improves the TabView minimum size and added more unit tests. --- Terminal.Gui/Views/TabView.cs | 14 +- UnitTests/TabViewTests.cs | 462 ++++++++++++++++++++++++++++++++-- 2 files changed, 450 insertions(+), 26 deletions(-) diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index 2ea48c006..4905912d9 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -98,7 +98,7 @@ namespace Terminal.Gui { /// - /// Initialzies a class using layout. + /// Initializes a class using layout. /// public TabView () : base () { @@ -182,7 +182,7 @@ namespace Terminal.Gui { if (Style.ShowBorder) { - // How muc space do we need to leave at the bottom to show the tabs + // How much space do we need to leave at the bottom to show the tabs int spaceAtBottom = Math.Max (0, GetTabHeight (false) - 1); int startAtY = Math.Max (0, GetTabHeight (true) - 1); @@ -347,8 +347,10 @@ namespace Terminal.Gui { var maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth)); // if tab view is width <= 3 don't render any tabs - if (maxWidth == 0) - yield break; + if (maxWidth == 0) { + yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0); + break; + } if (tabTextWidth > maxWidth) { text = tab.Text.ToString ().Substring (0, (int)maxWidth); @@ -412,7 +414,7 @@ namespace Terminal.Gui { // if the currently selected tab is no longer a member of Tabs if (SelectedTab == null || !Tabs.Contains (SelectedTab)) { - // select the tab closest to the one that disapeared + // select the tab closest to the one that disappeared var toSelect = Math.Max (idx - 1, 0); if (toSelect < Tabs.Count) { @@ -657,7 +659,7 @@ namespace Terminal.Gui { Driver.AddRune (Driver.LeftArrow); } - // if there are mmore tabs to the right not visible + // if there are more tabs to the right not visible if (ShouldDrawRightScrollIndicator (tabLocations)) { Move (width - 1, y); diff --git a/UnitTests/TabViewTests.cs b/UnitTests/TabViewTests.cs index d69459d05..b1da15291 100644 --- a/UnitTests/TabViewTests.cs +++ b/UnitTests/TabViewTests.cs @@ -242,7 +242,7 @@ namespace Terminal.Gui.Views { } [Fact, AutoInitShutdown] - public void TestThinTabView_WithLongNames () + public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames () { var tv = GetTabView (out var tab1, out var tab2, false); tv.Width = 10; @@ -257,23 +257,34 @@ namespace Terminal.Gui.Views { tv.Redraw (tv.Bounds); - GraphViewTests.AssertDriverContentsAre (@" -┌──┐ -│12│13 + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌──┐ +│12│13 │ └─────┐ │hi │ └────────┘", output); + tv.SelectedTab = tab2; + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" + ┌──┐ + 12│13│ +┌──┘ └──┐ +│hi2 │ +└────────┘", output); + + tv.SelectedTab = tab1; // Test first tab name too long tab1.Text = "12345678910"; tab2.Text = "13"; tv.Redraw (tv.Bounds); - GraphViewTests.AssertDriverContentsAre (@" -┌───────┐ -│1234567│ + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌───────┐ +│1234567│ │ └► │hi │ └────────┘", output); @@ -282,9 +293,9 @@ namespace Terminal.Gui.Views { tv.SelectedTab = tab2; tv.Redraw (tv.Bounds); - GraphViewTests.AssertDriverContentsAre (@" -┌──┐ -│13│ + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌──┐ +│13│ ◄ └─────┐ │hi2 │ └────────┘", output); @@ -296,16 +307,94 @@ namespace Terminal.Gui.Views { tv.Redraw (tv.Bounds); - GraphViewTests.AssertDriverContentsAre (@" -┌───────┐ -│abcdefg│ + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌───────┐ +│abcdefg│ ◄ └┐ │hi2 │ └────────┘", output); } [Fact, AutoInitShutdown] - public void TestTabView_Width4 () + public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames () + { + var tv = GetTabView (out var tab1, out var tab2, false); + tv.Width = 10; + tv.Height = 5; + tv.Style = new TabView.TabStyle { ShowTopLine = false }; + tv.ApplyStyleChanges (); + + // Ensures that the tab bar subview gets the bounds of the parent TabView + tv.LayoutSubviews (); + + // Test two tab names that fit + tab1.Text = "12"; + tab2.Text = "13"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +│12│13 +│ └─────┐ +│hi │ +│ │ +└────────┘", output); + + + tv.SelectedTab = tab2; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" + 12│13│ +┌──┘ └──┐ +│hi2 │ +│ │ +└────────┘", output); + + tv.SelectedTab = tab1; + + // Test first tab name too long + tab1.Text = "12345678910"; + tab2.Text = "13"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +│1234567│ +│ └► +│hi │ +│ │ +└────────┘", output); + + //switch to tab2 + tv.SelectedTab = tab2; + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +│13│ +◄ └─────┐ +│hi2 │ +│ │ +└────────┘", output); + + + // now make both tabs too long + tab1.Text = "12345678910"; + tab2.Text = "abcdefghijklmnopq"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +│abcdefg│ +◄ └┐ +│hi2 │ +│ │ +└────────┘", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_True_TabsOnBottom_False_TestTabView_Width4 () { var tv = GetTabView (out _, out _, false); tv.Width = 4; @@ -314,16 +403,36 @@ namespace Terminal.Gui.Views { tv.Redraw (tv.Bounds); - GraphViewTests.AssertDriverContentsAre (@" -┌─┐ -│T│ + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌─┐ +│T│ │ └► │hi│ └──┘", output); } [Fact, AutoInitShutdown] - public void TestTabView_Width3 () + public void ShowTopLine_False_TabsOnBottom_False_TestTabView_Width4 () + { + var tv = GetTabView (out _, out _, false); + tv.Width = 4; + tv.Height = 5; + tv.Style = new TabView.TabStyle { ShowTopLine = false }; + tv.ApplyStyleChanges (); + tv.LayoutSubviews (); + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +│T│ +│ └► +│hi│ +│ │ +└──┘", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_True_TabsOnBottom_False_TestTabView_Width3 () { var tv = GetTabView (out _, out _, false); tv.Width = 3; @@ -332,12 +441,325 @@ namespace Terminal.Gui.Views { tv.Redraw (tv.Bounds); - GraphViewTests.AssertDriverContentsAre (@" -┌─┐ + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌┐ +││ +│└► │h│ └─┘", output); } + [Fact, AutoInitShutdown] + public void ShowTopLine_False_TabsOnBottom_False_TestTabView_Width3 () + { + var tv = GetTabView (out _, out _, false); + tv.Width = 3; + tv.Height = 5; + tv.Style = new TabView.TabStyle { ShowTopLine = false }; + tv.ApplyStyleChanges (); + tv.LayoutSubviews (); + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +││ +│└► +│h│ +│ │ +└─┘", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames () + { + var tv = GetTabView (out var tab1, out var tab2, false); + tv.Width = 10; + tv.Height = 5; + tv.Style = new TabView.TabStyle { TabsOnBottom = true }; + tv.ApplyStyleChanges (); + + // Ensures that the tab bar subview gets the bounds of the parent TabView + tv.LayoutSubviews (); + + // Test two tab names that fit + tab1.Text = "12"; + tab2.Text = "13"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi │ +│ ┌─────┘ +│12│13 +└──┘ ", output); + + + // Test first tab name too long + tab1.Text = "12345678910"; + tab2.Text = "13"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi │ +│ ┌► +│1234567│ +└───────┘ ", output); + + //switch to tab2 + tv.SelectedTab = tab2; + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi2 │ +◄ ┌─────┘ +│13│ +└──┘ ", output); + + + // now make both tabs too long + tab1.Text = "12345678910"; + tab2.Text = "abcdefghijklmnopq"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi2 │ +◄ ┌┘ +│abcdefg│ +└───────┘ ", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames () + { + var tv = GetTabView (out var tab1, out var tab2, false); + tv.Width = 10; + tv.Height = 5; + tv.Style = new TabView.TabStyle { ShowTopLine = false, TabsOnBottom = true }; + tv.ApplyStyleChanges (); + + // Ensures that the tab bar subview gets the bounds of the parent TabView + tv.LayoutSubviews (); + + // Test two tab names that fit + tab1.Text = "12"; + tab2.Text = "13"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi │ +│ │ +│ ┌─────┘ +│12│13 ", output); + + + tv.SelectedTab = tab2; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi2 │ +│ │ +└──┐ ┌──┘ + 12│13│ ", output); + + tv.SelectedTab = tab1; + + // Test first tab name too long + tab1.Text = "12345678910"; + tab2.Text = "13"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi │ +│ │ +│ ┌► +│1234567│ ", output); + + //switch to tab2 + tv.SelectedTab = tab2; + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi2 │ +│ │ +◄ ┌─────┘ +│13│ ", output); + + + // now make both tabs too long + tab1.Text = "12345678910"; + tab2.Text = "abcdefghijklmnopq"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────────┐ +│hi2 │ +│ │ +◄ ┌┘ +│abcdefg│ ", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_True_TabsOnBottom_True_TestTabView_Width4 () + { + var tv = GetTabView (out _, out _, false); + tv.Width = 4; + tv.Height = 5; + tv.Style = new TabView.TabStyle { TabsOnBottom = true }; + tv.ApplyStyleChanges (); + tv.LayoutSubviews (); + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌──┐ +│hi│ +│ ┌► +│T│ +└─┘ ", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_False_TabsOnBottom_True_TestTabView_Width4 () + { + var tv = GetTabView (out _, out _, false); + tv.Width = 4; + tv.Height = 5; + tv.Style = new TabView.TabStyle { ShowTopLine = false, TabsOnBottom = true }; + tv.ApplyStyleChanges (); + tv.LayoutSubviews (); + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌──┐ +│hi│ +│ │ +│ ┌► +│T│ ", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_True_TabsOnBottom_True_TestTabView_Width3 () + { + var tv = GetTabView (out _, out _, false); + tv.Width = 3; + tv.Height = 5; + tv.Style = new TabView.TabStyle { TabsOnBottom = true }; + tv.ApplyStyleChanges (); + tv.LayoutSubviews (); + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌─┐ +│h│ +│┌► +││ +└┘ ", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_False_TabsOnBottom_True_TestTabView_Width3 () + { + var tv = GetTabView (out _, out _, false); + tv.Width = 3; + tv.Height = 5; + tv.Style = new TabView.TabStyle { ShowTopLine = false, TabsOnBottom = true }; + tv.ApplyStyleChanges (); + tv.LayoutSubviews (); + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌─┐ +│h│ +│ │ +│┌► +││ ", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_True_TabsOnBottom_False_With_Unicode () + { + var tv = GetTabView (out var tab1, out var tab2, false); + tv.Width = 20; + tv.Height = 5; + + tv.LayoutSubviews (); + + tab1.Text = "Tab0"; + tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌────┐ +│Tab0│ +│ └─────────────► +│hi │ +└──────────────────┘", output); + + tv.SelectedTab = tab2; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌──────────────┐ +│Les Misérables│ +◄ └───┐ +│hi2 │ +└──────────────────┘", output); + } + + [Fact, AutoInitShutdown] + public void ShowTopLine_True_TabsOnBottom_True_With_Unicode () + { + var tv = GetTabView (out var tab1, out var tab2, false); + tv.Width = 20; + tv.Height = 5; + tv.Style = new TabView.TabStyle { TabsOnBottom = true }; + tv.ApplyStyleChanges (); + + tv.LayoutSubviews (); + + tab1.Text = "Tab0"; + tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables"; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌──────────────────┐ +│hi │ +│ ┌─────────────► +│Tab0│ +└────┘ ", output); + + tv.SelectedTab = tab2; + + tv.Redraw (tv.Bounds); + + GraphViewTests.AssertDriverContentsWithFrameAre (@" +┌──────────────────┐ +│hi2 │ +◄ ┌───┘ +│Les Misérables│ +└──────────────┘ ", output); + } + private void InitFakeDriver () { var driver = new FakeDriver (); From 38242dd1e636ef26180dd50b2628bc050fec9ea5 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 19 Oct 2022 17:00:22 -0600 Subject: [PATCH 37/58] Makes Notepad look better and fixes bugs --- UICatalog/Scenarios/Notepad.cs | 59 +++++++++++++--------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index b3cacf96c..7f5169080 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -1,30 +1,29 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.IO; using Terminal.Gui; -using static UICatalog.Scenario; namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "Notepad", Description: "Multi tab text editor uising the TabView control.")] + [ScenarioMetadata (Name: "Notepad", Description: "Multi-tab text editor uising the TabView control.")] [ScenarioCategory ("Controls"), ScenarioCategory ("TabView")] public class Notepad : Scenario { - TabView tabView; - Label lblStatus; private int numbeOfNewTabs = 1; + // Don't create a Window, just return the top-level view + public override void Init (Toplevel top, ColorScheme colorScheme) + { + Application.Init (); + + Top = top; + if (Top == null) { + Top = Application.Top; + } + Top.ColorScheme = Colors.Base; + } + public override void Setup () { - Win.Title = this.GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar - Top.LayoutSubviews (); - var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_New", "", () => New()), @@ -39,16 +38,17 @@ namespace UICatalog.Scenarios { tabView = new TabView () { X = 0, - Y = 0, + Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1), }; - tabView.Style.ShowBorder = false; + tabView.Style.ShowBorder = true; tabView.ApplyStyleChanges (); - Win.Add (tabView); + Top.Add (tabView); + var lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), @@ -58,26 +58,16 @@ namespace UICatalog.Scenarios { new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()), new StatusItem(Key.CtrlMask | Key.W, "~^W~ Close", () => Close()), + lenStatusItem, }); - Win.Add (lblStatus = new Label ("Len:") { - Y = Pos.Bottom (tabView), - Width = Dim.Fill (), - TextAlignment = TextAlignment.Right - }); - - tabView.SelectedTabChanged += (s, e) => UpdateStatus (e.NewTab); + tabView.SelectedTabChanged += (s, e) => lenStatusItem.Title = $"Len:{(e.NewTab?.View?.Text?.Length ?? 0)}"; Top.Add (statusBar); New (); } - private void UpdateStatus (TabView.Tab newTab) - { - lblStatus.Text = $"Len:{(newTab?.View?.Text?.Length ?? 0)}"; - } - private void New () { Open ("", null, $"new {numbeOfNewTabs++}"); @@ -109,12 +99,10 @@ namespace UICatalog.Scenarios { // close and dispose the tab tabView.RemoveTab (tab); tab.View.Dispose (); - } private void Open () { - var open = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true }; Application.Run (open); @@ -130,7 +118,6 @@ namespace UICatalog.Scenarios { Open (File.ReadAllText (path), new FileInfo (path), Path.GetFileName (path)); } } - } /// @@ -140,7 +127,6 @@ namespace UICatalog.Scenarios { /// File that was read or null if a new blank document private void Open (string initialText, FileInfo fileInfo, string tabName) { - var textView = new TextView () { X = 0, Y = 0, @@ -188,7 +174,7 @@ namespace UICatalog.Scenarios { } tab.Save (); - + tabView.SetNeedsDisplay (); } public bool SaveAs () @@ -207,14 +193,13 @@ namespace UICatalog.Scenarios { } tab.File = new FileInfo (fd.FilePath.ToString ()); + tab.Text = fd.FileName.ToString (); tab.Save (); return true; } private class OpenedFile : TabView.Tab { - - public FileInfo File { get; set; } /// From ddf78ce0f614a243dd6b956e2670751445c6d16f Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 19 Oct 2022 17:14:37 -0600 Subject: [PATCH 38/58] fixed shortcut for save as --- UICatalog/Scenarios/Notepad.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index 7f5169080..03f230b13 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -29,7 +29,7 @@ namespace UICatalog.Scenarios { new MenuItem ("_New", "", () => New()), new MenuItem ("_Open", "", () => Open()), new MenuItem ("_Save", "", () => Save()), - new MenuItem ("_Save As", "", () => SaveAs()), + new MenuItem ("Save _As", "", () => SaveAs()), new MenuItem ("_Close", "", () => Close()), new MenuItem ("_Quit", "", () => Quit()), }) From c15d9bcc575f93410f35e90b84dabfe2c041aaa8 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Thu, 20 Oct 2022 08:15:54 -0600 Subject: [PATCH 39/58] refactoring menutests --- Terminal.Gui/Views/Menu.cs | 10 +- UICatalog/Scenarios/CsvEditor.cs | 10 +- UnitTests/ContextMenuTests.cs | 8 +- UnitTests/MenuTests.cs | 167 +++++++++++++++++-------------- UnitTests/PosTests.cs | 4 +- 5 files changed, 109 insertions(+), 90 deletions(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 804ddb5ed..abab9c711 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1024,6 +1024,8 @@ namespace Terminal.Gui { isCleaning = false; } + static int leftPadding = 1; + static int rightPadding = 1; /// public override void Redraw (Rect bounds) { @@ -1033,7 +1035,7 @@ namespace Terminal.Gui { Driver.AddRune (' '); Move (1, 0); - int pos = 1; + int pos = 0; for (int i = 0; i < Menus.Length; i++) { var menu = Menus [i]; @@ -1050,8 +1052,8 @@ namespace Terminal.Gui { hotColor = GetNormalColor (); normalColor = GetNormalColor (); } - DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} {menu.Help} ", hotColor, normalColor); - pos += 1 + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + 2 : 0) + 2; + DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ", hotColor, normalColor); + pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + 3 : 0) + rightPadding; } PositionCursor (); } @@ -1073,7 +1075,7 @@ namespace Terminal.Gui { } return; } else { - pos += 1 + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2; + pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 3 : 0) + rightPadding; } } } diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index e344b2325..ab83b487d 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -43,12 +43,14 @@ namespace UICatalog.Scenarios { Height = Dim.Fill (1), }; - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { + var fileMenu = new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_Open CSV", "", () => Open()), new MenuItem ("_Save", "", () => Save()), - new MenuItem ("_Quit", "", () => Quit()), - }), + new MenuItem ("_Quit", "Quits The App", () => Quit()), + }); + //fileMenu.Help = "Help"; + var menu = new MenuBar (new MenuBarItem [] { + fileMenu, new MenuBarItem ("_Edit", new MenuItem [] { new MenuItem ("_New Column", "", () => AddColumn()), new MenuItem ("_New Row", "", () => AddRow()), diff --git a/UnitTests/ContextMenuTests.cs b/UnitTests/ContextMenuTests.cs index a0c88168a..aab102b6c 100644 --- a/UnitTests/ContextMenuTests.cs +++ b/UnitTests/ContextMenuTests.cs @@ -592,7 +592,7 @@ namespace Terminal.Gui.Core { Assert.Equal (new Point (9, 3), tf.ContextMenu.Position); Application.Top.Redraw (Application.Top.Bounds); var expected = @" - File Edit + File Edit Label: TextField @@ -612,7 +612,7 @@ namespace Terminal.Gui.Core { "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 32, 17), pos); + Assert.Equal (new Rect (1, 0, 32, 17), pos); } [Fact, AutoInitShutdown] @@ -656,7 +656,7 @@ namespace Terminal.Gui.Core { Assert.Equal (new Point (10, 5), tf.ContextMenu.Position); Application.Top.Redraw (Application.Top.Bounds); var expected = @" - File Edit + File Edit ┌ Window ──────────────────────────────────┐ │ │ │ │ @@ -676,7 +676,7 @@ namespace Terminal.Gui.Core { "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 44, 17), pos); + Assert.Equal (new Rect (1, 0, 44, 17), pos); } [Fact, AutoInitShutdown] diff --git a/UnitTests/MenuTests.cs b/UnitTests/MenuTests.cs index 77c336b8a..cedb32aec 100644 --- a/UnitTests/MenuTests.cs +++ b/UnitTests/MenuTests.cs @@ -1074,11 +1074,11 @@ Edit Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); var expected = @" - File Edit + File Edit "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 1), pos); + Assert.Equal (new Rect (1, 0, 11, 1), pos); Assert.True (menu.ProcessKey (new (Key.N, null))); Application.MainLoop.MainIteration (); @@ -1088,11 +1088,11 @@ Edit Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit + File Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 1), pos); + Assert.Equal (new Rect (1, 0, 11, 1), pos); Assert.True (menu.ProcessKey (new (Key.CursorRight, null))); Assert.True (menu.ProcessKey (new (Key.C, null))); @@ -1124,14 +1124,14 @@ Edit Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); var expected = @" - File Edit -┌──────┐ -│ New │ -└──────┘ + File Edit +┌──────┐ +│ New │ +└──────┘ "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 4), pos); + Assert.Equal (new Rect (1, 0, 11, 4), pos); Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.N, null))); Application.MainLoop.MainIteration (); @@ -1141,7 +1141,7 @@ Edit Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit + File Edit ┌───────┐ │ Copy │ └───────┘ @@ -1158,89 +1158,104 @@ Edit [Fact, AutoInitShutdown] public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKeys () { - // With HotKeys + var firstMenuBarText = "File"; + var firstMenuBarItemText = "New"; + var secondMenuBarText = "Edit"; + var secondMenuBarItemText = "Copy"; + + // Define expected MenuBar + // " File New " + var expectedMenuBarText = " " + firstMenuBarText + " " + " " + secondMenuBarText + " "; + + // Define expected menu frame + // "┌──────┐" + // "│ New │" + // "└──────┘" + var d = ((FakeDriver)Application.Driver); + // BUGBUG: The extra 4 spaces on these should not be there + var expectedFirstTopRow = $"{d.ULCorner}{new String (d.HLine.ToString () [0], firstMenuBarItemText.Length + 3)}{d.URCorner} "; + var expectedFirstBottomRow = $"{d.LLCorner}{new String (d.HLine.ToString () [0], firstMenuBarItemText.Length + 3)}{d.LRCorner} "; + + var expectedSecondTopRow = $"{d.ULCorner}{new String (d.HLine.ToString () [0], secondMenuBarItemText.Length + 3)}{d.URCorner}"; + var expectedSecondBottomRow = $"{d.LLCorner}{new String (d.HLine.ToString () [0], secondMenuBarItemText.Length + 3)}{d.LRCorner}"; + + var expectedClosed = " " + firstMenuBarText + " " + " " + secondMenuBarText + "" + "\n"; + + var expectedFirstMenuOpen = " " + firstMenuBarText + " " + " " + secondMenuBarText + "" + "\n" + + expectedFirstTopRow + "\n" + + $"{d.VLine} {firstMenuBarItemText} {d.VLine}" + " \n" + + expectedFirstBottomRow + "\n"; + + var expectedSecondMenuOpen = " " + firstMenuBarText + " " + " " + secondMenuBarText + " " + "\n" + + new String (' ', firstMenuBarItemText.Length + 4) + expectedSecondTopRow + "\n" + + new String (' ', firstMenuBarItemText.Length + 4) + $"{d.VLine} {secondMenuBarItemText} {d.VLine}" + "\n" + + new String (' ', firstMenuBarItemText.Length + 4) + expectedSecondBottomRow + "\n"; + + // Test without HotKeys first var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_New", "", null) + new MenuBarItem (firstMenuBarText, new MenuItem [] { + new MenuItem (firstMenuBarItemText, "", null) }), - new MenuBarItem ("_Edit", new MenuItem [] { - new MenuItem ("_Copy", "", null) + new MenuBarItem (secondMenuBarText, new MenuItem [] { + new MenuItem (secondMenuBarItemText, "", null) }) }); Application.Top.Add (menu); + // Open first Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - var expected = @" - File Edit -┌──────┐ -│ New │ -└──────┘ -"; - - var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 4), pos); + var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedFirstMenuOpen, output); + Assert.Equal (1, pos.X); + Assert.Equal (0, pos.Y); + // Open second Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit - ┌───────┐ - │ Copy │ - └───────┘ -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 16, 4), pos); + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedSecondMenuOpen, output); + // Close menu Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit -"; + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedClosed, output); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 1), pos); + Application.Top.Remove (menu); - // Without HotKeys + // Now test WITH HotKeys menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("File", new MenuItem [] { - new MenuItem ("New", "", null) + new MenuBarItem ("_" + firstMenuBarText, new MenuItem [] { + new MenuItem ("_" + firstMenuBarItemText, "", null) }), - new MenuBarItem ("Edit", new MenuItem [] { - new MenuItem ("Copy", "", null) + new MenuBarItem ("_" + secondMenuBarText, new MenuItem [] { + new MenuItem ("_" + secondMenuBarItemText, "", null) }) }); + Application.Top.Add (menu); + + // Open first Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit -┌──────┐ -│ New │ -└──────┘ -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 4), pos); + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedFirstMenuOpen, output); + Assert.Equal (1, pos.X); + Assert.Equal (0, pos.Y); + // Open second Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit - ┌───────┐ - │ Copy │ - └───────┘ -"; + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedSecondMenuOpen, output); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 16, 4), pos); + // Close menu + Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); + Assert.False (menu.IsMenuOpen); + Application.Top.Redraw (Application.Top.Bounds); + pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedClosed, output); } [Fact, AutoInitShutdown] @@ -1261,24 +1276,24 @@ Edit Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); var expected = @" - File Edit + File Edit ┌──────┐ │ New │ └──────┘ "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 4), pos); + Assert.Equal (new Rect (1, 0, 11, 4), pos); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit + File Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 13, 1), pos); + Assert.Equal (new Rect (1, 0, 11, 1), pos); } [Fact] @@ -1320,7 +1335,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); var expected = @" - File Edit Format + File Edit Format ┌──────┐ │ New │ └──────┘ @@ -1334,7 +1349,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); @@ -1345,7 +1360,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format ┌───────┐ │ Wrap │ └───────┘ @@ -1359,7 +1374,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); @@ -1370,7 +1385,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format ┌──────┐ │ New │ └──────┘ @@ -1384,7 +1399,7 @@ Edit Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); @@ -1414,7 +1429,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); var expected = @" - File Edit Format + File Edit Format ┌──────┐ │ New │ └──────┘ @@ -1428,7 +1443,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); @@ -1439,7 +1454,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format ┌───────┐ │ Wrap │ └───────┘ @@ -1453,7 +1468,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); @@ -1464,7 +1479,7 @@ Edit Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format ┌──────┐ │ New │ └──────┘ @@ -1478,7 +1493,7 @@ Edit Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); expected = @" - File Edit Format + File Edit Format "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); diff --git a/UnitTests/PosTests.cs b/UnitTests/PosTests.cs index b3efecf0b..528a722eb 100644 --- a/UnitTests/PosTests.cs +++ b/UnitTests/PosTests.cs @@ -247,7 +247,7 @@ namespace Terminal.Gui.Core { win.Frame.Right, win.Frame.Bottom)); Assert.Equal (new Rect (0, 20, 78, 1), label.Frame); var expected = @" - Menu + Menu ┌──────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ @@ -310,7 +310,7 @@ namespace Terminal.Gui.Core { win.Frame.Right, win.Frame.Bottom)); Assert.Equal (new Rect (0, 20, 78, 1), label.Frame); var expected = @" - Menu + Menu ┌──────────────────────────────────────────────────────────────────────────────┐ │ │ │ │ From 52e6d10d84c94d7fb68e46bb60b962e92f4c3ca1 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Thu, 20 Oct 2022 09:37:05 -0600 Subject: [PATCH 40/58] Fixed tons of little doc typos --- Terminal.Gui/Core/View.cs | 161 ++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 84 deletions(-) diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index aa121d131..8548be267 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1,16 +1,4 @@ -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// -// Pending: -// - Check for NeedDisplay on the hierarchy and repaint -// - Layout support -// - "Colors" type or "Attributes" type? -// - What to surface as "BackgroundColor" when clearing a window, an attribute or colors? -// -// Optimizations -// - Add rendering limitation to the exposed area -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -19,9 +7,9 @@ using NStack; namespace Terminal.Gui { /// - /// Determines the LayoutStyle for a view, if Absolute, during LayoutSubviews, the - /// value from the Frame will be used, if the value is Computed, then the Frame - /// will be updated from the X, Y Pos objects and the Width and Height Dim objects. + /// Determines the LayoutStyle for a , if Absolute, during , the + /// value from the will be used, if the value is Computed, then + /// will be updated from the X, Y objects and the Width and Height objects. /// public enum LayoutStyle { /// @@ -37,17 +25,19 @@ namespace Terminal.Gui { } /// - /// View is the base class for all views on the screen and represents a visible element that can render itself and contains zero or more nested views. + /// View is the base class for all views on the screen and represents a visible element that can render itself and + /// contains zero or more nested views. /// /// /// - /// The View defines the base functionality for user interface elements in Terminal.Gui. Views + /// The View defines the base functionality for user interface elements in Terminal.Gui. Views /// can contain one or more subviews, can respond to user input and render themselves on the screen. /// /// - /// Views supports two layout styles: Absolute or Computed. The choice as to which layout style is used by the View + /// Views supports two layout styles: or . + /// The choice as to which layout style is used by the View /// is determined when the View is initialized. To create a View using Absolute layout, call a constructor that takes a - /// Rect parameter to specify the absolute position and size (the View.)/. To create a View + /// Rect parameter to specify the absolute position and size (the View.). To create a View /// using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height /// properties on the view. Both approaches use coordinates that are relative to the container they are being added to. /// @@ -60,9 +50,9 @@ namespace Terminal.Gui { /// properties are Dim and Pos objects that dynamically update the position of a view. /// The X and Y properties are of type /// and you can use either absolute positions, percentages or anchor - /// points. The Width and Height properties are of type + /// points. The Width and Height properties are of type /// and can use absolute position, - /// percentages and anchors. These are useful as they will take + /// percentages and anchors. These are useful as they will take /// care of repositioning views when view's frames are resized or /// if the terminal size changes. /// @@ -72,17 +62,17 @@ namespace Terminal.Gui { /// property. /// /// - /// Subviews (child views) can be added to a View by calling the method. + /// Subviews (child views) can be added to a View by calling the method. /// The container of a View can be accessed with the property. /// /// - /// To flag a region of the View's to be redrawn call . To flag the entire view - /// for redraw call . + /// To flag a region of the View's to be redrawn call . + /// To flag the entire view for redraw call . /// /// /// Views have a property that defines the default colors that subviews - /// should use for rendering. This ensures that the views fit in the context where - /// they are being used, and allows for themes to be plugged in. For example, the + /// should use for rendering. This ensures that the views fit in the context where + /// they are being used, and allows for themes to be plugged in. For example, the /// default colors for windows and toplevels uses a blue background, while it uses /// a white background for dialog boxes and a red background for errors. /// @@ -98,16 +88,16 @@ namespace Terminal.Gui { /// /// /// Views that are focusable should implement the to make sure that - /// the cursor is placed in a location that makes sense. Unix terminals do not have + /// the cursor is placed in a location that makes sense. Unix terminals do not have /// a way of hiding the cursor, so it can be distracting to have the cursor left at - /// the last focused view. So views should make sure that they place the cursor + /// the last focused view. So views should make sure that they place the cursor /// in a visually sensible place. /// /// /// The method is invoked when the size or layout of a view has - /// changed. The default processing system will keep the size and dimensions - /// for views that use the , and will recompute the - /// frames for the vies that use . + /// changed. The default processing system will keep the size and dimensions + /// for views that use the , and will recompute the + /// frames for the vies that use . /// /// public partial class View : Responder, ISupportInitializeNotification { @@ -333,7 +323,8 @@ namespace Terminal.Gui { bool tabStop = true; /// - /// This only be true if the is also true and the focus can be avoided by setting this to false + /// This only be if the is also + /// and the focus can be avoided by setting this to /// public bool TabStop { get => tabStop; @@ -431,7 +422,7 @@ namespace Terminal.Gui { /// /// Gets or sets a value indicating whether this wants mouse position reports. /// - /// true if want mouse position reports; otherwise, false. + /// if want mouse position reports; otherwise, . public virtual bool WantMousePositionReports { get; set; } /// @@ -518,7 +509,7 @@ namespace Terminal.Gui { Pos x, y; /// - /// Gets or sets the X position for the view (the column). Only used the is . + /// Gets or sets the X position for the view (the column). Only used if the is . /// /// The X Position. /// @@ -538,7 +529,7 @@ namespace Terminal.Gui { } /// - /// Gets or sets the Y position for the view (the row). Only used the is . + /// Gets or sets the Y position for the view (the row). Only used if the is . /// /// The y position (line). /// @@ -633,7 +624,7 @@ namespace Terminal.Gui { /// Verifies if the minimum width or height can be sets in the view. /// /// The size. - /// if the size can be set, otherwise. + /// if the size can be set, otherwise. public bool GetMinWidthHeight (out Size size) { size = Size.Empty; @@ -662,7 +653,7 @@ namespace Terminal.Gui { /// /// Sets the minimum width or height if the view can be resized. /// - /// if the size can be set, otherwise. + /// if the size can be set, otherwise. public bool SetMinWidthHeight () { if (GetMinWidthHeight (out Size size)) { @@ -686,7 +677,7 @@ namespace Terminal.Gui { /// /// Initializes a new instance of a class with the absolute - /// dimensions specified in the frame parameter. + /// dimensions specified in the parameter. /// /// The region covered by this view. /// @@ -704,12 +695,12 @@ namespace Terminal.Gui { /// /// /// Use , , , and properties to dynamically control the size and location of the view. - /// The will be created using - /// coordinates. The initial size ( will be + /// The will be created using + /// coordinates. The initial size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// - /// If Height is greater than one, word wrapping is provided. + /// If is greater than one, word wrapping is provided. /// /// /// This constructor initialize a View with a of . @@ -724,15 +715,15 @@ namespace Terminal.Gui { /// /// /// The will be created at the given - /// coordinates with the given string. The size ( will be + /// coordinates with the given string. The size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// /// No line wrapping is provided. /// /// - /// column to locate the Label. - /// row to locate the Label. + /// column to locate the View. + /// row to locate the View. /// text to initialize the property with. public View (int x, int y, ustring text) : this (TextFormatter.CalcRect (x, y, text), text) { } @@ -742,7 +733,7 @@ namespace Terminal.Gui { /// /// /// The will be created at the given - /// coordinates with the given string. The initial size ( will be + /// coordinates with the given string. The initial size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// @@ -763,11 +754,11 @@ namespace Terminal.Gui { /// /// /// The will be created using - /// coordinates with the given string. The initial size ( will be + /// coordinates with the given string. The initial size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// - /// If Height is greater than one, word wrapping is provided. + /// If is greater than one, word wrapping is provided. /// /// /// text to initialize the property with. @@ -921,7 +912,8 @@ namespace Terminal.Gui { /// Adds a subview (child) to this view. /// /// - /// The Views that have been added to this view can be retrieved via the property. See also + /// The Views that have been added to this view can be retrieved via the property. + /// See also /// public virtual void Add (View view) { @@ -961,7 +953,8 @@ namespace Terminal.Gui { /// /// Array of one or more views (can be optional parameter). /// - /// The Views that have been added to this view can be retrieved via the property. See also + /// The Views that have been added to this view can be retrieved via the property. + /// See also /// public void Add (params View [] views) { @@ -1133,7 +1126,7 @@ namespace Terminal.Gui { /// View-relative row. /// Absolute column; screen-relative. /// Absolute row; screen-relative. - /// Whether to clip the result of the ViewToScreen method, if set to true, the rcol, rrow values are clamped to the screen (terminal) dimensions (0..TerminalDim-1). + /// Whether to clip the result of the ViewToScreen method, if set to , the rcol, rrow values are clamped to the screen (terminal) dimensions (0..TerminalDim-1). internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true) { // Computes the real row, col relative to the screen. @@ -1219,7 +1212,7 @@ namespace Terminal.Gui { /// /// View-relative region for the frame to be drawn. /// The padding to add around the outside of the drawn frame. - /// If set to true it fill will the contents. + /// If set to it fill will the contents. public void DrawFrame (Rect region, int padding = 0, bool fill = false) { var scrRect = ViewToScreen (region); @@ -1256,7 +1249,7 @@ namespace Terminal.Gui { /// Utility function to draw strings that contains a hotkey using a and the "focused" state. /// /// String to display, the underscore before a letter flags the next letter as the hotkey. - /// If set to true this uses the focused colors from the color scheme, otherwise the regular ones. + /// If set to this uses the focused colors from the color scheme, otherwise the regular ones. /// The color scheme to use. public void DrawHotString (ustring text, bool focused, ColorScheme scheme) { @@ -1273,7 +1266,7 @@ namespace Terminal.Gui { /// Col. /// Row. /// Whether to clip the result of the ViewToScreen method, - /// if set to true, the col, row values are clamped to the screen (terminal) dimensions (0..TerminalDim-1). + /// if set to , the col, row values are clamped to the screen (terminal) dimensions (0..TerminalDim-1). public void Move (int col, int row, bool clipped = true) { if (Driver.Rows == 0) { @@ -1355,7 +1348,7 @@ namespace Terminal.Gui { } /// - /// Method invoked when a subview is being added to this view. + /// Method invoked when a subview is being added to this view. /// /// The subview being added. public virtual void OnAdded (View view) @@ -1414,7 +1407,7 @@ namespace Terminal.Gui { /// /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). /// - /// The most focused. + /// The most focused View. public View MostFocused { get { if (Focused == null) @@ -1485,7 +1478,7 @@ namespace Terminal.Gui { /// /// /// Overrides of must ensure they do not set Driver.Clip to a clip region - /// larger than the region parameter. + /// larger than the parameter, as this will cause the driver to clip the entire region. /// /// public virtual void Redraw (Rect bounds) @@ -1748,11 +1741,11 @@ namespace Terminal.Gui { /// If the key is already bound to a different it will be /// rebound to this one /// Commands are only ever applied to the current (i.e. this feature - /// cannot be used to switch focus to another view and perform multiple commands there) + /// cannot be used to switch focus to another view and perform multiple commands there) /// /// /// The command(s) to run on the when is pressed. - /// When specifying multiple, all commands will be applied in sequence. The bound strike + /// When specifying multiple commands, all commands will be applied in sequence. The bound strike /// will be consumed if any took effect. public void AddKeyBinding (Key key, params Command [] command) { @@ -1782,18 +1775,17 @@ namespace Terminal.Gui { } /// - /// Checks if key combination already exist. + /// Checks if the key binding already exists. /// /// The key to check. - /// true If the key already exist, falseotherwise. + /// If the key already exist, otherwise. public bool ContainsKeyBinding (Key key) { return KeyBindings.ContainsKey (key); } /// - /// Removes all bound keys from the View making including the default - /// key combinations such as cursor navigation, scrolling etc + /// Removes all bound keys from the View and resets the default bindings. /// public void ClearKeybindings () { @@ -1801,7 +1793,7 @@ namespace Terminal.Gui { } /// - /// Clears the existing keybinding (if any) for the given + /// Clears the existing keybinding (if any) for the given . /// /// public void ClearKeybinding (Key key) @@ -1810,7 +1802,7 @@ namespace Terminal.Gui { } /// - /// Removes all key bindings that trigger the given command. Views can have multiple different + /// Removes all key bindings that trigger the given command. Views can have multiple different /// keys bound to the same command and this method will clear all of them. /// /// @@ -1843,7 +1835,7 @@ namespace Terminal.Gui { } /// - /// Returns all commands that are supported by this + /// Returns all commands that are supported by this . /// /// public IEnumerable GetSupportedCommands () @@ -1913,7 +1905,7 @@ namespace Terminal.Gui { } /// - /// Invoked when a key is pressed + /// Invoked when a key is pressed. /// public event Action KeyDown; @@ -1943,7 +1935,7 @@ namespace Terminal.Gui { } /// - /// Invoked when a key is released + /// Invoked when a key is released. /// public event Action KeyUp; @@ -1973,7 +1965,7 @@ namespace Terminal.Gui { } /// - /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing. + /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing. /// public void EnsureFocus () { @@ -2036,7 +2028,7 @@ namespace Terminal.Gui { /// /// Focuses the previous view. /// - /// true, if previous was focused, false otherwise. + /// if previous was focused, otherwise. public bool FocusPrev () { if (!CanBeVisible (this)) { @@ -2083,7 +2075,7 @@ namespace Terminal.Gui { /// /// Focuses the next view. /// - /// true, if next was focused, false otherwise. + /// if next was focused, otherwise. public bool FocusNext () { if (!CanBeVisible (this)) { @@ -2447,10 +2439,10 @@ namespace Terminal.Gui { /// /// Gets or sets a flag that determines whether the View will be automatically resized to fit the . - /// The default is `false`. Set to `true` to turn on AutoSize. If is `true` the + /// The default is . Set to to turn on AutoSize. If is the /// and will always be used if the text size is lower. If the text size is higher the bounds will /// be resized to fit it. - /// In addition, if is `true` the new values of and + /// In addition, if is the new values of and /// must be of the same types of the existing one to avoid breaking the settings. /// public virtual bool AutoSize { @@ -2469,9 +2461,10 @@ namespace Terminal.Gui { /// /// Gets or sets a flag that determines whether will have trailing spaces preserved - /// or not when is enabled. If `true` any trailing spaces will be trimmed when - /// either the property is changed or when is set to `true`. - /// The default is `false`. + /// or not when is enabled. If + /// any trailing spaces will be trimmed when either the property is changed or + /// when is set to . + /// The default is . /// public virtual bool PreserveTrailingSpaces { get => TextFormatter.PreserveTrailingSpaces; @@ -2713,7 +2706,7 @@ namespace Terminal.Gui { /// /// Get the width or height of the length. /// - /// trueif is the width (default)falseif is the height. + /// if is the width (default) if is the height. /// The length of the . public int GetHotKeySpecifierLength (bool isWidth = true) { @@ -2752,7 +2745,7 @@ namespace Terminal.Gui { } /// - /// Specifies the event arguments for . This is a higher-level construct + /// Specifies the event arguments for . This is a higher-level construct /// than the wrapped class and is used for the events defined on /// and subclasses of View (e.g. and ). /// @@ -2817,7 +2810,7 @@ namespace Terminal.Gui { /// Method invoked when a mouse event is generated /// /// - /// true, if the event was handled, false otherwise. + /// , if the event was handled, otherwise. public virtual bool OnMouseEvent (MouseEvent mouseEvent) { if (!Enabled) { @@ -2987,7 +2980,7 @@ namespace Terminal.Gui { /// /// The desired width. /// The real result width. - /// true if the width can be directly assigned, false otherwise. + /// if the width can be directly assigned, otherwise. public bool SetWidth (int desiredWidth, out int resultWidth) { return CanSetWidth (desiredWidth, out resultWidth); @@ -2998,7 +2991,7 @@ namespace Terminal.Gui { /// /// The desired height. /// The real result height. - /// true if the height can be directly assigned, false otherwise. + /// if the height can be directly assigned, otherwise. public bool SetHeight (int desiredHeight, out int resultHeight) { return CanSetHeight (desiredHeight, out resultHeight); @@ -3008,7 +3001,7 @@ namespace Terminal.Gui { /// Gets the current width based on the settings. /// /// The real current width. - /// true if the width can be directly assigned, false otherwise. + /// if the width can be directly assigned, otherwise. public bool GetCurrentWidth (out int currentWidth) { SetRelativeLayout (SuperView?.frame ?? frame); @@ -3021,7 +3014,7 @@ namespace Terminal.Gui { /// Calculate the height based on the settings. /// /// The real current height. - /// true if the height can be directly assigned, false otherwise. + /// if the height can be directly assigned, otherwise. public bool GetCurrentHeight (out int currentHeight) { SetRelativeLayout (SuperView?.frame ?? frame); @@ -3060,7 +3053,7 @@ namespace Terminal.Gui { /// /// The view. /// The method name. - /// if it's overridden, otherwise. + /// if it's overridden, otherwise. public bool IsOverridden (View view, string method) { Type t = view.GetType (); From 54934cdc78bf64b958c21a625d4a8dc6800c4514 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 20 Oct 2022 18:43:04 +0100 Subject: [PATCH 41/58] Added some documentation into the testenvironments.json file. --- testenvironments.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testenvironments.json b/testenvironments.json index 70dbd0b4c..898ac827d 100644 --- a/testenvironments.json +++ b/testenvironments.json @@ -1,4 +1,8 @@ { + // Remote Testing (experimental preview). + // Here is some documentation https://learn.microsoft.com/en-us/visualstudio/test/remote-testing?view=vs-2022. + // Here a screen shot of the VS2022 where are the Test Explorer https://user-images.githubusercontent.com/13117724/196798350-5a6f94d3-b6cd-424e-b4e8-a9b507dc057a.png. + // Ignore "Could not find 'mono' host" error because unit tests don't use the .NET Framework. "version": "1", "environments": [ { From eb7fe9f126778ee7169db36b6176bae223926f58 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Thu, 20 Oct 2022 15:38:50 -0600 Subject: [PATCH 42/58] further progress --- Terminal.Gui/Views/Menu.cs | 1 + UnitTests/MenuTests.cs | 264 +++++++++++++++++-------------------- 2 files changed, 124 insertions(+), 141 deletions(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index abab9c711..bc4eb3876 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1068,6 +1068,7 @@ namespace Terminal.Gui { for (int i = 0; i < Menus.Length; i++) { if (i == selected) { pos++; + // BUGBUG: This if is not needed if (IsMenuOpen) Move (pos + 1, 0); else { diff --git a/UnitTests/MenuTests.cs b/UnitTests/MenuTests.cs index cedb32aec..9b65cd4d5 100644 --- a/UnitTests/MenuTests.cs +++ b/UnitTests/MenuTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Xunit; using Xunit.Abstractions; +using static Terminal.Gui.Views.MenuTests; namespace Terminal.Gui.Views { public class MenuTests { @@ -1100,21 +1101,67 @@ Edit Assert.True (copyAction); } + // Defines the expected strings for a Menu + // + // File Edit + // New Copy + public class ExpectedMenu : MenuBar { + FakeDriver d = ((FakeDriver)Application.Driver); + + public string expectedMenuBarText => " " + Menus [0].Title.ToString () + " " + " " + Menus [1].Title.ToString () + " "; + + // The expected strings when the menu is closed + public string expectedClosed => " " + Menus [0].Title.ToString () + " " + " " + Menus [1].Title.ToString () + "" + "\n"; + + // left padding for the sub menu + public string padding (int i) => i > 0 ? new string (' ', Menus [i].Children [0].TitleLength + 4) : ""; + + // Define expected menu frame + // "┌──────┐" + // "│ New │" + // "└──────┘" + // BUGBUG: The extra 4 spaces on these should not be there + public string expectedTopRow (int i) => $"{d.ULCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.URCorner} \n"; + public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children[0].Title} {d.VLine}" + " \n"; + public string expectedBottomRow (int i) => $"{d.LLCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.LRCorner} \n"; + + // The fulll expected string for an open sub menu + public string expectedSubMenuOpen (int i) => expectedClosed + + padding (i) + expectedTopRow(i) + + padding (i) + expectedMenuItemRow (i) + + padding (i) + expectedBottomRow (i); + + public ExpectedMenu (MenuBarItem [] menus) : base (menus) + { + } + } + [Fact, AutoInitShutdown] public void HotKey_MenuBar_ProcessHotKey_Menu_ProcessKey () { var newAction = false; var copyAction = false; - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_New", "", () => newAction = true) + // Define the expected menu + var twoMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { + new MenuBarItem ("File", new MenuItem [] { + new MenuItem ("New", "", null) }), - new MenuBarItem ("_Edit", new MenuItem [] { - new MenuItem ("_Copy", "", () => copyAction = true) + new MenuBarItem ("Edit", new MenuItem [] { + new MenuItem ("Copy", "", null) }) }); + // The real menu + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_" + twoMenuItemMenu.Menus[0].Title, new MenuItem [] { + new MenuItem ("_" + twoMenuItemMenu.Menus[0].Children[0].Title, "", () => newAction = true) + }), + new MenuBarItem ("_" + twoMenuItemMenu.Menus[1].Title, new MenuItem [] { + new MenuItem ("_" + twoMenuItemMenu.Menus[1].Children[0].Title, "", () => copyAction = true) + }), + }); + Application.Top.Add (menu); Assert.False (newAction); @@ -1123,15 +1170,7 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.F, new KeyModifiers () { Alt = true }))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - var expected = @" - File Edit -┌──────┐ -│ New │ -└──────┘ -"; - - var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (1, 0, 11, 4), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedSubMenuOpen(0), output); Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.N, null))); Application.MainLoop.MainIteration (); @@ -1140,15 +1179,7 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.E, new KeyModifiers () { Alt = true }))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit - ┌───────┐ - │ Copy │ - └───────┘ -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 16, 4), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedSubMenuOpen (1), output); Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.C, null))); Application.MainLoop.MainIteration (); @@ -1158,46 +1189,23 @@ Edit [Fact, AutoInitShutdown] public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKeys () { - var firstMenuBarText = "File"; - var firstMenuBarItemText = "New"; - var secondMenuBarText = "Edit"; - var secondMenuBarItemText = "Copy"; - - // Define expected MenuBar - // " File New " - var expectedMenuBarText = " " + firstMenuBarText + " " + " " + secondMenuBarText + " "; - - // Define expected menu frame - // "┌──────┐" - // "│ New │" - // "└──────┘" - var d = ((FakeDriver)Application.Driver); - // BUGBUG: The extra 4 spaces on these should not be there - var expectedFirstTopRow = $"{d.ULCorner}{new String (d.HLine.ToString () [0], firstMenuBarItemText.Length + 3)}{d.URCorner} "; - var expectedFirstBottomRow = $"{d.LLCorner}{new String (d.HLine.ToString () [0], firstMenuBarItemText.Length + 3)}{d.LRCorner} "; - - var expectedSecondTopRow = $"{d.ULCorner}{new String (d.HLine.ToString () [0], secondMenuBarItemText.Length + 3)}{d.URCorner}"; - var expectedSecondBottomRow = $"{d.LLCorner}{new String (d.HLine.ToString () [0], secondMenuBarItemText.Length + 3)}{d.LRCorner}"; - - var expectedClosed = " " + firstMenuBarText + " " + " " + secondMenuBarText + "" + "\n"; - - var expectedFirstMenuOpen = " " + firstMenuBarText + " " + " " + secondMenuBarText + "" + "\n" + - expectedFirstTopRow + "\n" + - $"{d.VLine} {firstMenuBarItemText} {d.VLine}" + " \n" + - expectedFirstBottomRow + "\n"; - - var expectedSecondMenuOpen = " " + firstMenuBarText + " " + " " + secondMenuBarText + " " + "\n" + - new String (' ', firstMenuBarItemText.Length + 4) + expectedSecondTopRow + "\n" + - new String (' ', firstMenuBarItemText.Length + 4) + $"{d.VLine} {secondMenuBarItemText} {d.VLine}" + "\n" + - new String (' ', firstMenuBarItemText.Length + 4) + expectedSecondBottomRow + "\n"; - + // Define the expected menu + var twoMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { + new MenuBarItem ("File", new MenuItem [] { + new MenuItem ("12", "", null) + }), + new MenuBarItem ("Edit", new MenuItem [] { + new MenuItem ("Copy", "", null) + }) + }); + // Test without HotKeys first var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem (firstMenuBarText, new MenuItem [] { - new MenuItem (firstMenuBarItemText, "", null) + new MenuBarItem (twoMenuItemMenu.Menus[0].Title, new MenuItem [] { + new MenuItem (twoMenuItemMenu.Menus[0].Children[0].Title, "", null) }), - new MenuBarItem (secondMenuBarText, new MenuItem [] { - new MenuItem (secondMenuBarItemText, "", null) + new MenuBarItem (twoMenuItemMenu.Menus[1].Title, new MenuItem [] { + new MenuItem (twoMenuItemMenu.Menus[1].Children[0].Title, "", null) }) }); @@ -1207,32 +1215,30 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedFirstMenuOpen, output); - Assert.Equal (1, pos.X); - Assert.Equal (0, pos.Y); - + GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (0), output); + // Open second Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedSecondMenuOpen, output); - + GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (1), output); + // Close menu Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedClosed, output); Application.Top.Remove (menu); // Now test WITH HotKeys menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_" + firstMenuBarText, new MenuItem [] { - new MenuItem ("_" + firstMenuBarItemText, "", null) + new MenuBarItem ("_" + twoMenuItemMenu.Menus[0].Title, new MenuItem [] { + new MenuItem ("_" + twoMenuItemMenu.Menus[0].Children[0].Title, "", null) + }), + new MenuBarItem ("_" + twoMenuItemMenu.Menus[1].Title, new MenuItem [] { + new MenuItem ("_" + twoMenuItemMenu.Menus[1].Children[0].Title, "", null) }), - new MenuBarItem ("_" + secondMenuBarText, new MenuItem [] { - new MenuItem ("_" + secondMenuBarItemText, "", null) - }) }); Application.Top.Add (menu); @@ -1241,59 +1247,56 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedFirstMenuOpen, output); - Assert.Equal (1, pos.X); - Assert.Equal (0, pos.Y); + GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (0), output); // Open second Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedSecondMenuOpen, output); + GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (1), output); // Close menu Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedClosed, output); } [Fact, AutoInitShutdown] public void MenuBar_ButtonPressed_Open_The_Menu_ButtonPressed_Again_Close_The_Menu () { - var menu = new MenuBar (new MenuBarItem [] { + // Define the expected menu + var twoMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { - new MenuItem ("New", "", null) + new MenuItem ("Open", "", null) }), new MenuBarItem ("Edit", new MenuItem [] { new MenuItem ("Copy", "", null) }) }); + // Test without HotKeys first + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_" + twoMenuItemMenu.Menus[0].Title, new MenuItem [] { + new MenuItem ("_" + twoMenuItemMenu.Menus[0].Children[0].Title, "", null) + }), + new MenuBarItem ("_" + twoMenuItemMenu.Menus[1].Title, new MenuItem [] { + new MenuItem ("_" + twoMenuItemMenu.Menus[1].Children[0].Title, "", null) + }), + }); + Application.Top.Add (menu); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - var expected = @" - File Edit -┌──────┐ -│ New │ -└──────┘ -"; - var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (1, 0, 11, 4), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedSubMenuOpen (0), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (1, 0, 11, 1), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedClosed, output); } [Fact] @@ -1315,16 +1318,33 @@ Edit [Fact, AutoInitShutdown] public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse () { - var menu = new MenuBar (new MenuBarItem [] { + // File Edit Format + //┌──────┐ ┌───────┐ + //│ New │ │ Wrap │ + //└──────┘ └───────┘ + + // Define the expected menu + var threeMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { - new MenuItem ("New", "", null) - }), - new MenuBarItem ("Edit", new MenuItem [] { + new MenuItem ("New", "", null) }), + new MenuBarItem ("Edit", new MenuItem [] {}), new MenuBarItem ("Format", new MenuItem [] { new MenuItem ("Wrap", "", null) }) }); + + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem (threeMenuItemMenu.Menus[0].Title, new MenuItem [] { + new MenuItem (threeMenuItemMenu.Menus[0].Children[0].Title, "", null) + }), + new MenuBarItem (threeMenuItemMenu.Menus[1].Title, new MenuItem [] {}), + new MenuBarItem (threeMenuItemMenu.Menus[2].Title, new MenuItem [] { + new MenuItem (threeMenuItemMenu.Menus[2].Children[0].Title, "", null) + }) + }); + + var tf = new TextField () { Y = 2, Width = 10 }; Application.Top.Add (menu, tf); @@ -1334,76 +1354,38 @@ Edit Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - var expected = @" - File Edit Format -┌──────┐ -│ New │ -└──────┘ -"; - - var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 4), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (0), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 1), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (1), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 15, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format - ┌───────┐ - │ Wrap │ - └───────┘ -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 23, 4), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (2), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 1), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedClosed, output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -┌──────┐ -│ New │ -└──────┘ -"; + GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (0), output); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 4), pos); Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 1), pos); + GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedClosed, output); } [Fact, AutoInitShutdown] From 40df03d3e33a7600d8c5b3b36e418bf36f37abed Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Thu, 20 Oct 2022 17:56:08 -0600 Subject: [PATCH 43/58] Finished refactoring tests --- Terminal.Gui/Core/ContextMenu.cs | 2 +- Terminal.Gui/Views/Menu.cs | 2 +- UICatalog/Scenarios/Notepad.cs | 6 +- UnitTests/MenuTests.cs | 336 +++++++++++++++++-------------- 4 files changed, 194 insertions(+), 152 deletions(-) diff --git a/Terminal.Gui/Core/ContextMenu.cs b/Terminal.Gui/Core/ContextMenu.cs index 726fc52c6..c51b2eea2 100644 --- a/Terminal.Gui/Core/ContextMenu.cs +++ b/Terminal.Gui/Core/ContextMenu.cs @@ -116,7 +116,7 @@ namespace Terminal.Gui { Y = position.Y, Width = 0, Height = 0, - UseSubMenusSingleFrame = UseSubMenusSingleFrame + UseSubMenusSingleFrame = UseSubMenusSingleFrame // BUGBUG: This line of code does nothing }; menuBar.isContextMenuLoading = true; diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index bc4eb3876..979ad0b66 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1224,7 +1224,7 @@ namespace Terminal.Gui { } for (int i = 0; i < index; i++) - pos += 1 + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2; + pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2; openMenu = new Menu (this, Frame.X + pos, Frame.Y + 1, Menus [index]); openCurrentMenu = openMenu; openCurrentMenu.previousSubFocused = openMenu; diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index 03f230b13..7fcd5c087 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -32,7 +32,11 @@ namespace UICatalog.Scenarios { new MenuItem ("Save _As", "", () => SaveAs()), new MenuItem ("_Close", "", () => Close()), new MenuItem ("_Quit", "", () => Quit()), - }) + }), + new MenuBarItem ("_Edit", new MenuItem [] {}), + new MenuBarItem ("_Help", new MenuItem [] { + new MenuItem ("_About", "", null) + }) }); Top.Add (menu); diff --git a/UnitTests/MenuTests.cs b/UnitTests/MenuTests.cs index 9b65cd4d5..5f14632d1 100644 --- a/UnitTests/MenuTests.cs +++ b/UnitTests/MenuTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Xunit; using Xunit.Abstractions; using static Terminal.Gui.Views.MenuTests; @@ -706,16 +707,15 @@ Edit Application.Top.Redraw (Application.Top.Bounds); var expected = @" - Numbers + Numbers "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -724,12 +724,11 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│┌─────────────┐ @@ -739,12 +738,11 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 25, 7), pos); Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.CursorLeft, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -753,16 +751,14 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); } [Fact, AutoInitShutdown] @@ -786,11 +782,11 @@ Edit Application.Top.Redraw (Application.Top.Bounds); var expected = @" - Numbers + Numbers "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); + Assert.Equal (new Rect (1, 0, 8, 1), pos); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, @@ -800,7 +796,7 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -809,7 +805,7 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); + Assert.Equal (new Rect (1, 0, 10, 6), pos); Assert.False (menu.MouseEvent (new MouseEvent () { X = 1, @@ -819,7 +815,7 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│┌─────────────┐ @@ -829,7 +825,7 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 25, 7), pos); + Assert.Equal (new Rect (1, 0, 25, 7), pos); Assert.False (menu.MouseEvent (new MouseEvent () { X = 1, @@ -839,7 +835,7 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -848,7 +844,7 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); + Assert.Equal (new Rect (1, 0, 10, 6), pos); Assert.False (menu.MouseEvent (new MouseEvent () { X = 70, @@ -858,11 +854,11 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); + Assert.Equal (new Rect (1, 0, 8, 1), pos); } [Fact, AutoInitShutdown] @@ -888,16 +884,16 @@ Edit Application.Top.Redraw (Application.Top.Bounds); var expected = @" - Numbers + Numbers "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); + Assert.Equal (new Rect (1, 0, 8, 1), pos); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -906,13 +902,13 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); + Assert.Equal (new Rect (1, 0, 10, 6), pos); Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null))); Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌─────────────┐ │◄ Two │ ├─────────────┤ @@ -922,12 +918,12 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 15, 7), pos); + Assert.Equal (new Rect (1, 0, 15, 7), pos); Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.Enter, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -936,16 +932,16 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); + Assert.Equal (new Rect (1, 0, 10, 6), pos); Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null))); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); + Assert.Equal (new Rect (1, 0, 8, 1), pos); } [Fact, AutoInitShutdown] @@ -971,11 +967,11 @@ Edit Application.Top.Redraw (Application.Top.Bounds); var expected = @" - Numbers + Numbers "; var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); + Assert.Equal (new Rect (1, 0, 8, 1), pos); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, @@ -985,7 +981,7 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -994,7 +990,7 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); + Assert.Equal (new Rect (1, 0, 10, 6), pos); Assert.False (menu.MouseEvent (new MouseEvent () { X = 1, @@ -1004,7 +1000,7 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌─────────────┐ │◄ Two │ ├─────────────┤ @@ -1014,7 +1010,7 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 15, 7), pos); + Assert.Equal (new Rect (1, 0, 15, 7), pos); Assert.False (menu.MouseEvent (new MouseEvent () { X = 1, @@ -1024,7 +1020,7 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers ┌────────┐ │ One │ │ Two ►│ @@ -1033,7 +1029,7 @@ Edit "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 10, 6), pos); + Assert.Equal (new Rect (1, 0, 10, 6), pos); Assert.False (menu.MouseEvent (new MouseEvent () { X = 70, @@ -1043,11 +1039,11 @@ Edit })); Application.Top.Redraw (Application.Top.Bounds); expected = @" - Numbers + Numbers "; pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 9, 1), pos); + Assert.Equal (new Rect (1, 0, 8, 1), pos); } [Fact, AutoInitShutdown] @@ -1108,34 +1104,103 @@ Edit public class ExpectedMenu : MenuBar { FakeDriver d = ((FakeDriver)Application.Driver); - public string expectedMenuBarText => " " + Menus [0].Title.ToString () + " " + " " + Menus [1].Title.ToString () + " "; + public string expectedMenuBarText { + get { + string txt = string.Empty; + foreach (var m in Menus) { + + txt += " " + m.Title.ToString () + " "; + } + return txt; + } + } // The expected strings when the menu is closed - public string expectedClosed => " " + Menus [0].Title.ToString () + " " + " " + Menus [1].Title.ToString () + "" + "\n"; + public string expectedClosed => expectedMenuBarText + "\n"; // left padding for the sub menu - public string padding (int i) => i > 0 ? new string (' ', Menus [i].Children [0].TitleLength + 4) : ""; + public string padding (int i) + { + int n = 0; + while (i > 0){ + n += Menus [i-1].TitleLength + 2; + i--; + } + return new string (' ', n); + } // Define expected menu frame // "┌──────┐" // "│ New │" // "└──────┘" // BUGBUG: The extra 4 spaces on these should not be there - public string expectedTopRow (int i) => $"{d.ULCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.URCorner} \n"; - public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children[0].Title} {d.VLine}" + " \n"; - public string expectedBottomRow (int i) => $"{d.LLCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.LRCorner} \n"; + public string expectedTopRow (int i) => $"{d.ULCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.URCorner} \n"; + public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children [0].Title} {d.VLine}" + " \n"; + public string expectedBottomRow (int i) => $"{d.LLCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.LRCorner} \n"; // The fulll expected string for an open sub menu - public string expectedSubMenuOpen (int i) => expectedClosed + - padding (i) + expectedTopRow(i) + + public string expectedSubMenuOpen (int i) => expectedClosed + + (Menus [i].Children.Length > 0 ? + padding (i) + expectedTopRow (i) + padding (i) + expectedMenuItemRow (i) + - padding (i) + expectedBottomRow (i); - + padding (i) + expectedBottomRow (i) + : + ""); + public ExpectedMenu (MenuBarItem [] menus) : base (menus) { } } + [Fact, AutoInitShutdown] + public void MenuBar_Submenus_Alignment_Correct () + { + // Define the expected menu + var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + new MenuBarItem ("File", new MenuItem [] { + new MenuItem ("Really Long Sub Menu", "", null) + }), + new MenuBarItem ("123", new MenuItem [] { + new MenuItem ("Copy", "", null) + }), + new MenuBarItem ("Format", new MenuItem [] { + new MenuItem ("Word Wrap", "", null) + }), + new MenuBarItem ("Help", new MenuItem [] { + new MenuItem ("About", "", null) + }), + new MenuBarItem ("1", new MenuItem [] { + new MenuItem ("2", "", null) + }), + new MenuBarItem ("3", new MenuItem [] { + new MenuItem ("2", "", null) + }), + new MenuBarItem ("Last one", new MenuItem [] { + new MenuItem ("Test", "", null) + }) + }); + + MenuBarItem [] items = new MenuBarItem [expectedMenu.Menus.Length]; + for (var i = 0; i < expectedMenu.Menus.Length; i++) { + items [i] = new MenuBarItem (expectedMenu.Menus [i].Title, new MenuItem [] { + new MenuItem (expectedMenu.Menus [i].Children [0].Title, "", null) + }); + } + var menu = new MenuBar (items); + + Application.Top.Add (menu); + + Application.Top.Redraw (Application.Top.Bounds); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + + for (var i = 0; i < expectedMenu.Menus.Length; i++) { + menu.OpenMenu (i); + Assert.True (menu.IsMenuOpen); + Application.Top.Redraw (Application.Top.Bounds); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (i), output); + } + } + [Fact, AutoInitShutdown] public void HotKey_MenuBar_ProcessHotKey_Menu_ProcessKey () { @@ -1143,7 +1208,7 @@ Edit var copyAction = false; // Define the expected menu - var twoMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenu (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1154,11 +1219,11 @@ Edit // The real menu var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_" + twoMenuItemMenu.Menus[0].Title, new MenuItem [] { - new MenuItem ("_" + twoMenuItemMenu.Menus[0].Children[0].Title, "", () => newAction = true) + new MenuBarItem ("_" + expectedMenu.Menus[0].Title, new MenuItem [] { + new MenuItem ("_" + expectedMenu.Menus[0].Children[0].Title, "", () => newAction = true) }), - new MenuBarItem ("_" + twoMenuItemMenu.Menus[1].Title, new MenuItem [] { - new MenuItem ("_" + twoMenuItemMenu.Menus[1].Children[0].Title, "", () => copyAction = true) + new MenuBarItem ("_" + expectedMenu.Menus[1].Title, new MenuItem [] { + new MenuItem ("_" + expectedMenu.Menus[1].Children[0].Title, "", () => copyAction = true) }), }); @@ -1170,7 +1235,7 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.F, new KeyModifiers () { Alt = true }))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedSubMenuOpen(0), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.N, null))); Application.MainLoop.MainIteration (); @@ -1179,7 +1244,7 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.E, new KeyModifiers () { Alt = true }))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedSubMenuOpen (1), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output); Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.C, null))); Application.MainLoop.MainIteration (); @@ -1190,7 +1255,7 @@ Edit public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKeys () { // Define the expected menu - var twoMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenu (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("12", "", null) }), @@ -1198,14 +1263,14 @@ Edit new MenuItem ("Copy", "", null) }) }); - + // Test without HotKeys first var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem (twoMenuItemMenu.Menus[0].Title, new MenuItem [] { - new MenuItem (twoMenuItemMenu.Menus[0].Children[0].Title, "", null) + new MenuBarItem (expectedMenu.Menus[0].Title, new MenuItem [] { + new MenuItem (expectedMenu.Menus[0].Children[0].Title, "", null) }), - new MenuBarItem (twoMenuItemMenu.Menus[1].Title, new MenuItem [] { - new MenuItem (twoMenuItemMenu.Menus[1].Children[0].Title, "", null) + new MenuBarItem (expectedMenu.Menus[1].Title, new MenuItem [] { + new MenuItem (expectedMenu.Menus[1].Children[0].Title, "", null) }) }); @@ -1215,29 +1280,29 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (0), output); - + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); + // Open second Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (1), output); - + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output); + // Close menu Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); Application.Top.Remove (menu); // Now test WITH HotKeys menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_" + twoMenuItemMenu.Menus[0].Title, new MenuItem [] { - new MenuItem ("_" + twoMenuItemMenu.Menus[0].Children[0].Title, "", null) + new MenuBarItem ("_" + expectedMenu.Menus[0].Title, new MenuItem [] { + new MenuItem ("_" + expectedMenu.Menus[0].Children[0].Title, "", null) }), - new MenuBarItem ("_" + twoMenuItemMenu.Menus[1].Title, new MenuItem [] { - new MenuItem ("_" + twoMenuItemMenu.Menus[1].Children[0].Title, "", null) + new MenuBarItem ("_" + expectedMenu.Menus[1].Title, new MenuItem [] { + new MenuItem ("_" + expectedMenu.Menus[1].Children[0].Title, "", null) }), }); @@ -1247,26 +1312,26 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (0), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); // Open second Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null))); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedSubMenuOpen (1), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output); // Close menu Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (twoMenuItemMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); } [Fact, AutoInitShutdown] public void MenuBar_ButtonPressed_Open_The_Menu_ButtonPressed_Again_Close_The_Menu () { // Define the expected menu - var twoMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenu (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("Open", "", null) }), @@ -1277,26 +1342,26 @@ Edit // Test without HotKeys first var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_" + twoMenuItemMenu.Menus[0].Title, new MenuItem [] { - new MenuItem ("_" + twoMenuItemMenu.Menus[0].Children[0].Title, "", null) + new MenuBarItem ("_" + expectedMenu.Menus[0].Title, new MenuItem [] { + new MenuItem ("_" + expectedMenu.Menus[0].Children[0].Title, "", null) }), - new MenuBarItem ("_" + twoMenuItemMenu.Menus[1].Title, new MenuItem [] { - new MenuItem ("_" + twoMenuItemMenu.Menus[1].Children[0].Title, "", null) + new MenuBarItem ("_" + expectedMenu.Menus[1].Title, new MenuItem [] { + new MenuItem ("_" + expectedMenu.Menus[1].Children[0].Title, "", null) }), }); - + Application.Top.Add (menu); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.True (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedSubMenuOpen (0), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (twoMenuItemMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); } [Fact] @@ -1324,7 +1389,7 @@ Edit //└──────┘ └───────┘ // Define the expected menu - var threeMenuItemMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenu (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1335,72 +1400,81 @@ Edit }); var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem (threeMenuItemMenu.Menus[0].Title, new MenuItem [] { - new MenuItem (threeMenuItemMenu.Menus[0].Children[0].Title, "", null) + new MenuBarItem (expectedMenu.Menus[0].Title, new MenuItem [] { + new MenuItem (expectedMenu.Menus[0].Children[0].Title, "", null) }), - new MenuBarItem (threeMenuItemMenu.Menus[1].Title, new MenuItem [] {}), - new MenuBarItem (threeMenuItemMenu.Menus[2].Title, new MenuItem [] { - new MenuItem (threeMenuItemMenu.Menus[2].Children[0].Title, "", null) + new MenuBarItem (expectedMenu.Menus[1].Title, new MenuItem [] {}), + new MenuBarItem (expectedMenu.Menus[2].Title, new MenuItem [] { + new MenuItem (expectedMenu.Menus[2].Children[0].Title, "", null) }) }); - var tf = new TextField () { Y = 2, Width = 10 }; Application.Top.Add (menu, tf); - Application.Begin (Application.Top); + Assert.True (tf.HasFocus); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (0), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (1), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 15, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (2), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (2), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedSubMenuOpen (0), output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsWithFrameAre (threeMenuItemMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); } [Fact, AutoInitShutdown] public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Keyboard () { - var menu = new MenuBar (new MenuBarItem [] { + var expectedMenu = new ExpectedMenu (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), - new MenuBarItem ("Edit", new MenuItem [] { - }), + new MenuBarItem ("Edit", Array.Empty ()), new MenuBarItem ("Format", new MenuItem [] { new MenuItem ("Wrap", "", null) }) }); + + MenuBarItem [] items = new MenuBarItem [expectedMenu.Menus.Length]; + for (var i = 0; i < expectedMenu.Menus.Length; i++) { + items [i] = new MenuBarItem (expectedMenu.Menus [i].Title, expectedMenu.Menus [i].Children.Length > 0 + ? new MenuItem [] { + new MenuItem (expectedMenu.Menus [i].Children [0].Title, "", null), + } + : Array.Empty ()); + } + var menu = new MenuBar (items); + var tf = new TextField () { Y = 2, Width = 10 }; Application.Top.Add (menu, tf); @@ -1410,76 +1484,40 @@ Edit Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - var expected = @" - File Edit Format -┌──────┐ -│ New │ -└──────┘ -"; - - var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 4), pos); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen(0), output); + // Right - Edit has no sub menu; this tests that no sub menu shows Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 1), pos); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output); + // Right - Format Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format - ┌───────┐ - │ Wrap │ - └───────┘ -"; + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (2), output); - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 23, 4), pos); + // Left - Edit + Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()))); + Assert.True (menu.IsMenuOpen); + Assert.False (tf.HasFocus); + Application.Top.Redraw (Application.Top.Bounds); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output); Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()))); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 1), pos); - - Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()))); - Assert.True (menu.IsMenuOpen); - Assert.False (tf.HasFocus); - Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -┌──────┐ -│ New │ -└──────┘ -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 4), pos); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output); Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - expected = @" - File Edit Format -"; - - pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rect (2, 0, 22, 1), pos); + GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); } } } From 5aebe14a6470826bda06b34ce6905107954226d4 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Thu, 20 Oct 2022 17:57:35 -0600 Subject: [PATCH 44/58] removed test code from Notepad --- UICatalog/Scenarios/Notepad.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index 7fcd5c087..03f230b13 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -32,11 +32,7 @@ namespace UICatalog.Scenarios { new MenuItem ("Save _As", "", () => SaveAs()), new MenuItem ("_Close", "", () => Close()), new MenuItem ("_Quit", "", () => Quit()), - }), - new MenuBarItem ("_Edit", new MenuItem [] {}), - new MenuBarItem ("_Help", new MenuItem [] { - new MenuItem ("_About", "", null) - }) + }) }); Top.Add (menu); From ce7567e73382d074d9c5340880afaebbb61d81c9 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Thu, 20 Oct 2022 20:42:52 -0600 Subject: [PATCH 45/58] Fixes HotColor for non-activated menus --- Terminal.Gui/Views/Menu.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 804ddb5ed..8855a707b 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1041,14 +1041,10 @@ namespace Terminal.Gui { Attribute hotColor, normalColor; if (i == selected && IsMenuOpen) { hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal; - normalColor = i == selected ? ColorScheme.Focus : - GetNormalColor (); - } else if (openedByAltKey) { + normalColor = i == selected ? ColorScheme.Focus : GetNormalColor (); + } else { hotColor = ColorScheme.HotNormal; normalColor = GetNormalColor (); - } else { - hotColor = GetNormalColor (); - normalColor = GetNormalColor (); } DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} {menu.Help} ", hotColor, normalColor); pos += 1 + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + 2 : 0) + 2; From 8ccfdd9311ac0e76671d3f943158d4f0a9b79515 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 21 Oct 2022 09:08:46 +0100 Subject: [PATCH 46/58] Add menu toggle for DesiredCursorVisibility to TreeView UICatalog scenario TreeViewFileSystem --- UICatalog/Scenarios/TreeViewFileSystem.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index 65ea36566..3414c2fe5 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -25,6 +25,7 @@ namespace UICatalog.Scenarios { private MenuItem miFullPaths; private MenuItem miLeaveLastRow; private MenuItem miCustomColors; + private MenuItem miCursor; private Terminal.Gui.Attribute green; private Terminal.Gui.Attribute red; @@ -54,6 +55,7 @@ namespace UICatalog.Scenarios { miFullPaths = new MenuItem ("_FullPaths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked}, miLeaveLastRow = new MenuItem ("_LeaveLastRow", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked}, miCustomColors = new MenuItem ("C_ustomColors", "", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked}, + miCursor = new MenuItem ("Curs_or", "", () => SetCursor()){Checked = true, CheckType = MenuItemCheckStyle.Checked}, }), }); Top.Add (menu); @@ -269,6 +271,12 @@ namespace UICatalog.Scenarios { miLeaveLastRow.Checked = !miLeaveLastRow.Checked; treeViewFiles.Style.LeaveLastRow = miLeaveLastRow.Checked; } + private void SetCursor() + { + miCursor.Checked = !miCursor.Checked; + treeViewFiles.DesiredCursorVisibility = miCursor.Checked ? CursorVisibility.Default : CursorVisibility.Invisible; + } + private void SetCustomColors() { var yellow = new ColorScheme From d93a247a4df602938d1735950fc9227348c8249c Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 21 Oct 2022 08:12:26 -0600 Subject: [PATCH 47/58] documenting behavior --- Terminal.Gui/Views/Menu.cs | 21 +++++++++++-- UICatalog/UICatalog.cs | 2 +- UnitTests/MenuTests.cs | 60 +++++++++++++++++++++++--------------- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 979ad0b66..8685119bc 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -141,6 +141,12 @@ namespace Terminal.Gui { return CanExecute == null ? true : CanExecute (); } + // ┌──────────────────────────────┐ + // │ Quit Quit UI Catalog Ctrl+Q │ + // └──────────────────────────────┘ + // ┌─────────────────┐ + // │ ◌ TopLevel Alt+T │ + // └─────────────────┘ internal int Width => 1 + TitleLength + (Help.ConsoleWidth > 0 ? Help.ConsoleWidth + 2 : 0) + (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + (ShortcutTag.ConsoleWidth > 0 ? ShortcutTag.ConsoleWidth + 2 : 0) + 2; @@ -459,6 +465,7 @@ namespace Terminal.Gui { return GetNormalColor (); } + // Draws the Menu, within the Frame public override void Redraw (Rect bounds) { Driver.SetAttribute (GetNormalColor ()); @@ -484,6 +491,7 @@ namespace Terminal.Gui { Driver.AddRune (Driver.HLine); else if (i == 0 && p == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null) Driver.AddRune (Driver.LeftArrow); + // TODO: Change this `- 3` to a const (is it spacesAfterTitle?) else if (p == Frame.Width - 3 && barItems.SubMenu (barItems.Children [i]) != null) Driver.AddRune (Driver.RightArrow); else @@ -516,6 +524,7 @@ namespace Terminal.Gui { textToDraw = item.Title; } + // Draw the item. The `2` is for the left border and the space before the text ViewToScreen (2, i + 1, out int vtsCol, out _, false); if (vtsCol < Driver.Cols) { Move (2, i + 1); @@ -527,6 +536,7 @@ namespace Terminal.Gui { HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw }; + // TODO: Change this `- 3` to a const (is it spacesAfterTitle?) tf.Draw (ViewToScreen (new Rect (2, i + 1, Frame.Width - 3, 1)), i == current ? ColorScheme.Focus : GetNormalColor (), i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal, @@ -1025,7 +1035,8 @@ namespace Terminal.Gui { } static int leftPadding = 1; - static int rightPadding = 1; + static int rightPadding = 1; + static int spaceAfterTitle = 3; /// public override void Redraw (Rect bounds) { @@ -1053,7 +1064,7 @@ namespace Terminal.Gui { normalColor = GetNormalColor (); } DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ", hotColor, normalColor); - pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + 3 : 0) + rightPadding; + pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + spaceAfterTitle : 0) + rightPadding; } PositionCursor (); } @@ -1076,7 +1087,7 @@ namespace Terminal.Gui { } return; } else { - pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 3 : 0) + rightPadding; + pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + spaceAfterTitle : 0)+ rightPadding; } } } @@ -1211,6 +1222,7 @@ namespace Terminal.Gui { int pos = 0; switch (subMenu) { case null: + // Open a submenu below a MenuBar lastFocused = lastFocused ?? (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused); if (openSubMenu != null && !CloseMenu (false, true)) return; @@ -1223,6 +1235,8 @@ namespace Terminal.Gui { openMenu.Dispose (); } + // This positions the submenu horizontally aligned with the first character of the + // menu it belongs to's text for (int i = 0; i < index; i++) pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2; openMenu = new Menu (this, Frame.X + pos, Frame.Y + 1, Menus [index]); @@ -1237,6 +1251,7 @@ namespace Terminal.Gui { openMenu.SetFocus (); break; default: + // Opens a submenu next to another submenu (openSubMenu) if (openSubMenu == null) openSubMenu = new List (); if (sIndex > -1) { diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 895228942..8cd4f3dce 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -169,7 +169,7 @@ namespace UICatalog { _menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_Quit", "", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask) + new MenuItem ("_Quit", "Quit UI Catalog", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask) }), new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()), new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()), diff --git a/UnitTests/MenuTests.cs b/UnitTests/MenuTests.cs index 5f14632d1..549551fc6 100644 --- a/UnitTests/MenuTests.cs +++ b/UnitTests/MenuTests.cs @@ -1097,14 +1097,24 @@ Edit Assert.True (copyAction); } - // Defines the expected strings for a Menu + // Defines the expected strings for a Menu. Currently supports + // - MenuBar with any number of MenuItems + // - Each top-level MenuItem can have a SINGLE sub-menu + // + // TODO: Enable multiple sub-menus + // TODO: Enable checked sub-menus + // TODO: Enable sub-menus with sub-menus (perhaps better to put this in a separate class with focused unit tests?) + // + // E.g: // // File Edit // New Copy - public class ExpectedMenu : MenuBar { + public class ExpectedMenuBar : MenuBar { FakeDriver d = ((FakeDriver)Application.Driver); - public string expectedMenuBarText { + // Each MenuBar title has a 1 space pad on each side + // See `static int leftPadding` and `static int rightPadding` on line 1037 of Menu.cs + public string MenuBarText { get { string txt = string.Empty; foreach (var m in Menus) { @@ -1116,10 +1126,11 @@ Edit } // The expected strings when the menu is closed - public string expectedClosed => expectedMenuBarText + "\n"; + public string ClosedMenuText => MenuBarText + "\n"; - // left padding for the sub menu - public string padding (int i) + // Padding for the X of the sub menu Frane + // Menu.cs - Line 1239 in `internal void OpenMenu` is where the Menu is created + string padding (int i) { int n = 0; while (i > 0){ @@ -1133,13 +1144,16 @@ Edit // "┌──────┐" // "│ New │" // "└──────┘" - // BUGBUG: The extra 4 spaces on these should not be there + // + // The width of the Frame is determined in Menu.cs line 144, where `Width` is calculated + // 1 space before the Title and 2 spaces after the Title/Check/Help public string expectedTopRow (int i) => $"{d.ULCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.URCorner} \n"; - public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children [0].Title} {d.VLine}" + " \n"; + // The 3 spaces at end are a result of Menu.cs line 1062 where `pos` is calculated (` + spacesAfterTitle`) + public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children [0].Title} {d.VLine} \n"; public string expectedBottomRow (int i) => $"{d.LLCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.LRCorner} \n"; // The fulll expected string for an open sub menu - public string expectedSubMenuOpen (int i) => expectedClosed + + public string expectedSubMenuOpen (int i) => ClosedMenuText + (Menus [i].Children.Length > 0 ? padding (i) + expectedTopRow (i) + padding (i) + expectedMenuItemRow (i) + @@ -1147,7 +1161,7 @@ Edit : ""); - public ExpectedMenu (MenuBarItem [] menus) : base (menus) + public ExpectedMenuBar (MenuBarItem [] menus) : base (menus) { } } @@ -1156,7 +1170,7 @@ Edit public void MenuBar_Submenus_Alignment_Correct () { // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("Really Long Sub Menu", "", null) }), @@ -1191,7 +1205,7 @@ Edit Application.Top.Add (menu); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); for (var i = 0; i < expectedMenu.Menus.Length; i++) { menu.OpenMenu (i); @@ -1208,7 +1222,7 @@ Edit var copyAction = false; // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1255,7 +1269,7 @@ Edit public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKeys () { // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("12", "", null) }), @@ -1292,7 +1306,7 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); Application.Top.Remove (menu); @@ -1324,14 +1338,14 @@ Edit Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ()))); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } [Fact, AutoInitShutdown] public void MenuBar_ButtonPressed_Open_The_Menu_ButtonPressed_Again_Close_The_Menu () { // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("Open", "", null) }), @@ -1361,7 +1375,7 @@ Edit Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } [Fact] @@ -1389,7 +1403,7 @@ Edit //└──────┘ └───────┘ // Define the expected menu - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1436,7 +1450,7 @@ Edit Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu })); Assert.True (menu.IsMenuOpen); @@ -1449,13 +1463,13 @@ Edit Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } [Fact, AutoInitShutdown] public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Keyboard () { - var expectedMenu = new ExpectedMenu (new MenuBarItem [] { + var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] { new MenuBarItem ("File", new MenuItem [] { new MenuItem ("New", "", null) }), @@ -1517,7 +1531,7 @@ Edit Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); Application.Top.Redraw (Application.Top.Bounds); - GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedClosed, output); + GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); } } } From f3e704207739ab2c2cff6c692986c42cb66dbf05 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 21 Oct 2022 08:47:09 -0600 Subject: [PATCH 48/58] added constants to MenuBar --- Terminal.Gui/Views/Menu.cs | 37 ++++++++++++++++++++++--------------- UICatalog/UICatalog.cs | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 8685119bc..326cc1036 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -141,13 +141,14 @@ namespace Terminal.Gui { return CanExecute == null ? true : CanExecute (); } - // ┌──────────────────────────────┐ - // │ Quit Quit UI Catalog Ctrl+Q │ - // └──────────────────────────────┘ + // ┌─────────────────────────────┐ + // │ Quit Quit UI Catalog Ctrl+Q │ + // └─────────────────────────────┘ // ┌─────────────────┐ // │ ◌ TopLevel Alt+T │ // └─────────────────┘ - internal int Width => 1 + TitleLength + (Help.ConsoleWidth > 0 ? Help.ConsoleWidth + 2 : 0) + + // TODO: Repalace the `2` literals with named constants (e.g. spacesAfterHelp and spacesAfterCheck and spacesAfterShortCutTag) + internal int Width => + TitleLength + (Help.ConsoleWidth > 0 ? Help.ConsoleWidth + 2 : 0) + (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + (ShortcutTag.ConsoleWidth > 0 ? ShortcutTag.ConsoleWidth + 2 : 0) + 2; @@ -226,7 +227,7 @@ namespace Terminal.Gui { /// Initializes a new as a . /// /// Title for the menu item. - /// Help text to display. + /// Help text to display. Will be displayed next to the Title surrounded by parentheses. /// Action to invoke when the menu item is activated. /// Function to determine if the action can currently be executed. /// The parent of this if exist, otherwise is null. @@ -397,8 +398,8 @@ namespace Terminal.Gui { } int minX = x; int minY = y; - int maxW = (items.Max (z => z?.Width) ?? 0) + 2; - int maxH = items.Length + 2; + int maxW = (items.Max (z => z?.Width) ?? 0) + 2; // This 2 is frame border? + int maxH = items.Length + 2; // This 2 is frame border? if (parent != null && x + maxW > Driver.Cols) { minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0); } @@ -484,7 +485,7 @@ namespace Terminal.Gui { Move (1, i + 1); Driver.SetAttribute (DetermineColorSchemeFor (item, i)); - for (int p = Bounds.X; p < Frame.Width - 2; p++) { + for (int p = Bounds.X; p < Frame.Width - 2; p++) { // This - 2 is for the border? if (p < 0) continue; if (item == null) @@ -1034,9 +1035,14 @@ namespace Terminal.Gui { isCleaning = false; } + // The column where the MenuBar starts + static int xOrigin = 0; + // Spaces before the Title static int leftPadding = 1; + // Spaces after the Title static int rightPadding = 1; - static int spaceAfterTitle = 3; + // Spaces after the submenu Title, before Help + static int parensAroundHelp = 3; /// public override void Redraw (Rect bounds) { @@ -1063,8 +1069,9 @@ namespace Terminal.Gui { hotColor = GetNormalColor (); normalColor = GetNormalColor (); } + // Note Help on MenuBar is drawn with parens around it DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ", hotColor, normalColor); - pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + spaceAfterTitle : 0) + rightPadding; + pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? leftPadding + menu.Help.ConsoleWidth + parensAroundHelp : 0) + rightPadding; } PositionCursor (); } @@ -1087,7 +1094,7 @@ namespace Terminal.Gui { } return; } else { - pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + spaceAfterTitle : 0)+ rightPadding; + pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + parensAroundHelp : 0)+ rightPadding; } } } @@ -1238,7 +1245,7 @@ namespace Terminal.Gui { // This positions the submenu horizontally aligned with the first character of the // menu it belongs to's text for (int i = 0; i < index; i++) - pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2; + pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + leftPadding + rightPadding; openMenu = new Menu (this, Frame.X + pos, Frame.Y + 1, Menus [index]); openCurrentMenu = openMenu; openCurrentMenu.previousSubFocused = openMenu; @@ -1791,10 +1798,10 @@ namespace Terminal.Gui { if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.Button1Clicked || (me.Flags == MouseFlags.ReportMousePosition && selected > -1) || (me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && selected > -1)) { - int pos = 1; + int pos = xOrigin; int cx = me.X; for (int i = 0; i < Menus.Length; i++) { - if (cx >= pos && cx < pos + 1 + Menus [i].TitleLength + Menus [i].Help.ConsoleWidth + 2) { + if (cx >= pos && cx < pos + leftPadding + Menus [i].TitleLength + Menus [i].Help.ConsoleWidth + rightPadding) { if (me.Flags == MouseFlags.Button1Clicked) { if (Menus [i].IsTopLevel) { var menu = new Menu (this, i, 0, Menus [i]); @@ -1823,7 +1830,7 @@ namespace Terminal.Gui { } return true; } - pos += 1 + Menus [i].TitleLength + 2; + pos += leftPadding + Menus [i].TitleLength + rightPadding; } } return false; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 8cd4f3dce..5b94d3e55 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -178,7 +178,7 @@ namespace UICatalog { new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2), new MenuItem ("_About...", "About UI Catalog", () => MessageBox.Query ("About UI Catalog", aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A), - }) + }), }); _leftPane = new FrameView ("Categories") { From a69ceb2a29d3b07c9482ddb97faa44a1fc4da8fc Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 21 Oct 2022 09:31:41 -0700 Subject: [PATCH 49/58] testing improve this doc... --- docfx/overrides/Terminal_Gui_Application.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 docfx/overrides/Terminal_Gui_Application.md diff --git a/docfx/overrides/Terminal_Gui_Application.md b/docfx/overrides/Terminal_Gui_Application.md new file mode 100644 index 000000000..f94551697 --- /dev/null +++ b/docfx/overrides/Terminal_Gui_Application.md @@ -0,0 +1,8 @@ +--- +uid: Terminal.Gui.Application +summary: '*You can override summary for the API here using *MARKDOWN* syntax' +--- + +*Please type below more information about this API:* + +![Sample](images/sample.png) From 4ef36560d71ea9a9ae1114d474c59a86a6ede73b Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 21 Oct 2022 10:52:41 -0600 Subject: [PATCH 50/58] made code clearer --- Terminal.Gui/Views/Menu.cs | 123 +++++++++++++++++-------------------- UICatalog/UICatalog.cs | 5 +- 2 files changed, 60 insertions(+), 68 deletions(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 326cc1036..7a7d6e3d3 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1,13 +1,3 @@ -// -// Menu.cs: application menus and submenus -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// -// TODO: -// Add accelerator support, but should also support chords (Shortcut in MenuItem) -// Allow menus inside menus - using System; using NStack; using System.Linq; @@ -26,18 +16,19 @@ namespace Terminal.Gui { NoCheck = 0b_0000_0000, /// - /// The menu item will indicate checked/un-checked state (see . + /// The menu item will indicate checked/un-checked state (see ). /// Checked = 0b_0000_0001, /// - /// The menu item is part of a menu radio group (see and will indicate selected state. + /// The menu item is part of a menu radio group (see ) and will indicate selected state. /// Radio = 0b_0000_0010, }; /// - /// A has a title, an associated help text, and an action to execute on activation. + /// A has title, an associated help text, and an action to execute on activation. + /// MenuItems can also have a checked indicator (see ). /// public class MenuItem { ustring title; @@ -78,14 +69,28 @@ namespace Terminal.Gui { } /// - /// The HotKey is used when the menu is active, the shortcut can be triggered when the menu is not active. - /// For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry - /// if the Shortcut is set to "Control-N", this would be a global hotkey that would trigger as well + /// The HotKey is used to activate a with they keyboard. HotKeys are defined by prefixing the + /// of a MenuItem with an underscore ('_'). + /// + /// Pressing Alt-Hotkey for a (menu items on the menu bar) works even if the menu is not active). + /// Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem. + /// + /// + /// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the File menu. + /// Pressing the N key will then activate the New MenuItem. + /// + /// + /// See also which enable global key-bindings to menu items. + /// /// public Rune HotKey; /// - /// This is the global setting that can be used as a global to invoke the action on the menu. + /// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the that is + /// the parent of the or this . + /// + /// The will be drawn on the MenuItem to the right of the and text. See . + /// /// public Key Shortcut { get => shortcutHelper.Shortcut; @@ -97,12 +102,12 @@ namespace Terminal.Gui { } /// - /// The keystroke combination used in the as string. + /// Gets the text describing the keystroke combination defined by . /// public ustring ShortcutTag => ShortcutHelper.GetShortcutTag (shortcutHelper.Shortcut); /// - /// Gets or sets the title. + /// Gets or sets the title of the menu item . /// /// The title. public ustring Title { @@ -116,41 +121,46 @@ namespace Terminal.Gui { } /// - /// Gets or sets the help text for the menu item. + /// Gets or sets the help text for the menu item. The help text is drawn to the right of the . /// /// The help text. public ustring Help { get; set; } /// - /// Gets or sets the action to be invoked when the menu is triggered + /// Gets or sets the action to be invoked when the menu item is triggered. /// /// Method to invoke. public Action Action { get; set; } /// - /// Gets or sets the action to be invoked if the menu can be triggered + /// Gets or sets the action to be invoked to determine if the menu can be triggered. If returns + /// the menu item will be enabled. Otherwise it will be disabled. /// - /// Function to determine if action is ready to be executed. + /// Function to determine if the action is can be executed or not. public Func CanExecute { get; set; } /// - /// Shortcut to check if the menu item is enabled + /// Returns if the menu item is enabled. This method is a wrapper around . /// public bool IsEnabled () { return CanExecute == null ? true : CanExecute (); } + // // ┌─────────────────────────────┐ // │ Quit Quit UI Catalog Ctrl+Q │ // └─────────────────────────────┘ // ┌─────────────────┐ // │ ◌ TopLevel Alt+T │ // └─────────────────┘ - // TODO: Repalace the `2` literals with named constants (e.g. spacesAfterHelp and spacesAfterCheck and spacesAfterShortCutTag) - internal int Width => + TitleLength + (Help.ConsoleWidth > 0 ? Help.ConsoleWidth + 2 : 0) + - (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + - (ShortcutTag.ConsoleWidth > 0 ? ShortcutTag.ConsoleWidth + 2 : 0) + 2; + // TODO: Repalace the `2` literals with named constants + internal int Width => 1 + // space before Title + TitleLength + + 2 + // space after Title - BUGBUG: This should be 1 + (Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + // check glyph + space + (Help.ConsoleWidth > 0 ? 2 + Help.ConsoleWidth : 0) + // Two spaces before Help + (ShortcutTag.ConsoleWidth > 0 ? 2 + ShortcutTag.ConsoleWidth : 0); // Pad two spaces before shortcut tag (which are also aligned right) /// /// Sets or gets whether the shows a check indicator or not. See . @@ -158,12 +168,12 @@ namespace Terminal.Gui { public bool Checked { set; get; } /// - /// Sets or gets the type selection indicator the menu item will be displayed with. + /// Sets or gets the of a menu item where is set to . /// public MenuItemCheckStyle CheckType { get; set; } /// - /// Gets or sets the parent for this . + /// Gets the parent for this . /// /// The parent. public MenuItem Parent { get; internal set; } @@ -174,7 +184,7 @@ namespace Terminal.Gui { internal bool IsFromSubMenu { get { return Parent != null; } } /// - /// Merely a debugging aid to see the interaction with main + /// Merely a debugging aid to see the interaction with main. /// public MenuItem GetMenuItem () { @@ -182,7 +192,7 @@ namespace Terminal.Gui { } /// - /// Merely a debugging aid to see the interaction with main + /// Merely a debugging aid to see the interaction with main. /// public bool GetMenuBarItem () { @@ -220,7 +230,8 @@ namespace Terminal.Gui { } /// - /// A contains s or s. + /// is a menu item on an app's . MenuBarItems do not support + /// . /// public class MenuBarItem : MenuItem { /// @@ -296,19 +307,6 @@ namespace Terminal.Gui { } } - //static int GetMaxTitleLength (MenuItem [] children) - //{ - // int maxLength = 0; - // foreach (var item in children) { - // int len = GetMenuBarItemLength (item.Title); - // if (len > maxLength) - // maxLength = len; - // item.IsFromSubMenu = true; - // } - - // return maxLength; - //} - void SetChildrensParent (MenuItem [] childrens) { foreach (var child in childrens) { @@ -370,12 +368,6 @@ namespace Terminal.Gui { Title = title; } - ///// - ///// Gets or sets the title to display. - ///// - ///// The title. - //public ustring Title { get; set; } - /// /// Gets or sets an array of objects that are the children of this /// @@ -485,14 +477,14 @@ namespace Terminal.Gui { Move (1, i + 1); Driver.SetAttribute (DetermineColorSchemeFor (item, i)); - for (int p = Bounds.X; p < Frame.Width - 2; p++) { // This - 2 is for the border? + for (int p = Bounds.X; p < Frame.Width - 2; p++) { // This - 2 is for the border if (p < 0) continue; if (item == null) Driver.AddRune (Driver.HLine); else if (i == 0 && p == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null) Driver.AddRune (Driver.LeftArrow); - // TODO: Change this `- 3` to a const (is it spacesAfterTitle?) + // This `- 3` is left border + right border + one row in from right else if (p == Frame.Width - 3 && barItems.SubMenu (barItems.Children [i]) != null) Driver.AddRune (Driver.RightArrow); else @@ -525,7 +517,6 @@ namespace Terminal.Gui { textToDraw = item.Title; } - // Draw the item. The `2` is for the left border and the space before the text ViewToScreen (2, i + 1, out int vtsCol, out _, false); if (vtsCol < Driver.Cols) { Move (2, i + 1); @@ -537,7 +528,7 @@ namespace Terminal.Gui { HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw }; - // TODO: Change this `- 3` to a const (is it spacesAfterTitle?) + // The -3 is left/right border + one space (not sure what for) tf.Draw (ViewToScreen (new Rect (2, i + 1, Frame.Width - 3, 1)), i == current ? ColorScheme.Focus : GetNormalColor (), i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal, @@ -846,14 +837,19 @@ namespace Terminal.Gui { /// + /// /// Provides a menu bar with drop-down and cascading menus. + /// + /// + /// + /// /// /// /// - /// The appears on the first row of the terminal. + /// The appears on the first row of the terminal and uses the full width. /// /// - /// The provides global hotkeys for the application. + /// The provides global hotkeys for the application. See . /// /// public class MenuBar : View { @@ -1035,7 +1031,7 @@ namespace Terminal.Gui { isCleaning = false; } - // The column where the MenuBar starts + // The column where the MenuBar starts static int xOrigin = 0; // Spaces before the Title static int leftPadding = 1; @@ -1086,15 +1082,10 @@ namespace Terminal.Gui { for (int i = 0; i < Menus.Length; i++) { if (i == selected) { pos++; - // BUGBUG: This if is not needed - if (IsMenuOpen) - Move (pos + 1, 0); - else { - Move (pos + 1, 0); - } + Move (pos + 1, 0); return; } else { - pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + parensAroundHelp : 0)+ rightPadding; + pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + parensAroundHelp : 0) + rightPadding; } } } diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 5b94d3e55..9949c337f 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -318,7 +318,7 @@ namespace UICatalog { { List menuItems = new List (); var item = new MenuItem (); - item.Title = "_Disable/Enable Mouse"; + item.Title = "_Disable Mouse"; item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0]; item.CheckType |= MenuItemCheckStyle.Checked; item.Checked = Application.IsMouseDisabled; @@ -334,7 +334,8 @@ namespace UICatalog { List menuItems = new List (); var item = new MenuItem (); - item.Title = "Keybindings"; + item.Title = "_Key Bindings"; + item.Help = "Change which keys do what"; item.Action += () => { var dlg = new KeyBindingsDialog (); Application.Run (dlg); From 138d0dacd126421626edb9e5ea21bc3414b88794 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 21 Oct 2022 11:03:28 -0600 Subject: [PATCH 51/58] made code clearer --- Terminal.Gui/Core/ContextMenu.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core/ContextMenu.cs b/Terminal.Gui/Core/ContextMenu.cs index c51b2eea2..79c1a9cb2 100644 --- a/Terminal.Gui/Core/ContextMenu.cs +++ b/Terminal.Gui/Core/ContextMenu.cs @@ -110,13 +110,13 @@ namespace Terminal.Gui { } else if (ForceMinimumPosToZero && position.Y < 0) { position.Y = 0; } - + menuBar = new MenuBar (new [] { MenuItems }) { X = position.X, Y = position.Y, Width = 0, Height = 0, - UseSubMenusSingleFrame = UseSubMenusSingleFrame // BUGBUG: This line of code does nothing + UseSubMenusSingleFrame = this.UseSubMenusSingleFrame }; menuBar.isContextMenuLoading = true; From 802820edfa0aee2631f41ce93dbaf45f1b688367 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 21 Oct 2022 21:42:55 +0100 Subject: [PATCH 52/58] Fixes #2121. ContextMenu: When open, Shift-F10 should close menu. --- Terminal.Gui/Core/ContextMenu.cs | 3 ++- Terminal.Gui/Views/Menu.cs | 9 +++++++-- UnitTests/ContextMenuTests.cs | 14 ++++++++++++++ UnitTests/MenuTests.cs | 26 ++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Core/ContextMenu.cs b/Terminal.Gui/Core/ContextMenu.cs index 726fc52c6..916f23b52 100644 --- a/Terminal.Gui/Core/ContextMenu.cs +++ b/Terminal.Gui/Core/ContextMenu.cs @@ -116,7 +116,8 @@ namespace Terminal.Gui { Y = position.Y, Width = 0, Height = 0, - UseSubMenusSingleFrame = UseSubMenusSingleFrame + UseSubMenusSingleFrame = UseSubMenusSingleFrame, + Key = Key }; menuBar.isContextMenuLoading = true; diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 804ddb5ed..42ad3b839 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -630,7 +630,7 @@ namespace Terminal.Gui { } } } - return false; + return host.ProcessHotKey (kb); } void RunSelected () @@ -905,6 +905,11 @@ namespace Terminal.Gui { } } + /// + /// The used to activate the menu bar by keyboard. + /// + public Key Key { get; set; } = Key.F9; + /// /// Initializes a new instance of the . /// @@ -1678,7 +1683,7 @@ namespace Terminal.Gui { /// public override bool ProcessHotKey (KeyEvent kb) { - if (kb.Key == Key.F9) { + if (kb.Key == Key) { if (!IsMenuOpen) OpenMenu (); else diff --git a/UnitTests/ContextMenuTests.cs b/UnitTests/ContextMenuTests.cs index a0c88168a..1cae21537 100644 --- a/UnitTests/ContextMenuTests.cs +++ b/UnitTests/ContextMenuTests.cs @@ -888,5 +888,19 @@ namespace Terminal.Gui.Core { │ SubMenu7 │────┘ ", output); } + + [Fact, AutoInitShutdown] + public void Key_Open_And_Close_The_ContextMenu () + { + var tf = new TextField (); + var top = Application.Top; + top.Add (tf); + Application.Begin (top); + + Assert.True (tf.ProcessKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ()))); + Assert.True (tf.ContextMenu.MenuBar.IsMenuOpen); + Assert.True (top.Subviews [1].ProcessKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ()))); + Assert.Null (tf.ContextMenu.MenuBar); + } } } diff --git a/UnitTests/MenuTests.cs b/UnitTests/MenuTests.cs index 77c336b8a..a8687c491 100644 --- a/UnitTests/MenuTests.cs +++ b/UnitTests/MenuTests.cs @@ -1484,5 +1484,31 @@ Edit pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output); Assert.Equal (new Rect (2, 0, 22, 1), pos); } + + [Fact, AutoInitShutdown] + public void Key_Open_And_Close_The_MenuBar () + { + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("File", new MenuItem [] { + new MenuItem ("New", "", null) + }) + }); + Application.Top.Add (menu); + Application.Begin (Application.Top); + + Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); + Assert.True (menu.IsMenuOpen); + Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); + Assert.False (menu.IsMenuOpen); + + menu.Key = Key.F10 | Key.ShiftMask; + Assert.False (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ()))); + Assert.False (menu.IsMenuOpen); + + Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ()))); + Assert.True (menu.IsMenuOpen); + Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ()))); + Assert.False (menu.IsMenuOpen); + } } } From 76dd8a70d461b99967ef8145eef4d128eb0ccbab Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 21 Oct 2022 16:03:30 -0600 Subject: [PATCH 53/58] more doc improvements --- Terminal.Gui/{Core => Views}/ContextMenu.cs | 57 ++++++--- Terminal.Gui/Views/Menu.cs | 131 +++++++------------- UICatalog/Scenarios/Editor.cs | 2 + 3 files changed, 82 insertions(+), 108 deletions(-) rename Terminal.Gui/{Core => Views}/ContextMenu.cs (62%) diff --git a/Terminal.Gui/Core/ContextMenu.cs b/Terminal.Gui/Views/ContextMenu.cs similarity index 62% rename from Terminal.Gui/Core/ContextMenu.cs rename to Terminal.Gui/Views/ContextMenu.cs index 79c1a9cb2..b5dcde5f6 100644 --- a/Terminal.Gui/Core/ContextMenu.cs +++ b/Terminal.Gui/Views/ContextMenu.cs @@ -2,8 +2,24 @@ namespace Terminal.Gui { /// - /// A context menu window derived from containing menu items - /// which can be opened in any position. + /// ContextMenu provides a pop-up menu that can be positioned anywhere within a . + /// ContextMenu is analogous to and, once activated, works like a sub-menu + /// of a (but can be positioned anywhere). + /// + /// By default, a ContextMenu with sub-menus is displayed in a cascading manner, where each sub-menu pops out of the ContextMenu frame + /// (either to the right or left, depending on where the ContextMenu is relative to the edge of the screen). By setting + /// to , this behavior can be changed such that all sub-menus are + /// drawn within the ContextMenu frame. + /// + /// + /// ContextMenus can be activated using the Shift-F10 key (by default; use the to change to another key). + /// + /// + /// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling . + /// + /// + /// ContextMenus are located using screen using screen coordinates and appear above all other Views. + /// /// public sealed class ContextMenu : IDisposable { private static MenuBar menuBar; @@ -12,15 +28,15 @@ namespace Terminal.Gui { private Toplevel container; /// - /// Initialize a context menu with empty menu items. + /// Initializes a context menu with no menu items. /// public ContextMenu () : this (0, 0, new MenuBarItem ()) { } /// - /// Initialize a context menu with menu items from a host . + /// Initializes a context menu, with a specifiying the parent/hose of the menu. /// /// The host view. - /// The menu items. + /// The menu items for the context menu. public ContextMenu (View host, MenuBarItem menuItems) : this (host.Frame.X, host.Frame.Y, menuItems) { @@ -28,10 +44,10 @@ namespace Terminal.Gui { } /// - /// Initialize a context menu with menu items. + /// Initializes a context menu with menu items at a specific screen location. /// - /// The left position. - /// The top position. + /// The left position (screen relative). + /// The top position (screen relative). /// The menu items. public ContextMenu (int x, int y, MenuBarItem menuItems) { @@ -48,7 +64,7 @@ namespace Terminal.Gui { } /// - /// Disposes the all the context menu objects instances. + /// Disposes the context menu object. /// public void Dispose () { @@ -65,7 +81,7 @@ namespace Terminal.Gui { } /// - /// Open the menu items. + /// Shows (opens) the ContextMenu, displaying the s it contains. /// public void Show () { @@ -116,7 +132,7 @@ namespace Terminal.Gui { Y = position.Y, Width = 0, Height = 0, - UseSubMenusSingleFrame = this.UseSubMenusSingleFrame + UseSubMenusSingleFrame = this.UseSubMenusSingleFrame }; menuBar.isContextMenuLoading = true; @@ -138,7 +154,7 @@ namespace Terminal.Gui { } /// - /// Close the menu items. + /// Hides (closes) the ContextMenu. /// public void Hide () { @@ -157,7 +173,7 @@ namespace Terminal.Gui { public event Action MouseFlagsChanged; /// - /// Gets or set the menu position. + /// Gets or sets the menu position. /// public Point Position { get; set; } @@ -167,7 +183,7 @@ namespace Terminal.Gui { public MenuBarItem MenuItems { get; set; } /// - /// The used to activate the context menu by keyboard. + /// specifies they keyboard key that will activate the context menu with the keyboard. /// public Key Key { get => key; @@ -179,7 +195,7 @@ namespace Terminal.Gui { } /// - /// The used to activate the context menu by mouse. + /// specifies the mouse action used to activate the context menu by mouse. /// public MouseFlags MouseFlags { get => mouseFlags; @@ -191,7 +207,7 @@ namespace Terminal.Gui { } /// - /// Gets information whether menu is showing or not. + /// Gets whether the ContextMenu is showing or not. /// public static bool IsShow { get; private set; } @@ -202,8 +218,9 @@ namespace Terminal.Gui { public View Host { get; set; } /// - /// Gets or sets whether forces the minimum position to zero - /// if the left or right position are negative. + /// Sets or gets whether the context menu be forced to the right, ensuring it is not clipped, if the x position + /// is less than zero. The default is which means the context menu will be forced to the right. + /// If set to , the context menu will be clipped on the left if x is less than zero. /// public bool ForceMinimumPosToZero { get; set; } = true; @@ -213,7 +230,9 @@ namespace Terminal.Gui { public MenuBar MenuBar { get => menuBar; } /// - /// Gets or sets if the sub-menus must be displayed in a single or multiple frames. + /// Gets or sets if sub-menus will be displayed using a "single frame" menu style. If , the ContextMenu + /// and any sub-menus that would normally cascade will be displayed within a single frame. If (the default), + /// sub-menus will cascade using separate frames for each level of the menu hierarchy. /// public bool UseSubMenusSingleFrame { get; set; } } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 7a7d6e3d3..ed037a10f 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -11,7 +11,7 @@ namespace Terminal.Gui { [Flags] public enum MenuItemCheckStyle { /// - /// The menu item will be shown normally, with no check indicator. + /// The menu item will be shown normally, with no check indicator. The default. /// NoCheck = 0b_0000_0000, @@ -69,7 +69,7 @@ namespace Terminal.Gui { } /// - /// The HotKey is used to activate a with they keyboard. HotKeys are defined by prefixing the + /// The HotKey is used to activate a with the keyboard. HotKeys are defined by prefixing the /// of a MenuItem with an underscore ('_'). /// /// Pressing Alt-Hotkey for a (menu items on the menu bar) works even if the menu is not active). @@ -134,7 +134,7 @@ namespace Terminal.Gui { /// /// Gets or sets the action to be invoked to determine if the menu can be triggered. If returns - /// the menu item will be enabled. Otherwise it will be disabled. + /// the menu item will be enabled. Otherwise, it will be disabled. /// /// Function to determine if the action is can be executed or not. public Func CanExecute { get; set; } @@ -154,7 +154,7 @@ namespace Terminal.Gui { // ┌─────────────────┐ // │ ◌ TopLevel Alt+T │ // └─────────────────┘ - // TODO: Repalace the `2` literals with named constants + // TODO: Replace the `2` literals with named constants internal int Width => 1 + // space before Title TitleLength + 2 + // space after Title - BUGBUG: This should be 1 @@ -230,8 +230,8 @@ namespace Terminal.Gui { } /// - /// is a menu item on an app's . MenuBarItems do not support - /// . + /// is a menu item on an app's . + /// MenuBarItems do not support . /// public class MenuBarItem : MenuItem { /// @@ -834,30 +834,35 @@ namespace Terminal.Gui { } } - - /// /// - /// Provides a menu bar with drop-down and cascading menus. - /// - /// - /// + /// Provides a menu bar that spans the top of a View with drop-down and cascading menus. /// + /// + /// By default, any sub-sub-menus (sub-menus of the s added to s) + /// are displayed in a cascading manner, where each sub-sub-menu pops out of the sub-menu frame + /// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting + /// to , this behavior can be changed such that all sub-sub-menus are + /// drawn within a single frame below the MenuBar. + /// /// /// /// - /// The appears on the first row of the terminal and uses the full width. + /// The appears on the first row of the parent View and uses the full width. /// /// /// The provides global hotkeys for the application. See . /// + /// + /// See also: + /// /// public class MenuBar : View { internal int selected; internal int selectedSub; /// - /// Gets or sets the array of s for the menu. Only set this when the is visible. + /// Gets or sets the array of s for the menu. Only set this after the is visible. /// /// The menu array. public MenuBarItem [] Menus { get; set; } @@ -880,7 +885,7 @@ namespace Terminal.Gui { static ustring shortcutDelimiter = "+"; /// - /// Used for change the shortcut delimiter separator. + /// Sets or gets the shortcut delimiter separator. The default is "+". /// public static ustring ShortcutDelimiter { get => shortcutDelimiter; @@ -900,6 +905,13 @@ namespace Terminal.Gui { /// /// Gets or sets if the sub-menus must be displayed in a single or multiple frames. + /// + /// By default any sub-sub-menus (sub-menus of the main s) are displayed in a cascading manner, + /// where each sub-sub-menu pops out of the sub-menu frame + /// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting + /// to , this behavior can be changed such that all sub-sub-menus are + /// drawn within a single frame below the MenuBar. + /// /// public bool UseSubMenusSingleFrame { get => useSubMenusSingleFrame; @@ -1123,7 +1135,7 @@ namespace Terminal.Gui { public event Action MenuClosing; /// - /// Raised when all the menu are closed. + /// Raised when all the menu is closed. /// public event Action MenuAllClosed; @@ -1146,7 +1158,7 @@ namespace Terminal.Gui { internal bool isMenuClosing; /// - /// True if the menu is open; otherwise false. + /// if the menu is open; otherwise . /// public bool IsMenuOpen { get; protected set; } @@ -1179,7 +1191,7 @@ namespace Terminal.Gui { } /// - /// Virtual method that will invoke the + /// Virtual method that will invoke the . /// /// The current menu to be closed. /// Whether the current menu will be reopen. @@ -1192,7 +1204,7 @@ namespace Terminal.Gui { } /// - /// Virtual method that will invoke the + /// Virtual method that will invoke the . /// public virtual void OnMenuAllClosed () { @@ -1202,7 +1214,7 @@ namespace Terminal.Gui { View lastFocused; /// - /// Get the lasted focused view before open the menu. + /// Gets the view that was last focused before opening the menu. /// public View LastFocused { get; private set; } @@ -1290,7 +1302,7 @@ namespace Terminal.Gui { } /// - /// Opens the current Menu programatically. + /// Opens the Menu programatically, as though the F9 key were pressed. /// public void OpenMenu () { @@ -1372,7 +1384,7 @@ namespace Terminal.Gui { } /// - /// Closes the current Menu programatically, if open and not canceled. + /// Closes the Menu programmatically if open and not canceled (as though F9 were pressed). /// public bool CloseMenu (bool ignoreUseSubMenusSingleFrame = false) { @@ -1474,26 +1486,6 @@ namespace Terminal.Gui { if (openSubMenu.Count > 0) openCurrentMenu = openSubMenu.Last (); - //if (openMenu.Subviews.Count == 0) - // return; - //if (index == 0) { - // //SuperView.SetFocus (previousSubFocused); - // FocusPrev (); - // return; - //} - - //for (int i = openMenu.Subviews.Count - 1; i > index; i--) { - // isMenuClosing = true; - // if (openMenu.Subviews.Count - 1 > 0) - // SuperView.SetFocus (openMenu.Subviews [i - 1]); - // else - // SuperView.SetFocus (openMenu); - // if (openMenu != null) { - // Remove (openMenu.Subviews [i]); - // openMenu.Remove (openMenu.Subviews [i]); - // } - // RemoveSubMenu (i); - //} isMenuClosing = false; } @@ -1894,47 +1886,6 @@ namespace Terminal.Gui { handled = false; return false; } - //if (me.View != this && me.Flags != MouseFlags.Button1Pressed) - // return true; - //else if (me.View != this && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) { - // Application.UngrabMouse (); - // host.CloseAllMenus (); - // return true; - //} - - - //if (!(me.View is MenuBar) && !(me.View is Menu) && me.Flags != MouseFlags.Button1Pressed)) - // return false; - - //if (Application.MouseGrabView != null) { - // if (me.View is MenuBar || me.View is Menu) { - // me.X -= me.OfX; - // me.Y -= me.OfY; - // me.View.MouseEvent (me); - // return true; - // } else if (!(me.View is MenuBar || me.View is Menu) && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) { - // Application.UngrabMouse (); - // CloseAllMenus (); - // } - //} else if (!isMenuClosed && selected == -1 && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) { - // Application.GrabMouse (this); - // return true; - //} - - //if (Application.MouseGrabView != null) { - // if (Application.MouseGrabView == me.View && me.View == current) { - // me.X -= me.OfX; - // me.Y -= me.OfY; - // } else if (me.View != current && me.View is MenuBar && me.View is Menu) { - // Application.UngrabMouse (); - // Application.GrabMouse (me.View); - // } else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) { - // Application.UngrabMouse (); - // CloseMenu (); - // } - //} else if ((!isMenuClosed && selected > -1)) { - // Application.GrabMouse (current); - //} handled = true; @@ -1988,12 +1939,13 @@ namespace Terminal.Gui { /// public MenuBarItem NewMenuBarItem { get; set; } /// - /// Flag that allows you to cancel the opening of the menu. + /// Flag that allows the cancellation of the event. If set to in the + /// event handler, the event will be canceled. /// public bool Cancel { get; set; } /// - /// Initializes a new instance of + /// Initializes a new instance of . /// /// The current parent. public MenuOpeningEventArgs (MenuBarItem currentMenu) @@ -2012,7 +1964,7 @@ namespace Terminal.Gui { public MenuBarItem CurrentMenu { get; } /// - /// Indicates whether the current menu will be reopen. + /// Indicates whether the current menu will reopen. /// public bool Reopen { get; } @@ -2022,15 +1974,16 @@ namespace Terminal.Gui { public bool IsSubMenu { get; } /// - /// Flag that allows you to cancel the opening of the menu. + /// Flag that allows the cancellation of the event. If set to in the + /// event handler, the event will be canceled. /// public bool Cancel { get; set; } /// - /// Initializes a new instance of + /// Initializes a new instance of . /// /// The current parent. - /// Whether the current menu will be reopen. + /// Whether the current menu will reopen. /// Indicates whether it is a sub-menu. public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu) { diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 633158ee5..5f679c74b 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -116,6 +116,8 @@ namespace UICatalog.Scenarios { new MenuBarItem ("_Languages", GetSupportedCultures ()) }) }); + menu.UseSubMenusSingleFrame = true; + Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { From e6c322b1d5522563bff9038f29c345c7875fc0db Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Fri, 21 Oct 2022 16:12:02 -0600 Subject: [PATCH 54/58] backed out testing change --- UICatalog/Scenarios/Editor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 5f679c74b..96f11363e 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -116,7 +116,6 @@ namespace UICatalog.Scenarios { new MenuBarItem ("_Languages", GetSupportedCultures ()) }) }); - menu.UseSubMenusSingleFrame = true; Top.Add (menu); From ff3a1326ba9a063d9b512cbc1ba29368d4e33082 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 22 Oct 2022 19:30:28 +0100 Subject: [PATCH 55/58] Change TreeView default cursor to invisible and force invisible only when not MultiSelect --- Terminal.Gui/Views/TreeView.cs | 18 +++++++++++------- UICatalog/Scenarios/TreeViewFileSystem.cs | 10 +++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs index 5067295fb..4f8692d74 100644 --- a/Terminal.Gui/Views/TreeView.cs +++ b/Terminal.Gui/Views/TreeView.cs @@ -219,19 +219,23 @@ namespace Terminal.Gui { /// public AspectGetterDelegate AspectGetter { get; set; } = (o) => o.ToString () ?? ""; - CursorVisibility desiredCursorVisibility = CursorVisibility.Default; + CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible; /// - /// Get / Set the wished cursor when the tree is focused + /// Get / Set the wished cursor when the tree is focused. + /// Only applies when is true. + /// Defaults to /// public CursorVisibility DesiredCursorVisibility { - get => desiredCursorVisibility; + get { + return MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible; + } set { - if (desiredCursorVisibility != value && HasFocus) { - Application.Driver.SetCursorVisibility (value); - } - desiredCursorVisibility = value; + + if (desiredCursorVisibility != value && HasFocus) { + Application.Driver.SetCursorVisibility (DesiredCursorVisibility); + } } } diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index 3414c2fe5..57fa181c7 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -26,6 +26,7 @@ namespace UICatalog.Scenarios { private MenuItem miLeaveLastRow; private MenuItem miCustomColors; private MenuItem miCursor; + private MenuItem miMultiSelect; private Terminal.Gui.Attribute green; private Terminal.Gui.Attribute red; @@ -55,7 +56,8 @@ namespace UICatalog.Scenarios { miFullPaths = new MenuItem ("_FullPaths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked}, miLeaveLastRow = new MenuItem ("_LeaveLastRow", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked}, miCustomColors = new MenuItem ("C_ustomColors", "", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked}, - miCursor = new MenuItem ("Curs_or", "", () => SetCursor()){Checked = true, CheckType = MenuItemCheckStyle.Checked}, + miCursor = new MenuItem ("Curs_or (MultiSelect only)", "", () => SetCursor()){Checked = false, CheckType = MenuItemCheckStyle.Checked}, + miMultiSelect = new MenuItem ("_MultiSelect", "", () => SetMultiSelect()){Checked = true, CheckType = MenuItemCheckStyle.Checked}, }), }); Top.Add (menu); @@ -276,6 +278,12 @@ namespace UICatalog.Scenarios { miCursor.Checked = !miCursor.Checked; treeViewFiles.DesiredCursorVisibility = miCursor.Checked ? CursorVisibility.Default : CursorVisibility.Invisible; } + private void SetMultiSelect() + { + miMultiSelect.Checked = !miMultiSelect.Checked; + treeViewFiles.MultiSelect = miMultiSelect.Checked; + } + private void SetCustomColors() { From 81d7c614444d637e6488982c5da2e728ceaaf6e7 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 25 Oct 2022 15:20:10 +0100 Subject: [PATCH 56/58] Fixes #2135. Character Map scenario doesn't show the content view. --- Terminal.Gui/Views/ScrollView.cs | 22 +++++++++++++++-- UnitTests/ScrollViewTests.cs | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index fc186bc0e..c42308dd0 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -29,7 +29,13 @@ namespace Terminal.Gui { /// /// public class ScrollView : View { - View contentView = null; + private class ContentView : View { + public ContentView (Rect frame) : base (frame) + { + } + } + + ContentView contentView; ScrollBarView vertical, horizontal; /// @@ -52,7 +58,7 @@ namespace Terminal.Gui { void Initialize (Rect frame) { - contentView = new View (frame); + contentView = new ContentView (frame); vertical = new ScrollBarView (1, 0, isVertical: true) { X = Pos.AnchorEnd (1), Y = 0, @@ -177,6 +183,12 @@ namespace Terminal.Gui { set { if (autoHideScrollBars != value) { autoHideScrollBars = value; + if (Subviews.Contains (vertical)) { + vertical.AutoHideScrollBars = value; + } + if (Subviews.Contains (horizontal)) { + horizontal.AutoHideScrollBars = value; + } SetNeedsDisplay (); } } @@ -251,6 +263,8 @@ namespace Terminal.Gui { SetNeedsLayout (); if (value) { base.Add (horizontal); + horizontal.ShowScrollIndicator = value; + horizontal.AutoHideScrollBars = autoHideScrollBars; horizontal.OtherScrollBarView = vertical; horizontal.OtherScrollBarView.ShowScrollIndicator = value; horizontal.MouseEnter += View_MouseEnter; @@ -290,6 +304,8 @@ namespace Terminal.Gui { SetNeedsLayout (); if (value) { base.Add (vertical); + vertical.ShowScrollIndicator = value; + vertical.AutoHideScrollBars = autoHideScrollBars; vertical.OtherScrollBarView = horizontal; vertical.OtherScrollBarView.ShowScrollIndicator = value; vertical.MouseEnter += View_MouseEnter; @@ -322,10 +338,12 @@ namespace Terminal.Gui { ShowHideScrollBars (); } else { if (ShowVerticalScrollIndicator) { + vertical.SetRelativeLayout (Bounds); vertical.Redraw (vertical.Bounds); } if (ShowHorizontalScrollIndicator) { + horizontal.SetRelativeLayout (Bounds); horizontal.Redraw (horizontal.Bounds); } } diff --git a/UnitTests/ScrollViewTests.cs b/UnitTests/ScrollViewTests.cs index 8d8a6b4b0..8fa8ae381 100644 --- a/UnitTests/ScrollViewTests.cs +++ b/UnitTests/ScrollViewTests.cs @@ -4,9 +4,17 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace Terminal.Gui.Views { public class ScrollViewTests { + readonly ITestOutputHelper output; + + public ScrollViewTests (ITestOutputHelper output) + { + this.output = output; + } + [Fact] public void Constructors_Defaults () { @@ -173,5 +181,39 @@ namespace Terminal.Gui.Views { Assert.False (sv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask, new KeyModifiers ()))); Assert.Equal (new Point (-39, -19), sv.ContentOffset); } + + [Fact, AutoInitShutdown] + public void AutoHideScrollBars_ShowHorizontalScrollIndicator_ShowVerticalScrollIndicator () + { + var sv = new ScrollView { + Width = 10, + Height = 10 + }; + + Application.Top.Add (sv); + Application.Begin (Application.Top); + + Assert.True (sv.AutoHideScrollBars); + Assert.False (sv.ShowHorizontalScrollIndicator); + Assert.False (sv.ShowVerticalScrollIndicator); + GraphViewTests.AssertDriverContentsWithFrameAre ("", output); + + sv.AutoHideScrollBars = false; + sv.ShowHorizontalScrollIndicator = true; + sv.ShowVerticalScrollIndicator = true; + sv.Redraw (sv.Bounds); + GraphViewTests.AssertDriverContentsWithFrameAre (@" + ▲ + ┬ + │ + │ + │ + │ + │ + ┴ + ▼ +◄├─────┤► +", output); + } } } \ No newline at end of file From 6a509bd0f268fe7db45a8c4c1385bc13745f1abf Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 26 Oct 2022 12:34:13 +0100 Subject: [PATCH 57/58] Added one more unit test. --- UnitTests/ScrollViewTests.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/UnitTests/ScrollViewTests.cs b/UnitTests/ScrollViewTests.cs index 8fa8ae381..55085f017 100644 --- a/UnitTests/ScrollViewTests.cs +++ b/UnitTests/ScrollViewTests.cs @@ -213,6 +213,35 @@ namespace Terminal.Gui.Views { ┴ ▼ ◄├─────┤► +", output); + } + + [Fact, AutoInitShutdown] + public void ContentSize_AutoHideScrollBars_ShowHorizontalScrollIndicator_ShowVerticalScrollIndicator () + { + var sv = new ScrollView { + Width = 10, + Height = 10, + ContentSize = new Size (50, 50) + }; + + Application.Top.Add (sv); + Application.Begin (Application.Top); + + Assert.True (sv.AutoHideScrollBars); + Assert.True (sv.ShowHorizontalScrollIndicator); + Assert.True (sv.ShowVerticalScrollIndicator); + GraphViewTests.AssertDriverContentsWithFrameAre (@" + ▲ + ┬ + ┴ + ░ + ░ + ░ + ░ + ░ + ▼ +◄├┤░░░░░► ", output); } } From 60a94ebab30c09210a03f8330b566b65c774085d Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 26 Oct 2022 12:54:23 +0100 Subject: [PATCH 58/58] Added ContentOffset and ContentSize unit tests. --- UnitTests/ScrollViewTests.cs | 38 +++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/UnitTests/ScrollViewTests.cs b/UnitTests/ScrollViewTests.cs index 55085f017..434f06ddb 100644 --- a/UnitTests/ScrollViewTests.cs +++ b/UnitTests/ScrollViewTests.cs @@ -228,6 +228,8 @@ namespace Terminal.Gui.Views { Application.Top.Add (sv); Application.Begin (Application.Top); + Assert.Equal (50, sv.ContentSize.Width); + Assert.Equal (50, sv.ContentSize.Height); Assert.True (sv.AutoHideScrollBars); Assert.True (sv.ShowHorizontalScrollIndicator); Assert.True (sv.ShowVerticalScrollIndicator); @@ -242,7 +244,41 @@ namespace Terminal.Gui.Views { ░ ▼ ◄├┤░░░░░► +", output); + } + + [Fact, AutoInitShutdown] + public void ContentOffset_ContentSize_AutoHideScrollBars_ShowHorizontalScrollIndicator_ShowVerticalScrollIndicator () + { + var sv = new ScrollView { + Width = 10, + Height = 10, + ContentSize = new Size (50, 50), + ContentOffset = new Point (25, 25) + }; + + Application.Top.Add (sv); + Application.Begin (Application.Top); + + Assert.Equal (-25, sv.ContentOffset.X); + Assert.Equal (-25, sv.ContentOffset.Y); + Assert.Equal (50, sv.ContentSize.Width); + Assert.Equal (50, sv.ContentSize.Height); + Assert.True (sv.AutoHideScrollBars); + Assert.True (sv.ShowHorizontalScrollIndicator); + Assert.True (sv.ShowVerticalScrollIndicator); + GraphViewTests.AssertDriverContentsWithFrameAre (@" + ▲ + ░ + ░ + ░ + ┬ + │ + ┴ + ░ + ▼ +◄░░░├─┤░► ", output); } } -} \ No newline at end of file +}