From 2dc5fce8654ffeb6f3e570b0bdefcc6a5b6a6d2b Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 15 Apr 2020 08:25:57 -0600 Subject: [PATCH 01/33] Revert "Drop NuGet restore" This reverts commit 5c7a0d05f077755943ec66e6a82db890a24cd056. --- azure-pipelines.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3ccc9a2ac..1ec952ef5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,6 +9,9 @@ steps: msbuild /t:Restore Terminal.sln displayName: Restore dependencies +- script: | + nuget restore + displayName: Nuget restore - script: | if echo $BUILD_SOURCEBRANCH | grep /release/; then perl -pi -e "s/PackageVersion>.*${BUILD_SOURCEBRANCHNAME} Date: Wed, 15 Apr 2020 08:28:02 -0600 Subject: [PATCH 02/33] Revert "Revert "Drop NuGet restore"" This reverts commit 2dc5fce8654ffeb6f3e570b0bdefcc6a5b6a6d2b. --- azure-pipelines.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1ec952ef5..3ccc9a2ac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,9 +9,6 @@ steps: msbuild /t:Restore Terminal.sln displayName: Restore dependencies -- script: | - nuget restore - displayName: Nuget restore - script: | if echo $BUILD_SOURCEBRANCH | grep /release/; then perl -pi -e "s/PackageVersion>.*${BUILD_SOURCEBRANCHNAME} Date: Sun, 19 Apr 2020 13:03:01 -0600 Subject: [PATCH 03/33] terminal.sln --- Terminal.sln | 118 +++++++++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/Terminal.sln b/Terminal.sln index c5070ec8a..5bace6618 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -1,59 +1,59 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{B0A602CD-E176-449D-8663-64238D54F857}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x86 = Debug|x86 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.ActiveCfg = Debug|x86 - {B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.Build.0 = Debug|x86 - {B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.ActiveCfg = Release|x86 - {B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.Build.0 = Release|x86 - {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.ActiveCfg = Debug|Any CPU - {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.Build.0 = Debug|Any CPU - {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.ActiveCfg = Release|Any CPU - {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.Build.0 = Release|Any CPU - {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.ActiveCfg = Debug|x86 - {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.Build.0 = Debug|x86 - {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.ActiveCfg = Release|x86 - {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.TextStylePolicy = $1 - $1.FileWidth = 80 - $1.scope = text/x-csharp - $1.TabWidth = 8 - $1.IndentWidth = 8 - $0.CSharpFormattingPolicy = $2 - $2.scope = text/x-csharp - $2.IndentSwitchSection = False - $2.NewLinesForBracesInTypes = False - $2.NewLinesForBracesInProperties = False - $2.NewLinesForBracesInAccessors = False - $2.NewLinesForBracesInAnonymousMethods = False - $2.NewLinesForBracesInControlBlocks = False - $2.NewLinesForBracesInAnonymousTypes = False - $2.NewLinesForBracesInObjectCollectionArrayInitializers = False - $2.NewLinesForBracesInLambdaExpressionBody = False - $2.NewLineForElse = False - $2.NewLineForCatch = False - $2.NewLineForFinally = False - $2.NewLineForMembersInObjectInit = False - $2.NewLineForMembersInAnonymousTypes = False - $2.NewLineForClausesInQuery = False - $2.SpacingAfterMethodDeclarationName = True - $2.SpaceAfterMethodCallName = True - $2.SpaceBeforeOpenSquareBracket = True - $0.DotNetNamingPolicy = $3 - $0.StandardHeader = $4 - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{B0A602CD-E176-449D-8663-64238D54F857}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.ActiveCfg = Debug|x86 + {B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.Build.0 = Debug|x86 + {B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.ActiveCfg = Release|x86 + {B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.Build.0 = Release|x86 + {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.ActiveCfg = Debug|Any CPU + {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.Build.0 = Debug|Any CPU + {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.ActiveCfg = Release|Any CPU + {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.Build.0 = Release|Any CPU + {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.ActiveCfg = Debug|x86 + {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.Build.0 = Debug|x86 + {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.ActiveCfg = Release|x86 + {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.FileWidth = 80 + $1.scope = text/x-csharp + $1.TabWidth = 8 + $1.IndentWidth = 8 + $0.CSharpFormattingPolicy = $2 + $2.scope = text/x-csharp + $2.IndentSwitchSection = False + $2.NewLinesForBracesInTypes = False + $2.NewLinesForBracesInProperties = False + $2.NewLinesForBracesInAccessors = False + $2.NewLinesForBracesInAnonymousMethods = False + $2.NewLinesForBracesInControlBlocks = False + $2.NewLinesForBracesInAnonymousTypes = False + $2.NewLinesForBracesInObjectCollectionArrayInitializers = False + $2.NewLinesForBracesInLambdaExpressionBody = False + $2.NewLineForElse = False + $2.NewLineForCatch = False + $2.NewLineForFinally = False + $2.NewLineForMembersInObjectInit = False + $2.NewLineForMembersInAnonymousTypes = False + $2.NewLineForClausesInQuery = False + $2.SpacingAfterMethodDeclarationName = True + $2.SpaceAfterMethodCallName = True + $2.SpaceBeforeOpenSquareBracket = True + $0.DotNetNamingPolicy = $3 + $0.StandardHeader = $4 + EndGlobalSection +EndGlobal From ea813ce1e7e13abdb82b7e3d7184b7e4f08792e0 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Mon, 18 May 2020 22:42:04 -0600 Subject: [PATCH 04/33] Refactored keydown/up/press events to use event vs. Action --- Example/demo.cs | 10 +++++----- Terminal.Gui/Core.cs | 37 +++++++++++++++++++++---------------- Terminal.Gui/Views/Menu.cs | 12 ++++++------ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 6db1bb40b..9edfeb4d0 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -90,8 +90,8 @@ static class Demo { Width = Dim.Fill (), Height = Dim.Fill () }; - container.OnKeyUp += (KeyEvent ke) => { - if (ke.Key == Key.Esc) + container.KeyUp += (sender, e) => { + if (e.KeyEvent.Key == Key.Esc) container.Running = false; }; @@ -469,9 +469,9 @@ static class Demo { } - container.OnKeyDown += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Down"); - container.OnKeyPress += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Press"); - container.OnKeyUp += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Up"); + container.KeyDown += (o, e) => KeyDownPressUp (e.KeyEvent, "Down"); + container.KeyPress += (o, e) => KeyDownPressUp (e.KeyEvent, "Press"); + container.KeyUp += (o, e) => KeyDownPressUp (e.KeyEvent, "Up"); Application.Run (container); } #endregion diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 91135771e..ad1fb8549 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -126,7 +126,7 @@ namespace Terminal.Gui { /// /// Contains the details about the key that produced the event. /// true if the event was handled - public virtual bool KeyDown (KeyEvent keyEvent) + public virtual bool OnKeyDown (KeyEvent keyEvent) { return false; } @@ -136,7 +136,7 @@ namespace Terminal.Gui { /// /// Contains the details about the key that produced the event. /// true if the event was handled - public virtual bool KeyUp (KeyEvent keyEvent) + public virtual bool OnKeyUp (KeyEvent keyEvent) { return false; } @@ -1066,15 +1066,20 @@ namespace Terminal.Gui { SuperView?.SetFocus(this); } + public class KeyEventEventArgs : EventArgs { + public KeyEventEventArgs(KeyEvent ke) => KeyEvent = ke; + public KeyEvent KeyEvent { get; set; } + } + /// /// Invoked when a character key is pressed and occurs after the key down event. /// - public Action OnKeyPress; + public event EventHandler KeyPress; /// Contains the details about the key that produced the event. public override bool ProcessKey (KeyEvent keyEvent) { - OnKeyPress?.Invoke (keyEvent); + KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); if (Focused?.ProcessKey (keyEvent) == true) return true; @@ -1084,7 +1089,7 @@ namespace Terminal.Gui { /// Contains the details about the key that produced the event. public override bool ProcessHotKey (KeyEvent keyEvent) { - OnKeyPress?.Invoke (keyEvent); + KeyPress?.Invoke (this, new KeyEventEventArgs (keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1096,7 +1101,7 @@ namespace Terminal.Gui { /// Contains the details about the key that produced the event. public override bool ProcessColdKey (KeyEvent keyEvent) { - OnKeyPress?.Invoke (keyEvent); + KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1108,16 +1113,16 @@ namespace Terminal.Gui { /// /// Invoked when a key is pressed /// - public Action OnKeyDown; + public event EventHandler KeyDown; /// Contains the details about the key that produced the event. - public override bool KeyDown (KeyEvent keyEvent) + public override bool OnKeyDown (KeyEvent keyEvent) { - OnKeyDown?.Invoke (keyEvent); + KeyDown?.Invoke (this, new KeyEventEventArgs (keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) - if (view.KeyDown (keyEvent)) + if (view.OnKeyDown (keyEvent)) return true; return false; @@ -1126,16 +1131,16 @@ namespace Terminal.Gui { /// /// Invoked when a key is released /// - public Action OnKeyUp; + public event EventHandler KeyUp; /// Contains the details about the key that produced the event. - public override bool KeyUp (KeyEvent keyEvent) + public override bool OnKeyUp (KeyEvent keyEvent) { - OnKeyUp?.Invoke (keyEvent); + KeyUp?.Invoke (this, new KeyEventEventArgs (keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) - if (view.KeyUp (keyEvent)) + if (view.OnKeyUp (keyEvent)) return true; return false; @@ -2142,7 +2147,7 @@ namespace Terminal.Gui { { var chain = toplevels.ToList (); foreach (var topLevel in chain) { - if (topLevel.KeyDown (ke)) + if (topLevel.OnKeyDown (ke)) return; if (topLevel.Modal) break; @@ -2154,7 +2159,7 @@ namespace Terminal.Gui { { var chain = toplevels.ToList (); foreach (var topLevel in chain) { - if (topLevel.KeyUp (ke)) + if (topLevel.OnKeyUp (ke)) return; if (topLevel.Modal) break; diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 47f736676..0e40bd391 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -344,7 +344,7 @@ namespace Terminal.Gui { }); } - public override bool KeyDown (KeyEvent keyEvent) + public override bool OnKeyDown (KeyEvent keyEvent) { if (keyEvent.IsAlt) { host.CloseAllMenus (); @@ -359,7 +359,7 @@ namespace Terminal.Gui { // To ncurses simulate a AltMask key pressing Alt+Space because // it can´t detect an alone special key down was pressed. if (keyEvent.IsAlt && keyEvent.Key == Key.AltMask) { - KeyDown (keyEvent); + OnKeyDown (keyEvent); return true; } @@ -559,7 +559,7 @@ namespace Terminal.Gui { } bool openedByAltKey; - public override bool KeyDown (KeyEvent keyEvent) + public override bool OnKeyDown (KeyEvent keyEvent) { if (keyEvent.IsAlt) { openedByAltKey = true; @@ -575,7 +575,7 @@ namespace Terminal.Gui { /// /// /// - public override bool KeyUp (KeyEvent keyEvent) + public override bool OnKeyUp (KeyEvent keyEvent) { if (keyEvent.IsAlt) { // User pressed Alt - this may be a precursor to a menu accelerator (e.g. Alt-F) @@ -982,8 +982,8 @@ namespace Terminal.Gui { // To ncurses simulate a AltMask key pressing Alt+Space because // it can´t detect an alone special key down was pressed. if (kb.IsAlt && kb.Key == Key.AltMask && openMenu == null) { - KeyDown (kb); - KeyUp (kb); + OnKeyDown (kb); + OnKeyUp (kb); return true; } else if (kb.IsAlt) { if (FindAndOpenMenuByHotkey (kb)) return true; From 2c5d09a521b040a5001948d61ed23d7b9947320f Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Mon, 18 May 2020 23:00:54 -0600 Subject: [PATCH 05/33] Refactored onload/onresize events to use event vs. Action --- Example/demo.cs | 2 +- Terminal.Gui/Core.cs | 70 ++++++++++++++++++++------------- Terminal.Gui/Views/StatusBar.cs | 4 +- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 6db1bb40b..91460b29a 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -604,7 +604,7 @@ static class Demo { var bottom2 = new Label ("This should go on the bottom of another top-level!"); top.Add (bottom2); - Application.OnLoad = () => { + Application.Loaded += (sender, e) => { bottom.X = win.X; bottom.Y = Pos.Bottom (win) - Pos.Top (win) - margin; bottom2.X = Pos.Left (win); diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 91135771e..e2b04e953 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -607,7 +607,7 @@ namespace Terminal.Gui { return; while (subviews.Count > 0) { - Remove (subviews[0]); + Remove (subviews [0]); } } @@ -705,9 +705,9 @@ namespace Terminal.Gui { { PerformActionForSubview (subview, x => { var idx = subviews.IndexOf (x); - if (idx+1 < subviews.Count) { + if (idx + 1 < subviews.Count) { subviews.Remove (x); - subviews.Insert (idx+1, x); + subviews.Insert (idx + 1, x); } }); } @@ -913,7 +913,7 @@ namespace Terminal.Gui { OnEnter (); else OnLeave (); - SetNeedsDisplay (); + SetNeedsDisplay (); base.HasFocus = value; // Remove focus down the chain of subviews if focus is removed @@ -1063,7 +1063,7 @@ namespace Terminal.Gui { focused.EnsureFocus (); // Send focus upwards - SuperView?.SetFocus(this); + SuperView?.SetFocus (this); } /// @@ -1176,7 +1176,7 @@ namespace Terminal.Gui { public void FocusLast () { if (subviews == null) { - SuperView?.SetFocus(this); + SuperView?.SetFocus (this); return; } @@ -1223,7 +1223,7 @@ namespace Terminal.Gui { w.FocusLast (); SetFocus (w); - return true; + return true; } } if (focused != null) { @@ -1614,7 +1614,7 @@ namespace Terminal.Gui { internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny) { nx = Math.Max (x, 0); - nx = nx + top.Frame.Width > Driver.Cols ? Math.Max(Driver.Cols - top.Frame.Width, 0) : nx; + nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx; bool m, s; if (SuperView == null) m = Application.Top.HasMenuBar; @@ -1628,7 +1628,7 @@ namespace Terminal.Gui { s = ((Toplevel)SuperView).HasStatusBar; l = s ? Driver.Rows - 1 : Driver.Rows; ny = Math.Min (ny, l); - ny = ny + top.Frame.Height > l ? Math.Max(l - top.Frame.Height, m ? 1 : 0) : ny; + ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; } internal void PositionToplevels () @@ -1775,7 +1775,7 @@ namespace Terminal.Gui { this.Title = title; int wb = 1 + padding; this.padding = padding; - contentView = new ContentView () { + contentView = new ContentView () { X = wb, Y = wb, Width = Dim.Fill (wb), @@ -2056,7 +2056,7 @@ namespace Terminal.Gui { if (UseSystemConsole) { mainLoopDriver = new Mono.Terminal.NetMainLoop (); Driver = new NetDriver (); - } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows){ + } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { var windowsDriver = new WindowsDriver (); mainLoopDriver = windowsDriver; Driver = windowsDriver; @@ -2114,7 +2114,7 @@ namespace Terminal.Gui { static void ProcessKeyEvent (KeyEvent ke) { - var chain = toplevels.ToList(); + var chain = toplevels.ToList (); foreach (var topLevel in chain) { if (topLevel.ProcessHotKey (ke)) return; @@ -2171,7 +2171,7 @@ namespace Terminal.Gui { return null; } - if (start.InternalSubviews != null){ + if (start.InternalSubviews != null) { int count = start.InternalSubviews.Count; if (count > 0) { var rx = x - startFrame.X; @@ -2187,8 +2187,8 @@ namespace Terminal.Gui { } } } - resx = x-startFrame.X; - resy = y-startFrame.Y; + resx = x - startFrame.X; + resy = y - startFrame.Y; return start; } @@ -2281,9 +2281,10 @@ namespace Terminal.Gui { } /// - /// Action that is invoked once at beginning. + /// This event is fired once when the application is first loaded. The dimensions of the + /// terminal are provided. /// - static public Action OnLoad; + static public event EventHandler Loaded; /// /// Building block API: Prepares the provided toplevel for execution. @@ -2307,11 +2308,11 @@ namespace Terminal.Gui { Init (); if (toplevel is ISupportInitializeNotification initializableNotification && !initializableNotification.IsInitialized) { - initializableNotification.BeginInit(); - initializableNotification.EndInit(); + initializableNotification.BeginInit (); + initializableNotification.EndInit (); } else if (toplevel is ISupportInitialize initializable) { - initializable.BeginInit(); - initializable.EndInit(); + initializable.BeginInit (); + initializable.EndInit (); } toplevels.Push (toplevel); Current = toplevel; @@ -2319,7 +2320,7 @@ namespace Terminal.Gui { if (toplevel.LayoutStyle == LayoutStyle.Computed) toplevel.RelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); toplevel.LayoutSubviews (); - OnLoad?.Invoke (); + Loaded?.Invoke (null, new ResizedEventArgs () { Rows = Driver.Rows, Cols = Driver.Cols } ); toplevel.WillPresent (); Redraw (toplevel); toplevel.PositionCursor (); @@ -2386,9 +2387,8 @@ namespace Terminal.Gui { toplevels.Pop (); if (toplevels.Count == 0) Shutdown (); - else - { - Current = toplevels.Peek(); + else { + Current = toplevels.Peek (); Refresh (); } } @@ -2450,7 +2450,7 @@ namespace Terminal.Gui { /// public static void Run () where T : Toplevel, new() { - Init (() => new T()); + Init (() => new T ()); Run (Top); } @@ -2494,15 +2494,29 @@ namespace Terminal.Gui { Current.Running = false; } + /// + /// Event arguments for the event. + /// + public class ResizedEventArgs : EventArgs { + /// + /// The number of rows in the resized terminal. + /// + public int Rows { get; set; } + /// + /// The number of columns in the resized terminal. + /// + public int Cols { get; set; } + } + /// /// Invoked when the terminal was resized. /// - static public Action OnResized; + static public event EventHandler Resized; static void TerminalResized () { - OnResized?.Invoke (); var full = new Rect (0, 0, Driver.Cols, Driver.Rows); + Resized?.Invoke (null, new ResizedEventArgs () { Cols = full.Width, Rows = full.Height }); Driver.Clip = full; foreach (var t in toplevels) { t.PositionToplevels (); diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index fe1573a91..044321f98 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -102,7 +102,7 @@ namespace Terminal.Gui { CanFocus = false; ColorScheme = Colors.Menu; - Application.OnLoad += () => { + Application.Loaded += (sender, e) => { X = 0; Height = 1; #if SNAP_TO_TOP @@ -114,7 +114,7 @@ namespace Terminal.Gui { case StatusBarStyle.SnapToBottom: #endif if (Parent == null) { - Y = Application.Driver.Rows - 1; // TODO: using internals of Application + Y = e.Rows - 1; } else { Y = Pos.Bottom (Parent); } From c7b4b3472a1c8a9b9153503fb23f0c6b77b995fe Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Mon, 18 May 2020 23:08:40 -0600 Subject: [PATCH 06/33] oops. left args off Resized --- Terminal.Gui/Core.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index e2b04e953..72b5085b8 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -2509,9 +2509,9 @@ namespace Terminal.Gui { } /// - /// Invoked when the terminal was resized. + /// Invoked when the terminal was resized. The new size of the terminal is provided. /// - static public event EventHandler Resized; + static public event EventHandler Resized; static void TerminalResized () { From dc87bc4e07517c06f41034ac26e7ea3983520e49 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Mon, 18 May 2020 23:10:45 -0600 Subject: [PATCH 07/33] sorry. for. my. OCD. --- Terminal.Gui/Core.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 72b5085b8..96f6f38be 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -1985,7 +1985,7 @@ namespace Terminal.Gui { /// /// See also /// - static public event EventHandler Iteration; + public static event EventHandler Iteration; /// /// Returns a rectangle that is centered in the screen for the provided size. @@ -2219,7 +2219,7 @@ namespace Terminal.Gui { /// /// Merely a debugging aid to see the raw mouse events /// - static public Action RootMouseEvent; + public static Action RootMouseEvent; internal static View wantContinuousButtonPressedView; static View lastMouseOwnerView; @@ -2284,7 +2284,7 @@ namespace Terminal.Gui { /// This event is fired once when the application is first loaded. The dimensions of the /// terminal are provided. /// - static public event EventHandler Loaded; + public static event EventHandler Loaded; /// /// Building block API: Prepares the provided toplevel for execution. @@ -2299,7 +2299,7 @@ namespace Terminal.Gui { /// the method, and then the method upon termination which will /// undo these changes. /// - static public RunState Begin (Toplevel toplevel) + public static RunState Begin (Toplevel toplevel) { if (toplevel == null) throw new ArgumentNullException (nameof (toplevel)); @@ -2333,7 +2333,7 @@ namespace Terminal.Gui { /// Building block API: completes the execution of a Toplevel that was started with Begin. /// /// The runstate returned by the method. - static public void End (RunState runState) + public static void End (RunState runState) { if (runState == null) throw new ArgumentNullException (nameof (runState)); @@ -2511,7 +2511,7 @@ namespace Terminal.Gui { /// /// Invoked when the terminal was resized. The new size of the terminal is provided. /// - static public event EventHandler Resized; + public static event EventHandler Resized; static void TerminalResized () { From b3f8f3bb29a0318767d3d5a3ddc74aa29e15f601 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Tue, 19 May 2020 00:04:14 -0600 Subject: [PATCH 08/33] missed some renames --- Example/demo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 9edfeb4d0..cd39607d3 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -413,11 +413,11 @@ static class Demo { #endregion - #region OnKeyDown / OnKeyPress / OnKeyUp Demo + #region KeyDown / KeyPress / KeyUp Demo private static void OnKeyDownPressUpDemo () { var container = new Dialog ( - "OnKeyDown & OnKeyPress & OnKeyUp demo", 80, 20, + "KeyDown & KeyPress & KeyUp demo", 80, 20, new Button ("Close") { Clicked = () => { Application.RequestStop (); } }) { Width = Dim.Fill (), Height = Dim.Fill (), From 97c6181567dc7af8bb2d60552bb12f7d9e17bce8 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Tue, 19 May 2020 20:08:01 -0600 Subject: [PATCH 09/33] Seeing how github actions will work --- .github/workflows/dotnetcore.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/dotnetcore.yml diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml new file mode 100644 index 000000000..f734ea7b5 --- /dev/null +++ b/.github/workflows/dotnetcore.yml @@ -0,0 +1,25 @@ +name: .NET Core + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.101 + - name: Install dependencies + run: dotnet restore + - name: Build + run: dotnet build --configuration Release --no-restore + - name: Test + run: dotnet test --no-restore --verbosity normal From a5a1ecbd8e1f7ad032e150ba3b077643f77312bc Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 20 May 2020 11:26:56 -0600 Subject: [PATCH 10/33] Fixes All Warnings (#450) * Revert "Drop NuGet restore" This reverts commit 5c7a0d05f077755943ec66e6a82db890a24cd056. * Revert "Revert "Drop NuGet restore"" This reverts commit 2dc5fce8654ffeb6f3e570b0bdefcc6a5b6a6d2b. * terminal.sln * there. That wasn't so hard * fixed some cases where should have been used * fixed some cases where should have been used --- Terminal.Gui/Core.cs | 38 +++++++++------ Terminal.Gui/Dialogs/Dialog.cs | 5 +- Terminal.Gui/Dialogs/FileDialog.cs | 7 +-- Terminal.Gui/Drivers/ConsoleDriver.cs | 8 ++-- Terminal.Gui/Drivers/CursesDriver.cs | 4 +- Terminal.Gui/Drivers/NetDriver.cs | 2 +- Terminal.Gui/Drivers/WindowsDriver.cs | 6 +-- Terminal.Gui/MonoCurses/UnmanagedLibrary.cs | 6 ++- Terminal.Gui/MonoCurses/binding.cs | 4 +- Terminal.Gui/MonoCurses/constants.cs | 2 + Terminal.Gui/MonoCurses/handles.cs | 4 +- Terminal.Gui/MonoCurses/mainloop.cs | 4 ++ Terminal.Gui/Views/Button.cs | 6 +++ Terminal.Gui/Views/Checkbox.cs | 4 ++ Terminal.Gui/Views/Clipboard.cs | 6 +++ Terminal.Gui/Views/HexView.cs | 3 ++ Terminal.Gui/Views/Label.cs | 1 + Terminal.Gui/Views/ListView.cs | 52 +++++++++++++++++++++ Terminal.Gui/Views/Menu.cs | 35 +++++++++++--- Terminal.Gui/Views/ScrollView.cs | 9 +++- Terminal.Gui/Views/StatusBar.cs | 8 ++++ Terminal.Gui/Views/TextView.cs | 6 +++ 22 files changed, 184 insertions(+), 36 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 91135771e..4da96a6e3 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -899,10 +899,7 @@ namespace Terminal.Gui { Move (frame.X, frame.Y); } - /// - /// Gets or sets a value indicating whether this has focus. - /// - /// true if has focus; otherwise, false. + /// public override bool HasFocus { get { return base.HasFocus; @@ -925,12 +922,14 @@ namespace Terminal.Gui { } } + /// public override bool OnEnter () { Enter?.Invoke (this, new EventArgs ()); return base.OnEnter (); } + /// public override bool OnLeave () { Leave?.Invoke (this, new EventArgs ()); @@ -1071,7 +1070,7 @@ namespace Terminal.Gui { /// public Action OnKeyPress; - /// Contains the details about the key that produced the event. + /// public override bool ProcessKey (KeyEvent keyEvent) { OnKeyPress?.Invoke (keyEvent); @@ -1081,7 +1080,7 @@ namespace Terminal.Gui { return false; } - /// Contains the details about the key that produced the event. + /// public override bool ProcessHotKey (KeyEvent keyEvent) { OnKeyPress?.Invoke (keyEvent); @@ -1093,7 +1092,7 @@ namespace Terminal.Gui { return false; } - /// Contains the details about the key that produced the event. + /// public override bool ProcessColdKey (KeyEvent keyEvent) { OnKeyPress?.Invoke (keyEvent); @@ -1110,7 +1109,7 @@ namespace Terminal.Gui { /// public Action OnKeyDown; - /// Contains the details about the key that produced the event. + /// public override bool KeyDown (KeyEvent keyEvent) { OnKeyDown?.Invoke (keyEvent); @@ -1128,7 +1127,7 @@ namespace Terminal.Gui { /// public Action OnKeyUp; - /// Contains the details about the key that produced the event. + /// public override bool KeyUp (KeyEvent keyEvent) { OnKeyUp?.Invoke (keyEvent); @@ -1140,6 +1139,7 @@ namespace Terminal.Gui { return false; } + /// /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing. /// @@ -1410,15 +1410,13 @@ namespace Terminal.Gui { layoutNeeded = false; } - /// - /// Returns a that represents the current . - /// - /// A that represents the current . + /// public override string ToString () { return $"{GetType ().Name}({Id})({Frame})"; } + /// public override bool OnMouseEnter (MouseEvent mouseEvent) { if (!base.OnMouseEnter (mouseEvent)) { @@ -1428,6 +1426,7 @@ namespace Terminal.Gui { return true; } + /// public override bool OnMouseLeave (MouseEvent mouseEvent) { if (!base.OnMouseLeave (mouseEvent)) { @@ -1508,6 +1507,10 @@ namespace Terminal.Gui { return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows)); } + /// + /// Gets or sets a value indicating whether this can focus. + /// + /// true if can focus; otherwise, false. public override bool CanFocus { get => true; } @@ -1529,6 +1532,7 @@ namespace Terminal.Gui { /// public bool HasStatusBar { get; set; } + /// public override bool ProcessKey (KeyEvent keyEvent) { if (base.ProcessKey (keyEvent)) @@ -1580,6 +1584,7 @@ namespace Terminal.Gui { return false; } + /// public override void Add (View view) { if (this == Application.Top) { @@ -1591,6 +1596,7 @@ namespace Terminal.Gui { base.Add (view); } + /// public override void Remove (View view) { if (this == Application.Top) { @@ -1602,6 +1608,7 @@ namespace Terminal.Gui { base.Remove (view); } + /// public override void RemoveAll () { if (this == Application.Top) { @@ -1656,6 +1663,7 @@ namespace Terminal.Gui { } } + /// public override void Redraw (Rect region) { Application.CurrentView = this; @@ -1838,6 +1846,7 @@ namespace Terminal.Gui { contentView.RemoveAll (); } + /// public override void Redraw (Rect bounds) { Application.CurrentView = this; @@ -1873,6 +1882,7 @@ namespace Terminal.Gui { // internal static Point? dragPosition; Point start; + /// public override bool MouseEvent (MouseEvent mouseEvent) { // FIXED:The code is currently disabled, because the @@ -2426,7 +2436,7 @@ namespace Terminal.Gui { } } - internal static bool DebugDrawBounds; + internal static bool DebugDrawBounds = false; // Need to look into why this does not work properly. static void DrawBounds (View v) diff --git a/Terminal.Gui/Dialogs/Dialog.cs b/Terminal.Gui/Dialogs/Dialog.cs index a1f809b97..2aef337d5 100644 --- a/Terminal.Gui/Dialogs/Dialog.cs +++ b/Terminal.Gui/Dialogs/Dialog.cs @@ -61,7 +61,9 @@ namespace Terminal.Gui { Add (button); } - + /// + /// Lays out the subviews for the Dialog. + /// public override void LayoutSubviews () { base.LayoutSubviews (); @@ -86,6 +88,7 @@ namespace Terminal.Gui { } } + /// public override bool ProcessKey (KeyEvent kb) { switch (kb.Key) { diff --git a/Terminal.Gui/Dialogs/FileDialog.cs b/Terminal.Gui/Dialogs/FileDialog.cs index 069b8eea4..7c9b54374 100644 --- a/Terminal.Gui/Dialogs/FileDialog.cs +++ b/Terminal.Gui/Dialogs/FileDialog.cs @@ -212,9 +212,9 @@ namespace Terminal.Gui { } } - public Action<(string,bool)> SelectedChanged; - public Action DirectoryChanged; - public Action FileChanged; + public Action<(string, bool)> SelectedChanged { get; set; } + public Action DirectoryChanged { get; set; } + public Action FileChanged { get; set; } void SelectionChanged () { @@ -494,6 +494,7 @@ namespace Terminal.Gui { internal bool canceled; + /// public override void WillPresent () { base.WillPresent (); diff --git a/Terminal.Gui/Drivers/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs index 92be65a21..32f488674 100644 --- a/Terminal.Gui/Drivers/ConsoleDriver.cs +++ b/Terminal.Gui/Drivers/ConsoleDriver.cs @@ -466,9 +466,11 @@ namespace Terminal.Gui { /// /// Prepare the driver and set the key and mouse events handlers. /// - /// - /// - /// + /// The main loop. + /// The handler for ProcessKey + /// The handler for key down events + /// The handler for key up events + /// The handler for mouse events public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler); /// diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs index 1ecfb47a6..8ec7eda9c 100644 --- a/Terminal.Gui/Drivers/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver.cs @@ -17,6 +17,7 @@ namespace Terminal.Gui { /// This is the Curses driver for the gui.cs/Terminal framework. /// public class CursesDriver : ConsoleDriver { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public override int Cols => Curses.Cols; public override int Rows => Curses.Lines; @@ -37,7 +38,7 @@ namespace Terminal.Gui { } } - static bool sync; + static bool sync = false; public override void AddRune (Rune rune) { if (Clip.Contains (ccol, crow)) { @@ -490,6 +491,7 @@ namespace Terminal.Gui { killpg (0, signal); return true; } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } } diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs index 96224c11a..99cef7ac5 100644 --- a/Terminal.Gui/Drivers/NetDriver.cs +++ b/Terminal.Gui/Drivers/NetDriver.cs @@ -37,7 +37,7 @@ namespace Terminal.Gui { dirtyLine [row] = true; } - static bool sync; + static bool sync = false; public NetDriver () { diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 5fc86fa78..9625dbc7d 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -97,7 +97,7 @@ namespace Terminal.Gui { public void Cleanup () { ConsoleMode = originalConsoleMode; - ContinueListeningForConsoleEvents = false; + //ContinueListeningForConsoleEvents = false; if (!SetConsoleActiveScreenBuffer (OutputHandle)) { var err = Marshal.GetLastWin32Error (); Console.WriteLine ("Error: {0}", err); @@ -109,7 +109,7 @@ namespace Terminal.Gui { ScreenBuffer = IntPtr.Zero; } - bool ContinueListeningForConsoleEvents = true; + //bool ContinueListeningForConsoleEvents = true; public uint ConsoleMode { get { @@ -426,7 +426,7 @@ namespace Terminal.Gui { } internal class WindowsDriver : ConsoleDriver, Mono.Terminal.IMainLoopDriver { - static bool sync; + static bool sync = false; ManualResetEventSlim eventReady = new ManualResetEventSlim (false); ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); MainLoop mainLoop; diff --git a/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs b/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs index 09882dbfd..92c0bbca2 100644 --- a/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs +++ b/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs @@ -37,7 +37,11 @@ namespace Mono.Terminal.Internal { const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS"; static bool IsWindows, IsLinux, IsMacOS; static bool Is64Bit; +#if GUICS + static bool IsMono; +#else static bool IsMono, IsUnity, IsXamarinIOS, IsXamarinAndroid, IsXamarin; +#endif static bool IsNetCore; public static bool IsMacOSPlatform => IsMacOS; @@ -75,7 +79,7 @@ namespace Mono.Terminal.Internal { IsNetCore = Type.GetType ("System.MathF") != null; } #if GUICS - IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false; + //IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false; #else IsUnity = Type.GetType (UnityEngineApplicationClassName) != null; IsXamarinIOS = Type.GetType (XamarinIOSObjectClassName) != null; diff --git a/Terminal.Gui/MonoCurses/binding.cs b/Terminal.Gui/MonoCurses/binding.cs index bd9167801..de1ca1f7e 100644 --- a/Terminal.Gui/MonoCurses/binding.cs +++ b/Terminal.Gui/MonoCurses/binding.cs @@ -47,6 +47,7 @@ using System.Runtime.InteropServices; using Mono.Terminal.Internal; namespace Unix.Terminal { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public partial class Curses { [StructLayout (LayoutKind.Sequential)] @@ -61,7 +62,7 @@ namespace Unix.Terminal { static IntPtr curses_handle, curscr_ptr, lines_ptr, cols_ptr; // If true, uses the DllImport into "ncurses", otherwise "libncursesw.so.5" - static bool use_naked_driver; + //static bool use_naked_driver; static UnmanagedLibrary curses_library; static NativeMethods methods; @@ -451,4 +452,5 @@ namespace Unix.Terminal { mousemask = lib.GetNativeMethodDelegate ("mousemask"); } } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/Terminal.Gui/MonoCurses/constants.cs b/Terminal.Gui/MonoCurses/constants.cs index c2da74157..df19c30e2 100644 --- a/Terminal.Gui/MonoCurses/constants.cs +++ b/Terminal.Gui/MonoCurses/constants.cs @@ -5,6 +5,7 @@ using System; namespace Unix.Terminal { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public partial class Curses { public const int A_NORMAL = unchecked((int)0x0); public const int A_STANDOUT = unchecked((int)0x10000); @@ -139,4 +140,5 @@ namespace Unix.Terminal { return 0 + n * 256; } } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/Terminal.Gui/MonoCurses/handles.cs b/Terminal.Gui/MonoCurses/handles.cs index 85a1b6ac2..e81528ade 100644 --- a/Terminal.Gui/MonoCurses/handles.cs +++ b/Terminal.Gui/MonoCurses/handles.cs @@ -31,6 +31,7 @@ using System.Runtime.InteropServices; namespace Unix.Terminal { public partial class Curses { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public class Window { public readonly IntPtr Handle; static Window curscr; @@ -167,7 +168,8 @@ namespace Unix.Terminal { Handle = handle; } } - + +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } } diff --git a/Terminal.Gui/MonoCurses/mainloop.cs b/Terminal.Gui/MonoCurses/mainloop.cs index 21bd1571a..5a9c49360 100644 --- a/Terminal.Gui/MonoCurses/mainloop.cs +++ b/Terminal.Gui/MonoCurses/mainloop.cs @@ -53,6 +53,10 @@ namespace Mono.Terminal { /// true, if there were pending events, false otherwise. /// If set to true wait until an event is available, otherwise return immediately. bool EventsPending (bool wait); + + /// + /// The interation function. + /// void MainIteration (); } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 39788ea78..8fd4e2a2c 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -153,6 +153,7 @@ namespace Terminal.Gui { Text = text; } + /// public override void Redraw (Rect region) { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); @@ -166,6 +167,7 @@ namespace Terminal.Gui { } } + /// public override void PositionCursor () { Move (hot_pos == -1 ? 1 : hot_pos, 0); @@ -181,6 +183,7 @@ namespace Terminal.Gui { return false; } + /// public override bool ProcessHotKey (KeyEvent kb) { if (kb.IsAlt) @@ -189,6 +192,7 @@ namespace Terminal.Gui { return false; } + /// public override bool ProcessColdKey (KeyEvent kb) { if (IsDefault && kb.KeyValue == '\n') { @@ -199,6 +203,7 @@ namespace Terminal.Gui { return CheckKey (kb); } + /// public override bool ProcessKey (KeyEvent kb) { var c = kb.KeyValue; @@ -210,6 +215,7 @@ namespace Terminal.Gui { return base.ProcessKey (kb); } + /// public override bool MouseEvent(MouseEvent me) { if (me.Flags == MouseFlags.Button1Clicked) { diff --git a/Terminal.Gui/Views/Checkbox.cs b/Terminal.Gui/Views/Checkbox.cs index 5ae8bb428..06cb26ab3 100644 --- a/Terminal.Gui/Views/Checkbox.cs +++ b/Terminal.Gui/Views/Checkbox.cs @@ -99,6 +99,7 @@ namespace Terminal.Gui { } } + /// public override void Redraw (Rect region) { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); @@ -113,11 +114,13 @@ namespace Terminal.Gui { } } + /// public override void PositionCursor () { Move (1, 0); } + /// public override bool ProcessKey (KeyEvent kb) { if (kb.KeyValue == ' ') { @@ -132,6 +135,7 @@ namespace Terminal.Gui { return base.ProcessKey (kb); } + /// public override bool MouseEvent (MouseEvent me) { if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) diff --git a/Terminal.Gui/Views/Clipboard.cs b/Terminal.Gui/Views/Clipboard.cs index eb1860f10..e0615da01 100644 --- a/Terminal.Gui/Views/Clipboard.cs +++ b/Terminal.Gui/Views/Clipboard.cs @@ -2,7 +2,13 @@ using NStack; namespace Terminal.Gui { + /// + /// + /// public static class Clipboard { + /// + /// + /// public static ustring Contents { get; set; } } } diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index 313179268..63aea9470 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -98,6 +98,7 @@ namespace Terminal.Gui { const int bsize = 4; int bytesPerLine; + /// public override Rect Frame { get => base.Frame; set { @@ -128,6 +129,7 @@ namespace Terminal.Gui { return buffer [offset]; } + /// public override void Redraw (Rect region) { Attribute currentAttribute; @@ -280,6 +282,7 @@ namespace Terminal.Gui { RedisplayLine (position); } + /// public override bool ProcessKey (KeyEvent keyEvent) { switch (keyEvent.Key) { diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index f87d941ee..29808e781 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -160,6 +160,7 @@ namespace Terminal.Gui { lineResult.Add(ClipAndJustify(textStr[lp, textLen], width, talign)); } + /// public override void Redraw (Rect region) { if (recalcPending) diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 02deb08ad..9cb5bfe16 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -329,6 +329,10 @@ namespace Terminal.Gui { return base.ProcessKey (kb); } + /// + /// + /// + /// public virtual bool AllowsAll () { if (!allowsMarking) @@ -344,6 +348,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MarkUnmarkRow(){ if (AllowsAll ()) { Source.SetMark(SelectedItem, !Source.IsMarked(SelectedItem)); @@ -354,6 +362,10 @@ namespace Terminal.Gui { return false; } + /// + /// + /// + /// public virtual bool MovePageUp(){ int n = (selected - Frame.Height); if (n < 0) @@ -369,6 +381,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MovePageDown(){ var n = (selected + Frame.Height); if (n > source.Count) @@ -387,6 +403,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MoveDown(){ if (selected + 1 < source.Count){ selected++; @@ -400,6 +420,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MoveUp(){ if (selected > 0){ selected--; @@ -424,6 +448,7 @@ namespace Terminal.Gui { Move (0, selected - top); } + /// public override bool MouseEvent(MouseEvent me) { if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) @@ -460,6 +485,10 @@ namespace Terminal.Gui { BitArray marks; int count; + /// + /// constructor + /// + /// public ListWrapper (IList source) { count = source.Count; @@ -467,6 +496,9 @@ namespace Terminal.Gui { this.src = source; } + /// + /// Count of items. + /// public int Count => src.Count; void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width) @@ -487,6 +519,16 @@ namespace Terminal.Gui { } } + /// + /// Renders an item in the the list. + /// + /// + /// + /// + /// + /// + /// + /// public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width) { container.Move (col, line); @@ -499,6 +541,11 @@ namespace Terminal.Gui { RenderUstr (driver, t.ToString (), col, line, width); } + /// + /// Returns true of the item is marked. false if not. + /// + /// + /// public bool IsMarked (int item) { if (item >= 0 && item < count) @@ -506,6 +553,11 @@ namespace Terminal.Gui { return false; } + /// + /// Sets the marked state of an item. + /// + /// + /// public void SetMark (int item, bool value) { if (item >= 0 && item < count) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 47f736676..73e0c7ade 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -21,6 +21,9 @@ namespace Terminal.Gui { /// public class MenuItem { + /// + /// constructor + /// public MenuItem () { Title = ""; @@ -207,10 +210,10 @@ namespace Terminal.Gui { return len; } - /// - /// Gets or sets the title to display. - /// - /// The title. + ///// + ///// Gets or sets the title to display. + ///// + ///// The title. //public ustring Title { get; set; } /// @@ -559,6 +562,8 @@ namespace Terminal.Gui { } bool openedByAltKey; + + /// public override bool KeyDown (KeyEvent keyEvent) { if (keyEvent.IsAlt) { @@ -615,6 +620,7 @@ namespace Terminal.Gui { return false; } + /// public override void Redraw (Rect region) { Move (0, 0); @@ -645,6 +651,7 @@ namespace Terminal.Gui { PositionCursor (); } + /// public override void PositionCursor () { int pos = 0; @@ -672,8 +679,16 @@ namespace Terminal.Gui { action = item.Action; } + /// + /// Raised as a menu is opened. + /// public event EventHandler OnOpenMenu; + + /// + /// Raised when a menu is closing. + /// public event EventHandler OnCloseMenu; + internal Menu openMenu; Menu openCurrentMenu; internal List openSubMenu; @@ -681,7 +696,12 @@ namespace Terminal.Gui { internal bool isMenuOpening; internal bool isMenuClosing; internal bool isMenuClosed; - public bool MenuOpen; + + /// + /// True of the menu is open; otherwise false. + /// + public bool MenuOpen { get; set; } + View lastFocused; /// @@ -940,7 +960,7 @@ namespace Terminal.Gui { bool openedByHotKey; internal bool FindAndOpenMenuByHotkey (KeyEvent kb) { - int pos = 0; + //int pos = 0; var c = ((uint)kb.Key & (uint)Key.CharMask); for (int i = 0; i < Menus.Length; i++) { // TODO: this code is duplicated, hotkey should be part of the MenuBarItem @@ -969,6 +989,7 @@ namespace Terminal.Gui { } } + /// public override bool ProcessHotKey (KeyEvent kb) { if (kb.Key == Key.F9) { @@ -993,6 +1014,7 @@ namespace Terminal.Gui { return base.ProcessHotKey (kb); } + /// public override bool ProcessKey (KeyEvent kb) { switch (kb.Key) { @@ -1047,6 +1069,7 @@ namespace Terminal.Gui { return true; } + /// public override bool MouseEvent (MouseEvent me) { if (!handled && !HandleGrabView (me, this)) { diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index d9c23ea87..b10e567b2 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -189,6 +189,7 @@ namespace Terminal.Gui { } } + /// public override bool MouseEvent(MouseEvent me) { if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && @@ -249,6 +250,10 @@ namespace Terminal.Gui { View contentView; ScrollBarView vertical, horizontal; + /// + /// Constructs a ScrollView + /// + /// public ScrollView (Rect frame) : base (frame) { contentView = new View (frame); @@ -363,7 +368,7 @@ namespace Terminal.Gui { /// /// This event is raised when the contents have scrolled /// - public event Action Scrolled; + //public event Action Scrolled; public override void Redraw(Rect region) { @@ -383,6 +388,7 @@ namespace Terminal.Gui { } } + /// public override void PositionCursor() { if (InternalSubviews.Count == 0) @@ -448,6 +454,7 @@ namespace Terminal.Gui { return true; } + /// public override bool ProcessKey(KeyEvent kb) { if (base.ProcessKey (kb)) diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index fe1573a91..1bb5f2b33 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -85,8 +85,14 @@ namespace Terminal.Gui { public StatusBarStyle Style { get; set; } = StatusBarStyle.Default; #endif + /// + /// The parent view of the StatusBar. + /// public View Parent { get; set; } + /// + /// The items that compose the StatusBar + /// public StatusItem [] Items { get; set; } /// @@ -132,6 +138,7 @@ namespace Terminal.Gui { return result; } + /// public override void Redraw (Rect region) { if (Frame.Y != Driver.Rows - 1) { @@ -161,6 +168,7 @@ namespace Terminal.Gui { } } + /// public override bool ProcessHotKey (KeyEvent kb) { foreach (var item in Items) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 8d239e86c..0d6f35a5c 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -266,6 +266,9 @@ namespace Terminal.Gui { bool selecting; //bool used; + /// + /// Raised when the Text of the TextView changes. + /// public event EventHandler TextChanged; #if false @@ -535,6 +538,7 @@ namespace Terminal.Gui { PositionCursor (); } + /// public override bool CanFocus { get => true; set { base.CanFocus = value; } @@ -682,6 +686,7 @@ namespace Terminal.Gui { bool lastWasKill; + /// public override bool ProcessKey (KeyEvent kb) { int restCount; @@ -1139,6 +1144,7 @@ namespace Terminal.Gui { return null; } + /// public override bool MouseEvent (MouseEvent ev) { if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) { From 921a6a617041eb8d1b623700fabc789fbbae6b6d Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 20 May 2020 11:27:48 -0600 Subject: [PATCH 11/33] Add Toplevel.Ready event (#446) * add Ready event to Toplevel --- Terminal.Gui/Core.cs | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 4da96a6e3..08fa9a53a 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -1469,10 +1469,25 @@ namespace Terminal.Gui { /// public class Toplevel : View { /// - /// This flag is checked on each iteration of the mainloop and it continues - /// running until this flag is set to false. + /// Gets or sets whether the for this is running or not. Setting + /// this property to false will cause the MainLoop to exit. /// - public bool Running; + public bool Running { get; set; } + + /// + /// Fired once the Toplevel's has started it's first iteration. + /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. + /// changes. A Ready event handler is a good place to finalize initialization after calling `(topLevel)`. + /// + public event EventHandler Ready; + + /// + /// Called from Application.RunLoop after the has entered it's first iteration of the loop. + /// + internal virtual void OnReady () + { + Ready?.Invoke (this, EventArgs.Empty); + } /// /// Initializes a new instance of the class with the specified absolute layout. @@ -2419,8 +2434,15 @@ namespace Terminal.Gui { if (state.Toplevel == null) throw new ObjectDisposedException ("state"); + bool firstIteration = true; for (state.Toplevel.Running = true; state.Toplevel.Running;) { if (MainLoop.EventsPending (wait)) { + // Notify Toplevel it's ready + if (firstIteration) { + state.Toplevel.OnReady (); + } + firstIteration = false; + MainLoop.MainIteration (); Iteration?.Invoke (null, EventArgs.Empty); } else if (wait == false) From e3acbf2d9068f72de9c302c84157293c42f484b9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 May 2020 18:32:18 +0100 Subject: [PATCH 12/33] Updates screen on Unix window resizing. (#419) * Updates screen on Unix window resizing. --- Terminal.Gui/Drivers/CursesDriver.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs index 8ec7eda9c..0edc7e23c 100644 --- a/Terminal.Gui/Drivers/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver.cs @@ -67,8 +67,13 @@ namespace Terminal.Gui { AddRune (rune); } - public override void Refresh () => Curses.refresh (); - public override void UpdateCursor () => Curses.refresh (); + public override void Refresh () { + Curses.refresh (); + if (Curses.CheckWinChange ()) { + TerminalResized?.Invoke (); + } + } + public override void UpdateCursor () => Refresh (); public override void End () => Curses.endwin (); public override void UpdateScreen () => window.redrawwin (); public override void SetAttribute (Attribute c) => Curses.attrset (c.value); From 88d6047c74cf710ce5cced7eea122a710454d0f9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 May 2020 18:36:27 +0100 Subject: [PATCH 13/33] Prevents mouse all events, which perform any of the mouse events, to be invoked if mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0. (#453) * Prevents mouse all events, which perform any of the mouse events, to be invoked if mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0. * Prevents another View under the menu from being triggered after the button is pressed in the menu. --- Terminal.Gui/Drivers/WindowsDriver.cs | 2 ++ Terminal.Gui/Views/Menu.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 9625dbc7d..8de136777 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -807,6 +807,8 @@ namespace Terminal.Gui { } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) { mouseFlag = MouseFlags.ReportMousePosition; + } else if (mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0) { + mouseFlag = 0; } mouseFlag = SetControlKeyStates (mouseEvent, mouseFlag); diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 73e0c7ade..56eecf66c 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -458,7 +458,7 @@ namespace Terminal.Gui { } host.handled = false; bool disabled; - if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) { + if (me.Flags == MouseFlags.Button1Clicked) { disabled = false; if (me.Y < 1) return true; From 658e0239040073de43f2f63157deb906ca1e9d9f Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 20 May 2020 18:36:41 +0100 Subject: [PATCH 14/33] Forces conversion to long date format even if CultureInfo.CurrentCulture doesn't have it. (#431) --- Terminal.Gui/Views/DateField.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 1f1915a10..0c82ea499 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -41,7 +41,7 @@ namespace Terminal.Gui { { CultureInfo cultureInfo = CultureInfo.CurrentCulture; sepChar = cultureInfo.DateTimeFormat.DateSeparator; - longFormat = $" {cultureInfo.DateTimeFormat.ShortDatePattern}"; + longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern); shortFormat = GetShortFormat(longFormat); this.isShort = isShort; CursorPosition = 1; @@ -55,7 +55,21 @@ namespace Terminal.Gui { Text = e; } - string GetShortFormat(string lf) + string GetLongFormat (string lf) + { + ustring [] frm = ustring.Make (lf).Split (ustring.Make (sepChar)); + for (int i = 0; i < frm.Length; i++) { + if (frm [i].Contains ("M") && frm [i].Length < 2) + lf = lf.Replace ("M", "MM"); + if (frm [i].Contains ("d") && frm [i].Length < 2) + lf = lf.Replace ("d", "dd"); + if (frm [i].Contains ("y") && frm [i].Length < 4) + lf = lf.Replace ("yy", "yyyy"); + } + return $" {lf}"; + } + + string GetShortFormat (string lf) { return lf.Replace("yyyy", "yy"); } From 5b845307a2fa2a44bfc32fbd0e4d9a085dff2857 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Wed, 20 May 2020 11:37:12 -0600 Subject: [PATCH 15/33] UI catalog (#387) * key down/up support * line endings? * line endings * KeyDown/Up support * line endings * line endings * Revert "Drop NuGet restore" This reverts commit 5c7a0d05f077755943ec66e6a82db890a24cd056. * Revert "Revert "Drop NuGet restore"" This reverts commit 2dc5fce8654ffeb6f3e570b0bdefcc6a5b6a6d2b. * updated demo * defined styles * Smarter StatusBar bottom tracking. * Prepping for https://github.com/migueldeicaza/gui.cs/issues/376 * Oops. * Fixed StatusBar 'snap to bottom' * line endings * Revert "Fixed StatusBar 'snap to bottom'" This reverts commit 9a91c957e2ed40f5b36c301eda3d107366aebb3d. * started UICatalog project * Initial working POC. * Fix newlines * merge * textalignment demo tweaks * textalignment demo tweaks * Unicode Menu Scenario * not sure why this keeps changing * re-added project to .sln file * re-enabled status bar * moved scenarios to dir * building a dim and pos demo * terminal.sln * progress...barely * fixed exit * progress with some underlying fixes to Label * added readme * fixes build issue * launch * made default colors readable on Windows * major UI Catalog upgrade * added more demos and updated readme * refactored and added more tests * added ref to Issue #437 * added OnKeyUp support to Curses and Net drivers * more tweaks - grab PR #438 first * Added a OpenSelectedItem event to the ListView #429 * updates * moved KeyUpHandler out of special ESC stuff * more tweaks & improvements * testing top window bug * supported OpenSelectedItem * lots of updates * fixed regression, fixed #444 * better button scenario * tweaks * add Ready event to Toplevel * dotfx .gitignroe * ready for ready * updated colors based on feedback; consolodated config code * tweaked readme * readme * Added Editor demonstrating TextView * Added Editor demonstrating TextView * added hexeditor scenario Co-authored-by: Miguel de Icaza Co-authored-by: BDisp --- Designer/Designer.csproj | 1 + Example/Example.csproj | 1 + Example/demo.cs.orig | 611 +++++++++++++++++++++ Terminal.Gui/Core.cs | 4 +- Terminal.Gui/Drivers/CursesDriver.cs | 38 +- Terminal.Gui/Drivers/NetDriver.cs | 1 + Terminal.Gui/Drivers/WindowsDriver.cs | 121 ++-- Terminal.Gui/Event.cs | 33 +- Terminal.Gui/Views/Label.cs | 7 +- Terminal.Gui/Views/ListView.cs | 17 +- Terminal.sln | 10 + UICatalog/.editorconfig | 23 + UICatalog/.gitignore | 9 + UICatalog/Program.cs | 271 +++++++++ UICatalog/Properties/launchSettings.json | 8 + UICatalog/README.md | 122 ++++ UICatalog/Scenario.cs | 181 ++++++ UICatalog/Scenarios/Buttons.cs | 105 ++++ UICatalog/Scenarios/DimAndPosLayout.cs | 82 +++ UICatalog/Scenarios/Editor.cs | 150 +++++ UICatalog/Scenarios/Generic.cs | 17 + UICatalog/Scenarios/HexEditor.cs | 154 ++++++ UICatalog/Scenarios/Keys.cs | 181 ++++++ UICatalog/Scenarios/MessageBoxes.cs | 42 ++ UICatalog/Scenarios/Mouse.cs | 34 ++ UICatalog/Scenarios/TextAlignment.cs | 26 + UICatalog/Scenarios/TopLevelNoWindowBug.cs | 40 ++ UICatalog/Scenarios/UnicodeInMenu.cs | 35 ++ UICatalog/UICatalog.csproj | 13 + UICatalog/generic_screenshot.png | Bin 0 -> 22113 bytes UICatalog/screenshot.png | Bin 0 -> 26223 bytes 31 files changed, 2259 insertions(+), 78 deletions(-) create mode 100644 Example/demo.cs.orig create mode 100644 UICatalog/.editorconfig create mode 100644 UICatalog/.gitignore create mode 100644 UICatalog/Program.cs create mode 100644 UICatalog/Properties/launchSettings.json create mode 100644 UICatalog/README.md create mode 100644 UICatalog/Scenario.cs create mode 100644 UICatalog/Scenarios/Buttons.cs create mode 100644 UICatalog/Scenarios/DimAndPosLayout.cs create mode 100644 UICatalog/Scenarios/Editor.cs create mode 100644 UICatalog/Scenarios/Generic.cs create mode 100644 UICatalog/Scenarios/HexEditor.cs create mode 100644 UICatalog/Scenarios/Keys.cs create mode 100644 UICatalog/Scenarios/MessageBoxes.cs create mode 100644 UICatalog/Scenarios/Mouse.cs create mode 100644 UICatalog/Scenarios/TextAlignment.cs create mode 100644 UICatalog/Scenarios/TopLevelNoWindowBug.cs create mode 100644 UICatalog/Scenarios/UnicodeInMenu.cs create mode 100644 UICatalog/UICatalog.csproj create mode 100644 UICatalog/generic_screenshot.png create mode 100644 UICatalog/screenshot.png diff --git a/Designer/Designer.csproj b/Designer/Designer.csproj index 545c2b0b9..84de5ca1d 100644 --- a/Designer/Designer.csproj +++ b/Designer/Designer.csproj @@ -8,6 +8,7 @@ Designer Designer v4.7.2 + win-x86 diff --git a/Example/Example.csproj b/Example/Example.csproj index dc751b396..001459c47 100644 --- a/Example/Example.csproj +++ b/Example/Example.csproj @@ -8,6 +8,7 @@ Terminal Terminal v4.7.2 + win-x64 diff --git a/Example/demo.cs.orig b/Example/demo.cs.orig new file mode 100644 index 000000000..f3363488b --- /dev/null +++ b/Example/demo.cs.orig @@ -0,0 +1,611 @@ +using Terminal.Gui; +using System; +using Mono.Terminal; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Reflection; +using NStack; +using System.Text; + +static class Demo { + //class Box10x : View, IScrollView { + 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 region) + { + //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 { + 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++) { + Rune r; + switch (x % 3) { + case 0: + Driver.AddRune (y.ToString ().ToCharArray (0, 1) [0]); + if (y > 9) + Driver.AddRune (y.ToString ().ToCharArray (1, 1) [0]); + r = '.'; + break; + case 1: + r = 'o'; + break; + default: + r = 'O'; + break; + } + Driver.AddRune (r); + } + } + } + } + + static void ShowTextAlignments () + { +<<<<<<< HEAD + var container = new Window ($"Show Text Alignments") { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + container.OnKeyUp += (KeyEvent ke) => { + if (ke.Key == Key.Esc) + container.Running = false; + }; +======= + var container = new Dialog ( + "Text Alignments", 70, 20, + new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } }, + new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } }); + +>>>>>>> cb40c5c2491a559658481d20dd4b6a3343c0183f + + int i = 0; + string txt = "Hello world, how are you doing today?"; + container.Add ( +<<<<<<< HEAD + 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 () } +======= + new Label (new Rect (0, 1, 50, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left }, + new Label (new Rect (0, 3, 50, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right }, + new Label (new Rect (0, 5, 50, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered }, + new Label (new Rect (0, 7, 50, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified } +>>>>>>> cb40c5c2491a559658481d20dd4b6a3343c0183f + ); + + 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 + scrollView.Add (new Filler (new Rect (0, 0, 40, 40))); +#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"); + // Add some content + container.Add ( + login, + loginText, + password, + passText, + new FrameView (new Rect (3, 10, 25, 6), "Options"){ + new CheckBox (1, 0, "Remember me"), + new RadioGroup (1, 2, new [] { "_Personal", "_Company" }), + }, + 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), + new TimeField (23, 20, DateTime.Now, 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) to activate the menubar"), + menuKeysStyle, + menuAutoMouseNav + + ); + container.SendSubviewToBack (tf); + } + + public static Label ml2; + + static void NewFile () + { + var d = new Dialog ( + "New File", 50, 20, + new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } }, + new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } }); + ml2 = new Label (1, 1, "Mouse Debug Line"); + d.Add (ml2); + Application.Run (d); + } + + // + // Creates a nested editor + static void Editor (Toplevel top) + { + var tframe = top.Frame; + var ntop = new Toplevel (tframe); + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Close", "", () => {Application.RequestStop ();}), + }), + new MenuBarItem ("_Edit", new MenuItem [] { + new MenuItem ("_Copy", "", null), + new MenuItem ("C_ut", "", null), + new MenuItem ("_Paste", "", null) + }), + }); + ntop.Add (menu); + + string fname = null; + foreach (var s in new [] { "/etc/passwd", "c:\\windows\\win.ini" }) + if (System.IO.File.Exists (s)) { + fname = s; + break; + } + + var win = new Window (fname ?? "Untitled") { + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + ntop.Add (win); + + var text = new TextView (new Rect (0, 0, tframe.Width - 2, tframe.Height - 3)); + + if (fname != null) + text.Text = System.IO.File.ReadAllText (fname); + win.Add (text); + + Application.Run (ntop); + } + + 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", string.Join (", ", d.FilePaths), "Ok"); + } + + public static void ShowHex (Toplevel top) + { + var tframe = top.Frame; + var ntop = new Toplevel (tframe); + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Close", "", () => {Application.RequestStop ();}), + }), + }); + ntop.Add (menu); + + var win = new Window ("/etc/passwd") { + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + ntop.Add (win); + + var source = System.IO.File.OpenRead ("/etc/passwd"); + 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 (object sender, EventArgs e) + { + menu.UseKeysUpDownAsKeysLeftRight = menuKeysStyle.Checked; + } + + static void MenuAutoMouseNav_Toggled (object sender, EventArgs e) + { + menu.WantMousePositionReports = menuAutoMouseNav.Checked; + } + + + static void Copy () + { + TextField textField = menu.LastFocused as TextField; + if (textField != null && textField.SelectedLength != 0) { + textField.Copy (); + } + } + + static void Cut () + { + TextField textField = menu.LastFocused as TextField; + if (textField != null && textField.SelectedLength != 0) { + textField.Cut (); + } + } + + static void Paste () + { + TextField textField = menu.LastFocused as TextField; + if (textField != null) { + textField.Paste (); + } + } + + static void Help () + { + MessageBox.Query (50, 7, "Help", "This is a small help\nBe kind.", "Ok"); + } + + #region Selection Demo + + static void ListSelectionDemo (bool multiple) + { + var d = new Dialog ("Selection Demo", 60, 20, + new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } }, + new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } }); + + 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"); + } + #endregion + + + #region OnKeyDown / OnKeyUp Demo + private static void OnKeyDownUpDemo () + { + var container = new Dialog ( + "OnKeyDown & OnKeyUp demo", 80, 20, + new Button ("Close") { Clicked = () => { Application.RequestStop (); } }) { + 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 KeyUpDown (KeyEvent keyEvent, string updown) + { + if ((keyEvent.Key & Key.CtrlMask) != 0) { + list.Add ($"Key{updown,-4}: Ctrl "); + } else if ((keyEvent.Key & Key.AltMask) != 0) { + list.Add ($"Key{updown,-4}: Alt "); + } else { + list.Add ($"Key{updown,-4}: {(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}"); + } + listView.MoveDown (); + } + + container.OnKeyDown += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Down"); + container.OnKeyUp += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Up"); + Application.Run (container); + } + #endregion + + public static Label ml; + public static MenuBar menu; + public static CheckBox menuKeysStyle; + public static CheckBox menuAutoMouseNav; + static void Main () + { + if (Debugger.IsAttached) + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); + + //Application.UseSystemConsole = true; + + Application.Init (); + + 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 ("_Not 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]); + + menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("Text _Editor Demo", "", () => { Editor (top); }), + new MenuItem ("_New", "Creates new file", NewFile), + new MenuItem ("_Open", "", Open), + new MenuItem ("_Hex", "", () => ShowHex (top)), + new MenuItem ("_Close", "", () => Close ()), + new MenuItem ("_Disabled", "", () => { }, () => false), + null, + new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; }) + }), + new MenuBarItem ("_Edit", new MenuItem [] { + new MenuItem ("_Copy", "", Copy), + new MenuItem ("C_ut", "", Cut), + new MenuItem ("_Paste", "", Paste), + new MenuItem ("_Find and Replace", + new MenuBarItem (new MenuItem[] {menuItems [0], menuItems [1] })), + menuItems[3] + }), + new MenuBarItem ("_List Demos", new MenuItem [] { + new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)), + new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)), + }), + new MenuBarItem ("A_ssorted", new MenuItem [] { + new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()), + new MenuItem ("_OnKeyDown/Up", "", () => OnKeyDownUpDemo ()) + }), + new MenuBarItem ("_Test Menu and SubMenus", new MenuItem [] { + new MenuItem ("SubMenu1Item_1", + new MenuBarItem (new MenuItem[] { + new MenuItem ("SubMenu2Item_1", + new MenuBarItem (new MenuItem [] { + new MenuItem ("SubMenu3Item_1", + new MenuBarItem (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.TextColor = Colors.TopLevel.Normal; + 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 = 24 }; + 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", null), + new StatusItem(Key.F3, "~F3~ Save", null), + new StatusItem(Key.ControlX, "~^X~ Quit", () => { if (Quit ()) top.Running = false; }), + }) { + Parent = null, + }; + + win.Add (drag, dragText); +#if true + // FIXED: This currently causes a stack overflow, because it is referencing a window that has not had its size allocated yet + + 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); + + Application.OnLoad = () => { + bottom.X = win.X; + bottom.Y = Pos.Bottom (win) - Pos.Top (win) - margin; + bottom2.X = Pos.Left (win); + bottom2.Y = Pos.Bottom (win); + }; +#endif + + + top.Add (win); + //top.Add (menu); + top.Add (menu, statusBar); + Application.Run (); + } +} diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 08fa9a53a..4bed3fbf6 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -1066,7 +1066,7 @@ namespace Terminal.Gui { } /// - /// Invoked when a character key is pressed and occurs after the key down event. + /// Invoked when a character key is pressed and occurs after the key up event. /// public Action OnKeyPress; @@ -1083,7 +1083,6 @@ namespace Terminal.Gui { /// public override bool ProcessHotKey (KeyEvent keyEvent) { - OnKeyPress?.Invoke (keyEvent); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1095,7 +1094,6 @@ namespace Terminal.Gui { /// public override bool ProcessColdKey (KeyEvent keyEvent) { - OnKeyPress?.Invoke (keyEvent); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs index 0edc7e23c..95a658108 100644 --- a/Terminal.Gui/Drivers/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver.cs @@ -52,7 +52,7 @@ namespace Terminal.Gui { if (sync) Application.Driver.Refresh (); ccol++; - var runeWidth = Rune.ColumnWidth(rune); + var runeWidth = Rune.ColumnWidth (rune); if (runeWidth > 1) { for (int i = 1; i < runeWidth; i++) { ccol++; @@ -192,7 +192,7 @@ namespace Terminal.Gui { }; } - void ProcessInput (Action keyHandler, Action mouseHandler) + void ProcessInput (Action keyHandler, Action keyUpHandler, Action mouseHandler) { int wch; var code = Curses.get_wch (out wch); @@ -212,6 +212,7 @@ namespace Terminal.Gui { return; } keyHandler (new KeyEvent (MapCursesKey (wch))); + keyUpHandler (new KeyEvent (MapCursesKey (wch))); return; } @@ -219,7 +220,7 @@ namespace Terminal.Gui { if (wch == 27) { Curses.timeout (200); - code = Curses.get_wch (out wch); + code = Curses.get_wch (out int wch2); if (code == Curses.KEY_CODE_YES) keyHandler (new KeyEvent (Key.AltMask | MapCursesKey (wch))); if (code == 0) { @@ -227,23 +228,28 @@ namespace Terminal.Gui { // The ESC-number handling, debatable. // Simulates the AltMask itself by pressing Alt + Space. - if (wch == (int)Key.Space) + if (wch2 == (int)Key.Space) key = new KeyEvent (Key.AltMask); - else if (wch - (int)Key.Space >= 'A' && wch - (int)Key.Space <= 'Z') - key = new KeyEvent ((Key)((uint)Key.AltMask + (wch - (int)Key.Space))); - else if (wch >= '1' && wch <= '9') - key = new KeyEvent ((Key)((int)Key.F1 + (wch - '0' - 1))); - else if (wch == '0') + else if (wch2 - (int)Key.Space >= 'A' && wch2 - (int)Key.Space <= 'Z') + key = new KeyEvent ((Key)((uint)Key.AltMask + (wch2 - (int)Key.Space))); + else if (wch2 >= '1' && wch <= '9') + key = new KeyEvent ((Key)((int)Key.F1 + (wch2 - '0' - 1))); + else if (wch2 == '0') key = new KeyEvent (Key.F10); - else if (wch == 27) - key = new KeyEvent ((Key)wch); + else if (wch2 == 27) + key = new KeyEvent ((Key)wch2); else - key = new KeyEvent (Key.AltMask | (Key)wch); + key = new KeyEvent (Key.AltMask | (Key)wch2); keyHandler (key); - } else + } else { keyHandler (new KeyEvent (Key.Esc)); - } else + } + } else { keyHandler (new KeyEvent ((Key)wch)); + } + // Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above + // will not impact KeyUp. + keyUpHandler (new KeyEvent ((Key)wch)); } public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) @@ -252,7 +258,7 @@ namespace Terminal.Gui { Curses.timeout (-1); (mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => { - ProcessInput (keyHandler, mouseHandler); + ProcessInput (keyHandler, keyUpHandler, mouseHandler); return true; }); @@ -314,7 +320,7 @@ namespace Terminal.Gui { Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK); Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN); Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN); - Colors.Menu.Disabled = MakeColor(Curses.COLOR_WHITE, Curses.COLOR_CYAN); + Colors.Menu.Disabled = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN); Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE); Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN); diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs index 99cef7ac5..3b7fa188d 100644 --- a/Terminal.Gui/Drivers/NetDriver.cs +++ b/Terminal.Gui/Drivers/NetDriver.cs @@ -328,6 +328,7 @@ namespace Terminal.Gui { if (map == (Key)0xffffffff) return; keyHandler (new KeyEvent (map)); + keyUpHandler (new KeyEvent (map)); }; } diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 8de136777..343182dc0 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -440,15 +440,10 @@ namespace Terminal.Gui { public WindowsDriver () { - Colors.TopLevel = new ColorScheme (); - - Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black); - Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); - Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); - Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.DarkCyan); - winConsole = new WindowsConsole (); + SetupColorsAndBorders (); + cols = Console.WindowWidth; rows = Console.WindowHeight; WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); @@ -459,6 +454,54 @@ namespace Terminal.Gui { Task.Run ((Action)WindowsInputHandler); } + private void SetupColorsAndBorders () + { + Colors.TopLevel = new ColorScheme (); + Colors.Base = new ColorScheme (); + Colors.Dialog = new ColorScheme (); + Colors.Menu = new ColorScheme (); + Colors.Error = new ColorScheme (); + + Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black); + Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); + Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); + Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan); + + Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue); + Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); + Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue); + Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); + + Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray); + Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black); + Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray); + Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black); + Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray); + + Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); + Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray); + Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray); + Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray); + + Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White); + Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed); + Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White); + Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed); + + HLine = '\u2500'; + VLine = '\u2502'; + Stipple = '\u2592'; + Diamond = '\u25c6'; + ULCorner = '\u250C'; + LLCorner = '\u2514'; + URCorner = '\u2510'; + LRCorner = '\u2518'; + LeftTee = '\u251c'; + RightTee = '\u2524'; + TopTee = '\u22a4'; + BottomTee = '\u22a5'; + } + [StructLayout (LayoutKind.Sequential)] public struct ConsoleKeyInfoEx { public ConsoleKeyInfo consoleKeyInfo; @@ -564,11 +607,24 @@ namespace Terminal.Gui { case WindowsConsole.EventType.Key: var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent)); if (map == (Key)0xffffffff) { - KeyEvent key = default; + KeyEvent key = new KeyEvent (); + // Shift = VK_SHIFT = 0x10 // Ctrl = VK_CONTROL = 0x11 // Alt = VK_MENU = 0x12 + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.CapslockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.CapslockOn; + } + + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ScrolllockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.ScrolllockOn; + } + + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.NumlockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.NumlockOn; + } + switch (inputEvent.KeyEvent.dwControlKeyState) { case WindowsConsole.ControlKeyState.RightAltPressed: case WindowsConsole.ControlKeyState.RightAltPressed | @@ -617,10 +673,10 @@ namespace Terminal.Gui { keyUpHandler (key); } else { if (inputEvent.KeyEvent.bKeyDown) { - // Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event keyDownHandler (new KeyEvent (map)); - keyHandler (new KeyEvent (map)); } else { + // Key Up - Fire KeyDown Event and KeyStroke (ProcessKey) Event + keyHandler (new KeyEvent (map)); keyUpHandler (new KeyEvent (map)); } } @@ -918,6 +974,9 @@ namespace Terminal.Gui { case ConsoleKey.OemComma: case ConsoleKey.OemPlus: case ConsoleKey.OemMinus: + if (keyInfo.KeyChar == 0) + return Key.Unknown; + return (Key)((uint)keyInfo.KeyChar); } @@ -971,48 +1030,10 @@ namespace Terminal.Gui { public override void Init (Action terminalResized) { TerminalResized = terminalResized; - - Colors.Base = new ColorScheme (); - Colors.Dialog = new ColorScheme (); - Colors.Menu = new ColorScheme (); - Colors.Error = new ColorScheme (); - - HLine = '\u2500'; - VLine = '\u2502'; - Stipple = '\u2592'; - Diamond = '\u25c6'; - ULCorner = '\u250C'; - LLCorner = '\u2514'; - URCorner = '\u2510'; - LRCorner = '\u2518'; - LeftTee = '\u251c'; - RightTee = '\u2524'; - TopTee = '\u22a4'; - BottomTee = '\u22a5'; - - Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue); - Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan); - Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue); - Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan); - - Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan); - Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black); - Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan); - Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black); - Colors.Menu.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Cyan); - - Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); - Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan); - Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); - Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan); - - Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red); - Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); - Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red); - Colors.Error.HotFocus = Colors.Error.HotNormal; - Console.Clear (); + SetupColorsAndBorders (); } + void ResizeScreen () { OutputBuffer = new WindowsConsole.CharInfo [Rows * Cols]; diff --git a/Terminal.Gui/Event.cs b/Terminal.Gui/Event.cs index c4e9b9e1a..169b60084 100644 --- a/Terminal.Gui/Event.cs +++ b/Terminal.Gui/Event.cs @@ -45,7 +45,7 @@ namespace Terminal.Gui { ControlSpace = 0, /// - /// The key code for the user pressing Control-A + /// The key code for the user pressing Control-A /// ControlA = 1, /// @@ -288,8 +288,7 @@ namespace Terminal.Gui { /// /// Describes a keyboard event. /// - public struct KeyEvent { - + public class KeyEvent { /// /// Symb olid definition for the key. /// @@ -321,6 +320,10 @@ namespace Terminal.Gui { //public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26); public bool IsCtrl => (Key & Key.CtrlMask) != 0; + public KeyEvent () + { + Key = Key.Unknown; + } /// /// Constructs a new KeyEvent from the provided Key value - can be a rune cast into a Key value /// @@ -328,6 +331,28 @@ namespace Terminal.Gui { { Key = k; } + + public override string ToString () + { + string msg = ""; + var key = this.Key; + if ((this.Key & Key.ShiftMask) != 0) { + msg += "Shift-"; + } + if ((this.Key & Key.CtrlMask) != 0) { + msg += "Ctrl-"; + } + if ((this.Key & Key.AltMask) != 0) { + msg += "Alt-"; + } + + if (string.IsNullOrEmpty (msg)) { + msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"{key}")}"; + } else { + msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"")}"; + } + return msg; + } } /// @@ -486,7 +511,7 @@ namespace Terminal.Gui { /// Returns a that represents the current . /// /// A that represents the current . - public override string ToString() + public override string ToString () { return $"({X},{Y}:{Flags}"; } diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 29808e781..b265fccda 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -124,7 +124,7 @@ namespace Terminal.Gui { for (int i = 0; i < spaces; i++) s.Append (' '); if (extras > 0) { - s.Append ('_'); + //s.Append ('_'); extras--; } } @@ -179,8 +179,11 @@ namespace Terminal.Gui { int x; switch (textAlignment) { case TextAlignment.Left: + x = Frame.Left; + break; case TextAlignment.Justified: - x = 0; + Recalc (); + x = Frame.Left; break; case TextAlignment.Right: x = Frame.Right - str.Length; diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 9cb5bfe16..309c408e0 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -294,6 +294,11 @@ namespace Terminal.Gui { /// public event Action SelectedChanged; + /// + /// This event is raised on Enter key or Double Click to open the selected item. + /// + public event EventHandler OpenSelectedItem; + /// /// Handles cursor movement for this view, passes all other events. /// @@ -325,6 +330,11 @@ namespace Terminal.Gui { return true; else break; + + case Key.Enter: + OpenSelectedItem?.Invoke (this, new EventArgs ()); + break; + } return base.ProcessKey (kb); } @@ -451,7 +461,7 @@ namespace Terminal.Gui { /// public override bool MouseEvent(MouseEvent me) { - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) + if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) return false; if (!HasFocus) @@ -469,9 +479,10 @@ namespace Terminal.Gui { SetNeedsDisplay (); return true; } - if (SelectedChanged != null) - SelectedChanged(); + SelectedChanged?.Invoke (); SetNeedsDisplay (); + if (me.Flags == MouseFlags.Button1DoubleClicked) + OpenSelectedItem?.Invoke (this, new EventArgs ()); return true; } } diff --git a/Terminal.sln b/Terminal.sln index 5bace6618..77680ffd0 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UICatalog", "UICatalog\UICatalog.csproj", "{88979F89-9A42-448F-AE3E-3060145F6375}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -25,6 +27,14 @@ Global {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.Build.0 = Debug|x86 {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.ActiveCfg = Release|x86 {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.Build.0 = Release|x86 + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.ActiveCfg = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.Build.0 = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.ActiveCfg = Release|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 diff --git a/UICatalog/.editorconfig b/UICatalog/.editorconfig new file mode 100644 index 000000000..040e7abd9 --- /dev/null +++ b/UICatalog/.editorconfig @@ -0,0 +1,23 @@ +[*.cs] +indent_style = tab +indent_size = 8 +tab_width = 8 +csharp_new_line_before_open_brace = methods,local_functions +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +end_of_line = crlf + +csharp_indent_case_contents = true +csharp_indent_switch_labels = false +csharp_indent_labels = flush_left +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_preserve_single_line_blocks = true +dotnet_style_require_accessibility_modifiers = never +csharp_style_var_when_type_is_apparent = true +csharp_prefer_braces = false +csharp_space_before_open_square_brackets = true +csharp_space_between_method_call_name_and_opening_parenthesis = true +csharp_space_between_method_declaration_name_and_open_parenthesis = true \ No newline at end of file diff --git a/UICatalog/.gitignore b/UICatalog/.gitignore new file mode 100644 index 000000000..4378419e7 --- /dev/null +++ b/UICatalog/.gitignore @@ -0,0 +1,9 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site diff --git a/UICatalog/Program.cs b/UICatalog/Program.cs new file mode 100644 index 000000000..14a7956db --- /dev/null +++ b/UICatalog/Program.cs @@ -0,0 +1,271 @@ +using NStack; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + /// + /// Main program for the Terminal.gui UI Catalog app. This app provides a chooser that allows + /// for a calalog of UI demos, examples, and tests. + /// + class Program { + private static Toplevel _top; + private static MenuBar _menu; + private static int _nameColumnWidth; + private static Window _leftPane; + private static List _categories; + private static ListView _categoryListView; + private static Window _rightPane; + private static List _scenarios; + private static ListView _scenarioListView; + private static StatusBar _statusBar; + + private static Scenario _runningScenario = null; + + static void Main (string [] args) + { + if (Debugger.IsAttached) + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); + + _scenarios = Scenario.GetDerivedClassesCollection ().ToList (); + + if (args.Length > 0) { + var item = _scenarios.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals (args [0], StringComparison.OrdinalIgnoreCase)); + _runningScenario = (Scenario)Activator.CreateInstance (_scenarios [item]); + Application.Init (); + _runningScenario.Init (Application.Top); + _runningScenario.Setup (); + _runningScenario.Run (); + _runningScenario = null; + return; + } + + Scenario scenario = GetScenarioToRun (); + while (scenario != null) { + Application.Init (); + scenario.Init (Application.Top); + scenario.Setup (); + scenario.Run (); + scenario = GetScenarioToRun (); + } + } + + /// + /// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs. + /// + private static void Setup () + { + _menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Quit", "", () => Application.RequestStop() ) + }), + new MenuBarItem ("_About...", "About this app", () => MessageBox.Query (0, 6, "About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok")), + }); + + _leftPane = new Window ("Categories") { + X = 0, + Y = 1, // for menu + Width = 25, + Height = Dim.Fill (), + CanFocus = false, + }; + + + _categories = Scenario.GetAllCategories (); + _categoryListView = new ListView (_categories) { + X = 1, + Y = 0, + Width = Dim.Fill (0), + Height = Dim.Fill (2), + AllowsMarking = false, + CanFocus = true, + }; + _categoryListView.OpenSelectedItem += (o, a) => { + _top.SetFocus (_rightPane); + }; + _categoryListView.SelectedChanged += CategoryListView_SelectedChanged; + _leftPane.Add (_categoryListView); + + _rightPane = new Window ("Scenarios") { + X = 25, + Y = 1, // for menu + Width = Dim.Fill (), + Height = Dim.Fill (), + CanFocus = false, + + }; + + _nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length; + + _scenarioListView = new ListView () { + X = 0, + Y = 0, + Width = Dim.Fill (0), + Height = Dim.Fill (0), + AllowsMarking = false, + CanFocus = true, + }; + + //_scenarioListView.OnKeyPress += (KeyEvent ke) => { + // if (_top.MostFocused == _scenarioListView && ke.Key == Key.Enter) { + // _scenarioListView_OpenSelectedItem (null, null); + // } + //}; + + _scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem; + _rightPane.Add (_scenarioListView); + + _categoryListView.SelectedItem = 0; + CategoryListView_SelectedChanged (); + + _statusBar = new StatusBar (new StatusItem [] { + //new StatusItem(Key.F1, "~F1~ Help", () => Help()), + new StatusItem(Key.ControlQ, "~CTRL-Q~ Quit", () => { + if (_runningScenario is null){ + // This causes GetScenarioToRun to return null + _runningScenario = null; + Application.RequestStop(); + } else { + _runningScenario.RequestStop(); + } + }), + }); + } + + /// + /// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything. + /// + /// + private static Scenario GetScenarioToRun () + { + Application.Init (); + + if (_menu == null) { + Setup (); + } + + _top = Application.Top; + _top.OnKeyUp += KeyUpHandler; + _top.Add (_menu); + _top.Add (_leftPane); + _top.Add (_rightPane); + _top.Add (_statusBar); + + // HACK: There is no other way to SetFocus before Application.Run. See Issue #445 +#if false + if (_runningScenario != null) + Application.Iteration += Application_Iteration; +#else + _top.Ready += (o, a) => { + if (_runningScenario != null) { + _top.SetFocus (_rightPane); + _runningScenario = null; + } + }; +#endif + + Application.Run (_top); + return _runningScenario; + } + +#if false + private static void Application_Iteration (object sender, EventArgs e) + { + Application.Iteration -= Application_Iteration; + _top.SetFocus (_rightPane); + } +#endif + private static void _scenarioListView_OpenSelectedItem (object sender, EventArgs e) + { + if (_runningScenario is null) { + var source = _scenarioListView.Source as ScenarioListDataSource; + _runningScenario = (Scenario)Activator.CreateInstance (source.Scenarios [_scenarioListView.SelectedItem]); + Application.RequestStop (); + } + } + + internal class ScenarioListDataSource : IListDataSource { + public List Scenarios { get; set; } + + public bool IsMarked (int item) => false;// Scenarios [item].IsMarked; + + public int Count => Scenarios.Count; + + public ScenarioListDataSource (List itemList) => Scenarios = itemList; + + public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width) + { + container.Move (col, line); + // Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible + var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item])); + RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width); + } + + public void SetMark (int item, bool value) + { + } + + // A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461 + private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width) + { + int used = 0; + int index = 0; + while (index < ustr.Length) { + (var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length); + var count = Rune.ColumnWidth (rune); + if (used + count >= width) break; + driver.AddRune (rune); + used += count; + index += size; + } + + while (used < width) { + driver.AddRune (' '); + used++; + } + } + } + + /// + /// When Scenarios are running we need to override the behavior of the Menu + /// and Statusbar to enable Scenarios that use those (or related key input) + /// to not be impacted. Same as for tabs. + /// + /// + private static void KeyUpHandler (KeyEvent ke) + { + if (_runningScenario != null) { + //switch (ke.Key) { + //case Key.Esc: + // //_runningScenario.RequestStop (); + // break; + //case Key.Enter: + // break; + //} + } else if (ke.Key == Key.Tab || ke.Key == Key.BackTab) { + // BUGBUG: Work around Issue #434 by implementing our own TAB navigation + if (_top.MostFocused == _categoryListView) + _top.SetFocus (_rightPane); + else + _top.SetFocus (_leftPane); + } + } + + private static void CategoryListView_SelectedChanged () + { + var item = _categories [_categoryListView.SelectedItem]; + List newlist; + if (item.Equals ("All")) { + newlist = _scenarios; + + } else { + newlist = _scenarios.Where (t => Scenario.ScenarioCategory.GetCategories (t).Contains (item)).ToList (); + } + _scenarioListView.Source = new ScenarioListDataSource (newlist); + _scenarioListView.SelectedItem = 0; + } + } +} diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json new file mode 100644 index 000000000..1d6b7542f --- /dev/null +++ b/UICatalog/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "UICatalog": { + "commandName": "Project", + "commandLineArgs": "HexEditor" + } + } +} \ No newline at end of file diff --git a/UICatalog/README.md b/UICatalog/README.md new file mode 100644 index 000000000..6f6b019f8 --- /dev/null +++ b/UICatalog/README.md @@ -0,0 +1,122 @@ +# Terminal.Gui UI Catalog + +UI Catalog is a comprehensive sample library for Terminal.Gui. It attempts to satisfy the following goals: + +1. Be an easy to use showcase for Terminal.Gui concepts and features. +2. Provide sample code that illustrates how to properly implement said concepts & features. +3. Make it easy for contributors to add additional samples in a structured way. + +![screenshot](screenshot.png) + +## Motivation + +The original `demo.cs` sample app for Terminal.Gui is neither good to showcase, nor does it explain different concepts. In addition, because it is built on a single source file, it has proven to cause friction when multiple contributors are simultaneously working on different aspects of Terminal.Gui. See [Issue #368](https://github.com/migueldeicaza/Terminal.Gui/issues/368) for more background. + +## How To Use + +`Program.cs` is the main app and provides a UI for selecting and running **Scenarios**. Each **Scenario* is implemented as a class derived from `Scenario` and `Program.cs` uses reflection to dynamically build the UI. + +**Scenarios** are tagged with categories using the `[ScenarioCategory]` attribute. The left pane of the main screen lists the categories. Clicking on a category shows all the scenarios in that category. + +**Scenarios** can be run either from the **UICatalog.exe** app UI or by being specified on the command line: + +``` +UICatalog.exe +``` + +e.g. + +``` +UICatalog.exe Buttons +``` + +When a **Scenario** is run, it runs as though it were a standalone `Terminal.Gui` app. However, scaffolding is provided (in the `Scenario` base class) that (optionally) takes care of `Terminal.Gui` initialization. + +## Contributing by Adding Scenarios + +To add a new **Scenario** simply: + +1. Create a new `.cs` file in the `Scenarios` directory that derives from `Scenario`. +2. Add a `[ScenarioMetaData]` attribute to the class specifying the scenario's name and description. +3. Add one or more `[ScenarioCategory]` attributes to the class specifying which categories the sceanrio belongs to. If you don't specify a category the sceanrio will show up in "All". +4. Implement the `Setup` override which will be called when a user selects the scenario to run. +5. Optionally, implement the `Init` and/or `Run` overrides to provide a custom implementation. + +The sample below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named: + +```csharp +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")] + [ScenarioCategory ("Controls")] + class MyScenario : Scenario { + public override void Setup () + { + // Put your scenario code here, e.g. + Win.Add (new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No") + }); + } + } +} +``` + +`Scenario` provides a `Toplevel` and `Window` the provides a canvas for the Scenario to operate. The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key. + +![screenshot](generic_screenshot.png) + +To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply set the `Top` and `Window` properties as appropriate, as seen in the `UnicodeInMenu` scenario: + +```csharp +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")] + [ScenarioCategory ("Text")] + [ScenarioCategory ("Controls")] + class UnicodeInMenu : Scenario { + public override void Setup () + { + Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows)); + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_Файл", new MenuItem [] { + new MenuItem ("_Создать", "Creates new file", null), + new MenuItem ("_Открыть", "", null), + new MenuItem ("Со_хранить", "", null), + new MenuItem ("_Выход", "", () => Application.RequestStop() ) + }), + new MenuBarItem ("_Edit", new MenuItem [] { + new MenuItem ("_Copy", "", null), + new MenuItem ("C_ut", "", null), + new MenuItem ("_Paste", "", null) + }) + }); + Top.Add (menu); + + Win = new Window ($"Scenario: {GetName ()}") { + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + Top.Add (Win); + } + } +} +``` + +For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` assigns `Application.Top` to `Top` and creates `Win`. The `base.Run` simply calls `Application.Run(Top)`. + +## Contribution Guidelines + +- Provide a terse, descriptive name for `Scenarios`. Keep them short; the `ListView` that displays them dynamically sizes the column width and long names will make it hard for people to use. +- Provide a clear description. +- Comment `Scenario` code to describe to others why it's a useful `Scenario`. +- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Try to minimize the number of new categories created. +- Use the `Bug Rero` Category for `Scnarios` that reproduce bugs. + - Include the Github Issue # in the Description. + - Once the bug has been fixed in `master` submit another PR to remove the `Scenario` (or modify it to provide a good regression test). +- Tag bugs or suggestions for `UI Catalog` in the main `Terminal.Gui` Github Issues with "UICatalog: ". \ No newline at end of file diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs new file mode 100644 index 000000000..e491a4c8e --- /dev/null +++ b/UICatalog/Scenario.cs @@ -0,0 +1,181 @@ +using NStack; +using System; +using System.Collections.Generic; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + /// + /// Base class for each demo/scenario. To define a new sceanrio simply + /// + /// 1) declare a class derived from Scenario, + /// 2) Set Name and Description as appropriate using [ScenarioMetadata] attribute + /// 3) Set one or more categories with the [ScenarioCategory] attribute + /// 4) Implement Setup. + /// 5) Optionally, implement Run. + /// + /// The Main program uses reflection to find all sceanarios and adds them to the + /// ListViews. Press ENTER to run the selected sceanrio. Press CTRL-Q to exit it. + /// + public class Scenario { + /// + /// The Top level for the Scenario. This should be set to `Application.Top` in most cases. + /// + public Toplevel Top { get; set; } + + /// + /// + public Window Win { get; set; } + + /// + /// Helper that provides the default Window implementation with a frame and + /// label showing the name of the Scenario and logic to exit back to + /// the Scenario picker UI. + /// Override Init to provide any `Toplevel` behavior needed. + /// + /// + public virtual void Init(Toplevel top) + { + Top = top; + Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + Top.Add (Win); + } + + [System.AttributeUsage (System.AttributeTargets.Class)] + public class ScenarioMetadata : System.Attribute { + /// + /// Scenario Name + /// + public string Name { get; set; } + + /// + /// Scenario Description + /// + public string Description { get; set; } + + public ScenarioMetadata (string Name, string Description) + { + this.Name = Name; + this.Description = Description; + } + + /// + /// Static helper function to get the Scenario Name given a Type + /// + /// + /// + public static string GetName (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Name; + + /// + /// Static helper function to get the Scenario Description given a Type + /// + /// + /// + public static string GetDescription (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Description; + } + + /// + /// Helper to get the Scenario Name + /// + /// + public string GetName () => ScenarioMetadata.GetName (this.GetType ()); + + /// + /// Helper to get the Scenario Descripiton + /// + /// + public string GetDescription () => ScenarioMetadata.GetDescription (this.GetType ()); + + [System.AttributeUsage (System.AttributeTargets.Class, AllowMultiple = true)] + public class ScenarioCategory : System.Attribute { + /// + /// Category Name + /// + public string Name { get; set; } + + public ScenarioCategory (string Name) => this.Name = Name; + + /// + /// Static helper function to get the Scenario Name given a Type + /// + /// + /// + public static string GetName (Type t) => ((ScenarioCategory)System.Attribute.GetCustomAttributes (t) [0]).Name; + + /// + /// Static helper function to get the Scenario Categories given a Type + /// + /// + /// + public static List GetCategories (Type t) => System.Attribute.GetCustomAttributes (t) + .ToList () + .Where (a => a is ScenarioCategory) + .Select (a => ((ScenarioCategory)a).Name) + .ToList (); + } + + /// + /// Helper function to get the Categories of a Scenario + /// + /// + public List GetCategories () => ScenarioCategory.GetCategories (this.GetType ()); + + public override string ToString () => $"{GetName (),-30}{GetDescription ()}"; + + /// + /// Override this to implement the Scenario setup logic (create controls, etc...). + /// + public virtual void Setup () + { + } + + /// + /// Runs the scenario. Override to start the scearnio using a Top level different than `Top`. + /// + public virtual void Run () + { + Application.Run (Top); + } + + /// + /// Stops the scenario. Override to implement shutdown behavior for the Scenario. + /// + public virtual void RequestStop () + { + Application.RequestStop (); + } + + /// + /// Returns a list of all Categories set by all of the scenarios defined in the project. + /// + internal static List GetAllCategories () + { + List categories = new List () { "All" }; + foreach (Type type in typeof (Scenario).Assembly.GetTypes () + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { + List attrs = System.Attribute.GetCustomAttributes (type).ToList (); + categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList (); + } + return categories; + } + + /// + /// Returns an instance of each Scenario defined in the project. + /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class + /// + internal static List GetDerivedClassesCollection () + { + List objects = new List (); + foreach (Type type in typeof (Scenario).Assembly.GetTypes () + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { + objects.Add (type); + } + return objects; + } + } +} diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs new file mode 100644 index 000000000..64b92bfce --- /dev/null +++ b/UICatalog/Scenarios/Buttons.cs @@ -0,0 +1,105 @@ +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons")] + [ScenarioCategory ("Controls")] + [ScenarioCategory ("Layout")] + class Buttons : Scenario { + public override void Setup () + { + // Add a label & text field so we can demo IsDefault + var editLabel = new Label ("TextField (to demo IsDefault):") { + X = 0, + Y = 0, + }; + Win.Add (editLabel); + var edit = new TextField ("") { + X = Pos.Right (editLabel) + 1, + Y = Pos.Top (editLabel), + Width = Dim.Fill (2), + }; + Win.Add (edit); + + // This is the default button (IsDefault = true); if user presses ENTER in the TextField + // the scenario will quit + var defaultButton = new Button ("Quit") { + X = Pos.Center (), + // BUGBUG: Throws an exception + //Y= Pos.Bottom(Win), + Y = 20, + IsDefault = true, + Clicked = () => Application.RequestStop (), + }; + Win.Add (defaultButton); + + var y = 2; + var button = new Button (10, y, "Base Color") { + ColorScheme = Colors.Base, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }; + Win.Add (button); + + y += 2; + Win.Add (new Button (10, y, "Error Color") { + ColorScheme = Colors.Error, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "Dialog Color") { + ColorScheme = Colors.Dialog, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "Menu Color") { + ColorScheme = Colors.Menu, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "TopLevel Color") { + ColorScheme = Colors.TopLevel, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "A super long button that will probably expose a bug in clipping or wrapping of text. Will it?") { + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + // Note the 'N' in 'Newline' will be the hotkey + Win.Add (new Button (10, y, "a Newline\nin the button") { + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + // BUGBUG: Buttons don't support specifying hotkeys with _?!? + Win.Add (button = new Button (10, y, "Te_xt Changer") { + }); + button.Clicked = () => button.Text += $"{y++}"; + + Win.Add (new Button ("Lets see if this will move as \"Text Changer\" grows") { + X = Pos.Right(button) + 10, + Y = y, + }); + + y += 2; + Win.Add (new Button (10, y, "Delete") { + ColorScheme = Colors.Error, + Clicked = () => Win.Remove (button) + }); + + y += 2; + Win.Add (new Button (10, y, "Change Default") { + Clicked = () => { + defaultButton.IsDefault = !defaultButton.IsDefault; + button.IsDefault = !button.IsDefault; + }, + }); + + + } + } +} diff --git a/UICatalog/Scenarios/DimAndPosLayout.cs b/UICatalog/Scenarios/DimAndPosLayout.cs new file mode 100644 index 000000000..f1f543a57 --- /dev/null +++ b/UICatalog/Scenarios/DimAndPosLayout.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Terminal.Gui; + +namespace UICatalog { + /// + /// This Scenario demonstrates how to use Termina.gui's Dim and Pos Layout System. + /// [x] - Using Dim.Fill to fill a window + /// [x] - Using Dim.Fill and Dim.Pos to automatically align controls based on an initial control + /// [ ] - ... + /// + [ScenarioMetadata (Name: "DimAndPosLayout", Description: "Demonstrates using the Dim and Pos Layout System")] + [ScenarioCategory ("Layout")] + class DimAndPosLayout : Scenario { + + public override void Setup () + { + Top.LayoutStyle = LayoutStyle.Computed; + // Demonstrate using Dim to create a ruler that always measures the top-level window's width + // BUGBUG: Dim.Fill returns too big a value sometimes. + //const string rule = "|123456789"; + //var labelRuler = new Label ("ruler") { + // X = 0, + // Y = 0, + // Width = Dim.Fill (1), // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does. + // ColorScheme = Colors.Error + //}; + + //Application.OnResized += () => { + // labelRuler.Text = rule.Repeat ((int)Math.Ceiling((double)(labelRuler.Bounds.Width) / (double)rule.Length))[0..(labelRuler.Bounds.Width)]; + //}; + + //win.Add (labelRuler); + + // Demonstrate using Dim to create a window that fills the parent with a margin + int margin = 20; + var subWin = new Window ($"Sub Windoww with {margin} character margin") { + X = margin, + Y = 2, + Width = Dim.Fill (margin), + Height = Dim.Fill () + }; + Win.Add (subWin); + + int i = 1; + string txt = "Hello world, how are you doing today"; + var labelList = new List