From d49e32365245216aa0bfb32a1fa2cb52c7a2cc98 Mon Sep 17 00:00:00 2001 From: Tig Kindel Date: Sat, 23 Dec 2023 14:12:21 -0700 Subject: [PATCH 01/85] Initial commit --- Terminal.Gui/Views/Bar.cs | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Terminal.Gui/Views/Bar.cs diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs new file mode 100644 index 000000000..dfca76bde --- /dev/null +++ b/Terminal.Gui/Views/Bar.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; + +namespace Terminal.Gui; + + +public class BarItem : View { + public BarItem () + { + Height = 1; + } + public override string Text { + set { + base.Text = $"{KeyBindings.Bindings.FirstOrDefault (b => b.Value.Scope != KeyBindingScope.Focused).Key} `{value}`"; + } + get { + return $"{KeyBindings.Bindings.FirstOrDefault(b => b.Value.Scope != KeyBindingScope.Focused).Key} `{base.Text}`"; + } + } +} +/// +/// The Bar provides a container for other views to be used as a toolbar or status bar. +/// +/// +/// Views added to a Bar will be positioned horizontally from left to right. +/// +public class Bar : View { + /// + public Bar () => SetInitialProperties (); + + void SetInitialProperties () + { + X = 0; + Y = Pos.AnchorEnd (1); + Width = Dim.Fill (); + Height = 1; + AutoSize = false; + ColorScheme = Colors.Menu; + } + + public override void Add (View view) + { + // Align the views horizontally from left to right. Use Border to separate them. + + // until we know this view is not the rightmost, make it fill the bar + //view.Width = Dim.Fill (); + + view.Margin.Thickness = new Thickness (1, 0, 0, 0); + view.Margin.ColorScheme = Colors.Menu; + + // Light up right border + view.BorderStyle = LineStyle.Single; + view.Border.Thickness = new Thickness (0, 0, 1, 0); + view.Padding.Thickness = new Thickness (0, 0, 1, 0); + view.Padding.ColorScheme = Colors.Menu; + + // leftmost view is at X=0 + if (Subviews.Count == 0) { + view.X = 0; + } else { + // Make view to right be autosize + //Subviews [^1].AutoSize = true; + + // Align the view to the right of the previous view + view.X = Pos.Right (Subviews [^1]); + + } + + base.Add (view); + } +} \ No newline at end of file From 4b5fafa67af71dbc22796720d612bf88a59496d7 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 9 Jan 2024 14:52:16 -0700 Subject: [PATCH 02/85] Merge branch 'v2_develop' into v2_fixes_3037_Bar --- Terminal.Gui/Terminal.Gui.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index f0840c041..a8b1f64ba 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -136,5 +136,6 @@ true true Miguel de Icaza, Tig Kindel (@tig), @BDisp + true \ No newline at end of file From 8493b3073de7154df9c71986345e920d70c3aaa6 Mon Sep 17 00:00:00 2001 From: Tig Kindel Date: Tue, 9 Jan 2024 17:28:18 -0700 Subject: [PATCH 03/85] Fixed a bunch of Keybinding issues --- Terminal.Gui/Views/Bar.cs | 6 +++-- UICatalog/Scenarios/Bars.cs | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 UICatalog/Scenarios/Bars.cs diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index dfca76bde..d704bbf93 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -8,13 +8,14 @@ public class BarItem : View { public BarItem () { Height = 1; + AddCommand (Command.ToggleExpandCollapse, () => { Visible = !Visible; return true; }); } public override string Text { set { - base.Text = $"{KeyBindings.Bindings.FirstOrDefault (b => b.Value.Scope != KeyBindingScope.Focused).Key} `{value}`"; + base.Text = $"{KeyBindings.Bindings.FirstOrDefault (b => b.Value.Scope != KeyBindingScope.Focused).Key} {value}"; } get { - return $"{KeyBindings.Bindings.FirstOrDefault(b => b.Value.Scope != KeyBindingScope.Focused).Key} `{base.Text}`"; + return $"{KeyBindings.Bindings.FirstOrDefault(b => b.Value.Scope != KeyBindingScope.Focused).Key} {base.Text}"; } } } @@ -36,6 +37,7 @@ public class Bar : View { Height = 1; AutoSize = false; ColorScheme = Colors.Menu; + CanFocus = true; } public override void Add (View view) diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs new file mode 100644 index 000000000..edad3b749 --- /dev/null +++ b/UICatalog/Scenarios/Bars.cs @@ -0,0 +1,52 @@ +using Terminal.Gui; + +namespace UICatalog.Scenarios; +[ScenarioMetadata (Name: "Bars", Description: "Illustrates Bar views (e.g. StatusBar)")] +[ScenarioCategory ("Controls")] +public class bars : Scenario { + public override void Init () + { + Application.Init (); + ConfigurationManager.Themes.Theme = Theme; + ConfigurationManager.Apply (); + Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; + Application.Top.Loaded += Top_Initialized; + } + + // Setting everything up in Initialized handler because we change the + // QuitKey and it only sticks if changed after init + void Top_Initialized (object sender, System.EventArgs e) + { + Application.QuitKey = Key.Z.WithCtrl; + + var bar = new Bar () { + }; + var barITem = new BarItem () { Text = $"Quit - {Application.QuitKey}", AutoSize = true }; + barITem.KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel); + bar.Add (barITem); + + barITem = new BarItem () { Text = $"Show/Hide - {Key.F10}", AutoSize = true }; + barITem.KeyBindings.Add (Key.F10, KeyBindingScope.Application, Command.ToggleExpandCollapse); + bar.Add (barITem); + + bar.Add (new Label () { Text = "FocusLabel", CanFocus = true }); + + var button = new Button ("Press me!") { + AutoSize = true + }; + button.Clicked += Button_Clicked; + + bar.Add (button); + + button = new Button ("Or me!") { + AutoSize = true, + }; + button.Clicked += Button_Clicked; + + bar.Add (button); + + Application.Top.Add (bar); + } + + void Button_Clicked (object sender, System.EventArgs e) => MessageBox.Query("Hi", $"You clicked {sender}"); +} From a03687a92c6c4b4bebbcb9fbe54efbafbb579d47 Mon Sep 17 00:00:00 2001 From: Tig Kindel Date: Tue, 9 Jan 2024 18:43:10 -0700 Subject: [PATCH 04/85] Added more keybinding tests From 4dbeb2513305f614964794828a109e10dab37567 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 9 Jun 2024 20:13:36 -0600 Subject: [PATCH 05/85] Moved bar files back --- Terminal.Gui/Views/Bar.cs | 236 +++++++++++++++ Terminal.Gui/Views/Shortcut.cs | 529 +++++++++++++++++++++++++++++++++ UICatalog/Scenarios/Bars.cs | 490 ++++++++++++++++++++++++++++++ 3 files changed, 1255 insertions(+) create mode 100644 Terminal.Gui/Views/Bar.cs create mode 100644 Terminal.Gui/Views/Shortcut.cs create mode 100644 UICatalog/Scenarios/Bars.cs diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs new file mode 100644 index 000000000..335da5512 --- /dev/null +++ b/Terminal.Gui/Views/Bar.cs @@ -0,0 +1,236 @@ +using System; +using System.Linq; + +namespace Terminal.Gui; + +/// +/// Provides a horizontally or vertically oriented container for other views to be used as a menu, toolbar, or status bar. +/// +/// +/// +public class Bar : View +{ + /// + public Bar () + { + SetInitialProperties (); + } + + /// + /// Gets or sets the for this . The default is + /// . + /// + public Orientation Orientation { get; set; } = Orientation.Horizontal; + + public bool StatusBarStyle { get; set; } = true; + + public override void Add (View view) + { + if (Orientation == Orientation.Horizontal) + { + //view.AutoSize = true; + } + + //if (StatusBarStyle) + //{ + // // Light up right border + // view.BorderStyle = LineStyle.Single; + // view.Border.Thickness = new Thickness (0, 0, 1, 0); + //} + + //if (view is not Shortcut) + //{ + // if (StatusBarStyle) + // { + // view.Padding.Thickness = new Thickness (0, 0, 1, 0); + // } + + // view.Margin.Thickness = new Thickness (1, 0, 0, 0); + //} + + //view.ColorScheme = ColorScheme; + + // Add any HotKey keybindings to our bindings + IEnumerable> bindings = view.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.HotKey); + + foreach (KeyValuePair binding in bindings) + { + AddCommand ( + binding.Value.Commands [0], + () => + { + if (view is Shortcut shortcut) + { + return shortcut.CommandView.InvokeCommands (binding.Value.Commands); + } + + return false; + }); + KeyBindings.Add (binding.Key, binding.Value); + } + + base.Add (view); + } + + private void Bar_LayoutStarted (object sender, LayoutEventArgs e) + { + View prevBarItem = null; + + switch (Orientation) + { + case Orientation.Horizontal: + for (var index = 0; index < Subviews.Count; index++) + { + View barItem = Subviews [index]; + + if (!barItem.Visible) + { + continue; + } + + if (prevBarItem == null) + { + barItem.X = 0; + } + else + { + // Make view to right be autosize + //Subviews [^1].AutoSize = true; + + // Align the view to the right of the previous view + barItem.X = Pos.Right (prevBarItem); + } + + barItem.Y = Pos.Center (); + barItem.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + prevBarItem = barItem; + } + + break; + + case Orientation.Vertical: + // CommandView is aligned left, HelpView is aligned right, KeyView is aligned right + // All CommandView's are the same width, all HelpView's are the same width, + // all KeyView's are the same width + + int maxCommandWidth = 0; + int maxHelpWidth = 0; + + List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); + + foreach (Shortcut shortcut in shortcuts) + { + // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView + shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + shortcut.KeyView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + shortcut.HelpView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + } + + maxCommandWidth = shortcuts.Max (s => s.CommandView.Frame.Width); + maxHelpWidth = shortcuts.Max (s => s.HelpView.Frame.Width); + + // Set the width of all CommandView's and HelpView's to the max width + foreach (Shortcut shortcut in shortcuts) + { + shortcut.CommandView.Width = Dim.Auto (minimumContentDim: maxCommandWidth); + shortcut.KeyView.Width = Dim.Auto (); + shortcut.HelpView.Width = Dim.Auto (minimumContentDim: maxHelpWidth); + + // shortcut.LayoutSubviews (); + } + + // Set the overall size of the Bar and arrange the views vertically + + var maxBarItemWidth = 0; + + for (var index = 0; index < Subviews.Count; index++) + { + View barItem = Subviews [index]; + + if (!barItem.Visible) + { + continue; + } + + if (prevBarItem == null) + { + barItem.Y = 0; + } + else + { + // Align the view to the bottom of the previous view + barItem.Y = index; + } + + prevBarItem = barItem; + + if (barItem is Shortcut shortcut) + { + //shortcut.SetRelativeLayout (new (int.MaxValue, int.MaxValue)); + maxBarItemWidth = Math.Max (maxBarItemWidth, shortcut.Frame.Width); + } + else + { + maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); + } + + barItem.X = 0; + } + + foreach (Shortcut shortcut in shortcuts) + { + if (Width is DimAuto) + { + shortcut.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: maxBarItemWidth); + } + else + { + //shortcut._container.Width = Dim.Fill (); + // shortcut.Width = Dim.Fill (); + } + + shortcut.LayoutSubviews (); + } + + + //for (var index = 0; index < Subviews.Count; index++) + //{ + // var shortcut = Subviews [index] as Shortcut; + + // if (shortcut is { Visible: false }) + // { + // continue; + // } + + // if (Width is DimAuto) + // { + // shortcut._container.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: maxBarItemWidth); + // } + // else + // { + // shortcut._container.Width = Dim.Fill (); + // shortcut.Width = Dim.Fill (); + // } + + // //shortcut.SetContentSize (new (maxBarItemWidth, 1)); + // //shortcut.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: int.Max(maxBarItemWidth, GetContentSize().Width)); + + //} + + + + break; + } + } + + private void SetInitialProperties () + { + ColorScheme = Colors.ColorSchemes ["Menu"]; + CanFocus = true; + + Width = Dim.Auto (); + Height = Dim.Auto (); + + LayoutStarted += Bar_LayoutStarted; + } +} diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs new file mode 100644 index 000000000..a5ca12d3f --- /dev/null +++ b/Terminal.Gui/Views/Shortcut.cs @@ -0,0 +1,529 @@ +using System.ComponentModel; + +namespace Terminal.Gui; + +// TODO: I don't love the name Shortcut, but I can't think of a better one right now. Shortcut is a bit overloaded. +// TODO: It can mean "Application-scoped key binding" or "A key binding that is displayed in a visual way". +// TODO: I tried `BarItem` but that's not great either as it implies it can only be used in `Bar`s. + +/// +/// Displays a command, help text, and a key binding. Useful for displaying a command in such as a +/// menu, toolbar, or status bar. +/// +/// +/// +/// When the user clicks on the or presses the key +/// specified by the command is invoked, causing the +/// event to be fired +/// +/// +/// If is , the +/// be invoked regardless of what View has focus, enabling an application-wide keyboard shortcut. +/// +/// +/// Set to change the Command text displayed in the . +/// By default, the text is the of . +/// +/// +/// Set to change the Help text displayed in the . +/// +/// +/// The text displayed for the is the string representation of the . +/// If the is , the text is not displayed. +/// +/// +public class Shortcut : View +{ + // Hosts the Command, Help, and Key Views. Needed (IIRC - wrote a long time ago) to allow mouse clicks to be handled by the Shortcut. + internal readonly View _container; + + /// + /// Creates a new instance of . + /// + public Shortcut () + { + CanFocus = true; + Width = Dim.Auto (DimAutoStyle.Content); + Height = Dim.Auto (DimAutoStyle.Content); + + //Height = Dim.Auto (minimumContentDim: 1, maximumContentDim: 1); + + AddCommand (Gui.Command.HotKey, () => true); + AddCommand (Gui.Command.Accept, OnAccept); + KeyBindings.Add (KeyCode.Space, Gui.Command.Accept); + KeyBindings.Add (KeyCode.Enter, Gui.Command.Accept); + + _container = new () + { + Id = "_container", + // Only the Shortcut (_container) should be able to have focus, not any subviews. + CanFocus = true, + Width = Dim.Auto (DimAutoStyle.Content, 1), + Height = Dim.Auto (DimAutoStyle.Content, 1), + BorderStyle = LineStyle.Dashed + }; + + CommandView = new (); + + HelpView = new () + { + Id = "_helpView", + // Only the Shortcut should be able to have focus, not any subviews + CanFocus = false, + X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems), + Y = Pos.Center (), + + // Helpview is the only subview that doesn't have a min width + Width = Dim.Auto (DimAutoStyle.Text), + Height = Dim.Auto (DimAutoStyle.Text), + ColorScheme = Colors.ColorSchemes ["Error"] + }; + _container.Add (HelpView); + + // HelpView.TextAlignment = Alignment.End; + HelpView.MouseClick += Shortcut_MouseClick; + + KeyView = new () + { + Id = "_keyView", + // Only the Shortcut should be able to have focus, not any subviews + CanFocus = false, + X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems), + Y = Pos.Center (), + + // Bar will set the width of all KeyViews to the width of the widest KeyView. + Width = Dim.Auto (DimAutoStyle.Text), + Height = Dim.Auto (DimAutoStyle.Text), + }; + _container.Add (KeyView); + + KeyView.MouseClick += Shortcut_MouseClick; + + CommandView.Margin.Thickness = new Thickness (1, 0, 1, 0); + HelpView.Margin.Thickness = new Thickness (1, 0, 1, 0); + KeyView.Margin.Thickness = new Thickness (1, 0, 1, 0); + + MouseClick += Shortcut_MouseClick; + + TitleChanged += Shortcut_TitleChanged; + Initialized += OnInitialized; + + Add (_container); + + return; + + void OnInitialized (object sender, EventArgs e) + { + if (ColorScheme != null) + { + var cs = new ColorScheme (ColorScheme) + { + Normal = ColorScheme.HotNormal, + HotNormal = ColorScheme.Normal + }; + KeyView.ColorScheme = cs; + } + } + } + + private void Shortcut_MouseClick (object sender, MouseEventEventArgs e) + { + // When the Shortcut is clicked, we want to invoke the Command and Set focus + View view = sender as View; + if (!e.Handled && Command.HasValue) + { + // If the subview (likely CommandView) didn't handle the mouse click, invoke the command. + bool? handled = false; + handled = InvokeCommand (Command.Value); + if (handled.HasValue) + { + e.Handled = handled.Value; + } + } + if (CanFocus) + { + SetFocus (); + } + e.Handled = true; + } + + /// + public override ColorScheme ColorScheme + { + get + { + if (base.ColorScheme == null) + { + return SuperView?.ColorScheme ?? base.ColorScheme; + } + + return base.ColorScheme; + } + set + { + base.ColorScheme = value; + + if (ColorScheme != null) + { + var cs = new ColorScheme (ColorScheme) + { + Normal = ColorScheme.HotNormal, + HotNormal = ColorScheme.Normal + }; + KeyView.ColorScheme = cs; + } + } + } + + #region Command + + private Command? _command; + + /// + /// Gets or sets the that will be invoked when the user clicks on the or + /// presses . + /// + public Command? Command + { + get => _command; + set + { + if (value != null) + { + _command = value.Value; + UpdateKeyBinding (); + } + } + } + + private View _commandView; + + /// + /// Gets or sets the View that displays the command text and hotkey. + /// + /// + /// + /// By default, the of the is displayed as the Shortcut's + /// command text. + /// + /// + /// By default, the CommandView is a with set to + /// . + /// + /// + /// Setting the will add it to the and remove any existing + /// . + /// + /// + /// + /// + /// This example illustrates how to add a to a that toggles the + /// property. + /// + /// + /// var force16ColorsShortcut = new Shortcut + /// { + /// Key = Key.F6, + /// KeyBindingScope = KeyBindingScope.HotKey, + /// Command = Command.Accept, + /// CommandView = new CheckBox { Text = "Force 16 Colors" } + /// }; + /// var cb = force16ColorsShortcut.CommandView as CheckBox; + /// cb.Checked = Application.Force16Colors; + /// + /// cb.Toggled += (s, e) => + /// { + /// var cb = s as CheckBox; + /// Application.Force16Colors = cb!.Checked == true; + /// Application.Refresh(); + /// }; + /// StatusBar.Add(force16ColorsShortcut); + /// + /// + + public View CommandView + { + get => _commandView; + set + { + if (value == null) + { + throw new ArgumentNullException (); + } + + if (_commandView is { }) + { + _container.Remove (_commandView); + _commandView?.Dispose (); + } + + + _commandView = value; + _commandView.Id = "_commandView"; + + // TODO: Determine if it makes sense to allow the CommandView to be focusable. + // Right now, we don't set CanFocus to false here. + _commandView.CanFocus = false; + + // Bar will set the width of all CommandViews to the width of the widest CommandViews. + _commandView.Width = Dim.Auto (DimAutoStyle.Text); + _commandView.Height = Dim.Auto (DimAutoStyle.Text); + _commandView.X = X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems); + _commandView.Y = Pos.Center (); + + _commandView.MouseClick += Shortcut_MouseClick; + _commandView.Accept += CommandView_Accept; + + _commandView.Margin.Thickness = new (1, 0, 1, 0); + + _commandView.HotKeyChanged += (s, e) => + { + if (e.NewKey != Key.Empty) + { + // Add it + AddKeyBindingsForHotKey (e.OldKey, e.NewKey); + } + }; + + _commandView.HotKeySpecifier = new ('_'); + + _container.Remove (HelpView); + _container.Remove (KeyView); + _container.Add (_commandView, HelpView, KeyView); + + UpdateKeyBinding(); + + } + } + + private void _commandView_MouseEvent (object sender, MouseEventEventArgs e) + { + e.Handled = true; + } + + private void Shortcut_TitleChanged (object sender, StateEventArgs e) + { + // If the Title changes, update the CommandView text. This is a helper to make it easier to set the CommandView text. + // CommandView is public and replaceable, but this is a convenience. + _commandView.Text = Title; + } + + private void CommandView_Accept (object sender, CancelEventArgs e) + { + // When the CommandView fires its Accept event, we want to act as though the + // Shortcut was clicked. + var args = new HandledEventArgs (); + Accept?.Invoke (this, args); + + if (args.Handled) + { + e.Cancel = args.Handled; + } + } + + #endregion Command + + #region Help + + /// + /// The subview that displays the help text for the command. Internal for unit testing. + /// + internal View HelpView { get; set; } + + /// + /// Gets or sets the help text displayed in the middle of the Shortcut. + /// + public override string Text + { + get => base.Text; + set + { + //base.Text = value; + if (HelpView != null) + { + HelpView.Text = value; + } + } + } + + #endregion Help + + #region Key + + private Key _key; + + /// + /// Gets or sets the that will be bound to the command. + /// + public Key Key + { + get => _key; + set + { + if (value == null) + { + throw new ArgumentNullException (); + } + + _key = value; + + if (Command != null) + { + UpdateKeyBinding (); + } + + KeyView.Text = $"{Key}"; + KeyView.Visible = Key != Key.Empty; + } + } + + private KeyBindingScope _keyBindingScope; + + /// + /// Gets or sets the scope for the key binding for how is bound to . + /// + public KeyBindingScope KeyBindingScope + { + get => _keyBindingScope; + set + { + _keyBindingScope = value; + + if (Command != null) + { + UpdateKeyBinding (); + } + } + } + + /// + /// Gets the subview that displays the key. Internal for unit testing. + /// + + internal View KeyView { get; } + + private void UpdateKeyBinding () + { + if (KeyBindingScope == KeyBindingScope.Application) + { + // return; + } + + if (Command != null && Key != null && Key != Key.Empty) + { + // CommandView holds our command/keybinding + // Add a key binding for this command to this Shortcut + if (CommandView.GetSupportedCommands ().Contains (Command.Value)) + { + CommandView.KeyBindings.Remove (Key); + CommandView.KeyBindings.Add (Key, KeyBindingScope, Command.Value); + } + else + { + // throw new InvalidOperationException ($"CommandView does not support the command {Command.Value}"); + } + } + } + + #endregion Key + + /// + /// The event fired when the command is received. This + /// occurs if the user clicks on the Shortcut or presses . + /// + public new event EventHandler Accept; + + /// + /// Called when the command is received. This + /// occurs if the user clicks on the Bar with the mouse or presses the key bound to + /// Command.Accept (Space by default). + /// + protected new bool? OnAccept () + { + // TODO: This is not completely thought through. + + + + if (Key == null || Key == Key.Empty) + { + return false; + } + + var handled = false; + var keyCopy = new Key (Key); + + switch (KeyBindingScope) + { + case KeyBindingScope.Application: + // Simulate a key down to invoke the Application scoped key binding + handled = Application.OnKeyDown (keyCopy); + + break; + case KeyBindingScope.Focused: + handled = InvokeCommand (Command.Value) == true; + handled = false; + + break; + case KeyBindingScope.HotKey: + if (Command.HasValue) + { + //handled = _commandView.InvokeCommand (Gui.Command.HotKey) == true; + //handled = false; + } + break; + } + + //if (handled == false) + { + var args = new HandledEventArgs (); + Accept?.Invoke (this, args); + handled = args.Handled; + } + + return handled; + } + + /// + public override bool OnEnter (View view) + { + // TODO: This is a hack. Need to refine this. + var cs = new ColorScheme (ColorScheme) + { + Normal = ColorScheme.Focus, + HotNormal = ColorScheme.HotFocus + }; + + _container.ColorScheme = cs; + + cs = new (ColorScheme) + { + Normal = ColorScheme.HotFocus, + HotNormal = ColorScheme.Focus + }; + KeyView.ColorScheme = cs; + + return base.OnEnter (view); + } + + /// + public override bool OnLeave (View view) + { + // TODO: This is a hack. Need to refine this. + var cs = new ColorScheme (ColorScheme) + { + Normal = ColorScheme.Normal, + HotNormal = ColorScheme.HotNormal + }; + + _container.ColorScheme = cs; + + cs = new (ColorScheme) + { + Normal = ColorScheme.HotNormal, + HotNormal = ColorScheme.Normal + }; + KeyView.ColorScheme = cs; + + return base.OnLeave (view); + } +} diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs new file mode 100644 index 000000000..185b14281 --- /dev/null +++ b/UICatalog/Scenarios/Bars.cs @@ -0,0 +1,490 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("Bars", "Illustrates Bar views (e.g. StatusBar)")] +[ScenarioCategory ("Controls")] +public class Bars : Scenario +{ + public override void Main () + { + Application.Init (); + Window app = new (); + + app.Loaded += App_Loaded; + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); + } + + + // Setting everything up in Loaded handler because we change the + // QuitKey and it only sticks if changed after init + private void App_Loaded (object sender, EventArgs e) + { + Application.QuitKey = Key.Z.WithCtrl; + Application.Top.Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"; + + ObservableCollection eventSource = new (); + ListView eventLog = new ListView () + { + X = Pos.AnchorEnd (), + Width = 50, + Height = Dim.Fill (), + ColorScheme = Colors.ColorSchemes ["Toplevel"], + Source = new ListWrapper (eventSource) + }; + Application.Top.Add (eventLog); + + var shortcut1 = new Shortcut + { + Title = "_Zigzag", + Key = Key.Z.WithAlt, + Text = "Gonna zig zag", + KeyBindingScope = KeyBindingScope.HotKey, + Command = Command.Accept, + }; + shortcut1.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + }; + Application.Top.Add (shortcut1); + shortcut1.SetFocus (); + + //var shortcut2 = new Shortcut + //{ + // Title = "Za_G", + // Text = "Gonna zag", + // Key = Key.G.WithAlt, + // KeyBindingScope = KeyBindingScope.HotKey, + // Command = Command.Accept, + // X = Pos.Left (shortcut1), + // Y = Pos.Bottom (shortcut1), + // //Width = 50, + //}; + + + //var shortcut3 = new Shortcut + //{ + // Title = "Shortcut3", + // Key = Key.D3.WithCtrl, + // Text = "Number Three", + // KeyBindingScope = KeyBindingScope.Application, + // Command = Command.Accept, + //}; + + //shortcut3.Accept += (s, e) => + // { + // eventSource.Add ($"Accept: {s}"); + // eventLog.MoveDown (); + // }; + + //var shortcut4 = new Shortcut + //{ + // Title = "Shortcut4", + // Text = "Number 4", + // Key = Key.F4, + // KeyBindingScope = KeyBindingScope.Application, + // Command = Command.Accept, + //}; + + //var cb = new CheckBox () + //{ + // Title = "Hello",// shortcut4.Text + //}; + + //cb.Toggled += (s, e) => + // { + // eventSource.Add ($"Toggled: {s}"); + // eventLog.MoveDown (); + // }; + + //shortcut4.CommandView = cb; + + //shortcut4.Accept += (s, e) => + // { + // eventSource.Add ($"Accept: {s}"); + // eventLog.MoveDown (); + // }; + + //var bar = new Bar + //{ + // X = 2, + // Y = Pos.Bottom(shortcut1), + // Orientation = Orientation.Vertical, + // StatusBarStyle = false, + // Width = Dim.Percent(40) + //}; + //bar.Add (shortcut3, shortcut4); + + ////CheckBox hello = new () + ////{ + //// Title = "Hello", + //// X = 0, + //// Y = 1, + ////}; + ////Application.Top.Add (hello); + ////hello.Toggled += (s, e) => + //// { + //// eventSource.Add ($"Toggled: {s}"); + //// eventLog.MoveDown (); + //// }; + + //Application.Top.Add (bar); + + // BUGBUG: This should not be needed + //Application.Top.LayoutSubviews (); + + //SetupMenuBar (); + //SetupContentMenu (); + // SetupStatusBar (); + } + + private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } + + //private void SetupContentMenu () + //{ + // Application.Top.Add (new Label { Text = "Right Click for Context Menu", X = Pos.Center (), Y = 4 }); + // Application.Top.MouseClick += ShowContextMenu; + //} + + //private void ShowContextMenu (object s, MouseEventEventArgs e) + //{ + // if (e.MouseEvent.Flags != MouseFlags.Button3Clicked) + // { + // return; + // } + + // var contextMenu = new Bar + // { + // Id = "contextMenu", + // X = e.MouseEvent.Position.X, + // Y = e.MouseEvent.Position.Y, + // Width = Dim.Auto (DimAutoStyle.Content), + // Height = Dim.Auto (DimAutoStyle.Content), + // Orientation = Orientation.Vertical, + // StatusBarStyle = false, + // BorderStyle = LineStyle.Rounded, + // Modal = true, + // }; + + // var newMenu = new Shortcut + // { + // Title = "_New...", + // Text = "Create a new file", + // Key = Key.N.WithCtrl, + // CanFocus = true + // }; + + // newMenu.Accept += (s, e) => + // { + // contextMenu.RequestStop (); + + // Application.AddTimeout ( + // new TimeSpan (0), + // () => + // { + // MessageBox.Query ("File", "New"); + + // return false; + // }); + // }; + + // var open = new Shortcut + // { + // Title = "_Open...", + // Text = "Show the File Open Dialog", + // Key = Key.O.WithCtrl, + // CanFocus = true + // }; + + // open.Accept += (s, e) => + // { + // contextMenu.RequestStop (); + + // Application.AddTimeout ( + // new TimeSpan (0), + // () => + // { + // MessageBox.Query ("File", "Open"); + + // return false; + // }); + // }; + + // var save = new Shortcut + // { + // Title = "_Save...", + // Text = "Save", + // Key = Key.S.WithCtrl, + // CanFocus = true + // }; + + // save.Accept += (s, e) => + // { + // contextMenu.RequestStop (); + + // Application.AddTimeout ( + // new TimeSpan (0), + // () => + // { + // MessageBox.Query ("File", "Save"); + + // return false; + // }); + // }; + + // var saveAs = new Shortcut + // { + // Title = "Save _As...", + // Text = "Save As", + // Key = Key.A.WithCtrl, + // CanFocus = true + // }; + + // saveAs.Accept += (s, e) => + // { + // contextMenu.RequestStop (); + + // Application.AddTimeout ( + // new TimeSpan (0), + // () => + // { + // MessageBox.Query ("File", "Save As"); + + // return false; + // }); + // }; + + // contextMenu.Add (newMenu, open, save, saveAs); + + // contextMenu.KeyBindings.Add (Key.Esc, Command.QuitToplevel); + + // contextMenu.Initialized += Menu_Initialized; + + // void Application_MouseEvent (object sender, MouseEvent e) + // { + // // If user clicks outside of the menuWindow, close it + // if (!contextMenu.Frame.Contains (e.Position.X, e.Position.Y)) + // { + // if (e.Flags is (MouseFlags.Button1Clicked or MouseFlags.Button3Clicked)) + // { + // contextMenu.RequestStop (); + // } + // } + // } + + // Application.MouseEvent += Application_MouseEvent; + + // Application.Run (contextMenu); + // contextMenu.Dispose (); + + // Application.MouseEvent -= Application_MouseEvent; + //} + + private void Menu_Initialized (object sender, EventArgs e) + { + // BUGBUG: this should not be needed + + ((View)(sender)).LayoutSubviews (); + } + + //private void SetupMenuBar () + //{ + // var menuBar = new Bar + // { + // Id = "menuBar", + + // X = 0, + // Y = 0, + // Width = Dim.Fill (), + // Height = Dim.Auto (DimAutoStyle.Content), + // StatusBarStyle = true + // }; + + // var fileMenu = new Shortcut + // { + // Title = "_File", + // Key = Key.F.WithAlt, + // KeyBindingScope = KeyBindingScope.HotKey, + // Command = Command.Accept, + // }; + // fileMenu.HelpView.Visible = false; + // fileMenu.KeyView.Visible = false; + + // fileMenu.Accept += (s, e) => + // { + // fileMenu.SetFocus (); + + // if (s is View view) + // { + // var menu = new Bar + // { + // X = view.Frame.X + 1, + // Y = view.Frame.Y + 1, + // ColorScheme = view.ColorScheme, + // Orientation = Orientation.Vertical, + // StatusBarStyle = false, + // BorderStyle = LineStyle.Dotted, + // Width = Dim.Auto (DimAutoStyle.Content), + // Height = Dim.Auto (DimAutoStyle.Content), + // }; + + // menu.KeyBindings.Add (Key.Esc, Command.QuitToplevel); + + // var newMenu = new Shortcut + // { + // Title = "_New...", + // Text = "Create a new file", + // Key = Key.N.WithCtrl + // }; + + // var open = new Shortcut + // { + // Title = "_Open...", + // Text = "Show the File Open Dialog", + // Key = Key.O.WithCtrl + // }; + + // var save = new Shortcut + // { + // Title = "_Save...", + // Text = "Save", + // Key = Key.S.WithCtrl + // }; + + // menu.Add (newMenu, open, save); + + // // BUGBUG: this is all bad + // menu.Initialized += Menu_Initialized; + // open.Initialized += Menu_Initialized; + // save.Initialized += Menu_Initialized; + // newMenu.Initialized += Menu_Initialized; + + // Application.Run (menu); + // menu.Dispose (); + // Application.Refresh (); + // } + // }; + + // var editMenu = new Shortcut + // { + // Title = "_Edit", + + // //Key = Key.E.WithAlt, + // KeyBindingScope = KeyBindingScope.HotKey, + // Command = Command.Accept + // }; + + // editMenu.Accept += (s, e) => { }; + // editMenu.HelpView.Visible = false; + // editMenu.KeyView.Visible = false; + + // menuBar.Add (fileMenu, editMenu); + + // menuBar.Initialized += Menu_Initialized; + + // Application.Top.Add (menuBar); + //} + + private void SetupStatusBar () + { + var statusBar = new Bar + { + Id = "statusBar", + X = 0, + Y = Pos.AnchorEnd (), + Width = Dim.Fill (), + }; + + var shortcut = new Shortcut + { + Text = "Quit Application", + Title = "Q_uit", + Key = Application.QuitKey, + KeyBindingScope = KeyBindingScope.Application, + Command = Command.QuitToplevel, + CanFocus = false + }; + + statusBar.Add (shortcut); + + shortcut = new Shortcut + { + Text = "Help Text", + Title = "Help", + Key = Key.F1, + KeyBindingScope = KeyBindingScope.HotKey, + Command = Command.Accept, + CanFocus = false + }; + + var labelHelp = new Label + { + X = Pos.Center (), + Y = Pos.Top (statusBar) - 1, + Text = "Help" + }; + Application.Top.Add (labelHelp); + + shortcut.Accept += (s, e) => + { + labelHelp.Text = labelHelp.Text + "!"; + e.Handled = true; + }; + + statusBar.Add (shortcut); + + shortcut = new Shortcut + { + Title = "_Show/Hide", + Key = Key.F10, + KeyBindingScope = KeyBindingScope.HotKey, + Command = Command.ToggleExpandCollapse, + CommandView = new CheckBox + { + Text = "_Show/Hide" + }, + CanFocus = false + }; + + statusBar.Add (shortcut); + + var button1 = new Button + { + Text = "I'll Hide", + Visible = false + }; + button1.Accept += Button_Clicked; + statusBar.Add (button1); + + ((CheckBox)shortcut.CommandView).Toggled += (s, e) => + { + button1.Visible = !button1.Visible; + button1.Enabled = button1.Visible; + }; + + statusBar.Add (new Label { HotKeySpecifier = new Rune ('_'), Text = "Fo_cusLabel", CanFocus = true }); + + var button2 = new Button + { + Text = "Or me!", + }; + button2.Accept += (s, e) => Application.RequestStop (); + + statusBar.Add (button2); + + statusBar.Initialized += Menu_Initialized; + + Application.Top.Add (statusBar); + + + } + +} From 1facff726b2f3f3385bc3ead011f86230ae5b785 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 9 Jun 2024 20:15:04 -0600 Subject: [PATCH 06/85] Moved dimauto back --- Terminal.Gui/View/Layout/DimAuto.cs | 132 +++++++++++++++++++++------- 1 file changed, 99 insertions(+), 33 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 0538c6f08..38ec1e086 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -1,4 +1,6 @@ #nullable enable +using System.Drawing; + namespace Terminal.Gui; /// @@ -20,6 +22,7 @@ public class DimAuto () : Dim /// /// Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. /// + // ReSharper disable once ConvertToAutoProperty public required Dim? MaximumContentDim { @@ -32,6 +35,7 @@ public class DimAuto () : Dim /// /// Gets the minimum dimension the View's ContentSize will be constrained to. /// + // ReSharper disable once ConvertToAutoProperty public required Dim? MinimumContentDim { @@ -44,6 +48,7 @@ public class DimAuto () : Dim /// /// Gets the style of the DimAuto. /// + // ReSharper disable once ConvertToAutoProperty public required DimAutoStyle Style { @@ -81,10 +86,11 @@ public class DimAuto () : Dim // TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451). subviewsSize = 0; - List includedSubviews = us.Subviews.ToList();//.Where (v => !v.ExcludeFromLayout).ToList (); + List includedSubviews = us.Subviews.ToList (); //.Where (v => !v.ExcludeFromLayout).ToList (); List subviews; #region Not Anchored and Are Not Dependent + // Start with subviews that are not anchored to the end, aligned, or dependent on content size // [x] PosAnchorEnd // [x] PosAlign @@ -98,17 +104,25 @@ public class DimAuto () : Dim // [ ] DimView if (dimension == Dimension.Width) { - subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd - && v.X is not PosAlign - // && v.X is not PosCenter - && v.Width is not DimFill).ToList (); + subviews = includedSubviews.Where ( + v => v.X is not PosAnchorEnd + && v.X is not PosAlign + + // && v.X is not PosCenter + && v.Width is not DimAuto + && v.Width is not DimFill) + .ToList (); } else { - subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd - && v.Y is not PosAlign - // && v.Y is not PosCenter - && v.Height is not DimFill).ToList (); + subviews = includedSubviews.Where ( + v => v.Y is not PosAnchorEnd + && v.Y is not PosAlign + + // && v.Y is not PosCenter + && v.Height is not DimAuto + && v.Height is not DimFill) + .ToList (); } for (var i = 0; i < subviews.Count; i++) @@ -123,9 +137,11 @@ public class DimAuto () : Dim subviewsSize = size; } } + #endregion Not Anchored and Are Not Dependent #region Anchored + // Now, handle subviews that are anchored to the end // [x] PosAnchorEnd if (dimension == Dimension.Width) @@ -138,6 +154,7 @@ public class DimAuto () : Dim } int maxAnchorEnd = 0; + for (var i = 0; i < subviews.Count; i++) { View v = subviews [i]; @@ -145,47 +162,84 @@ public class DimAuto () : Dim } subviewsSize += maxAnchorEnd; + #endregion Anchored - //#region Center - //// Now, handle subviews that are Centered - //if (dimension == Dimension.Width) - //{ - // subviews = us.Subviews.Where (v => v.X is PosCenter).ToList (); - //} - //else - //{ - // subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList (); - //} - //int maxCenter = 0; - //for (var i = 0; i < subviews.Count; i++) - //{ - // View v = subviews [i]; - // maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; - //} + #region Auto - //subviewsSize += maxCenter; - //#endregion Center + if (dimension == Dimension.Width) + { + subviews = includedSubviews.Where (v => v.Width is DimAuto).ToList (); + } + else + { + subviews = includedSubviews.Where (v => v.Height is DimAuto).ToList (); + } + + int maxAuto = 0; + + for (var i = 0; i < subviews.Count; i++) + { + View v = subviews [i]; + + maxAuto = CalculateMinDimension (us, dimension); + + if (maxAuto > subviewsSize) + { + // BUGBUG: Should we break here? Or choose min/max? + subviewsSize = maxAuto; + } + } + + // subviewsSize += maxAuto; + + #endregion Auto + + #region Center + + // Now, handle subviews that are Centered + if (dimension == Dimension.Width) + { + subviews = us.Subviews.Where (v => v.X is PosCenter).ToList (); + } + else + { + subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList (); + } + + int maxCenter = 0; + + for (var i = 0; i < subviews.Count; i++) + { + View v = subviews [i]; + maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; + + if (maxCenter > subviewsSize) + { + // BUGBUG: Should we break here? Or choose min/max? + subviewsSize = maxCenter; + } + } + + #endregion Center #region Are Dependent + // Now, go back to those that are dependent on content size // [x] DimFill // [ ] DimPercent if (dimension == Dimension.Width) { - subviews = includedSubviews.Where (v => v.Width is DimFill - // || v.X is PosCenter - ).ToList (); + subviews = includedSubviews.Where (v => v.Width is DimFill).ToList (); } else { - subviews = includedSubviews.Where (v => v.Height is DimFill - //|| v.Y is PosCenter - ).ToList (); + subviews = includedSubviews.Where (v => v.Height is DimFill).ToList (); } int maxFill = 0; + for (var i = 0; i < subviews.Count; i++) { View v = subviews [i]; @@ -198,10 +252,12 @@ public class DimAuto () : Dim { v.SetRelativeLayout (new Size (0, autoMax - subviewsSize)); } + maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; } subviewsSize += maxFill; + #endregion Are Dependent } } @@ -227,6 +283,16 @@ public class DimAuto () : Dim return int.Min (max, autoMax); } + internal int CalculateMinDimension (View us, Dimension dimension) + { + + + int min = dimension == Dimension.Width ? us.Frame.Width : us.Frame.Height; + + return min; + } + + internal override bool ReferencesOtherViews () { // BUGBUG: This is not correct. _contentSize may be null. From bfb8fd4b2f3a54370768ee2b343344a47e4b67a9 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 9 Jun 2024 20:21:20 -0600 Subject: [PATCH 07/85] trying a diff version of dimauto --- Terminal.Gui/View/Layout/DimAuto.cs | 142 ++++++++++++++++------------ 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 38ec1e086..6638eca37 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -22,7 +22,6 @@ public class DimAuto () : Dim /// /// Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. /// - // ReSharper disable once ConvertToAutoProperty public required Dim? MaximumContentDim { @@ -35,7 +34,6 @@ public class DimAuto () : Dim /// /// Gets the minimum dimension the View's ContentSize will be constrained to. /// - // ReSharper disable once ConvertToAutoProperty public required Dim? MinimumContentDim { @@ -48,7 +46,6 @@ public class DimAuto () : Dim /// /// Gets the style of the DimAuto. /// - // ReSharper disable once ConvertToAutoProperty public required DimAutoStyle Style { @@ -86,11 +83,10 @@ public class DimAuto () : Dim // TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451). subviewsSize = 0; - List includedSubviews = us.Subviews.ToList (); //.Where (v => !v.ExcludeFromLayout).ToList (); + List includedSubviews = us.Subviews.ToList ();//.Where (v => !v.ExcludeFromLayout).ToList (); List subviews; #region Not Anchored and Are Not Dependent - // Start with subviews that are not anchored to the end, aligned, or dependent on content size // [x] PosAnchorEnd // [x] PosAlign @@ -104,25 +100,19 @@ public class DimAuto () : Dim // [ ] DimView if (dimension == Dimension.Width) { - subviews = includedSubviews.Where ( - v => v.X is not PosAnchorEnd - && v.X is not PosAlign - + subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd + && v.X is not PosAlign // && v.X is not PosCenter && v.Width is not DimAuto - && v.Width is not DimFill) - .ToList (); + && v.Width is not DimFill).ToList (); } else { - subviews = includedSubviews.Where ( - v => v.Y is not PosAnchorEnd - && v.Y is not PosAlign - + subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd + && v.Y is not PosAlign // && v.Y is not PosCenter && v.Height is not DimAuto - && v.Height is not DimFill) - .ToList (); + && v.Height is not DimFill).ToList (); } for (var i = 0; i < subviews.Count; i++) @@ -137,11 +127,9 @@ public class DimAuto () : Dim subviewsSize = size; } } - #endregion Not Anchored and Are Not Dependent #region Anchored - // Now, handle subviews that are anchored to the end // [x] PosAnchorEnd if (dimension == Dimension.Width) @@ -154,7 +142,6 @@ public class DimAuto () : Dim } int maxAnchorEnd = 0; - for (var i = 0; i < subviews.Count; i++) { View v = subviews [i]; @@ -162,9 +149,52 @@ public class DimAuto () : Dim } subviewsSize += maxAnchorEnd; - #endregion Anchored + #region Aligned + + // Now, handle subviews that are anchored to the end + // [x] PosAnchorEnd + int maxAlign = 0; + if (dimension == Dimension.Width) + { + // Use Linq to get a list of distinct GroupIds from the subviews + List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); + + foreach (var groupId in groupIds) + { + List dimensionsList = new (); + + // PERF: If this proves a perf issue, consider caching a ref to this list in each item + List posAlignsInGroup = includedSubviews.Where ( + v => + { + return dimension switch + { + Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, + Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, + _ => false + }; + }) + .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) + .ToList (); + + if (posAlignsInGroup.Count == 0) + { + continue; + } + + maxAlign = posAlignsInGroup [0].CalculateMinDimension (groupId, includedSubviews, dimension); + } + } + else + { + subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); + } + + subviewsSize = int.Max (subviewsSize, maxAlign); + #endregion Aligned + #region Auto @@ -178,12 +208,19 @@ public class DimAuto () : Dim } int maxAuto = 0; - for (var i = 0; i < subviews.Count; i++) { View v = subviews [i]; - maxAuto = CalculateMinDimension (us, dimension); + //if (dimension == Dimension.Width) + //{ + // v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0)); + //} + //else + //{ + // v.SetRelativeLayout (new Size (0, autoMax - subviewsSize)); + //} + maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; if (maxAuto > subviewsSize) { @@ -192,40 +229,32 @@ public class DimAuto () : Dim } } - // subviewsSize += maxAuto; +// subviewsSize += maxAuto; #endregion Auto - #region Center + //#region Center + //// Now, handle subviews that are Centered + //if (dimension == Dimension.Width) + //{ + // subviews = us.Subviews.Where (v => v.X is PosCenter).ToList (); + //} + //else + //{ + // subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList (); + //} - // Now, handle subviews that are Centered - if (dimension == Dimension.Width) - { - subviews = us.Subviews.Where (v => v.X is PosCenter).ToList (); - } - else - { - subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList (); - } + //int maxCenter = 0; + //for (var i = 0; i < subviews.Count; i++) + //{ + // View v = subviews [i]; + // maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; + //} - int maxCenter = 0; - - for (var i = 0; i < subviews.Count; i++) - { - View v = subviews [i]; - maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; - - if (maxCenter > subviewsSize) - { - // BUGBUG: Should we break here? Or choose min/max? - subviewsSize = maxCenter; - } - } - - #endregion Center + //subviewsSize += maxCenter; + //#endregion Center #region Are Dependent - // Now, go back to those that are dependent on content size // [x] DimFill // [ ] DimPercent @@ -239,7 +268,6 @@ public class DimAuto () : Dim } int maxFill = 0; - for (var i = 0; i < subviews.Count; i++) { View v = subviews [i]; @@ -252,12 +280,10 @@ public class DimAuto () : Dim { v.SetRelativeLayout (new Size (0, autoMax - subviewsSize)); } - maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height; } subviewsSize += maxFill; - #endregion Are Dependent } } @@ -283,16 +309,6 @@ public class DimAuto () : Dim return int.Min (max, autoMax); } - internal int CalculateMinDimension (View us, Dimension dimension) - { - - - int min = dimension == Dimension.Width ? us.Frame.Width : us.Frame.Height; - - return min; - } - - internal override bool ReferencesOtherViews () { // BUGBUG: This is not correct. _contentSize may be null. From 9612d7fbb9b5cddc364324b464fba459263f2434 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 9 Jun 2024 22:27:07 -0600 Subject: [PATCH 08/85] Reworked Shortcut --- Terminal.Gui/View/Layout/DimAuto.cs | 80 ++++++++++---------- Terminal.Gui/View/Layout/PosAlign.cs | 4 +- Terminal.Gui/View/ViewKeyboard.cs | 8 +- Terminal.Gui/Views/Shortcut.cs | 104 +++++++++++++++----------- UICatalog/Scenarios/Shortcuts.cs | 106 +++++++++++++++++++++++++++ 5 files changed, 216 insertions(+), 86 deletions(-) create mode 100644 UICatalog/Scenarios/Shortcuts.cs diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 6638eca37..e00109f3b 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -151,49 +151,49 @@ public class DimAuto () : Dim subviewsSize += maxAnchorEnd; #endregion Anchored - #region Aligned + //#region Aligned - // Now, handle subviews that are anchored to the end - // [x] PosAnchorEnd - int maxAlign = 0; - if (dimension == Dimension.Width) - { - // Use Linq to get a list of distinct GroupIds from the subviews - List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); + //// Now, handle subviews that are anchored to the end + //// [x] PosAnchorEnd + //int maxAlign = 0; + //if (dimension == Dimension.Width) + //{ + // // Use Linq to get a list of distinct GroupIds from the subviews + // List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList (); - foreach (var groupId in groupIds) - { - List dimensionsList = new (); + // foreach (var groupId in groupIds) + // { + // List dimensionsList = new (); - // PERF: If this proves a perf issue, consider caching a ref to this list in each item - List posAlignsInGroup = includedSubviews.Where ( - v => - { - return dimension switch - { - Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, - Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, - _ => false - }; - }) - .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) - .ToList (); + // // PERF: If this proves a perf issue, consider caching a ref to this list in each item + // List posAlignsInGroup = includedSubviews.Where ( + // v => + // { + // return dimension switch + // { + // Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, + // Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, + // _ => false + // }; + // }) + // .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign) + // .ToList (); - if (posAlignsInGroup.Count == 0) - { - continue; - } + // if (posAlignsInGroup.Count == 0) + // { + // continue; + // } - maxAlign = posAlignsInGroup [0].CalculateMinDimension (groupId, includedSubviews, dimension); - } - } - else - { - subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); - } + // maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension); + // } + //} + //else + //{ + // subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList (); + //} - subviewsSize = int.Max (subviewsSize, maxAlign); - #endregion Aligned + //subviewsSize = int.Max (subviewsSize, maxAlign); + //#endregion Aligned #region Auto @@ -295,9 +295,11 @@ public class DimAuto () : Dim // And, if min: is set, it wins if larger max = int.Max (max, autoMin); + // And, if max: is set, it wins if smaller + max = int.Min (max, autoMax); + // Factor in adornments Thickness thickness = us.GetAdornmentsThickness (); - max += dimension switch { Dimension.Width => thickness.Horizontal, @@ -306,7 +308,7 @@ public class DimAuto () : Dim _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null) }; - return int.Min (max, autoMax); + return max; } internal override bool ReferencesOtherViews () diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs index 31bb81a26..555eea2eb 100644 --- a/Terminal.Gui/View/Layout/PosAlign.cs +++ b/Terminal.Gui/View/Layout/PosAlign.cs @@ -168,7 +168,7 @@ public class PosAlign : Pos return 0; } - internal int CalculateMinDimension (int groupId, IList views, Dimension dimension) + public static int CalculateMinDimension (int groupId, IList views, Dimension dimension) { List dimensionsList = new (); @@ -217,6 +217,6 @@ public class PosAlign : Pos aligner.ContainerSize = dimensionsList.Sum(); int [] locations = aligner.Align (dimensionsList.ToArray ()); - return locations.Sum (); + return dimensionsList.Sum (); } } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 7863c6799..935765bf9 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -717,6 +717,12 @@ public partial class View return true; } } + + bool recurse = subview.ProcessSubViewKeyBindings (keyEvent, ref handled); + if (recurse || (handled is { } && (bool)handled)) + { + return true; + } } return false; @@ -871,7 +877,7 @@ public partial class View /// The function. protected void AddCommand (Command command, Func f) { - CommandImplementations [command] = ctx => f (); ; + CommandImplementations [command] = ctx => f (); } /// Returns all commands that are supported by this . diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index a5ca12d3f..fbd87860f 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -34,35 +34,23 @@ namespace Terminal.Gui; /// public class Shortcut : View { - // Hosts the Command, Help, and Key Views. Needed (IIRC - wrote a long time ago) to allow mouse clicks to be handled by the Shortcut. - internal readonly View _container; - /// /// Creates a new instance of . /// public Shortcut () { + Id= "_shortcut"; + HighlightStyle = HighlightStyle.Pressed; + Highlight += Shortcut_Highlight; CanFocus = true; - Width = Dim.Auto (DimAutoStyle.Content); - Height = Dim.Auto (DimAutoStyle.Content); + Width = Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); + Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1); - //Height = Dim.Auto (minimumContentDim: 1, maximumContentDim: 1); - - AddCommand (Gui.Command.HotKey, () => true); + AddCommand (Gui.Command.HotKey, OnAccept); AddCommand (Gui.Command.Accept, OnAccept); KeyBindings.Add (KeyCode.Space, Gui.Command.Accept); KeyBindings.Add (KeyCode.Enter, Gui.Command.Accept); - _container = new () - { - Id = "_container", - // Only the Shortcut (_container) should be able to have focus, not any subviews. - CanFocus = true, - Width = Dim.Auto (DimAutoStyle.Content, 1), - Height = Dim.Auto (DimAutoStyle.Content, 1), - BorderStyle = LineStyle.Dashed - }; - CommandView = new (); HelpView = new () @@ -70,15 +58,15 @@ public class Shortcut : View Id = "_helpView", // Only the Shortcut should be able to have focus, not any subviews CanFocus = false, - X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems), - Y = Pos.Center (), + X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast), + Y = 0,//Pos.Center (), // Helpview is the only subview that doesn't have a min width Width = Dim.Auto (DimAutoStyle.Text), Height = Dim.Auto (DimAutoStyle.Text), - ColorScheme = Colors.ColorSchemes ["Error"] }; - _container.Add (HelpView); + HelpView.Margin.Thickness = new Thickness (1, 0, 1, 0); + Add (HelpView); // HelpView.TextAlignment = Alignment.End; HelpView.MouseClick += Shortcut_MouseClick; @@ -88,28 +76,24 @@ public class Shortcut : View Id = "_keyView", // Only the Shortcut should be able to have focus, not any subviews CanFocus = false, - X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems), - Y = Pos.Center (), + X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast), + Y = 0,//Pos.Center (), // Bar will set the width of all KeyViews to the width of the widest KeyView. Width = Dim.Auto (DimAutoStyle.Text), Height = Dim.Auto (DimAutoStyle.Text), }; - _container.Add (KeyView); + KeyView.Margin.Thickness = new Thickness (1, 0, 1, 0); + Add (KeyView); KeyView.MouseClick += Shortcut_MouseClick; - CommandView.Margin.Thickness = new Thickness (1, 0, 1, 0); - HelpView.Margin.Thickness = new Thickness (1, 0, 1, 0); - KeyView.Margin.Thickness = new Thickness (1, 0, 1, 0); MouseClick += Shortcut_MouseClick; TitleChanged += Shortcut_TitleChanged; Initialized += OnInitialized; - Add (_container); - return; void OnInitialized (object sender, EventArgs e) @@ -126,6 +110,36 @@ public class Shortcut : View } } + private Color? _savedForeColor; + + private void Shortcut_Highlight (object sender, HighlightEventArgs e) + { + if (e.HighlightStyle.HasFlag (HighlightStyle.Pressed)) + { + if (!_savedForeColor.HasValue) + { + _savedForeColor = ColorScheme.Normal.Foreground; + } + + ColorScheme cs = new ColorScheme (ColorScheme) + { + Normal = new Attribute (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background) + }; + ColorScheme = cs; + } + + if (e.HighlightStyle == HighlightStyle.None && _savedForeColor.HasValue) + { + ColorScheme cs = new ColorScheme (ColorScheme) + { + Normal = new Attribute (_savedForeColor.Value, ColorScheme.Normal.Background) + }; + ColorScheme = cs; + } + SuperView?.SetNeedsDisplay (); + e.Cancel = true; + } + private void Shortcut_MouseClick (object sender, MouseEventEventArgs e) { // When the Shortcut is clicked, we want to invoke the Command and Set focus @@ -253,7 +267,7 @@ public class Shortcut : View if (_commandView is { }) { - _container.Remove (_commandView); + Remove (_commandView); _commandView?.Dispose (); } @@ -268,8 +282,8 @@ public class Shortcut : View // Bar will set the width of all CommandViews to the width of the widest CommandViews. _commandView.Width = Dim.Auto (DimAutoStyle.Text); _commandView.Height = Dim.Auto (DimAutoStyle.Text); - _commandView.X = X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast | AlignmentModes.AddSpaceBetweenItems); - _commandView.Y = Pos.Center (); + _commandView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + _commandView.Y = 0;//Pos.Center (); _commandView.MouseClick += Shortcut_MouseClick; _commandView.Accept += CommandView_Accept; @@ -287,11 +301,11 @@ public class Shortcut : View _commandView.HotKeySpecifier = new ('_'); - _container.Remove (HelpView); - _container.Remove (KeyView); - _container.Add (_commandView, HelpView, KeyView); + Remove (HelpView); + Remove (KeyView); + Add (_commandView, HelpView, KeyView); - UpdateKeyBinding(); + UpdateKeyBinding (); } } @@ -319,6 +333,8 @@ public class Shortcut : View { e.Cancel = args.Handled; } + + e.Cancel = true; } #endregion Command @@ -406,7 +422,7 @@ public class Shortcut : View { if (KeyBindingScope == KeyBindingScope.Application) { - // return; + // return; } if (Command != null && Key != null && Key != Key.Empty) @@ -420,7 +436,7 @@ public class Shortcut : View } else { - // throw new InvalidOperationException ($"CommandView does not support the command {Command.Value}"); + // throw new InvalidOperationException ($"CommandView does not support the command {Command.Value}"); } } } @@ -480,7 +496,7 @@ public class Shortcut : View handled = args.Handled; } - return handled; + return true; } /// @@ -493,14 +509,14 @@ public class Shortcut : View HotNormal = ColorScheme.HotFocus }; - _container.ColorScheme = cs; + // _container.ColorScheme = cs; cs = new (ColorScheme) { Normal = ColorScheme.HotFocus, HotNormal = ColorScheme.Focus }; - KeyView.ColorScheme = cs; + //KeyView.ColorScheme = cs; return base.OnEnter (view); } @@ -515,14 +531,14 @@ public class Shortcut : View HotNormal = ColorScheme.HotNormal }; - _container.ColorScheme = cs; + // _container.ColorScheme = cs; cs = new (ColorScheme) { Normal = ColorScheme.HotNormal, HotNormal = ColorScheme.Normal }; - KeyView.ColorScheme = cs; + //KeyView.ColorScheme = cs; return base.OnLeave (view); } diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs new file mode 100644 index 000000000..9027e1f4d --- /dev/null +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("Shortcuts", "Illustrates Shortcut class.")] +[ScenarioCategory ("Controls")] +public class Shortcuts : Scenario +{ + public override void Main () + { + Application.Init (); + Window app = new (); + + app.Loaded += App_Loaded; + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); + } + + + // Setting everything up in Loaded handler because we change the + // QuitKey and it only sticks if changed after init + private void App_Loaded (object sender, EventArgs e) + { + Application.QuitKey = Key.Z.WithCtrl; + Application.Top.Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"; + + ObservableCollection eventSource = new (); + ListView eventLog = new ListView () + { + X = Pos.AnchorEnd (), + Width = 50, + Height = Dim.Fill (), + ColorScheme = Colors.ColorSchemes ["Toplevel"], + Source = new ListWrapper (eventSource) + }; + Application.Top.Add (eventLog); + + var shortcut1 = new Shortcut + { + Title = "Zi_gzag", + Key = Key.F1, + Text = "Gonna zig zag", + KeyBindingScope = KeyBindingScope.HotKey, + Command = Command.Accept, + BorderStyle = LineStyle.Dotted + }; + shortcut1.Border.Thickness = new Thickness (1, 0, 1, 0); + shortcut1.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + }; + Application.Top.Add (shortcut1); + + var shortcut2 = new Shortcut + { + Y = Pos.Bottom (shortcut1), + Width = Dim.Width(shortcut1), + Title = "_Two", + Key = Key.F2.WithAlt, + Text = "Number two", + KeyBindingScope = KeyBindingScope.HotKey, + Command = Command.Accept, + BorderStyle = LineStyle.Dotted + }; + shortcut2.Border.Thickness = new Thickness (1, 0, 1, 0); + shortcut2.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + }; + Application.Top.Add (shortcut2); + + var shortcut3 = new Shortcut + { + Y = Pos.Bottom (shortcut2), + Width = Dim.Width (shortcut1), + Title = "T_hree", + Key = Key.F3, + Text = "Number 3", + KeyBindingScope = KeyBindingScope.HotKey, + Command = Command.Accept, + BorderStyle = LineStyle.Dotted + }; + shortcut3.Border.Thickness = new Thickness (1, 0, 1, 0); + + shortcut3.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + }; + Application.Top.Add (shortcut3); + + shortcut1.SetFocus (); + + } + + private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } + +} From dd54e9d5d3240ddf7eb517699ab0772c9de848d7 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 11 Jun 2024 14:00:38 -0700 Subject: [PATCH 09/85] Got Shortcut working great. Needs more unit tests --- Terminal.Gui/Input/KeyBindingScope.cs | 3 + Terminal.Gui/View/Layout/PosAlign.cs | 8 +- Terminal.Gui/Views/Shortcut.cs | 417 +++++++++++++++++--------- UICatalog/Scenarios/Bars.cs | 8 +- UICatalog/Scenarios/Shortcuts.cs | 94 +++++- UnitTests/Views/ShortcutTests.cs | 198 ++++++++++++ 6 files changed, 578 insertions(+), 150 deletions(-) create mode 100644 UnitTests/Views/ShortcutTests.cs diff --git a/Terminal.Gui/Input/KeyBindingScope.cs b/Terminal.Gui/Input/KeyBindingScope.cs index 9799fc831..3b6c53ebc 100644 --- a/Terminal.Gui/Input/KeyBindingScope.cs +++ b/Terminal.Gui/Input/KeyBindingScope.cs @@ -13,6 +13,9 @@ namespace Terminal.Gui; [GenerateEnumExtensionMethods (FastHasFlags = true)] public enum KeyBindingScope { + /// The key binding is disabled. + Disabled = 0, + /// The key binding is scoped to just the view that has focus. Focused = 1, diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs index 555eea2eb..306017d65 100644 --- a/Terminal.Gui/View/Layout/PosAlign.cs +++ b/Terminal.Gui/View/Layout/PosAlign.cs @@ -29,7 +29,7 @@ public class PosAlign : Pos /// /// The cached location. Used to store the calculated location to minimize recalculating it. /// - private int? _cachedLocation; + public int? _cachedLocation; /// /// Gets the identifier of a set of views that should be aligned together. When only a single @@ -206,6 +206,12 @@ public class PosAlign : Pos if (index == 0) { firstInGroup = posAlign.Aligner; + + //if (!posAlign._cachedLocation.HasValue) + //{ + // AlignAndUpdateGroup (groupId, viewsInGroup, dimension, firstInGroup.ContainerSize ); + //} + } dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height); diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index fbd87860f..cfb3580b7 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -7,7 +7,7 @@ namespace Terminal.Gui; // TODO: I tried `BarItem` but that's not great either as it implies it can only be used in `Bar`s. /// -/// Displays a command, help text, and a key binding. Useful for displaying a command in such as a +/// Displays a command, help text, and a key binding. When the key is pressed, the command will be invoked. Useful for displaying a command in such as a /// menu, toolbar, or status bar. /// /// @@ -17,18 +17,20 @@ namespace Terminal.Gui; /// event to be fired /// /// -/// If is , the +/// If is , the command /// be invoked regardless of what View has focus, enabling an application-wide keyboard shortcut. /// /// -/// Set to change the Command text displayed in the . -/// By default, the text is the of . +/// A Shortcut displays the command text on the left side, the help text in the middle, and the key binding on the right side. /// /// -/// Set to change the Help text displayed in the . +/// The command text can be set by setting the 's Text property or by setting . /// /// -/// The text displayed for the is the string representation of the . +/// The help text can be set by setting the property or by setting . +/// +/// +/// The key text is set by setting the property. /// If the is , the text is not displayed. /// /// @@ -39,65 +41,63 @@ public class Shortcut : View /// public Shortcut () { - Id= "_shortcut"; + Id = "_shortcut"; HighlightStyle = HighlightStyle.Pressed; Highlight += Shortcut_Highlight; CanFocus = true; - Width = Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); - Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1); + Width = GetWidthDimAuto (); + Height = Dim.Auto (DimAutoStyle.Content, 1); AddCommand (Gui.Command.HotKey, OnAccept); AddCommand (Gui.Command.Accept, OnAccept); KeyBindings.Add (KeyCode.Space, Gui.Command.Accept); KeyBindings.Add (KeyCode.Enter, Gui.Command.Accept); - CommandView = new (); + TitleChanged += Shortcut_TitleChanged; // This needs to be set before CommandView is set - HelpView = new () - { - Id = "_helpView", - // Only the Shortcut should be able to have focus, not any subviews - CanFocus = false, - X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast), - Y = 0,//Pos.Center (), + CommandView = new View (); - // Helpview is the only subview that doesn't have a min width - Width = Dim.Auto (DimAutoStyle.Text), - Height = Dim.Auto (DimAutoStyle.Text), - }; - HelpView.Margin.Thickness = new Thickness (1, 0, 1, 0); + HelpView.Id = "_helpView"; + HelpView.CanFocus = false; + SetHelpViewDefaultLayout (); Add (HelpView); // HelpView.TextAlignment = Alignment.End; HelpView.MouseClick += Shortcut_MouseClick; - KeyView = new () - { - Id = "_keyView", - // Only the Shortcut should be able to have focus, not any subviews - CanFocus = false, - X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast), - Y = 0,//Pos.Center (), + KeyView.Id = "_keyView"; - // Bar will set the width of all KeyViews to the width of the widest KeyView. - Width = Dim.Auto (DimAutoStyle.Text), - Height = Dim.Auto (DimAutoStyle.Text), - }; - KeyView.Margin.Thickness = new Thickness (1, 0, 1, 0); + // Only the Shortcut should be able to have focus, not any subviews + KeyView.CanFocus = false; + + // Right align the text in the keyview + KeyView.TextAlignment = Alignment.End; + + SetKeyViewDefaultLayout (); Add (KeyView); KeyView.MouseClick += Shortcut_MouseClick; - MouseClick += Shortcut_MouseClick; - TitleChanged += Shortcut_TitleChanged; Initialized += OnInitialized; + LayoutStarted += OnLayoutStarted; + return; void OnInitialized (object sender, EventArgs e) { + ShowHide (CommandView); + ShowHide (HelpView); + ShowHide (KeyView); + + // Force Width to DimAuto to calculate natural width and then set it back + Dim savedDim = Width; + Width = GetWidthDimAuto (); + _naturalWidth = Frame.Width; + Width = savedDim; + if (ColorScheme != null) { var cs = new ColorScheme (ColorScheme) @@ -105,9 +105,104 @@ public class Shortcut : View Normal = ColorScheme.HotNormal, HotNormal = ColorScheme.Normal }; + KeyView.ColorScheme = cs; } } + + Dim GetWidthDimAuto () + { + return Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); + } + + } + + // When one of the subviews is "empty" we don't want to show it. So we + // Use Add/Remove. We need to be careful to add them in the right order + // so Pos.Align works correctly. + private void ShowHide (View subView) + { + RemoveAll (); + if (!string.IsNullOrEmpty (CommandView.Text)) + { + Add (CommandView); + } + if (!string.IsNullOrEmpty (HelpView.Text)) + { + Add (HelpView); + } + if (Key != Key.Empty) + { + Add (KeyView); + } + } + + private int? _naturalWidth; + + private void OnLayoutStarted (object sender, LayoutEventArgs e) + { + if (Width is DimAuto widthAuto) + { + _naturalWidth = Frame.Width; + } + else + { + if (string.IsNullOrEmpty (HelpView.Text)) + { + return; + } + + int currentWidth = Frame.Width; + + // If our width is smaller than the natural then reduce width of HelpView. + if (currentWidth < _naturalWidth) + { + int delta = _naturalWidth.Value - currentWidth; + int maxHelpWidth = int.Max (0, HelpView.Text.GetColumns () + 2 - delta); + + switch (maxHelpWidth) + { + case 0: + // Hide HelpView + HelpView.Visible = false; + HelpView.X = 0; + + break; + + case 1: + // Scrunch it by removing margins + HelpView.Margin.Thickness = new (0, 0, 0, 0); + + break; + + case 2: + // Scrunch just the right margin + HelpView.Margin.Thickness = new (1, 0, 0, 0); + + break; + + default: + // Default margin + HelpView.Margin.Thickness = new (1, 0, 1, 0); + + break; + } + + if (maxHelpWidth > 0) + { + HelpView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + + // Leverage Dim.Auto's max: + HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: maxHelpWidth); + HelpView.Visible = true; + } + } + else + { + // Reset to default + SetHelpViewDefaultLayout (); + } + } } private Color? _savedForeColor; @@ -121,21 +216,22 @@ public class Shortcut : View _savedForeColor = ColorScheme.Normal.Foreground; } - ColorScheme cs = new ColorScheme (ColorScheme) + var cs = new ColorScheme (ColorScheme) { - Normal = new Attribute (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background) + Normal = new (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background) }; ColorScheme = cs; } if (e.HighlightStyle == HighlightStyle.None && _savedForeColor.HasValue) { - ColorScheme cs = new ColorScheme (ColorScheme) + var cs = new ColorScheme (ColorScheme) { - Normal = new Attribute (_savedForeColor.Value, ColorScheme.Normal.Background) + Normal = new (_savedForeColor.Value, ColorScheme.Normal.Background) }; ColorScheme = cs; } + SuperView?.SetNeedsDisplay (); e.Cancel = true; } @@ -143,21 +239,32 @@ public class Shortcut : View private void Shortcut_MouseClick (object sender, MouseEventEventArgs e) { // When the Shortcut is clicked, we want to invoke the Command and Set focus - View view = sender as View; - if (!e.Handled && Command.HasValue) + var view = sender as View; + if (view != CommandView) + { + CommandView.InvokeCommand (Command.Accept); + e.Handled = true; + + return; + } + + if (!e.Handled) { // If the subview (likely CommandView) didn't handle the mouse click, invoke the command. bool? handled = false; - handled = InvokeCommand (Command.Value); + handled = InvokeCommand (Command.Accept); + if (handled.HasValue) { e.Handled = handled.Value; } } + if (CanFocus) { SetFocus (); } + e.Handled = true; } @@ -191,26 +298,7 @@ public class Shortcut : View #region Command - private Command? _command; - - /// - /// Gets or sets the that will be invoked when the user clicks on the or - /// presses . - /// - public Command? Command - { - get => _command; - set - { - if (value != null) - { - _command = value.Value; - UpdateKeyBinding (); - } - } - } - - private View _commandView; + private View _commandView = new (); /// /// Gets or sets the View that displays the command text and hotkey. @@ -239,7 +327,6 @@ public class Shortcut : View /// { /// Key = Key.F6, /// KeyBindingScope = KeyBindingScope.HotKey, - /// Command = Command.Accept, /// CommandView = new CheckBox { Text = "Force 16 Colors" } /// }; /// var cb = force16ColorsShortcut.CommandView as CheckBox; @@ -271,7 +358,6 @@ public class Shortcut : View _commandView?.Dispose (); } - _commandView = value; _commandView.Id = "_commandView"; @@ -283,10 +369,10 @@ public class Shortcut : View _commandView.Width = Dim.Auto (DimAutoStyle.Text); _commandView.Height = Dim.Auto (DimAutoStyle.Text); _commandView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); - _commandView.Y = 0;//Pos.Center (); + _commandView.Y = 0; //Pos.Center (); _commandView.MouseClick += Shortcut_MouseClick; - _commandView.Accept += CommandView_Accept; + _commandView.Accept += CommandViewAccept; _commandView.Margin.Thickness = new (1, 0, 1, 0); @@ -301,42 +387,51 @@ public class Shortcut : View _commandView.HotKeySpecifier = new ('_'); + Title = _commandView.Text; + _commandView.TextChanged += CommandViewTextChanged; + Remove (HelpView); Remove (KeyView); Add (_commandView, HelpView, KeyView); + ShowHide (_commandView); UpdateKeyBinding (); - } - } + return; - private void _commandView_MouseEvent (object sender, MouseEventEventArgs e) - { - e.Handled = true; + void CommandViewMouseEvent (object sender, MouseEventEventArgs e) { e.Handled = true; } + + void CommandViewTextChanged (object sender, StateEventArgs e) + { + Title = _commandView.Text; + ShowHide (_commandView); + } + + void CommandViewAccept (object sender, CancelEventArgs e) + { + // When the CommandView fires its Accept event, we want to act as though the + // Shortcut was clicked. + var args = new HandledEventArgs (); + Accept?.Invoke (this, args); + + if (args.Handled) + { + e.Cancel = args.Handled; + } + + //e.Cancel = true; + } + } } private void Shortcut_TitleChanged (object sender, StateEventArgs e) { - // If the Title changes, update the CommandView text. This is a helper to make it easier to set the CommandView text. + // If the Title changes, update the CommandView text. + // This is a helper to make it easier to set the CommandView text. // CommandView is public and replaceable, but this is a convenience. _commandView.Text = Title; } - private void CommandView_Accept (object sender, CancelEventArgs e) - { - // When the CommandView fires its Accept event, we want to act as though the - // Shortcut was clicked. - var args = new HandledEventArgs (); - Accept?.Invoke (this, args); - - if (args.Handled) - { - e.Cancel = args.Handled; - } - - e.Cancel = true; - } - #endregion Command #region Help @@ -344,20 +439,47 @@ public class Shortcut : View /// /// The subview that displays the help text for the command. Internal for unit testing. /// - internal View HelpView { get; set; } + internal View HelpView { get; } = new (); + + private void SetHelpViewDefaultLayout () + { + HelpView.Margin.Thickness = new (1, 0, 1, 0); + HelpView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + HelpView.Y = 0; //Pos.Center (), + HelpView.Width = Dim.Auto (DimAutoStyle.Text); + HelpView.Height = Dim.Auto (DimAutoStyle.Text); + HelpView.Visible = true; + } + + /// + /// Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to + /// . + /// + public override string Text + { + get => HelpView?.Text; + set + { + if (HelpView != null) + { + HelpView.Text = value; + ShowHide (HelpView); + } + } + } /// /// Gets or sets the help text displayed in the middle of the Shortcut. /// - public override string Text + public string HelpText { - get => base.Text; + get => HelpView?.Text; set { - //base.Text = value; if (HelpView != null) { HelpView.Text = value; + ShowHide (HelpView); } } } @@ -366,7 +488,7 @@ public class Shortcut : View #region Key - private Key _key; + private Key _key = Key.Empty; /// /// Gets or sets the that will be bound to the command. @@ -383,17 +505,14 @@ public class Shortcut : View _key = value; - if (Command != null) - { - UpdateKeyBinding (); - } + UpdateKeyBinding (); - KeyView.Text = $"{Key}"; - KeyView.Visible = Key != Key.Empty; + KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}"; + ShowHide (KeyView); } } - private KeyBindingScope _keyBindingScope; + private KeyBindingScope _keyBindingScope = KeyBindingScope.HotKey; /// /// Gets or sets the scope for the key binding for how is bound to . @@ -405,18 +524,50 @@ public class Shortcut : View { _keyBindingScope = value; - if (Command != null) - { - UpdateKeyBinding (); - } + UpdateKeyBinding (); } } + // TODO: Make internal once Bar is done /// /// Gets the subview that displays the key. Internal for unit testing. /// - internal View KeyView { get; } + public View KeyView { get; } = new (); + + private int _minimumKeyViewSize; + /// + /// + /// + public int MinimumKeyViewSize + { + get => _minimumKeyViewSize; + set + { + if (value == _minimumKeyViewSize) + { + //return; + } + _minimumKeyViewSize = value; + SetKeyViewDefaultLayout(); + CommandView.SetNeedsLayout(); + HelpView.SetNeedsLayout (); + KeyView.SetNeedsLayout (); + SetSubViewNeedsDisplay (); + } + } + + private int GetMinimumKeyViewSize () { return MinimumKeyViewSize; } + + private void SetKeyViewDefaultLayout () + { + KeyView.Margin.Thickness = new (1, 0, 1, 0); + KeyView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + KeyView.Y = 0; //Pos.Center (), + KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func(GetMinimumKeyViewSize)); + KeyView.Height = Dim.Auto (DimAutoStyle.Text); + KeyView.Visible = true; + } private void UpdateKeyBinding () { @@ -425,19 +576,13 @@ public class Shortcut : View // return; } - if (Command != null && Key != null && Key != Key.Empty) + if (Key != null) { // CommandView holds our command/keybinding // Add a key binding for this command to this Shortcut - if (CommandView.GetSupportedCommands ().Contains (Command.Value)) - { - CommandView.KeyBindings.Remove (Key); - CommandView.KeyBindings.Add (Key, KeyBindingScope, Command.Value); - } - else - { - // throw new InvalidOperationException ($"CommandView does not support the command {Command.Value}"); - } + + CommandView.KeyBindings.Remove (Key); + CommandView.KeyBindings.Add (Key, KeyBindingScope, Command.Accept); } } @@ -458,8 +603,6 @@ public class Shortcut : View { // TODO: This is not completely thought through. - - if (Key == null || Key == Key.Empty) { return false; @@ -468,26 +611,27 @@ public class Shortcut : View var handled = false; var keyCopy = new Key (Key); - switch (KeyBindingScope) - { - case KeyBindingScope.Application: - // Simulate a key down to invoke the Application scoped key binding - handled = Application.OnKeyDown (keyCopy); + //switch (KeyBindingScope) + //{ + // case KeyBindingScope.Application: + // // Simulate a key down to invoke the Application scoped key binding + // handled = Application.OnKeyDown (keyCopy); - break; - case KeyBindingScope.Focused: - handled = InvokeCommand (Command.Value) == true; - handled = false; + // break; + // case KeyBindingScope.Focused: + // handled = InvokeCommand (Command.Value) == true; + // handled = false; - break; - case KeyBindingScope.HotKey: - if (Command.HasValue) - { - //handled = _commandView.InvokeCommand (Gui.Command.HotKey) == true; - //handled = false; - } - break; - } + // break; + // case KeyBindingScope.HotKey: + // if (Command.HasValue) + // { + // //handled = _commandView.InvokeCommand (Gui.Command.HotKey) == true; + // //handled = false; + // } + + // break; + //} //if (handled == false) { @@ -516,6 +660,7 @@ public class Shortcut : View Normal = ColorScheme.HotFocus, HotNormal = ColorScheme.Focus }; + //KeyView.ColorScheme = cs; return base.OnEnter (view); @@ -538,8 +683,10 @@ public class Shortcut : View Normal = ColorScheme.HotNormal, HotNormal = ColorScheme.Normal }; + //KeyView.ColorScheme = cs; return base.OnLeave (view); } } + diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 185b14281..03a5fbcf7 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -44,10 +44,9 @@ public class Bars : Scenario var shortcut1 = new Shortcut { Title = "_Zigzag", - Key = Key.Z.WithAlt, + Key = Key.G.WithCtrl, Text = "Gonna zig zag", KeyBindingScope = KeyBindingScope.HotKey, - Command = Command.Accept, }; shortcut1.Accept += (s, e) => { @@ -409,7 +408,7 @@ public class Bars : Scenario Title = "Q_uit", Key = Application.QuitKey, KeyBindingScope = KeyBindingScope.Application, - Command = Command.QuitToplevel, +// Command = Command.QuitToplevel, CanFocus = false }; @@ -421,7 +420,6 @@ public class Bars : Scenario Title = "Help", Key = Key.F1, KeyBindingScope = KeyBindingScope.HotKey, - Command = Command.Accept, CanFocus = false }; @@ -446,7 +444,7 @@ public class Bars : Scenario Title = "_Show/Hide", Key = Key.F10, KeyBindingScope = KeyBindingScope.HotKey, - Command = Command.ToggleExpandCollapse, + //Command = Command.ToggleExpandCollapse, CommandView = new CheckBox { Text = "_Show/Hide" diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 9027e1f4d..c7cf9f6aa 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Text; using Terminal.Gui; @@ -43,13 +44,13 @@ public class Shortcuts : Scenario var shortcut1 = new Shortcut { + //Width =30, Title = "Zi_gzag", Key = Key.F1, Text = "Gonna zig zag", - KeyBindingScope = KeyBindingScope.HotKey, - Command = Command.Accept, + KeyBindingScope = KeyBindingScope.Application, BorderStyle = LineStyle.Dotted - }; + }; shortcut1.Border.Thickness = new Thickness (1, 0, 1, 0); shortcut1.Accept += (s, e) => { @@ -61,12 +62,11 @@ public class Shortcuts : Scenario var shortcut2 = new Shortcut { Y = Pos.Bottom (shortcut1), - Width = Dim.Width(shortcut1), + Width = Dim.Width (shortcut1), Title = "_Two", Key = Key.F2.WithAlt, Text = "Number two", KeyBindingScope = KeyBindingScope.HotKey, - Command = Command.Accept, BorderStyle = LineStyle.Dotted }; shortcut2.Border.Thickness = new Thickness (1, 0, 1, 0); @@ -81,23 +81,99 @@ public class Shortcuts : Scenario { Y = Pos.Bottom (shortcut2), Width = Dim.Width (shortcut1), - Title = "T_hree", + CommandView = new CheckBox () { Text = "_Align" }, Key = Key.F3, - Text = "Number 3", + HelpText = "Alignment", KeyBindingScope = KeyBindingScope.HotKey, - Command = Command.Accept, BorderStyle = LineStyle.Dotted }; shortcut3.Border.Thickness = new Thickness (1, 0, 1, 0); + ((CheckBox)shortcut3.CommandView).Toggled += (s, e) => + { + eventSource.Add ($"Toggled: {s}"); + eventLog.MoveDown (); + + if (shortcut3.CommandView is CheckBox cb) + { + int max = 0; + if (e.NewValue == true) + { + foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) + { + max = Math.Max (max, peer.KeyView.Text.GetColumns ()); + } + + } + foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) + { + peer.MinimumKeyViewSize = max; + } + } + + //Application.Top.SetNeedsDisplay (); + //Application.Top.LayoutSubviews (); + //Application.Top.SetNeedsDisplay (); + //Application.Top.Draw (); + }; shortcut3.Accept += (s, e) => { eventSource.Add ($"Accept: {s}"); eventLog.MoveDown (); + }; Application.Top.Add (shortcut3); - shortcut1.SetFocus (); + var shortcut4 = new Shortcut + { + X = 20, + Y = Pos.Bottom (shortcut3), + Width = Dim.Fill (50), + Title = "C", + Text = "H", + Key = Key.K, + KeyBindingScope = KeyBindingScope.HotKey, + // Command = Command.Accept, + BorderStyle = LineStyle.Dotted + }; + shortcut4.Border.Thickness = new Thickness (1, 0, 1, 0); + shortcut4.Margin.Thickness = new Thickness (0, 1, 0, 0); + View.Diagnostics = ViewDiagnosticFlags.Ruler; + + shortcut4.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + MessageBox.Query ("Hi", $"You clicked {s}"); + }; + Application.Top.Add (shortcut4); + + var shortcut5 = new Shortcut + { + X = 20, + Y = Pos.Bottom (shortcut4), + Width = Dim.Fill (50), + Title = "Fi_ve", + Key = Key.F5.WithCtrl.WithAlt.WithShift, + Text = "Help text", + KeyBindingScope = KeyBindingScope.HotKey, + BorderStyle = LineStyle.Dotted + }; + shortcut5.Border.Thickness = new Thickness (1, 0, 1, 0); + shortcut5.Margin.Thickness = new Thickness (0, 1, 0, 0); + View.Diagnostics = ViewDiagnosticFlags.Ruler; + + shortcut5.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + }; + Application.Top.Add (shortcut5); + + + ((CheckBox)shortcut3.CommandView).OnToggled (); + + //shortcut1.SetFocus (); } diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs new file mode 100644 index 000000000..a3b934626 --- /dev/null +++ b/UnitTests/Views/ShortcutTests.cs @@ -0,0 +1,198 @@ +using System.CommandLine; +using JetBrains.Annotations; +using UICatalog.Scenarios; + +namespace Terminal.Gui.ViewsTests; + +[TestSubject (typeof (Shortcut))] +public class ShortcutTests +{ + + [Fact] + public void Constructor_Defaults () + { + Shortcut shortcut = new Shortcut (); + + Assert.NotNull (shortcut); + Assert.True (shortcut.CanFocus); + Assert.IsType (shortcut.Width); + Assert.IsType (shortcut.Height); + + // TOOD: more + } + + [Theory] + [InlineData ("", "", KeyCode.Null, 0)] + [InlineData ("C", "", KeyCode.Null, 3)] + [InlineData ("", "H", KeyCode.Null, 3)] + [InlineData ("", "", KeyCode.K, 3)] + [InlineData ("C", "", KeyCode.K, 6)] + [InlineData ("C", "H", KeyCode.Null, 6)] + [InlineData ("", "H", KeyCode.K, 6)] + [InlineData ("C", "H", KeyCode.K, 9)] + public void NaturalSize (string command, string help, Key key, int expectedWidth) + { + Shortcut shortcut = new Shortcut () + { + Title = command, + Text = help, + Key = key, + }; + + Assert.IsType (shortcut.Width); + Assert.IsType (shortcut.Height); + + shortcut.LayoutSubviews (); + shortcut.SetRelativeLayout (new Size (100, 100)); + + // |0123456789 + // | C H K | + Assert.Equal (expectedWidth, shortcut.Frame.Width); + } + + [Theory] + [InlineData (5, 0, 3, 6)] + [InlineData (6, 0, 3, 6)] + [InlineData (7, 0, 3, 6)] + [InlineData (8, 0, 3, 6)] + [InlineData (9, 0, 3, 6)] + [InlineData (10, 0, 4, 7)] + [InlineData (11, 0, 5, 8)] + public void Set_Width_Layouts_Correctly (int width, int expectedCmdX, int expectedHelpX, int expectedKeyX) + { + Shortcut shortcut = new Shortcut () + { + Width = width, + Title = "C", + Text = "H", + Key = Key.K + }; + + shortcut.LayoutSubviews (); + shortcut.SetRelativeLayout (new Size (100, 100)); + + // 0123456789 + // -C--H--K- + Assert.Equal (expectedCmdX, shortcut.CommandView.Frame.X); + Assert.Equal (expectedHelpX, shortcut.HelpView.Frame.X); + Assert.Equal (expectedKeyX, shortcut.KeyView.Frame.X); + } + + [Fact] + public void CommandView_Text_And_Title_Are_The_Same () + { + Shortcut shortcut = new Shortcut () + { + Title = "T", + }; + + Assert.Equal (shortcut.Title, shortcut.CommandView.Text); + + shortcut = new Shortcut () + { + }; + shortcut.CommandView.Text = "T"; + Assert.Equal (shortcut.Title, shortcut.CommandView.Text); + + shortcut = new Shortcut () + { + }; + shortcut.CommandView = new View () + { + Text = "T" + }; + Assert.Equal (shortcut.Title, shortcut.CommandView.Text); + } + + [Fact] + public void HelpText_And_Text_Are_The_Same () + { + Shortcut shortcut = new Shortcut () + { + Text = "H", + }; + + Assert.Equal (shortcut.Text, shortcut.HelpText); + + shortcut = new Shortcut () + { + HelpText = "H", + }; + + Assert.Equal (shortcut.Text, shortcut.HelpText); + } + + [Theory] + [InlineData (KeyCode.Null, "")] + [InlineData (KeyCode.F1, "F1")] + public void KeyView_Text_Tracks_Key (Key key, string expected) + { + Shortcut shortcut = new Shortcut () + { + Key = key, + }; + + Assert.Equal (expected, shortcut.KeyView.Text); + } + + // Test Key + [Fact] + public void Key_Defaults_To_Empty () + { + Shortcut shortcut = new Shortcut (); + + Assert.Equal (Key.Empty, shortcut.Key); + } + + [Fact] + public void Key_Can_Be_Set () + { + Shortcut shortcut = new Shortcut (); + + shortcut.Key = Key.F1; + + Assert.Equal (Key.F1, shortcut.Key); + } + + [Fact] + public void Key_Can_Be_Set_To_Empty () + { + Shortcut shortcut = new Shortcut (); + + shortcut.Key = Key.Empty; + + Assert.Equal (Key.Empty, shortcut.Key); + } + + // Test KeyBindingScope + + // Test Key gets bound correctly + [Fact] + public void KeyBindingScope_Defaults_To_HotKey () + { + Shortcut shortcut = new Shortcut (); + + Assert.Equal (KeyBindingScope.HotKey, shortcut.KeyBindingScope); + } + + [Fact] + public void KeyBindingScope_Can_Be_Set () + { + Shortcut shortcut = new Shortcut (); + + shortcut.KeyBindingScope = KeyBindingScope.Application; + + Assert.Equal (KeyBindingScope.Application, shortcut.KeyBindingScope); + } + + [Fact] + public void Setting_Key_Binds_Key_To_CommandView_Accept () + { + Shortcut shortcut = new Shortcut (); + + shortcut.Key = Key.F1; + + // Assert.Equal (Command.Accept, shortcut.CommandView.Get); + } + +} From 8fe8e5355c2a42d8f61b97b52a534099a767f4b9 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 11 Jun 2024 14:08:50 -0700 Subject: [PATCH 10/85] Added horiz example --- UICatalog/Scenarios/Shortcuts.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index c7cf9f6aa..79f670eb6 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -124,6 +124,25 @@ public class Shortcuts : Scenario }; Application.Top.Add (shortcut3); + + var shortcutH = new Shortcut + { + Y = Pos.Top (shortcut3), + X = Pos.Right (shortcut3), + Title = "Horizo_ntal", + Key = Key.F10, + Text = "Hey!", + KeyBindingScope = KeyBindingScope.HotKey, + BorderStyle = LineStyle.Dotted + }; + shortcutH.Border.Thickness = new Thickness (0, 0, 1, 0); + shortcutH.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + }; + Application.Top.Add (shortcutH); + var shortcut4 = new Shortcut { X = 20, From 9645d6fcb2c3167d596899f7aa6b263e268d6587 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 12 Jun 2024 09:31:00 -0700 Subject: [PATCH 11/85] Refinements. Added OnAccept call to Sliser --- Terminal.Gui/View/Layout/DimAuto.cs | 4 ++ Terminal.Gui/Views/Shortcut.cs | 36 ++++++------ Terminal.Gui/Views/Slider.cs | 3 +- UICatalog/Scenarios/Shortcuts.cs | 89 +++++++++++++++++------------ 4 files changed, 77 insertions(+), 55 deletions(-) diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index e00109f3b..5d34a9729 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -272,6 +272,10 @@ public class DimAuto () : Dim { View v = subviews [i]; + if (autoMax == int.MaxValue) + { + autoMax = superviewContentSize; + } if (dimension == Dimension.Width) { v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0)); diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index cfb3580b7..526da816d 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -70,9 +70,6 @@ public class Shortcut : View // Only the Shortcut should be able to have focus, not any subviews KeyView.CanFocus = false; - // Right align the text in the keyview - KeyView.TextAlignment = Alignment.End; - SetKeyViewDefaultLayout (); Add (KeyView); @@ -88,9 +85,7 @@ public class Shortcut : View void OnInitialized (object sender, EventArgs e) { - ShowHide (CommandView); - ShowHide (HelpView); - ShowHide (KeyView); + ShowHide (); // Force Width to DimAuto to calculate natural width and then set it back Dim savedDim = Width; @@ -120,7 +115,7 @@ public class Shortcut : View // When one of the subviews is "empty" we don't want to show it. So we // Use Add/Remove. We need to be careful to add them in the right order // so Pos.Align works correctly. - private void ShowHide (View subView) + private void ShowHide () { RemoveAll (); if (!string.IsNullOrEmpty (CommandView.Text)) @@ -366,8 +361,8 @@ public class Shortcut : View _commandView.CanFocus = false; // Bar will set the width of all CommandViews to the width of the widest CommandViews. - _commandView.Width = Dim.Auto (DimAutoStyle.Text); - _commandView.Height = Dim.Auto (DimAutoStyle.Text); + _commandView.Width = Dim.Auto (); + _commandView.Height = Dim.Auto (); _commandView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); _commandView.Y = 0; //Pos.Center (); @@ -394,7 +389,7 @@ public class Shortcut : View Remove (KeyView); Add (_commandView, HelpView, KeyView); - ShowHide (_commandView); + ShowHide (); UpdateKeyBinding (); return; @@ -404,7 +399,7 @@ public class Shortcut : View void CommandViewTextChanged (object sender, StateEventArgs e) { Title = _commandView.Text; - ShowHide (_commandView); + ShowHide (); } void CommandViewAccept (object sender, CancelEventArgs e) @@ -445,10 +440,12 @@ public class Shortcut : View { HelpView.Margin.Thickness = new (1, 0, 1, 0); HelpView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); - HelpView.Y = 0; //Pos.Center (), + HelpView.Y = 0; //Pos.Center (), HelpView.Width = Dim.Auto (DimAutoStyle.Text); - HelpView.Height = Dim.Auto (DimAutoStyle.Text); + HelpView.Height = Dim.Height(CommandView); HelpView.Visible = true; + HelpView.VerticalTextAlignment = Alignment.Center; + } /// @@ -463,7 +460,7 @@ public class Shortcut : View if (HelpView != null) { HelpView.Text = value; - ShowHide (HelpView); + ShowHide (); } } } @@ -479,7 +476,7 @@ public class Shortcut : View if (HelpView != null) { HelpView.Text = value; - ShowHide (HelpView); + ShowHide (); } } } @@ -508,7 +505,7 @@ public class Shortcut : View UpdateKeyBinding (); KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}"; - ShowHide (KeyView); + ShowHide (); } } @@ -563,10 +560,13 @@ public class Shortcut : View { KeyView.Margin.Thickness = new (1, 0, 1, 0); KeyView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); - KeyView.Y = 0; //Pos.Center (), + //KeyView.Y = Pos.Center (); KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func(GetMinimumKeyViewSize)); - KeyView.Height = Dim.Auto (DimAutoStyle.Text); + KeyView.Height = Dim.Height(CommandView); KeyView.Visible = true; + // Right align the text in the keyview + KeyView.TextAlignment = Alignment.End; + KeyView.VerticalTextAlignment = Alignment.Center; } private void UpdateKeyBinding () diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index c42247299..eefc202d7 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -1650,6 +1650,8 @@ public class Slider : View default: throw new ArgumentOutOfRangeException (_config._type.ToString ()); } + OnAccept (); + } internal bool ExtendPlus () @@ -1735,7 +1737,6 @@ public class Slider : View internal bool Set () { SetFocusedOption (); - return true; } diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 79f670eb6..4ab677197 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -35,7 +35,7 @@ public class Shortcuts : Scenario ListView eventLog = new ListView () { X = Pos.AnchorEnd (), - Width = 50, + Width = 40, Height = Dim.Fill (), ColorScheme = Colors.ColorSchemes ["Toplevel"], Source = new ListWrapper (eventSource) @@ -44,10 +44,11 @@ public class Shortcuts : Scenario var shortcut1 = new Shortcut { - //Width =30, + X = 20, + Width = 30, Title = "Zi_gzag", Key = Key.F1, - Text = "Gonna zig zag", + Text = "Width is 30", KeyBindingScope = KeyBindingScope.Application, BorderStyle = LineStyle.Dotted }; @@ -61,14 +62,20 @@ public class Shortcuts : Scenario var shortcut2 = new Shortcut { + X = 20, Y = Pos.Bottom (shortcut1), Width = Dim.Width (shortcut1), - Title = "_Two", - Key = Key.F2.WithAlt, - Text = "Number two", + Key = Key.F2, + Text = "Width is ^", KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Dotted + BorderStyle = LineStyle.Dotted, + CommandView = new RadioGroup () + { + Orientation = Orientation.Vertical, + RadioLabels = ["One", "Two", "Three", "Four"], + }, }; + shortcut2.Border.Thickness = new Thickness (1, 0, 1, 0); shortcut2.Accept += (s, e) => { @@ -79,11 +86,12 @@ public class Shortcuts : Scenario var shortcut3 = new Shortcut { + X = 20, Y = Pos.Bottom (shortcut2), - Width = Dim.Width (shortcut1), CommandView = new CheckBox () { Text = "_Align" }, Key = Key.F3, - HelpText = "Alignment", + HelpText = "Width is Fill", + Width = Dim.Fill () - Dim.Width (eventLog), KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted }; @@ -124,40 +132,19 @@ public class Shortcuts : Scenario }; Application.Top.Add (shortcut3); - - var shortcutH = new Shortcut - { - Y = Pos.Top (shortcut3), - X = Pos.Right (shortcut3), - Title = "Horizo_ntal", - Key = Key.F10, - Text = "Hey!", - KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Dotted - }; - shortcutH.Border.Thickness = new Thickness (0, 0, 1, 0); - shortcutH.Accept += (s, e) => - { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); - }; - Application.Top.Add (shortcutH); - var shortcut4 = new Shortcut { X = 20, Y = Pos.Bottom (shortcut3), - Width = Dim.Fill (50), + Width = Dim.Width (shortcut3), Title = "C", - Text = "H", + HelpText = "Width is Fill", Key = Key.K, KeyBindingScope = KeyBindingScope.HotKey, // Command = Command.Accept, BorderStyle = LineStyle.Dotted }; shortcut4.Border.Thickness = new Thickness (1, 0, 1, 0); - shortcut4.Margin.Thickness = new Thickness (0, 1, 0, 0); - View.Diagnostics = ViewDiagnosticFlags.Ruler; shortcut4.Accept += (s, e) => { @@ -171,16 +158,15 @@ public class Shortcuts : Scenario { X = 20, Y = Pos.Bottom (shortcut4), - Width = Dim.Fill (50), + Width = Dim.Width (shortcut4), + Title = "Fi_ve", Key = Key.F5.WithCtrl.WithAlt.WithShift, - Text = "Help text", + HelpText = "Width is Fill", KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted }; shortcut5.Border.Thickness = new Thickness (1, 0, 1, 0); - shortcut5.Margin.Thickness = new Thickness (0, 1, 0, 0); - View.Diagnostics = ViewDiagnosticFlags.Ruler; shortcut5.Accept += (s, e) => { @@ -190,9 +176,40 @@ public class Shortcuts : Scenario Application.Top.Add (shortcut5); + var shortcutSlider = new Shortcut + { + X = 20, + Y = Pos.Bottom (shortcut5), + Key = Key.F5, + HelpText = "Width is Fill", + Width = Dim.Width (shortcut5), + + KeyBindingScope = KeyBindingScope.HotKey, + BorderStyle = LineStyle.Dotted, + CommandView = new Slider () + { + Orientation = Orientation.Vertical, + AllowEmpty = false, + } + }; + + + ((Slider)shortcutSlider.CommandView).Options = new List> () + { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; + ((Slider)shortcutSlider.CommandView).SetOption (0); + shortcutSlider.Border.Thickness = new Thickness (1, 0, 1, 0); + + shortcutSlider.Accept += (s, e) => + { + eventSource.Add ($"Accept: {s}"); + eventLog.MoveDown (); + }; + Application.Top.Add (shortcutSlider); + ; ((CheckBox)shortcut3.CommandView).OnToggled (); //shortcut1.SetFocus (); + //View.Diagnostics = ViewDiagnosticFlags.Ruler; } From 23cb8968cd04a3f64dcf0f207547fa0fc81b294e Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 12 Jun 2024 10:21:25 -0700 Subject: [PATCH 12/85] Refineed accept logic --- Terminal.Gui/Views/RadioGroup.cs | 6 +- Terminal.Gui/Views/Shortcut.cs | 6 +- UICatalog/Scenarios/Shortcuts.cs | 157 +++++++++++++++---------------- 3 files changed, 83 insertions(+), 86 deletions(-) diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index efdfa5260..26ffaffde 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -355,7 +355,11 @@ public class RadioGroup : View /// /// public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem) - { + { + if (_selected == selectedItem) + { + return; + } _selected = selectedItem; SelectedItemChanged?.Invoke (this, new (selectedItem, previousSelectedItem)); } diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 526da816d..c850a600a 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -385,10 +385,8 @@ public class Shortcut : View Title = _commandView.Text; _commandView.TextChanged += CommandViewTextChanged; - Remove (HelpView); - Remove (KeyView); - Add (_commandView, HelpView, KeyView); - + SetHelpViewDefaultLayout (); + SetKeyViewDefaultLayout(); ShowHide (); UpdateKeyBinding (); diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 4ab677197..0a26298bb 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Text; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -23,7 +21,6 @@ public class Shortcuts : Scenario Application.Shutdown (); } - // Setting everything up in Loaded handler because we change the // QuitKey and it only sticks if changed after init private void App_Loaded (object sender, EventArgs e) @@ -32,7 +29,8 @@ public class Shortcuts : Scenario Application.Top.Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"; ObservableCollection eventSource = new (); - ListView eventLog = new ListView () + + var eventLog = new ListView { X = Pos.AnchorEnd (), Width = 40, @@ -45,19 +43,14 @@ public class Shortcuts : Scenario var shortcut1 = new Shortcut { X = 20, - Width = 30, - Title = "Zi_gzag", + Width = 35, + Title = "A_pp Shortcut", Key = Key.F1, Text = "Width is 30", KeyBindingScope = KeyBindingScope.Application, BorderStyle = LineStyle.Dotted }; - shortcut1.Border.Thickness = new Thickness (1, 0, 1, 0); - shortcut1.Accept += (s, e) => - { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); - }; + shortcut1.Border.Thickness = new (1, 0, 1, 0); Application.Top.Add (shortcut1); var shortcut2 = new Shortcut @@ -69,67 +62,70 @@ public class Shortcuts : Scenario Text = "Width is ^", KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted, - CommandView = new RadioGroup () + CommandView = new RadioGroup { Orientation = Orientation.Vertical, - RadioLabels = ["One", "Two", "Three", "Four"], - }, + RadioLabels = ["One", "Two", "Three", "Four"] + } }; - shortcut2.Border.Thickness = new Thickness (1, 0, 1, 0); - shortcut2.Accept += (s, e) => + ((RadioGroup)shortcut2.CommandView).SelectedItemChanged += (o, args) => + { + eventSource.Add ($"SelectedItemChanged: {o.GetType ().Name} - {args.SelectedItem}"); + eventLog.MoveDown (); + }; + + shortcut2.Accept += (o, args) => { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); + // Cycle to next item. If at end, set 0 + if (((RadioGroup)shortcut2.CommandView).SelectedItem < ((RadioGroup)shortcut2.CommandView).RadioLabels.Length-1) + { + ((RadioGroup)shortcut2.CommandView).SelectedItem++; + } + else + { + ((RadioGroup)shortcut2.CommandView).SelectedItem = 0; + } }; + shortcut2.Border.Thickness = new (1, 0, 1, 0); Application.Top.Add (shortcut2); var shortcut3 = new Shortcut { X = 20, Y = Pos.Bottom (shortcut2), - CommandView = new CheckBox () { Text = "_Align" }, + CommandView = new CheckBox { Text = "_Align" }, Key = Key.F3, HelpText = "Width is Fill", Width = Dim.Fill () - Dim.Width (eventLog), KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted }; - shortcut3.Border.Thickness = new Thickness (1, 0, 1, 0); + shortcut3.Border.Thickness = new (1, 0, 1, 0); ((CheckBox)shortcut3.CommandView).Toggled += (s, e) => - { - eventSource.Add ($"Toggled: {s}"); - eventLog.MoveDown (); + { + if (shortcut3.CommandView is CheckBox cb) + { + eventSource.Add ($"Toggled: {cb.Text}"); + eventLog.MoveDown (); - if (shortcut3.CommandView is CheckBox cb) - { - int max = 0; - if (e.NewValue == true) - { - foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) - { - max = Math.Max (max, peer.KeyView.Text.GetColumns ()); - } + var max = 0; - } - foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) - { - peer.MinimumKeyViewSize = max; - } - } + if (e.NewValue == true) + { + foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) + { + max = Math.Max (max, peer.KeyView.Text.GetColumns ()); + } + } - //Application.Top.SetNeedsDisplay (); - //Application.Top.LayoutSubviews (); - //Application.Top.SetNeedsDisplay (); - //Application.Top.Draw (); - }; - shortcut3.Accept += (s, e) => - { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); - - }; + foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) + { + peer.MinimumKeyViewSize = max; + } + } + }; Application.Top.Add (shortcut3); var shortcut4 = new Shortcut @@ -137,21 +133,19 @@ public class Shortcuts : Scenario X = 20, Y = Pos.Bottom (shortcut3), Width = Dim.Width (shortcut3), - Title = "C", + CommandView = new Button + { + Title = "_Button" + }, HelpText = "Width is Fill", Key = Key.K, KeyBindingScope = KeyBindingScope.HotKey, - // Command = Command.Accept, BorderStyle = LineStyle.Dotted }; - shortcut4.Border.Thickness = new Thickness (1, 0, 1, 0); - shortcut4.Accept += (s, e) => - { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); - MessageBox.Query ("Hi", $"You clicked {s}"); - }; + shortcut4.CommandView.Accept += Button_Clicked; + shortcut4.Border.Thickness = new (1, 0, 1, 0); + Application.Top.Add (shortcut4); var shortcut5 = new Shortcut @@ -166,16 +160,10 @@ public class Shortcuts : Scenario KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted }; - shortcut5.Border.Thickness = new Thickness (1, 0, 1, 0); + shortcut5.Border.Thickness = new (1, 0, 1, 0); - shortcut5.Accept += (s, e) => - { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); - }; Application.Top.Add (shortcut5); - var shortcutSlider = new Shortcut { X = 20, @@ -186,33 +174,40 @@ public class Shortcuts : Scenario KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted, - CommandView = new Slider () + CommandView = new Slider { Orientation = Orientation.Vertical, - AllowEmpty = false, + AllowEmpty = false } }; - - ((Slider)shortcutSlider.CommandView).Options = new List> () - { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; + ((Slider)shortcutSlider.CommandView).Options = new() { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; ((Slider)shortcutSlider.CommandView).SetOption (0); - shortcutSlider.Border.Thickness = new Thickness (1, 0, 1, 0); + shortcutSlider.Border.Thickness = new (1, 0, 1, 0); + + ((Slider)shortcutSlider.CommandView).OptionsChanged += (o, args) => + { + eventSource.Add ($"OptionsChanged: {o.GetType ().Name} - {args.Options}"); + eventLog.MoveDown (); + }; - shortcutSlider.Accept += (s, e) => - { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); - }; Application.Top.Add (shortcutSlider); - ; - ((CheckBox)shortcut3.CommandView).OnToggled (); + + foreach (View sh in Application.Top.Subviews.Where (v => v is Shortcut)!) + { + if (sh is Shortcut shortcut) + { + shortcut.Accept += (o, args) => + { + eventSource.Add ($"Accept: {shortcut!.CommandView.Text}"); + eventLog.MoveDown (); + }; + } + } //shortcut1.SetFocus (); //View.Diagnostics = ViewDiagnosticFlags.Ruler; - } private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } - } From 2a3808a7c058aa4d2bde5ad42e6a584a6bda8de3 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 12 Jun 2024 13:31:40 -0700 Subject: [PATCH 13/85] Enabled CanFocus for COmmandView --- Terminal.Gui/Views/Shortcut.cs | 266 ++++++++++++++++--------------- UICatalog/Scenarios/Shortcuts.cs | 46 ++++-- 2 files changed, 170 insertions(+), 142 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index c850a600a..296932398 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -7,7 +7,8 @@ namespace Terminal.Gui; // TODO: I tried `BarItem` but that's not great either as it implies it can only be used in `Bar`s. /// -/// Displays a command, help text, and a key binding. When the key is pressed, the command will be invoked. Useful for displaying a command in such as a +/// Displays a command, help text, and a key binding. When the key is pressed, the command will be invoked. Useful for +/// displaying a command in such as a /// menu, toolbar, or status bar. /// /// @@ -17,14 +18,17 @@ namespace Terminal.Gui; /// event to be fired /// /// -/// If is , the command +/// If is , the +/// command /// be invoked regardless of what View has focus, enabling an application-wide keyboard shortcut. /// /// -/// A Shortcut displays the command text on the left side, the help text in the middle, and the key binding on the right side. +/// A Shortcut displays the command text on the left side, the help text in the middle, and the key binding on the +/// right side. /// /// -/// The command text can be set by setting the 's Text property or by setting . +/// The command text can be set by setting the 's Text property or by setting +/// . /// /// /// The help text can be set by setting the property or by setting . @@ -48,28 +52,23 @@ public class Shortcut : View Width = GetWidthDimAuto (); Height = Dim.Auto (DimAutoStyle.Content, 1); - AddCommand (Gui.Command.HotKey, OnAccept); - AddCommand (Gui.Command.Accept, OnAccept); - KeyBindings.Add (KeyCode.Space, Gui.Command.Accept); - KeyBindings.Add (KeyCode.Enter, Gui.Command.Accept); + AddCommand (Command.HotKey, OnAccept); + AddCommand (Command.Accept, OnAccept); + KeyBindings.Add (KeyCode.Space, Command.Accept); + KeyBindings.Add (KeyCode.Enter, Command.Accept); TitleChanged += Shortcut_TitleChanged; // This needs to be set before CommandView is set - CommandView = new View (); + CommandView = new (); HelpView.Id = "_helpView"; HelpView.CanFocus = false; SetHelpViewDefaultLayout (); Add (HelpView); - - // HelpView.TextAlignment = Alignment.End; HelpView.MouseClick += Shortcut_MouseClick; KeyView.Id = "_keyView"; - - // Only the Shortcut should be able to have focus, not any subviews KeyView.CanFocus = false; - SetKeyViewDefaultLayout (); Add (KeyView); @@ -85,14 +84,18 @@ public class Shortcut : View void OnInitialized (object sender, EventArgs e) { + SuperViewRendersLineCanvas = true; + Border.ShowTitle = false; + ShowHide (); // Force Width to DimAuto to calculate natural width and then set it back Dim savedDim = Width; Width = GetWidthDimAuto (); - _naturalWidth = Frame.Width; + _minimumDimAutoWidth = Frame.Width; Width = savedDim; + // Set KeyView's colors to show "hot" if (ColorScheme != null) { var cs = new ColorScheme (ColorScheme) @@ -105,11 +108,11 @@ public class Shortcut : View } } + // Helper to set Width consistently Dim GetWidthDimAuto () { return Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); } - } // When one of the subviews is "empty" we don't want to show it. So we @@ -118,27 +121,32 @@ public class Shortcut : View private void ShowHide () { RemoveAll (); + if (!string.IsNullOrEmpty (CommandView.Text)) { Add (CommandView); } + if (!string.IsNullOrEmpty (HelpView.Text)) { Add (HelpView); } + if (Key != Key.Empty) { Add (KeyView); } } - private int? _naturalWidth; + // This is used to calculate the minimum width of the Shortcut when the width is NOT Dim.Auto + private int? _minimumDimAutoWidth; + // When layout starts, we need to adjust the layout of the HelpView and KeyView private void OnLayoutStarted (object sender, LayoutEventArgs e) { if (Width is DimAuto widthAuto) { - _naturalWidth = Frame.Width; + _minimumDimAutoWidth = Frame.Width; } else { @@ -150,9 +158,9 @@ public class Shortcut : View int currentWidth = Frame.Width; // If our width is smaller than the natural then reduce width of HelpView. - if (currentWidth < _naturalWidth) + if (currentWidth < _minimumDimAutoWidth) { - int delta = _naturalWidth.Value - currentWidth; + int delta = _minimumDimAutoWidth.Value - currentWidth; int maxHelpWidth = int.Max (0, HelpView.Text.GetColumns () + 2 - delta); switch (maxHelpWidth) @@ -235,6 +243,7 @@ public class Shortcut : View { // When the Shortcut is clicked, we want to invoke the Command and Set focus var view = sender as View; + if (view != CommandView) { CommandView.InvokeCommand (Command.Accept); @@ -263,34 +272,6 @@ public class Shortcut : View e.Handled = true; } - /// - public override ColorScheme ColorScheme - { - get - { - if (base.ColorScheme == null) - { - return SuperView?.ColorScheme ?? base.ColorScheme; - } - - return base.ColorScheme; - } - set - { - base.ColorScheme = value; - - if (ColorScheme != null) - { - var cs = new ColorScheme (ColorScheme) - { - Normal = ColorScheme.HotNormal, - HotNormal = ColorScheme.Normal - }; - KeyView.ColorScheme = cs; - } - } - } - #region Command private View _commandView = new (); @@ -386,7 +367,7 @@ public class Shortcut : View _commandView.TextChanged += CommandViewTextChanged; SetHelpViewDefaultLayout (); - SetKeyViewDefaultLayout(); + SetKeyViewDefaultLayout (); ShowHide (); UpdateKeyBinding (); @@ -440,10 +421,9 @@ public class Shortcut : View HelpView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); HelpView.Y = 0; //Pos.Center (), HelpView.Width = Dim.Auto (DimAutoStyle.Text); - HelpView.Height = Dim.Height(CommandView); + HelpView.Height = Dim.Height (CommandView); HelpView.Visible = true; HelpView.VerticalTextAlignment = Alignment.Center; - } /// @@ -531,8 +511,8 @@ public class Shortcut : View public View KeyView { get; } = new (); private int _minimumKeyViewSize; + /// - /// /// public int MinimumKeyViewSize { @@ -543,9 +523,10 @@ public class Shortcut : View { //return; } + _minimumKeyViewSize = value; - SetKeyViewDefaultLayout(); - CommandView.SetNeedsLayout(); + SetKeyViewDefaultLayout (); + CommandView.SetNeedsLayout (); HelpView.SetNeedsLayout (); KeyView.SetNeedsLayout (); SetSubViewNeedsDisplay (); @@ -558,34 +539,31 @@ public class Shortcut : View { KeyView.Margin.Thickness = new (1, 0, 1, 0); KeyView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + //KeyView.Y = Pos.Center (); - KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func(GetMinimumKeyViewSize)); - KeyView.Height = Dim.Height(CommandView); + KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); + KeyView.Height = Dim.Height (CommandView); KeyView.Visible = true; + // Right align the text in the keyview KeyView.TextAlignment = Alignment.End; KeyView.VerticalTextAlignment = Alignment.Center; + KeyView.KeyBindings.Clear (); } private void UpdateKeyBinding () { - if (KeyBindingScope == KeyBindingScope.Application) - { - // return; - } - if (Key != null) { - // CommandView holds our command/keybinding - // Add a key binding for this command to this Shortcut - - CommandView.KeyBindings.Remove (Key); - CommandView.KeyBindings.Add (Key, KeyBindingScope, Command.Accept); + KeyBindings.Remove (Key); + KeyBindings.Add (Key, KeyBindingScope, Command.Accept); } } #endregion Key + #region Accept Handling + /// /// The event fired when the command is received. This /// occurs if the user clicks on the Shortcut or presses . @@ -599,39 +577,26 @@ public class Shortcut : View /// protected new bool? OnAccept () { - // TODO: This is not completely thought through. + var handled = true; - if (Key == null || Key == Key.Empty) + switch (KeyBindingScope) { - return false; + case KeyBindingScope.Application: + break; + + case KeyBindingScope.Focused: + // TODO: Figure this out + handled = false; + + break; + case KeyBindingScope.HotKey: + handled = _commandView.InvokeCommand (Command.HotKey) == true; + handled = false; + + break; } - var handled = false; - var keyCopy = new Key (Key); - - //switch (KeyBindingScope) - //{ - // case KeyBindingScope.Application: - // // Simulate a key down to invoke the Application scoped key binding - // handled = Application.OnKeyDown (keyCopy); - - // break; - // case KeyBindingScope.Focused: - // handled = InvokeCommand (Command.Value) == true; - // handled = false; - - // break; - // case KeyBindingScope.HotKey: - // if (Command.HasValue) - // { - // //handled = _commandView.InvokeCommand (Gui.Command.HotKey) == true; - // //handled = false; - // } - - // break; - //} - - //if (handled == false) + if (handled == false) { var args = new HandledEventArgs (); Accept?.Invoke (this, args); @@ -641,25 +606,56 @@ public class Shortcut : View return true; } + #endregion Accept Handling + + #region Focus + + /// + public override ColorScheme ColorScheme + { + get + { + if (base.ColorScheme == null) + { + return SuperView?.ColorScheme ?? base.ColorScheme; + } + + return base.ColorScheme; + } + set + { + base.ColorScheme = value; + + if (CommandView.CanFocus) + { + CommandView.ColorScheme = SuperView?.ColorScheme ?? ColorScheme; + } + + if (ColorScheme != null) + { + var cs = new ColorScheme (ColorScheme) + { + Normal = ColorScheme.HotNormal, + HotNormal = ColorScheme.Normal + }; + KeyView.ColorScheme = cs; + } + + Border.ColorScheme = SuperView?.ColorScheme ?? ColorScheme; + } + } + /// public override bool OnEnter (View view) { - // TODO: This is a hack. Need to refine this. - var cs = new ColorScheme (ColorScheme) + if (SuperView is { }) { - Normal = ColorScheme.Focus, - HotNormal = ColorScheme.HotFocus - }; - - // _container.ColorScheme = cs; - - cs = new (ColorScheme) - { - Normal = ColorScheme.HotFocus, - HotNormal = ColorScheme.Focus - }; - - //KeyView.ColorScheme = cs; + ColorScheme = new (SuperView?.ColorScheme) + { + Normal = SuperView.ColorScheme.Focus, + HotNormal = SuperView.ColorScheme.HotFocus + }; + } return base.OnEnter (view); } @@ -667,24 +663,42 @@ public class Shortcut : View /// public override bool OnLeave (View view) { - // TODO: This is a hack. Need to refine this. - var cs = new ColorScheme (ColorScheme) + ColorScheme = null; + + return base.OnLeave (view); + if (SuperView is { }) { - Normal = ColorScheme.Normal, - HotNormal = ColorScheme.HotNormal - }; - - // _container.ColorScheme = cs; - - cs = new (ColorScheme) - { - Normal = ColorScheme.HotNormal, - HotNormal = ColorScheme.Normal - }; - - //KeyView.ColorScheme = cs; + ColorScheme = new (SuperView?.ColorScheme) + { + Normal = SuperView.ColorScheme.Normal, + HotNormal = SuperView.ColorScheme.HotNormal + }; + } return base.OnLeave (view); } -} + #endregion Focus + + /// + protected override void Dispose (bool disposing) + { + if (disposing) + { + if (CommandView?.IsAdded == false) + { + CommandView.Dispose (); + } + if (HelpView?.IsAdded == false) + { + HelpView.Dispose (); + } + if (KeyView?.IsAdded == false) + { + KeyView.Dispose (); + } + } + base.Dispose (disposing); + + } +} diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 0a26298bb..9bcc7426f 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -50,13 +50,13 @@ public class Shortcuts : Scenario KeyBindingScope = KeyBindingScope.Application, BorderStyle = LineStyle.Dotted }; - shortcut1.Border.Thickness = new (1, 0, 1, 0); + shortcut1.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (shortcut1); var shortcut2 = new Shortcut { X = 20, - Y = Pos.Bottom (shortcut1), + Y = Pos.Bottom (shortcut1) - 1, Width = Dim.Width (shortcut1), Key = Key.F2, Text = "Width is ^", @@ -78,7 +78,7 @@ public class Shortcuts : Scenario shortcut2.Accept += (o, args) => { // Cycle to next item. If at end, set 0 - if (((RadioGroup)shortcut2.CommandView).SelectedItem < ((RadioGroup)shortcut2.CommandView).RadioLabels.Length-1) + if (((RadioGroup)shortcut2.CommandView).SelectedItem < ((RadioGroup)shortcut2.CommandView).RadioLabels.Length - 1) { ((RadioGroup)shortcut2.CommandView).SelectedItem++; } @@ -87,7 +87,7 @@ public class Shortcuts : Scenario ((RadioGroup)shortcut2.CommandView).SelectedItem = 0; } }; - shortcut2.Border.Thickness = new (1, 0, 1, 0); + shortcut2.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (shortcut2); var shortcut3 = new Shortcut @@ -101,7 +101,8 @@ public class Shortcuts : Scenario KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted }; - shortcut3.Border.Thickness = new (1, 0, 1, 0); + shortcut3.CommandView.CanFocus = true; + shortcut3.Border.Thickness = new (1, 1, 1, 0); ((CheckBox)shortcut3.CommandView).Toggled += (s, e) => { @@ -135,23 +136,24 @@ public class Shortcuts : Scenario Width = Dim.Width (shortcut3), CommandView = new Button { - Title = "_Button" + Title = "B_utton", }, HelpText = "Width is Fill", Key = Key.K, KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted }; - + Button button = (Button)shortcut4.CommandView; shortcut4.CommandView.Accept += Button_Clicked; - shortcut4.Border.Thickness = new (1, 0, 1, 0); + shortcut4.CommandView.CanFocus = true; + shortcut4.Border.Thickness = new (1, 0, 1,0); Application.Top.Add (shortcut4); var shortcut5 = new Shortcut { X = 20, - Y = Pos.Bottom (shortcut4), + Y = Pos.Bottom (shortcut4) , Width = Dim.Width (shortcut4), Title = "Fi_ve", @@ -160,14 +162,14 @@ public class Shortcuts : Scenario KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Dotted }; - shortcut5.Border.Thickness = new (1, 0, 1, 0); + shortcut5.Border.Thickness = new (1, 0, 1, 1); Application.Top.Add (shortcut5); var shortcutSlider = new Shortcut { X = 20, - Y = Pos.Bottom (shortcut5), + Y = Pos.Bottom (shortcut5) - 1, Key = Key.F5, HelpText = "Width is Fill", Width = Dim.Width (shortcut5), @@ -181,9 +183,9 @@ public class Shortcuts : Scenario } }; - ((Slider)shortcutSlider.CommandView).Options = new() { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; + ((Slider)shortcutSlider.CommandView).Options = new () { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; ((Slider)shortcutSlider.CommandView).SetOption (0); - shortcutSlider.Border.Thickness = new (1, 0, 1, 0); + shortcutSlider.Border.Thickness = new (1, 1, 1, 1); ((Slider)shortcutSlider.CommandView).OptionsChanged += (o, args) => { @@ -193,20 +195,32 @@ public class Shortcuts : Scenario Application.Top.Add (shortcutSlider); + var shortcut6 = new Shortcut + { + X = 20, + Y = Pos.Bottom (shortcutSlider) - 1, + Width = Dim.Width (shortcutSlider), + + Title = "_No Key", + HelpText = "Keyless", + BorderStyle = LineStyle.Dotted + }; + shortcut6.Border.Thickness = new (1, 1, 1, 1); + + Application.Top.Add (shortcut6); + foreach (View sh in Application.Top.Subviews.Where (v => v is Shortcut)!) { if (sh is Shortcut shortcut) { shortcut.Accept += (o, args) => { + var x = button; eventSource.Add ($"Accept: {shortcut!.CommandView.Text}"); eventLog.MoveDown (); }; } } - - //shortcut1.SetFocus (); - //View.Diagnostics = ViewDiagnosticFlags.Ruler; } private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } From b82cc67f54809df4d4d58605cde0fd094375a72e Mon Sep 17 00:00:00 2001 From: John Baughman <1634414+johnmbaughman@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:51:50 -0500 Subject: [PATCH 14/85] Add Community Toolkit example --- .../CommunityToolkitExample.csproj | 19 +++ CommunityToolkitExample/LoginAction.cs | 7 ++ CommunityToolkitExample/LoginView.Designer.cs | 62 +++++++++ CommunityToolkitExample/LoginView.cs | 68 ++++++++++ CommunityToolkitExample/LoginViewModel.cs | 118 ++++++++++++++++++ CommunityToolkitExample/Message.cs | 6 + CommunityToolkitExample/Program.cs | 25 ++++ Terminal.sln | 32 +++-- 8 files changed, 324 insertions(+), 13 deletions(-) create mode 100644 CommunityToolkitExample/CommunityToolkitExample.csproj create mode 100644 CommunityToolkitExample/LoginAction.cs create mode 100644 CommunityToolkitExample/LoginView.Designer.cs create mode 100644 CommunityToolkitExample/LoginView.cs create mode 100644 CommunityToolkitExample/LoginViewModel.cs create mode 100644 CommunityToolkitExample/Message.cs create mode 100644 CommunityToolkitExample/Program.cs diff --git a/CommunityToolkitExample/CommunityToolkitExample.csproj b/CommunityToolkitExample/CommunityToolkitExample.csproj new file mode 100644 index 000000000..fdb41dfcd --- /dev/null +++ b/CommunityToolkitExample/CommunityToolkitExample.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/CommunityToolkitExample/LoginAction.cs b/CommunityToolkitExample/LoginAction.cs new file mode 100644 index 000000000..2fd8d4a45 --- /dev/null +++ b/CommunityToolkitExample/LoginAction.cs @@ -0,0 +1,7 @@ +namespace CommunityToolkitExample; + +internal enum LoginAction +{ + Validation, + LoginProgress +} diff --git a/CommunityToolkitExample/LoginView.Designer.cs b/CommunityToolkitExample/LoginView.Designer.cs new file mode 100644 index 000000000..e1bddff45 --- /dev/null +++ b/CommunityToolkitExample/LoginView.Designer.cs @@ -0,0 +1,62 @@ +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginView : Window +{ + private Label titleLabel; + private Label usernameLengthLabel; + private TextField usernameInput; + private Label passwordLengthLabel; + private TextField passwordInput; + private Label validationLabel; + private Button loginButton; + private Button clearButton; + private Label loginProgressLabel; + + private void InitializeComponent () + { + titleLabel = new Label (); + titleLabel.Text = "Login Form"; + Add (titleLabel); + usernameLengthLabel = new Label (); + usernameLengthLabel.X = Pos.Left (titleLabel); + usernameLengthLabel.Y = Pos.Top (titleLabel) + 1; + Add (usernameLengthLabel); + usernameInput = new TextField (); + usernameInput.X = Pos.Right (usernameLengthLabel) + 1; + usernameInput.Y = Pos.Top (usernameLengthLabel); + usernameInput.Width = 40; + Add (usernameInput); + passwordLengthLabel = new Label (); + passwordLengthLabel.X = Pos.Left (usernameLengthLabel); + passwordLengthLabel.Y = Pos.Top (usernameLengthLabel) + 1; + Add (passwordLengthLabel); + passwordInput = new TextField (); + passwordInput.X = Pos.Right (passwordLengthLabel) + 1; + passwordInput.Y = Pos.Top (passwordLengthLabel); + passwordInput.Width = 40; + passwordInput.Secret = true; + Add (passwordInput); + validationLabel = new Label (); + validationLabel.X = Pos.Left (passwordInput); + validationLabel.Y = Pos.Top (passwordInput) + 1; + Add (validationLabel); + loginButton = new Button (); + loginButton.X = Pos.Left (validationLabel); + loginButton.Y = Pos.Top (validationLabel) + 1; + loginButton.Text = "_Login"; + Add (loginButton); + clearButton = new Button (); + clearButton.X = Pos.Left (loginButton); + clearButton.Y = Pos.Top (loginButton) + 1; + clearButton.Text = "_Clear"; + Add (clearButton); + loginProgressLabel = new Label (); + loginProgressLabel.X = Pos.Left (clearButton); + loginProgressLabel.Y = Pos.Top (clearButton) + 1; + loginProgressLabel.Width = 40; + loginProgressLabel.Height = 1; + Add (loginProgressLabel); + } +} diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs new file mode 100644 index 000000000..262928e35 --- /dev/null +++ b/CommunityToolkitExample/LoginView.cs @@ -0,0 +1,68 @@ +using CommunityToolkit.Mvvm.Messaging; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginView : IRecipient> +{ + public LoginView (LoginViewModel viewModel) + { + WeakReferenceMessenger.Default.Register (this); + Title = $"Community Toolkit MVVM Example - {Application.QuitKey} to Exit"; + ViewModel = viewModel; + InitializeComponent (); + usernameInput.TextChanged += (_, _) => + { + ViewModel.Username = usernameInput.Text; + SetText (); + }; + passwordInput.TextChanged += (_, _) => + { + ViewModel.Password = passwordInput.Text; + SetText (); + }; + loginButton.Accept += (_, _) => + { + if (!ViewModel.CanLogin) { return; } + ViewModel.LoginCommand.Execute (null); + }; + + clearButton.Accept += (_, _) => + { + ViewModel.ClearCommand.Execute (null); + SetText (); + }; + + Initialized += (_, _) => { ViewModel.Initialized (); }; + } + + public LoginViewModel ViewModel { get; set; } + + public void Receive (Message message) + { + switch (message.Value) + { + case LoginAction.LoginProgress: + { + loginProgressLabel.Text = ViewModel.LoginProgressMessage; + break; + } + case LoginAction.Validation: + { + validationLabel.Text = ViewModel.ValidationMessage; + validationLabel.ColorScheme = ViewModel.ValidationColorScheme; + break; + } + } + SetText(); + Application.Refresh (); + } + + private void SetText () + { + usernameInput.Text = ViewModel.Username; + usernameLengthLabel.Text = ViewModel.UsernameLengthMessage; + passwordInput.Text = ViewModel.Password; + passwordLengthLabel.Text = ViewModel.PasswordLengthMessage; + } +} \ No newline at end of file diff --git a/CommunityToolkitExample/LoginViewModel.cs b/CommunityToolkitExample/LoginViewModel.cs new file mode 100644 index 000000000..b5fbfa32f --- /dev/null +++ b/CommunityToolkitExample/LoginViewModel.cs @@ -0,0 +1,118 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginViewModel : ObservableObject +{ + private const string DEFAULT_LOGIN_PROGRESS_MESSAGE = "Press 'Login' to log in."; + private const string LOGGING_IN_PROGRESS_MESSAGE = "Logging in..."; + private const string VALID_LOGIN_MESSAGE = "The input is valid!"; + private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; + + [ObservableProperty] + private bool _canLogin; + + private string _password; + + [ObservableProperty] + private string _passwordLengthMessage; + + private string _username; + + [ObservableProperty] + private string _usernameLengthMessage; + + [ObservableProperty] + private string _loginProgressMessage; + + [ObservableProperty] + private string _validationMessage; + + [ObservableProperty] + private ColorScheme? _validationColorScheme; + + public LoginViewModel () + { + Username = string.Empty; + Password = string.Empty; + + ClearCommand = new (Clear); + LoginCommand = new (Execute); + + Clear (); + + return; + + async void Execute () { await Login (); } + } + + public RelayCommand ClearCommand { get; } + + public RelayCommand LoginCommand { get; } + + public string Password + { + get => _password; + set + { + SetProperty (ref _password, value); + PasswordLengthMessage = $"_Password ({_password.Length} characters):"; + ValidateLogin (); + } + } + + private void ValidateLogin () + { + CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); + SendMessage (LoginAction.Validation); + } + + public string Username + { + get => _username; + set + { + SetProperty (ref _username, value); + UsernameLengthMessage = $"_Username ({_username.Length} characters):"; + ValidateLogin (); + } + } + + private void Clear () + { + Username = string.Empty; + Password = string.Empty; + SendMessage (LoginAction.Validation); + SendMessage (LoginAction.LoginProgress, DEFAULT_LOGIN_PROGRESS_MESSAGE); + } + + private async Task Login () + { + SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); + await Task.Delay (TimeSpan.FromSeconds (1)); + Clear (); + } + + private void SendMessage (LoginAction loginAction, string message = "") + { + switch (loginAction) + { + case LoginAction.LoginProgress: + LoginProgressMessage = message; + break; + case LoginAction.Validation: + ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE; + ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"]; + break; + } + WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); + } + + public void Initialized () + { + Clear (); + } +} diff --git a/CommunityToolkitExample/Message.cs b/CommunityToolkitExample/Message.cs new file mode 100644 index 000000000..a1081512e --- /dev/null +++ b/CommunityToolkitExample/Message.cs @@ -0,0 +1,6 @@ +namespace CommunityToolkitExample; + +internal class Message +{ + public T Value { get; set; } +} diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs new file mode 100644 index 000000000..0a557fc47 --- /dev/null +++ b/CommunityToolkitExample/Program.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +public static class Program +{ + public static IServiceProvider Services { get; private set; } + + private static void Main (string [] args) + { + Services = ConfigureServices (); + Application.Init (); + Application.Run (Services.GetRequiredService ()); + Application.Shutdown (); + } + + private static IServiceProvider ConfigureServices () + { + var services = new ServiceCollection (); + services.AddTransient (); + services.AddTransient (); + return services.BuildServiceProvider (); + } +} \ No newline at end of file diff --git a/Terminal.sln b/Terminal.sln index 98de83b41..7dbb5432a 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -38,12 +38,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Inte EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Internal.Debugging", "Analyzers\Terminal.Gui.Analyzers.Internal.Debugging\Terminal.Gui.Analyzers.Internal.Debugging.csproj", "{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}" +EndProject Global - GlobalSection(NestedProjects) = preSolution - {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -53,14 +50,6 @@ Global {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {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}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -77,14 +66,31 @@ Global {B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.Build.0 = Release|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03} EndGlobalSection From 182b63da929647099285beed7a940da515709686 Mon Sep 17 00:00:00 2001 From: John Baughman <1634414+johnmbaughman@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:24:27 -0500 Subject: [PATCH 15/85] added recommendations; fixed .sln --- CommunityToolkitExample/LoginViewModel.cs | 31 ++-- CommunityToolkitExample/Program.cs | 1 + CommunityToolkitExample/README.md | 180 ++++++++++++++++++++++ ReactiveExample/Program.cs | 1 + Terminal.sln | 26 ++-- 5 files changed, 209 insertions(+), 30 deletions(-) create mode 100644 CommunityToolkitExample/README.md diff --git a/CommunityToolkitExample/LoginViewModel.cs b/CommunityToolkitExample/LoginViewModel.cs index b5fbfa32f..2de4688a3 100644 --- a/CommunityToolkitExample/LoginViewModel.cs +++ b/CommunityToolkitExample/LoginViewModel.cs @@ -8,13 +8,15 @@ namespace CommunityToolkitExample; internal partial class LoginViewModel : ObservableObject { private const string DEFAULT_LOGIN_PROGRESS_MESSAGE = "Press 'Login' to log in."; + private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; private const string LOGGING_IN_PROGRESS_MESSAGE = "Logging in..."; private const string VALID_LOGIN_MESSAGE = "The input is valid!"; - private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; - [ObservableProperty] private bool _canLogin; + [ObservableProperty] + private string _loginProgressMessage; + private string _password; [ObservableProperty] @@ -24,16 +26,11 @@ internal partial class LoginViewModel : ObservableObject [ObservableProperty] private string _usernameLengthMessage; - - [ObservableProperty] - private string _loginProgressMessage; - - [ObservableProperty] - private string _validationMessage; - [ObservableProperty] private ColorScheme? _validationColorScheme; + [ObservableProperty] + private string _validationMessage; public LoginViewModel () { Username = string.Empty; @@ -64,12 +61,6 @@ internal partial class LoginViewModel : ObservableObject } } - private void ValidateLogin () - { - CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); - SendMessage (LoginAction.Validation); - } - public string Username { get => _username; @@ -81,6 +72,11 @@ internal partial class LoginViewModel : ObservableObject } } + public void Initialized () + { + Clear (); + } + private void Clear () { Username = string.Empty; @@ -111,8 +107,9 @@ internal partial class LoginViewModel : ObservableObject WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); } - public void Initialized () + private void ValidateLogin () { - Clear (); + CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); + SendMessage (LoginAction.Validation); } } diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs index 0a557fc47..96c43a14a 100644 --- a/CommunityToolkitExample/Program.cs +++ b/CommunityToolkitExample/Program.cs @@ -12,6 +12,7 @@ public static class Program Services = ConfigureServices (); Application.Init (); Application.Run (Services.GetRequiredService ()); + Application.Top.Dispose(); Application.Shutdown (); } diff --git a/CommunityToolkitExample/README.md b/CommunityToolkitExample/README.md new file mode 100644 index 000000000..254ca9bc4 --- /dev/null +++ b/CommunityToolkitExample/README.md @@ -0,0 +1,180 @@ +# CommunityToolkit.MVVM Example + +This small demo gives an example of using the `CommunityToolkit.MVVM` framework's `ObservableObject`, `ObservableProperty`, and `IRecipient` in conjunction with `Microsoft.Extensions.DependencyInjection`. + +### Startup + +Right away we use IoC to load our views and view models. + +``` csharp +// As a public property for access further in the application if needed. +public static IServiceProvider Services { get; private set; } + +. . . + +// In Main +Services = ConfigureServices (); + +. . . + +private static IServiceProvider ConfigureServices () +{ + var services = new ServiceCollection (); + services.AddTransient (); + services.AddTransient (); + return services.BuildServiceProvider (); +} +``` + +Now, we start the app and get our main view. + +``` csharp +Application.Run (Services.GetRequiredService ()); +``` + +Our view implements `IRecipient` to demonstrate the use of the `WeakReferenceMessenger`. The binding of the view events is then created. + +``` csharp +internal partial class LoginView : IRecipient> +{ + public LoginView (LoginViewModel viewModel) + { + // Initialize our Receive method + WeakReferenceMessenger.Default.Register (this); + + ... + + ViewModel = viewModel; + + ... + + passwordInput.TextChanged += (_, _) => + { + ViewModel.Password = passwordInput.Text; + SetText (); + }; + loginButton.Accept += (_, _) => + { + if (!ViewModel.CanLogin) { return; } + ViewModel.LoginCommand.Execute (null); + }; + + ... + + Initialized += (_, _) => { ViewModel.Initialized (); }; + } + + ... + +} +``` + +Momentarily slipping over to the view model, all bindable properties use some form of `ObservableProperty` with the class deriving from `ObservableObject`. Commands are of the `RelayCommand` type. + +``` csharp + +internal partial class LoginViewModel : ObservableObject +{ + ... + + [ObservableProperty] + private bool _canLogin; + + private string _password; + + ... + + public LoginViewModel () + { + ... + + Password = string.Empty; + + ... + + LoginCommand = new (Execute); + + Clear (); + + return; + + async void Execute () { await Login (); } + } + + ... + + public RelayCommand LoginCommand { get; } + + public string Password + { + get => _password; + set + { + SetProperty (ref _password, value); + PasswordLengthMessage = $"_Password ({_password.Length} characters):"; + ValidateLogin (); + } + } +``` + +The use of `WeakReferenceMessenger` provides one method of signaling the view from the view model. It's just one way to handle cross-thread messaging in this framework. + +``` csharp +... + +private async Task Login () +{ + SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); + await Task.Delay (TimeSpan.FromSeconds (1)); + Clear (); +} + +private void SendMessage (LoginAction loginAction, string message = "") +{ + switch (loginAction) + { + case LoginAction.LoginProgress: + LoginProgressMessage = message; + break; + case LoginAction.Validation: + ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE; + ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"]; + break; + } + WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); +} + +private void ValidateLogin () +{ + CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); + SendMessage (LoginAction.Validation); +} + +... +``` + +And the view's `Receive` function which provides an `Application.Refresh()` call to update the UI immediately. + +``` csharp +public void Receive (Message message) +{ + switch (message.Value) + { + case LoginAction.LoginProgress: + { + loginProgressLabel.Text = ViewModel.LoginProgressMessage; + break; + } + case LoginAction.Validation: + { + validationLabel.Text = ViewModel.ValidationMessage; + validationLabel.ColorScheme = ViewModel.ValidationColorScheme; + break; + } + } + SetText(); + Application.Refresh (); +} +``` + +There are other ways of handling cross-thread communication, this gives just one example. \ No newline at end of file diff --git a/ReactiveExample/Program.cs b/ReactiveExample/Program.cs index 2b5105b8c..2bbc6667b 100644 --- a/ReactiveExample/Program.cs +++ b/ReactiveExample/Program.cs @@ -12,6 +12,7 @@ public static class Program RxApp.MainThreadScheduler = TerminalScheduler.Default; RxApp.TaskpoolScheduler = TaskPoolScheduler.Default; Application.Run (new LoginView (new LoginViewModel ())); + Application.Top.Dispose(); Application.Shutdown (); } } diff --git a/Terminal.sln b/Terminal.sln index 7dbb5432a..ee838a41a 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -41,6 +41,11 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}" EndProject Global + GlobalSection(NestedProjects) = preSolution + {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -50,6 +55,14 @@ Global {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {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}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -66,14 +79,6 @@ Global {B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -86,11 +91,6 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03} EndGlobalSection From 35379e8c19838306271d889b73ed71461cc52bb4 Mon Sep 17 00:00:00 2001 From: "John M. Baughman" <1634414+johnmbaughman@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:31:54 -0500 Subject: [PATCH 16/85] Update README.md --- CommunityToolkitExample/README.md | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/CommunityToolkitExample/README.md b/CommunityToolkitExample/README.md index 254ca9bc4..24c8663d8 100644 --- a/CommunityToolkitExample/README.md +++ b/CommunityToolkitExample/README.md @@ -9,14 +9,10 @@ Right away we use IoC to load our views and view models. ``` csharp // As a public property for access further in the application if needed. public static IServiceProvider Services { get; private set; } - -. . . - +... // In Main Services = ConfigureServices (); - -. . . - +... private static IServiceProvider ConfigureServices () { var services = new ServiceCollection (); @@ -41,13 +37,9 @@ internal partial class LoginView : IRecipient> { // Initialize our Receive method WeakReferenceMessenger.Default.Register (this); - ... - ViewModel = viewModel; - ... - passwordInput.TextChanged += (_, _) => { ViewModel.Password = passwordInput.Text; @@ -58,14 +50,11 @@ internal partial class LoginView : IRecipient> if (!ViewModel.CanLogin) { return; } ViewModel.LoginCommand.Execute (null); }; - ... - + // Let the view model know the view is intialized. Initialized += (_, _) => { ViewModel.Initialized (); }; } - ... - } ``` @@ -76,22 +65,16 @@ Momentarily slipping over to the view model, all bindable properties use some fo internal partial class LoginViewModel : ObservableObject { ... - [ObservableProperty] private bool _canLogin; private string _password; - ... - public LoginViewModel () { ... - Password = string.Empty; - - ... - + ... LoginCommand = new (Execute); Clear (); @@ -100,9 +83,7 @@ internal partial class LoginViewModel : ObservableObject async void Execute () { await Login (); } } - ... - public RelayCommand LoginCommand { get; } public string Password @@ -121,7 +102,6 @@ The use of `WeakReferenceMessenger` provides one method of signaling the view fr ``` csharp ... - private async Task Login () { SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); @@ -149,7 +129,6 @@ private void ValidateLogin () CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); SendMessage (LoginAction.Validation); } - ... ``` @@ -177,4 +156,4 @@ public void Receive (Message message) } ``` -There are other ways of handling cross-thread communication, this gives just one example. \ No newline at end of file +There are other ways of handling cross-thread communication, this gives just one example. From 5709843d8f2f6bfe3c3f94c57bcefce34696703f Mon Sep 17 00:00:00 2001 From: "John M. Baughman" <1634414+johnmbaughman@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:54:00 -0500 Subject: [PATCH 17/85] Update README.md --- CommunityToolkitExample/README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/CommunityToolkitExample/README.md b/CommunityToolkitExample/README.md index 24c8663d8..c3a712868 100644 --- a/CommunityToolkitExample/README.md +++ b/CommunityToolkitExample/README.md @@ -2,8 +2,6 @@ This small demo gives an example of using the `CommunityToolkit.MVVM` framework's `ObservableObject`, `ObservableProperty`, and `IRecipient` in conjunction with `Microsoft.Extensions.DependencyInjection`. -### Startup - Right away we use IoC to load our views and view models. ``` csharp @@ -58,10 +56,9 @@ internal partial class LoginView : IRecipient> } ``` -Momentarily slipping over to the view model, all bindable properties use some form of `ObservableProperty` with the class deriving from `ObservableObject`. Commands are of the `RelayCommand` type. +Momentarily slipping over to the view model, all bindable properties use some form of `ObservableProperty` with the class deriving from `ObservableObject`. Commands are of the `RelayCommand` type. The use of `ObservableProperty` generates the code for handling `INotifyPropertyChanged` and `INotifyPropertyChanging`. ``` csharp - internal partial class LoginViewModel : ObservableObject { ... @@ -155,5 +152,3 @@ public void Receive (Message message) Application.Refresh (); } ``` - -There are other ways of handling cross-thread communication, this gives just one example. From f02ca8abc0bcbf59a85fb0da0d6d2fd2215d9ea1 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 13 Jun 2024 08:52:55 -0700 Subject: [PATCH 18/85] Added horizontal scenario stuff --- Terminal.Gui/Input/CommandContext.cs | 5 +- Terminal.Gui/View/Layout/Pos.cs | 18 ++ Terminal.Gui/View/Layout/PosAlign.cs | 103 +++++------ Terminal.Gui/Views/ColorPicker.cs | 2 +- Terminal.Gui/Views/Shortcut.cs | 91 +++++---- UICatalog/Scenarios/Shortcuts.cs | 266 ++++++++++++++++++++------- 6 files changed, 334 insertions(+), 151 deletions(-) diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs index 5d3269226..555b179c8 100644 --- a/Terminal.Gui/Input/CommandContext.cs +++ b/Terminal.Gui/Input/CommandContext.cs @@ -1,14 +1,17 @@ #nullable enable namespace Terminal.Gui; + +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// Provides context for a that is being invoked. /// /// /// To define a that is invoked with context, -/// use +/// use . /// /// +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved public record struct CommandContext { /// diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index a5cf52249..f6966e64f 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -336,6 +336,24 @@ public abstract class Pos /// internal virtual bool ReferencesOtherViews () { return false; } + public bool Has (Type type, out Pos pos) + { + pos = this; + if (type == GetType()) + { + return true; + } + + // If we are a PosCombine, we have to check the left and right + // to see if they are of the type we are looking for. + if (this is PosCombine { } combine && (combine.Left.Has (type, out pos) || combine.Right.Has (type, out pos))) + { + return true; + } + + return false; + } + #endregion virtual methods #region operators diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs index 306017d65..79e22d410 100644 --- a/Terminal.Gui/View/Layout/PosAlign.cs +++ b/Terminal.Gui/View/Layout/PosAlign.cs @@ -70,59 +70,67 @@ public class PosAlign : Pos List dimensionsList = new (); // PERF: If this proves a perf issue, consider caching a ref to this list in each item - List viewsInGroup = views.Where ( - v => - { - return dimension switch - { - Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, - Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, - _ => false - }; - }) - .ToList (); + List posAligns = views.Select ( + v => + { + switch (dimension) + { + case Dimension.Width when v.X.Has (typeof (PosAlign), out var pos): - if (viewsInGroup.Count == 0) - { - return; - } + if (pos is PosAlign posAlignX && posAlignX.GroupId == groupId) + { + return posAlignX; + } + + break; + case Dimension.Height when v.Y.Has (typeof (PosAlign), out var pos): + if (pos is PosAlign posAlignY && posAlignY.GroupId == groupId) + { + return posAlignY; + } + + break; + } + + return null; + }) + .ToList (); // PERF: We iterate over viewsInGroup multiple times here. Aligner? firstInGroup = null; // Update the dimensionList with the sizes of the views - for (var index = 0; index < viewsInGroup.Count; index++) + for (var index = 0; index < posAligns.Count; index++) { - View view = viewsInGroup [index]; - PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign; - - if (posAlign is { }) + if (posAligns [index] is { }) { - if (index == 0) + if (firstInGroup is null) { - firstInGroup = posAlign.Aligner; + firstInGroup = posAligns [index]!.Aligner; } - dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height); + dimensionsList.Add (dimension == Dimension.Width ? views [index].Frame.Width : views [index].Frame.Height); } } + if (firstInGroup is null) + { + return; + } + // Update the first item in the group with the new container size. - firstInGroup!.ContainerSize = size; + firstInGroup.ContainerSize = size; // Align int [] locations = firstInGroup.Align (dimensionsList.ToArray ()); // Update the cached location for each item - for (var index = 0; index < viewsInGroup.Count; index++) + for (int posIndex = 0, locIndex = 0; posIndex < posAligns.Count; posIndex++) { - View view = viewsInGroup [index]; - PosAlign? align = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign; - - if (align is { }) + if (posAligns [posIndex] is { }) { - align._cachedLocation = locations [index]; + posAligns [posIndex]!._cachedLocation = locations [locIndex++]; } } } @@ -168,6 +176,14 @@ public class PosAlign : Pos return 0; } + // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. + /// + /// Returns the minimum size a group of views with the same can be. + /// + /// + /// + /// + /// public static int CalculateMinDimension (int groupId, IList views, Dimension dimension) { List dimensionsList = new (); @@ -177,11 +193,11 @@ public class PosAlign : Pos v => { return dimension switch - { - Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, - Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, - _ => false - }; + { + Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, + Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, + _ => false + }; }) .ToList (); @@ -192,8 +208,6 @@ public class PosAlign : Pos // PERF: We iterate over viewsInGroup multiple times here. - Aligner? firstInGroup = null; - // Update the dimensionList with the sizes of the views for (var index = 0; index < viewsInGroup.Count; index++) { @@ -203,26 +217,11 @@ public class PosAlign : Pos if (posAlign is { }) { - if (index == 0) - { - firstInGroup = posAlign.Aligner; - - //if (!posAlign._cachedLocation.HasValue) - //{ - // AlignAndUpdateGroup (groupId, viewsInGroup, dimension, firstInGroup.ContainerSize ); - //} - - } - dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height); } } // Align - var aligner = firstInGroup; - aligner.ContainerSize = dimensionsList.Sum(); - int [] locations = aligner.Align (dimensionsList.ToArray ()); - return dimensionsList.Sum (); } } diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index addf7a1a4..c033cb4f0 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -48,7 +48,7 @@ public class ColorPicker : View private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me) { - if (CanFocus) + // if (CanFocus) { Cursor = new Point (me.MouseEvent.Position.X / _boxWidth, me.MouseEvent.Position.Y / _boxHeight); SetFocus (); diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 296932398..1dfabeb86 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -27,8 +27,7 @@ namespace Terminal.Gui; /// right side. /// /// -/// The command text can be set by setting the 's Text property or by setting -/// . +/// The command text can be set by setting the 's Text property. /// /// /// The help text can be set by setting the property or by setting . @@ -59,27 +58,31 @@ public class Shortcut : View TitleChanged += Shortcut_TitleChanged; // This needs to be set before CommandView is set - CommandView = new (); + CommandView = new () + { + Width = Dim.Auto (), + Height = Dim.Auto (), + }; HelpView.Id = "_helpView"; HelpView.CanFocus = false; SetHelpViewDefaultLayout (); Add (HelpView); - HelpView.MouseClick += Shortcut_MouseClick; KeyView.Id = "_keyView"; KeyView.CanFocus = false; SetKeyViewDefaultLayout (); Add (KeyView); + // If the user clicks anywhere on the Shortcut, other than the CommandView, invoke the Command + MouseClick += Shortcut_MouseClick; + HelpView.MouseClick += Shortcut_MouseClick; KeyView.MouseClick += Shortcut_MouseClick; - MouseClick += Shortcut_MouseClick; + LayoutStarted += OnLayoutStarted; Initialized += OnInitialized; - LayoutStarted += OnLayoutStarted; - return; void OnInitialized (object sender, EventArgs e) @@ -111,10 +114,19 @@ public class Shortcut : View // Helper to set Width consistently Dim GetWidthDimAuto () { + // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. return Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); } } + + /// + /// Gets or sets the for this . The default is + /// , which is ideal for status bars and toolbars. If set to , + /// the Shortcut will be configured for vertical layout, which is ideal for menus. + /// + public Orientation Orientation { get; set; } = Orientation.Horizontal; + // When one of the subviews is "empty" we don't want to show it. So we // Use Add/Remove. We need to be careful to add them in the right order // so Pos.Align works correctly. @@ -122,7 +134,7 @@ public class Shortcut : View { RemoveAll (); - if (!string.IsNullOrEmpty (CommandView.Text)) + if (CommandView.Visible) { Add (CommandView); } @@ -161,7 +173,7 @@ public class Shortcut : View if (currentWidth < _minimumDimAutoWidth) { int delta = _minimumDimAutoWidth.Value - currentWidth; - int maxHelpWidth = int.Max (0, HelpView.Text.GetColumns () + 2 - delta); + int maxHelpWidth = int.Max (0, HelpView.Text.GetColumns () + Margin.Thickness.Horizontal - delta); switch (maxHelpWidth) { @@ -180,13 +192,14 @@ public class Shortcut : View case 2: // Scrunch just the right margin - HelpView.Margin.Thickness = new (1, 0, 0, 0); + var t = GetMarginThickness (); + HelpView.Margin.Thickness = new (t.Right, t.Top, t.Left - 1, t.Bottom); break; default: // Default margin - HelpView.Margin.Thickness = new (1, 0, 1, 0); + HelpView.Margin.Thickness = GetMarginThickness (); break; } @@ -208,6 +221,18 @@ public class Shortcut : View } } + private Thickness GetMarginThickness () + { + if (Orientation == Orientation.Vertical) + { + return new Thickness (1, 0, 1, 0); + } + else + { + return new Thickness (1, 0, 1, 0); + } + } + private Color? _savedForeColor; private void Shortcut_Highlight (object sender, HighlightEventArgs e) @@ -339,18 +364,26 @@ public class Shortcut : View // TODO: Determine if it makes sense to allow the CommandView to be focusable. // Right now, we don't set CanFocus to false here. - _commandView.CanFocus = false; + //_commandView.CanFocus = true; + + //// Bar will set the width of all CommandViews to the width of the widest CommandViews. + ////if (_commandView.Width == Dim.Absolute(0)) + //{ + // _commandView.Width = Dim.Auto (); + //} + + ////if (_commandView.Height == Dim.Absolute (0)) + //{ + // _commandView.Height = Dim.Auto (); + //} - // Bar will set the width of all CommandViews to the width of the widest CommandViews. - _commandView.Width = Dim.Auto (); - _commandView.Height = Dim.Auto (); _commandView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); _commandView.Y = 0; //Pos.Center (); _commandView.MouseClick += Shortcut_MouseClick; _commandView.Accept += CommandViewAccept; - _commandView.Margin.Thickness = new (1, 0, 1, 0); + _commandView.Margin.Thickness = GetMarginThickness (); _commandView.HotKeyChanged += (s, e) => { @@ -364,7 +397,6 @@ public class Shortcut : View _commandView.HotKeySpecifier = new ('_'); Title = _commandView.Text; - _commandView.TextChanged += CommandViewTextChanged; SetHelpViewDefaultLayout (); SetKeyViewDefaultLayout (); @@ -373,8 +405,6 @@ public class Shortcut : View return; - void CommandViewMouseEvent (object sender, MouseEventEventArgs e) { e.Handled = true; } - void CommandViewTextChanged (object sender, StateEventArgs e) { Title = _commandView.Text; @@ -417,11 +447,12 @@ public class Shortcut : View private void SetHelpViewDefaultLayout () { - HelpView.Margin.Thickness = new (1, 0, 1, 0); + HelpView.Margin.Thickness = GetMarginThickness (); HelpView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); HelpView.Y = 0; //Pos.Center (), HelpView.Width = Dim.Auto (DimAutoStyle.Text); - HelpView.Height = Dim.Height (CommandView); + HelpView.Height = CommandView?.IsAdded == true ? Dim.Height (CommandView) : 1; + HelpView.Visible = true; HelpView.VerticalTextAlignment = Alignment.Center; } @@ -537,12 +568,12 @@ public class Shortcut : View private void SetKeyViewDefaultLayout () { - KeyView.Margin.Thickness = new (1, 0, 1, 0); + KeyView.Margin.Thickness = GetMarginThickness(); KeyView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); - //KeyView.Y = Pos.Center (); KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); - KeyView.Height = Dim.Height (CommandView); + KeyView.Height = CommandView?.IsAdded == true ? Dim.Height (CommandView) : 1; + KeyView.Visible = true; // Right align the text in the keyview @@ -582,6 +613,7 @@ public class Shortcut : View switch (KeyBindingScope) { case KeyBindingScope.Application: + handled = false; break; case KeyBindingScope.Focused: @@ -663,18 +695,9 @@ public class Shortcut : View /// public override bool OnLeave (View view) { + // Reset the color scheme (to SuperView). ColorScheme = null; - return base.OnLeave (view); - if (SuperView is { }) - { - ColorScheme = new (SuperView?.ColorScheme) - { - Normal = SuperView.ColorScheme.Normal, - HotNormal = SuperView.ColorScheme.HotNormal - }; - } - return base.OnLeave (view); } diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 9bcc7426f..e232f47fe 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Linq; +using System.Timers; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -34,15 +35,16 @@ public class Shortcuts : Scenario { X = Pos.AnchorEnd (), Width = 40, - Height = Dim.Fill (), + Height = Dim.Fill (4), ColorScheme = Colors.ColorSchemes ["Toplevel"], Source = new ListWrapper (eventSource) }; Application.Top.Add (eventLog); - var shortcut1 = new Shortcut + var vShortcut1 = new Shortcut { - X = 20, + Orientation = Orientation.Vertical, + X = 0, Width = 35, Title = "A_pp Shortcut", Key = Key.F1, @@ -50,14 +52,15 @@ public class Shortcuts : Scenario KeyBindingScope = KeyBindingScope.Application, BorderStyle = LineStyle.Dotted }; - shortcut1.Border.Thickness = new (1, 1, 1, 1); - Application.Top.Add (shortcut1); + vShortcut1.Border.Thickness = new (1, 1, 1, 1); + Application.Top.Add (vShortcut1); - var shortcut2 = new Shortcut + var vShortcut2 = new Shortcut { - X = 20, - Y = Pos.Bottom (shortcut1) - 1, - Width = Dim.Width (shortcut1), + Orientation = Orientation.Vertical, + X = 0, + Y = Pos.Bottom (vShortcut1) - 1, + Width = Dim.Width (vShortcut1), Key = Key.F2, Text = "Width is ^", KeyBindingScope = KeyBindingScope.HotKey, @@ -69,71 +72,73 @@ public class Shortcuts : Scenario } }; - ((RadioGroup)shortcut2.CommandView).SelectedItemChanged += (o, args) => + ((RadioGroup)vShortcut2.CommandView).SelectedItemChanged += (o, args) => { eventSource.Add ($"SelectedItemChanged: {o.GetType ().Name} - {args.SelectedItem}"); eventLog.MoveDown (); }; - shortcut2.Accept += (o, args) => + vShortcut2.Accept += (o, args) => { // Cycle to next item. If at end, set 0 - if (((RadioGroup)shortcut2.CommandView).SelectedItem < ((RadioGroup)shortcut2.CommandView).RadioLabels.Length - 1) + if (((RadioGroup)vShortcut2.CommandView).SelectedItem < ((RadioGroup)vShortcut2.CommandView).RadioLabels.Length - 1) { - ((RadioGroup)shortcut2.CommandView).SelectedItem++; + ((RadioGroup)vShortcut2.CommandView).SelectedItem++; } else { - ((RadioGroup)shortcut2.CommandView).SelectedItem = 0; + ((RadioGroup)vShortcut2.CommandView).SelectedItem = 0; } }; - shortcut2.Border.Thickness = new (1, 1, 1, 1); - Application.Top.Add (shortcut2); + vShortcut2.Border.Thickness = new (1, 1, 1, 1); + Application.Top.Add (vShortcut2); - var shortcut3 = new Shortcut + var vShortcut3 = new Shortcut { - X = 20, - Y = Pos.Bottom (shortcut2), + Orientation = Orientation.Vertical, + X = 0, + Y = Pos.Bottom (vShortcut2), CommandView = new CheckBox { Text = "_Align" }, Key = Key.F3, HelpText = "Width is Fill", Width = Dim.Fill () - Dim.Width (eventLog), KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Dotted + BorderStyle = LineStyle.Rounded }; - shortcut3.CommandView.CanFocus = true; - shortcut3.Border.Thickness = new (1, 1, 1, 0); + vShortcut3.Border.Thickness = new (1, 1, 1, 0); - ((CheckBox)shortcut3.CommandView).Toggled += (s, e) => + ((CheckBox)vShortcut3.CommandView).Toggled += (s, e) => { - if (shortcut3.CommandView is CheckBox cb) + if (vShortcut3.CommandView is CheckBox cb) { eventSource.Add ($"Toggled: {cb.Text}"); eventLog.MoveDown (); var max = 0; + var toAlign = Application.Top.Subviews.Where (v => v is Shortcut s && s.Orientation == Orientation.Vertical && s.BorderStyle == LineStyle.Rounded); if (e.NewValue == true) { - foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) + foreach (Shortcut peer in toAlign) { max = Math.Max (max, peer.KeyView.Text.GetColumns ()); } } - foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) + foreach (Shortcut peer in toAlign) { peer.MinimumKeyViewSize = max; } } }; - Application.Top.Add (shortcut3); + Application.Top.Add (vShortcut3); - var shortcut4 = new Shortcut + var vShortcut4 = new Shortcut { - X = 20, - Y = Pos.Bottom (shortcut3), - Width = Dim.Width (shortcut3), + Orientation = Orientation.Vertical, + X = 0, + Y = Pos.Bottom (vShortcut3), + Width = Dim.Width (vShortcut3), CommandView = new Button { Title = "B_utton", @@ -141,41 +146,56 @@ public class Shortcuts : Scenario HelpText = "Width is Fill", Key = Key.K, KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Dotted + BorderStyle = LineStyle.Rounded }; - Button button = (Button)shortcut4.CommandView; - shortcut4.CommandView.Accept += Button_Clicked; - shortcut4.CommandView.CanFocus = true; - shortcut4.Border.Thickness = new (1, 0, 1,0); + Button button = (Button)vShortcut4.CommandView; + vShortcut4.CommandView.Accept += Button_Clicked; + vShortcut4.Border.Thickness = new (1, 0, 1, 0); - Application.Top.Add (shortcut4); + Application.Top.Add (vShortcut4); - var shortcut5 = new Shortcut + var vShortcut5 = new Shortcut { - X = 20, - Y = Pos.Bottom (shortcut4) , - Width = Dim.Width (shortcut4), + Orientation = Orientation.Vertical, + X = 0, + Y = Pos.Bottom (vShortcut4), + Width = Dim.Width (vShortcut4), - Title = "Fi_ve", Key = Key.F5.WithCtrl.WithAlt.WithShift, - HelpText = "Width is Fill", + HelpText = "CommandView.CanFocus", KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Dotted + BorderStyle = LineStyle.Rounded, + CommandView = new CheckBox { Text = "_CanFocus" }, }; - shortcut5.Border.Thickness = new (1, 0, 1, 1); + vShortcut5.Border.Thickness = new (1, 0, 1, 1); - Application.Top.Add (shortcut5); + ((CheckBox)vShortcut5.CommandView).Toggled += (s, e) => + { + if (vShortcut5.CommandView is CheckBox cb) + { + eventSource.Add ($"Toggled: {cb.Text}"); + eventLog.MoveDown (); - var shortcutSlider = new Shortcut + foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) + { + peer.CanFocus = e.NewValue == true; + peer.CommandView.CanFocus = e.NewValue == true; + } + } + }; + Application.Top.Add (vShortcut5); + + var vShortcutSlider = new Shortcut { - X = 20, - Y = Pos.Bottom (shortcut5) - 1, + Orientation = Orientation.Vertical, + X = 0, + Y = Pos.Bottom (vShortcut5) - 1, Key = Key.F5, HelpText = "Width is Fill", - Width = Dim.Width (shortcut5), + Width = Dim.Width (vShortcut5), KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Dotted, + BorderStyle = LineStyle.Rounded, CommandView = new Slider { Orientation = Orientation.Vertical, @@ -183,31 +203,151 @@ public class Shortcuts : Scenario } }; - ((Slider)shortcutSlider.CommandView).Options = new () { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; - ((Slider)shortcutSlider.CommandView).SetOption (0); - shortcutSlider.Border.Thickness = new (1, 1, 1, 1); + ((Slider)vShortcutSlider.CommandView).Options = new () { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; + ((Slider)vShortcutSlider.CommandView).SetOption (0); + vShortcutSlider.Border.Thickness = new (1, 1, 1, 1); - ((Slider)shortcutSlider.CommandView).OptionsChanged += (o, args) => + ((Slider)vShortcutSlider.CommandView).OptionsChanged += (o, args) => { eventSource.Add ($"OptionsChanged: {o.GetType ().Name} - {args.Options}"); eventLog.MoveDown (); }; - Application.Top.Add (shortcutSlider); + Application.Top.Add (vShortcutSlider); - var shortcut6 = new Shortcut + var vShortcut6 = new Shortcut { - X = 20, - Y = Pos.Bottom (shortcutSlider) - 1, - Width = Dim.Width (shortcutSlider), + Orientation = Orientation.Vertical, + X = 0, + Y = Pos.Bottom (vShortcutSlider) - 1, + Width = Dim.Width (vShortcutSlider), Title = "_No Key", HelpText = "Keyless", - BorderStyle = LineStyle.Dotted + BorderStyle = LineStyle.Rounded }; - shortcut6.Border.Thickness = new (1, 1, 1, 1); + vShortcut6.Border.Thickness = new (1, 1, 1, 1); + + Application.Top.Add (vShortcut6); + + ((CheckBox)vShortcut3.CommandView).OnToggled(); + + // Horizontal + var hShortcut1 = new Shortcut + { + X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), + Y = Pos.Bottom (eventLog) + 1, + Key = Key.F7, + HelpText = "Horizontal", + BorderStyle = LineStyle.Dashed, + CanFocus = false + }; + hShortcut1.Border.Thickness = new (0, 0, 1, 0); + + hShortcut1.CommandView = new ProgressBar + { + Text = "Progress", + Title = "P", + Fraction = 0.5f, + Width = 10, + Height = 1, + ProgressBarStyle = ProgressBarStyle.Continuous + }; + hShortcut1.CommandView.Width = 10; + hShortcut1.CommandView.Height = 1; + hShortcut1.CommandView.CanFocus = false; + Timer timer = new (10) + { + AutoReset = true, + }; + timer.Elapsed += (o, args) => + { + if (hShortcut1.CommandView is ProgressBar pb) + { + if (pb.Fraction == 1.0) + { + pb.Fraction = 0; + } + pb.Fraction += 0.01f; + + Application.Wakeup (); + + pb.SetNeedsDisplay (); + } + }; + timer.Start (); + + Application.Top.Add (hShortcut1); + + var textField = new TextField () + { + Text = "Edit me", + Width = 10, + Height = 1, + CanFocus = true + }; + + var hShortcut2 = new Shortcut + { + X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), + Y = Pos.Top (hShortcut1), + Key = Key.F8, + HelpText = "Edit", + CanFocus = true, + BorderStyle = LineStyle.Dashed, + CommandView = textField, + }; + hShortcut2.Border.Thickness = new (0, 0, 1, 0); + + Application.Top.Add (hShortcut2); + + var hShortcutBG = new Shortcut + { + X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1)-1, + Y = Pos.Top (hShortcut2), + Key = Key.F9, + HelpText = "BG Color", + BorderStyle = LineStyle.Dashed, + CanFocus = false + }; + + var bgColor = new ColorPicker () + { + CanFocus = false, + BoxHeight = 1, + BoxWidth = 1, + }; + bgColor.ColorChanged += (o, args) => + { + Application.Top.ColorScheme = new ColorScheme (Application.Top.ColorScheme) + { + Normal = new Attribute (Application.Top.ColorScheme.Normal.Foreground, args.Color), + }; + }; + hShortcutBG.CommandView = bgColor; + hShortcutBG.Border.Thickness = new (1, 0, 1, 0); + + Application.Top.Add (hShortcutBG); + + var hShortcut3 = new Shortcut + { + X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), + Y = Pos.Top (hShortcut2), + Key = Key.Esc, + KeyBindingScope = KeyBindingScope.Application, + Title = "Quit", + HelpText = "App Scope", + BorderStyle = LineStyle.Dashed, + CanFocus = false + }; + hShortcut3.Border.Thickness = new (0); + hShortcut3.Accept += (o, args) => + { + Application.RequestStop (); + }; + + Application.Top.Add (hShortcut3); - Application.Top.Add (shortcut6); foreach (View sh in Application.Top.Subviews.Where (v => v is Shortcut)!) { From f0984f08f8bac7e022a1fb3e0d4b2df948de4c81 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 13 Jun 2024 10:37:57 -0700 Subject: [PATCH 19/85] Fixed stuff --- Terminal.Gui/Drawing/Aligner.cs | 14 +++++++++----- UnitTests/Drawing/AlignerTests.cs | 2 +- UnitTests/Views/ShortcutTests.cs | 18 ++++++------------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Terminal.Gui/Drawing/Aligner.cs b/Terminal.Gui/Drawing/Aligner.cs index 1c96a2ac0..47b4109f8 100644 --- a/Terminal.Gui/Drawing/Aligner.cs +++ b/Terminal.Gui/Drawing/Aligner.cs @@ -108,10 +108,11 @@ public class Aligner : INotifyPropertyChanged spacesToGive = containerSize - totalItemsSize; } + AlignmentModes mode = alignmentMode & ~AlignmentModes.AddSpaceBetweenItems; // copy to avoid modifying the original switch (alignment) { case Alignment.Start: - switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems) + switch (mode) { case AlignmentModes.StartToEnd: return Start (in sizesCopy, maxSpaceBetweenItems, spacesToGive); @@ -129,7 +130,7 @@ public class Aligner : INotifyPropertyChanged break; case Alignment.End: - switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems) + switch (mode) { case AlignmentModes.StartToEnd: return End (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive); @@ -147,7 +148,8 @@ public class Aligner : INotifyPropertyChanged break; case Alignment.Center: - switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems) + mode &= ~AlignmentModes.IgnoreFirstOrLast; + switch (mode) { case AlignmentModes.StartToEnd: return Center (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive); @@ -159,7 +161,8 @@ public class Aligner : INotifyPropertyChanged break; case Alignment.Fill: - switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems) + mode &= ~AlignmentModes.IgnoreFirstOrLast; + switch (mode) { case AlignmentModes.StartToEnd: return Fill (in sizesCopy, containerSize, totalItemsSize); @@ -260,7 +263,8 @@ public class Aligner : INotifyPropertyChanged var currentPosition = 0; if (totalItemsSize > containerSize) { - currentPosition = containerSize - totalItemsSize - spacesToGive; + // Don't allow negative positions + currentPosition = int.Max(0, containerSize - totalItemsSize - spacesToGive); } for (var i = 0; i < sizes.Length; i++) diff --git a/UnitTests/Drawing/AlignerTests.cs b/UnitTests/Drawing/AlignerTests.cs index cc9d1c92b..ecbe91c58 100644 --- a/UnitTests/Drawing/AlignerTests.cs +++ b/UnitTests/Drawing/AlignerTests.cs @@ -218,7 +218,7 @@ public class AlignerTests (ITestOutputHelper output) [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 1, 2, 3 }, 9, new [] { 0, 2, 6 })] [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 1, 2, 3 }, 10, new [] { 0, 2, 7 })] [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 1, 2, 3 }, 11, new [] { 0, 2, 8 })] - [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 1, 2, 3 }, 5, new [] { -1, 0, 2 })] // 5 is too small to fit the items. The first item is at -1.})] + [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 1, 2, 3 }, 5, new [] { 0, 1, 2 })] [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 1, 2, 3, 4 }, 10, new [] { 0, 1, 3, 6 })] [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 1, 2, 3, 4 }, 11, new [] { 0, 2, 4, 7 })] [InlineData (Alignment.Start, AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems | AlignmentModes.IgnoreFirstOrLast, new [] { 3, 3, 3 }, 21, new [] { 0, 4, 18 })] diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index a3b934626..efd1bd432 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -22,20 +22,20 @@ public class ShortcutTests } [Theory] - [InlineData ("", "", KeyCode.Null, 0)] + [InlineData ("", "", KeyCode.Null, 2)] [InlineData ("C", "", KeyCode.Null, 3)] - [InlineData ("", "H", KeyCode.Null, 3)] - [InlineData ("", "", KeyCode.K, 3)] + [InlineData ("", "H", KeyCode.Null, 5)] + [InlineData ("", "", KeyCode.K, 5)] [InlineData ("C", "", KeyCode.K, 6)] [InlineData ("C", "H", KeyCode.Null, 6)] - [InlineData ("", "H", KeyCode.K, 6)] + [InlineData ("", "H", KeyCode.K, 8)] [InlineData ("C", "H", KeyCode.K, 9)] public void NaturalSize (string command, string help, Key key, int expectedWidth) { Shortcut shortcut = new Shortcut () { Title = command, - Text = help, + HelpText = help, Key = key, }; @@ -79,7 +79,7 @@ public class ShortcutTests } [Fact] - public void CommandView_Text_And_Title_Are_The_Same () + public void CommandView_Text_And_Title_Track () { Shortcut shortcut = new Shortcut () { @@ -88,12 +88,6 @@ public class ShortcutTests Assert.Equal (shortcut.Title, shortcut.CommandView.Text); - shortcut = new Shortcut () - { - }; - shortcut.CommandView.Text = "T"; - Assert.Equal (shortcut.Title, shortcut.CommandView.Text); - shortcut = new Shortcut () { }; From 14361b1ee30e25d0847a5e61306740e60a1a786f Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 13 Jun 2024 19:24:52 +0100 Subject: [PATCH 20/85] Fixes #3536. Application.Run is broken by not calling Init before initializing T. --- Terminal.Gui/Application/Application.cs | 15 +++++++++++++-- UnitTests/Application/ApplicationTests.cs | 20 +++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index f015065fe..f7eea3c94 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -679,6 +679,12 @@ public static partial class Application public static T Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new () { + if (!_initialized) + { + // Init() has NOT been called. + InternalInit (driver, null, true); + } + var top = new T (); Run (top, errorHandler, driver); @@ -708,7 +714,10 @@ public static partial class Application /// method will only process any pending events, timers, idle handlers and then /// return control immediately. /// - /// Calling first is not needed as this function will initialize the application. + /// Calling first is not needed if only or + /// + /// has been used, otherwise it's needed to call first. + /// /// /// RELEASE builds only: When is any exceptions will be /// rethrown. Otherwise, if will be called. If @@ -746,7 +755,9 @@ public static partial class Application else { // Init() has NOT been called. - InternalInit (driver, null, true); + throw new InvalidOperationException ( + "Init() has not been called. Only Run() or Run() can be used without calling Init()." + ); } var resume = true; diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 498842437..c78903771 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -863,7 +863,7 @@ public class ApplicationTests #if DEBUG_IDISPOSABLE Assert.False (w.WasDisposed); - Exception exception = Record.Exception (() => Application.Shutdown ()); // Invalid - w has not been disposed. + Exception exception = Record.Exception (Application.Shutdown); // Invalid - w has not been disposed. Assert.NotNull (exception); w.Dispose (); @@ -902,7 +902,7 @@ public class ApplicationTests #if DEBUG_IDISPOSABLE Assert.Equal (top, Application.Top); Assert.False (top.WasDisposed); - Exception exception = Record.Exception (() => Application.Shutdown ()); + Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); Assert.False (top.WasDisposed); #endif @@ -934,7 +934,7 @@ public class ApplicationTests Application.Run (null, driver); #if DEBUG_IDISPOSABLE Assert.False (Application.Top.WasDisposed); - Exception exception = Record.Exception (() => Application.Shutdown ()); + Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); Assert.False (Application.Top.WasDisposed); @@ -949,12 +949,22 @@ public class ApplicationTests } [Fact] - public void Run_t_Creates_Top_Without_Init () + public void Run_t_Does_Not_Creates_Top_Without_Init () { + // When a Toplevel is created it must already have all the Application configuration loaded + // This is only possible by two ways: + // 1 - Using Application.Init first + // 2 - Using Application.Run() or Application.Run() + // The Application.Run(new(Toplevel)) must always call Application.Init() first because + // the new(Toplevel) may be a derived class that is possible using Application static + // properties that is only available after the Application.Init was called var driver = new FakeDriver (); Assert.Null (Application.Top); + Assert.Throws (() => Application.Run (new (), null, driver)); + + Application.Init (driver); Application.Iteration += (s, e) => { Assert.NotNull (Application.Top); @@ -963,7 +973,7 @@ public class ApplicationTests Application.Run (new (), null, driver); #if DEBUG_IDISPOSABLE Assert.False (Application.Top.WasDisposed); - Exception exception = Record.Exception (() => Application.Shutdown ()); + Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); Assert.False (Application.Top.WasDisposed); From 817b9b858a39db34a98a0c24d9f219a34fdaccae Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 13 Jun 2024 12:13:24 -0700 Subject: [PATCH 21/85] Got Bar working agian. Updaged UI Catalog to use Bar --- Terminal.Gui/Views/Bar.cs | 109 +++++------------- UICatalog/Scenarios/Bars.cs | 78 +++++++------ UICatalog/Scenarios/Shortcuts.cs | 1 - UICatalog/UICatalog.cs | 190 ++++++++++++++++++------------- 4 files changed, 187 insertions(+), 191 deletions(-) diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 335da5512..251261993 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -51,23 +51,24 @@ public class Bar : View //view.ColorScheme = ColorScheme; // Add any HotKey keybindings to our bindings - IEnumerable> bindings = view.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.HotKey); + //IEnumerable> bindings = view.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.HotKey); - foreach (KeyValuePair binding in bindings) - { - AddCommand ( - binding.Value.Commands [0], - () => - { - if (view is Shortcut shortcut) - { - return shortcut.CommandView.InvokeCommands (binding.Value.Commands); - } + //foreach (KeyValuePair binding in bindings) + //{ + // AddCommand ( + // binding.Value.Commands [0], + // () => + // { + // if (view is Shortcut shortcut) + // { + // return shortcut.CommandView.InvokeCommands (binding.Value.Commands); + // } + + // return false; + // }); + // KeyBindings.Add (binding.Key, binding.Value); + //} - return false; - }); - KeyBindings.Add (binding.Key, binding.Value); - } base.Add (view); } @@ -88,21 +89,18 @@ public class Bar : View continue; } - if (prevBarItem == null) + barItem.BorderStyle = LineStyle.Dashed; + if (index == Subviews.Count - 1) { - barItem.X = 0; + barItem.Border.Thickness = new Thickness (0, 0, 0, 0); } else { - // Make view to right be autosize - //Subviews [^1].AutoSize = true; - - // Align the view to the right of the previous view - barItem.X = Pos.Right (prevBarItem); + barItem.Border.Thickness = new Thickness (0, 0, 1, 0); } + barItem.X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast); barItem.Y = Pos.Center (); - barItem.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); prevBarItem = barItem; } @@ -115,38 +113,29 @@ public class Bar : View int maxCommandWidth = 0; int maxHelpWidth = 0; + int minKeyWidth = 0; List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); foreach (Shortcut shortcut in shortcuts) { // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView - shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); - shortcut.KeyView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); - shortcut.HelpView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); - } - - maxCommandWidth = shortcuts.Max (s => s.CommandView.Frame.Width); - maxHelpWidth = shortcuts.Max (s => s.HelpView.Frame.Width); - - // Set the width of all CommandView's and HelpView's to the max width - foreach (Shortcut shortcut in shortcuts) - { - shortcut.CommandView.Width = Dim.Auto (minimumContentDim: maxCommandWidth); - shortcut.KeyView.Width = Dim.Auto (); - shortcut.HelpView.Width = Dim.Auto (minimumContentDim: maxHelpWidth); - - // shortcut.LayoutSubviews (); + //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); } // Set the overall size of the Bar and arrange the views vertically - var maxBarItemWidth = 0; for (var index = 0; index < Subviews.Count; index++) { View barItem = Subviews [index]; + if (barItem is Shortcut scBarItem) + { + scBarItem.MinimumKeyViewSize = minKeyWidth; + } + if (!barItem.Visible) { continue; @@ -166,7 +155,6 @@ public class Bar : View if (barItem is Shortcut shortcut) { - //shortcut.SetRelativeLayout (new (int.MaxValue, int.MaxValue)); maxBarItemWidth = Math.Max (maxBarItemWidth, shortcut.Frame.Width); } else @@ -179,45 +167,10 @@ public class Bar : View foreach (Shortcut shortcut in shortcuts) { - if (Width is DimAuto) - { - shortcut.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: maxBarItemWidth); - } - else - { - //shortcut._container.Width = Dim.Fill (); - // shortcut.Width = Dim.Fill (); - } - - shortcut.LayoutSubviews (); + shortcut.Width = maxBarItemWidth; } - - - //for (var index = 0; index < Subviews.Count; index++) - //{ - // var shortcut = Subviews [index] as Shortcut; - - // if (shortcut is { Visible: false }) - // { - // continue; - // } - - // if (Width is DimAuto) - // { - // shortcut._container.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: maxBarItemWidth); - // } - // else - // { - // shortcut._container.Width = Dim.Fill (); - // shortcut.Width = Dim.Fill (); - // } - - // //shortcut.SetContentSize (new (maxBarItemWidth, 1)); - // //shortcut.Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: int.Max(maxBarItemWidth, GetContentSize().Width)); - - //} - + Height = Subviews.Count; break; } diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 03a5fbcf7..441e83a5a 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Text; using Terminal.Gui; @@ -35,7 +36,7 @@ public class Bars : Scenario { X = Pos.AnchorEnd (), Width = 50, - Height = Dim.Fill (), + Height = Dim.Fill (3), ColorScheme = Colors.ColorSchemes ["Toplevel"], Source = new ListWrapper (eventSource) }; @@ -46,28 +47,19 @@ public class Bars : Scenario Title = "_Zigzag", Key = Key.G.WithCtrl, Text = "Gonna zig zag", - KeyBindingScope = KeyBindingScope.HotKey, }; shortcut1.Accept += (s, e) => { eventSource.Add ($"Accept: {s}"); eventLog.MoveDown (); }; - Application.Top.Add (shortcut1); - shortcut1.SetFocus (); - - //var shortcut2 = new Shortcut - //{ - // Title = "Za_G", - // Text = "Gonna zag", - // Key = Key.G.WithAlt, - // KeyBindingScope = KeyBindingScope.HotKey, - // Command = Command.Accept, - // X = Pos.Left (shortcut1), - // Y = Pos.Bottom (shortcut1), - // //Width = 50, - //}; + var shortcut2 = new Shortcut + { + Title = "Za_G", + Text = "Gonna zag", + Key = Key.G.WithAlt, + }; //var shortcut3 = new Shortcut //{ @@ -112,15 +104,14 @@ public class Bars : Scenario // eventLog.MoveDown (); // }; - //var bar = new Bar - //{ - // X = 2, - // Y = Pos.Bottom(shortcut1), - // Orientation = Orientation.Vertical, - // StatusBarStyle = false, - // Width = Dim.Percent(40) - //}; - //bar.Add (shortcut3, shortcut4); + var bar = new Bar + { + X = 2, + Y = 2, + Orientation = Orientation.Vertical, + StatusBarStyle = false, + }; + bar.Add (shortcut1, shortcut2); ////CheckBox hello = new () ////{ @@ -135,14 +126,27 @@ public class Bars : Scenario //// eventLog.MoveDown (); //// }; - //Application.Top.Add (bar); + + Application.Top.Add (bar); // BUGBUG: This should not be needed - //Application.Top.LayoutSubviews (); + Application.Top.LayoutSubviews (); //SetupMenuBar (); //SetupContentMenu (); - // SetupStatusBar (); + SetupStatusBar (); + + foreach (Bar barView in Application.Top.Subviews.Where (b => b is Bar)!) + { + foreach (Shortcut sh in barView.Subviews.Where (s => s is Shortcut)!) + { + sh.Accept += (o, args) => + { + eventSource.Add ($"Accept: {sh!.CommandView.Text}"); + eventLog.MoveDown (); + }; + } + } } private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } @@ -404,11 +408,10 @@ public class Bars : Scenario var shortcut = new Shortcut { - Text = "Quit Application", + Text = "Quit", Title = "Q_uit", Key = Application.QuitKey, KeyBindingScope = KeyBindingScope.Application, -// Command = Command.QuitToplevel, CanFocus = false }; @@ -443,8 +446,7 @@ public class Bars : Scenario { Title = "_Show/Hide", Key = Key.F10, - KeyBindingScope = KeyBindingScope.HotKey, - //Command = Command.ToggleExpandCollapse, + KeyBindingScope = KeyBindingScope.Application, CommandView = new CheckBox { Text = "_Show/Hide" @@ -457,18 +459,23 @@ public class Bars : Scenario var button1 = new Button { Text = "I'll Hide", - Visible = false + // Visible = false }; button1.Accept += Button_Clicked; statusBar.Add (button1); - ((CheckBox)shortcut.CommandView).Toggled += (s, e) => + shortcut.Accept += (s, e) => { button1.Visible = !button1.Visible; button1.Enabled = button1.Visible; }; - statusBar.Add (new Label { HotKeySpecifier = new Rune ('_'), Text = "Fo_cusLabel", CanFocus = true }); + statusBar.Add (new Label + { + HotKeySpecifier = new Rune ('_'), + Text = "Fo_cusLabel", + CanFocus = true + }); var button2 = new Button { @@ -482,7 +489,6 @@ public class Bars : Scenario Application.Top.Add (statusBar); - } } diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index e232f47fe..c2411a379 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -355,7 +355,6 @@ public class Shortcuts : Scenario { shortcut.Accept += (o, args) => { - var x = button; eventSource.Add ($"Accept: {shortcut!.CommandView.Text}"); eventLog.MoveDown (); }; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 6c57e4d67..801739287 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -371,12 +371,14 @@ internal class UICatalogApp public class UICatalogTopLevel : Toplevel { public ListView CategoryList; - public StatusItem DriverName; - public MenuItem? miForce16Colors; - public MenuItem? miIsMenuBorderDisabled; - public MenuItem? miIsMouseDisabled; - public MenuItem? miUseSubMenusSingleFrame; - public StatusItem OS; + public MenuItem? MiForce16Colors; + public MenuItem? MiIsMenuBorderDisabled; + public MenuItem? MiIsMouseDisabled; + public MenuItem? MiUseSubMenusSingleFrame; + public new Bar StatusBar; + public Shortcut? ShForce16Colors; + //public Shortcut? ShDiagnostics; + public Shortcut? ShVersion; // UI Catalog uses TableView for the scenario list instead of a ListView to demonstate how // TableView works. There's no real reason not to use ListView. Because we use TableView, and TableView @@ -446,42 +448,80 @@ internal class UICatalogApp ] }; - DriverName = new (Key.Empty, "Driver:", null); - OS = new (Key.Empty, "OS:", null); - - StatusBar = new () { Visible = ShowStatusBar }; - - StatusBar.Items = new [] + ShVersion = new () { - new ( - Application.QuitKey, - $"~{Application.QuitKey} to quit", - () => - { - if (_selectedScenario is null) - { - // This causes GetScenarioToRun to return null - _selectedScenario = null; - RequestStop (); - } - } - ), - new ( - Key.F10, - "~F10~ Status Bar", - () => - { - StatusBar.Visible = !StatusBar.Visible; + Title = "Version Info", + CanFocus = false, - //ContentPane!.Height = Dim.Fill(StatusBar.Visible ? 1 : 0); - LayoutSubviews (); - SetSubViewNeedsDisplay (); - } - ), - DriverName, - OS }; + StatusBar = new () + { + Visible = ShowStatusBar, + Orientation = Orientation.Horizontal, + Y = Pos.AnchorEnd (), + Width = Dim.Fill (), + CanFocus = false, + }; + + Shortcut statusBarShortcut = new Shortcut () + { + Key = Key.F10, + KeyBindingScope = KeyBindingScope.Application, + Title = "Status Bar", + CanFocus = false, + }; + statusBarShortcut.Accept += (sender, args) => + { + StatusBar.Visible = !StatusBar.Visible; + }; + + ShForce16Colors = new Shortcut () + { + Key = Key.F6, + KeyBindingScope = KeyBindingScope.Application, + CommandView = new CheckBox() + { + Title ="16 Colors", + Checked = Application.Force16Colors, + CanFocus = false, + }, + CanFocus = false, + }; + ShForce16Colors.Accept += (sender, args) => + { + ((CheckBox)ShForce16Colors.CommandView).Checked = Application.Force16Colors = (bool)!((CheckBox)ShForce16Colors.CommandView).Checked!; + MiForce16Colors.Checked = Application.Force16Colors; + Application.Refresh (); + + }; + + //ShDiagnostics = new Shortcut () + //{ + // HelpText = "Diagnostic flags", + // CommandView = new RadioGroup() + // { + // RadioLabels = ["Off", "Ruler", "Padding", "MouseEnter"], + + // CanFocus = false, + // Orientation = Orientation.Vertical, + // } + //}; + + StatusBar.Add ( + new Shortcut () + { + Title = "Quit", + Key = Application.QuitKey, + KeyBindingScope = KeyBindingScope.Application, + CanFocus = false, + }, + statusBarShortcut, + ShForce16Colors, + //ShDiagnostics, + ShVersion + ); + // Create the Category list view. This list never changes. CategoryList = new () { @@ -507,7 +547,7 @@ internal class UICatalogApp X = Pos.Right (CategoryList) - 1, Y = 1, Width = Dim.Fill (), - Height = Dim.Fill (1), + Height = Dim.Height(CategoryList), //AllowsMarking = false, CanFocus = true, @@ -620,10 +660,10 @@ internal class UICatalogApp ColorScheme = Colors.ColorSchemes [_topLevelColorScheme]; MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey; - StatusBar.Items [0].Shortcut = Application.QuitKey; - StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit"; - miIsMouseDisabled!.Checked = Application.IsMouseDisabled; + ((Shortcut)StatusBar.Subviews [0]).Key = Application.QuitKey; + + MiIsMouseDisabled!.Checked = Application.IsMouseDisabled; int height = ShowStatusBar ? 1 : 0; // + (MenuBar.Visible ? 1 : 0); @@ -894,22 +934,22 @@ internal class UICatalogApp private MenuItem [] CreateDisabledEnabledMenuBorder () { List menuItems = new (); - miIsMenuBorderDisabled = new () { Title = "Disable Menu _Border" }; + MiIsMenuBorderDisabled = new () { Title = "Disable Menu _Border" }; - miIsMenuBorderDisabled.Shortcut = - (KeyCode)new Key (miIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt + MiIsMenuBorderDisabled.Shortcut = + (KeyCode)new Key (MiIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt .WithCtrl.NoShift; - miIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked; + MiIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked; - miIsMenuBorderDisabled.Action += () => + MiIsMenuBorderDisabled.Action += () => { - miIsMenuBorderDisabled.Checked = (bool)!miIsMenuBorderDisabled.Checked!; + MiIsMenuBorderDisabled.Checked = (bool)!MiIsMenuBorderDisabled.Checked!; - MenuBar.MenusBorderStyle = !(bool)miIsMenuBorderDisabled.Checked + MenuBar.MenusBorderStyle = !(bool)MiIsMenuBorderDisabled.Checked ? LineStyle.Single : LineStyle.None; }; - menuItems.Add (miIsMenuBorderDisabled); + menuItems.Add (MiIsMenuBorderDisabled); return menuItems.ToArray (); } @@ -917,18 +957,18 @@ internal class UICatalogApp private MenuItem [] CreateDisabledEnabledMouseItems () { List menuItems = new (); - miIsMouseDisabled = new () { Title = "_Disable Mouse" }; + MiIsMouseDisabled = new () { Title = "_Disable Mouse" }; - miIsMouseDisabled.Shortcut = - (KeyCode)new Key (miIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl.NoShift; - miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked; + MiIsMouseDisabled.Shortcut = + (KeyCode)new Key (MiIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl.NoShift; + MiIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked; - miIsMouseDisabled.Action += () => + MiIsMouseDisabled.Action += () => { - miIsMouseDisabled.Checked = - Application.IsMouseDisabled = (bool)!miIsMouseDisabled.Checked!; + MiIsMouseDisabled.Checked = + Application.IsMouseDisabled = (bool)!MiIsMouseDisabled.Checked!; }; - menuItems.Add (miIsMouseDisabled); + menuItems.Add (MiIsMouseDisabled); return menuItems.ToArray (); } @@ -937,20 +977,20 @@ internal class UICatalogApp private MenuItem [] CreateDisabledEnableUseSubMenusSingleFrame () { List menuItems = new (); - miUseSubMenusSingleFrame = new () { Title = "Enable _Sub-Menus Single Frame" }; + MiUseSubMenusSingleFrame = new () { Title = "Enable _Sub-Menus Single Frame" }; - miUseSubMenusSingleFrame.Shortcut = KeyCode.CtrlMask + MiUseSubMenusSingleFrame.Shortcut = KeyCode.CtrlMask | KeyCode.AltMask - | (KeyCode)miUseSubMenusSingleFrame!.Title!.Substring (8, 1) [ + | (KeyCode)MiUseSubMenusSingleFrame!.Title!.Substring (8, 1) [ 0]; - miUseSubMenusSingleFrame.CheckType |= MenuItemCheckStyle.Checked; + MiUseSubMenusSingleFrame.CheckType |= MenuItemCheckStyle.Checked; - miUseSubMenusSingleFrame.Action += () => + MiUseSubMenusSingleFrame.Action += () => { - miUseSubMenusSingleFrame.Checked = (bool)!miUseSubMenusSingleFrame.Checked!; - MenuBar.UseSubMenusSingleFrame = (bool)miUseSubMenusSingleFrame.Checked; + MiUseSubMenusSingleFrame.Checked = (bool)!MiUseSubMenusSingleFrame.Checked!; + MenuBar.UseSubMenusSingleFrame = (bool)MiUseSubMenusSingleFrame.Checked; }; - menuItems.Add (miUseSubMenusSingleFrame); + menuItems.Add (MiUseSubMenusSingleFrame); return menuItems.ToArray (); } @@ -959,21 +999,22 @@ internal class UICatalogApp { List menuItems = new (); - miForce16Colors = new () + MiForce16Colors = new () { Title = "Force _16 Colors", Shortcut = (KeyCode)Key.F6, Checked = Application.Force16Colors, CanExecute = () => Application.Driver.SupportsTrueColor }; - miForce16Colors.CheckType |= MenuItemCheckStyle.Checked; + MiForce16Colors.CheckType |= MenuItemCheckStyle.Checked; - miForce16Colors.Action += () => + MiForce16Colors.Action += () => { - miForce16Colors.Checked = Application.Force16Colors = (bool)!miForce16Colors.Checked!; + MiForce16Colors.Checked = Application.Force16Colors = (bool)!MiForce16Colors.Checked!; + ((CheckBox)ShForce16Colors!.CommandView!).Checked = Application.Force16Colors; Application.Refresh (); }; - menuItems.Add (miForce16Colors); + menuItems.Add (MiForce16Colors); return menuItems.ToArray (); } @@ -1000,11 +1041,8 @@ internal class UICatalogApp { ConfigChanged (); - miIsMouseDisabled!.Checked = Application.IsMouseDisabled; - DriverName.Title = $"Driver: {Driver.GetVersionInfo ()}"; - - OS.Title = - $"OS: {RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}"; + MiIsMouseDisabled!.Checked = Application.IsMouseDisabled; + ShVersion.Title = $"{RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}, {Driver.GetVersionInfo ()}"; if (_selectedScenario != null) { From b764ce9d9c512d6780f115148c70c1c23cfe98af Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 13 Jun 2024 20:54:13 +0100 Subject: [PATCH 22/85] Remove unnecessary using. --- Terminal.Gui/Application/Application.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index f7eea3c94..696071404 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Globalization; using System.Reflection; -using System.Text.Json.Serialization; namespace Terminal.Gui; From 8f4fc23e590b1a5dd12890a4f41c898d1e8f7eca Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 13 Jun 2024 20:56:25 +0100 Subject: [PATCH 23/85] Changes xml doc as @tig suggest. --- Terminal.Gui/Application/Application.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 696071404..72df1be6d 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -713,9 +713,9 @@ public static partial class Application /// method will only process any pending events, timers, idle handlers and then /// return control immediately. /// - /// Calling first is not needed if only or + /// When using or /// - /// has been used, otherwise it's needed to call first. + /// will be called automatically. /// /// /// RELEASE builds only: When is any exceptions will be From 12adcf3bf48a70fc78f7fcc9a80fb1cd353a893f Mon Sep 17 00:00:00 2001 From: John Baughman <1634414+johnmbaughman@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:51:50 -0500 Subject: [PATCH 24/85] Add Community Toolkit example --- .../CommunityToolkitExample.csproj | 19 +++ CommunityToolkitExample/LoginAction.cs | 7 ++ CommunityToolkitExample/LoginView.Designer.cs | 62 +++++++++ CommunityToolkitExample/LoginView.cs | 68 ++++++++++ CommunityToolkitExample/LoginViewModel.cs | 118 ++++++++++++++++++ CommunityToolkitExample/Message.cs | 6 + CommunityToolkitExample/Program.cs | 25 ++++ Terminal.sln | 32 +++-- 8 files changed, 324 insertions(+), 13 deletions(-) create mode 100644 CommunityToolkitExample/CommunityToolkitExample.csproj create mode 100644 CommunityToolkitExample/LoginAction.cs create mode 100644 CommunityToolkitExample/LoginView.Designer.cs create mode 100644 CommunityToolkitExample/LoginView.cs create mode 100644 CommunityToolkitExample/LoginViewModel.cs create mode 100644 CommunityToolkitExample/Message.cs create mode 100644 CommunityToolkitExample/Program.cs diff --git a/CommunityToolkitExample/CommunityToolkitExample.csproj b/CommunityToolkitExample/CommunityToolkitExample.csproj new file mode 100644 index 000000000..fdb41dfcd --- /dev/null +++ b/CommunityToolkitExample/CommunityToolkitExample.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/CommunityToolkitExample/LoginAction.cs b/CommunityToolkitExample/LoginAction.cs new file mode 100644 index 000000000..2fd8d4a45 --- /dev/null +++ b/CommunityToolkitExample/LoginAction.cs @@ -0,0 +1,7 @@ +namespace CommunityToolkitExample; + +internal enum LoginAction +{ + Validation, + LoginProgress +} diff --git a/CommunityToolkitExample/LoginView.Designer.cs b/CommunityToolkitExample/LoginView.Designer.cs new file mode 100644 index 000000000..e1bddff45 --- /dev/null +++ b/CommunityToolkitExample/LoginView.Designer.cs @@ -0,0 +1,62 @@ +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginView : Window +{ + private Label titleLabel; + private Label usernameLengthLabel; + private TextField usernameInput; + private Label passwordLengthLabel; + private TextField passwordInput; + private Label validationLabel; + private Button loginButton; + private Button clearButton; + private Label loginProgressLabel; + + private void InitializeComponent () + { + titleLabel = new Label (); + titleLabel.Text = "Login Form"; + Add (titleLabel); + usernameLengthLabel = new Label (); + usernameLengthLabel.X = Pos.Left (titleLabel); + usernameLengthLabel.Y = Pos.Top (titleLabel) + 1; + Add (usernameLengthLabel); + usernameInput = new TextField (); + usernameInput.X = Pos.Right (usernameLengthLabel) + 1; + usernameInput.Y = Pos.Top (usernameLengthLabel); + usernameInput.Width = 40; + Add (usernameInput); + passwordLengthLabel = new Label (); + passwordLengthLabel.X = Pos.Left (usernameLengthLabel); + passwordLengthLabel.Y = Pos.Top (usernameLengthLabel) + 1; + Add (passwordLengthLabel); + passwordInput = new TextField (); + passwordInput.X = Pos.Right (passwordLengthLabel) + 1; + passwordInput.Y = Pos.Top (passwordLengthLabel); + passwordInput.Width = 40; + passwordInput.Secret = true; + Add (passwordInput); + validationLabel = new Label (); + validationLabel.X = Pos.Left (passwordInput); + validationLabel.Y = Pos.Top (passwordInput) + 1; + Add (validationLabel); + loginButton = new Button (); + loginButton.X = Pos.Left (validationLabel); + loginButton.Y = Pos.Top (validationLabel) + 1; + loginButton.Text = "_Login"; + Add (loginButton); + clearButton = new Button (); + clearButton.X = Pos.Left (loginButton); + clearButton.Y = Pos.Top (loginButton) + 1; + clearButton.Text = "_Clear"; + Add (clearButton); + loginProgressLabel = new Label (); + loginProgressLabel.X = Pos.Left (clearButton); + loginProgressLabel.Y = Pos.Top (clearButton) + 1; + loginProgressLabel.Width = 40; + loginProgressLabel.Height = 1; + Add (loginProgressLabel); + } +} diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs new file mode 100644 index 000000000..262928e35 --- /dev/null +++ b/CommunityToolkitExample/LoginView.cs @@ -0,0 +1,68 @@ +using CommunityToolkit.Mvvm.Messaging; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginView : IRecipient> +{ + public LoginView (LoginViewModel viewModel) + { + WeakReferenceMessenger.Default.Register (this); + Title = $"Community Toolkit MVVM Example - {Application.QuitKey} to Exit"; + ViewModel = viewModel; + InitializeComponent (); + usernameInput.TextChanged += (_, _) => + { + ViewModel.Username = usernameInput.Text; + SetText (); + }; + passwordInput.TextChanged += (_, _) => + { + ViewModel.Password = passwordInput.Text; + SetText (); + }; + loginButton.Accept += (_, _) => + { + if (!ViewModel.CanLogin) { return; } + ViewModel.LoginCommand.Execute (null); + }; + + clearButton.Accept += (_, _) => + { + ViewModel.ClearCommand.Execute (null); + SetText (); + }; + + Initialized += (_, _) => { ViewModel.Initialized (); }; + } + + public LoginViewModel ViewModel { get; set; } + + public void Receive (Message message) + { + switch (message.Value) + { + case LoginAction.LoginProgress: + { + loginProgressLabel.Text = ViewModel.LoginProgressMessage; + break; + } + case LoginAction.Validation: + { + validationLabel.Text = ViewModel.ValidationMessage; + validationLabel.ColorScheme = ViewModel.ValidationColorScheme; + break; + } + } + SetText(); + Application.Refresh (); + } + + private void SetText () + { + usernameInput.Text = ViewModel.Username; + usernameLengthLabel.Text = ViewModel.UsernameLengthMessage; + passwordInput.Text = ViewModel.Password; + passwordLengthLabel.Text = ViewModel.PasswordLengthMessage; + } +} \ No newline at end of file diff --git a/CommunityToolkitExample/LoginViewModel.cs b/CommunityToolkitExample/LoginViewModel.cs new file mode 100644 index 000000000..b5fbfa32f --- /dev/null +++ b/CommunityToolkitExample/LoginViewModel.cs @@ -0,0 +1,118 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +internal partial class LoginViewModel : ObservableObject +{ + private const string DEFAULT_LOGIN_PROGRESS_MESSAGE = "Press 'Login' to log in."; + private const string LOGGING_IN_PROGRESS_MESSAGE = "Logging in..."; + private const string VALID_LOGIN_MESSAGE = "The input is valid!"; + private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; + + [ObservableProperty] + private bool _canLogin; + + private string _password; + + [ObservableProperty] + private string _passwordLengthMessage; + + private string _username; + + [ObservableProperty] + private string _usernameLengthMessage; + + [ObservableProperty] + private string _loginProgressMessage; + + [ObservableProperty] + private string _validationMessage; + + [ObservableProperty] + private ColorScheme? _validationColorScheme; + + public LoginViewModel () + { + Username = string.Empty; + Password = string.Empty; + + ClearCommand = new (Clear); + LoginCommand = new (Execute); + + Clear (); + + return; + + async void Execute () { await Login (); } + } + + public RelayCommand ClearCommand { get; } + + public RelayCommand LoginCommand { get; } + + public string Password + { + get => _password; + set + { + SetProperty (ref _password, value); + PasswordLengthMessage = $"_Password ({_password.Length} characters):"; + ValidateLogin (); + } + } + + private void ValidateLogin () + { + CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); + SendMessage (LoginAction.Validation); + } + + public string Username + { + get => _username; + set + { + SetProperty (ref _username, value); + UsernameLengthMessage = $"_Username ({_username.Length} characters):"; + ValidateLogin (); + } + } + + private void Clear () + { + Username = string.Empty; + Password = string.Empty; + SendMessage (LoginAction.Validation); + SendMessage (LoginAction.LoginProgress, DEFAULT_LOGIN_PROGRESS_MESSAGE); + } + + private async Task Login () + { + SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); + await Task.Delay (TimeSpan.FromSeconds (1)); + Clear (); + } + + private void SendMessage (LoginAction loginAction, string message = "") + { + switch (loginAction) + { + case LoginAction.LoginProgress: + LoginProgressMessage = message; + break; + case LoginAction.Validation: + ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE; + ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"]; + break; + } + WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); + } + + public void Initialized () + { + Clear (); + } +} diff --git a/CommunityToolkitExample/Message.cs b/CommunityToolkitExample/Message.cs new file mode 100644 index 000000000..a1081512e --- /dev/null +++ b/CommunityToolkitExample/Message.cs @@ -0,0 +1,6 @@ +namespace CommunityToolkitExample; + +internal class Message +{ + public T Value { get; set; } +} diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs new file mode 100644 index 000000000..0a557fc47 --- /dev/null +++ b/CommunityToolkitExample/Program.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Terminal.Gui; + +namespace CommunityToolkitExample; + +public static class Program +{ + public static IServiceProvider Services { get; private set; } + + private static void Main (string [] args) + { + Services = ConfigureServices (); + Application.Init (); + Application.Run (Services.GetRequiredService ()); + Application.Shutdown (); + } + + private static IServiceProvider ConfigureServices () + { + var services = new ServiceCollection (); + services.AddTransient (); + services.AddTransient (); + return services.BuildServiceProvider (); + } +} \ No newline at end of file diff --git a/Terminal.sln b/Terminal.sln index 98de83b41..7dbb5432a 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -38,12 +38,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Inte EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Internal.Debugging", "Analyzers\Terminal.Gui.Analyzers.Internal.Debugging\Terminal.Gui.Analyzers.Internal.Debugging.csproj", "{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}" +EndProject Global - GlobalSection(NestedProjects) = preSolution - {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -53,14 +50,6 @@ Global {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {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}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -77,14 +66,31 @@ Global {B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.Build.0 = Release|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03} EndGlobalSection From dea0e5696f3f4e23675b32b3fd3b04237000e72e Mon Sep 17 00:00:00 2001 From: John Baughman <1634414+johnmbaughman@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:24:27 -0500 Subject: [PATCH 25/85] added recommendations; fixed .sln --- CommunityToolkitExample/LoginViewModel.cs | 31 ++-- CommunityToolkitExample/Program.cs | 1 + CommunityToolkitExample/README.md | 180 ++++++++++++++++++++++ ReactiveExample/Program.cs | 1 + Terminal.sln | 26 ++-- 5 files changed, 209 insertions(+), 30 deletions(-) create mode 100644 CommunityToolkitExample/README.md diff --git a/CommunityToolkitExample/LoginViewModel.cs b/CommunityToolkitExample/LoginViewModel.cs index b5fbfa32f..2de4688a3 100644 --- a/CommunityToolkitExample/LoginViewModel.cs +++ b/CommunityToolkitExample/LoginViewModel.cs @@ -8,13 +8,15 @@ namespace CommunityToolkitExample; internal partial class LoginViewModel : ObservableObject { private const string DEFAULT_LOGIN_PROGRESS_MESSAGE = "Press 'Login' to log in."; + private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; private const string LOGGING_IN_PROGRESS_MESSAGE = "Logging in..."; private const string VALID_LOGIN_MESSAGE = "The input is valid!"; - private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; - [ObservableProperty] private bool _canLogin; + [ObservableProperty] + private string _loginProgressMessage; + private string _password; [ObservableProperty] @@ -24,16 +26,11 @@ internal partial class LoginViewModel : ObservableObject [ObservableProperty] private string _usernameLengthMessage; - - [ObservableProperty] - private string _loginProgressMessage; - - [ObservableProperty] - private string _validationMessage; - [ObservableProperty] private ColorScheme? _validationColorScheme; + [ObservableProperty] + private string _validationMessage; public LoginViewModel () { Username = string.Empty; @@ -64,12 +61,6 @@ internal partial class LoginViewModel : ObservableObject } } - private void ValidateLogin () - { - CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); - SendMessage (LoginAction.Validation); - } - public string Username { get => _username; @@ -81,6 +72,11 @@ internal partial class LoginViewModel : ObservableObject } } + public void Initialized () + { + Clear (); + } + private void Clear () { Username = string.Empty; @@ -111,8 +107,9 @@ internal partial class LoginViewModel : ObservableObject WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); } - public void Initialized () + private void ValidateLogin () { - Clear (); + CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); + SendMessage (LoginAction.Validation); } } diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs index 0a557fc47..96c43a14a 100644 --- a/CommunityToolkitExample/Program.cs +++ b/CommunityToolkitExample/Program.cs @@ -12,6 +12,7 @@ public static class Program Services = ConfigureServices (); Application.Init (); Application.Run (Services.GetRequiredService ()); + Application.Top.Dispose(); Application.Shutdown (); } diff --git a/CommunityToolkitExample/README.md b/CommunityToolkitExample/README.md new file mode 100644 index 000000000..254ca9bc4 --- /dev/null +++ b/CommunityToolkitExample/README.md @@ -0,0 +1,180 @@ +# CommunityToolkit.MVVM Example + +This small demo gives an example of using the `CommunityToolkit.MVVM` framework's `ObservableObject`, `ObservableProperty`, and `IRecipient` in conjunction with `Microsoft.Extensions.DependencyInjection`. + +### Startup + +Right away we use IoC to load our views and view models. + +``` csharp +// As a public property for access further in the application if needed. +public static IServiceProvider Services { get; private set; } + +. . . + +// In Main +Services = ConfigureServices (); + +. . . + +private static IServiceProvider ConfigureServices () +{ + var services = new ServiceCollection (); + services.AddTransient (); + services.AddTransient (); + return services.BuildServiceProvider (); +} +``` + +Now, we start the app and get our main view. + +``` csharp +Application.Run (Services.GetRequiredService ()); +``` + +Our view implements `IRecipient` to demonstrate the use of the `WeakReferenceMessenger`. The binding of the view events is then created. + +``` csharp +internal partial class LoginView : IRecipient> +{ + public LoginView (LoginViewModel viewModel) + { + // Initialize our Receive method + WeakReferenceMessenger.Default.Register (this); + + ... + + ViewModel = viewModel; + + ... + + passwordInput.TextChanged += (_, _) => + { + ViewModel.Password = passwordInput.Text; + SetText (); + }; + loginButton.Accept += (_, _) => + { + if (!ViewModel.CanLogin) { return; } + ViewModel.LoginCommand.Execute (null); + }; + + ... + + Initialized += (_, _) => { ViewModel.Initialized (); }; + } + + ... + +} +``` + +Momentarily slipping over to the view model, all bindable properties use some form of `ObservableProperty` with the class deriving from `ObservableObject`. Commands are of the `RelayCommand` type. + +``` csharp + +internal partial class LoginViewModel : ObservableObject +{ + ... + + [ObservableProperty] + private bool _canLogin; + + private string _password; + + ... + + public LoginViewModel () + { + ... + + Password = string.Empty; + + ... + + LoginCommand = new (Execute); + + Clear (); + + return; + + async void Execute () { await Login (); } + } + + ... + + public RelayCommand LoginCommand { get; } + + public string Password + { + get => _password; + set + { + SetProperty (ref _password, value); + PasswordLengthMessage = $"_Password ({_password.Length} characters):"; + ValidateLogin (); + } + } +``` + +The use of `WeakReferenceMessenger` provides one method of signaling the view from the view model. It's just one way to handle cross-thread messaging in this framework. + +``` csharp +... + +private async Task Login () +{ + SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); + await Task.Delay (TimeSpan.FromSeconds (1)); + Clear (); +} + +private void SendMessage (LoginAction loginAction, string message = "") +{ + switch (loginAction) + { + case LoginAction.LoginProgress: + LoginProgressMessage = message; + break; + case LoginAction.Validation: + ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE; + ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"]; + break; + } + WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); +} + +private void ValidateLogin () +{ + CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); + SendMessage (LoginAction.Validation); +} + +... +``` + +And the view's `Receive` function which provides an `Application.Refresh()` call to update the UI immediately. + +``` csharp +public void Receive (Message message) +{ + switch (message.Value) + { + case LoginAction.LoginProgress: + { + loginProgressLabel.Text = ViewModel.LoginProgressMessage; + break; + } + case LoginAction.Validation: + { + validationLabel.Text = ViewModel.ValidationMessage; + validationLabel.ColorScheme = ViewModel.ValidationColorScheme; + break; + } + } + SetText(); + Application.Refresh (); +} +``` + +There are other ways of handling cross-thread communication, this gives just one example. \ No newline at end of file diff --git a/ReactiveExample/Program.cs b/ReactiveExample/Program.cs index 2b5105b8c..2bbc6667b 100644 --- a/ReactiveExample/Program.cs +++ b/ReactiveExample/Program.cs @@ -12,6 +12,7 @@ public static class Program RxApp.MainThreadScheduler = TerminalScheduler.Default; RxApp.TaskpoolScheduler = TaskPoolScheduler.Default; Application.Run (new LoginView (new LoginViewModel ())); + Application.Top.Dispose(); Application.Shutdown (); } } diff --git a/Terminal.sln b/Terminal.sln index 7dbb5432a..ee838a41a 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -41,6 +41,11 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}" EndProject Global + GlobalSection(NestedProjects) = preSolution + {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -50,6 +55,14 @@ Global {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {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}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -66,14 +79,6 @@ Global {B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -86,11 +91,6 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - {C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03} EndGlobalSection From 7b60527446581911ac2f795ed89a716d78c76291 Mon Sep 17 00:00:00 2001 From: John Baughman <1634414+johnmbaughman@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:44:08 -0500 Subject: [PATCH 26/85] fix warnings; tidy up a bit more --- .../{LoginAction.cs => LoginActions.cs} | 3 +- CommunityToolkitExample/LoginView.cs | 18 +++++++----- CommunityToolkitExample/LoginViewModel.cs | 29 ++++++++++++++----- CommunityToolkitExample/Message.cs | 2 +- CommunityToolkitExample/Program.cs | 2 +- 5 files changed, 36 insertions(+), 18 deletions(-) rename CommunityToolkitExample/{LoginAction.cs => LoginActions.cs} (66%) diff --git a/CommunityToolkitExample/LoginAction.cs b/CommunityToolkitExample/LoginActions.cs similarity index 66% rename from CommunityToolkitExample/LoginAction.cs rename to CommunityToolkitExample/LoginActions.cs index 2fd8d4a45..3cbc6972c 100644 --- a/CommunityToolkitExample/LoginAction.cs +++ b/CommunityToolkitExample/LoginActions.cs @@ -1,7 +1,8 @@ namespace CommunityToolkitExample; -internal enum LoginAction +internal enum LoginActions { + Clear, Validation, LoginProgress } diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs index 262928e35..b0d891fa9 100644 --- a/CommunityToolkitExample/LoginView.cs +++ b/CommunityToolkitExample/LoginView.cs @@ -3,7 +3,7 @@ using Terminal.Gui; namespace CommunityToolkitExample; -internal partial class LoginView : IRecipient> +internal partial class LoginView : IRecipient> { public LoginView (LoginViewModel viewModel) { @@ -14,12 +14,10 @@ internal partial class LoginView : IRecipient> usernameInput.TextChanged += (_, _) => { ViewModel.Username = usernameInput.Text; - SetText (); }; passwordInput.TextChanged += (_, _) => { ViewModel.Password = passwordInput.Text; - SetText (); }; loginButton.Accept += (_, _) => { @@ -30,7 +28,6 @@ internal partial class LoginView : IRecipient> clearButton.Accept += (_, _) => { ViewModel.ClearCommand.Execute (null); - SetText (); }; Initialized += (_, _) => { ViewModel.Initialized (); }; @@ -38,16 +35,23 @@ internal partial class LoginView : IRecipient> public LoginViewModel ViewModel { get; set; } - public void Receive (Message message) + public void Receive (Message message) { switch (message.Value) { - case LoginAction.LoginProgress: + case LoginActions.Clear: + { + loginProgressLabel.Text = ViewModel.LoginProgressMessage; + validationLabel.Text = ViewModel.ValidationMessage; + validationLabel.ColorScheme = ViewModel.ValidationColorScheme; + break; + } + case LoginActions.LoginProgress: { loginProgressLabel.Text = ViewModel.LoginProgressMessage; break; } - case LoginAction.Validation: + case LoginActions.Validation: { validationLabel.Text = ViewModel.ValidationMessage; validationLabel.ColorScheme = ViewModel.ValidationColorScheme; diff --git a/CommunityToolkitExample/LoginViewModel.cs b/CommunityToolkitExample/LoginViewModel.cs index 2de4688a3..506641801 100644 --- a/CommunityToolkitExample/LoginViewModel.cs +++ b/CommunityToolkitExample/LoginViewModel.cs @@ -11,6 +11,7 @@ internal partial class LoginViewModel : ObservableObject private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password."; private const string LOGGING_IN_PROGRESS_MESSAGE = "Logging in..."; private const string VALID_LOGIN_MESSAGE = "The input is valid!"; + [ObservableProperty] private bool _canLogin; @@ -26,6 +27,7 @@ internal partial class LoginViewModel : ObservableObject [ObservableProperty] private string _usernameLengthMessage; + [ObservableProperty] private ColorScheme? _validationColorScheme; @@ -33,6 +35,13 @@ internal partial class LoginViewModel : ObservableObject private string _validationMessage; public LoginViewModel () { + _loginProgressMessage = string.Empty; + _password = string.Empty; + _passwordLengthMessage = string.Empty; + _username = string.Empty; + _usernameLengthMessage = string.Empty; + _validationMessage = string.Empty; + Username = string.Empty; Password = string.Empty; @@ -81,35 +90,39 @@ internal partial class LoginViewModel : ObservableObject { Username = string.Empty; Password = string.Empty; - SendMessage (LoginAction.Validation); - SendMessage (LoginAction.LoginProgress, DEFAULT_LOGIN_PROGRESS_MESSAGE); + SendMessage (LoginActions.Clear, DEFAULT_LOGIN_PROGRESS_MESSAGE); } private async Task Login () { - SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); + SendMessage (LoginActions.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE); await Task.Delay (TimeSpan.FromSeconds (1)); Clear (); } - private void SendMessage (LoginAction loginAction, string message = "") + private void SendMessage (LoginActions loginAction, string message = "") { switch (loginAction) { - case LoginAction.LoginProgress: + case LoginActions.Clear: + LoginProgressMessage = message; + ValidationMessage = INVALID_LOGIN_MESSAGE; + ValidationColorScheme = Colors.ColorSchemes ["Error"]; + break; + case LoginActions.LoginProgress: LoginProgressMessage = message; break; - case LoginAction.Validation: + case LoginActions.Validation: ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE; ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"]; break; } - WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); + WeakReferenceMessenger.Default.Send (new Message { Value = loginAction }); } private void ValidateLogin () { CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password); - SendMessage (LoginAction.Validation); + SendMessage (LoginActions.Validation); } } diff --git a/CommunityToolkitExample/Message.cs b/CommunityToolkitExample/Message.cs index a1081512e..f0e8ad530 100644 --- a/CommunityToolkitExample/Message.cs +++ b/CommunityToolkitExample/Message.cs @@ -2,5 +2,5 @@ internal class Message { - public T Value { get; set; } + public T? Value { get; set; } } diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs index 96c43a14a..0d4f21c30 100644 --- a/CommunityToolkitExample/Program.cs +++ b/CommunityToolkitExample/Program.cs @@ -5,7 +5,7 @@ namespace CommunityToolkitExample; public static class Program { - public static IServiceProvider Services { get; private set; } + public static IServiceProvider? Services { get; private set; } private static void Main (string [] args) { From 37d237aad9a77359fa94ed7a4069cd9007cc5c58 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 14 Jun 2024 12:50:23 +0100 Subject: [PATCH 27/85] Removes unnecessary driver parameter. --- Terminal.Gui/Application/Application.cs | 9 ++------- UnitTests/Application/ApplicationTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 72df1be6d..c66f70cfb 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -686,7 +686,7 @@ public static partial class Application var top = new T (); - Run (top, errorHandler, driver); + Run (top, errorHandler); return top; } @@ -729,12 +729,7 @@ public static partial class Application /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, /// rethrows when null). /// - /// - /// The to use. If not specified the default driver for the platform will - /// be used ( , , or ). Must be - /// if was called. - /// - public static void Run (Toplevel view, Func errorHandler = null, ConsoleDriver driver = null) + public static void Run (Toplevel view, Func errorHandler = null) { ArgumentNullException.ThrowIfNull (view); diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index c78903771..2f0aa9b44 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -962,7 +962,7 @@ public class ApplicationTests Assert.Null (Application.Top); - Assert.Throws (() => Application.Run (new (), null, driver)); + Assert.Throws (() => Application.Run (new Toplevel ())); Application.Init (driver); Application.Iteration += (s, e) => @@ -970,7 +970,7 @@ public class ApplicationTests Assert.NotNull (Application.Top); Application.RequestStop (); }; - Application.Run (new (), null, driver); + Application.Run (new Toplevel ()); #if DEBUG_IDISPOSABLE Assert.False (Application.Top.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); From 84971a2f066438c2566fb3d81a0590406805b959 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 14 Jun 2024 11:45:56 -0700 Subject: [PATCH 28/85] fxed color & focus issues --- Terminal.Gui/View/ViewAdornments.cs | 87 ++++++++++---- Terminal.Gui/View/ViewDrawing.cs | 10 +- Terminal.Gui/Views/Bar.cs | 144 +++++++++++++--------- Terminal.Gui/Views/Shortcut.cs | 123 ++++++++++--------- Terminal.Gui/Views/Slider.cs | 3 +- UICatalog/Scenarios/Bars.cs | 179 +++++++++++++++------------- UICatalog/Scenarios/Shortcuts.cs | 35 +++--- 7 files changed, 346 insertions(+), 235 deletions(-) diff --git a/Terminal.Gui/View/ViewAdornments.cs b/Terminal.Gui/View/ViewAdornments.cs index 0ebf5499f..bc2a93468 100644 --- a/Terminal.Gui/View/ViewAdornments.cs +++ b/Terminal.Gui/View/ViewAdornments.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui; +using System.ComponentModel; + +namespace Terminal.Gui; public partial class View { @@ -95,29 +97,74 @@ public partial class View get => Border?.LineStyle ?? LineStyle.Single; set { - if (Border is null) - { - return; - } + StateEventArgs e = new (Border?.LineStyle ?? LineStyle.None, value); + OnBorderStyleChanging (e); - if (value != LineStyle.None) - { - if (Border.Thickness == Thickness.Empty) - { - Border.Thickness = new (1); - } - } - else - { - Border.Thickness = new (0); - } - - Border.LineStyle = value; - LayoutAdornments (); - SetNeedsLayout (); } } + /// + /// Called when the is changing. Invokes , which allows the event to be cancelled. + /// + /// + /// Override to prevent the from changing. Set to `true` to cancel the event. + /// + /// + protected void OnBorderStyleChanging (StateEventArgs e) + { + if (Border is null) + { + return; + } + + BorderStyleChanging?.Invoke (this, e); + if (e.Cancel) + { + return; + } + + SetBorderStyle (e.NewValue); + LayoutAdornments (); + SetNeedsLayout (); + + return; + } + + /// + /// Sets the of the view to the specified value. + /// + /// + /// + /// is a helper for manipulating the view's . Setting this property to any value other + /// than is equivalent to setting 's + /// to `1` and to the value. + /// + /// + /// Setting this property to is equivalent to setting 's + /// to `0` and to . + /// + /// For more advanced customization of the view's border, manipulate see directly. + /// + /// + public virtual void SetBorderStyle (LineStyle value) + { + if (value != LineStyle.None) + { + if (Border.Thickness == Thickness.Empty) + { + Border.Thickness = new (1); + } + } + else + { + Border.Thickness = new (0); + } + + Border.LineStyle = value; + } + + public event EventHandler> BorderStyleChanging; + /// /// The inside of the view that offsets the /// from the . diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 74beffd3f..604b7d51a 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -640,17 +640,17 @@ public partial class View { SubViewNeedsDisplay = true; + if (this is Adornment adornment) + { + adornment.Parent?.SetSubViewNeedsDisplay (); + } + if (SuperView is { SubViewNeedsDisplay: false }) { SuperView.SetSubViewNeedsDisplay (); return; } - - if (this is Adornment adornment) - { - adornment.Parent?.SetSubViewNeedsDisplay (); - } } /// Clears and . diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 251261993..4edeb57f9 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Microsoft.CodeAnalysis; namespace Terminal.Gui; @@ -8,12 +9,33 @@ namespace Terminal.Gui; /// /// /// -public class Bar : View +public class Bar : Toplevel { /// public Bar () { - SetInitialProperties (); + CanFocus = true; + + Width = Dim.Auto (); + Height = Dim.Auto (); + + LayoutStarted += Bar_LayoutStarted; + + Initialized += Bar_Initialized; + + } + + private void Bar_Initialized (object sender, EventArgs e) + { + ColorScheme = Colors.ColorSchemes ["Menu"]; + AdjustSubviewBorders(); + } + + /// + public override void SetBorderStyle (LineStyle value) + { + // The default changes the thickness. We don't want that. We just set the style. + Border.LineStyle = value; } /// @@ -31,46 +53,59 @@ public class Bar : View //view.AutoSize = true; } - //if (StatusBarStyle) - //{ - // // Light up right border - // view.BorderStyle = LineStyle.Single; - // view.Border.Thickness = new Thickness (0, 0, 1, 0); - //} - - //if (view is not Shortcut) - //{ - // if (StatusBarStyle) - // { - // view.Padding.Thickness = new Thickness (0, 0, 1, 0); - // } - - // view.Margin.Thickness = new Thickness (1, 0, 0, 0); - //} - - //view.ColorScheme = ColorScheme; - - // Add any HotKey keybindings to our bindings - //IEnumerable> bindings = view.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.HotKey); - - //foreach (KeyValuePair binding in bindings) - //{ - // AddCommand ( - // binding.Value.Commands [0], - // () => - // { - // if (view is Shortcut shortcut) - // { - // return shortcut.CommandView.InvokeCommands (binding.Value.Commands); - // } - - // return false; - // }); - // KeyBindings.Add (binding.Key, binding.Value); - //} - - base.Add (view); + AdjustSubviewBorders (); + + } + + /// + public override void Remove (View view) + { + base.Remove (view); + AdjustSubviewBorders (); + } + + private void AdjustSubviewBorders () + { + for (var index = 0; index < Subviews.Count; index++) + { + View barItem = Subviews [index]; + + barItem.Border.LineStyle = BorderStyle; + barItem.SuperViewRendersLineCanvas = true; + barItem.ColorScheme = ColorScheme; + + + if (!barItem.Visible) + { + continue; + } + + if (StatusBarStyle) + { + if (index == 0) + { + barItem.Border.Thickness = new Thickness (0, 0, 1, 0); + } + + if (index == Subviews.Count - 1) + { + barItem.Border.Thickness = new Thickness (0, 0, 0, 0); + } + } + else + { + if (index == 0) + { + barItem.Border.Thickness = new Thickness (1, 1, 1, 0); + } + + if (index == Subviews.Count - 1) + { + barItem.Border.Thickness = new Thickness (1, 0, 1, 1); + } + } + } } private void Bar_LayoutStarted (object sender, LayoutEventArgs e) @@ -89,7 +124,15 @@ public class Bar : View continue; } - barItem.BorderStyle = LineStyle.Dashed; + if (StatusBarStyle) + { + barItem.BorderStyle = LineStyle.Dashed; + } + else + { + barItem.BorderStyle = LineStyle.None; + } + if (index == Subviews.Count - 1) { barItem.Border.Thickness = new Thickness (0, 0, 0, 0); @@ -99,7 +142,7 @@ public class Bar : View barItem.Border.Thickness = new Thickness (0, 0, 1, 0); } - barItem.X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast); + barItem.X = Pos.Align (Alignment.Start, StatusBarStyle ? AlignmentModes.IgnoreFirstOrLast : 0); barItem.Y = Pos.Center (); prevBarItem = barItem; } @@ -126,6 +169,7 @@ public class Bar : View // Set the overall size of the Bar and arrange the views vertically var maxBarItemWidth = 0; + var totalHeight = 0; for (var index = 0; index < Subviews.Count; index++) { @@ -148,7 +192,7 @@ public class Bar : View else { // Align the view to the bottom of the previous view - barItem.Y = index; + barItem.Y = Pos.Bottom(prevBarItem); } prevBarItem = barItem; @@ -163,6 +207,7 @@ public class Bar : View } barItem.X = 0; + totalHeight += barItem.Frame.Height; } foreach (Shortcut shortcut in shortcuts) @@ -170,20 +215,11 @@ public class Bar : View shortcut.Width = maxBarItemWidth; } - Height = Subviews.Count; + Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: totalHeight); break; } } - private void SetInitialProperties () - { - ColorScheme = Colors.ColorSchemes ["Menu"]; - CanFocus = true; - Width = Dim.Auto (); - Height = Dim.Auto (); - - LayoutStarted += Bar_LayoutStarted; - } } diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 1dfabeb86..e4484d128 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -45,8 +45,8 @@ public class Shortcut : View public Shortcut () { Id = "_shortcut"; - HighlightStyle = HighlightStyle.Pressed; - Highlight += Shortcut_Highlight; + //HighlightStyle = HighlightStyle.Pressed; + //Highlight += Shortcut_Highlight; CanFocus = true; Width = GetWidthDimAuto (); Height = Dim.Auto (DimAutoStyle.Content, 1); @@ -80,6 +80,7 @@ public class Shortcut : View KeyView.MouseClick += Shortcut_MouseClick; LayoutStarted += OnLayoutStarted; + DrawContent += Shortcut_DrawContent; Initialized += OnInitialized; @@ -98,17 +99,7 @@ public class Shortcut : View _minimumDimAutoWidth = Frame.Width; Width = savedDim; - // Set KeyView's colors to show "hot" - if (ColorScheme != null) - { - var cs = new ColorScheme (ColorScheme) - { - Normal = ColorScheme.HotNormal, - HotNormal = ColorScheme.Normal - }; - - KeyView.ColorScheme = cs; - } + //SetColorScheme (); } // Helper to set Width consistently @@ -119,6 +110,11 @@ public class Shortcut : View } } + private void Shortcut_DrawContent (object sender, DrawEventArgs e) + { + //SetColorScheme(); + } + /// /// Gets or sets the for this . The default is @@ -139,12 +135,12 @@ public class Shortcut : View Add (CommandView); } - if (!string.IsNullOrEmpty (HelpView.Text)) + if (HelpView.Visible && !string.IsNullOrEmpty (HelpView.Text)) { Add (HelpView); } - if (Key != Key.Empty) + if (KeyView.Visible && Key != Key.Empty) { Add (KeyView); } @@ -273,8 +269,6 @@ public class Shortcut : View { CommandView.InvokeCommand (Command.Accept); e.Handled = true; - - return; } if (!e.Handled) @@ -568,7 +562,7 @@ public class Shortcut : View private void SetKeyViewDefaultLayout () { - KeyView.Margin.Thickness = GetMarginThickness(); + KeyView.Margin.Thickness = GetMarginThickness (); KeyView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); //KeyView.Y = Pos.Center (); KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); @@ -645,50 +639,69 @@ public class Shortcut : View /// public override ColorScheme ColorScheme { - get - { - if (base.ColorScheme == null) - { - return SuperView?.ColorScheme ?? base.ColorScheme; - } - - return base.ColorScheme; - } + get => base.ColorScheme; set { base.ColorScheme = value; - - if (CommandView.CanFocus) - { - CommandView.ColorScheme = SuperView?.ColorScheme ?? ColorScheme; - } - - if (ColorScheme != null) - { - var cs = new ColorScheme (ColorScheme) - { - Normal = ColorScheme.HotNormal, - HotNormal = ColorScheme.Normal - }; - KeyView.ColorScheme = cs; - } - - Border.ColorScheme = SuperView?.ColorScheme ?? ColorScheme; + SetColorScheme (); } } + public void SetColorScheme () + { + // Border should match superview. + Border.ColorScheme = SuperView?.ColorScheme; + + if (HasFocus) + { + // When we have focus, we invert the SuperView's colors + base.ColorScheme = new (base.ColorScheme) + { + Normal = base.ColorScheme.Focus, + HotNormal = base.ColorScheme.HotFocus, + HotFocus = base.ColorScheme.HotNormal, + Focus = base.ColorScheme.Normal + }; + } + else + { + base.ColorScheme = SuperView?.ColorScheme; + } + + //// If the command view is focusable, invert the focus colors + if (CommandView.CanFocus) + { + ColorScheme commandViewCS = new (base.ColorScheme) + { + Normal = base.ColorScheme.Focus, + HotNormal = base.ColorScheme.HotFocus, + HotFocus = base.ColorScheme.HotNormal, + Focus = base.ColorScheme.Normal + }; + CommandView.ColorScheme = commandViewCS; + } + else + { + CommandView.ColorScheme = base.ColorScheme; + } + + //HelpView.ColorScheme = base.ColorScheme; + + //// Set KeyView's colors to show "hot" + //var cs = new ColorScheme (ColorScheme) + //{ + // Normal = base.ColorScheme.HotNormal, + // HotNormal = base.ColorScheme.Normal + //}; + + //KeyView.ColorScheme = cs; + + } + /// public override bool OnEnter (View view) { - if (SuperView is { }) - { - ColorScheme = new (SuperView?.ColorScheme) - { - Normal = SuperView.ColorScheme.Focus, - HotNormal = SuperView.ColorScheme.HotFocus - }; - } - + SetColorScheme (); return base.OnEnter (view); } @@ -696,8 +709,8 @@ public class Shortcut : View public override bool OnLeave (View view) { // Reset the color scheme (to SuperView). - ColorScheme = null; - + //ColorScheme = null; + SetColorScheme (); return base.OnLeave (view); } diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index eefc202d7..076a2adce 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -1377,7 +1377,8 @@ public class Slider : View SetNeedsDisplay (); - return true; + mouseEvent.Handled = true; + return OnMouseClick (new (mouseEvent)); } return false; diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 441e83a5a..0fcdf7038 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -110,6 +110,7 @@ public class Bars : Scenario Y = 2, Orientation = Orientation.Vertical, StatusBarStyle = false, + BorderStyle = LineStyle.Rounded }; bar.Add (shortcut1, shortcut2); @@ -132,9 +133,9 @@ public class Bars : Scenario // BUGBUG: This should not be needed Application.Top.LayoutSubviews (); - //SetupMenuBar (); + // SetupMenuBar (); //SetupContentMenu (); - SetupStatusBar (); + // SetupStatusBar (); foreach (Bar barView in Application.Top.Subviews.Where (b => b is Bar)!) { @@ -298,103 +299,111 @@ public class Bars : Scenario ((View)(sender)).LayoutSubviews (); } - //private void SetupMenuBar () - //{ - // var menuBar = new Bar - // { - // Id = "menuBar", + private void SetupMenuBar () + { + var menuBar = new Bar + { + Id = "menuBar", + Width = Dim.Fill (), + Height = 1,//Dim.Auto (DimAutoStyle.Content), + Orientation = Orientation.Horizontal, + StatusBarStyle = false, + }; - // X = 0, - // Y = 0, - // Width = Dim.Fill (), - // Height = Dim.Auto (DimAutoStyle.Content), - // StatusBarStyle = true - // }; + var fileMenuBarItem = new Shortcut + { + Title = "_File", + KeyBindingScope = KeyBindingScope.Application, + Key = Key.F.WithAlt, + }; + fileMenuBarItem.KeyView.Visible = false; + + var editMenuBarItem = new Shortcut + { + Title = "_Edit", - // var fileMenu = new Shortcut - // { - // Title = "_File", - // Key = Key.F.WithAlt, - // KeyBindingScope = KeyBindingScope.HotKey, - // Command = Command.Accept, - // }; - // fileMenu.HelpView.Visible = false; - // fileMenu.KeyView.Visible = false; + KeyBindingScope = KeyBindingScope.HotKey, + }; - // fileMenu.Accept += (s, e) => - // { - // fileMenu.SetFocus (); + editMenuBarItem.Accept += (s, e) => { }; + //editMenu.HelpView.Visible = false; + //editMenu.KeyView.Visible = false; - // if (s is View view) - // { - // var menu = new Bar - // { - // X = view.Frame.X + 1, - // Y = view.Frame.Y + 1, - // ColorScheme = view.ColorScheme, - // Orientation = Orientation.Vertical, - // StatusBarStyle = false, - // BorderStyle = LineStyle.Dotted, - // Width = Dim.Auto (DimAutoStyle.Content), - // Height = Dim.Auto (DimAutoStyle.Content), - // }; + menuBar.Add (fileMenuBarItem, editMenuBarItem); + menuBar.Initialized += Menu_Initialized; + Application.Top.Add (menuBar); - // menu.KeyBindings.Add (Key.Esc, Command.QuitToplevel); + var fileMenu = new Bar + { + X = 1, + Y = 1, + Orientation = Orientation.Vertical, + StatusBarStyle = false, + Modal = true, + Visible = false, + }; - // var newMenu = new Shortcut - // { - // Title = "_New...", - // Text = "Create a new file", - // Key = Key.N.WithCtrl - // }; + var newShortcut = new Shortcut + { + Title = "_New...", + Text = "Create a new file", + Key = Key.N.WithCtrl + }; + newShortcut.Border.Thickness = new Thickness (0, 1, 0, 0); - // var open = new Shortcut - // { - // Title = "_Open...", - // Text = "Show the File Open Dialog", - // Key = Key.O.WithCtrl - // }; + var openShortcut = new Shortcut + { + Title = "_Open...", + Text = "Show the File Open Dialog", + Key = Key.O.WithCtrl + }; - // var save = new Shortcut - // { - // Title = "_Save...", - // Text = "Save", - // Key = Key.S.WithCtrl - // }; + var saveShortcut = new Shortcut + { + Title = "_Save...", + Text = "Save", + Key = Key.S.WithCtrl, + Enabled = false + }; - // menu.Add (newMenu, open, save); + var exitShortcut = new Shortcut + { + Title = "E_xit", + Text = "Exit", + Key = Key.X.WithCtrl, + }; + exitShortcut.Border.Thickness = new Thickness (0, 1, 0, 1); - // // BUGBUG: this is all bad - // menu.Initialized += Menu_Initialized; - // open.Initialized += Menu_Initialized; - // save.Initialized += Menu_Initialized; - // newMenu.Initialized += Menu_Initialized; + fileMenu.Add (newShortcut, openShortcut, saveShortcut, exitShortcut); - // Application.Run (menu); - // menu.Dispose (); - // Application.Refresh (); - // } - // }; + View prevFocus = null; + fileMenuBarItem.Accept += (s, e) => + { + if (fileMenu.Visible) + { + fileMenu.RequestStop (); + prevFocus?.SetFocus (); + return; + } - // var editMenu = new Shortcut - // { - // Title = "_Edit", + //fileMenu.Visible = !fileMenu.Visible; + var sender = s as Shortcut; + var screen = sender.FrameToScreen (); + fileMenu.X = screen.X; + fileMenu.Y = screen.Y + 1; + fileMenu.Visible = true; + prevFocus = Application.Top.Focused; + fileMenuBarItem.SetFocus (); + Application.Run (fileMenu); + fileMenu.Visible = false; + }; - // //Key = Key.E.WithAlt, - // KeyBindingScope = KeyBindingScope.HotKey, - // Command = Command.Accept - // }; + Application.Top.Closed += (s, e) => + { + fileMenu.Dispose (); + }; - // editMenu.Accept += (s, e) => { }; - // editMenu.HelpView.Visible = false; - // editMenu.KeyView.Visible = false; - - // menuBar.Add (fileMenu, editMenu); - - // menuBar.Initialized += Menu_Initialized; - - // Application.Top.Add (menuBar); - //} + } private void SetupStatusBar () { diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index c2411a379..2c63d3d8c 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.Linq; +using System.Threading.Tasks; using System.Timers; using Terminal.Gui; @@ -81,14 +82,14 @@ public class Shortcuts : Scenario vShortcut2.Accept += (o, args) => { // Cycle to next item. If at end, set 0 - if (((RadioGroup)vShortcut2.CommandView).SelectedItem < ((RadioGroup)vShortcut2.CommandView).RadioLabels.Length - 1) - { - ((RadioGroup)vShortcut2.CommandView).SelectedItem++; - } - else - { - ((RadioGroup)vShortcut2.CommandView).SelectedItem = 0; - } + //if (((RadioGroup)vShortcut2.CommandView).SelectedItem < ((RadioGroup)vShortcut2.CommandView).RadioLabels.Length - 1) + //{ + // ((RadioGroup)vShortcut2.CommandView).SelectedItem++; + //} + //else + //{ + // ((RadioGroup)vShortcut2.CommandView).SelectedItem = 0; + //} }; vShortcut2.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (vShortcut2); @@ -99,7 +100,7 @@ public class Shortcuts : Scenario X = 0, Y = Pos.Bottom (vShortcut2), CommandView = new CheckBox { Text = "_Align" }, - Key = Key.F3, + Key = Key.F5.WithCtrl.WithAlt.WithShift, HelpText = "Width is Fill", Width = Dim.Fill () - Dim.Width (eventLog), KeyBindingScope = KeyBindingScope.HotKey, @@ -161,7 +162,7 @@ public class Shortcuts : Scenario Y = Pos.Bottom (vShortcut4), Width = Dim.Width (vShortcut4), - Key = Key.F5.WithCtrl.WithAlt.WithShift, + Key = Key.F4, HelpText = "CommandView.CanFocus", KeyBindingScope = KeyBindingScope.HotKey, BorderStyle = LineStyle.Rounded, @@ -178,8 +179,11 @@ public class Shortcuts : Scenario foreach (Shortcut peer in Application.Top.Subviews.Where (v => v is Shortcut)!) { - peer.CanFocus = e.NewValue == true; - peer.CommandView.CanFocus = e.NewValue == true; + if (peer.CanFocus) + { + peer.CommandView.CanFocus = e.NewValue == true; + peer.SetColorScheme (); + } } } }; @@ -224,13 +228,14 @@ public class Shortcuts : Scenario Title = "_No Key", HelpText = "Keyless", - BorderStyle = LineStyle.Rounded + BorderStyle = LineStyle.Rounded, }; vShortcut6.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (vShortcut6); + vShortcut6.SetFocus (); - ((CheckBox)vShortcut3.CommandView).OnToggled(); + ((CheckBox)vShortcut3.CommandView).OnToggled (); // Horizontal var hShortcut1 = new Shortcut @@ -303,7 +308,7 @@ public class Shortcuts : Scenario var hShortcutBG = new Shortcut { - X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1)-1, + X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1) - 1, Y = Pos.Top (hShortcut2), Key = Key.F9, HelpText = "BG Color", From 05613bb2291459f15445c065539169798cb18493 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 14 Jun 2024 13:01:33 -0700 Subject: [PATCH 29/85] Start on replacing old StatusBar with new --- Terminal.Gui/Views/Bar.cs | 9 +- Terminal.Gui/Views/Shortcut.cs | 39 ++- Terminal.Gui/Views/StatusBar.cs | 239 +++--------------- Terminal.Gui/Views/StatusItem.cs | 59 ----- .../Scenarios/BackgroundWorkerCollection.cs | 4 +- UICatalog/Scenarios/Bars.cs | 6 +- UICatalog/Scenarios/ConfigurationEditor.cs | 45 ++-- UICatalog/Scenarios/CsvEditor.cs | 2 + UICatalog/Scenarios/DynamicStatusBar.cs | 113 +++------ UICatalog/Scenarios/Editor.cs | 2 + UICatalog/Scenarios/GraphViewExample.cs | 2 + UICatalog/Scenarios/HexEditor.cs | 7 +- UICatalog/Scenarios/InteractiveTree.cs | 3 +- UICatalog/Scenarios/LineViewExample.cs | 2 + UICatalog/Scenarios/ListColumns.cs | 2 + UICatalog/Scenarios/MultiColouredTable.cs | 3 + UICatalog/Scenarios/Notepad.cs | 10 +- UICatalog/Scenarios/SingleBackgroundWorker.cs | 4 + UICatalog/Scenarios/SyntaxHighlighting.cs | 2 + UICatalog/Scenarios/TabViewExample.cs | 2 + UICatalog/Scenarios/TableEditor.cs | 2 + .../Scenarios/TextViewAutocompletePopup.cs | 19 +- UICatalog/Scenarios/TreeUseCases.cs | 2 + UICatalog/Scenarios/Unicode.cs | 2 + UICatalog/UICatalog.cs | 5 - UnitTests/View/NavigationTests.cs | 2 + UnitTests/Views/ContextMenuTests.cs | 4 + UnitTests/Views/LabelTests.cs | 66 +---- UnitTests/Views/StatusBarTests.cs | 5 +- UnitTests/Views/WindowTests.cs | 16 +- 30 files changed, 212 insertions(+), 466 deletions(-) delete mode 100644 Terminal.Gui/Views/StatusItem.cs diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 4edeb57f9..2b1c11c26 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -9,7 +9,7 @@ namespace Terminal.Gui; /// /// /// -public class Bar : Toplevel +public class Bar : View { /// public Bar () @@ -44,15 +44,12 @@ public class Bar : Toplevel /// public Orientation Orientation { get; set; } = Orientation.Horizontal; + + public bool StatusBarStyle { get; set; } = true; public override void Add (View view) { - if (Orientation == Orientation.Horizontal) - { - //view.AutoSize = true; - } - base.Add (view); AdjustSubviewBorders (); diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index e4484d128..4b4c24d79 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -1,4 +1,6 @@ using System.ComponentModel; +using System.Reflection.Metadata; +using Terminal.Gui.Analyzers.Internal.Attributes; namespace Terminal.Gui; @@ -6,6 +8,18 @@ namespace Terminal.Gui; // TODO: It can mean "Application-scoped key binding" or "A key binding that is displayed in a visual way". // TODO: I tried `BarItem` but that's not great either as it implies it can only be used in `Bar`s. +[Flags] +[GenerateEnumExtensionMethods (FastHasFlags = true)] +public enum ShortcutStyles +{ + None = 0, + + SeparatorBefore = 8, + + SeparatorAfter = 16, +} + + /// /// Displays a command, help text, and a key binding. When the key is pressed, the command will be invoked. Useful for /// displaying a command in such as a @@ -80,7 +94,6 @@ public class Shortcut : View KeyView.MouseClick += Shortcut_MouseClick; LayoutStarted += OnLayoutStarted; - DrawContent += Shortcut_DrawContent; Initialized += OnInitialized; @@ -110,12 +123,6 @@ public class Shortcut : View } } - private void Shortcut_DrawContent (object sender, DrawEventArgs e) - { - //SetColorScheme(); - } - - /// /// Gets or sets the for this . The default is /// , which is ideal for status bars and toolbars. If set to , @@ -123,6 +130,8 @@ public class Shortcut : View /// public Orientation Orientation { get; set; } = Orientation.Horizontal; + public ShortcutStyles ShortcutStyle { get; set; } = ShortcutStyles.None; + // When one of the subviews is "empty" we don't want to show it. So we // Use Add/Remove. We need to be careful to add them in the right order // so Pos.Align works correctly. @@ -622,16 +631,26 @@ public class Shortcut : View break; } + if (AcceptAction is null) + { + AcceptAction = () => + { + var args = new HandledEventArgs (); + Accept?.Invoke (this, args); + }; + } + if (handled == false) { - var args = new HandledEventArgs (); - Accept?.Invoke (this, args); - handled = args.Handled; + AcceptAction.Invoke(); } return true; } + [CanBeNull] + public Action AcceptAction { get; set; } + #endregion Accept Handling #region Focus diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 4694eebfb..ba7f64fad 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -2,242 +2,67 @@ namespace Terminal.Gui; /// /// A status bar is a that snaps to the bottom of a displaying set of -/// s. The should be context sensitive. This means, if the main menu +/// s. The should be context sensitive. This means, if the main menu /// and an open text editor are visible, the items probably shown will be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog /// to ask a file to load is executed, the remaining commands will probably be ~F1~ Help. So for each context must be a /// new instance of a status bar. /// -public class StatusBar : View +public class StatusBar : Bar { - private static Rune _shortcutDelimiter = (Rune)'='; - private StatusItem [] _items = []; - - /// Initializes a new instance of the class. - public StatusBar () : this (new StatusItem [] { }) { } - - /// - /// Initializes a new instance of the class with the specified set of - /// s. The will be drawn on the lowest line of the terminal or - /// (if not null). - /// - /// A list of status bar items. - public StatusBar (StatusItem [] items) + public StatusBar () { - if (items is { }) - { - Items = items; - } - - CanFocus = false; - ColorScheme = Colors.ColorSchemes ["Menu"]; - X = 0; + Orientation = Orientation.Horizontal; Y = Pos.AnchorEnd (); Width = Dim.Fill (); - Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize (). - - AddCommand (Command.Accept, ctx => InvokeItem ((StatusItem)ctx.KeyBinding?.Context)); } - /// The items that compose the - public StatusItem [] Items + /// + public override void Add (View view) { - get => _items; - set - { - foreach (StatusItem item in _items) - { - KeyBindings.Remove (item.Shortcut); - } - - _items = value; - - foreach (StatusItem item in _items.Where (i => i.Shortcut != Key.Empty)) - { - KeyBinding keyBinding = new (new [] { Command.Accept }, KeyBindingScope.HotKey, item); - KeyBindings.Add (item.Shortcut, keyBinding); - } - } + view.CanFocus = false; + base.Add (view); } - /// Gets or sets shortcut delimiter separator. The default is "-". - public static Rune ShortcutDelimiter - { - get => _shortcutDelimiter; - set - { - if (_shortcutDelimiter != value) - { - _shortcutDelimiter = value == default (Rune) ? (Rune)'=' : value; - } - } - } - - /// Inserts a in the specified index of . + /// Inserts a in the specified index of . /// The zero-based index at which item should be inserted. /// The item to insert. - public void AddItemAt (int index, StatusItem item) + public void AddShortcutAt (int index, Shortcut item) { - List itemsList = new (Items); - itemsList.Insert (index, item); - Items = itemsList.ToArray (); + List savedSubViewList = Subviews.ToList (); + int count = savedSubViewList.Count; + RemoveAll (); + for (int i = 0; i < count; i++) + { + if (i == index) + { + Add (item); + } + Add (savedSubViewList [i]); + } SetNeedsDisplay (); } - /// - protected internal override bool OnMouseEvent (MouseEvent me) - { - if (me.Flags != MouseFlags.Button1Clicked) - { - return false; - } - - var pos = 1; - - for (var i = 0; i < Items.Length; i++) - { - if (me.Position.X >= pos && me.Position.X < pos + GetItemTitleLength (Items [i].Title)) - { - StatusItem item = Items [i]; - - if (item.IsEnabled ()) - { - Run (item.Action); - } - - break; - } - - pos += GetItemTitleLength (Items [i].Title) + 3; - } - - return true; - } - - /// - public override void OnDrawContent (Rectangle viewport) - { - Move (0, 0); - Driver.SetAttribute (GetNormalColor ()); - - for (var i = 0; i < Frame.Width; i++) - { - Driver.AddRune ((Rune)' '); - } - - Move (1, 0); - Attribute scheme = GetNormalColor (); - Driver.SetAttribute (scheme); - - for (var i = 0; i < Items.Length; i++) - { - string title = Items [i].Title; - Driver.SetAttribute (DetermineColorSchemeFor (Items [i])); - - for (var n = 0; n < Items [i].Title.GetRuneCount (); n++) - { - if (title [n] == '~') - { - if (Items [i].IsEnabled ()) - { - scheme = ToggleScheme (scheme); - } - - continue; - } - - Driver.AddRune ((Rune)title [n]); - } - - if (i + 1 < Items.Length) - { - Driver.AddRune ((Rune)' '); - Driver.AddRune (Glyphs.VLine); - Driver.AddRune ((Rune)' '); - } - } - } - - /// Removes a at specified index of . + /// Removes a at specified index of . /// The zero-based index of the item to remove. - /// The removed. - public StatusItem RemoveItem (int index) + /// The removed. + public Shortcut RemoveItem (int index) { - List itemsList = new (Items); - StatusItem item = itemsList [index]; - itemsList.RemoveAt (index); - Items = itemsList.ToArray (); - SetNeedsDisplay (); - - return item; - } - - private Attribute DetermineColorSchemeFor (StatusItem item) - { - if (item is { }) + View toRemove = null; + for (int i = 0; i < Subviews.Count; i++) { - if (item.IsEnabled ()) + if (i == index) { - return GetNormalColor (); + toRemove = Subviews [i]; } - - return ColorScheme.Disabled; } - return GetNormalColor (); - } - - private int GetItemTitleLength (string title) - { - var len = 0; - - foreach (char ch in title) + if (toRemove is { }) { - if (ch == '~') - { - continue; - } - - len++; + Remove (toRemove); + SetNeedsDisplay (); } - return len; - } - - private bool? InvokeItem (StatusItem itemToInvoke) - { - if (itemToInvoke is { Action: { } }) - { - itemToInvoke.Action.Invoke (); - - return true; - } - - return false; - } - - private void Run (Action action) - { - if (action is null) - { - return; - } - - Application.MainLoop.AddIdle ( - () => - { - action (); - - return false; - } - ); - } - - private Attribute ToggleScheme (Attribute scheme) - { - Attribute result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal; - Driver.SetAttribute (result); - - return result; + return toRemove as Shortcut; } } diff --git a/Terminal.Gui/Views/StatusItem.cs b/Terminal.Gui/Views/StatusItem.cs deleted file mode 100644 index 5028f722e..000000000 --- a/Terminal.Gui/Views/StatusItem.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Terminal.Gui; - -/// -/// objects are contained by s. Each -/// has a title, a shortcut (hotkey), and an that will be invoked when -/// the is pressed. The will be a global hotkey for -/// the application in the current context of the screen. The color of the will be -/// changed after each ~. A set to `~F1~ Help` will render as *F1* using -/// and *Help* as . -/// -public class StatusItem -{ - /// Initializes a new . - /// Shortcut to activate the . - /// Title for the . - /// Action to invoke when the is activated. - /// Function to determine if the action can currently be executed. - public StatusItem (Key shortcut, string title, Action action, Func canExecute = null) - { - Title = title ?? ""; - Shortcut = shortcut; - Action = action; - CanExecute = canExecute; - } - - /// Gets or sets the action to be invoked when the is triggered - /// Action to invoke. - public Action Action { get; set; } - - /// - /// Gets or sets the action to be invoked to determine if the can be triggered. If - /// returns the status item will be enabled. Otherwise, it will be - /// disabled. - /// - /// Function to determine if the action is can be executed or not. - public Func CanExecute { get; set; } - - /// Gets or sets arbitrary data for the status item. - /// This property is not used internally. - public object Data { get; set; } - - /// Gets the global shortcut to invoke the action on the menu. - public Key Shortcut { get; set; } - - /// Gets or sets the title. - /// The title. - /// - /// The colour of the will be changed after each ~. A - /// set to `~F1~ Help` will render as *F1* using and - /// *Help* as . - /// - public string Title { get; set; } - - /// - /// Returns if the status item is enabled. This method is a wrapper around - /// . - /// - public bool IsEnabled () { return CanExecute?.Invoke () ?? true; } -} diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index e6e35debb..29eb2b1c9 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -88,7 +88,7 @@ public class BackgroundWorkerCollection : Scenario ; _menu.MenuOpening += Menu_MenuOpening; Add (_menu); - +#if V2_STATUSBAR var statusBar = new StatusBar ( new [] { @@ -106,7 +106,7 @@ public class BackgroundWorkerCollection : Scenario } ); Add (statusBar); - +#endif Ready += OverlappedMain_Ready; Activate += OverlappedMain_Activate; Deactivate += OverlappedMain_Deactivate; diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 0fcdf7038..c0449572a 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -339,7 +339,7 @@ public class Bars : Scenario Y = 1, Orientation = Orientation.Vertical, StatusBarStyle = false, - Modal = true, + // Modal = true, Visible = false, }; @@ -381,7 +381,7 @@ public class Bars : Scenario { if (fileMenu.Visible) { - fileMenu.RequestStop (); + // fileMenu.RequestStop (); prevFocus?.SetFocus (); return; } @@ -394,7 +394,7 @@ public class Bars : Scenario fileMenu.Visible = true; prevFocus = Application.Top.Focused; fileMenuBarItem.SetFocus (); - Application.Run (fileMenu); + //Application.Run (fileMenu); fileMenu.Visible = false; }; diff --git a/UICatalog/Scenarios/ConfigurationEditor.cs b/UICatalog/Scenarios/ConfigurationEditor.cs index b24e35592..99b664bd9 100644 --- a/UICatalog/Scenarios/ConfigurationEditor.cs +++ b/UICatalog/Scenarios/ConfigurationEditor.cs @@ -22,7 +22,7 @@ public class ConfigurationEditor : Scenario }; private static Action _editorColorSchemeChanged; - private StatusItem _lenStatusItem; + private Shortcut _lenShortcut; private TileView _tileView; [SerializableConfigurationProperty (Scope = typeof (AppScope))] @@ -48,21 +48,34 @@ public class ConfigurationEditor : Scenario top.Add (_tileView); - _lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null); + _lenShortcut = new Shortcut () + { + Title = "Len: ", + }; - var statusBar = new StatusBar ( - new [] - { - new ( - Application.QuitKey, - $"{Application.QuitKey} Quit", - () => Quit () - ), - new (KeyCode.F5, "~F5~ Reload", () => Reload ()), - new (KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save ()), - _lenStatusItem - } - ); + var quitShortcut = new Shortcut () + { + Key = Application.QuitKey, + Title = $"{Application.QuitKey} Quit", + AcceptAction = Quit + }; + + var reloadShortcut = new Shortcut () + { + Key = KeyCode.F5, + Title = "Reload", + AcceptAction = Reload + }; + var saveShortcut = new Shortcut () + { + Key = Key.S.WithCtrl, + Title = "Save", + AcceptAction = Save + }; + + + var statusBar = new StatusBar (); + statusBar.Add (quitShortcut, reloadShortcut, saveShortcut, _lenShortcut); top.Add (statusBar); @@ -120,7 +133,7 @@ public class ConfigurationEditor : Scenario textView.Read (); - textView.Enter += (s, e) => { _lenStatusItem.Title = $"Len:{textView.Text.Length}"; }; + textView.Enter += (s, e) => { _lenShortcut.Title = $"Len:{textView.Text.Length}"; }; } Application.Top.LayoutSubviews (); diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index 2584462bc..c230f5259 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -103,6 +103,7 @@ public class CsvEditor : Scenario }; Top.Add (menu); +#if V2_STATUSBAR var statusBar = new StatusBar ( new StatusItem [] { @@ -124,6 +125,7 @@ public class CsvEditor : Scenario } ); Top.Add (statusBar); +#endif Win.Add (_tableView); diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs index e4b0de705..08eb9d512 100644 --- a/UICatalog/Scenarios/DynamicStatusBar.cs +++ b/UICatalog/Scenarios/DynamicStatusBar.cs @@ -8,6 +8,7 @@ using Terminal.Gui; namespace UICatalog.Scenarios; +#if V2_STATUSBAR [ScenarioMetadata ("Dynamic StatusBar", "Demonstrates how to add and remove a StatusBar and change items dynamically.")] [ScenarioCategory ("Top Level Windows")] public class DynamicStatusBar : Scenario @@ -90,9 +91,9 @@ public class DynamicStatusBar : Scenario public class DynamicStatusBarDetails : FrameView { - private StatusItem _statusItem; + private Shortcut _statusItem; - public DynamicStatusBarDetails (StatusItem statusItem = null) : this () + public DynamicStatusBarDetails (Shortcut statusItem = null) : this () { _statusItem = statusItem; Title = statusItem == null ? "Adding New StatusBar Item." : "Editing StatusBar Item."; @@ -155,7 +156,7 @@ public class DynamicStatusBar : Scenario bool CheckShortcut (KeyCode k, bool pre) { - StatusItem m = _statusItem != null ? _statusItem : new StatusItem (k, "", null); + Shortcut m = _statusItem != null ? _statusItem : new Shortcut (k, "", null); if (pre && !ShortcutHelper.PreShortcutValidation (k)) { @@ -166,28 +167,10 @@ public class DynamicStatusBar : Scenario if (!pre) { - if (!ShortcutHelper.PostShortcutValidation ( - ShortcutHelper.GetShortcutFromTag ( - TextShortcut.Text, - StatusBar.ShortcutDelimiter - ) - )) - { - TextShortcut.Text = ""; - - return false; - } - return true; } - TextShortcut.Text = - Key.ToString ( - k, - StatusBar - .ShortcutDelimiter - ); //ShortcutHelper.GetShortcutTag (k, StatusBar.ShortcutDelimiter); - + TextShortcut.Text = k.ToString(); return true; } @@ -213,7 +196,7 @@ public class DynamicStatusBar : Scenario public TextField TextTitle { get; } public Action CreateAction (DynamicStatusItem item) { return () => MessageBox.ErrorQuery (item.Title, item.Action, "Ok"); } - public void EditStatusItem (StatusItem statusItem) + public void EditStatusItem (Shortcut statusItem) { if (statusItem == null) { @@ -231,12 +214,7 @@ public class DynamicStatusBar : Scenario ? GetTargetAction (statusItem.Action) : string.Empty; - TextShortcut.Text = - Key.ToString ( - (KeyCode)statusItem.Shortcut, - StatusBar - .ShortcutDelimiter - ); //ShortcutHelper.GetShortcutTag (statusItem.Shortcut, StatusBar.ShortcutDelimiter) ?? ""; + TextShortcut.Text = statusItem.Shortcut; } public DynamicStatusItem EnterStatusItem () @@ -334,32 +312,15 @@ public class DynamicStatusBar : Scenario public class DynamicStatusBarSample : Window { private readonly ListView _lstItems; - private StatusItem _currentEditStatusItem; + private Shortcut _currentEditStatusItem; private int _currentSelectedStatusBar = -1; - private StatusItem _currentStatusItem; + private Shortcut _currentStatusItem; private StatusBar _statusBar; public DynamicStatusBarSample () { DataContext = new DynamicStatusItemModel (); - var _frmDelimiter = new FrameView - { - X = Pos.Center (), - Y = 0, - Width = 25, - Height = 4, - Title = "Shortcut Delimiter:" - }; - - var _txtDelimiter = new TextField { X = Pos.Center (), Width = 2, Text = $"{StatusBar.ShortcutDelimiter}" }; - - _txtDelimiter.TextChanged += (s, _) => - StatusBar.ShortcutDelimiter = _txtDelimiter.Text.ToRunes () [0]; - _frmDelimiter.Add (_txtDelimiter); - - Add (_frmDelimiter); - var _frmStatusBar = new FrameView { Y = 5, Width = Dim.Percent (50), Height = Dim.Fill (2), Title = "Items:" @@ -404,18 +365,18 @@ public class DynamicStatusBar : Scenario Y = Pos.Top (_frmStatusBar), Width = Dim.Fill (), Height = Dim.Fill (4), - Title = "StatusBar Item Details:" + Title = "Shortcut Details:" }; Add (_frmStatusBarDetails); _btnUp.Accept += (s, e) => { int i = _lstItems.SelectedItem; - StatusItem statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null; + Shortcut statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].Shortcut : null; if (statusItem != null) { - StatusItem [] items = _statusBar.Items; + Shortcut [] items = _statusBar.Items; if (i > 0) { @@ -434,11 +395,11 @@ public class DynamicStatusBar : Scenario _btnDown.Accept += (s, e) => { int i = _lstItems.SelectedItem; - StatusItem statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null; + Shortcut statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].Shortcut : null; if (statusItem != null) { - StatusItem [] items = _statusBar.Items; + Shortcut [] items = _statusBar.Items; if (i < items.Length - 1) { @@ -511,9 +472,9 @@ public class DynamicStatusBar : Scenario return; } - StatusItem newStatusItem = CreateNewStatusBar (item); + Shortcut newStatusItem = CreateNewStatusBar (item); _currentSelectedStatusBar++; - _statusBar.AddItemAt (_currentSelectedStatusBar, newStatusItem); + _statusBar.AddShortcutAt (_currentSelectedStatusBar, newStatusItem); DataContext.Items.Add (new DynamicStatusItemList (newStatusItem.Title, newStatusItem)); _lstItems.MoveDown (); SetFrameDetails (); @@ -521,8 +482,8 @@ public class DynamicStatusBar : Scenario _btnRemove.Accept += (s, e) => { - StatusItem statusItem = DataContext.Items.Count > 0 - ? DataContext.Items [_lstItems.SelectedItem].StatusItem + Shortcut statusItem = DataContext.Items.Count > 0 + ? DataContext.Items [_lstItems.SelectedItem].Shortcut : null; if (statusItem != null) @@ -542,8 +503,8 @@ public class DynamicStatusBar : Scenario _lstItems.Enter += (s, e) => { - StatusItem statusItem = DataContext.Items.Count > 0 - ? DataContext.Items [_lstItems.SelectedItem].StatusItem + Shortcut statusItem = DataContext.Items.Count > 0 + ? DataContext.Items [_lstItems.SelectedItem].Shortcut : null; SetFrameDetails (statusItem); }; @@ -582,14 +543,14 @@ public class DynamicStatusBar : Scenario var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter); - void SetFrameDetails (StatusItem statusItem = null) + void SetFrameDetails (Shortcut statusItem = null) { - StatusItem newStatusItem; + Shortcut newStatusItem; if (statusItem == null) { newStatusItem = DataContext.Items.Count > 0 - ? DataContext.Items [_lstItems.SelectedItem].StatusItem + ? DataContext.Items [_lstItems.SelectedItem].Shortcut : null; } else @@ -608,10 +569,10 @@ public class DynamicStatusBar : Scenario } } - void SetListViewSource (StatusItem _currentStatusItem, bool fill = false) + void SetListViewSource (Shortcut _currentStatusItem, bool fill = false) { DataContext.Items = []; - StatusItem statusItem = _currentStatusItem; + Shortcut statusItem = _currentStatusItem; if (!fill) { @@ -620,29 +581,22 @@ public class DynamicStatusBar : Scenario if (statusItem != null) { - foreach (StatusItem si in _statusBar.Items) + foreach (Shortcut si in _statusBar.Items) { DataContext.Items.Add (new DynamicStatusItemList (si.Title, si)); } } } - StatusItem CreateNewStatusBar (DynamicStatusItem item) + Shortcut CreateNewStatusBar (DynamicStatusItem item) { - var newStatusItem = new StatusItem ( - ShortcutHelper.GetShortcutFromTag ( - item.Shortcut, - StatusBar.ShortcutDelimiter - ), - item.Title, - _frmStatusBarDetails.CreateAction (item) - ); + var newStatusItem = new Shortcut (item.Shortcut); return newStatusItem; } void UpdateStatusItem ( - StatusItem _currentEditStatusItem, + Shortcut _currentEditStatusItem, DynamicStatusItem statusItem, int index ) @@ -702,15 +656,15 @@ public class DynamicStatusBar : Scenario { public DynamicStatusItemList () { } - public DynamicStatusItemList (string title, StatusItem statusItem) + public DynamicStatusItemList (string title, Shortcut statusItem) { Title = title; - StatusItem = statusItem; + Shortcut = statusItem; } - public StatusItem StatusItem { get; set; } + public Shortcut Shortcut { get; set; } public string Title { get; set; } - public override string ToString () { return $"{Title}, {StatusItem}"; } + public override string ToString () { return $"{Title}, {Shortcut}"; } } public class DynamicStatusItemModel : INotifyPropertyChanged @@ -781,3 +735,4 @@ public class DynamicStatusBar : Scenario } } } +#endif \ No newline at end of file diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 05277e7d4..4d50e5199 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -237,6 +237,7 @@ public class Editor : Scenario }; _appWindow.Add (menu); +#if V2_STATUSBAR var siCursorPosition = new StatusItem (KeyCode.Null, "", null); @@ -266,6 +267,7 @@ public class Editor : Scenario }; _appWindow.Add (statusBar); +#endif _scrollBar = new (_textView, true); diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index b60a95c3b..3798b26c3 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -165,6 +165,7 @@ public class GraphViewExample : Scenario Win.Add (frameRight); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -178,6 +179,7 @@ public class GraphViewExample : Scenario () => _graphs [_currentGraph++ % _graphs.Length] () ) } +#endif ); Top.Add (statusBar); } diff --git a/UICatalog/Scenarios/HexEditor.cs b/UICatalog/Scenarios/HexEditor.cs index 1427f61c0..ba3da9a19 100644 --- a/UICatalog/Scenarios/HexEditor.cs +++ b/UICatalog/Scenarios/HexEditor.cs @@ -16,8 +16,10 @@ public class HexEditor : Scenario private HexView _hexView; private MenuItem _miAllowEdits; private bool _saved = true; +#if V2_STATUSBAR private StatusItem _siPositionChanged; private StatusBar _statusBar; +#endif public override void Setup () { @@ -75,7 +77,7 @@ public class HexEditor : Scenario ] }; Top.Add (menu); - +#if V2_STATUSBAR _statusBar = new StatusBar ( new [] { @@ -102,12 +104,14 @@ public class HexEditor : Scenario } ); Top.Add (_statusBar); +#endif } private void _hexView_Edited (object sender, HexViewEditEventArgs e) { _saved = false; } private void _hexView_PositionChanged (object sender, HexViewEventArgs obj) { +#if V2_STATUSBAR _siPositionChanged.Title = $"Position: { obj.Position @@ -119,6 +123,7 @@ public class HexEditor : Scenario obj.BytesPerLine }"; _statusBar.SetNeedsDisplay (); +#endif } private void Copy () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok"); } diff --git a/UICatalog/Scenarios/InteractiveTree.cs b/UICatalog/Scenarios/InteractiveTree.cs index fafc34351..22a3b7f08 100644 --- a/UICatalog/Scenarios/InteractiveTree.cs +++ b/UICatalog/Scenarios/InteractiveTree.cs @@ -29,8 +29,8 @@ public class InteractiveTree : Scenario _treeView.KeyDown += TreeView_KeyPress; Win.Add (_treeView); - var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -54,6 +54,7 @@ public class InteractiveTree : Scenario RenameNode ) } +#endif ); Top.Add (statusBar); } diff --git a/UICatalog/Scenarios/LineViewExample.cs b/UICatalog/Scenarios/LineViewExample.cs index dc386ba94..bfdc09d70 100644 --- a/UICatalog/Scenarios/LineViewExample.cs +++ b/UICatalog/Scenarios/LineViewExample.cs @@ -73,6 +73,7 @@ public class LineViewExample : Scenario Win.Add (verticalArrow); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -81,6 +82,7 @@ public class LineViewExample : Scenario () => Quit () ) } +#endif ); Top.Add (statusBar); } diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 09aad302e..d4cf0c42c 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -212,6 +212,7 @@ public class ListColumns : Scenario Top.Add (menu); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -235,6 +236,7 @@ public class ListColumns : Scenario () => Quit () ) } +#endif ); Top.Add (statusBar); diff --git a/UICatalog/Scenarios/MultiColouredTable.cs b/UICatalog/Scenarios/MultiColouredTable.cs index 7050c0639..8b824fee0 100644 --- a/UICatalog/Scenarios/MultiColouredTable.cs +++ b/UICatalog/Scenarios/MultiColouredTable.cs @@ -32,6 +32,8 @@ public class MultiColouredTable : Scenario Top.Add (menu); var statusBar = new StatusBar ( +#if V2_STATUSBAR + new StatusItem [] { new ( @@ -40,6 +42,7 @@ public class MultiColouredTable : Scenario () => Quit () ) } +#endif ); Top.Add (statusBar); diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index 9a2613ae6..ba182e0ed 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -11,7 +11,9 @@ namespace UICatalog.Scenarios; public class Notepad : Scenario { private TabView _focusedTabView; +#if V2_STATUSBAR private StatusItem _lenStatusItem; +#endif private int _numNewTabs = 1; private TabView _tabView; @@ -66,7 +68,7 @@ public class Notepad : Scenario split.LineStyle = LineStyle.None; top.Add (split); - +#if V2_STATUSBAR _lenStatusItem = new (KeyCode.CharMask, "Len: ", null); var statusBar = new StatusBar ( @@ -87,11 +89,13 @@ public class Notepad : Scenario _lenStatusItem } ); + top.Add (statusBar); +#endif + _focusedTabView = _tabView; _tabView.SelectedTabChanged += TabView_SelectedTabChanged; _tabView.Enter += (s, e) => _focusedTabView = _tabView; - top.Add (statusBar); top.Ready += (s, e) => New (); Application.Run (top); @@ -323,7 +327,9 @@ public class Notepad : Scenario private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e) { +#if V2_STATUSBAR _lenStatusItem.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}"; +#endif e.NewTab?.View?.SetFocus (); } diff --git a/UICatalog/Scenarios/SingleBackgroundWorker.cs b/UICatalog/Scenarios/SingleBackgroundWorker.cs index 37904201e..7cc0dc8c5 100644 --- a/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -62,6 +62,7 @@ public class SingleBackgroundWorker : Scenario Add (menu); var statusBar = new StatusBar ( +#if V2_STATUSBAR new [] { new StatusItem ( @@ -75,6 +76,7 @@ public class SingleBackgroundWorker : Scenario () => RunWorker () ) } +#endif ); Add (statusBar); @@ -275,6 +277,7 @@ public class SingleBackgroundWorker : Scenario _top.Add (menu); var statusBar = new StatusBar ( +#if V2_STATUSBAR new [] { new StatusItem ( @@ -289,6 +292,7 @@ public class SingleBackgroundWorker : Scenario } ) } +#endif ); _top.Add (statusBar); diff --git a/UICatalog/Scenarios/SyntaxHighlighting.cs b/UICatalog/Scenarios/SyntaxHighlighting.cs index 2aa49d4b2..78aaff959 100644 --- a/UICatalog/Scenarios/SyntaxHighlighting.cs +++ b/UICatalog/Scenarios/SyntaxHighlighting.cs @@ -173,6 +173,7 @@ public class SyntaxHighlighting : Scenario Win.Add (_textView); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -181,6 +182,7 @@ public class SyntaxHighlighting : Scenario () => Quit () ) } +#endif ); Top.Add (statusBar); diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index 838619b29..86957713b 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -172,6 +172,7 @@ public class TabViewExample : Scenario Win.Add (frameBelow); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -180,6 +181,7 @@ public class TabViewExample : Scenario Quit ) } +#endif ); Top.Add (statusBar); } diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index 70c3151b8..8cbe2aef2 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -672,6 +672,7 @@ public class TableEditor : Scenario Top.Add (menu); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -695,6 +696,7 @@ public class TableEditor : Scenario () => Quit () ) } +#endif ); Top.Add (statusBar); diff --git a/UICatalog/Scenarios/TextViewAutocompletePopup.cs b/UICatalog/Scenarios/TextViewAutocompletePopup.cs index 8dbbdddf8..44ee6da44 100644 --- a/UICatalog/Scenarios/TextViewAutocompletePopup.cs +++ b/UICatalog/Scenarios/TextViewAutocompletePopup.cs @@ -13,8 +13,10 @@ public class TextViewAutocompletePopup : Scenario private int _height = 10; private MenuItem _miMultiline; private MenuItem _miWrap; +#if V2_STATUSBAR private StatusItem _siMultiline; private StatusItem _siWrap; +#endif private TextView _textViewBottomLeft; private TextView _textViewBottomRight; private TextView _textViewCentered; @@ -94,6 +96,7 @@ public class TextViewAutocompletePopup : Scenario _miWrap.Checked = _textViewTopLeft.WordWrap; var statusBar = new StatusBar ( +#if V2_STATUSBAR new [] { new ( @@ -104,6 +107,7 @@ public class TextViewAutocompletePopup : Scenario _siMultiline = new StatusItem (KeyCode.Null, "", null), _siWrap = new StatusItem (KeyCode.Null, "", null) } +#endif ); Top.Add (statusBar); @@ -132,8 +136,19 @@ public class TextViewAutocompletePopup : Scenario .ToList (); } - private void SetMultilineStatusText () { _siMultiline.Title = $"Multiline: {_miMultiline.Checked}"; } - private void SetWrapStatusText () { _siWrap.Title = $"WordWrap: {_miWrap.Checked}"; } + private void SetMultilineStatusText () + { +#if V2_STATUSBAR + _siMultiline.Title = $"Multiline: {_miMultiline.Checked}"; +#endif + } + + private void SetWrapStatusText () + { +#if V2_STATUSBAR + _siWrap.Title = $"WordWrap: {_miWrap.Checked}"; +#endif + } private void TextViewBottomLeft_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewBottomLeft); } private void TextViewBottomRight_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewBottomRight); } private void TextViewCentered_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewCentered); } diff --git a/UICatalog/Scenarios/TreeUseCases.cs b/UICatalog/Scenarios/TreeUseCases.cs index f306d1a91..274e2d4be 100644 --- a/UICatalog/Scenarios/TreeUseCases.cs +++ b/UICatalog/Scenarios/TreeUseCases.cs @@ -50,6 +50,7 @@ public class TreeUseCases : Scenario Top.Add (menu); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -58,6 +59,7 @@ public class TreeUseCases : Scenario () => Quit () ) } +#endif ); Top.Add (statusBar); diff --git a/UICatalog/Scenarios/Unicode.cs b/UICatalog/Scenarios/Unicode.cs index 61dc9166b..f2f84bd0d 100644 --- a/UICatalog/Scenarios/Unicode.cs +++ b/UICatalog/Scenarios/Unicode.cs @@ -63,6 +63,7 @@ public class UnicodeInMenu : Scenario Top.Add (menu); var statusBar = new StatusBar ( +#if V2_STATUSBAR new StatusItem [] { new ( @@ -73,6 +74,7 @@ public class UnicodeInMenu : Scenario new (KeyCode.Null, "~F2~ Создать", null), new (KeyCode.Null, "~F3~ Со_хранить", null) } +#endif ); Top.Add (statusBar); diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 801739287..5c227c43a 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -375,7 +375,6 @@ internal class UICatalogApp public MenuItem? MiIsMenuBorderDisabled; public MenuItem? MiIsMouseDisabled; public MenuItem? MiUseSubMenusSingleFrame; - public new Bar StatusBar; public Shortcut? ShForce16Colors; //public Shortcut? ShDiagnostics; public Shortcut? ShVersion; @@ -458,10 +457,6 @@ internal class UICatalogApp StatusBar = new () { Visible = ShowStatusBar, - Orientation = Orientation.Horizontal, - Y = Pos.AnchorEnd (), - Width = Dim.Fill (), - CanFocus = false, }; Shortcut statusBarShortcut = new Shortcut () diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/NavigationTests.cs index c326e7a33..a23f29359 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/NavigationTests.cs @@ -620,6 +620,7 @@ public class NavigationTests (ITestOutputHelper output) top1.Dispose (); } +#if V2_STATUSBAR [Fact] [AutoInitShutdown] public void HotKey_Will_Invoke_KeyPressed_Only_For_The_MostFocused_With_Top_KeyPress_Event () @@ -747,6 +748,7 @@ public class NavigationTests (ITestOutputHelper output) #endif top.Dispose (); } +#endif [Fact] [SetupFakeDriver] diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index ae697c7f7..b1917e5c0 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/UnitTests/Views/ContextMenuTests.cs @@ -133,10 +133,12 @@ public class ContextMenuTests (ITestOutputHelper output) var tf = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = "TextField" }; var statusBar = new StatusBar ( +#if V2_STATUSBAR [ new StatusItem (KeyCode.F1, "~F1~ Help", null), new StatusItem (KeyCode.CtrlMask | KeyCode.Q, "~^Q~ Quit", null) ] +#endif ); var top = new Toplevel (); @@ -200,11 +202,13 @@ public class ContextMenuTests (ITestOutputHelper output) win.Add (label, tf); var statusBar = new StatusBar ( +#if V2_STATUSBAR new [] { new StatusItem (KeyCode.F1, "~F1~ Help", null), new StatusItem (KeyCode.CtrlMask | KeyCode.Q, "~^Q~ Quit", null) } +#endif ); var top = new Toplevel (); diff --git a/UnitTests/Views/LabelTests.cs b/UnitTests/Views/LabelTests.cs index 6c85de076..7c04bcbe6 100644 --- a/UnitTests/Views/LabelTests.cs +++ b/UnitTests/Views/LabelTests.cs @@ -907,69 +907,6 @@ e top.Dispose (); } - [Fact] - [AutoInitShutdown] - public void AnchorEnd_Better_Than_Bottom_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel () - { - var win = new Window (); - - // Label is AutoSize == true - var label = new Label - { - Text = "This should be the last line.", - ColorScheme = Colors.ColorSchemes ["Menu"], - - X = 0, - Y = Pos.AnchorEnd (1) - }; - - win.Add (label); - - var menu = new MenuBar { Menus = new MenuBarItem [] { new ("Menu", "", null) } }; - var status = new StatusBar (new StatusItem [] { new (KeyCode.F1, "~F1~ Help", null) }); - Toplevel top = new (); - top.Add (win, menu, status); - RunState rs = Application.Begin (top); - - Assert.Equal (new (0, 0, 80, 25), top.Frame); - Assert.Equal (new (0, 0, 80, 1), menu.Frame); - Assert.Equal (new (0, 24, 80, 1), status.Frame); - Assert.Equal (new (0, 1, 80, 23), win.Frame); - Assert.Equal (new (0, 20, 29, 1), label.Frame); - - var expected = @" - Menu -┌──────────────────────────────────────────────────────────────────────────────┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│This should be the last line. │ -└──────────────────────────────────────────────────────────────────────────────┘ - F1 Help -"; - - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Application.End (rs); - top.Dispose (); - } - // TODO: This is a Label test. Move to label tests if there's not already a test for this. [Fact] [AutoInitShutdown] @@ -1018,6 +955,7 @@ e top.Dispose (); } +#if V2_STATUSBAR // TODO: This is a Label test. Move to label tests if there's not already a test for this. [Fact] @@ -1083,7 +1021,7 @@ e Application.End (rs); top.Dispose (); } - +#endif // TODO: This is a Dim test. Move to Dim tests. [Fact] diff --git a/UnitTests/Views/StatusBarTests.cs b/UnitTests/Views/StatusBarTests.cs index 9de049845..d702dd95a 100644 --- a/UnitTests/Views/StatusBarTests.cs +++ b/UnitTests/Views/StatusBarTests.cs @@ -1,7 +1,7 @@ using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; - +#if V2_STATUSBAR public class StatusBarTests (ITestOutputHelper output) { [Fact] @@ -16,7 +16,7 @@ public class StatusBarTests (ITestOutputHelper output) } ); - sb.AddItemAt (2, new (KeyCode.CtrlMask | KeyCode.Q, "~^C~ Close", null)); + sb.AddShortcutAt (2, new (KeyCode.CtrlMask | KeyCode.Q, "~^C~ Close", null)); Assert.Equal ("~^O~ Open", sb.Items [0].Title); Assert.Equal ("~^S~ Save", sb.Items [1].Title); @@ -205,3 +205,4 @@ CTRL-O Open { Application.Shutdown (); } } +#endif diff --git a/UnitTests/Views/WindowTests.cs b/UnitTests/Views/WindowTests.cs index 3c1fbf13a..26956ff42 100644 --- a/UnitTests/Views/WindowTests.cs +++ b/UnitTests/Views/WindowTests.cs @@ -29,6 +29,7 @@ public class WindowTests top.Dispose (); } +#if V2_STATUSBAR [Fact] [AutoInitShutdown] public void MenuBar_And_StatusBar_Inside_Window () @@ -122,6 +123,7 @@ public class WindowTests ); top.Dispose (); } +#endif [Fact] public void New_Initializes () @@ -168,9 +170,9 @@ public class WindowTests Assert.Equal (0, windowWithFrameRectEmpty.Width); Assert.Equal (0, windowWithFrameRectEmpty.Height); Assert.False (windowWithFrameRectEmpty.IsCurrentTop); - #if DEBUG +#if DEBUG Assert.Equal (windowWithFrameRectEmpty.Title, windowWithFrameRectEmpty.Id); - #endif +#endif Assert.False (windowWithFrameRectEmpty.WantContinuousButtonPressed); Assert.False (windowWithFrameRectEmpty.WantMousePositionReports); Assert.Null (windowWithFrameRectEmpty.SuperView); @@ -183,11 +185,11 @@ public class WindowTests windowWithFrame1234.Title = "title"; Assert.Equal ("title", windowWithFrame1234.Title); Assert.NotNull (windowWithFrame1234); - #if DEBUG +#if DEBUG Assert.Equal ($"Window(title){windowWithFrame1234.Frame}", windowWithFrame1234.ToString ()); - #else +#else Assert.Equal ($"Window(){windowWithFrame1234.Frame}", windowWithFrame1234.ToString ()); - #endif +#endif Assert.True (windowWithFrame1234.CanFocus); Assert.False (windowWithFrame1234.HasFocus); Assert.Equal (new (0, 0, 1, 2), windowWithFrame1234.Viewport); @@ -199,9 +201,9 @@ public class WindowTests Assert.Equal (3, windowWithFrame1234.Width); Assert.Equal (4, windowWithFrame1234.Height); Assert.False (windowWithFrame1234.IsCurrentTop); - #if DEBUG +#if DEBUG Assert.Equal (windowWithFrame1234.Title, windowWithFrame1234.Id); - #endif +#endif Assert.False (windowWithFrame1234.WantContinuousButtonPressed); Assert.False (windowWithFrame1234.WantMousePositionReports); Assert.Null (windowWithFrame1234.SuperView); From 4610a3253d58c6bbeb7ddfc4db21470a29fb5b1c Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 16 Jun 2024 00:14:08 +0100 Subject: [PATCH 30/85] Fixes #3540. V2: Keyboard input not working on Unix platforms --- .../ConsoleDrivers/ConsoleKeyMapping.cs | 13 ++- .../CursesDriver/CursesDriver.cs | 79 ++++++++++++------- .../ConsoleDrivers/CursesDriver/constants.cs | 1 - .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 33 ++++++-- Terminal.Gui/Views/Menu/MenuBar.cs | 2 + UnitTests/Input/EscSeqUtilsTests.cs | 63 +++++++++------ 6 files changed, 129 insertions(+), 62 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs index 331580083..8043c65eb 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs @@ -859,13 +859,24 @@ public static class ConsoleKeyMapping case ConsoleKey.F24: keyCode = KeyCode.F24; + break; + case ConsoleKey.Clear: + keyCode = KeyCode.Clear; + break; case ConsoleKey.Tab: keyCode = KeyCode.Tab; break; default: - keyCode = (KeyCode)consoleKeyInfo.KeyChar; + if ((int)consoleKeyInfo.KeyChar is >= 1 and <= 26) + { + keyCode = (KeyCode)(consoleKeyInfo.KeyChar + 64); + } + else + { + keyCode = (KeyCode)consoleKeyInfo.KeyChar; + } break; } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 25725b21e..14d71c637 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -515,9 +515,10 @@ internal class CursesDriver : ConsoleDriver { // The ESC-number handling, debatable. // Simulates the AltMask itself by pressing Alt + Space. + // Needed for macOS if (wch2 == (int)KeyCode.Space) { - k = KeyCode.AltMask; + k = KeyCode.AltMask | KeyCode.Space; } else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z) @@ -532,41 +533,51 @@ internal class CursesDriver : ConsoleDriver { k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0)); } - else if (wch2 == Curses.KeyCSI) + else { ConsoleKeyInfo [] cki = - { - new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false) - }; + [ + new ((char)KeyCode.Esc, 0, false, false, false), new ((char)wch2, 0, false, false, false) + ]; HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); return; } - else - { - // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. - if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) - { - k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask)); - } + //else if (wch2 == Curses.KeyCSI) + //{ + // ConsoleKeyInfo [] cki = + // { + // new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false) + // }; + // HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki); - if (wch2 == 0) - { - k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space; - } - else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) - { - k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space; - } - else if (wch2 < 256) - { - k = (KeyCode)wch2; // | KeyCode.AltMask; - } - else - { - k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2); - } - } + // return; + //} + //else + //{ + // // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa. + // if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) + // { + // k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask)); + // } + + // if (wch2 == 0) + // { + // k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space; + // } + // //else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) + // //{ + // // k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space; + // //} + // else if (wch2 < 256) + // { + // k = (KeyCode)wch2; // | KeyCode.AltMask; + // } + // else + // { + // k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2); + // } + //} key = new Key (k); } @@ -584,6 +595,13 @@ internal class CursesDriver : ConsoleDriver OnKeyDown (new Key (k)); OnKeyUp (new Key (k)); } + else if (wch == 127) + { + // Backspace needed for macOS + k = KeyCode.Backspace; + OnKeyDown (new Key (k)); + OnKeyUp (new Key (k)); + } else { // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa. @@ -611,7 +629,8 @@ internal class CursesDriver : ConsoleDriver } // Strip the KeyCode.Space flag off if it's set - if (k != KeyCode.Space && k.HasFlag (KeyCode.Space)) + //if (k != KeyCode.Space && k.HasFlag (KeyCode.Space)) + if (Key.GetIsKeyCodeAtoZ (k) && (k & KeyCode.Space) != 0) { k &= ~KeyCode.Space; } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs index 0b9d358cb..5700b779f 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs @@ -106,7 +106,6 @@ public partial class Curses public const int KeyPPage = 0x153; public const int KeyHome = 0x106; public const int KeyMouse = 0x199; - public const int KeyCSI = 0x5b; public const int KeyEnd = 0x168; public const int KeyDeleteChar = 0x14a; public const int KeyInsertChar = 0x14b; diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 7c334b5d2..a764c083c 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -195,6 +195,7 @@ public static class EscSeqUtils buttonState = new List { 0 }; pos = default (Point); isResponse = false; + char keyChar = '\0'; switch (c1Control) { @@ -242,10 +243,10 @@ public static class EscSeqUtils break; case "SS3": - key = GetConsoleKey (terminator [0], values [0], ref mod); + key = GetConsoleKey (terminator [0], values [0], ref mod, ref keyChar); newConsoleKeyInfo = new ConsoleKeyInfo ( - '\0', + keyChar, key, (mod & ConsoleModifiers.Shift) != 0, (mod & ConsoleModifiers.Alt) != 0, @@ -271,7 +272,7 @@ public static class EscSeqUtils if (!string.IsNullOrEmpty (terminator)) { - key = GetConsoleKey (terminator [0], values [0], ref mod); + key = GetConsoleKey (terminator [0], values [0], ref mod, ref keyChar); if (key != 0 && values.Length > 1) { @@ -279,7 +280,7 @@ public static class EscSeqUtils } newConsoleKeyInfo = new ConsoleKeyInfo ( - '\0', + keyChar, key, (mod & ConsoleModifiers.Shift) != 0, (mod & ConsoleModifiers.Alt) != 0, @@ -344,13 +345,23 @@ public static class EscSeqUtils /// The value. /// The which may changes. /// The and probably the . - public static ConsoleKey GetConsoleKey (char terminator, string? value, ref ConsoleModifiers mod) + public static ConsoleKey GetConsoleKey (char terminator, string? value, ref ConsoleModifiers mod, ref char keyChar) { if (terminator == 'Z') { mod |= ConsoleModifiers.Shift; } + if (terminator == 'l') + { + keyChar = '+'; + } + + if (terminator == 'm') + { + keyChar = '-'; + } + return (terminator, value) switch { ('A', _) => ConsoleKey.UpArrow, @@ -376,6 +387,18 @@ public static class EscSeqUtils ('~', "21") => ConsoleKey.F10, ('~', "23") => ConsoleKey.F11, ('~', "24") => ConsoleKey.F12, + ('l', _) => ConsoleKey.Add, + ('m', _) => ConsoleKey.Subtract, + ('p', _) => ConsoleKey.Insert, + ('q', _) => ConsoleKey.End, + ('r', _) => ConsoleKey.DownArrow, + ('s', _) => ConsoleKey.PageDown, + ('t', _) => ConsoleKey.LeftArrow, + ('u', _) => ConsoleKey.Clear, + ('v', _) => ConsoleKey.RightArrow, + ('w', _) => ConsoleKey.Home, + ('x', _) => ConsoleKey.UpArrow, + ('y', _) => ConsoleKey.PageUp, (_, _) => 0 }; } diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 08b045f81..9111f6bc2 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -136,6 +136,8 @@ public class MenuBar : View // TODO: Why do we have two keybindings for opening the menu? Ctrl-Space and Key? KeyBindings.Add (Key.Space.WithCtrl, keyBinding); + // This is needed for macOS because Key.Space.WithCtrl doesn't work + KeyBindings.Add (Key.Space.WithAlt, keyBinding); // TODO: Figure out how to make Alt work (on Windows) //KeyBindings.Add (Key.WithAlt, keyBinding); diff --git a/UnitTests/Input/EscSeqUtilsTests.cs b/UnitTests/Input/EscSeqUtilsTests.cs index f947f2bb4..3ac8e4c60 100644 --- a/UnitTests/Input/EscSeqUtilsTests.cs +++ b/UnitTests/Input/EscSeqUtilsTests.cs @@ -968,32 +968,45 @@ public class EscSeqUtilsTests public void GetConsoleKey_Tests () { ConsoleModifiers mod = 0; - Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('A', "", ref mod)); - Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('B', "", ref mod)); - Assert.Equal (_key = ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('C', "", ref mod)); - Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('D', "", ref mod)); - Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('F', "", ref mod)); - Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('H', "", ref mod)); - Assert.Equal (ConsoleKey.F1, EscSeqUtils.GetConsoleKey ('P', "", ref mod)); - Assert.Equal (ConsoleKey.F2, EscSeqUtils.GetConsoleKey ('Q', "", ref mod)); - Assert.Equal (ConsoleKey.F3, EscSeqUtils.GetConsoleKey ('R', "", ref mod)); - Assert.Equal (ConsoleKey.F4, EscSeqUtils.GetConsoleKey ('S', "", ref mod)); - Assert.Equal (ConsoleKey.Tab, EscSeqUtils.GetConsoleKey ('Z', "", ref mod)); + char keyChar = '\0'; + Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('A', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('B', "", ref mod, ref keyChar)); + Assert.Equal (_key = ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('C', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('D', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('F', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('H', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F1, EscSeqUtils.GetConsoleKey ('P', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F2, EscSeqUtils.GetConsoleKey ('Q', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F3, EscSeqUtils.GetConsoleKey ('R', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F4, EscSeqUtils.GetConsoleKey ('S', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Tab, EscSeqUtils.GetConsoleKey ('Z', "", ref mod, ref keyChar)); Assert.Equal (ConsoleModifiers.Shift, mod); - Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('\0', "", ref mod)); - Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('~', "2", ref mod)); - Assert.Equal (ConsoleKey.Delete, EscSeqUtils.GetConsoleKey ('~', "3", ref mod)); - Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('~', "5", ref mod)); - Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('~', "6", ref mod)); - Assert.Equal (ConsoleKey.F5, EscSeqUtils.GetConsoleKey ('~', "15", ref mod)); - Assert.Equal (ConsoleKey.F6, EscSeqUtils.GetConsoleKey ('~', "17", ref mod)); - Assert.Equal (ConsoleKey.F7, EscSeqUtils.GetConsoleKey ('~', "18", ref mod)); - Assert.Equal (ConsoleKey.F8, EscSeqUtils.GetConsoleKey ('~', "19", ref mod)); - Assert.Equal (ConsoleKey.F9, EscSeqUtils.GetConsoleKey ('~', "20", ref mod)); - Assert.Equal (ConsoleKey.F10, EscSeqUtils.GetConsoleKey ('~', "21", ref mod)); - Assert.Equal (ConsoleKey.F11, EscSeqUtils.GetConsoleKey ('~', "23", ref mod)); - Assert.Equal (ConsoleKey.F12, EscSeqUtils.GetConsoleKey ('~', "24", ref mod)); - Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('~', "", ref mod)); + Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('\0', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('~', "2", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Delete, EscSeqUtils.GetConsoleKey ('~', "3", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('~', "5", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('~', "6", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F5, EscSeqUtils.GetConsoleKey ('~', "15", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F6, EscSeqUtils.GetConsoleKey ('~', "17", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F7, EscSeqUtils.GetConsoleKey ('~', "18", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F8, EscSeqUtils.GetConsoleKey ('~', "19", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F9, EscSeqUtils.GetConsoleKey ('~', "20", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F10, EscSeqUtils.GetConsoleKey ('~', "21", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F11, EscSeqUtils.GetConsoleKey ('~', "23", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.F12, EscSeqUtils.GetConsoleKey ('~', "24", ref mod, ref keyChar)); + Assert.Equal (0, (int)EscSeqUtils.GetConsoleKey ('~', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Add, EscSeqUtils.GetConsoleKey ('l', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Subtract, EscSeqUtils.GetConsoleKey ('m', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Insert, EscSeqUtils.GetConsoleKey ('p', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.End, EscSeqUtils.GetConsoleKey ('q', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.DownArrow, EscSeqUtils.GetConsoleKey ('r', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageDown, EscSeqUtils.GetConsoleKey ('s', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.LeftArrow, EscSeqUtils.GetConsoleKey ('t', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Clear, EscSeqUtils.GetConsoleKey ('u', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.RightArrow, EscSeqUtils.GetConsoleKey ('v', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.Home, EscSeqUtils.GetConsoleKey ('w', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.UpArrow, EscSeqUtils.GetConsoleKey ('x', "", ref mod, ref keyChar)); + Assert.Equal (ConsoleKey.PageUp, EscSeqUtils.GetConsoleKey ('y', "", ref mod, ref keyChar)); } [Fact] From cb2405cd946c983b3f9bd2930ee641bf3738e4fb Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 16 Jun 2024 00:15:09 +0100 Subject: [PATCH 31/85] Fix scenario from throwing exception. --- UICatalog/Scenarios/Buttons.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index d8e612ae5..d22704e5d 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -14,6 +14,8 @@ public class Buttons : Scenario { public override void Main () { + Application.Init (); + Window main = new () { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" From 08821101fd11e0de7a19a920287bfe8a76c79922 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 16 Jun 2024 00:27:20 +0100 Subject: [PATCH 32/85] Fix XML doc. --- Terminal.Gui/Application/Application.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index c66f70cfb..f1b161118 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -183,7 +183,7 @@ public static partial class Application /// /// /// The function combines - /// and + /// and /// into a single /// call. An application cam use without explicitly calling /// . @@ -344,7 +344,7 @@ public static partial class Application /// Shutdown an application initialized with . /// /// Shutdown must be called for every call to or - /// to ensure all resources are cleaned + /// to ensure all resources are cleaned /// up (Disposed) /// and terminal settings are restored. /// @@ -639,7 +639,7 @@ public static partial class Application /// /// Runs the application by creating a object and calling - /// . + /// . /// /// /// Calling first is not needed as this function will initialize the application. @@ -656,7 +656,7 @@ public static partial class Application /// /// Runs the application by creating a -derived object of type T and calling - /// . + /// . /// /// /// Calling first is not needed as this function will initialize the application. @@ -698,11 +698,11 @@ public static partial class Application /// modal s such as boxes. /// /// - /// To make a stop execution, call + /// To make a stop execution, call /// . /// /// - /// Calling is equivalent to calling + /// Calling is equivalent to calling /// , followed by , and then calling /// . /// @@ -996,7 +996,7 @@ public static partial class Application /// Stops the provided , causing or the if provided. /// The to stop. /// - /// This will cause to return. + /// This will cause to return. /// /// Calling is equivalent to setting the /// property on the currently running to false. From 44ae053487e4af8c10dc5d3e53154b867592db81 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 16 Jun 2024 00:31:46 +0100 Subject: [PATCH 33/85] Fix XML doc. --- Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index a764c083c..f9c547af6 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -343,7 +343,8 @@ public static class EscSeqUtils /// . /// /// The value. - /// The which may changes. + /// The which may change. + /// Normally is '\0' but on some cases may need other value. /// The and probably the . public static ConsoleKey GetConsoleKey (char terminator, string? value, ref ConsoleModifiers mod, ref char keyChar) { From 9e6a00bf3a497e07fa5ac939f60dca191a7aa4d4 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 12:51:40 -0700 Subject: [PATCH 34/85] StatusBar conversion progress. Fixed CSvEditor --- Terminal.Gui/Views/Bar.cs | 109 +++++++++--- Terminal.Gui/Views/Shortcut.cs | 164 +++++++++-------- Terminal.Gui/Views/StatusBar.cs | 52 ++---- UICatalog/Scenarios/Bars.cs | 2 +- UICatalog/Scenarios/ConfigurationEditor.cs | 16 +- UICatalog/Scenarios/CsvEditor.cs | 198 +++++++++++---------- UICatalog/Scenarios/Editor.cs | 27 ++- UICatalog/Scenarios/Shortcuts.cs | 11 +- UICatalog/UICatalog.cs | 20 +-- 9 files changed, 319 insertions(+), 280 deletions(-) diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 2b1c11c26..f69579417 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -12,7 +12,10 @@ namespace Terminal.Gui; public class Bar : View { /// - public Bar () + public Bar () : this ([]) { } + + /// + public Bar (IEnumerable shortcuts) { CanFocus = true; @@ -23,12 +26,16 @@ public class Bar : View Initialized += Bar_Initialized; + foreach (Shortcut shortcut in shortcuts) + { + Add (shortcut); + } } private void Bar_Initialized (object sender, EventArgs e) { ColorScheme = Colors.ColorSchemes ["Menu"]; - AdjustSubviewBorders(); + AdjustSubviewBorders (); } /// @@ -44,7 +51,10 @@ public class Bar : View /// public Orientation Orientation { get; set; } = Orientation.Horizontal; - + /// + /// Gets or sets the for this . The default is . + /// + public AlignmentModes AlignmentModes { get; set; } = AlignmentModes.StartToEnd; public bool StatusBarStyle { get; set; } = true; @@ -52,7 +62,6 @@ public class Bar : View { base.Add (view); AdjustSubviewBorders (); - } /// @@ -62,6 +71,49 @@ public class Bar : View AdjustSubviewBorders (); } + + /// Inserts a in the specified index of . + /// The zero-based index at which item should be inserted. + /// The item to insert. + public void AddShortcutAt (int index, Shortcut item) + { + List savedSubViewList = Subviews.ToList (); + int count = savedSubViewList.Count; + RemoveAll (); + for (int i = 0; i < count; i++) + { + if (i == index) + { + Add (item); + } + Add (savedSubViewList [i]); + } + SetNeedsDisplay (); + } + + /// Removes a at specified index of . + /// The zero-based index of the item to remove. + /// The removed. + public Shortcut RemoveShortcut (int index) + { + View toRemove = null; + for (int i = 0; i < Subviews.Count; i++) + { + if (i == index) + { + toRemove = Subviews [i]; + } + } + + if (toRemove is { }) + { + Remove (toRemove); + SetNeedsDisplay (); + } + + return toRemove as Shortcut; + } + private void AdjustSubviewBorders () { for (var index = 0; index < Subviews.Count; index++) @@ -72,7 +124,6 @@ public class Bar : View barItem.SuperViewRendersLineCanvas = true; barItem.ColorScheme = ColorScheme; - if (!barItem.Visible) { continue; @@ -80,18 +131,20 @@ public class Bar : View if (StatusBarStyle) { - if (index == 0) - { - barItem.Border.Thickness = new Thickness (0, 0, 1, 0); - } + barItem.BorderStyle = LineStyle.Dashed; if (index == Subviews.Count - 1) { barItem.Border.Thickness = new Thickness (0, 0, 0, 0); } + else + { + barItem.Border.Thickness = new Thickness (0, 0, 1, 0); + } } else { + barItem.BorderStyle = LineStyle.None; if (index == 0) { barItem.Border.Thickness = new Thickness (1, 1, 1, 0); @@ -121,25 +174,33 @@ public class Bar : View continue; } - if (StatusBarStyle) + //if (StatusBarStyle) + //{ + // barItem.BorderStyle = LineStyle.Dashed; + //} + //else + //{ + // barItem.BorderStyle = LineStyle.None; + //} + + //if (index == Subviews.Count - 1) + //{ + // barItem.Border.Thickness = new Thickness (0, 0, 0, 0); + //} + //else + //{ + // barItem.Border.Thickness = new Thickness (0, 0, 1, 0); + //} + + if (barItem is Shortcut shortcut) { - barItem.BorderStyle = LineStyle.Dashed; + shortcut.X = Pos.Align (Alignment.Start, AlignmentModes); } else { - barItem.BorderStyle = LineStyle.None; + barItem.X = Pos.Align (Alignment.Start, AlignmentModes); } - - if (index == Subviews.Count - 1) - { - barItem.Border.Thickness = new Thickness (0, 0, 0, 0); - } - else - { - barItem.Border.Thickness = new Thickness (0, 0, 1, 0); - } - - barItem.X = Pos.Align (Alignment.Start, StatusBarStyle ? AlignmentModes.IgnoreFirstOrLast : 0); + barItem.Y = Pos.Center (); prevBarItem = barItem; } @@ -189,7 +250,7 @@ public class Bar : View else { // Align the view to the bottom of the previous view - barItem.Y = Pos.Bottom(prevBarItem); + barItem.Y = Pos.Bottom (prevBarItem); } prevBarItem = barItem; diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 4b4c24d79..348a21029 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -53,14 +53,18 @@ public enum ShortcutStyles /// public class Shortcut : View { + /// - /// Creates a new instance of . + /// Creates a new instance of ; /// - public Shortcut () + /// + /// + /// + public Shortcut (Key key, string commandText, Action action, string helpText = null) { Id = "_shortcut"; - //HighlightStyle = HighlightStyle.Pressed; - //Highlight += Shortcut_Highlight; + HighlightStyle = HighlightStyle.Pressed; + Highlight += Shortcut_Highlight; CanFocus = true; Width = GetWidthDimAuto (); Height = Dim.Auto (DimAutoStyle.Content, 1); @@ -80,23 +84,24 @@ public class Shortcut : View HelpView.Id = "_helpView"; HelpView.CanFocus = false; - SetHelpViewDefaultLayout (); + HelpView.Text = helpText; Add (HelpView); KeyView.Id = "_keyView"; KeyView.CanFocus = false; - SetKeyViewDefaultLayout (); Add (KeyView); // If the user clicks anywhere on the Shortcut, other than the CommandView, invoke the Command MouseClick += Shortcut_MouseClick; HelpView.MouseClick += Shortcut_MouseClick; KeyView.MouseClick += Shortcut_MouseClick; - LayoutStarted += OnLayoutStarted; - Initialized += OnInitialized; + Key = key; + Title = commandText; + Action = action; + return; void OnInitialized (object sender, EventArgs e) @@ -112,17 +117,26 @@ public class Shortcut : View _minimumDimAutoWidth = Frame.Width; Width = savedDim; - //SetColorScheme (); + SetCommandViewDefaultLayout (); + SetHelpViewDefaultLayout (); + SetKeyViewDefaultLayout (); + + SetColorScheme (); } // Helper to set Width consistently Dim GetWidthDimAuto () { // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. - return Dim.Auto (DimAutoStyle.Content, maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); + return Dim.Auto (DimAutoStyle.Content, minimumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)), maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); } } + /// + /// Creates a new instance of . + /// + public Shortcut () : this (Gui.Key.Empty, string.Empty, null) { } + /// /// Gets or sets the for this . The default is /// , which is ideal for status bars and toolbars. If set to , @@ -130,6 +144,23 @@ public class Shortcut : View /// public Orientation Orientation { get; set; } = Orientation.Horizontal; + private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; + + /// + /// Gets or sets the for this . The default is . + /// + public AlignmentModes AlignmentModes + { + get => _alignmentModes; + set + { + _alignmentModes = value; + SetCommandViewDefaultLayout (); + SetHelpViewDefaultLayout (); + SetKeyViewDefaultLayout (); + } + } + public ShortcutStyles ShortcutStyle { get; set; } = ShortcutStyles.None; // When one of the subviews is "empty" we don't want to show it. So we @@ -211,7 +242,7 @@ public class Shortcut : View if (maxHelpWidth > 0) { - HelpView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + HelpView.X = Pos.Align (Alignment.End, AlignmentModes); // Leverage Dim.Auto's max: HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: maxHelpWidth); @@ -221,7 +252,9 @@ public class Shortcut : View else { // Reset to default + //SetCommandViewDefaultLayout(); SetHelpViewDefaultLayout (); + //SetKeyViewDefaultLayout (); } } } @@ -246,23 +279,23 @@ public class Shortcut : View { if (!_savedForeColor.HasValue) { - _savedForeColor = ColorScheme.Normal.Foreground; + _savedForeColor = base.ColorScheme.Normal.Foreground; } - var cs = new ColorScheme (ColorScheme) + var cs = new ColorScheme (base.ColorScheme) { - Normal = new (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background) + Normal = new (ColorScheme.Normal.Foreground.GetHighlightColor (), base.ColorScheme.Normal.Background) }; - ColorScheme = cs; + base.ColorScheme = cs; } if (e.HighlightStyle == HighlightStyle.None && _savedForeColor.HasValue) { - var cs = new ColorScheme (ColorScheme) + var cs = new ColorScheme (base.ColorScheme) { - Normal = new (_savedForeColor.Value, ColorScheme.Normal.Background) + Normal = new (_savedForeColor.Value, base.ColorScheme.Normal.Background) }; - ColorScheme = cs; + base.ColorScheme = cs; } SuperView?.SetNeedsDisplay (); @@ -365,29 +398,8 @@ public class Shortcut : View _commandView = value; _commandView.Id = "_commandView"; - // TODO: Determine if it makes sense to allow the CommandView to be focusable. - // Right now, we don't set CanFocus to false here. - //_commandView.CanFocus = true; - - //// Bar will set the width of all CommandViews to the width of the widest CommandViews. - ////if (_commandView.Width == Dim.Absolute(0)) - //{ - // _commandView.Width = Dim.Auto (); - //} - - ////if (_commandView.Height == Dim.Absolute (0)) - //{ - // _commandView.Height = Dim.Auto (); - //} - - _commandView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); - _commandView.Y = 0; //Pos.Center (); - _commandView.MouseClick += Shortcut_MouseClick; _commandView.Accept += CommandViewAccept; - - _commandView.Margin.Thickness = GetMarginThickness (); - _commandView.HotKeyChanged += (s, e) => { if (e.NewKey != Key.Empty) @@ -401,6 +413,7 @@ public class Shortcut : View Title = _commandView.Text; + SetCommandViewDefaultLayout (); SetHelpViewDefaultLayout (); SetKeyViewDefaultLayout (); ShowHide (); @@ -431,6 +444,14 @@ public class Shortcut : View } } + private void SetCommandViewDefaultLayout () + { + CommandView.Margin.Thickness = GetMarginThickness (); + CommandView.X = Pos.Align (Alignment.End, AlignmentModes); + CommandView.Y = 0; //Pos.Center (), + } + + private void Shortcut_TitleChanged (object sender, StateEventArgs e) { // If the Title changes, update the CommandView text. @@ -451,7 +472,7 @@ public class Shortcut : View private void SetHelpViewDefaultLayout () { HelpView.Margin.Thickness = GetMarginThickness (); - HelpView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + HelpView.X = Pos.Align (Alignment.End, AlignmentModes); HelpView.Y = 0; //Pos.Center (), HelpView.Width = Dim.Auto (DimAutoStyle.Text); HelpView.Height = CommandView?.IsAdded == true ? Dim.Height (CommandView) : 1; @@ -546,6 +567,7 @@ public class Shortcut : View private int _minimumKeyViewSize; + /// /// public int MinimumKeyViewSize @@ -572,7 +594,7 @@ public class Shortcut : View private void SetKeyViewDefaultLayout () { KeyView.Margin.Thickness = GetMarginThickness (); - KeyView.X = Pos.Align (Alignment.End, AlignmentModes.IgnoreFirstOrLast); + KeyView.X = Pos.Align (Alignment.End, AlignmentModes); //KeyView.Y = Pos.Center (); KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); KeyView.Height = CommandView?.IsAdded == true ? Dim.Height (CommandView) : 1; @@ -631,25 +653,31 @@ public class Shortcut : View break; } - if (AcceptAction is null) - { - AcceptAction = () => - { - var args = new HandledEventArgs (); - Accept?.Invoke (this, args); - }; - } if (handled == false) { - AcceptAction.Invoke(); + var args = new HandledEventArgs (); + Accept?.Invoke (this, args); + + if (args.Handled is false) + { + Action?.Invoke (); + } + + args.Handled = true; } return true; } + /// + /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the mouse. + /// + /// + /// Note, the event is fired first, and if cancelled, will not be invoked. + /// [CanBeNull] - public Action AcceptAction { get; set; } + public Action Action { get; set; } #endregion Accept Handling @@ -673,7 +701,7 @@ public class Shortcut : View if (HasFocus) { - // When we have focus, we invert the SuperView's colors + // When we have focus, we invert the colors base.ColorScheme = new (base.ColorScheme) { Normal = base.ColorScheme.Focus, @@ -687,34 +715,16 @@ public class Shortcut : View base.ColorScheme = SuperView?.ColorScheme; } - //// If the command view is focusable, invert the focus colors - if (CommandView.CanFocus) + // Set KeyView's colors to show "hot" + if (IsInitialized) { - ColorScheme commandViewCS = new (base.ColorScheme) + var cs = new ColorScheme (base.ColorScheme) { - Normal = base.ColorScheme.Focus, - HotNormal = base.ColorScheme.HotFocus, - HotFocus = base.ColorScheme.HotNormal, - Focus = base.ColorScheme.Normal + Normal = base.ColorScheme.HotNormal, + HotNormal = base.ColorScheme.Normal }; - CommandView.ColorScheme = commandViewCS; + KeyView.ColorScheme = cs; } - else - { - CommandView.ColorScheme = base.ColorScheme; - } - - //HelpView.ColorScheme = base.ColorScheme; - - //// Set KeyView's colors to show "hot" - //var cs = new ColorScheme (ColorScheme) - //{ - // Normal = base.ColorScheme.HotNormal, - // HotNormal = base.ColorScheme.Normal - //}; - - //KeyView.ColorScheme = cs; - } /// @@ -727,8 +737,6 @@ public class Shortcut : View /// public override bool OnLeave (View view) { - // Reset the color scheme (to SuperView). - //ColorScheme = null; SetColorScheme (); return base.OnLeave (view); } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index ba7f64fad..e5c26baff 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -9,60 +9,28 @@ namespace Terminal.Gui; /// public class StatusBar : Bar { + /// + public StatusBar () : this ([]) { } - public StatusBar () + /// + public StatusBar (IEnumerable shortcuts) : base (shortcuts) { Orientation = Orientation.Horizontal; Y = Pos.AnchorEnd (); Width = Dim.Fill (); + StatusBarStyle = true; } /// public override void Add (View view) { view.CanFocus = false; + if (view is Shortcut shortcut) + { + shortcut.KeyBindingScope = KeyBindingScope.Application; + shortcut.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; + } base.Add (view); } - /// Inserts a in the specified index of . - /// The zero-based index at which item should be inserted. - /// The item to insert. - public void AddShortcutAt (int index, Shortcut item) - { - List savedSubViewList = Subviews.ToList (); - int count = savedSubViewList.Count; - RemoveAll (); - for (int i = 0; i < count; i++) - { - if (i == index) - { - Add (item); - } - Add (savedSubViewList [i]); - } - SetNeedsDisplay (); - } - - /// Removes a at specified index of . - /// The zero-based index of the item to remove. - /// The removed. - public Shortcut RemoveItem (int index) - { - View toRemove = null; - for (int i = 0; i < Subviews.Count; i++) - { - if (i == index) - { - toRemove = Subviews [i]; - } - } - - if (toRemove is { }) - { - Remove (toRemove); - SetNeedsDisplay (); - } - - return toRemove as Shortcut; - } } diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index c0449572a..9c310dfa2 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -135,7 +135,7 @@ public class Bars : Scenario // SetupMenuBar (); //SetupContentMenu (); - // SetupStatusBar (); + SetupStatusBar (); foreach (Bar barView in Application.Top.Subviews.Where (b => b is Bar)!) { diff --git a/UICatalog/Scenarios/ConfigurationEditor.cs b/UICatalog/Scenarios/ConfigurationEditor.cs index 99b664bd9..1e095a527 100644 --- a/UICatalog/Scenarios/ConfigurationEditor.cs +++ b/UICatalog/Scenarios/ConfigurationEditor.cs @@ -41,6 +41,7 @@ public class ConfigurationEditor : Scenario Application.Init (); Toplevel top = new (); + _tileView = new TileView (0) { Width = Dim.Fill (), Height = Dim.Fill (1), Orientation = Orientation.Vertical, LineStyle = LineStyle.Single @@ -57,25 +58,24 @@ public class ConfigurationEditor : Scenario { Key = Application.QuitKey, Title = $"{Application.QuitKey} Quit", - AcceptAction = Quit + Action = Quit }; var reloadShortcut = new Shortcut () { - Key = KeyCode.F5, + Key = Key.F5.WithShift, Title = "Reload", - AcceptAction = Reload }; + reloadShortcut.Accept += (s, e) => { Reload (); }; + var saveShortcut = new Shortcut () { - Key = Key.S.WithCtrl, + Key = Key.F4, Title = "Save", - AcceptAction = Save + Action = Save }; - - var statusBar = new StatusBar (); - statusBar.Add (quitShortcut, reloadShortcut, saveShortcut, _lenShortcut); + var statusBar = new StatusBar ([quitShortcut, reloadShortcut, saveShortcut, _lenShortcut]); top.Add (statusBar); diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index c230f5259..fe56cc658 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -1,11 +1,11 @@ using System; -using System.Collections.Generic; using System.Data; using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; using CsvHelper; +using SixLabors.ImageSharp.ColorSpaces; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -26,16 +26,23 @@ public class CsvEditor : Scenario private MenuItem _miCentered; private MenuItem _miLeft; private MenuItem _miRight; - private TextField _selectedCellLabel; + private TextField _selectedCellTextField; private TableView _tableView; - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + // Init + Application.Init (); - _tableView = new TableView { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill (1) }; + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new () + { + Title = $"{GetName ()}" + }; + + //appWindow.Height = Dim.Fill (1); // status bar + + _tableView = new () { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (2) }; var fileMenu = new MenuBarItem ( "_File", @@ -53,99 +60,96 @@ public class CsvEditor : Scenario Menus = [ fileMenu, - new MenuBarItem ( - "_Edit", - new MenuItem [] - { - new ("_New Column", "", () => AddColumn ()), - new ("_New Row", "", () => AddRow ()), - new ( - "_Rename Column", - "", - () => RenameColumn () - ), - new ("_Delete Column", "", () => DeleteColum ()), - new ("_Move Column", "", () => MoveColumn ()), - new ("_Move Row", "", () => MoveRow ()), - new ("_Sort Asc", "", () => Sort (true)), - new ("_Sort Desc", "", () => Sort (false)) - } - ), - new MenuBarItem ( - "_View", - new [] - { - _miLeft = new MenuItem ( - "_Align Left", - "", - () => Align (Alignment.Start) - ), - _miRight = new MenuItem ( - "_Align Right", - "", - () => Align (Alignment.End) - ), - _miCentered = new MenuItem ( - "_Align Centered", - "", - () => Align (Alignment.Center) - ), + new ( + "_Edit", + new MenuItem [] + { + new ("_New Column", "", () => AddColumn ()), + new ("_New Row", "", () => AddRow ()), + new ( + "_Rename Column", + "", + () => RenameColumn () + ), + new ("_Delete Column", "", () => DeleteColum ()), + new ("_Move Column", "", () => MoveColumn ()), + new ("_Move Row", "", () => MoveRow ()), + new ("_Sort Asc", "", () => Sort (true)), + new ("_Sort Desc", "", () => Sort (false)) + } + ), + new ( + "_View", + new [] + { + _miLeft = new ( + "_Align Left", + "", + () => Align (Alignment.Start) + ), + _miRight = new ( + "_Align Right", + "", + () => Align (Alignment.End) + ), + _miCentered = new ( + "_Align Centered", + "", + () => Align (Alignment.Center) + ), - // Format requires hard typed data table, when we read a CSV everything is untyped (string) so this only works for new columns in this demo - _miCentered = new MenuItem ( - "_Set Format Pattern", - "", - () => SetFormat () - ) - } - ) + // Format requires hard typed data table, when we read a CSV everything is untyped (string) so this only works for new columns in this demo + _miCentered = new ( + "_Set Format Pattern", + "", + () => SetFormat () + ) + } + ) ] }; - Top.Add (menu); + appWindow.Add (menu); -#if V2_STATUSBAR - var statusBar = new StatusBar ( - new StatusItem [] - { - new ( - KeyCode.CtrlMask | KeyCode.O, - "~^O~ Open", - () => Open () - ), - new ( - KeyCode.CtrlMask | KeyCode.S, - "~^S~ Save", - () => Save () - ), - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ) - } - ); - Top.Add (statusBar); -#endif - - Win.Add (_tableView); - - _selectedCellLabel = new TextField + _selectedCellTextField = new () { - X = 0, - Y = Pos.Bottom (_tableView), Text = "0,0", - Width = Dim.Fill (), - TextAlignment = Alignment.End + Width = 10, + Height = 1, }; - _selectedCellLabel.TextChanged += SelectedCellLabel_TextChanged; + _selectedCellTextField.TextChanged += SelectedCellLabel_TextChanged; - Win.Add (_selectedCellLabel); + var statusBar = new StatusBar ( + [ + new (Application.QuitKey, "Quit", Quit, "Quit!"), + new (Key.O.WithCtrl, "Open", Open, "Open a file."), + new (Key.S.WithCtrl, "Save", Save, "Save current."), + new () + { + HelpText = "Cell:", + CommandView = _selectedCellTextField, + AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast, + Enabled = false + } + ]) + { + AlignmentModes = AlignmentModes.IgnoreFirstOrLast + }; + appWindow.Add (statusBar); + + appWindow.Add (_tableView); _tableView.SelectedCellChanged += OnSelectedCellChanged; _tableView.CellActivated += EditCurrentCell; _tableView.KeyDown += TableViewKeyPress; SetupScrollBar (); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } private void AddColumn () @@ -302,10 +306,10 @@ public class CsvEditor : Scenario var ok = new Button { Text = "Ok", IsDefault = true }; ok.Accept += (s, e) => - { - okPressed = true; - Application.RequestStop (); - }; + { + okPressed = true; + Application.RequestStop (); + }; var cancel = new Button { Text = "Cancel" }; cancel.Accept += (s, e) => { Application.RequestStop (); }; var d = new Dialog { Title = title, Buttons = [ok, cancel] }; @@ -427,9 +431,9 @@ public class CsvEditor : Scenario private void OnSelectedCellChanged (object sender, SelectedCellChangedEventArgs e) { // only update the text box if the user is not manually editing it - if (!_selectedCellLabel.HasFocus) + if (!_selectedCellTextField.HasFocus) { - _selectedCellLabel.Text = $"{_tableView.SelectedRow},{_tableView.SelectedColumn}"; + _selectedCellTextField.Text = $"{_tableView.SelectedRow},{_tableView.SelectedColumn}"; } if (_tableView.Table == null || _tableView.SelectedColumn == -1) @@ -448,7 +452,7 @@ public class CsvEditor : Scenario { var ofd = new FileDialog { - AllowedTypes = new List { new AllowedType ("Comma Separated Values", ".csv") } + AllowedTypes = new () { new AllowedType ("Comma Separated Values", ".csv") } }; ofd.Style.OkButtonText = "Open"; @@ -458,6 +462,7 @@ public class CsvEditor : Scenario { Open (ofd.Path); } + ofd.Dispose (); } @@ -498,7 +503,8 @@ public class CsvEditor : Scenario // Only set the current filename if we successfully loaded the entire file _currentFile = filename; - Win.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}"; + _selectedCellTextField.SuperView.Enabled = true; + Application.Top.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}"; } catch (Exception ex) { @@ -563,13 +569,13 @@ public class CsvEditor : Scenario private void SelectedCellLabel_TextChanged (object sender, StateEventArgs e) { // if user is in the text control and editing the selected cell - if (!_selectedCellLabel.HasFocus) + if (!_selectedCellTextField.HasFocus) { return; } // change selected cell to the one the user has typed into the box - Match match = Regex.Match (_selectedCellLabel.Text, "^(\\d+),(\\d+)$"); + Match match = Regex.Match (_selectedCellTextField.Text, "^(\\d+),(\\d+)$"); if (match.Success) { diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 4d50e5199..73b14ffd0 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -237,29 +237,23 @@ public class Editor : Scenario }; _appWindow.Add (menu); -#if V2_STATUSBAR - var siCursorPosition = new StatusItem (KeyCode.Null, "", null); + var siCursorPosition = new Shortcut(KeyCode.Null, "", null); var statusBar = new StatusBar ( new [] { + new (Application.QuitKey, $"Quit", Quit), + new (Key.F2, "Open", Open), + new (Key.F3, "Save", () => Save ()), + new (Key.F4, "Save As", () => SaveAs ()), + new (Key.Empty, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null), siCursorPosition, - new (KeyCode.F2, "~F2~ Open", () => Open ()), - new (KeyCode.F3, "~F3~ Save", () => Save ()), - new (KeyCode.F4, "~F4~ Save As", () => SaveAs ()), - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ), - new ( - KeyCode.Null, - $"OS Clipboard IsSupported : {Clipboard.IsSupported}", - null - ) } - ); + ) + { + AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast + }; _textView.UnwrappedCursorPosition += (s, e) => { @@ -267,7 +261,6 @@ public class Editor : Scenario }; _appWindow.Add (statusBar); -#endif _scrollBar = new (_textView, true); diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 2c63d3d8c..26292b76d 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -261,6 +261,8 @@ public class Shortcuts : Scenario hShortcut1.CommandView.Width = 10; hShortcut1.CommandView.Height = 1; hShortcut1.CommandView.CanFocus = false; + hShortcut1.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; + Timer timer = new (10) { AutoReset = true, @@ -297,11 +299,12 @@ public class Shortcuts : Scenario X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), Y = Pos.Top (hShortcut1), Key = Key.F8, - HelpText = "Edit", + HelpText = "TextField", CanFocus = true, - BorderStyle = LineStyle.Dashed, CommandView = textField, + BorderStyle = LineStyle.Dashed, }; + hShortcut2.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; hShortcut2.Border.Thickness = new (0, 0, 1, 0); Application.Top.Add (hShortcut2); @@ -331,6 +334,7 @@ public class Shortcuts : Scenario }; hShortcutBG.CommandView = bgColor; hShortcutBG.Border.Thickness = new (1, 0, 1, 0); + hShortcutBG.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; Application.Top.Add (hShortcutBG); @@ -365,6 +369,9 @@ public class Shortcuts : Scenario }; } } + + ((CheckBox)vShortcut5.CommandView).OnToggled (); + ((CheckBox)vShortcut5.CommandView).OnToggled (); } private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 5c227c43a..ee9ab8fbc 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -347,6 +347,7 @@ internal class UICatalogApp // 'app' closed cleanly. foreach (Responder? inst in Responder.Instances) { + Debug.Assert (inst.WasDisposed); } @@ -457,14 +458,13 @@ internal class UICatalogApp StatusBar = new () { Visible = ShowStatusBar, + AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast }; Shortcut statusBarShortcut = new Shortcut () { Key = Key.F10, - KeyBindingScope = KeyBindingScope.Application, - Title = "Status Bar", - CanFocus = false, + Title = "Show/Hide Status Bar", }; statusBarShortcut.Accept += (sender, args) => { @@ -473,15 +473,13 @@ internal class UICatalogApp ShForce16Colors = new Shortcut () { - Key = Key.F6, - KeyBindingScope = KeyBindingScope.Application, - CommandView = new CheckBox() + CommandView = new CheckBox () { - Title ="16 Colors", + Title = "16 color mode", Checked = Application.Force16Colors, CanFocus = false, - }, - CanFocus = false, + }, HelpText = "", + Key = Key.F6, }; ShForce16Colors.Accept += (sender, args) => { @@ -508,8 +506,6 @@ internal class UICatalogApp { Title = "Quit", Key = Application.QuitKey, - KeyBindingScope = KeyBindingScope.Application, - CanFocus = false, }, statusBarShortcut, ShForce16Colors, @@ -542,7 +538,7 @@ internal class UICatalogApp X = Pos.Right (CategoryList) - 1, Y = 1, Width = Dim.Fill (), - Height = Dim.Height(CategoryList), + Height = Dim.Height (CategoryList), //AllowsMarking = false, CanFocus = true, From 3f472c673a2f5ab3c708d3737a5768084f77dad8 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 13:13:51 -0700 Subject: [PATCH 35/85] StatusBar conversion progress. Fixed GraphView --- UICatalog/Scenarios/CsvEditor.cs | 3 +- UICatalog/Scenarios/GraphViewExample.cs | 73 +++++++++++++++---------- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/UICatalog/Scenarios/CsvEditor.cs b/UICatalog/Scenarios/CsvEditor.cs index fe56cc658..b3d734ad1 100644 --- a/UICatalog/Scenarios/CsvEditor.cs +++ b/UICatalog/Scenarios/CsvEditor.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using CsvHelper; -using SixLabors.ImageSharp.ColorSpaces; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -114,7 +113,7 @@ public class CsvEditor : Scenario { Text = "0,0", Width = 10, - Height = 1, + Height = 1 }; _selectedCellTextField.TextChanged += SelectedCellLabel_TextChanged; diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index 3798b26c3..73f718615 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Terminal.Gui; +using static System.Net.Mime.MediaTypeNames; +using Application = Terminal.Gui.Application; namespace UICatalog.Scenarios; @@ -18,12 +20,10 @@ public class GraphViewExample : Scenario private GraphView _graphView; private MenuItem _miDiags; private MenuItem _miShowBorder; - - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + Application.Init (); + Toplevel app = new (); _graphs = new [] { @@ -117,9 +117,9 @@ public class GraphViewExample : Scenario .Checked }, _miDiags = new MenuItem ( - "Dri_ver Diagnostics", + "_Diagnostics", "", - () => EnableDiagnostics () + () => ToggleDiagnostics () ) { Checked = View.Diagnostics @@ -133,28 +133,28 @@ public class GraphViewExample : Scenario ) ] }; - Top.Add (menu); + app.Add (menu); _graphView = new GraphView { X = 0, - Y = 0, + Y = 1, Width = Dim.Percent (70), - Height = Dim.Fill (), + Height = Dim.Fill (1), BorderStyle = LineStyle.Single }; _graphView.Border.Thickness = _thickness; _graphView.Margin.Thickness = _thickness; _graphView.Padding.Thickness = _thickness; - Win.Add (_graphView); + app.Add (_graphView); var frameRight = new FrameView { - X = Pos.Right (_graphView) + 1, - Y = 0, + X = Pos.Right (_graphView), + Y = Pos.Top (_graphView), Width = Dim.Fill (), - Height = Dim.Fill (), + Height = Dim.Height (_graphView), Title = "About" }; @@ -162,29 +162,42 @@ public class GraphViewExample : Scenario _about = new TextView { Width = Dim.Fill (), Height = Dim.Fill () } ); - Win.Add (frameRight); + app.Add (frameRight); var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] + new Shortcut [] { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ), - new ( - KeyCode.CtrlMask | KeyCode.G, - "~^G~ Next", - () => _graphs [_currentGraph++ % _graphs.Length] () - ) + new (Key.G.WithCtrl, "Next Graph", () => _graphs [_currentGraph++ % _graphs.Length] ()), + new (Key.CursorUp, "Zoom In", () => Zoom (0.5f)), + new (Key.CursorDown, "Zoom Out", () => Zoom (2f)), } -#endif ); - Top.Add (statusBar); + app.Add (statusBar); + + var diagShortcut = new Shortcut () + { + Key = Key.F10, + CommandView = new CheckBox () + { + Title = "Diagnostics", + CanFocus = false + } + }; + diagShortcut.Accept += DiagShortcut_Accept; + statusBar.Add (diagShortcut); + + _graphs [_currentGraph++ % _graphs.Length] (); + Application.Run (app); + app.Dispose (); + Application.Shutdown (); } - private void EnableDiagnostics () + private void DiagShortcut_Accept (object sender, System.ComponentModel.HandledEventArgs e) + { + ToggleDiagnostics(); + } + + private void ToggleDiagnostics () { _miDiags.Checked = !_miDiags.Checked; From 713c5f112d1a58f05a50e0a7a8d5d2f82ff7d17a Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 13:34:02 -0700 Subject: [PATCH 36/85] View.Add returns added view --- Terminal.Gui/View/ViewSubViews.cs | 8 ++++++-- Terminal.Gui/Views/Bar.cs | 4 +++- Terminal.Gui/Views/ScrollView.cs | 3 ++- Terminal.Gui/Views/Shortcut.cs | 20 +++----------------- Terminal.Gui/Views/StatusBar.cs | 4 ++-- Terminal.Gui/Views/Toplevel.cs | 4 ++-- Terminal.Gui/Views/Wizard/WizardStep.cs | 4 +++- UICatalog/Scenarios/Bars.cs | 2 +- UICatalog/Scenarios/GraphViewExample.cs | 16 +++++++++++++--- UICatalog/Scenarios/Shortcuts.cs | 16 ++++++++-------- 10 files changed, 43 insertions(+), 38 deletions(-) diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 05d332e30..8369f12ea 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -40,11 +40,13 @@ public partial class View /// the lifecycle of the subviews to be transferred to this View. /// /// - public virtual void Add (View view) + /// The view to add. + /// The view that was added. + public virtual View Add (View view) { if (view is null) { - return; + return view; } if (_subviews is null) @@ -94,6 +96,8 @@ public partial class View CheckDimAuto (); SetNeedsLayout (); SetNeedsDisplay (); + + return view; } /// Adds the specified views (children) to the view. diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index f69579417..b25f0127a 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -58,10 +58,12 @@ public class Bar : View public bool StatusBarStyle { get; set; } = true; - public override void Add (View view) + public override View Add (View view) { base.Add (view); AdjustSubviewBorders (); + + return view; } /// diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 42a226544..bebe2936a 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -346,7 +346,7 @@ public class ScrollView : View /// Adds the view to the scrollview. /// The view to add to the scrollview. - public override void Add (View view) + public override View Add (View view) { if (view is ScrollBarView.ContentBottomRightCorner) { @@ -365,6 +365,7 @@ public class ScrollView : View } SetNeedsLayout (); + return view; } /// diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 348a21029..ff35a7159 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -431,12 +431,9 @@ public class Shortcut : View { // When the CommandView fires its Accept event, we want to act as though the // Shortcut was clicked. - var args = new HandledEventArgs (); - Accept?.Invoke (this, args); - - if (args.Handled) + if (base.OnAccept() == true) { - e.Cancel = args.Handled; + e.Cancel = true; } //e.Cancel = true; @@ -620,12 +617,6 @@ public class Shortcut : View #region Accept Handling - /// - /// The event fired when the command is received. This - /// occurs if the user clicks on the Shortcut or presses . - /// - public new event EventHandler Accept; - /// /// Called when the command is received. This /// occurs if the user clicks on the Bar with the mouse or presses the key bound to @@ -656,15 +647,10 @@ public class Shortcut : View if (handled == false) { - var args = new HandledEventArgs (); - Accept?.Invoke (this, args); - - if (args.Handled is false) + if (base.OnAccept () is false) { Action?.Invoke (); } - - args.Handled = true; } return true; diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index e5c26baff..966f2d59a 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -22,7 +22,7 @@ public class StatusBar : Bar } /// - public override void Add (View view) + public override View Add (View view) { view.CanFocus = false; if (view is Shortcut shortcut) @@ -30,7 +30,7 @@ public class StatusBar : Bar shortcut.KeyBindingScope = KeyBindingScope.Application; shortcut.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; } - base.Add (view); + return base.Add (view); } } diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 117599efb..5ca39c24d 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -186,11 +186,11 @@ public partial class Toplevel : View public event EventHandler Activate; /// - public override void Add (View view) + public override View Add (View view) { CanFocus = true; AddMenuStatusBar (view); - base.Add (view); + return base.Add (view); } /// diff --git a/Terminal.Gui/Views/Wizard/WizardStep.cs b/Terminal.Gui/Views/Wizard/WizardStep.cs index c28c3653c..e583f2334 100644 --- a/Terminal.Gui/Views/Wizard/WizardStep.cs +++ b/Terminal.Gui/Views/Wizard/WizardStep.cs @@ -126,7 +126,7 @@ public class WizardStep : FrameView /// Add the specified to the . /// to add to this container - public override void Add (View view) + public override View Add (View view) { _contentView.Add (view); @@ -136,6 +136,8 @@ public class WizardStep : FrameView } ShowHide (); + + return view; } /// Removes a from . diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 9c310dfa2..81d2cad6e 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -446,7 +446,7 @@ public class Bars : Scenario shortcut.Accept += (s, e) => { labelHelp.Text = labelHelp.Text + "!"; - e.Handled = true; + e.Cancel = true; }; statusBar.Add (shortcut); diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index 73f718615..9c9beb097 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using Terminal.Gui; @@ -20,6 +21,8 @@ public class GraphViewExample : Scenario private GraphView _graphView; private MenuItem _miDiags; private MenuItem _miShowBorder; + private ViewDiagnosticFlags _viewDiagnostics; + public override void Main () { Application.Init (); @@ -183,18 +186,25 @@ public class GraphViewExample : Scenario CanFocus = false } }; - diagShortcut.Accept += DiagShortcut_Accept; - statusBar.Add (diagShortcut); + statusBar.Add (diagShortcut).Accept += DiagShortcut_Accept; _graphs [_currentGraph++ % _graphs.Length] (); + + _viewDiagnostics = View.Diagnostics; Application.Run (app); + View.Diagnostics = _viewDiagnostics; app.Dispose (); Application.Shutdown (); + } - private void DiagShortcut_Accept (object sender, System.ComponentModel.HandledEventArgs e) + private void DiagShortcut_Accept (object sender, CancelEventArgs e) { ToggleDiagnostics(); + if (sender is Shortcut shortcut && shortcut.CommandView is CheckBox checkBox) + { + checkBox.Checked = _miDiags.Checked; + } } private void ToggleDiagnostics () diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 26292b76d..7e1906e2b 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -82,14 +82,14 @@ public class Shortcuts : Scenario vShortcut2.Accept += (o, args) => { // Cycle to next item. If at end, set 0 - //if (((RadioGroup)vShortcut2.CommandView).SelectedItem < ((RadioGroup)vShortcut2.CommandView).RadioLabels.Length - 1) - //{ - // ((RadioGroup)vShortcut2.CommandView).SelectedItem++; - //} - //else - //{ - // ((RadioGroup)vShortcut2.CommandView).SelectedItem = 0; - //} + if (((RadioGroup)vShortcut2.CommandView).SelectedItem < ((RadioGroup)vShortcut2.CommandView).RadioLabels.Length - 1) + { + ((RadioGroup)vShortcut2.CommandView).SelectedItem++; + } + else + { + ((RadioGroup)vShortcut2.CommandView).SelectedItem = 0; + } }; vShortcut2.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (vShortcut2); From f99a744ef843d69e2b50fce1c52f4c632a4d135a Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 13:38:58 -0700 Subject: [PATCH 37/85] View.Remove returns added view --- Terminal.Gui/View/ViewSubViews.cs | 6 ++++-- Terminal.Gui/Views/Bar.cs | 4 +++- Terminal.Gui/Views/ScrollView.cs | 6 ++++-- Terminal.Gui/Views/Toplevel.cs | 4 ++-- Terminal.Gui/Views/Wizard/WizardStep.cs | 6 ++++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 8369f12ea..0e02d5e28 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -209,11 +209,11 @@ public partial class View /// lifecycle to be transferred to the caller; the caller muse call . /// /// - public virtual void Remove (View view) + public virtual View Remove (View view) { if (view is null || _subviews is null) { - return; + return view; } Rectangle touched = view.Frame; @@ -238,6 +238,8 @@ public partial class View { Focused = null; } + + return view; } /// diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index b25f0127a..ad0a1a5fe 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -67,10 +67,12 @@ public class Bar : View } /// - public override void Remove (View view) + public override View Remove (View view) { base.Remove (view); AdjustSubviewBorders (); + + return view; } diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index bebe2936a..cfe50a955 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -457,11 +457,11 @@ public class ScrollView : View /// Removes the view from the scrollview. /// The view to remove from the scrollview. - public override void Remove (View view) + public override View Remove (View view) { if (view is null) { - return; + return view; } SetNeedsDisplay (); @@ -480,6 +480,8 @@ public class ScrollView : View { CanFocus = false; } + + return view; } /// Removes all widgets from this container. diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 5ca39c24d..22af364f2 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -451,14 +451,14 @@ public partial class Toplevel : View public event EventHandler Ready; /// - public override void Remove (View view) + public override View Remove (View view) { if (this is Toplevel { MenuBar: { } }) { RemoveMenuStatusBar (view); } - base.Remove (view); + return base.Remove (view); } /// diff --git a/Terminal.Gui/Views/Wizard/WizardStep.cs b/Terminal.Gui/Views/Wizard/WizardStep.cs index e583f2334..f6ebac473 100644 --- a/Terminal.Gui/Views/Wizard/WizardStep.cs +++ b/Terminal.Gui/Views/Wizard/WizardStep.cs @@ -142,11 +142,11 @@ public class WizardStep : FrameView /// Removes a from . /// - public override void Remove (View view) + public override View Remove (View view) { if (view is null) { - return; + return view; } SetNeedsDisplay (); @@ -167,6 +167,8 @@ public class WizardStep : FrameView } ShowHide (); + + return view; } /// Removes all s from the . From f43596c878f672b7a06dc2f73628ed94408f2f1a Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 16:07:40 -0700 Subject: [PATCH 38/85] restructure --- Terminal.Gui/Views/Bar.cs | 127 +++++------ Terminal.Gui/Views/Shortcut.cs | 19 +- Terminal.Gui/Views/StatusBar.cs | 48 +++- UICatalog/Scenarios/Bars.cs | 103 +++++---- UICatalog/Scenarios/GraphViewExample.cs | 283 ++++++++++++------------ UICatalog/UICatalog.cs | 2 +- UnitTests/Views/ContextMenuTests.cs | 136 ------------ 7 files changed, 304 insertions(+), 414 deletions(-) diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index ad0a1a5fe..958c2c32b 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis; namespace Terminal.Gui; @@ -23,9 +24,13 @@ public class Bar : View Height = Dim.Auto (); LayoutStarted += Bar_LayoutStarted; - Initialized += Bar_Initialized; + if (shortcuts is null) + { + return; + } + foreach (Shortcut shortcut in shortcuts) { Add (shortcut); @@ -35,7 +40,7 @@ public class Bar : View private void Bar_Initialized (object sender, EventArgs e) { ColorScheme = Colors.ColorSchemes ["Menu"]; - AdjustSubviewBorders (); + AdjustSubviews (); } /// @@ -45,23 +50,41 @@ public class Bar : View Border.LineStyle = value; } + private Orientation _orientation = Orientation.Horizontal; + /// /// Gets or sets the for this . The default is /// . /// - public Orientation Orientation { get; set; } = Orientation.Horizontal; + public Orientation Orientation + { + get => _orientation; + set + { + _orientation = value; + SetNeedsLayout (); + } + } + + private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd; /// /// Gets or sets the for this . The default is . /// - public AlignmentModes AlignmentModes { get; set; } = AlignmentModes.StartToEnd; - - public bool StatusBarStyle { get; set; } = true; + public AlignmentModes AlignmentModes + { + get => _alignmentModes; + set + { + _alignmentModes = value; + SetNeedsLayout (); + } + } public override View Add (View view) { base.Add (view); - AdjustSubviewBorders (); + AdjustSubviews (); return view; } @@ -70,7 +93,7 @@ public class Bar : View public override View Remove (View view) { base.Remove (view); - AdjustSubviewBorders (); + AdjustSubviews (); return view; } @@ -118,47 +141,31 @@ public class Bar : View return toRemove as Shortcut; } - private void AdjustSubviewBorders () + private void AdjustSubviews () { for (var index = 0; index < Subviews.Count; index++) { View barItem = Subviews [index]; - barItem.Border.LineStyle = BorderStyle; - barItem.SuperViewRendersLineCanvas = true; - barItem.ColorScheme = ColorScheme; + //barItem.Border.LineStyle = BorderStyle; + //barItem.SuperViewRendersLineCanvas = true; + //barItem.ColorScheme = ColorScheme; - if (!barItem.Visible) - { - continue; - } + //if (!barItem.Visible) + //{ + // continue; + //} - if (StatusBarStyle) - { - barItem.BorderStyle = LineStyle.Dashed; + //barItem.BorderStyle = LineStyle.None; + //if (index == 0) + //{ + // barItem.Border.Thickness = new Thickness (1, 1, 1, 0); + //} - if (index == Subviews.Count - 1) - { - barItem.Border.Thickness = new Thickness (0, 0, 0, 0); - } - else - { - barItem.Border.Thickness = new Thickness (0, 0, 1, 0); - } - } - else - { - barItem.BorderStyle = LineStyle.None; - if (index == 0) - { - barItem.Border.Thickness = new Thickness (1, 1, 1, 0); - } - - if (index == Subviews.Count - 1) - { - barItem.Border.Thickness = new Thickness (1, 0, 1, 1); - } - } + //if (index == Subviews.Count - 1) + //{ + // barItem.Border.Thickness = new Thickness (1, 0, 1, 1); + //} } } @@ -173,40 +180,12 @@ public class Bar : View { View barItem = Subviews [index]; - if (!barItem.Visible) - { - continue; - } + barItem.ColorScheme = ColorScheme; + barItem.X = Pos.Align (Alignment.Start, AlignmentModes); + barItem.Y = 0;//Pos.Center (); - //if (StatusBarStyle) - //{ - // barItem.BorderStyle = LineStyle.Dashed; - //} - //else - //{ - // barItem.BorderStyle = LineStyle.None; - //} - - //if (index == Subviews.Count - 1) - //{ - // barItem.Border.Thickness = new Thickness (0, 0, 0, 0); - //} - //else - //{ - // barItem.Border.Thickness = new Thickness (0, 0, 1, 0); - //} - - if (barItem is Shortcut shortcut) - { - shortcut.X = Pos.Align (Alignment.Start, AlignmentModes); - } - else - { - barItem.X = Pos.Align (Alignment.Start, AlignmentModes); - } - - barItem.Y = Pos.Center (); - prevBarItem = barItem; + // HACK: This should not be needed + barItem.SetRelativeLayout (GetContentSize ()); } break; diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index ff35a7159..fb66cf305 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -98,6 +98,11 @@ public class Shortcut : View LayoutStarted += OnLayoutStarted; Initialized += OnInitialized; + if (key is null) + { + key = Key.Empty; + } + Key = key; Title = commandText; Action = action; @@ -445,7 +450,7 @@ public class Shortcut : View { CommandView.Margin.Thickness = GetMarginThickness (); CommandView.X = Pos.Align (Alignment.End, AlignmentModes); - CommandView.Y = 0; //Pos.Center (), + CommandView.Y = 0;//Pos.Center (); } @@ -470,9 +475,9 @@ public class Shortcut : View { HelpView.Margin.Thickness = GetMarginThickness (); HelpView.X = Pos.Align (Alignment.End, AlignmentModes); - HelpView.Y = 0; //Pos.Center (), + HelpView.Y = 0;//Pos.Center (); HelpView.Width = Dim.Auto (DimAutoStyle.Text); - HelpView.Height = CommandView?.IsAdded == true ? Dim.Height (CommandView) : 1; + HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; HelpView.Visible = true; HelpView.VerticalTextAlignment = Alignment.Center; @@ -592,9 +597,9 @@ public class Shortcut : View { KeyView.Margin.Thickness = GetMarginThickness (); KeyView.X = Pos.Align (Alignment.End, AlignmentModes); - //KeyView.Y = Pos.Center (); + KeyView.Y = 0;//Pos.Center (); KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); - KeyView.Height = CommandView?.IsAdded == true ? Dim.Height (CommandView) : 1; + KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; KeyView.Visible = true; @@ -653,7 +658,7 @@ public class Shortcut : View } } - return true; + return false; } /// @@ -702,7 +707,7 @@ public class Shortcut : View } // Set KeyView's colors to show "hot" - if (IsInitialized) + if (IsInitialized && base.ColorScheme is {}) { var cs = new ColorScheme (base.ColorScheme) { diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 966f2d59a..69cb264a6 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -1,3 +1,6 @@ +using System; +using System.Reflection; + namespace Terminal.Gui; /// @@ -9,28 +12,61 @@ namespace Terminal.Gui; /// public class StatusBar : Bar { - /// + /// public StatusBar () : this ([]) { } - /// + /// public StatusBar (IEnumerable shortcuts) : base (shortcuts) { Orientation = Orientation.Horizontal; Y = Pos.AnchorEnd (); Width = Dim.Fill (); - StatusBarStyle = true; + Height = Dim.Auto (DimAutoStyle.Content, 1); + BorderStyle = LineStyle.Dashed; + ColorScheme = Colors.ColorSchemes ["Menu"]; + + LayoutStarted += StatusBar_LayoutStarted; } - /// + // StatusBar arranges the items horizontally. + // The first item has no left border, the last item has no right border. + // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart). + private void StatusBar_LayoutStarted (object sender, LayoutEventArgs e) + { + for (int index = 0; index < Subviews.Count; index++) + { + View barItem = Subviews [index]; + + barItem.BorderStyle = BorderStyle; + + if (index == Subviews.Count - 1) + { + barItem.Border.Thickness = new Thickness (0, 0, 0, 0); + } + else + { + barItem.Border.Thickness = new Thickness (0, 0, 1, 0); + } + + if (barItem is Shortcut shortcut) + { + shortcut.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; + + } + } + } + + + /// public override View Add (View view) { view.CanFocus = false; + if (view is Shortcut shortcut) { shortcut.KeyBindingScope = KeyBindingScope.Application; - shortcut.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; } + return base.Add (view); } - } diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 81d2cad6e..73ef5a546 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -36,7 +36,7 @@ public class Bars : Scenario { X = Pos.AnchorEnd (), Width = 50, - Height = Dim.Fill (3), + Height = Dim.Fill (6), ColorScheme = Colors.ColorSchemes ["Toplevel"], Source = new ListWrapper (eventSource) }; @@ -104,15 +104,14 @@ public class Bars : Scenario // eventLog.MoveDown (); // }; - var bar = new Bar + var vBar = new Bar { X = 2, Y = 2, Orientation = Orientation.Vertical, - StatusBarStyle = false, BorderStyle = LineStyle.Rounded }; - bar.Add (shortcut1, shortcut2); + vBar.Add (shortcut1, shortcut2); ////CheckBox hello = new () ////{ @@ -128,14 +127,48 @@ public class Bars : Scenario //// }; - Application.Top.Add (bar); + Application.Top.Add (vBar); // BUGBUG: This should not be needed Application.Top.LayoutSubviews (); - // SetupMenuBar (); + // SetupMenuBar (); //SetupContentMenu (); - SetupStatusBar (); + Label label = new Label () + { + Title = " Bar:", + X = 0, + Y = Pos.AnchorEnd () - 6 + }; + Application.Top.Add (label); + var bar = new Bar + { + Id = "bar", + X = Pos.Right (label), + Y = Pos.Top (label), + Width = Dim.Fill (), + Orientation = Orientation.Horizontal, + }; + ConfigStatusBar (bar); + Application.Top.Add (bar); + + label = new Label () + { + Title = "StatusBar:", + X = 0, + Y = Pos.AnchorEnd () - 3 + }; + Application.Top.Add (label); + bar = new StatusBar() + { + Id = "statusBar", + X = Pos.Right (label), + Y = Pos.Top (label), + Width = Dim.Fill (), + Orientation = Orientation.Horizontal, + }; + ConfigStatusBar (bar); + Application.Top.Add (bar); foreach (Bar barView in Application.Top.Subviews.Where (b => b is Bar)!) { @@ -143,14 +176,13 @@ public class Bars : Scenario { sh.Accept += (o, args) => { - eventSource.Add ($"Accept: {sh!.CommandView.Text}"); + eventSource.Add ($"Accept: {sh!.SuperView.Id} {sh!.CommandView.Text}"); eventLog.MoveDown (); }; } } } - private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } //private void SetupContentMenu () //{ @@ -307,7 +339,6 @@ public class Bars : Scenario Width = Dim.Fill (), Height = 1,//Dim.Auto (DimAutoStyle.Content), Orientation = Orientation.Horizontal, - StatusBarStyle = false, }; var fileMenuBarItem = new Shortcut @@ -317,7 +348,7 @@ public class Bars : Scenario Key = Key.F.WithAlt, }; fileMenuBarItem.KeyView.Visible = false; - + var editMenuBarItem = new Shortcut { Title = "_Edit", @@ -338,8 +369,7 @@ public class Bars : Scenario X = 1, Y = 1, Orientation = Orientation.Vertical, - StatusBarStyle = false, - // Modal = true, + // Modal = true, Visible = false, }; @@ -381,7 +411,7 @@ public class Bars : Scenario { if (fileMenu.Visible) { - // fileMenu.RequestStop (); + // fileMenu.RequestStop (); prevFocus?.SetFocus (); return; } @@ -405,26 +435,18 @@ public class Bars : Scenario } - private void SetupStatusBar () + private void ConfigStatusBar (Bar bar) { - var statusBar = new Bar - { - Id = "statusBar", - X = 0, - Y = Pos.AnchorEnd (), - Width = Dim.Fill (), - }; - var shortcut = new Shortcut { + Height = Dim.Auto (DimAutoStyle.Content, 3), Text = "Quit", Title = "Q_uit", Key = Application.QuitKey, KeyBindingScope = KeyBindingScope.Application, - CanFocus = false }; - statusBar.Add (shortcut); + bar.Add (shortcut); shortcut = new Shortcut { @@ -432,24 +454,9 @@ public class Bars : Scenario Title = "Help", Key = Key.F1, KeyBindingScope = KeyBindingScope.HotKey, - CanFocus = false }; - var labelHelp = new Label - { - X = Pos.Center (), - Y = Pos.Top (statusBar) - 1, - Text = "Help" - }; - Application.Top.Add (labelHelp); - - shortcut.Accept += (s, e) => - { - labelHelp.Text = labelHelp.Text + "!"; - e.Cancel = true; - }; - - statusBar.Add (shortcut); + bar.Add (shortcut); shortcut = new Shortcut { @@ -460,10 +467,9 @@ public class Bars : Scenario { Text = "_Show/Hide" }, - CanFocus = false }; - statusBar.Add (shortcut); + bar.Add (shortcut); var button1 = new Button { @@ -471,15 +477,16 @@ public class Bars : Scenario // Visible = false }; button1.Accept += Button_Clicked; - statusBar.Add (button1); + bar.Add (button1); shortcut.Accept += (s, e) => { button1.Visible = !button1.Visible; button1.Enabled = button1.Visible; + e.Cancel = false; }; - statusBar.Add (new Label + bar.Add (new Label { HotKeySpecifier = new Rune ('_'), Text = "Fo_cusLabel", @@ -492,11 +499,11 @@ public class Bars : Scenario }; button2.Accept += (s, e) => Application.RequestStop (); - statusBar.Add (button2); + bar.Add (button2); - statusBar.Initialized += Menu_Initialized; + return; - Application.Top.Add (statusBar); + void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } } diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs index 9c9beb097..754a8aa10 100644 --- a/UICatalog/Scenarios/GraphViewExample.cs +++ b/UICatalog/Scenarios/GraphViewExample.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using System.Linq; using System.Text; using Terminal.Gui; -using static System.Net.Mime.MediaTypeNames; using Application = Terminal.Gui.Application; namespace UICatalog.Scenarios; @@ -44,101 +43,101 @@ public class GraphViewExample : Scenario { Menus = [ - new MenuBarItem ( - "_File", - new MenuItem [] - { - new ( - "Scatter _Plot", - "", - () => _graphs [_currentGraph = - 0] () - ), - new ( - "_V Bar Graph", - "", - () => _graphs [_currentGraph = - 1] () - ), - new ( - "_H Bar Graph", - "", - () => _graphs [_currentGraph = - 2] () - ), - new ( - "P_opulation Pyramid", - "", - () => _graphs [_currentGraph = - 3] () - ), - new ( - "_Line Graph", - "", - () => _graphs [_currentGraph = - 4] () - ), - new ( - "Sine _Wave", - "", - () => _graphs [_currentGraph = - 5] () - ), - new ( - "Silent _Disco", - "", - () => _graphs [_currentGraph = - 6] () - ), - new ( - "_Multi Bar Graph", - "", - () => _graphs [_currentGraph = - 7] () - ), - new ("_Quit", "", () => Quit ()) - } - ), - new MenuBarItem ( - "_View", - new [] - { - new ("Zoom _In", "", () => Zoom (0.5f)), - new ("Zoom _Out", "", () => Zoom (2f)), - new ("MarginLeft++", "", () => Margin (true, true)), - new ("MarginLeft--", "", () => Margin (true, false)), - new ("MarginBottom++", "", () => Margin (false, true)), - new ("MarginBottom--", "", () => Margin (false, false)), - _miShowBorder = new MenuItem ( - "_Enable Margin, Border, and Padding", - "", - () => ShowBorder () - ) - { - Checked = true, - CheckType = MenuItemCheckStyle - .Checked - }, - _miDiags = new MenuItem ( - "_Diagnostics", - "", - () => ToggleDiagnostics () - ) - { - Checked = View.Diagnostics - == (ViewDiagnosticFlags - .Padding - | ViewDiagnosticFlags - .Ruler), - CheckType = MenuItemCheckStyle.Checked - } - } - ) + new ( + "_File", + new MenuItem [] + { + new ( + "Scatter _Plot", + "", + () => _graphs [_currentGraph = + 0] () + ), + new ( + "_V Bar Graph", + "", + () => _graphs [_currentGraph = + 1] () + ), + new ( + "_H Bar Graph", + "", + () => _graphs [_currentGraph = + 2] () + ), + new ( + "P_opulation Pyramid", + "", + () => _graphs [_currentGraph = + 3] () + ), + new ( + "_Line Graph", + "", + () => _graphs [_currentGraph = + 4] () + ), + new ( + "Sine _Wave", + "", + () => _graphs [_currentGraph = + 5] () + ), + new ( + "Silent _Disco", + "", + () => _graphs [_currentGraph = + 6] () + ), + new ( + "_Multi Bar Graph", + "", + () => _graphs [_currentGraph = + 7] () + ), + new ("_Quit", "", () => Quit ()) + } + ), + new ( + "_View", + new [] + { + new ("Zoom _In", "", () => Zoom (0.5f)), + new ("Zoom _Out", "", () => Zoom (2f)), + new ("MarginLeft++", "", () => Margin (true, true)), + new ("MarginLeft--", "", () => Margin (true, false)), + new ("MarginBottom++", "", () => Margin (false, true)), + new ("MarginBottom--", "", () => Margin (false, false)), + _miShowBorder = new ( + "_Enable Margin, Border, and Padding", + "", + () => ShowBorder () + ) + { + Checked = true, + CheckType = MenuItemCheckStyle + .Checked + }, + _miDiags = new ( + "_Diagnostics", + "", + () => ToggleDiagnostics () + ) + { + Checked = View.Diagnostics + == (ViewDiagnosticFlags + .Padding + | ViewDiagnosticFlags + .Ruler), + CheckType = MenuItemCheckStyle.Checked + } + } + ) ] }; app.Add (menu); - _graphView = new GraphView + _graphView = new() { X = 0, Y = 1, @@ -162,7 +161,7 @@ public class GraphViewExample : Scenario }; frameRight.Add ( - _about = new TextView { Width = Dim.Fill (), Height = Dim.Fill () } + _about = new() { Width = Dim.Fill (), Height = Dim.Fill () } ); app.Add (frameRight); @@ -172,15 +171,15 @@ public class GraphViewExample : Scenario { new (Key.G.WithCtrl, "Next Graph", () => _graphs [_currentGraph++ % _graphs.Length] ()), new (Key.CursorUp, "Zoom In", () => Zoom (0.5f)), - new (Key.CursorDown, "Zoom Out", () => Zoom (2f)), + new (Key.CursorDown, "Zoom Out", () => Zoom (2f)) } ); app.Add (statusBar); - var diagShortcut = new Shortcut () + var diagShortcut = new Shortcut { Key = Key.F10, - CommandView = new CheckBox () + CommandView = new CheckBox { Title = "Diagnostics", CanFocus = false @@ -195,12 +194,12 @@ public class GraphViewExample : Scenario View.Diagnostics = _viewDiagnostics; app.Dispose (); Application.Shutdown (); - } private void DiagShortcut_Accept (object sender, CancelEventArgs e) { - ToggleDiagnostics(); + ToggleDiagnostics (); + if (sender is Shortcut shortcut && shortcut.CommandView is CheckBox checkBox) { checkBox.Checked = _miDiags.Checked; @@ -212,9 +211,9 @@ public class GraphViewExample : Scenario _miDiags.Checked = !_miDiags.Checked; View.Diagnostics = _miDiags.Checked == true - ? ViewDiagnosticFlags.Padding - | ViewDiagnosticFlags.Ruler - : ViewDiagnosticFlags.Off; + ? ViewDiagnosticFlags.Padding + | ViewDiagnosticFlags.Ruler + : ViewDiagnosticFlags.Off; Application.Refresh (); } @@ -241,7 +240,7 @@ public class GraphViewExample : Scenario _about.Text = "Housing Expenditures by income thirds 1996-2003"; Color fore = _graphView.ColorScheme.Normal.Foreground == new Color (ColorName.Black) - ? new Color (ColorName.White) + ? new (ColorName.White) : _graphView.ColorScheme.Normal.Foreground; var black = new Attribute (fore, Color.Black); var cyan = new Attribute (Color.BrightCyan, Color.Black); @@ -263,7 +262,7 @@ public class GraphViewExample : Scenario series.AddBars ("'02", stiple, 6600, 11000, 16700); series.AddBars ("'03", stiple, 7000, 12000, 17000); - _graphView.CellSize = new PointF (0.25f, 1000); + _graphView.CellSize = new (0.25f, 1000); _graphView.Series.Add (series); _graphView.SetNeedsDisplay (); @@ -279,20 +278,20 @@ public class GraphViewExample : Scenario _graphView.AxisY.Minimum = 0; - var legend = new LegendAnnotation (new Rectangle (_graphView.Viewport.Width - 20, 0, 20, 5)); + var legend = new LegendAnnotation (new (_graphView.Viewport.Width - 20, 0, 20, 5)); legend.AddEntry ( - new GraphCellToRender (stiple, series.SubSeries.ElementAt (0).OverrideBarColor), + new (stiple, series.SubSeries.ElementAt (0).OverrideBarColor), "Lower Third" ); legend.AddEntry ( - new GraphCellToRender (stiple, series.SubSeries.ElementAt (1).OverrideBarColor), + new (stiple, series.SubSeries.ElementAt (1).OverrideBarColor), "Middle Third" ); legend.AddEntry ( - new GraphCellToRender (stiple, series.SubSeries.ElementAt (2).OverrideBarColor), + new (stiple, series.SubSeries.ElementAt (2).OverrideBarColor), "Upper Third" ); _graphView.Annotations.Add (legend); @@ -324,7 +323,7 @@ public class GraphViewExample : Scenario for (var i = 0; i < 31; i++) { bars.Add ( - new BarSeriesBar (null, stiple, r.Next (0, 100)) + new (null, stiple, r.Next (0, 100)) { //ColorGetter = colorDelegate } @@ -344,7 +343,7 @@ public class GraphViewExample : Scenario _graphView.Series.Add (series); // How much graph space each cell of the console depicts - _graphView.CellSize = new PointF (1, 10); + _graphView.CellSize = new (1, 10); _graphView.AxisX.Increment = 0; // No graph ticks _graphView.AxisX.ShowLabelsEvery = 0; // no labels @@ -399,7 +398,7 @@ public class GraphViewExample : Scenario var barSeries = new BarSeries { - Bars = new List + Bars = new() { new ("Switzerland", softStiple, 83.4f), new ( @@ -476,7 +475,7 @@ public class GraphViewExample : Scenario barSeries.Orientation = Orientation.Vertical; // How much graph space each cell of the console depicts - _graphView.CellSize = new PointF (0.1f, 0.25f); + _graphView.CellSize = new (0.1f, 0.25f); // No axis marks since Bar will add it's own categorical marks _graphView.AxisX.Increment = 0f; @@ -494,14 +493,14 @@ public class GraphViewExample : Scenario _graphView.MarginLeft = 6; // Start the graph at 80 years because that is where most of our data is - _graphView.ScrollOffset = new PointF (0, 80); + _graphView.ScrollOffset = new (0, 80); } else { barSeries.Orientation = Orientation.Horizontal; // How much graph space each cell of the console depicts - _graphView.CellSize = new PointF (0.1f, 1f); + _graphView.CellSize = new (0.1f, 1f); // No axis marks since Bar will add it's own categorical marks _graphView.AxisY.Increment = 0f; @@ -520,7 +519,7 @@ public class GraphViewExample : Scenario _graphView.MarginLeft = (uint)barSeries.Bars.Max (b => b.Text.Length) + 2; // Start the graph at 80 years because that is where most of our data is - _graphView.ScrollOffset = new PointF (80, 0); + _graphView.ScrollOffset = new (80, 0); } _graphView.SetNeedsDisplay (); @@ -547,7 +546,7 @@ public class GraphViewExample : Scenario for (var i = 0; i < 10; i++) { - randomPoints.Add (new PointF (r.Next (100), r.Next (100))); + randomPoints.Add (new (r.Next (100), r.Next (100))); } var points = new ScatterSeries { Points = randomPoints }; @@ -560,14 +559,14 @@ public class GraphViewExample : Scenario _graphView.Series.Add (points); _graphView.Annotations.Add (line); - randomPoints = new List (); + randomPoints = new (); for (var i = 0; i < 10; i++) { - randomPoints.Add (new PointF (r.Next (100), r.Next (100))); + randomPoints.Add (new (r.Next (100), r.Next (100))); } - var points2 = new ScatterSeries { Points = randomPoints, Fill = new GraphCellToRender ((Rune)'x', red) }; + var points2 = new ScatterSeries { Points = randomPoints, Fill = new ((Rune)'x', red) }; var line2 = new PathAnnotation { @@ -578,7 +577,7 @@ public class GraphViewExample : Scenario _graphView.Annotations.Add (line2); // How much graph space each cell of the console depicts - _graphView.CellSize = new PointF (2, 5); + _graphView.CellSize = new (2, 5); // leave space for axis labels _graphView.MarginBottom = 2; @@ -599,10 +598,10 @@ public class GraphViewExample : Scenario new TextAnnotation { Text = "(Max)", - GraphPosition = new PointF ( - max.X + 2 * _graphView.CellSize.X, - max.Y - ) + GraphPosition = new ( + max.X + 2 * _graphView.CellSize.X, + max.Y + ) } ); @@ -622,7 +621,7 @@ public class GraphViewExample : Scenario _graphView.Series.Add ( new ScatterSeries { - Points = new List + Points = new() { new (1, 1.007f), new (2, 4.002f), @@ -744,7 +743,7 @@ public class GraphViewExample : Scenario ); // How much graph space each cell of the console depicts - _graphView.CellSize = new PointF (1, 5); + _graphView.CellSize = new (1, 5); // leave space for axis labels _graphView.MarginBottom = 2; @@ -797,10 +796,10 @@ public class GraphViewExample : Scenario _graphView.Reset (); // How much graph space each cell of the console depicts - _graphView.CellSize = new PointF (100_000, 1); + _graphView.CellSize = new (100_000, 1); //center the x axis in middle of screen to show both sides - _graphView.ScrollOffset = new PointF (-3_000_000, 0); + _graphView.ScrollOffset = new (-3_000_000, 0); _graphView.AxisX.Text = "Number Of People"; _graphView.AxisX.Increment = 500_000; @@ -826,7 +825,7 @@ public class GraphViewExample : Scenario var malesSeries = new BarSeries { Orientation = Orientation.Horizontal, - Bars = new List + Bars = new() { new ("0-4", stiple, -2009363), new ("5-9", stiple, -2108550), @@ -857,7 +856,7 @@ public class GraphViewExample : Scenario var femalesSeries = new BarSeries { Orientation = Orientation.Horizontal, - Bars = new List + Bars = new() { new ("0-4", stiple, 1915127), new ("5-9", stiple, 2011016), @@ -920,15 +919,15 @@ public class GraphViewExample : Scenario // Generate line graph with 2,000 points for (float x = -500; x < 500; x += 0.5f) { - points.Points.Add (new PointF (x, (float)Math.Sin (x))); - line.Points.Add (new PointF (x, (float)Math.Sin (x))); + points.Points.Add (new (x, (float)Math.Sin (x))); + line.Points.Add (new (x, (float)Math.Sin (x))); } _graphView.Series.Add (points); _graphView.Annotations.Add (line); // How much graph space each cell of the console depicts - _graphView.CellSize = new PointF (0.1f, 0.1f); + _graphView.CellSize = new (0.1f, 0.1f); // leave space for axis labels _graphView.MarginBottom = 2; @@ -945,7 +944,7 @@ public class GraphViewExample : Scenario _graphView.AxisY.Text = "↑Y"; _graphView.AxisY.LabelGetter = v => v.Value.ToString ("N2"); - _graphView.ScrollOffset = new PointF (-2.5f, -1); + _graphView.ScrollOffset = new (-2.5f, -1); _graphView.SetNeedsDisplay (); } @@ -971,10 +970,10 @@ public class GraphViewExample : Scenario private void Zoom (float factor) { - _graphView.CellSize = new PointF ( - _graphView.CellSize.X * factor, - _graphView.CellSize.Y * factor - ); + _graphView.CellSize = new ( + _graphView.CellSize.X * factor, + _graphView.CellSize.Y * factor + ); _graphView.AxisX.Increment *= factor; _graphView.AxisY.Increment *= factor; @@ -992,11 +991,11 @@ public class GraphViewExample : Scenario public DiscoBarSeries () { - _green = new Attribute (Color.BrightGreen, Color.Black); - _brightgreen = new Attribute (Color.Green, Color.Black); - _brightyellow = new Attribute (Color.BrightYellow, Color.Black); - _red = new Attribute (Color.Red, Color.Black); - _brightred = new Attribute (Color.BrightRed, Color.Black); + _green = new (Color.BrightGreen, Color.Black); + _brightgreen = new (Color.Green, Color.Black); + _brightyellow = new (Color.BrightYellow, Color.Black); + _red = new (Color.Red, Color.Black); + _brightred = new (Color.BrightRed, Color.Black); } protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn) diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index ee9ab8fbc..4e3e8cc62 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -458,8 +458,8 @@ internal class UICatalogApp StatusBar = new () { Visible = ShowStatusBar, - AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast }; + StatusBar.AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; Shortcut statusBarShortcut = new Shortcut () { diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index b1917e5c0..6d42add38 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/UnitTests/Views/ContextMenuTests.cs @@ -113,142 +113,6 @@ public class ContextMenuTests (ITestOutputHelper output) top.Dispose (); } - [Fact] - [AutoInitShutdown] - public void ContextMenu_On_Toplevel_With_A_MenuBar_TextField_StatusBar () - { - Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US"); - - var menu = new MenuBar - { - Menus = - [ - new MenuBarItem ("File", "", null), - new MenuBarItem ("Edit", "", null) - ] - }; - - var label = new Label { X = 2, Y = 3, Text = "Label:" }; - - var tf = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = "TextField" }; - - var statusBar = new StatusBar ( -#if V2_STATUSBAR - [ - new StatusItem (KeyCode.F1, "~F1~ Help", null), - new StatusItem (KeyCode.CtrlMask | KeyCode.Q, "~^Q~ Quit", null) - ] -#endif - ); - - var top = new Toplevel (); - top.Add (menu, label, tf, statusBar); - ((FakeDriver)Application.Driver).SetBufferSize (45, 17); - Application.Begin (top); - - Assert.Equal (new Rectangle (9, 3, 20, 1), tf.Frame); - Assert.True (tf.HasFocus); - - tf.ContextMenu.Show (); - Assert.True (ContextMenu.IsShow); - Assert.Equal (new Point (9, 3), tf.ContextMenu.Position); - Application.Top.Draw (); - - var expected = @" - File Edit - - - Label: TextField - ┌─────────────────────┐ - │ Select All Ctrl+T │ - │ Delete All Ctrl+R │ - │ Copy Ctrl+C │ - │ Cut Ctrl+X │ - │ Paste Ctrl+V │ - │ Undo Ctrl+Z │ - │ Redo Ctrl+Y │ - └─────────────────────┘ - - - - F1 Help │ ^Q Quit -"; - - Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (1, 0, 32, 17), pos); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void ContextMenu_On_Toplevel_With_A_MenuBar_Window_TextField_StatusBar () - { - Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US"); - - var menu = new MenuBar - { - Menus = - [ - new MenuBarItem ("File", "", null), - new MenuBarItem ("Edit", "", null) - ] - }; - - var label = new Label { X = 2, Y = 3, Text = "Label:" }; - - var tf = new TextField { X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = "TextField" }; - - var win = new Window (); - win.Add (label, tf); - - var statusBar = new StatusBar ( -#if V2_STATUSBAR - new [] - { - new StatusItem (KeyCode.F1, "~F1~ Help", null), - new StatusItem (KeyCode.CtrlMask | KeyCode.Q, "~^Q~ Quit", null) - } -#endif - ); - - var top = new Toplevel (); - top.Add (menu, win, statusBar); - Application.Begin (top); - ((FakeDriver)Application.Driver).SetBufferSize (44, 17); - - Assert.Equal (new Rectangle (9, 3, 20, 1), tf.Frame); - Assert.True (tf.HasFocus); - - tf.ContextMenu.Show (); - Assert.True (ContextMenu.IsShow); - Assert.Equal (new Point (10, 5), tf.ContextMenu.Position); - Application.Top.Draw (); - - var expected = @" - File Edit -┌──────────────────────────────────────────┐ -│ │ -│ │ -│ │ -│ Label: TextField │ -│ ┌─────────────────────┐ │ -│ │ Select All Ctrl+T │ │ -│ │ Delete All Ctrl+R │ │ -│ │ Copy Ctrl+C │ │ -│ │ Cut Ctrl+X │ │ -│ │ Paste Ctrl+V │ │ -│ │ Undo Ctrl+Z │ │ -│ │ Redo Ctrl+Y │ │ -│ └─────────────────────┘ │ -└──────────────────────────────────────────┘ - F1 Help │ ^Q Quit -"; - - Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.Equal (new Rectangle (1, 0, 44, 17), pos); - top.Dispose (); - } - [Fact] [AutoInitShutdown] public void Draw_A_ContextMenu_Over_A_Borderless_Top () From a67645f818e5e2d4728f8059976049bdd443f4a1 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 16:13:23 -0700 Subject: [PATCH 39/85] restructure --- Terminal.Gui/Views/Bar.cs | 98 ++++++++++++--------------------------- 1 file changed, 29 insertions(+), 69 deletions(-) diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 958c2c32b..1c040436e 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -1,21 +1,22 @@ -using System; -using System.Linq; -using System.Reflection; -using Microsoft.CodeAnalysis; - namespace Terminal.Gui; /// -/// Provides a horizontally or vertically oriented container for other views to be used as a menu, toolbar, or status bar. +/// Provides a horizontally or vertically oriented container for s to be used as a menu, toolbar, or status +/// bar. /// /// +/// +/// Any can be added to a . However, the is designed to work with +/// objects. The class provides a way to display a command, help, and key and +/// align them in a specific order. +/// /// public class Bar : View { /// public Bar () : this ([]) { } - /// + /// public Bar (IEnumerable shortcuts) { CanFocus = true; @@ -37,13 +38,9 @@ public class Bar : View } } - private void Bar_Initialized (object sender, EventArgs e) - { - ColorScheme = Colors.ColorSchemes ["Menu"]; - AdjustSubviews (); - } + private void Bar_Initialized (object sender, EventArgs e) { ColorScheme = Colors.ColorSchemes ["Menu"]; } - /// + /// public override void SetBorderStyle (LineStyle value) { // The default changes the thickness. We don't want that. We just set the style. @@ -56,6 +53,12 @@ public class Bar : View /// Gets or sets the for this . The default is /// . /// + /// + /// + /// Horizontal orientation arranges the command, help, and key parts of each s from right to left + /// Vertical orientation arranges the command, help, and key parts of each s from left to right. + /// + /// public Orientation Orientation { get => _orientation; @@ -69,7 +72,8 @@ public class Bar : View private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd; /// - /// Gets or sets the for this . The default is . + /// Gets or sets the for this . The default is + /// . /// public AlignmentModes AlignmentModes { @@ -81,24 +85,6 @@ public class Bar : View } } - public override View Add (View view) - { - base.Add (view); - AdjustSubviews (); - - return view; - } - - /// - public override View Remove (View view) - { - base.Remove (view); - AdjustSubviews (); - - return view; - } - - /// Inserts a in the specified index of . /// The zero-based index at which item should be inserted. /// The item to insert. @@ -107,14 +93,17 @@ public class Bar : View List savedSubViewList = Subviews.ToList (); int count = savedSubViewList.Count; RemoveAll (); - for (int i = 0; i < count; i++) + + for (var i = 0; i < count; i++) { if (i == index) { Add (item); } + Add (savedSubViewList [i]); } + SetNeedsDisplay (); } @@ -124,7 +113,8 @@ public class Bar : View public Shortcut RemoveShortcut (int index) { View toRemove = null; - for (int i = 0; i < Subviews.Count; i++) + + for (var i = 0; i < Subviews.Count; i++) { if (i == index) { @@ -141,34 +131,6 @@ public class Bar : View return toRemove as Shortcut; } - private void AdjustSubviews () - { - for (var index = 0; index < Subviews.Count; index++) - { - View barItem = Subviews [index]; - - //barItem.Border.LineStyle = BorderStyle; - //barItem.SuperViewRendersLineCanvas = true; - //barItem.ColorScheme = ColorScheme; - - //if (!barItem.Visible) - //{ - // continue; - //} - - //barItem.BorderStyle = LineStyle.None; - //if (index == 0) - //{ - // barItem.Border.Thickness = new Thickness (1, 1, 1, 0); - //} - - //if (index == Subviews.Count - 1) - //{ - // barItem.Border.Thickness = new Thickness (1, 0, 1, 1); - //} - } - } - private void Bar_LayoutStarted (object sender, LayoutEventArgs e) { View prevBarItem = null; @@ -182,7 +144,7 @@ public class Bar : View barItem.ColorScheme = ColorScheme; barItem.X = Pos.Align (Alignment.Start, AlignmentModes); - barItem.Y = 0;//Pos.Center (); + barItem.Y = 0; //Pos.Center (); // HACK: This should not be needed barItem.SetRelativeLayout (GetContentSize ()); @@ -195,9 +157,9 @@ public class Bar : View // All CommandView's are the same width, all HelpView's are the same width, // all KeyView's are the same width - int maxCommandWidth = 0; - int maxHelpWidth = 0; - int minKeyWidth = 0; + var maxCommandWidth = 0; + var maxHelpWidth = 0; + var minKeyWidth = 0; List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); @@ -256,11 +218,9 @@ public class Bar : View shortcut.Width = maxBarItemWidth; } - Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: totalHeight); + Height = Dim.Auto (DimAutoStyle.Content, totalHeight); break; } } - - } From 58dbdf47589c3df3de630d30af7dd8d20bd926e8 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 16:44:02 -0700 Subject: [PATCH 40/85] Updated HexEditor. Fixed HexView Frame->Viewport --- Terminal.Gui/Views/HexView.cs | 29 +++++-------- UICatalog/Scenarios/HexEditor.cs | 72 +++++++++++++++++--------------- 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index a55386413..40e4e9030 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -249,8 +249,6 @@ public class HexView : View /// protected internal override bool OnMouseEvent (MouseEvent me) { - // BUGBUG: Test this with a border! Assumes Frame == Viewport! - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && !me.Flags.HasFlag (MouseFlags.WheeledDown) @@ -343,20 +341,17 @@ public class HexView : View Driver.SetAttribute (current); Move (0, 0); - // BUGBUG: Viewport!!!! - Rectangle frame = Frame; - int nblocks = bytesPerLine / bsize; - var data = new byte [nblocks * bsize * frame.Height]; + var data = new byte [nblocks * bsize * viewport.Height]; Source.Position = displayStart; int n = source.Read (data, 0, data.Length); Attribute activeColor = ColorScheme.HotNormal; Attribute trackingColor = ColorScheme.HotFocus; - for (var line = 0; line < frame.Height; line++) + for (var line = 0; line < viewport.Height; line++) { - Rectangle lineRect = new (0, line, frame.Width, 1); + Rectangle lineRect = new (0, line, viewport.Width, 1); if (!Viewport.Contains (lineRect)) { @@ -597,16 +592,15 @@ public class HexView : View private bool MoveDown (int bytes) { - // BUGBUG: Viewport! RedisplayLine (position); if (position + bytes < source.Length) { position += bytes; } - else if ((bytes == bytesPerLine * Frame.Height && source.Length >= DisplayStart + bytesPerLine * Frame.Height) - || (bytes <= bytesPerLine * Frame.Height - bytesPerLine - && source.Length <= DisplayStart + bytesPerLine * Frame.Height)) + else if ((bytes == bytesPerLine * Viewport.Height && source.Length >= DisplayStart + bytesPerLine * Viewport.Height) + || (bytes <= bytesPerLine * Viewport.Height - bytesPerLine + && source.Length <= DisplayStart + bytesPerLine * Viewport.Height)) { long p = position; @@ -618,7 +612,7 @@ public class HexView : View position = p; } - if (position >= DisplayStart + bytesPerLine * Frame.Height) + if (position >= DisplayStart + bytesPerLine * Viewport.Height) { SetDisplayStart (DisplayStart + bytes); SetNeedsDisplay (); @@ -635,8 +629,7 @@ public class HexView : View { position = source.Length; - // BUGBUG: Viewport! - if (position >= DisplayStart + bytesPerLine * Frame.Height) + if (position >= DisplayStart + bytesPerLine * Viewport.Height) { SetDisplayStart (position); SetNeedsDisplay (); @@ -722,8 +715,7 @@ public class HexView : View position++; } - // BUGBUG: Viewport! - if (position >= DisplayStart + bytesPerLine * Frame.Height) + if (position >= DisplayStart + bytesPerLine * Viewport.Height) { SetDisplayStart (DisplayStart + bytesPerLine); SetNeedsDisplay (); @@ -771,8 +763,7 @@ public class HexView : View var delta = (int)(pos - DisplayStart); int line = delta / bytesPerLine; - // BUGBUG: Viewport! - SetNeedsDisplay (new (0, line, Frame.Width, 1)); + SetNeedsDisplay (new (0, line, Viewport.Width, 1)); } private bool ToggleSide () diff --git a/UICatalog/Scenarios/HexEditor.cs b/UICatalog/Scenarios/HexEditor.cs index ba3da9a19..5f02806f1 100644 --- a/UICatalog/Scenarios/HexEditor.cs +++ b/UICatalog/Scenarios/HexEditor.cs @@ -16,23 +16,30 @@ public class HexEditor : Scenario private HexView _hexView; private MenuItem _miAllowEdits; private bool _saved = true; -#if V2_STATUSBAR - private StatusItem _siPositionChanged; + private Shortcut _siPositionChanged; private StatusBar _statusBar; -#endif - public override void Setup () + public override void Main () { - Win.Title = GetName () + "-" + _fileName ?? "Untitled"; + Toplevel app = new Toplevel () + { + ColorScheme = Colors.ColorSchemes ["Base"] + }; CreateDemoFile (_fileName); - //CreateUnicodeDemoFile (_fileName); - - _hexView = new HexView (LoadFile ()) { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; + _hexView = new HexView (new MemoryStream (Encoding.UTF8.GetBytes ("Demo text."))) + { + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill (1), + Title = _fileName ?? "Untitled", + BorderStyle = LineStyle.Rounded, + }; _hexView.Edited += _hexView_Edited; _hexView.PositionChanged += _hexView_PositionChanged; - Win.Add (_hexView); + app.Add (_hexView); var menu = new MenuBar { @@ -76,20 +83,20 @@ public class HexEditor : Scenario ) ] }; - Top.Add (menu); -#if V2_STATUSBAR + app.Add (menu); + _statusBar = new StatusBar ( new [] { - new (KeyCode.F2, "~F2~ Open", () => Open ()), - new (KeyCode.F3, "~F3~ Save", () => Save ()), + new (Key.F2, "Open", () => Open ()), + new (Key.F3, "Save", () => Save ()), new ( Application.QuitKey, - $"{Application.QuitKey} to Quit", + $"Quit", () => Quit () ), - _siPositionChanged = new StatusItem ( - KeyCode.Null, + _siPositionChanged = new Shortcut ( + Key.Empty, $"Position: { _hexView.Position } Line: { @@ -102,28 +109,25 @@ public class HexEditor : Scenario () => { } ) } - ); - Top.Add (_statusBar); -#endif + ) + { + AlignmentModes = AlignmentModes.IgnoreFirstOrLast + }; + app.Add (_statusBar); + + _hexView.Source = LoadFile (); + + Application.Run (app); + app.Dispose (); + Application.Shutdown (); } private void _hexView_Edited (object sender, HexViewEditEventArgs e) { _saved = false; } private void _hexView_PositionChanged (object sender, HexViewEventArgs obj) { -#if V2_STATUSBAR _siPositionChanged.Title = - $"Position: { - obj.Position - } Line: { - obj.CursorPosition.Y - } Col: { - obj.CursorPosition.X - } Line length: { - obj.BytesPerLine - }"; - _statusBar.SetNeedsDisplay (); -#endif + $"Position: {obj.Position} Line: {obj.CursorPosition.Y} Col: {obj.CursorPosition.X} Line length: {obj.BytesPerLine}"; } private void Copy () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok"); } @@ -159,7 +163,7 @@ public class HexEditor : Scenario { var stream = new MemoryStream (); - if (!_saved && _hexView != null && _hexView.Edits.Count > 0) + if (!_saved && _hexView.Edits.Count > 0) { if (MessageBox.ErrorQuery ( "Save", @@ -180,12 +184,12 @@ public class HexEditor : Scenario { byte [] bin = File.ReadAllBytes (_fileName); stream.Write (bin); - Win.Title = GetName () + "-" + _fileName; + _hexView.Title = _fileName; _saved = true; } else { - Win.Title = GetName () + "-" + (_fileName ?? "Untitled"); + _hexView.Title = (_fileName ?? "Untitled"); } return stream; From 78a71feb5911cd1f90c8ddb363e02627375719a5 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 16:47:59 -0700 Subject: [PATCH 41/85] Updated BGWC --- .../Scenarios/BackgroundWorkerCollection.cs | 24 +++++++++---------- UICatalog/UICatalog.cs | 1 - 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index 29eb2b1c9..b2f6cdfa9 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -88,25 +88,23 @@ public class BackgroundWorkerCollection : Scenario ; _menu.MenuOpening += Menu_MenuOpening; Add (_menu); -#if V2_STATUSBAR var statusBar = new StatusBar ( new [] { - new StatusItem (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()), - new StatusItem ( - KeyCode.CtrlMask | KeyCode.R, - "~^R~ Run Worker", - () => _workerApp.RunWorker () - ), - new StatusItem ( - KeyCode.CtrlMask | KeyCode.C, - "~^C~ Cancel Worker", - () => _workerApp.CancelWorker () - ) + new Shortcut (Application.QuitKey, $"Quit", Quit), + new Shortcut ( + Key.R.WithCtrl, + "Run Worker", + () => _workerApp.RunWorker () + ), + new Shortcut ( + Key.C.WithCtrl, + "Cancel Worker", + () => _workerApp.CancelWorker () + ) } ); Add (statusBar); -#endif Ready += OverlappedMain_Ready; Activate += OverlappedMain_Activate; Deactivate += OverlappedMain_Deactivate; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 4e3e8cc62..cf4c261d9 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -347,7 +347,6 @@ internal class UICatalogApp // 'app' closed cleanly. foreach (Responder? inst in Responder.Instances) { - Debug.Assert (inst.WasDisposed); } From 543daf411ec5df2382972a6adecaf10f4f18ae9d Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 16:54:31 -0700 Subject: [PATCH 42/85] Updated InteractiveTree --- UICatalog/Scenarios/InteractiveTree.cs | 68 ++++++++++++-------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/UICatalog/Scenarios/InteractiveTree.cs b/UICatalog/Scenarios/InteractiveTree.cs index 22a3b7f08..5f0344aa1 100644 --- a/UICatalog/Scenarios/InteractiveTree.cs +++ b/UICatalog/Scenarios/InteractiveTree.cs @@ -10,53 +10,47 @@ public class InteractiveTree : Scenario { private TreeView _treeView; - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + var appWindow = new Toplevel () + { + Title = GetName (), + }; var menu = new MenuBar { Menus = [ - new MenuBarItem ("_File", new MenuItem [] { new ("_Quit", "", Quit) }) + new ("_File", new MenuItem [] { new ("_Quit", "", Quit) }) ] }; - Top.Add (menu); + appWindow.Add (menu); - _treeView = new TreeView { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill (1) }; + _treeView = new () + { + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill (1) + }; _treeView.KeyDown += TreeView_KeyPress; - Win.Add (_treeView); + appWindow.Add (_treeView); + var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] + new Shortcut [] { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - Quit - ), - new ( - KeyCode.CtrlMask | KeyCode.C, - "~^C~ Add Child", - AddChildNode - ), - new ( - KeyCode.CtrlMask | KeyCode.T, - "~^T~ Add Root", - AddRootNode - ), - new ( - KeyCode.CtrlMask | KeyCode.R, - "~^R~ Rename Node", - RenameNode - ) + new (Application.QuitKey, "Quit", Quit), + new (Key.C.WithCtrl, "Add Child", AddChildNode), + new (Key.T.WithCtrl, "Add Root", AddRootNode), + new (Key.R.WithCtrl, "Rename Node", RenameNode) } -#endif ); - Top.Add (statusBar); + appWindow.Add (statusBar); + + Application.Run (appWindow); + appWindow.Dispose (); + Application.Shutdown (); } private void AddChildNode () @@ -88,10 +82,10 @@ public class InteractiveTree : Scenario var ok = new Button { Text = "Ok", IsDefault = true }; ok.Accept += (s, e) => - { - okPressed = true; - Application.RequestStop (); - }; + { + okPressed = true; + Application.RequestStop (); + }; var cancel = new Button { Text = "Cancel" }; cancel.Accept += (s, e) => Application.RequestStop (); var d = new Dialog { Title = title, Buttons = [ok, cancel] }; @@ -129,7 +123,7 @@ public class InteractiveTree : Scenario private void TreeView_KeyPress (object sender, Key obj) { - if (obj.KeyCode == KeyCode.Delete) + if (obj.KeyCode == Key.Delete) { ITreeNode toDelete = _treeView.SelectedObject; From d397d1b9d5d7c6afc21e6c5efd6f87c413f4c2fa Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 16:59:03 -0700 Subject: [PATCH 43/85] Updated LineView scenario --- UICatalog/Scenarios/LineViewExample.cs | 60 +++++++++++++------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/UICatalog/Scenarios/LineViewExample.cs b/UICatalog/Scenarios/LineViewExample.cs index bfdc09d70..4ba7cc3d3 100644 --- a/UICatalog/Scenarios/LineViewExample.cs +++ b/UICatalog/Scenarios/LineViewExample.cs @@ -9,11 +9,12 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Borders")] public class LineViewExample : Scenario { - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new () + { + }; var menu = new MenuBar { @@ -22,47 +23,47 @@ public class LineViewExample : Scenario new MenuBarItem ("_File", new MenuItem [] { new ("_Quit", "", () => Quit ()) }) ] }; - Top.Add (menu); + appWindow.Add (menu); - Win.Add (new Label { Y = 0, Text = "Regular Line" }); + appWindow.Add (new Label { Y = 1, Text = "Regular Line" }); // creates a horizontal line - var line = new LineView { Y = 1 }; + var line = new LineView { Y = 2 }; - Win.Add (line); + appWindow.Add (line); - Win.Add (new Label { Y = 2, Text = "Double Width Line" }); + appWindow.Add (new Label { Y = 3, Text = "Double Width Line" }); // creates a horizontal line - var doubleLine = new LineView { Y = 3, LineRune = (Rune)'\u2550' }; + var doubleLine = new LineView { Y = 4, LineRune = (Rune)'\u2550' }; - Win.Add (doubleLine); + appWindow.Add (doubleLine); - Win.Add (new Label { Y = 4, Text = "Short Line" }); + appWindow.Add (new Label { Y = 5, Text = "Short Line" }); // creates a horizontal line var shortLine = new LineView { Y = 5, Width = 10 }; - Win.Add (shortLine); + appWindow.Add (shortLine); - Win.Add (new Label { Y = 6, Text = "Arrow Line" }); + appWindow.Add (new Label { Y = 7, Text = "Arrow Line" }); // creates a horizontal line var arrowLine = new LineView { - Y = 7, Width = 10, StartingAnchor = CM.Glyphs.LeftTee, EndingAnchor = (Rune)'>' + Y = 8, Width = 10, StartingAnchor = CM.Glyphs.LeftTee, EndingAnchor = (Rune)'>' }; - Win.Add (arrowLine); + appWindow.Add (arrowLine); - Win.Add (new Label { Y = 9, X = 11, Text = "Vertical Line" }); + appWindow.Add (new Label { Y = 10, X = 11, Text = "Vertical Line" }); // creates a horizontal line var verticalLine = new LineView (Orientation.Vertical) { X = 25 }; - Win.Add (verticalLine); + appWindow.Add (verticalLine); - Win.Add (new Label { Y = 11, X = 28, Text = "Vertical Arrow" }); + appWindow.Add (new Label { Y = 12, X = 28, Text = "Vertical Arrow" }); // creates a horizontal line var verticalArrow = new LineView (Orientation.Vertical) @@ -70,21 +71,22 @@ public class LineViewExample : Scenario X = 27, StartingAnchor = CM.Glyphs.TopTee, EndingAnchor = (Rune)'V' }; - Win.Add (verticalArrow); + appWindow.Add (verticalArrow); var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] + new Shortcut [] { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ) + new (Application.QuitKey, "Quit", Quit) } -#endif ); - Top.Add (statusBar); + appWindow.Add (statusBar); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } private void Quit () { Application.RequestStop (); } From a723990cb78f5166c8364cc83d7a19a7ae9bf4a8 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 17:03:29 -0700 Subject: [PATCH 44/85] Updated ListViewWithSelection --- UICatalog/Scenarios/LineViewExample.cs | 6 +-- UICatalog/Scenarios/ListColumns.cs | 72 ++++++++++++-------------- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/UICatalog/Scenarios/LineViewExample.cs b/UICatalog/Scenarios/LineViewExample.cs index 4ba7cc3d3..824e6e199 100644 --- a/UICatalog/Scenarios/LineViewExample.cs +++ b/UICatalog/Scenarios/LineViewExample.cs @@ -12,15 +12,13 @@ public class LineViewExample : Scenario public override void Main () { // Setup - Create a top-level application window and configure it. - Toplevel appWindow = new () - { - }; + Toplevel appWindow = new (); var menu = new MenuBar { Menus = [ - new MenuBarItem ("_File", new MenuItem [] { new ("_Quit", "", () => Quit ()) }) + new ("_File", new MenuItem [] { new ("_Quit", "", () => Quit ()) }) ] }; appWindow.Add (menu); diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index d4cf0c42c..f0cd740ef 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -47,19 +47,24 @@ public class ListColumns : Scenario return list; } - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + // Init + Application.Init (); - _listColView = new() + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new () + { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" + }; + + _listColView = new () { X = 0, - Y = 0, + Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1), - Style = new() + Style = new () { ShowHeaders = false, ShowHorizontalHeaderOverline = false, @@ -209,38 +214,20 @@ public class ListColumns : Scenario ] }; - Top.Add (menu); + appWindow.Add (menu); var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] + new Shortcut [] { - new ( - KeyCode.F2, - "~F2~ OpenBigListEx", - () => OpenSimpleList (true) - ), - new ( - KeyCode.F3, - "~F3~ CloseExample", - () => CloseExample () - ), - new ( - KeyCode.F4, - "~F4~ OpenSmListEx", - () => OpenSimpleList (false) - ), - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ) + new (Key.F2, "OpenBigListEx", () => OpenSimpleList (true)), + new (Key.F3, "CloseExample", CloseExample), + new (Key.F4, "OpenSmListEx", () => OpenSimpleList (false)), + new (Application.QuitKey, "Quit", Quit) } -#endif ); - Top.Add (statusBar); + appWindow.Add (statusBar); - Win.Add (_listColView); + appWindow.Add (_listColView); var selectedCellLabel = new Label { @@ -252,18 +239,18 @@ public class ListColumns : Scenario TextAlignment = Alignment.End }; - Win.Add (selectedCellLabel); + appWindow.Add (selectedCellLabel); _listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{_listColView.SelectedRow},{_listColView.SelectedColumn}"; }; _listColView.KeyDown += TableViewKeyPress; SetupScrollBar (); - _alternatingColorScheme = new() + _alternatingColorScheme = new () { - Disabled = Win.ColorScheme.Disabled, - HotFocus = Win.ColorScheme.HotFocus, - Focus = Win.ColorScheme.Focus, + Disabled = appWindow.ColorScheme.Disabled, + HotFocus = appWindow.ColorScheme.HotFocus, + Focus = appWindow.ColorScheme.Focus, Normal = new (Color.White, Color.BrightBlue) }; @@ -271,6 +258,13 @@ public class ListColumns : Scenario _listColView.MouseClick += (s, e) => { _listColView.ScreenToCell (e.MouseEvent.Position, out int? clickedCol); }; _listColView.KeyBindings.Add (Key.Space, Command.Accept); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } private void CloseExample () { _listColView.Table = null; } @@ -372,7 +366,7 @@ public class ListColumns : Scenario private void TableViewKeyPress (object sender, Key e) { - if (e.KeyCode == KeyCode.Delete) + if (e.KeyCode == Key.Delete) { // set all selected cells to null foreach (Point pt in _listColView.GetAllSelectedCells ()) From 14033f499592bfb3976d5c13f14e7e96ccf687da Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 17:07:27 -0700 Subject: [PATCH 45/85] Updated MultiColouredTable --- UICatalog/Scenarios/MultiColouredTable.cs | 109 +++++++++++----------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/UICatalog/Scenarios/MultiColouredTable.cs b/UICatalog/Scenarios/MultiColouredTable.cs index 8b824fee0..01c0e6f1b 100644 --- a/UICatalog/Scenarios/MultiColouredTable.cs +++ b/UICatalog/Scenarios/MultiColouredTable.cs @@ -14,39 +14,33 @@ public class MultiColouredTable : Scenario private DataTable _table; private TableViewColors _tableView; - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + // Init + Application.Init (); - _tableView = new TableViewColors { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill (1) }; + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new () + { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" + }; + + _tableView = new() { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; var menu = new MenuBar { Menus = [ - new MenuBarItem ("_File", new MenuItem [] { new ("_Quit", "", () => Quit ()) }) + new ("_File", new MenuItem [] { new ("_Quit", "", Quit) }) ] }; - Top.Add (menu); + appWindow.Add (menu); - var statusBar = new StatusBar ( -#if V2_STATUSBAR + var statusBar = new StatusBar (new Shortcut [] { new (Application.QuitKey, "Quit", Quit) }); - new StatusItem [] - { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ) - } -#endif - ); - Top.Add (statusBar); + appWindow.Add (statusBar); - Win.Add (_tableView); + appWindow.Add (_tableView); _tableView.CellActivated += EditCurrentCell; @@ -61,15 +55,22 @@ public class MultiColouredTable : Scenario dt.Rows.Add (DBNull.Value, DBNull.Value); dt.Rows.Add (DBNull.Value, DBNull.Value); - _tableView.ColorScheme = new ColorScheme + _tableView.ColorScheme = new() { - Disabled = Win.ColorScheme.Disabled, - HotFocus = Win.ColorScheme.HotFocus, - Focus = Win.ColorScheme.Focus, - Normal = new Attribute (Color.DarkGray, Color.Black) + Disabled = appWindow.ColorScheme.Disabled, + HotFocus = appWindow.ColorScheme.HotFocus, + Focus = appWindow.ColorScheme.Focus, + Normal = new (Color.DarkGray, Color.Black) }; _tableView.Table = new DataTableSource (_table = dt); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } private void EditCurrentCell (object sender, CellActivatedEventArgs e) @@ -104,10 +105,10 @@ public class MultiColouredTable : Scenario var ok = new Button { Text = "Ok", IsDefault = true }; ok.Accept += (s, e) => - { - okPressed = true; - Application.RequestStop (); - }; + { + okPressed = true; + Application.RequestStop (); + }; var cancel = new Button { Text = "Cancel" }; cancel.Accept += (s, e) => { Application.RequestStop (); }; var d = new Dialog { Title = title, Buttons = [ok, cancel] }; @@ -140,7 +141,7 @@ public class MultiColouredTable : Scenario { if (unicorns != -1 && i >= unicorns && i <= unicorns + 8) { - Driver.SetAttribute (new Attribute (Color.White, cellColor.Background)); + Driver.SetAttribute (new (Color.White, cellColor.Background)); } if (rainbows != -1 && i >= rainbows && i <= rainbows + 8) @@ -150,60 +151,60 @@ public class MultiColouredTable : Scenario switch (letterOfWord) { case 0: - Driver.SetAttribute (new Attribute (Color.Red, cellColor.Background)); + Driver.SetAttribute (new (Color.Red, cellColor.Background)); break; case 1: Driver.SetAttribute ( - new Attribute ( - Color.BrightRed, - cellColor.Background - ) + new ( + Color.BrightRed, + cellColor.Background + ) ); break; case 2: Driver.SetAttribute ( - new Attribute ( - Color.BrightYellow, - cellColor.Background - ) + new ( + Color.BrightYellow, + cellColor.Background + ) ); break; case 3: - Driver.SetAttribute (new Attribute (Color.Green, cellColor.Background)); + Driver.SetAttribute (new (Color.Green, cellColor.Background)); break; case 4: Driver.SetAttribute ( - new Attribute ( - Color.BrightGreen, - cellColor.Background - ) + new ( + Color.BrightGreen, + cellColor.Background + ) ); break; case 5: Driver.SetAttribute ( - new Attribute ( - Color.BrightBlue, - cellColor.Background - ) + new ( + Color.BrightBlue, + cellColor.Background + ) ); break; case 6: Driver.SetAttribute ( - new Attribute ( - Color.BrightCyan, - cellColor.Background - ) + new ( + Color.BrightCyan, + cellColor.Background + ) ); break; case 7: - Driver.SetAttribute (new Attribute (Color.Cyan, cellColor.Background)); + Driver.SetAttribute (new (Color.Cyan, cellColor.Background)); break; } From e38f95e6414fd44a9a4ad36ffcc4955435e7a9bc Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 17:07:43 -0700 Subject: [PATCH 46/85] Updated MultiColouredTable --- UICatalog/Scenarios/MultiColouredTable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/MultiColouredTable.cs b/UICatalog/Scenarios/MultiColouredTable.cs index 01c0e6f1b..4ac82bb00 100644 --- a/UICatalog/Scenarios/MultiColouredTable.cs +++ b/UICatalog/Scenarios/MultiColouredTable.cs @@ -25,7 +25,7 @@ public class MultiColouredTable : Scenario Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" }; - _tableView = new() { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; + _tableView = new () { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; var menu = new MenuBar { @@ -55,7 +55,7 @@ public class MultiColouredTable : Scenario dt.Rows.Add (DBNull.Value, DBNull.Value); dt.Rows.Add (DBNull.Value, DBNull.Value); - _tableView.ColorScheme = new() + _tableView.ColorScheme = new () { Disabled = appWindow.ColorScheme.Disabled, HotFocus = appWindow.ColorScheme.HotFocus, From 8d2378386b657b823f62257d326d4c0683e0f4c3 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:03:00 -0700 Subject: [PATCH 47/85] Updated MultiNotepad --- Terminal.Gui/Views/Shortcut.cs | 40 ++++-------- UICatalog/Scenarios/Notepad.cs | 106 +++++++++++++++---------------- UICatalog/Scenarios/Shortcuts.cs | 2 +- 3 files changed, 63 insertions(+), 85 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index fb66cf305..0042d08c4 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -126,7 +126,7 @@ public class Shortcut : View SetHelpViewDefaultLayout (); SetKeyViewDefaultLayout (); - SetColorScheme (); + SetColors (); } // Helper to set Width consistently @@ -312,22 +312,10 @@ public class Shortcut : View // When the Shortcut is clicked, we want to invoke the Command and Set focus var view = sender as View; - if (view != CommandView) - { - CommandView.InvokeCommand (Command.Accept); - e.Handled = true; - } - if (!e.Handled) { // If the subview (likely CommandView) didn't handle the mouse click, invoke the command. - bool? handled = false; - handled = InvokeCommand (Command.Accept); - - if (handled.HasValue) - { - e.Handled = handled.Value; - } + e.Handled = InvokeCommand (Command.Accept) == true; } if (CanFocus) @@ -335,7 +323,6 @@ public class Shortcut : View SetFocus (); } - e.Handled = true; } #region Command @@ -426,12 +413,6 @@ public class Shortcut : View return; - void CommandViewTextChanged (object sender, StateEventArgs e) - { - Title = _commandView.Text; - ShowHide (); - } - void CommandViewAccept (object sender, CancelEventArgs e) { // When the CommandView fires its Accept event, we want to act as though the @@ -440,8 +421,6 @@ public class Shortcut : View { e.Cancel = true; } - - //e.Cancel = true; } } } @@ -643,7 +622,7 @@ public class Shortcut : View break; case KeyBindingScope.HotKey: - handled = _commandView.InvokeCommand (Command.HotKey) == true; + _commandView.InvokeCommand (Command.HotKey); handled = false; break; @@ -655,6 +634,8 @@ public class Shortcut : View if (base.OnAccept () is false) { Action?.Invoke (); + + return true; } } @@ -681,11 +662,14 @@ public class Shortcut : View set { base.ColorScheme = value; - SetColorScheme (); + SetColors (); } } - public void SetColorScheme () + /// + /// + /// + internal void SetColors () { // Border should match superview. Border.ColorScheme = SuperView?.ColorScheme; @@ -721,14 +705,14 @@ public class Shortcut : View /// public override bool OnEnter (View view) { - SetColorScheme (); + SetColors (); return base.OnEnter (view); } /// public override bool OnLeave (View view) { - SetColorScheme (); + SetColors (); return base.OnLeave (view); } diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index ba182e0ed..dd962bfc1 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -11,9 +11,7 @@ namespace UICatalog.Scenarios; public class Notepad : Scenario { private TabView _focusedTabView; -#if V2_STATUSBAR - private StatusItem _lenStatusItem; -#endif + public Shortcut LenShortcut { get; private set; } private int _numNewTabs = 1; private TabView _tabView; @@ -41,11 +39,11 @@ public class Notepad : Scenario | KeyCode.CtrlMask | KeyCode.AltMask ), - new ("_Open", "", () => Open ()), - new ("_Save", "", () => Save ()), + new ("_Open", "", Open), + new ("_Save", "", Save), new ("Save _As", "", () => SaveAs ()), - new ("_Close", "", () => Close ()), - new ("_Quit", "", () => Quit ()) + new ("_Close", "", Close), + new ("_Quit", "", Quit) } ), new ( @@ -68,35 +66,31 @@ public class Notepad : Scenario split.LineStyle = LineStyle.None; top.Add (split); -#if V2_STATUSBAR - _lenStatusItem = new (KeyCode.CharMask, "Len: ", null); + LenShortcut = new (Key.Empty, "Len: ", null); - var statusBar = new StatusBar ( - new [] - { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ), - - // These shortcut keys don't seem to work correctly in linux - //new StatusItem(Key.CtrlMask | Key.N, "~^O~ Open", () => Open()), - //new StatusItem(Key.CtrlMask | Key.N, "~^N~ New", () => New()), - - new (KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save ()), - new (KeyCode.CtrlMask | KeyCode.W, "~^W~ Close", () => Close ()), - _lenStatusItem + var statusBar = new StatusBar (new [] { + new (Application.QuitKey, $"Quit", Quit), + new Shortcut(Key.F2, "Open", Open), + new Shortcut(Key.F1, "New", New), + new (Key.F3, "Save", Save), + new (Key.F6, "Close", Close), + LenShortcut } - ); + ) + { + AlignmentModes = AlignmentModes.IgnoreFirstOrLast + }; top.Add (statusBar); -#endif _focusedTabView = _tabView; _tabView.SelectedTabChanged += TabView_SelectedTabChanged; _tabView.Enter += (s, e) => _focusedTabView = _tabView; - top.Ready += (s, e) => New (); + top.Ready += (s, e) => + { + New (); + LenShortcut.Title = $"Len:{_focusedTabView.Text?.Length ?? 0}"; + }; Application.Run (top); top.Dispose (); @@ -283,7 +277,7 @@ public class Notepad : Scenario /// File that was read or null if a new blank document private void Open (FileInfo fileInfo, string tabName) { - var tab = new OpenedFile { DisplayText = tabName, File = fileInfo }; + var tab = new OpenedFile (this) { DisplayText = tabName, File = fileInfo }; tab.View = tab.CreateTextView (fileInfo); tab.SavedText = tab.View.Text; tab.RegisterTextViewEvents (_focusedTabView); @@ -327,9 +321,8 @@ public class Notepad : Scenario private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e) { -#if V2_STATUSBAR - _lenStatusItem.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}"; -#endif + LenShortcut.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}"; + e.NewTab?.View?.SetFocus (); } @@ -376,11 +369,13 @@ public class Notepad : Scenario e.MouseEvent.Handled = true; } - private class OpenedFile : Tab + private class OpenedFile (Notepad notepad) : Tab { + private Notepad _notepad = notepad; + public OpenedFile CloneTo (TabView other) { - var newTab = new OpenedFile { DisplayText = base.Text, File = File }; + var newTab = new OpenedFile (_notepad) { DisplayText = base.Text, File = File }; newTab.View = newTab.CreateTextView (newTab.File); newTab.SavedText = newTab.View.Text; newTab.RegisterTextViewEvents (other); @@ -416,28 +411,27 @@ public class Notepad : Scenario var textView = (TextView)View; // when user makes changes rename tab to indicate unsaved - textView.KeyUp += (s, k) => - { - // if current text doesn't match saved text - bool areDiff = UnsavedChanges; + textView.ContentsChanged += (s, k) => + { + // if current text doesn't match saved text + bool areDiff = UnsavedChanges; - if (areDiff) - { - if (!Text.EndsWith ('*')) - { - Text = Text + '*'; - parent.SetNeedsDisplay (); - } - } - else - { - if (Text.EndsWith ('*')) - { - Text = Text.TrimEnd ('*'); - parent.SetNeedsDisplay (); - } - } - }; + if (areDiff) + { + if (!DisplayText.EndsWith ('*')) + { + DisplayText = Text + '*'; + } + } + else + { + if (DisplayText.EndsWith ('*')) + { + DisplayText = Text.TrimEnd ('*'); + } + } + _notepad.LenShortcut.Title = $"Len:{textView.Text.Length}"; + }; } /// The text of the tab the last time it was saved @@ -458,7 +452,7 @@ public class Notepad : Scenario System.IO.File.WriteAllText (File.FullName, newText); SavedText = newText; - Text = Text.TrimEnd ('*'); + DisplayText = DisplayText.TrimEnd ('*'); } } } diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 7e1906e2b..69720ac2c 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -182,7 +182,7 @@ public class Shortcuts : Scenario if (peer.CanFocus) { peer.CommandView.CanFocus = e.NewValue == true; - peer.SetColorScheme (); + //peer.SetColors (); } } } From 453f29269850e39857dbde1fd68b96a02a3fde88 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:07:31 -0700 Subject: [PATCH 48/85] Updated SingleBackgroundWorker --- UICatalog/Scenarios/SingleBackgroundWorker.cs | 205 ++++++++---------- 1 file changed, 85 insertions(+), 120 deletions(-) diff --git a/UICatalog/Scenarios/SingleBackgroundWorker.cs b/UICatalog/Scenarios/SingleBackgroundWorker.cs index 7cc0dc8c5..035f72428 100644 --- a/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -12,15 +12,11 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Top Level Windows")] public class SingleBackgroundWorker : Scenario { - public override void Init () + public override void Main () { - Application.Run (); - - Application.Top.Dispose (); + Application.Run ().Dispose (); } - public override void Run () { } - public class MainApp : Toplevel { private readonly ListView _listLog; @@ -34,59 +30,47 @@ public class SingleBackgroundWorker : Scenario { Menus = [ - new MenuBarItem ( - "_Options", - new MenuItem [] - { - new ( - "_Run Worker", - "", - () => RunWorker (), - null, - null, - KeyCode.CtrlMask | KeyCode.R - ), - null, - new ( - "_Quit", - "", - () => Application.RequestStop (), - null, - null, - KeyCode.CtrlMask | KeyCode.Q - ) - } - ) + new ( + "_Options", + new MenuItem [] + { + new ( + "_Run Worker", + "", + () => RunWorker (), + null, + null, + KeyCode.CtrlMask | KeyCode.R + ), + null, + new ( + "_Quit", + "", + () => Application.RequestStop (), + null, + null, + KeyCode.CtrlMask | KeyCode.Q + ) + } + ) ] }; Add (menu); var statusBar = new StatusBar ( -#if V2_STATUSBAR - new [] - { - new StatusItem ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Application.RequestStop () - ), - new StatusItem ( - KeyCode.CtrlMask | KeyCode.P, - "~^R~ Run Worker", - () => RunWorker () - ) - } -#endif - ); + [ + new (Application.QuitKey, "Quit", () => Application.RequestStop ()), + new (Key.R.WithCtrl, "Run Worker", RunWorker) + ]); Add (statusBar); - var workerLogTop = new Toplevel () { Title = "Worker Log Top"}; + var workerLogTop = new Toplevel { Title = "Worker Log Top" }; workerLogTop.Add ( - new Label { X = Pos.Center (), Y = 0, Text = "Worker Log" } - ); + new Label { X = Pos.Center (), Y = 0, Text = "Worker Log" } + ); - _listLog = new ListView + _listLog = new() { X = 0, Y = 2, @@ -101,26 +85,26 @@ public class SingleBackgroundWorker : Scenario private void RunWorker () { - _worker = new BackgroundWorker { WorkerSupportsCancellation = true }; + _worker = new() { WorkerSupportsCancellation = true }; var cancel = new Button { Text = "Cancel Worker" }; cancel.Accept += (s, e) => - { - if (_worker == null) - { - _log.Add ($"Worker is not running at {DateTime.Now}!"); - _listLog.SetNeedsDisplay (); + { + if (_worker == null) + { + _log.Add ($"Worker is not running at {DateTime.Now}!"); + _listLog.SetNeedsDisplay (); - return; - } + return; + } - _log.Add ( - $"Worker {_startStaging}.{_startStaging:fff} is canceling at {DateTime.Now}!" - ); - _listLog.SetNeedsDisplay (); - _worker.CancelAsync (); - }; + _log.Add ( + $"Worker {_startStaging}.{_startStaging:fff} is canceling at {DateTime.Now}!" + ); + _listLog.SetNeedsDisplay (); + _worker.CancelAsync (); + }; _startStaging = DateTime.Now; _log.Add ($"Worker is started at {_startStaging}.{_startStaging:fff}"); @@ -166,15 +150,7 @@ public class SingleBackgroundWorker : Scenario { // Failed _log.Add ( - $"Exception occurred { - e.Error.Message - } on Worker { - _startStaging - }.{ - _startStaging - :fff} at { - DateTime.Now - }" + $"Exception occurred {e.Error.Message} on Worker {_startStaging}.{_startStaging:fff} at {DateTime.Now}" ); _listLog.SetNeedsDisplay (); } @@ -197,7 +173,7 @@ public class SingleBackgroundWorker : Scenario var builderUI = new StagingUIController (_startStaging, e.Result as ObservableCollection); - var top = Application.Top; + Toplevel top = Application.Top; top.Visible = false; Application.Current.Visible = false; builderUI.Load (); @@ -219,7 +195,7 @@ public class SingleBackgroundWorker : Scenario public StagingUIController (DateTime? start, ObservableCollection list) { - _top = new Toplevel + _top = new() { Title = "_top", Width = Dim.Fill (), Height = Dim.Fill () }; @@ -252,48 +228,44 @@ public class SingleBackgroundWorker : Scenario { Menus = [ - new MenuBarItem ( - "_Stage", - new MenuItem [] - { - new ( - "_Close", - "", - () => - { - if (Close ()) - { - Application.RequestStop (); - } - }, - null, - null, - KeyCode.CtrlMask | KeyCode.C - ) - } - ) + new ( + "_Stage", + new MenuItem [] + { + new ( + "_Close", + "", + () => + { + if (Close ()) + { + Application.RequestStop (); + } + }, + null, + null, + KeyCode.CtrlMask | KeyCode.C + ) + } + ) ] }; _top.Add (menu); var statusBar = new StatusBar ( -#if V2_STATUSBAR - new [] - { - new StatusItem ( - KeyCode.CtrlMask | KeyCode.C, - "~^C~ Close", - () => - { - if (Close ()) - { - Application.RequestStop (); - } - } - ) - } -#endif - ); + [ + new ( + Key.C.WithCtrl, + "Close", + () => + { + if (Close ()) + { + Application.RequestStop (); + } + } + ) + ]); _top.Add (statusBar); Title = $"Worker started at {start}.{start:fff}"; @@ -313,18 +285,11 @@ public class SingleBackgroundWorker : Scenario _top.Add (this); } - public void Load () { + public void Load () + { Application.Run (_top); _top.Dispose (); _top = null; } - - ///// - //protected override void Dispose (bool disposing) - //{ - // _top?.Dispose (); - // _top = null; - // base.Dispose (disposing); - //} } } From 18bf19e9a459be3f8f22b54948604797adf22449 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:12:37 -0700 Subject: [PATCH 49/85] Updated SyntaxHighlighting --- UICatalog/Scenarios/SyntaxHighlighting.cs | 123 +++++++++++----------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/UICatalog/Scenarios/SyntaxHighlighting.cs b/UICatalog/Scenarios/SyntaxHighlighting.cs index 78aaff959..9a613492e 100644 --- a/UICatalog/Scenarios/SyntaxHighlighting.cs +++ b/UICatalog/Scenarios/SyntaxHighlighting.cs @@ -120,72 +120,77 @@ public class SyntaxHighlighting : Scenario } } - public override void Setup () + public override void Main () { - Win.Title = GetName (); + // Init + Application.Init (); + + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new (); var menu = new MenuBar { Menus = [ - new MenuBarItem ( - "_TextView", - new [] - { - _miWrap = new MenuItem ( - "_Word Wrap", - "", - () => WordWrap () - ) - { - CheckType = MenuItemCheckStyle - .Checked - }, - null, - new ( - "_Syntax Highlighting", - "", - () => ApplySyntaxHighlighting () - ), - null, - new ( - "_Load Rune Cells", - "", - () => ApplyLoadRuneCells () - ), - new ( - "_Save Rune Cells", - "", - () => SaveRuneCells () - ), - null, - new ("_Quit", "", () => Quit ()) - } - ) + new ( + "_TextView", + new [] + { + _miWrap = new ( + "_Word Wrap", + "", + () => WordWrap () + ) + { + CheckType = MenuItemCheckStyle + .Checked + }, + null, + new ( + "_Syntax Highlighting", + "", + () => ApplySyntaxHighlighting () + ), + null, + new ( + "_Load Rune Cells", + "", + () => ApplyLoadRuneCells () + ), + new ( + "_Save Rune Cells", + "", + () => SaveRuneCells () + ), + null, + new ("_Quit", "", () => Quit ()) + } + ) ] }; - Top.Add (menu); + appWindow.Add (menu); - _textView = new TextView { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; + _textView = new() + { + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill (1) + }; ApplySyntaxHighlighting (); - Win.Add (_textView); + appWindow.Add (_textView); - var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] - { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ) - } -#endif - ); + var statusBar = new StatusBar ([new (Application.QuitKey, "to Quit", Quit)]); - Top.Add (statusBar); + appWindow.Add (statusBar); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } /// @@ -238,10 +243,10 @@ public class SyntaxHighlighting : Scenario foreach (Rune rune in csName.EnumerateRunes ()) { - runeCells.Add (new RuneCell { Rune = rune, ColorScheme = color.Value }); + runeCells.Add (new() { Rune = rune, ColorScheme = color.Value }); } - runeCells.Add (new RuneCell { Rune = (Rune)'\n', ColorScheme = color.Value }); + runeCells.Add (new() { Rune = (Rune)'\n', ColorScheme = color.Value }); } if (File.Exists (_path)) @@ -262,10 +267,10 @@ public class SyntaxHighlighting : Scenario { ClearAllEvents (); - _green = new ColorScheme (new Attribute (Color.Green, Color.Black)); - _blue = new ColorScheme (new Attribute (Color.Blue, Color.Black)); - _magenta = new ColorScheme (new Attribute (Color.Magenta, Color.Black)); - _white = new ColorScheme (new Attribute (Color.White, Color.Black)); + _green = new (new Attribute (Color.Green, Color.Black)); + _blue = new (new Attribute (Color.Blue, Color.Black)); + _magenta = new (new Attribute (Color.Magenta, Color.Black)); + _white = new (new Attribute (Color.White, Color.Black)); _textView.ColorScheme = _white; _textView.Text = @@ -344,7 +349,7 @@ public class SyntaxHighlighting : Scenario private string IdxToWord (List line, int idx) { string [] words = Regex.Split ( - new string (line.Select (r => (char)r.Value).ToArray ()), + new (line.Select (r => (char)r.Value).ToArray ()), "\\b" ); From c35c45eda82f2af98d2bde990c0b7f2f187a5c14 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:21:11 -0700 Subject: [PATCH 50/85] Updated TableEditor --- UICatalog/Scenarios/TableEditor.cs | 123 +++++++++++++++-------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index 8cbe2aef2..df86aceaf 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -427,13 +427,15 @@ public class TableEditor : Scenario return dt; } - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + // Init + Application.Init (); - _tableView = new() { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill (1) }; + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new (); + + _tableView = new () { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; var menu = new MenuBar { @@ -669,50 +671,48 @@ public class TableEditor : Scenario ] }; - Top.Add (menu); - - var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] - { - new ( - KeyCode.F2, - "~F2~ OpenExample", - () => OpenExample (true) - ), - new ( - KeyCode.F3, - "~F3~ CloseExample", - () => CloseExample () - ), - new ( - KeyCode.F4, - "~F4~ OpenSimple", - () => OpenSimple (true) - ), - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ) - } -#endif - ); - Top.Add (statusBar); - - Win.Add (_tableView); + appWindow.Add (menu); var selectedCellLabel = new Label { - X = 0, - Y = Pos.Bottom (_tableView), - Text = "0,0", - - Width = Dim.Fill (), - TextAlignment = Alignment.End + Text = "0,0" }; - Win.Add (selectedCellLabel); + var statusBar = new StatusBar ( + [ + new ( + Application.QuitKey, + "Quit", + Quit + ), + new ( + Key.F2, + "OpenExample", + () => OpenExample (true) + ), + new ( + Key.F3, + "CloseExample", + CloseExample + ), + new ( + Key.F4, + "OpenSimple", + () => OpenSimple (true) + ), + new () + { + HelpText = "Cell:", + CommandView = selectedCellLabel + } + ] + ) + { + AlignmentModes = AlignmentModes.IgnoreFirstOrLast + }; + appWindow.Add (statusBar); + + appWindow.Add (_tableView); _tableView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{_tableView.SelectedRow},{_tableView.SelectedColumn}"; }; _tableView.CellActivated += EditCurrentCell; @@ -720,27 +720,27 @@ public class TableEditor : Scenario SetupScrollBar (); - _redColorScheme = new() + _redColorScheme = new () { - Disabled = Win.ColorScheme.Disabled, - HotFocus = Win.ColorScheme.HotFocus, - Focus = Win.ColorScheme.Focus, - Normal = new (Color.Red, Win.ColorScheme.Normal.Background) + Disabled = appWindow.ColorScheme.Disabled, + HotFocus = appWindow.ColorScheme.HotFocus, + Focus = appWindow.ColorScheme.Focus, + Normal = new (Color.Red, appWindow.ColorScheme.Normal.Background) }; - _alternatingColorScheme = new() + _alternatingColorScheme = new () { - Disabled = Win.ColorScheme.Disabled, - HotFocus = Win.ColorScheme.HotFocus, - Focus = Win.ColorScheme.Focus, + Disabled = appWindow.ColorScheme.Disabled, + HotFocus = appWindow.ColorScheme.HotFocus, + Focus = appWindow.ColorScheme.Focus, Normal = new (Color.White, Color.BrightBlue) }; - _redColorSchemeAlt = new() + _redColorSchemeAlt = new () { - Disabled = Win.ColorScheme.Disabled, - HotFocus = Win.ColorScheme.HotFocus, - Focus = Win.ColorScheme.Focus, + Disabled = appWindow.ColorScheme.Disabled, + HotFocus = appWindow.ColorScheme.HotFocus, + Focus = appWindow.ColorScheme.Focus, Normal = new (Color.Red, Color.BrightBlue) }; @@ -770,6 +770,13 @@ public class TableEditor : Scenario }; _tableView.KeyBindings.Add (Key.Space, Command.Accept); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } protected override void Dispose (bool disposing) @@ -1019,7 +1026,7 @@ public class TableEditor : Scenario _tableView, "Name", tree, - new() + new () { { "Extension", f => f.Extension }, { "CreationTime", f => f.CreationTime }, From 64e1ab8ebc5b377665c06a4ab96818f5e4ba3bc3 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:25:03 -0700 Subject: [PATCH 51/85] Updated TabView scenario --- UICatalog/Scenarios/SyntaxHighlighting.cs | 2 +- UICatalog/Scenarios/TabViewExample.cs | 48 +++++++++++------------ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/UICatalog/Scenarios/SyntaxHighlighting.cs b/UICatalog/Scenarios/SyntaxHighlighting.cs index 9a613492e..ce4e970a2 100644 --- a/UICatalog/Scenarios/SyntaxHighlighting.cs +++ b/UICatalog/Scenarios/SyntaxHighlighting.cs @@ -181,7 +181,7 @@ public class SyntaxHighlighting : Scenario appWindow.Add (_textView); - var statusBar = new StatusBar ([new (Application.QuitKey, "to Quit", Quit)]); + var statusBar = new StatusBar ([new (Application.QuitKey, "Quit", Quit)]); appWindow.Add (statusBar); diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index 86957713b..b64e5d774 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -15,11 +15,13 @@ public class TabViewExample : Scenario private MenuItem _miTabsOnBottom; private TabView _tabView; - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + // Init + Application.Init (); + + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new (); var menu = new MenuBar { @@ -67,12 +69,12 @@ public class TabViewExample : Scenario ) ] }; - Top.Add (menu); + appWindow.Add (menu); _tabView = new TabView { X = 0, - Y = 0, + Y = 1, Width = 60, Height = 20, BorderStyle = LineStyle.Single @@ -128,14 +130,14 @@ public class TabViewExample : Scenario _tabView.SelectedTab = _tabView.Tabs.First (); - Win.Add (_tabView); + appWindow.Add (_tabView); var frameRight = new FrameView { X = Pos.Right (_tabView), - Y = 0, + Y = 1, Width = Dim.Fill (), - Height = Dim.Fill (), + Height = Dim.Fill (1), Title = "About" }; @@ -148,14 +150,14 @@ public class TabViewExample : Scenario } ); - Win.Add (frameRight); + appWindow.Add (frameRight); var frameBelow = new FrameView { X = 0, Y = Pos.Bottom (_tabView), Width = _tabView.Width, - Height = Dim.Fill (), + Height = Dim.Fill (1), Title = "Bottom Frame" }; @@ -169,21 +171,17 @@ public class TabViewExample : Scenario } ); - Win.Add (frameBelow); + appWindow.Add (frameBelow); - var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] - { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - Quit - ) - } -#endif - ); - Top.Add (statusBar); + var statusBar = new StatusBar ([new (Application.QuitKey, "Quit", Quit)]); + appWindow.Add (statusBar); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } private void AddBlankTab () { _tabView.AddTab (new Tab (), false); } From fc9474e2b7b13e42f76bf4ad5ae7a37ccd6aee6a Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:29:45 -0700 Subject: [PATCH 52/85] Updated TextAutocomplete --- UICatalog/Scenarios/TabViewExample.cs | 101 +++++++++--------- .../Scenarios/TextViewAutocompletePopup.cs | 66 +++++++----- 2 files changed, 91 insertions(+), 76 deletions(-) diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index b64e5d774..f0309a092 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -27,51 +27,51 @@ public class TabViewExample : Scenario { Menus = [ - new MenuBarItem ( - "_File", - new MenuItem [] - { - new ("_Add Blank Tab", "", AddBlankTab), - new ( - "_Clear SelectedTab", - "", - () => _tabView.SelectedTab = null - ), - new ("_Quit", "", Quit) - } - ), - new MenuBarItem ( - "_View", - new [] - { - _miShowTopLine = - new MenuItem ("_Show Top Line", "", ShowTopLine) - { - Checked = true, CheckType = MenuItemCheckStyle.Checked - }, - _miShowBorder = - new MenuItem ("_Show Border", "", ShowBorder) - { - Checked = true, CheckType = MenuItemCheckStyle.Checked - }, - _miTabsOnBottom = - new MenuItem ("_Tabs On Bottom", "", SetTabsOnBottom) - { - Checked = false, CheckType = MenuItemCheckStyle.Checked - }, - _miShowTabViewBorder = - new MenuItem ( - "_Show TabView Border", - "", - ShowTabViewBorder - ) { Checked = true, CheckType = MenuItemCheckStyle.Checked } - } - ) + new ( + "_File", + new MenuItem [] + { + new ("_Add Blank Tab", "", AddBlankTab), + new ( + "_Clear SelectedTab", + "", + () => _tabView.SelectedTab = null + ), + new ("_Quit", "", Quit) + } + ), + new ( + "_View", + new [] + { + _miShowTopLine = + new ("_Show Top Line", "", ShowTopLine) + { + Checked = true, CheckType = MenuItemCheckStyle.Checked + }, + _miShowBorder = + new ("_Show Border", "", ShowBorder) + { + Checked = true, CheckType = MenuItemCheckStyle.Checked + }, + _miTabsOnBottom = + new ("_Tabs On Bottom", "", SetTabsOnBottom) + { + Checked = false, CheckType = MenuItemCheckStyle.Checked + }, + _miShowTabViewBorder = + new ( + "_Show TabView Border", + "", + ShowTabViewBorder + ) { Checked = true, CheckType = MenuItemCheckStyle.Checked } + } + ) ] }; appWindow.Add (menu); - _tabView = new TabView + _tabView = new() { X = 0, Y = 1, @@ -80,13 +80,13 @@ public class TabViewExample : Scenario BorderStyle = LineStyle.Single }; - _tabView.AddTab (new Tab { DisplayText = "Tab1", View = new Label { Text = "hodor!" } }, false); - _tabView.AddTab (new Tab { DisplayText = "Tab2", View = new TextField { Text = "durdur" } }, false); - _tabView.AddTab (new Tab { DisplayText = "Interactive Tab", View = GetInteractiveTab () }, false); - _tabView.AddTab (new Tab { DisplayText = "Big Text", View = GetBigTextFileTab () }, false); + _tabView.AddTab (new() { DisplayText = "Tab1", View = new Label { Text = "hodor!" } }, false); + _tabView.AddTab (new() { DisplayText = "Tab2", View = new TextField { Text = "durdur" } }, false); + _tabView.AddTab (new() { DisplayText = "Interactive Tab", View = GetInteractiveTab () }, false); + _tabView.AddTab (new() { DisplayText = "Big Text", View = GetBigTextFileTab () }, false); _tabView.AddTab ( - new Tab + new() { DisplayText = "Long name Tab, I mean seriously long. Like you would not believe how long this tab's name is its just too much really woooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooowwww thats long", @@ -100,7 +100,7 @@ public class TabViewExample : Scenario ); _tabView.AddTab ( - new Tab + new() { DisplayText = "Les Mise" + '\u0301' + "rables", View = new Label { Text = "This tab name is unicode" } }, @@ -108,7 +108,7 @@ public class TabViewExample : Scenario ); _tabView.AddTab ( - new Tab + new() { DisplayText = "Les Mise" + '\u0328' + '\u0301' + "rables", View = new Label @@ -123,7 +123,7 @@ public class TabViewExample : Scenario for (var i = 0; i < 100; i++) { _tabView.AddTab ( - new Tab { DisplayText = $"Tab{i}", View = new Label { Text = $"Welcome to tab {i}" } }, + new() { DisplayText = $"Tab{i}", View = new Label { Text = $"Welcome to tab {i}" } }, false ); } @@ -178,13 +178,14 @@ public class TabViewExample : Scenario // Run - Start the application. Application.Run (appWindow); + appWindow.Dispose (); // Shutdown - Calling Application.Shutdown is required. Application.Shutdown (); } - private void AddBlankTab () { _tabView.AddTab (new Tab (), false); } + private void AddBlankTab () { _tabView.AddTab (new (), false); } private View GetBigTextFileTab () { diff --git a/UICatalog/Scenarios/TextViewAutocompletePopup.cs b/UICatalog/Scenarios/TextViewAutocompletePopup.cs index 44ee6da44..f501eaa2f 100644 --- a/UICatalog/Scenarios/TextViewAutocompletePopup.cs +++ b/UICatalog/Scenarios/TextViewAutocompletePopup.cs @@ -13,19 +13,22 @@ public class TextViewAutocompletePopup : Scenario private int _height = 10; private MenuItem _miMultiline; private MenuItem _miWrap; -#if V2_STATUSBAR - private StatusItem _siMultiline; - private StatusItem _siWrap; -#endif + private Shortcut _siMultiline; + private Shortcut _siWrap; private TextView _textViewBottomLeft; private TextView _textViewBottomRight; private TextView _textViewCentered; private TextView _textViewTopLeft; private TextView _textViewTopRight; - public override void Setup () + public override void Main () { - Win.Title = GetName (); + // Init + Application.Init (); + + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new (); + var width = 20; var text = " jamp jemp jimp jomp jump"; @@ -53,22 +56,30 @@ public class TextViewAutocompletePopup : Scenario ) ] }; - Top.Add (menu); + appWindow.Add (menu); - _textViewTopLeft = new TextView { Width = width, Height = _height, Text = text }; + _textViewTopLeft = new TextView + { + Y = 1, + Width = width, Height = _height, Text = text + }; _textViewTopLeft.DrawContent += TextViewTopLeft_DrawContent; - Win.Add (_textViewTopLeft); + appWindow.Add (_textViewTopLeft); - _textViewTopRight = new TextView { X = Pos.AnchorEnd (width), Width = width, Height = _height, Text = text }; + _textViewTopRight = new TextView + { + X = Pos.AnchorEnd (width), Y = 1, + Width = width, Height = _height, Text = text + }; _textViewTopRight.DrawContent += TextViewTopRight_DrawContent; - Win.Add (_textViewTopRight); + appWindow.Add (_textViewTopRight); _textViewBottomLeft = new TextView { Y = Pos.AnchorEnd (_height), Width = width, Height = _height, Text = text }; _textViewBottomLeft.DrawContent += TextViewBottomLeft_DrawContent; - Win.Add (_textViewBottomLeft); + appWindow.Add (_textViewBottomLeft); _textViewBottomRight = new TextView { @@ -79,7 +90,7 @@ public class TextViewAutocompletePopup : Scenario Text = text }; _textViewBottomRight.DrawContent += TextViewBottomRight_DrawContent; - Win.Add (_textViewBottomRight); + appWindow.Add (_textViewBottomRight); _textViewCentered = new TextView { @@ -90,28 +101,35 @@ public class TextViewAutocompletePopup : Scenario Text = text }; _textViewCentered.DrawContent += TextViewCentered_DrawContent; - Win.Add (_textViewCentered); + appWindow.Add (_textViewCentered); _miMultiline.Checked = _textViewTopLeft.Multiline; _miWrap.Checked = _textViewTopLeft.WordWrap; var statusBar = new StatusBar ( -#if V2_STATUSBAR new [] { new ( Application.QuitKey, - $"{Application.QuitKey} to Quit", + $"Quit", () => Quit () ), - _siMultiline = new StatusItem (KeyCode.Null, "", null), - _siWrap = new StatusItem (KeyCode.Null, "", null) + _siMultiline = new Shortcut (Key.Empty, "", null), + _siWrap = new Shortcut (Key.Empty, "", null) } -#endif - ); - Top.Add (statusBar); - Win.LayoutStarted += Win_LayoutStarted; + ); + appWindow.Add (statusBar); + + appWindow.LayoutStarted += Win_LayoutStarted; + + // Run - Start the application. + Application.Run (appWindow); + + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } private void Multiline () @@ -138,16 +156,12 @@ public class TextViewAutocompletePopup : Scenario private void SetMultilineStatusText () { -#if V2_STATUSBAR _siMultiline.Title = $"Multiline: {_miMultiline.Checked}"; -#endif } private void SetWrapStatusText () { -#if V2_STATUSBAR _siWrap.Title = $"WordWrap: {_miWrap.Checked}"; -#endif } private void TextViewBottomLeft_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewBottomLeft); } private void TextViewBottomRight_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewBottomRight); } From ef9a7daa2ac48df54ea1ccb1492895cfa0ebfbc5 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:33:29 -0700 Subject: [PATCH 53/85] Updated Unicode Scenario --- .../Scenarios/TextViewAutocompletePopup.cs | 65 ++++++++-------- UICatalog/Scenarios/Unicode.cs | 74 +++++++++++-------- 2 files changed, 74 insertions(+), 65 deletions(-) diff --git a/UICatalog/Scenarios/TextViewAutocompletePopup.cs b/UICatalog/Scenarios/TextViewAutocompletePopup.cs index f501eaa2f..16de9c1f6 100644 --- a/UICatalog/Scenarios/TextViewAutocompletePopup.cs +++ b/UICatalog/Scenarios/TextViewAutocompletePopup.cs @@ -36,29 +36,29 @@ public class TextViewAutocompletePopup : Scenario { Menus = [ - new MenuBarItem ( - "_File", - new [] - { - _miMultiline = - new MenuItem ( - "_Multiline", - "", - () => Multiline () - ) { CheckType = MenuItemCheckStyle.Checked }, - _miWrap = new MenuItem ( - "_Word Wrap", - "", - () => WordWrap () - ) { CheckType = MenuItemCheckStyle.Checked }, - new ("_Quit", "", () => Quit ()) - } - ) + new ( + "_File", + new [] + { + _miMultiline = + new ( + "_Multiline", + "", + () => Multiline () + ) { CheckType = MenuItemCheckStyle.Checked }, + _miWrap = new ( + "_Word Wrap", + "", + () => WordWrap () + ) { CheckType = MenuItemCheckStyle.Checked }, + new ("_Quit", "", () => Quit ()) + } + ) ] }; appWindow.Add (menu); - _textViewTopLeft = new TextView + _textViewTopLeft = new() { Y = 1, Width = width, Height = _height, Text = text @@ -66,22 +66,22 @@ public class TextViewAutocompletePopup : Scenario _textViewTopLeft.DrawContent += TextViewTopLeft_DrawContent; appWindow.Add (_textViewTopLeft); - _textViewTopRight = new TextView + _textViewTopRight = new() { - X = Pos.AnchorEnd (width), Y = 1, + X = Pos.AnchorEnd (width), Y = 1, Width = width, Height = _height, Text = text }; _textViewTopRight.DrawContent += TextViewTopRight_DrawContent; appWindow.Add (_textViewTopRight); - _textViewBottomLeft = new TextView + _textViewBottomLeft = new() { Y = Pos.AnchorEnd (_height), Width = width, Height = _height, Text = text }; _textViewBottomLeft.DrawContent += TextViewBottomLeft_DrawContent; appWindow.Add (_textViewBottomLeft); - _textViewBottomRight = new TextView + _textViewBottomRight = new() { X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (_height), @@ -92,7 +92,7 @@ public class TextViewAutocompletePopup : Scenario _textViewBottomRight.DrawContent += TextViewBottomRight_DrawContent; appWindow.Add (_textViewBottomRight); - _textViewCentered = new TextView + _textViewCentered = new() { X = Pos.Center (), Y = Pos.Center (), @@ -111,13 +111,12 @@ public class TextViewAutocompletePopup : Scenario { new ( Application.QuitKey, - $"Quit", + "Quit", () => Quit () ), - _siMultiline = new Shortcut (Key.Empty, "", null), - _siWrap = new Shortcut (Key.Empty, "", null) + _siMultiline = new (Key.Empty, "", null), + _siWrap = new (Key.Empty, "", null) } - ); appWindow.Add (statusBar); @@ -154,15 +153,9 @@ public class TextViewAutocompletePopup : Scenario .ToList (); } - private void SetMultilineStatusText () - { - _siMultiline.Title = $"Multiline: {_miMultiline.Checked}"; - } + private void SetMultilineStatusText () { _siMultiline.Title = $"Multiline: {_miMultiline.Checked}"; } - private void SetWrapStatusText () - { - _siWrap.Title = $"WordWrap: {_miWrap.Checked}"; - } + private void SetWrapStatusText () { _siWrap.Title = $"WordWrap: {_miWrap.Checked}"; } private void TextViewBottomLeft_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewBottomLeft); } private void TextViewBottomRight_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewBottomRight); } private void TextViewCentered_DrawContent (object sender, DrawEventArgs e) { SetAllSuggestions (_textViewCentered); } diff --git a/UICatalog/Scenarios/Unicode.cs b/UICatalog/Scenarios/Unicode.cs index f2f84bd0d..d6c866b0d 100644 --- a/UICatalog/Scenarios/Unicode.cs +++ b/UICatalog/Scenarios/Unicode.cs @@ -10,7 +10,7 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Controls")] public class UnicodeInMenu : Scenario { - public override void Setup () + public override void Main () { var unicode = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ\nτὸ σπίτι φτωχικὸ στὶς ἀμμουδιὲς τοῦ Ὁμήρου.\nΜονάχη ἔγνοια ἡ γλῶσσα μου στὶς ἀμμουδιὲς τοῦ Ὁμήρου."; @@ -28,6 +28,15 @@ public class UnicodeInMenu : Scenario CM.Glyphs.HorizontalEllipsis }"; + // Init + Application.Init (); + + // Setup - Create a top-level application window and configure it. + Window appWindow = new () + { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", + }; + var menu = new MenuBar { Menus = @@ -60,26 +69,24 @@ public class UnicodeInMenu : Scenario ) ] }; - Top.Add (menu); + appWindow.Add (menu); var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] + new Shortcut [] { new ( Application.QuitKey, - $"{Application.QuitKey} Выход", + $"Выход", () => Application.RequestStop () ), - new (KeyCode.Null, "~F2~ Создать", null), - new (KeyCode.Null, "~F3~ Со_хранить", null) + new (Key.F2, "Создать", null), + new (Key.F3, "Со_хранить", null) } -#endif ); - Top.Add (statusBar); + appWindow.Add (statusBar); var label = new Label { X = 0, Y = 1, Text = "Label:" }; - Win.Add (label); + appWindow.Add (label); var testlabel = new Label { @@ -89,10 +96,10 @@ public class UnicodeInMenu : Scenario Width = Dim.Percent (50), Text = gitString }; - Win.Add (testlabel); + appWindow.Add (testlabel); label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "Label (CanFocus):" }; - Win.Add (label); + appWindow.Add (label); var sb = new StringBuilder (); sb.Append ('e'); sb.Append ('\u0301'); @@ -108,14 +115,14 @@ public class UnicodeInMenu : Scenario HotKeySpecifier = new ('&'), Text = $"Should be [e with two accents, but isn't due to #2616]: [{sb}]" }; - Win.Add (testlabel); + appWindow.Add (testlabel); label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "Button:" }; - Win.Add (label); + appWindow.Add (label); var button = new Button { X = 20, Y = Pos.Y (label), Text = "A123456789♥♦♣♠JQK" }; - Win.Add (button); + appWindow.Add (button); label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "CheckBox:" }; - Win.Add (label); + appWindow.Add (label); var checkBox = new CheckBox { @@ -137,27 +144,27 @@ public class UnicodeInMenu : Scenario TextAlignment = Alignment.End, Text = $"End - {gitString}" }; - Win.Add (checkBox, checkBoxRight); + appWindow.Add (checkBox, checkBoxRight); label = new() { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" }; - Win.Add (label); + appWindow.Add (label); var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) }; comboBox.SetSource (new ObservableCollection { gitString, "Со_хранить" }); - Win.Add (comboBox); + appWindow.Add (comboBox); comboBox.Text = gitString; label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 2, Text = "HexView:" }; - Win.Add (label); + appWindow.Add (label); var hexView = new HexView (new MemoryStream (Encoding.ASCII.GetBytes (gitString + " Со_хранить"))) { X = 20, Y = Pos.Y (label), Width = Dim.Percent (60), Height = 5 }; - Win.Add (hexView); + appWindow.Add (hexView); label = new() { X = Pos.X (label), Y = Pos.Bottom (hexView) + 1, Text = "ListView:" }; - Win.Add (label); + appWindow.Add (label); var listView = new ListView { @@ -169,10 +176,10 @@ public class UnicodeInMenu : Scenario ["item #1", gitString, "Со_хранить", unicode] ) }; - Win.Add (listView); + appWindow.Add (listView); label = new() { X = Pos.X (label), Y = Pos.Bottom (listView) + 1, Text = "RadioGroup:" }; - Win.Add (label); + appWindow.Add (label); var radioGroup = new RadioGroup { @@ -181,19 +188,19 @@ public class UnicodeInMenu : Scenario Width = Dim.Percent (60), RadioLabels = new [] { "item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ" } }; - Win.Add (radioGroup); + appWindow.Add (radioGroup); label = new() { X = Pos.X (label), Y = Pos.Bottom (radioGroup) + 1, Text = "TextField:" }; - Win.Add (label); + appWindow.Add (label); var textField = new TextField { X = 20, Y = Pos.Y (label), Width = Dim.Percent (60), Text = gitString + " = Со_хранить" }; - Win.Add (textField); + appWindow.Add (textField); label = new() { X = Pos.X (label), Y = Pos.Bottom (textField) + 1, Text = "TextView:" }; - Win.Add (label); + appWindow.Add (label); var textView = new TextView { @@ -203,6 +210,15 @@ public class UnicodeInMenu : Scenario Height = 5, Text = unicode }; - Win.Add (textView); + appWindow.Add (textView); + + + // Run - Start the application. + Application.Run (appWindow); + + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); } } From d49701b5c38bca5447063e659ffc085194c50789 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:39:44 -0700 Subject: [PATCH 54/85] Updated TreeuseCases --- UICatalog/Scenarios/TreeUseCases.cs | 58 ++++++++++++++--------------- UICatalog/Scenarios/Unicode.cs | 25 ++++++------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/UICatalog/Scenarios/TreeUseCases.cs b/UICatalog/Scenarios/TreeUseCases.cs index 274e2d4be..57cee729a 100644 --- a/UICatalog/Scenarios/TreeUseCases.cs +++ b/UICatalog/Scenarios/TreeUseCases.cs @@ -11,11 +11,13 @@ public class TreeUseCases : Scenario { private View _currentTree; - public override void Setup () + public override void Main () { - Win.Title = GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + // Init + Application.Init (); + + // Setup - Create a top-level application window and configure it. + Toplevel appWindow = new (); var menu = new MenuBar { @@ -47,25 +49,23 @@ public class TreeUseCases : Scenario ] }; - Top.Add (menu); + appWindow.Add (menu); - var statusBar = new StatusBar ( -#if V2_STATUSBAR - new StatusItem [] - { - new ( - Application.QuitKey, - $"{Application.QuitKey} to Quit", - () => Quit () - ) - } -#endif - ); + var statusBar = new StatusBar ([new (Application.QuitKey, "Quit", Quit)]); - Top.Add (statusBar); + appWindow.Add (statusBar); + + appWindow.Ready += (sender, args) => + // Start with the most basic use case + LoadSimpleNodes (); + + // Run - Start the application. + Application.Run (appWindow); + appWindow.Dispose (); + + // Shutdown - Calling Application.Shutdown is required. + Application.Shutdown (); - // Start with the most basic use case - LoadSimpleNodes (); } private void LoadArmies (bool useDelegate) @@ -78,11 +78,11 @@ public class TreeUseCases : Scenario if (_currentTree != null) { - Win.Remove (_currentTree); + Application.Top.Remove (_currentTree); _currentTree.Dispose (); } - TreeView tree = new () { X = 0, Y = 0, Width = 40, Height = 20 }; + TreeView tree = new () { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; if (useDelegate) { @@ -98,7 +98,7 @@ public class TreeUseCases : Scenario tree.TreeBuilder = new GameObjectTreeBuilder (); } - Win.Add (tree); + Application.Top.Add (tree); tree.AddObject (army1); @@ -118,13 +118,13 @@ public class TreeUseCases : Scenario if (_currentTree != null) { - Win.Remove (_currentTree); + Application.Top.Remove (_currentTree); _currentTree.Dispose (); } - var tree = new TreeView { X = 0, Y = 0, Width = 40, Height = 20 }; + var tree = new TreeView { X = 0, Y = 1, Width = Dim.Fill(), Height = Dim.Fill (1) }; - Win.Add (tree); + Application.Top.Add (tree); tree.AddObject (myHouse); @@ -135,13 +135,13 @@ public class TreeUseCases : Scenario { if (_currentTree != null) { - Win.Remove (_currentTree); + Application.Top.Remove (_currentTree); _currentTree.Dispose (); } - var tree = new TreeView { X = 0, Y = 0, Width = 40, Height = 20 }; + var tree = new TreeView { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; - Win.Add (tree); + Application.Top.Add (tree); var root1 = new TreeNode ("Root1"); root1.Children.Add (new TreeNode ("Child1.1")); diff --git a/UICatalog/Scenarios/Unicode.cs b/UICatalog/Scenarios/Unicode.cs index d6c866b0d..384369e12 100644 --- a/UICatalog/Scenarios/Unicode.cs +++ b/UICatalog/Scenarios/Unicode.cs @@ -34,7 +34,7 @@ public class UnicodeInMenu : Scenario // Setup - Create a top-level application window and configure it. Window appWindow = new () { - Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" }; var menu = new MenuBar @@ -76,7 +76,7 @@ public class UnicodeInMenu : Scenario { new ( Application.QuitKey, - $"Выход", + "Выход", () => Application.RequestStop () ), new (Key.F2, "Создать", null), @@ -98,14 +98,14 @@ public class UnicodeInMenu : Scenario }; appWindow.Add (testlabel); - label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "Label (CanFocus):" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "Label (CanFocus):" }; appWindow.Add (label); var sb = new StringBuilder (); sb.Append ('e'); sb.Append ('\u0301'); sb.Append ('\u0301'); - testlabel = new() + testlabel = new () { X = 20, Y = Pos.Y (label), @@ -116,12 +116,12 @@ public class UnicodeInMenu : Scenario Text = $"Should be [e with two accents, but isn't due to #2616]: [{sb}]" }; appWindow.Add (testlabel); - label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "Button:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "Button:" }; appWindow.Add (label); var button = new Button { X = 20, Y = Pos.Y (label), Text = "A123456789♥♦♣♠JQK" }; appWindow.Add (button); - label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "CheckBox:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (label) + 1, Text = "CheckBox:" }; appWindow.Add (label); var checkBox = new CheckBox @@ -146,7 +146,7 @@ public class UnicodeInMenu : Scenario }; appWindow.Add (checkBox, checkBoxRight); - label = new() { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1, Text = "ComboBox:" }; appWindow.Add (label); var comboBox = new ComboBox { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) }; comboBox.SetSource (new ObservableCollection { gitString, "Со_хранить" }); @@ -154,7 +154,7 @@ public class UnicodeInMenu : Scenario appWindow.Add (comboBox); comboBox.Text = gitString; - label = new() { X = Pos.X (label), Y = Pos.Bottom (label) + 2, Text = "HexView:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (label) + 2, Text = "HexView:" }; appWindow.Add (label); var hexView = new HexView (new MemoryStream (Encoding.ASCII.GetBytes (gitString + " Со_хранить"))) @@ -163,7 +163,7 @@ public class UnicodeInMenu : Scenario }; appWindow.Add (hexView); - label = new() { X = Pos.X (label), Y = Pos.Bottom (hexView) + 1, Text = "ListView:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (hexView) + 1, Text = "ListView:" }; appWindow.Add (label); var listView = new ListView @@ -178,7 +178,7 @@ public class UnicodeInMenu : Scenario }; appWindow.Add (listView); - label = new() { X = Pos.X (label), Y = Pos.Bottom (listView) + 1, Text = "RadioGroup:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (listView) + 1, Text = "RadioGroup:" }; appWindow.Add (label); var radioGroup = new RadioGroup @@ -190,7 +190,7 @@ public class UnicodeInMenu : Scenario }; appWindow.Add (radioGroup); - label = new() { X = Pos.X (label), Y = Pos.Bottom (radioGroup) + 1, Text = "TextField:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (radioGroup) + 1, Text = "TextField:" }; appWindow.Add (label); var textField = new TextField @@ -199,7 +199,7 @@ public class UnicodeInMenu : Scenario }; appWindow.Add (textField); - label = new() { X = Pos.X (label), Y = Pos.Bottom (textField) + 1, Text = "TextView:" }; + label = new () { X = Pos.X (label), Y = Pos.Bottom (textField) + 1, Text = "TextView:" }; appWindow.Add (label); var textView = new TextView @@ -212,7 +212,6 @@ public class UnicodeInMenu : Scenario }; appWindow.Add (textView); - // Run - Start the application. Application.Run (appWindow); From 16174651401f7ea20d60418fbebeaf3bccf62c19 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 22:46:47 -0700 Subject: [PATCH 55/85] Updated DynamicShortcut. Broken. --- UICatalog/Scenarios/DynamicStatusBar.cs | 28 +++++++++++-------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs index 08eb9d512..af29faa8e 100644 --- a/UICatalog/Scenarios/DynamicStatusBar.cs +++ b/UICatalog/Scenarios/DynamicStatusBar.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; @@ -8,20 +9,14 @@ using Terminal.Gui; namespace UICatalog.Scenarios; -#if V2_STATUSBAR [ScenarioMetadata ("Dynamic StatusBar", "Demonstrates how to add and remove a StatusBar and change items dynamically.")] [ScenarioCategory ("Top Level Windows")] public class DynamicStatusBar : Scenario { - public override void Init () + public override void Main () { - Application.Init (); - Top = new (); - - Top.Add ( - new DynamicStatusBarSample { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" } - ); + Application.Run ().Dispose(); } public class Binding @@ -214,7 +209,7 @@ public class DynamicStatusBar : Scenario ? GetTargetAction (statusItem.Action) : string.Empty; - TextShortcut.Text = statusItem.Shortcut; + TextShortcut.Text = statusItem.CommandView.Text; } public DynamicStatusItem EnterStatusItem () @@ -321,6 +316,8 @@ public class DynamicStatusBar : Scenario { DataContext = new DynamicStatusItemModel (); + Title = $"{Application.QuitKey} to Quit"; + var _frmStatusBar = new FrameView { Y = 5, Width = Dim.Percent (50), Height = Dim.Fill (2), Title = "Items:" @@ -376,7 +373,7 @@ public class DynamicStatusBar : Scenario if (statusItem != null) { - Shortcut [] items = _statusBar.Items; + Shortcut [] items = _statusBar.Subviews.Cast ().ToArray (); if (i > 0) { @@ -399,7 +396,7 @@ public class DynamicStatusBar : Scenario if (statusItem != null) { - Shortcut [] items = _statusBar.Items; + Shortcut [] items = _statusBar.Subviews.Cast ().ToArray (); if (i < items.Length - 1) { @@ -488,7 +485,7 @@ public class DynamicStatusBar : Scenario if (statusItem != null) { - _statusBar.RemoveItem (_currentSelectedStatusBar); + _statusBar.RemoveShortcut (_currentSelectedStatusBar); DataContext.Items.RemoveAt (_lstItems.SelectedItem); if (_lstItems.Source.Count > 0 && _lstItems.SelectedItem > _lstItems.Source.Count - 1) @@ -581,7 +578,7 @@ public class DynamicStatusBar : Scenario if (statusItem != null) { - foreach (Shortcut si in _statusBar.Items) + foreach (Shortcut si in _statusBar.Subviews.Cast ()) { DataContext.Items.Add (new DynamicStatusItemList (si.Title, si)); } @@ -590,7 +587,7 @@ public class DynamicStatusBar : Scenario Shortcut CreateNewStatusBar (DynamicStatusItem item) { - var newStatusItem = new Shortcut (item.Shortcut); + var newStatusItem = new Shortcut (Key.Empty, item.Title, null); return newStatusItem; } @@ -602,7 +599,7 @@ public class DynamicStatusBar : Scenario ) { _currentEditStatusItem = CreateNewStatusBar (statusItem); - _statusBar.Items [index] = _currentEditStatusItem; + //_statusBar.Items [index] = _currentEditStatusItem; if (DataContext.Items.Count == 0) { @@ -735,4 +732,3 @@ public class DynamicStatusBar : Scenario } } } -#endif \ No newline at end of file From a5494d59e7dd5a8edd5c2f9786b5b78b556a4d61 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 16 Jun 2024 23:06:23 -0700 Subject: [PATCH 56/85] Everything but DynamicShortcut scenario is working --- UnitTests/View/NavigationTests.cs | 222 +++++++++++++++--------------- UnitTests/Views/StatusBarTests.cs | 181 +++++++++--------------- UnitTests/Views/WindowTests.cs | 17 +-- 3 files changed, 178 insertions(+), 242 deletions(-) diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/NavigationTests.cs index a23f29359..3ae1ac500 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/NavigationTests.cs @@ -620,135 +620,133 @@ public class NavigationTests (ITestOutputHelper output) top1.Dispose (); } -#if V2_STATUSBAR - [Fact] - [AutoInitShutdown] - public void HotKey_Will_Invoke_KeyPressed_Only_For_The_MostFocused_With_Top_KeyPress_Event () - { - var sbQuiting = false; - var tfQuiting = false; - var topQuiting = false; +// [Fact] +// [AutoInitShutdown] +// public void HotKey_Will_Invoke_KeyPressed_Only_For_The_MostFocused_With_Top_KeyPress_Event () +// { +// var sbQuiting = false; +// var tfQuiting = false; +// var topQuiting = false; - var sb = new StatusBar ( - new StatusItem [] - { - new ( - KeyCode.CtrlMask | KeyCode.Q, - "~^Q~ Quit", - () => sbQuiting = true - ) - } - ); - var tf = new TextField (); - tf.KeyDown += Tf_KeyPressed; +// var sb = new StatusBar ( +// new Shortcut [] +// { +// new ( +// KeyCode.CtrlMask | KeyCode.Q, +// "Quit", +// () => sbQuiting = true +// ) +// } +// ); +// var tf = new TextField (); +// tf.KeyDown += Tf_KeyPressed; - void Tf_KeyPressed (object sender, Key obj) - { - if (obj.KeyCode == (KeyCode.Q | KeyCode.CtrlMask)) - { - obj.Handled = tfQuiting = true; - } - } +// void Tf_KeyPressed (object sender, Key obj) +// { +// if (obj.KeyCode == (KeyCode.Q | KeyCode.CtrlMask)) +// { +// obj.Handled = tfQuiting = true; +// } +// } - var win = new Window (); - win.Add (sb, tf); - Toplevel top = new (); - top.KeyDown += Top_KeyPress; +// var win = new Window (); +// win.Add (sb, tf); +// Toplevel top = new (); +// top.KeyDown += Top_KeyPress; - void Top_KeyPress (object sender, Key obj) - { - if (obj.KeyCode == (KeyCode.Q | KeyCode.CtrlMask)) - { - obj.Handled = topQuiting = true; - } - } +// void Top_KeyPress (object sender, Key obj) +// { +// if (obj.KeyCode == (KeyCode.Q | KeyCode.CtrlMask)) +// { +// obj.Handled = topQuiting = true; +// } +// } - top.Add (win); - Application.Begin (top); +// top.Add (win); +// Application.Begin (top); - Assert.False (sbQuiting); - Assert.False (tfQuiting); - Assert.False (topQuiting); +// Assert.False (sbQuiting); +// Assert.False (tfQuiting); +// Assert.False (topQuiting); - Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); - Assert.False (sbQuiting); - Assert.True (tfQuiting); - Assert.False (topQuiting); +// Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); +// Assert.False (sbQuiting); +// Assert.True (tfQuiting); +// Assert.False (topQuiting); -#if BROKE_WITH_2927 - tf.KeyPressed -= Tf_KeyPress; - tfQuiting = false; - Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); - Application.MainLoop.RunIteration (); - Assert.True (sbQuiting); - Assert.False (tfQuiting); - Assert.False (topQuiting); +//#if BROKE_WITH_2927 +// tf.KeyPressed -= Tf_KeyPress; +// tfQuiting = false; +// Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); +// Application.MainLoop.RunIteration (); +// Assert.True (sbQuiting); +// Assert.False (tfQuiting); +// Assert.False (topQuiting); - sb.RemoveItem (0); - sbQuiting = false; - Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); - Application.MainLoop.RunIteration (); - Assert.False (sbQuiting); - Assert.False (tfQuiting); +// sb.RemoveItem (0); +// sbQuiting = false; +// Application.Driver.SendKeys ('q', ConsoleKey.Q, false, false, true); +// Application.MainLoop.RunIteration (); +// Assert.False (sbQuiting); +// Assert.False (tfQuiting); -// This test is now invalid because `win` is focused, so it will receive the keypress - Assert.True (topQuiting); -#endif - top.Dispose (); - } +//// This test is now invalid because `win` is focused, so it will receive the keypress +// Assert.True (topQuiting); +//#endif +// top.Dispose (); +// } - [Fact] - [AutoInitShutdown] - public void HotKey_Will_Invoke_KeyPressed_Only_For_The_MostFocused_Without_Top_KeyPress_Event () - { - var sbQuiting = false; - var tfQuiting = false; +// [Fact] +// [AutoInitShutdown] +// public void HotKey_Will_Invoke_KeyPressed_Only_For_The_MostFocused_Without_Top_KeyPress_Event () +// { +// var sbQuiting = false; +// var tfQuiting = false; - var sb = new StatusBar ( - new StatusItem [] - { - new ( - KeyCode.CtrlMask | KeyCode.Q, - "~^Q~ Quit", - () => sbQuiting = true - ) - } - ); - var tf = new TextField (); - tf.KeyDown += Tf_KeyPressed; +// var sb = new StatusBar ( +// new Shortcut [] +// { +// new ( +// KeyCode.CtrlMask | KeyCode.Q, +// "~^Q~ Quit", +// () => sbQuiting = true +// ) +// } +// ); +// var tf = new TextField (); +// tf.KeyDown += Tf_KeyPressed; - void Tf_KeyPressed (object sender, Key obj) - { - if (obj.KeyCode == (KeyCode.Q | KeyCode.CtrlMask)) - { - obj.Handled = tfQuiting = true; - } - } +// void Tf_KeyPressed (object sender, Key obj) +// { +// if (obj.KeyCode == (KeyCode.Q | KeyCode.CtrlMask)) +// { +// obj.Handled = tfQuiting = true; +// } +// } - var win = new Window (); - win.Add (sb, tf); - Toplevel top = new (); - top.Add (win); - Application.Begin (top); +// var win = new Window (); +// win.Add (sb, tf); +// Toplevel top = new (); +// top.Add (win); +// Application.Begin (top); - Assert.False (sbQuiting); - Assert.False (tfQuiting); +// Assert.False (sbQuiting); +// Assert.False (tfQuiting); - Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); - Assert.False (sbQuiting); - Assert.True (tfQuiting); +// Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); +// Assert.False (sbQuiting); +// Assert.True (tfQuiting); - tf.KeyDown -= Tf_KeyPressed; - tfQuiting = false; - Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); - Application.MainLoop.RunIteration (); -#if BROKE_WITH_2927 - Assert.True (sbQuiting); - Assert.False (tfQuiting); -#endif - top.Dispose (); - } -#endif +// tf.KeyDown -= Tf_KeyPressed; +// tfQuiting = false; +// Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); +// Application.MainLoop.RunIteration (); +//#if BROKE_WITH_2927 +// Assert.True (sbQuiting); +// Assert.False (tfQuiting); +//#endif +// top.Dispose (); +// } [Fact] [SetupFakeDriver] diff --git a/UnitTests/Views/StatusBarTests.cs b/UnitTests/Views/StatusBarTests.cs index d702dd95a..ef710c332 100644 --- a/UnitTests/Views/StatusBarTests.cs +++ b/UnitTests/Views/StatusBarTests.cs @@ -1,137 +1,97 @@ using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; -#if V2_STATUSBAR public class StatusBarTests (ITestOutputHelper output) { [Fact] public void AddItemAt_RemoveItem_Replacing () { - var sb = new StatusBar ( - new StatusItem [] - { - new (KeyCode.CtrlMask | KeyCode.Q, "~^O~ Open", null), - new (KeyCode.CtrlMask | KeyCode.Q, "~^S~ Save", null), - new (KeyCode.CtrlMask | KeyCode.Q, "~^Q~ Quit", null) - } + var sb = new StatusBar ([ + new (Key.O.WithCtrl, "Open", null), + new (Key.S.WithCtrl, "Save", null), + new (Key.Q.WithCtrl, "Quit", null) + ] ); - sb.AddShortcutAt (2, new (KeyCode.CtrlMask | KeyCode.Q, "~^C~ Close", null)); + sb.AddShortcutAt (2, new (Key.C.WithCtrl, "Close", null)); - Assert.Equal ("~^O~ Open", sb.Items [0].Title); - Assert.Equal ("~^S~ Save", sb.Items [1].Title); - Assert.Equal ("~^C~ Close", sb.Items [2].Title); - Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title); + Assert.Equal ("Open", sb.Subviews [0].Title); + Assert.Equal ("Save", sb.Subviews [1].Title); + Assert.Equal ("Close", sb.Subviews [2].Title); + Assert.Equal ("Quit", sb.Subviews [^1].Title); - Assert.Equal ("~^S~ Save", sb.RemoveItem (1).Title); + Assert.Equal ("Save", sb.RemoveShortcut (1).Title); - Assert.Equal ("~^O~ Open", sb.Items [0].Title); - Assert.Equal ("~^C~ Close", sb.Items [1].Title); - Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title); + Assert.Equal ("Open", sb.Subviews [0].Title); + Assert.Equal ("Close", sb.Subviews [1].Title); + Assert.Equal ("Quit", sb.Subviews [^1].Title); - sb.Items [1] = new (KeyCode.CtrlMask | KeyCode.A, "~^A~ Save As", null); + sb.AddShortcutAt (1, new Shortcut (Key.A.WithCtrl, "Save As", null)); - Assert.Equal ("~^O~ Open", sb.Items [0].Title); - Assert.Equal ("~^A~ Save As", sb.Items [1].Title); - Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title); + Assert.Equal ("Open", sb.Subviews [0].Title); + Assert.Equal ("Save As", sb.Subviews [1].Title); + Assert.Equal ("Quit", sb.Subviews [^1].Title); } - [Fact] - [AutoInitShutdown] - public void CanExecute_ProcessHotKey () - { - Window win = null; + //[Fact] + //[AutoInitShutdown] + //public void CanExecute_ProcessHotKey () + //{ + // Window win = null; - var statusBar = new StatusBar ( - new StatusItem [] - { - new ( - KeyCode.CtrlMask | KeyCode.N, - "~^N~ New", - New, - CanExecuteNew - ), - new ( - KeyCode.CtrlMask | KeyCode.C, - "~^C~ Close", - Close, - CanExecuteClose - ) - } - ); - Toplevel top = new (); - top.Add (statusBar); + // var statusBar = new StatusBar ( + // new Shortcut [] + // { + // new ( + // KeyCode.CtrlMask | KeyCode.N, + // "~^N~ New", + // New, + // CanExecuteNew + // ), + // new ( + // KeyCode.CtrlMask | KeyCode.C, + // "~^C~ Close", + // Close, + // CanExecuteClose + // ) + // } + // ); + // Toplevel top = new (); + // top.Add (statusBar); - bool CanExecuteNew () { return win == null; } + // bool CanExecuteNew () { return win == null; } - void New () { win = new (); } + // void New () { win = new (); } - bool CanExecuteClose () { return win != null; } + // bool CanExecuteClose () { return win != null; } - void Close () { win = null; } + // void Close () { win = null; } - Application.Begin (top); + // Application.Begin (top); - Assert.Null (win); - Assert.True (CanExecuteNew ()); - Assert.False (CanExecuteClose ()); + // Assert.Null (win); + // Assert.True (CanExecuteNew ()); + // Assert.False (CanExecuteClose ()); - Assert.True (top.NewKeyDownEvent (Key.N.WithCtrl)); - Application.MainLoop.RunIteration (); - Assert.NotNull (win); - Assert.False (CanExecuteNew ()); - Assert.True (CanExecuteClose ()); - top.Dispose (); - } + // Assert.True (top.NewKeyDownEvent (Key.N.WithCtrl)); + // Application.MainLoop.RunIteration (); + // Assert.NotNull (win); + // Assert.False (CanExecuteNew ()); + // Assert.True (CanExecuteClose ()); + // top.Dispose (); + //} [Fact] [AutoInitShutdown] public void Redraw_Output () { - var sb = new StatusBar ( - new StatusItem [] - { - new (KeyCode.CtrlMask | KeyCode.O, "~^O~ Open", null), - new (Application.QuitKey, $"{Application.QuitKey} to Quit!", null) - } - ); - var top = new Toplevel (); - top.Add (sb); - - sb.OnDrawContent (sb.Viewport); - - var expected = @$" -^O Open { - CM.Glyphs.VLine -} Ctrl+Q to Quit! -"; - TestHelpers.AssertDriverContentsAre (expected, output); - top.Dispose (); } [Fact] [AutoInitShutdown] public void Redraw_Output_CTRLQ () { - var sb = new StatusBar ( - new StatusItem [] - { - new (KeyCode.CtrlMask | KeyCode.O, "~CTRL-O~ Open", null), - new (KeyCode.CtrlMask | KeyCode.Q, "~CTRL-Q~ Quit", null) - } - ); - var top = new Toplevel (); - top.Add (sb); - sb.OnDrawContent (sb.Viewport); - var expected = @$" -CTRL-O Open { - CM.Glyphs.VLine -} CTRL-Q Quit -"; - - TestHelpers.AssertDriverContentsAre (expected, output); - top.Dispose (); } [Fact] @@ -141,11 +101,11 @@ CTRL-O Open { var msg = ""; var sb = new StatusBar ( - new StatusItem [] + new Shortcut [] { new ( Application.QuitKey, - $"{Application.QuitKey} to Quit", + $"Quit", () => msg = "Quiting..." ) } @@ -157,13 +117,13 @@ CTRL-O Open { if (iteration == 0) { Assert.Equal ("", msg); - sb.NewKeyDownEvent (Key.Q.WithCtrl); + Application.OnKeyDown (Application.QuitKey); } else if (iteration == 1) { Assert.Equal ("Quiting...", msg); msg = ""; - sb.NewMouseEvent (new() { Position = new (1, 24), Flags = MouseFlags.Button1Clicked }); + sb.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }); } else { @@ -183,26 +143,13 @@ CTRL-O Open { { var sb = new StatusBar (); - Assert.Empty (sb.Items); - Assert.False (sb.CanFocus); + Assert.Empty (sb.Subviews); + Assert.True (sb.CanFocus); Assert.Equal (Colors.ColorSchemes ["Menu"], sb.ColorScheme); Assert.Equal (0, sb.X); Assert.Equal ("AnchorEnd()", sb.Y.ToString ()); Assert.Equal (Dim.Fill (), sb.Width); - Assert.Equal (1, sb.Height); + Assert.Equal (1, sb.Frame.Height); } - [Fact] - public void StatusItem_Constructor () - { - Application.Init (); - var si = new StatusItem (Application.QuitKey, $"{Application.QuitKey} to Quit", null); - Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, si.Shortcut); - Assert.Equal ($"{Application.QuitKey} to Quit", si.Title); - Assert.Null (si.Action); - si = new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => { }); - Assert.NotNull (si.Action); - Application.Shutdown (); - } } -#endif diff --git a/UnitTests/Views/WindowTests.cs b/UnitTests/Views/WindowTests.cs index 26956ff42..24ea77876 100644 --- a/UnitTests/Views/WindowTests.cs +++ b/UnitTests/Views/WindowTests.cs @@ -29,7 +29,6 @@ public class WindowTests top.Dispose (); } -#if V2_STATUSBAR [Fact] [AutoInitShutdown] public void MenuBar_And_StatusBar_Inside_Window () @@ -46,14 +45,7 @@ public class WindowTests ] }; - var sb = new StatusBar ( - new StatusItem [] - { - new ((KeyCode)Key.Q.WithCtrl, "~^Q~ Quit", null), - new ((KeyCode)Key.O.WithCtrl, "~^O~ Open", null), - new ((KeyCode)Key.C.WithCtrl, "~^C~ Copy", null) - } - ); + var sb = new StatusBar (); var fv = new FrameView { Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1), Title = "Frame View" }; var win = new Window (); @@ -73,7 +65,7 @@ public class WindowTests ││ ││ ││ ││ │└────────────────┘│ -│ ^Q Quit │ ^O Open│ +│ │ └──────────────────┘", _output ); @@ -100,7 +92,7 @@ public class WindowTests ││ ││ ││ ││ │└────────────────────────────────────┘│ -│ ^Q Quit │ ^O Open │ ^C Copy │ +│ │ └──────────────────────────────────────┘", _output ); @@ -117,13 +109,12 @@ public class WindowTests ││ ││ ││ ││ │└────────────────┘│ -│ ^Q Quit │ ^O Open│ +│ │ └──────────────────┘", _output ); top.Dispose (); } -#endif [Fact] public void New_Initializes () From c0f7af4bd9ea6afa9c9ce0e807a74c459a754365 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 06:42:39 -0700 Subject: [PATCH 57/85] Added statusbar test --- UnitTests/Views/StatusBarTests.cs | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/UnitTests/Views/StatusBarTests.cs b/UnitTests/Views/StatusBarTests.cs index ef710c332..ce404f7b5 100644 --- a/UnitTests/Views/StatusBarTests.cs +++ b/UnitTests/Views/StatusBarTests.cs @@ -152,4 +152,55 @@ public class StatusBarTests (ITestOutputHelper output) Assert.Equal (1, sb.Frame.Height); } + [Fact] + public void RemoveAndThenAddStatusBar_ShouldNotChangeWidth () + { + StatusBar statusBar; + StatusBar statusBar2; + + var w = new Window (); + statusBar2 = new StatusBar () { Id = "statusBar2" }; + statusBar = new StatusBar () { Id = "statusBar" }; + w.Width = Dim.Fill (0); + w.Height = Dim.Fill (0); + w.X = 0; + w.Y = 0; + + w.Visible = true; + w.Modal = false; + w.Title = ""; + statusBar.Width = Dim.Fill (0); + statusBar.Height = 1; + statusBar.X = 0; + statusBar.Y = 0; + statusBar.Visible = true; + w.Add (statusBar); + Assert.Equal (w.StatusBar, statusBar); + + statusBar2.Width = Dim.Fill (0); + statusBar2.Height = 1; + statusBar2.X = 0; + statusBar2.Y = 4; + statusBar2.Visible = true; + w.Add (statusBar2); + Assert.Equal (w.StatusBar, statusBar2); + + var menuBars = w.Subviews.OfType ().ToArray (); + Assert.Equal (2, menuBars.Length); + + Assert.Equal (Dim.Fill (0), menuBars [0].Width); + Assert.Equal (Dim.Fill (0), menuBars [1].Width); + + // Goes wrong here + w.Remove (statusBar); + w.Remove (statusBar2); + + w.Add (statusBar); + w.Add (statusBar2); + + // These assertions fail + Assert.Equal (Dim.Fill (0), menuBars [0].Width); + Assert.Equal (Dim.Fill (0), menuBars [1].Width); + } + } From 71e5118cdb09caf2a66eb14a1f681ce682bd146e Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 06:53:57 -0700 Subject: [PATCH 58/85] Fixed warnings --- Terminal.Gui/Application/Application.cs | 6 +++--- Terminal.Gui/Input/CommandContext.cs | 2 +- Terminal.Gui/View/Layout/Pos.cs | 8 +++++++- Terminal.Gui/View/ViewAdornments.cs | 5 ++++- Terminal.Gui/View/ViewKeyboard.cs | 5 +++-- Terminal.Gui/Views/Bar.cs | 6 ++---- Terminal.Gui/Views/Dialog.cs | 2 +- Terminal.Gui/Views/OpenDialog.cs | 2 +- Terminal.Gui/Views/SaveDialog.cs | 2 +- Terminal.Gui/Views/Shortcut.cs | 26 ++++++------------------- Terminal.Gui/Views/TextView.cs | 1 - Terminal.Gui/Views/Toplevel.cs | 6 +++--- Terminal.Gui/Views/Wizard/Wizard.cs | 2 +- 13 files changed, 33 insertions(+), 40 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index f1b161118..741c7ab75 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -177,15 +177,15 @@ public static partial class Application /// /// /// must be called when the application is closing (typically after - /// has returned) to ensure resources are cleaned up and + /// has returned) to ensure resources are cleaned up and /// terminal settings /// restored. /// /// - /// The function combines + /// The function combines /// and /// into a single - /// call. An application cam use without explicitly calling + /// call. An application cam use without explicitly calling /// . /// /// diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs index 555b179c8..5b6c23557 100644 --- a/Terminal.Gui/Input/CommandContext.cs +++ b/Terminal.Gui/Input/CommandContext.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui; #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// Provides context for a that is being invoked. -/// /// /// /// To define a that is invoked with context, diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index f6966e64f..2213524ae 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -336,10 +336,16 @@ public abstract class Pos /// internal virtual bool ReferencesOtherViews () { return false; } + /// + /// Indicates whether the specified type is in the hierarchy of this Pos object. + /// + /// + /// + /// public bool Has (Type type, out Pos pos) { pos = this; - if (type == GetType()) + if (type == GetType ()) { return true; } diff --git a/Terminal.Gui/View/ViewAdornments.cs b/Terminal.Gui/View/ViewAdornments.cs index bc2a93468..89b8ff0e7 100644 --- a/Terminal.Gui/View/ViewAdornments.cs +++ b/Terminal.Gui/View/ViewAdornments.cs @@ -107,7 +107,7 @@ public partial class View /// Called when the is changing. Invokes , which allows the event to be cancelled. /// /// - /// Override to prevent the from changing. Set to `true` to cancel the event. + /// Override to prevent the from changing. /// /// protected void OnBorderStyleChanging (StateEventArgs e) @@ -163,6 +163,9 @@ public partial class View Border.LineStyle = value; } + /// + /// Fired when the is changing. Allows the event to be cancelled. + /// public event EventHandler> BorderStyleChanging; /// diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 935765bf9..e43103381 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -783,10 +783,11 @@ public partial class View /// /// /// The key that caused the commands to be invoked, if any. + /// /// /// if no command was found. - /// if the command was invoked and it handled the command. - /// if the command was invoked and it did not handle the command. + /// if the command was invoked the command was handled. + /// if the command was invoked and the command was not handled. /// public bool? InvokeCommands (Command [] commands, [CanBeNull] Key key = null, [CanBeNull] KeyBinding? keyBinding = null) { diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 1c040436e..e95f9623f 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -85,7 +85,7 @@ public class Bar : View } } - /// Inserts a in the specified index of . + /// Inserts a in the specified index of . /// The zero-based index at which item should be inserted. /// The item to insert. public void AddShortcutAt (int index, Shortcut item) @@ -107,7 +107,7 @@ public class Bar : View SetNeedsDisplay (); } - /// Removes a at specified index of . + /// Removes a at specified index of . /// The zero-based index of the item to remove. /// The removed. public Shortcut RemoveShortcut (int index) @@ -157,8 +157,6 @@ public class Bar : View // All CommandView's are the same width, all HelpView's are the same width, // all KeyView's are the same width - var maxCommandWidth = 0; - var maxHelpWidth = 0; var minKeyWidth = 0; List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index 96a55f229..2ab97a98e 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -9,7 +9,7 @@ namespace Terminal.Gui; /// /// /// To run the modally, create the , and pass it to -/// . This will execute the dialog until +/// . This will execute the dialog until /// it terminates via the /// [ESC] or [CTRL-Q] key, or when one of the views or buttons added to the dialog calls /// . diff --git a/Terminal.Gui/Views/OpenDialog.cs b/Terminal.Gui/Views/OpenDialog.cs index ec7f8eb43..8197671d8 100644 --- a/Terminal.Gui/Views/OpenDialog.cs +++ b/Terminal.Gui/Views/OpenDialog.cs @@ -36,7 +36,7 @@ public enum OpenMode /// /// /// To use, create an instance of , and pass it to -/// . This will run the dialog modally, and when this returns, +/// . This will run the dialog modally, and when this returns, /// the list of files will be available on the property. /// /// To select more than one file, users can use the spacebar, or control-t. diff --git a/Terminal.Gui/Views/SaveDialog.cs b/Terminal.Gui/Views/SaveDialog.cs index f1075159d..61ffb88e4 100644 --- a/Terminal.Gui/Views/SaveDialog.cs +++ b/Terminal.Gui/Views/SaveDialog.cs @@ -17,7 +17,7 @@ namespace Terminal.Gui; /// /// /// To use, create an instance of , and pass it to -/// . This will run the dialog modally, and when this returns, +/// . This will run the dialog modally, and when this returns, /// the property will contain the selected file name or null if the user canceled. /// /// diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 0042d08c4..0e16fd714 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -8,18 +8,6 @@ namespace Terminal.Gui; // TODO: It can mean "Application-scoped key binding" or "A key binding that is displayed in a visual way". // TODO: I tried `BarItem` but that's not great either as it implies it can only be used in `Bar`s. -[Flags] -[GenerateEnumExtensionMethods (FastHasFlags = true)] -public enum ShortcutStyles -{ - None = 0, - - SeparatorBefore = 8, - - SeparatorAfter = 16, -} - - /// /// Displays a command, help text, and a key binding. When the key is pressed, the command will be invoked. Useful for /// displaying a command in such as a @@ -29,7 +17,7 @@ public enum ShortcutStyles /// /// When the user clicks on the or presses the key /// specified by the command is invoked, causing the -/// event to be fired +/// event to be fired /// /// /// If is , the @@ -53,13 +41,13 @@ public enum ShortcutStyles /// public class Shortcut : View { - /// /// Creates a new instance of ; /// /// - /// + /// /// + /// public Shortcut (Key key, string commandText, Action action, string helpText = null) { Id = "_shortcut"; @@ -166,8 +154,6 @@ public class Shortcut : View } } - public ShortcutStyles ShortcutStyle { get; set; } = ShortcutStyles.None; - // When one of the subviews is "empty" we don't want to show it. So we // Use Add/Remove. We need to be careful to add them in the right order // so Pos.Align works correctly. @@ -417,7 +403,7 @@ public class Shortcut : View { // When the CommandView fires its Accept event, we want to act as though the // Shortcut was clicked. - if (base.OnAccept() == true) + if (base.OnAccept () == true) { e.Cancel = true; } @@ -646,7 +632,7 @@ public class Shortcut : View /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the mouse. /// /// - /// Note, the event is fired first, and if cancelled, will not be invoked. + /// Note, the event is fired first, and if cancelled, the event will not be invoked. /// [CanBeNull] public Action Action { get; set; } @@ -691,7 +677,7 @@ public class Shortcut : View } // Set KeyView's colors to show "hot" - if (IsInitialized && base.ColorScheme is {}) + if (IsInitialized && base.ColorScheme is { }) { var cs = new ColorScheme (base.ColorScheme) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 8b4f17424..f97254410 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -1961,7 +1961,6 @@ public class TextView : View private readonly HistoryText _historyText = new (); private bool _allowsReturn = true; private bool _allowsTab = true; - private int _bottomOffset, _rightOffset; private bool _clickWithSelecting; // The column we are tracking, or -1 if we are not tracking any column diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 22af364f2..d9bd4d1be 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -7,7 +7,7 @@ namespace Terminal.Gui; /// /// /// Toplevels can run as modal (popup) views, started by calling -/// . They return control to the caller when +/// . They return control to the caller when /// has been called (which sets the /// property to false). /// @@ -15,7 +15,7 @@ namespace Terminal.Gui; /// A Toplevel is created when an application initializes Terminal.Gui by calling . /// The application Toplevel can be accessed via . Additional Toplevels can be created /// and run (e.g. s. To run a Toplevel, create the and call -/// . +/// . /// /// public partial class Toplevel : View @@ -445,7 +445,7 @@ public partial class Toplevel : View /// 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 - /// on this . + /// on this . /// /// public event EventHandler Ready; diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index ab81e515a..1f11036c1 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -125,7 +125,7 @@ public class Wizard : Dialog /// Add the Wizard to a containing view with . /// /// - /// If a non-Modal Wizard is added to the application after has + /// If a non-Modal Wizard is added to the application after has /// been called the first step must be explicitly set by setting to /// : /// From dfd4b9ed88a7976d426e4a070f2e0af76fdf992d Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 07:21:52 -0700 Subject: [PATCH 59/85] Cleanup --- Terminal.Gui/Views/Shortcut.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 0e16fd714..e235bb712 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -4,10 +4,6 @@ using Terminal.Gui.Analyzers.Internal.Attributes; namespace Terminal.Gui; -// TODO: I don't love the name Shortcut, but I can't think of a better one right now. Shortcut is a bit overloaded. -// TODO: It can mean "Application-scoped key binding" or "A key binding that is displayed in a visual way". -// TODO: I tried `BarItem` but that's not great either as it implies it can only be used in `Bar`s. - /// /// Displays a command, help text, and a key binding. When the key is pressed, the command will be invoked. Useful for /// displaying a command in such as a From 9e61ae8912ddeded39c409ff42cdffdbe6638373 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 07:49:41 -0700 Subject: [PATCH 60/85] Improved API docs --- Terminal.Gui/Views/Shortcut.cs | 65 +++++++++++++++++++--------------- docfx/docs/keyboard.md | 11 +++--- docfx/docs/migratingfromv1.md | 22 +++++++++++- docfx/docs/newinv2.md | 21 +++++------ 4 files changed, 73 insertions(+), 46 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index e235bb712..42c95ad70 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -1,6 +1,4 @@ using System.ComponentModel; -using System.Reflection.Metadata; -using Terminal.Gui.Analyzers.Internal.Attributes; namespace Terminal.Gui; @@ -21,11 +19,11 @@ namespace Terminal.Gui; /// be invoked regardless of what View has focus, enabling an application-wide keyboard shortcut. /// /// -/// A Shortcut displays the command text on the left side, the help text in the middle, and the key binding on the -/// right side. +/// By default, a Shortcut displays the command text on the left side, the help text in the middle, and the key binding on the +/// right side. Set to to reverse the order. /// /// -/// The command text can be set by setting the 's Text property. +/// The command text can be set by setting the 's Text property or by setting . /// /// /// The help text can be set by setting the property or by setting . @@ -38,8 +36,13 @@ namespace Terminal.Gui; public class Shortcut : View { /// - /// Creates a new instance of ; + /// Creates a new instance of . /// + /// + /// + /// This is a helper API that mimics the V1 API for creating StatusItems. + /// + /// /// /// /// @@ -63,7 +66,7 @@ public class Shortcut : View CommandView = new () { Width = Dim.Auto (), - Height = Dim.Auto (), + Height = Dim.Auto () }; HelpView.Id = "_helpView"; @@ -117,18 +120,22 @@ public class Shortcut : View Dim GetWidthDimAuto () { // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. - return Dim.Auto (DimAutoStyle.Content, minimumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)), maximumContentDim: Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); + return Dim.Auto ( + DimAutoStyle.Content, + Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)), + Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width))); } } /// /// Creates a new instance of . /// - public Shortcut () : this (Gui.Key.Empty, string.Empty, null) { } + public Shortcut () : this (Key.Empty, string.Empty, null) { } /// /// Gets or sets the for this . The default is - /// , which is ideal for status bars and toolbars. If set to , + /// , which is ideal for status bars and toolbars. If set to + /// , /// the Shortcut will be configured for vertical layout, which is ideal for menus. /// public Orientation Orientation { get; set; } = Orientation.Horizontal; @@ -136,7 +143,8 @@ public class Shortcut : View private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; /// - /// Gets or sets the for this . The default is . + /// Gets or sets the for this . The default is + /// . /// public AlignmentModes AlignmentModes { @@ -215,7 +223,7 @@ public class Shortcut : View case 2: // Scrunch just the right margin - var t = GetMarginThickness (); + Thickness t = GetMarginThickness (); HelpView.Margin.Thickness = new (t.Right, t.Top, t.Left - 1, t.Bottom); break; @@ -241,6 +249,7 @@ public class Shortcut : View // Reset to default //SetCommandViewDefaultLayout(); SetHelpViewDefaultLayout (); + //SetKeyViewDefaultLayout (); } } @@ -250,12 +259,10 @@ public class Shortcut : View { if (Orientation == Orientation.Vertical) { - return new Thickness (1, 0, 1, 0); - } - else - { - return new Thickness (1, 0, 1, 0); + return new (1, 0, 1, 0); } + + return new (1, 0, 1, 0); } private Color? _savedForeColor; @@ -304,7 +311,6 @@ public class Shortcut : View { SetFocus (); } - } #region Command @@ -374,6 +380,7 @@ public class Shortcut : View _commandView.MouseClick += Shortcut_MouseClick; _commandView.Accept += CommandViewAccept; + _commandView.HotKeyChanged += (s, e) => { if (e.NewKey != Key.Empty) @@ -411,10 +418,9 @@ public class Shortcut : View { CommandView.Margin.Thickness = GetMarginThickness (); CommandView.X = Pos.Align (Alignment.End, AlignmentModes); - CommandView.Y = 0;//Pos.Center (); + CommandView.Y = 0; //Pos.Center (); } - private void Shortcut_TitleChanged (object sender, StateEventArgs e) { // If the Title changes, update the CommandView text. @@ -436,7 +442,7 @@ public class Shortcut : View { HelpView.Margin.Thickness = GetMarginThickness (); HelpView.X = Pos.Align (Alignment.End, AlignmentModes); - HelpView.Y = 0;//Pos.Center (); + HelpView.Y = 0; //Pos.Center (); HelpView.Width = Dim.Auto (DimAutoStyle.Text); HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; @@ -530,7 +536,6 @@ public class Shortcut : View private int _minimumKeyViewSize; - /// /// public int MinimumKeyViewSize @@ -558,7 +563,7 @@ public class Shortcut : View { KeyView.Margin.Thickness = GetMarginThickness (); KeyView.X = Pos.Align (Alignment.End, AlignmentModes); - KeyView.Y = 0;//Pos.Center (); + KeyView.Y = 0; //Pos.Center (); KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize)); KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1; @@ -596,6 +601,7 @@ public class Shortcut : View { case KeyBindingScope.Application: handled = false; + break; case KeyBindingScope.Focused: @@ -610,7 +616,6 @@ public class Shortcut : View break; } - if (handled == false) { if (base.OnAccept () is false) @@ -625,7 +630,8 @@ public class Shortcut : View } /// - /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the mouse. + /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the + /// mouse. /// /// /// Note, the event is fired first, and if cancelled, the event will not be invoked. @@ -649,7 +655,6 @@ public class Shortcut : View } /// - /// /// internal void SetColors () { @@ -688,6 +693,7 @@ public class Shortcut : View public override bool OnEnter (View view) { SetColors (); + return base.OnEnter (view); } @@ -695,12 +701,13 @@ public class Shortcut : View public override bool OnLeave (View view) { SetColors (); + return base.OnLeave (view); } #endregion Focus - /// + /// protected override void Dispose (bool disposing) { if (disposing) @@ -709,16 +716,18 @@ public class Shortcut : View { CommandView.Dispose (); } + if (HelpView?.IsAdded == false) { HelpView.Dispose (); } + if (KeyView?.IsAdded == false) { KeyView.Dispose (); } } - base.Dispose (disposing); + base.Dispose (disposing); } } diff --git a/docfx/docs/keyboard.md b/docfx/docs/keyboard.md index edc258ab4..d5eba8b3e 100644 --- a/docfx/docs/keyboard.md +++ b/docfx/docs/keyboard.md @@ -43,17 +43,14 @@ A **HotKey** is a keypress that selects a visible UI item. For selecting items a By default, the `Text` of a `View` is used to determine the `HotKey` by looking for the first occurrence of the [HotKeySpecifier](~/api/Terminal.Gui.View.yml#Terminal_Gui_View_HotKeySpecifier) (which is underscore (`_`) by default). The character following the underscore is the `HotKey`. If the `HotKeySpecifier` is not found in `Text`, the first character of `Text` is used as the `HotKey`. The `Text` of a `View` can be changed at runtime, and the `HotKey` will be updated accordingly. [HotKey](~/api/Terminal.Gui.View.yml#Terminal_Gui_View_HotKey) is `virtual` enabling this behavior to be customized. -### **[Shortcut](~/api/Terminal.Gui.MenuItem.yml#Terminal_Gui_MenuItem_Shortcut)** +### **[Shortcut](~/api/Terminal.Gui.Shortcut.yml) - An opinionated (visually & API) View for displaying a command, helptext, key. +** -A **Shortcut** is a keypress that invokes a [Command](~/api/Terminal.Gui.Command.yml) or `View`-defined action regardless of whether the `View` that defines them is visible (but the `View` must be enabled). Shortcuts can be any keypress; `Key.A`, `Key.A | Key.Ctrl`, `Key.A | Key.Ctrl | Key.Alt`, `Key.Del`, and `Key.F1`, are all valid. +A **Shortcut** is a keypress that invokes a [Command](~/api/Terminal.Gui.Command.yml) or `View`-defined action even if the `View` that defines them is not focused or visible (but the `View` must be enabled). Shortcuts can be any keypress; `Key.A`, `Key.A | Key.Ctrl`, `Key.A | Key.Ctrl | Key.Alt`, `Key.Del`, and `Key.F1`, are all valid. `Shortcuts` are used to define application-wide actions (e.g. `Quit`), or actions that are not visible (e.g. `Copy`). -Not all `Views` support `Shortcut`s. [MenuBar](~/api/Terminal.Gui.MenuBar.yml), [ContextMenu](~/api/Terminal.Gui.ContextMenu.yml), and [StatusBar](~/api/Terminal.Gui.StatusBar.yml) support `Shortcut`s. However, the `Button` class does not. - -The `Shortcut` is provided by setting the [Shortcut](~/api/Terminal.Gui.MenuItem.yml#Terminal_Gui_MenuItem_Shortcut) property on either a [MenuItem](~/api/Terminal.Gui.MenuItem.yml) or [StatusItem](~/api/Terminal.Gui.StatusItem.yml). - -The [ShortcutDelimiter](~/api/Terminal.Gui.MenuBar.yml#Terminal_Gui_MenuBar_ShortcutDelimiter) (`+` by default) is used to separate the `Shortcut` from the `Text` of the `MenuItem` or `StatusItem`. For example, the `Shortcut` for `Quit` is `Ctrl+Q` and the `Text` is `Quit`. +[MenuBar](~/api/Terminal.Gui.MenuBar.yml), [ContextMenu](~/api/Terminal.Gui.ContextMenu.yml), and [StatusBar](~/api/Terminal.Gui.StatusBar.yml) support `Shortcut`s. ### **[Handling Keyboard Events](~/api/Terminal.Gui.View.yml#Terminal_Gui_View_KeyDown)** diff --git a/docfx/docs/migratingfromv1.md b/docfx/docs/migratingfromv1.md index 258e7c2a6..4d6c09267 100644 --- a/docfx/docs/migratingfromv1.md +++ b/docfx/docs/migratingfromv1.md @@ -164,6 +164,7 @@ In v1, scrolling was enabled by using `ScrollView` or `ScrollBarView`. In v2, th The API for handling keyboard input is significantly improved. See [Keyboard API](keyboard.md). * The [Key](~/api/Terminal.Gui.Key.yml) class replaces the `KeyEvent` struct and provides a platform-independent abstraction for common keyboard operations. It is used for processing keyboard input and raising keyboard events. This class provides a high-level abstraction with helper methods and properties for common keyboard operations. Use this class instead of the low-level [KeyCode](~/api/Terminal.Gui.KeyCode.yml) enum when possible. See [Key](~/api/Terminal.Gui.Key.yml) for more details. +* The preferred way to enable Application-wide or View-heirarchy-dependent keystrokes is to use the [Shortcut](~/api/Terminal.Gui.Shortcut.yml) View or the built-in View's that utilize it, such as the [Bar](~/api/Terminal.Gui.Bar.yml)-based views. * The preferred way to handle single keystrokes is to use **Key Bindings**. Key Bindings map a key press to a [Command](~/api/Terminal.Gui.Command.yml). A view can declare which commands it supports, and provide a lambda that implements the functionality of the command, using `View.AddCommand()`. Use the [View.Keybindings](~/api/Terminal.Gui.View.Keybindings.yml) to configure the key bindings. ### How to Fix @@ -201,7 +202,6 @@ The cursor and focus system has been redesigned in v2 to be more consistent and * Set [View.CursorVisibility](~/api/Terminal.Gui.View.CursorVisibility.yml) to the cursor style you want to use. * Remove any overrides of `OnEnter` and `OnLeave` that explicitly change the cursor. - ## Events now use `object sender, EventArgs args` signature Previously events in Terminal.Gui used a mixture of `Action` (no arguments), `Action` (or other raw datatype) and `Action`. Now all events use the `EventHandler` [standard .net design pattern](https://learn.microsoft.com/en-us/dotnet/csharp/event-pattern#event-delegate-signatures). @@ -274,3 +274,23 @@ The [Aligner](~/api/Terminal.Gui.Aligner.yml) class makes it easy to align eleme ### How to Fix * Replace `VerticalAlignment.Middle` is now [Alignment.Center](~/api/Terminal.Gui.Alignment.Center.yml). + +## `StatusBar`- `StatusItem` is replaced by `Shortcut` + +[StatusBar](~/api/Terminal.Gui.StatusBar.yml) has been upgraded to utilize [Shortcut](~/api/Terminal.Gui.Shortcut.yml). + +### How to Fix + +```diff +- var statusBar = new StatusBar ( +- new StatusItem [] +- { +- new ( +- Application.QuitKey, +- $"{Application.QuitKey} to Quit", +- () => Quit () +- ) +- } +- ); ++ var statusBar = new StatusBar (new Shortcut [] { new (Application.QuitKey, "Quit", Quit) }); +``` diff --git a/docfx/docs/newinv2.md b/docfx/docs/newinv2.md index 9ea1d0749..a42158455 100644 --- a/docfx/docs/newinv2.md +++ b/docfx/docs/newinv2.md @@ -12,7 +12,7 @@ Apps built with Terminal.Gui now feel modern thanks to these improvements: * *Enhanced Borders and Padding* - Terminal.Gui now supports a `Border`, `Margin`, and `Padding` property on all views. This simplifies View development and enables a sophisticated look and feel. See [Adornments](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#adornments) for details. * *User Configurable Color Themes* - See [Color Themes](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#color-themes) for details. * *Enhanced Unicode/Wide Character support* - Terminal.Gui now supports the full range of Unicode/wide characters. See [Unicode](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#unicode) for details. -* *Line Canvas* - Terminal.Gui now supports a line canvas enabling high-performance drawing of lines and shapes using box-drawing glyphs. `LineCanvas` provides *auto join*, a smart TUI drawing system that automatically selects the correct line/box drawing glyphs for intersections making drawing complex shapes easy. See [Line Canvas](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#line-canvas) for details. +* *[LineCanvas](~/api/Terminal.Gui.Line Canvas.yml)* - Terminal.Gui now supports a line canvas enabling high-performance drawing of lines and shapes using box-drawing glyphs. `LineCanvas` provides *auto join*, a smart TUI drawing system that automatically selects the correct line/box drawing glyphs for intersections making drawing complex shapes easy. See [Line Canvas](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#line-canvas) for details. ## Simplified API @@ -34,15 +34,16 @@ The entire library has been reviewed and simplified. As a result, the API is mor ## New and Improved Built-in Views -* *DatePicker* - NEW! -* *ScrollView* - Replaced by built-in scrolling. -* *ScrollBar* - Replaces *ScrollBarView* with a much simpler view. -* *Slider* - NEW! -* *Bars* - NEW! -* *StatusBar* - New implementation based on `Bar` -* *MenuBar* - New implementation based on `Bar` -* *ContextMenu* - New implementation based on `Bar` -* *File Dialog* - The new, modern file dialog includes icons (in TUI!) for files/folders, search, and a `TreeView``. See [FileDialog](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#filedialog) for details. +* *[DatePicker](~/api/Terminal.Gui.DatePicker.yml)* - NEW! +* *[ScrollView](~/api/Terminal.Gui.ScrollView.yml)* - Replaced by built-in scrolling. +* *[ScrollBar](~/api/Terminal.Gui.ScrollBar.yml)* - Replaces *ScrollBarView* with a much simpler view. +* *[Slider](~/api/Terminal.Gui.Slider.yml)* - NEW! +* *[Shortcut](~/api/Terminal.Gui.Shortcut.yml)* - NEW! An opinionated (visually & API) View for displaying a command, helptext, key. +* *[Bar](~/api/Terminal.Gui.Bar.yml)* - NEW! Building-block View for containing Shortcuts. Opinionated relative to Orientation but minimially so. The basis for the new StatusBar, MenuBar, and Menu views. +* *[StatusBar](~/api/Terminal.Gui.StatusBar.yml)* - New implementation based on `Bar` +* *[MenuBar](~/api/Terminal.Gui.MenuBar.yml)* - COMING SOON! New implementation based on `Bar` +* *[ContextMenu](~/api/Terminal.Gui.ContextMenu.yml)* - COMING SOON! New implementation based on `Bar` +* *[FileDialog](~/api/Terminal.Gui.FileDialog.yml)* - The new, modern file dialog includes icons (in TUI!) for files/folders, search, and a `TreeView``. See [FileDialog](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#filedialog) for details. ## Configuration Manager From c3fb9174d1514b19d09907d86cda6812b766a0f6 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 07:52:04 -0700 Subject: [PATCH 61/85] Improved API docs --- docfx/docs/newinv2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docfx/docs/newinv2.md b/docfx/docs/newinv2.md index a42158455..3d234aad2 100644 --- a/docfx/docs/newinv2.md +++ b/docfx/docs/newinv2.md @@ -43,7 +43,7 @@ The entire library has been reviewed and simplified. As a result, the API is mor * *[StatusBar](~/api/Terminal.Gui.StatusBar.yml)* - New implementation based on `Bar` * *[MenuBar](~/api/Terminal.Gui.MenuBar.yml)* - COMING SOON! New implementation based on `Bar` * *[ContextMenu](~/api/Terminal.Gui.ContextMenu.yml)* - COMING SOON! New implementation based on `Bar` -* *[FileDialog](~/api/Terminal.Gui.FileDialog.yml)* - The new, modern file dialog includes icons (in TUI!) for files/folders, search, and a `TreeView``. See [FileDialog](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#filedialog) for details. +* *[FileDialog](~/api/Terminal.Gui.FileDialog.yml)* - The new, modern file dialog includes icons (in TUI!) for files/folders, search, and a `TreeView`. ## Configuration Manager From f1d69fd4bb9df9061a286e4f23b8d0e7f4395359 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 07:55:11 -0700 Subject: [PATCH 62/85] Updated Run Scenario --- UICatalog/Scenarios/RunTExample.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/UICatalog/Scenarios/RunTExample.cs b/UICatalog/Scenarios/RunTExample.cs index ce82b1863..4a3126982 100644 --- a/UICatalog/Scenarios/RunTExample.cs +++ b/UICatalog/Scenarios/RunTExample.cs @@ -6,7 +6,7 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Top Level Windows")] public class RunTExample : Scenario { - public override void Init () + public override void Main () { // No need to call Init if Application.Run is used Application.Run (); @@ -14,8 +14,6 @@ public class RunTExample : Scenario Application.Top.Dispose (); } - public override void Run () { } - public class ExampleWindow : Window { private readonly TextField _usernameText; From 515bc3cce7265d0f571d5f0eb76c2a45ae633ddc Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 07:55:26 -0700 Subject: [PATCH 63/85] Updated Run Scenario --- UICatalog/Scenarios/RunTExample.cs | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/UICatalog/Scenarios/RunTExample.cs b/UICatalog/Scenarios/RunTExample.cs index 4a3126982..ae62eece6 100644 --- a/UICatalog/Scenarios/RunTExample.cs +++ b/UICatalog/Scenarios/RunTExample.cs @@ -25,7 +25,7 @@ public class RunTExample : Scenario // Create input components and labels var usernameLabel = new Label { Text = "Username:" }; - _usernameText = new TextField + _usernameText = new() { // Position text field adjacent to the label X = Pos.Right (usernameLabel) + 1, @@ -62,21 +62,21 @@ public class RunTExample : Scenario // When login button is clicked display a message popup btnLogin.Accept += (s, e) => - { - if (_usernameText.Text == "admin" && passwordText.Text == "password") - { - MessageBox.Query ("Login Successful", $"Username: {_usernameText.Text}", "Ok"); - Application.RequestStop (); - } - else - { - MessageBox.ErrorQuery ( - "Error Logging In", - "Incorrect username or password (hint: admin/password)", - "Ok" - ); - } - }; + { + if (_usernameText.Text == "admin" && passwordText.Text == "password") + { + MessageBox.Query ("Login Successful", $"Username: {_usernameText.Text}", "Ok"); + Application.RequestStop (); + } + else + { + MessageBox.ErrorQuery ( + "Error Logging In", + "Incorrect username or password (hint: admin/password)", + "Ok" + ); + } + }; // Add the views to the Window Add (usernameLabel, _usernameText, passwordLabel, passwordText, btnLogin); From d6933234b5e164db5a3a8826041dce139466cf1b Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 08:00:06 -0700 Subject: [PATCH 64/85] Added BarTests --- UnitTests/Views/BarTests.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 UnitTests/Views/BarTests.cs diff --git a/UnitTests/Views/BarTests.cs b/UnitTests/Views/BarTests.cs new file mode 100644 index 000000000..29637db77 --- /dev/null +++ b/UnitTests/Views/BarTests.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; + +namespace Terminal.Gui.ViewsTests; + +[TestSubject (typeof (Bar))] +public class BarTests +{ + [Fact] + public void Constructor_Defaults () + { + var bar = new Bar (); + + Assert.NotNull (bar); + Assert.True (bar.CanFocus); + Assert.IsType (bar.Width); + Assert.IsType (bar.Height); + + // TOOD: more + } + +} From b48d49e8c2e148de9fd615859cc2780ebadc78e7 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 08:14:53 -0700 Subject: [PATCH 65/85] Added more BarTests --- Terminal.Gui/Views/Bar.cs | 10 ++++- UnitTests/Views/BarTests.cs | 84 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index e95f9623f..aed8cd779 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -85,6 +85,7 @@ public class Bar : View } } + // TODO: Move this to View /// Inserts a in the specified index of . /// The zero-based index at which item should be inserted. /// The item to insert. @@ -94,19 +95,24 @@ public class Bar : View int count = savedSubViewList.Count; RemoveAll (); - for (var i = 0; i < count; i++) + for (var i = 0; i <= count; i++) { if (i == index) { Add (item); } - Add (savedSubViewList [i]); + if (i < count) + { + Add (savedSubViewList [i]); + } } SetNeedsDisplay (); } + // TODO: Move this to View + /// Removes a at specified index of . /// The zero-based index of the item to remove. /// The removed. diff --git a/UnitTests/Views/BarTests.cs b/UnitTests/Views/BarTests.cs index 29637db77..d5e50c7dc 100644 --- a/UnitTests/Views/BarTests.cs +++ b/UnitTests/Views/BarTests.cs @@ -18,4 +18,88 @@ public class BarTests // TOOD: more } + [Fact] + public void Constructor_InitializesEmpty_WhenNoShortcutsProvided () + { + var bar = new Bar (); + Assert.Empty (bar.Subviews); + } + + [Fact] + public void Constructor_InitializesWithShortcuts_WhenProvided () + { + var shortcuts = new List + { + new Shortcut(Key.Empty, "Command1", null, null), + new Shortcut(Key.Empty, "Command2", null, null) + }; + + var bar = new Bar (shortcuts); + + Assert.Equal (shortcuts.Count, bar.Subviews.Count); + for (int i = 0; i < shortcuts.Count; i++) + { + Assert.Same (shortcuts [i], bar.Subviews [i]); + } + } + + [Fact] + public void OrientationProperty_SetsCorrectly () + { + var bar = new Bar (); + Assert.Equal (Orientation.Horizontal, bar.Orientation); // Default value + + bar.Orientation = Orientation.Vertical; + Assert.Equal (Orientation.Vertical, bar.Orientation); + } + + [Fact] + public void AlignmentModesProperty_SetsCorrectly () + { + var bar = new Bar (); + Assert.Equal (AlignmentModes.StartToEnd, bar.AlignmentModes); // Default value + + bar.AlignmentModes = AlignmentModes.EndToStart; + Assert.Equal (AlignmentModes.EndToStart, bar.AlignmentModes); + } + + [Fact] + public void AddShortcutAt_InsertsShortcutCorrectly () + { + var bar = new Bar (); + var shortcut = new Shortcut (Key.Empty, "Command", null, null); + bar.AddShortcutAt (0, shortcut); + + Assert.Contains (shortcut, bar.Subviews); + } + + [Fact] + public void RemoveShortcut_RemovesShortcutCorrectly () + { + var shortcut1 = new Shortcut (Key.Empty, "Command1", null, null); + var shortcut2 = new Shortcut (Key.Empty, "Command2", null, null); + var bar = new Bar (new List { shortcut1, shortcut2 }); + + var removedShortcut = bar.RemoveShortcut (0); + + Assert.Same (shortcut1, removedShortcut); + Assert.DoesNotContain (shortcut1, bar.Subviews); + Assert.Contains (shortcut2, bar.Subviews); + } + + [Fact] + public void Layout_ChangesBasedOnOrientation () + { + var shortcut1 = new Shortcut (Key.Empty, "Command1", null, null); + var shortcut2 = new Shortcut (Key.Empty, "Command2", null, null); + var bar = new Bar (new List { shortcut1, shortcut2 }); + + bar.Orientation = Orientation.Horizontal; + bar.LayoutSubviews (); + // TODO: Assert specific layout expectations for horizontal orientation + + bar.Orientation = Orientation.Vertical; + bar.LayoutSubviews (); + // TODO: Assert specific layout expectations for vertical orientation + } } From 9ff5c76b077e5e3a7e90be6ad6738b6652c702a0 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 08:34:51 -0700 Subject: [PATCH 66/85] Added more ShortcutTests --- Terminal.Gui/Views/Shortcut.cs | 4 +- UnitTests/Views/ShortcutTests.cs | 95 +++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 42c95ad70..fc968cb23 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -161,7 +161,7 @@ public class Shortcut : View // When one of the subviews is "empty" we don't want to show it. So we // Use Add/Remove. We need to be careful to add them in the right order // so Pos.Align works correctly. - private void ShowHide () + internal void ShowHide () { RemoveAll (); @@ -674,7 +674,7 @@ public class Shortcut : View } else { - base.ColorScheme = SuperView?.ColorScheme; + base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme; } // Set KeyView's colors to show "hot" diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index efd1bd432..0ab4d3792 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -186,7 +186,100 @@ public class ShortcutTests shortcut.Key = Key.F1; - // Assert.Equal (Command.Accept, shortcut.CommandView.Get); + // TODO: + } + + + [Theory] + [InlineData (Orientation.Horizontal)] + [InlineData (Orientation.Vertical)] + public void Orientation_SetsCorrectly (Orientation orientation) + { + var shortcut = new Shortcut + { + Orientation = orientation + }; + + Assert.Equal (orientation, shortcut.Orientation); + } + + [Theory] + [InlineData (AlignmentModes.StartToEnd)] + [InlineData (AlignmentModes.EndToStart)] + public void AlignmentModes_SetsCorrectly (AlignmentModes alignmentModes) + { + var shortcut = new Shortcut + { + AlignmentModes = alignmentModes + }; + + Assert.Equal (alignmentModes, shortcut.AlignmentModes); + } + + [Fact] + public void Action_SetsAndGetsCorrectly () + { + bool actionInvoked = false; + var shortcut = new Shortcut + { + Action = () => { actionInvoked = true; } + }; + + shortcut.Action.Invoke (); + + Assert.True (actionInvoked); + } + + [Fact] + public void ColorScheme_SetsAndGetsCorrectly () + { + var colorScheme = new ColorScheme (); + var shortcut = new Shortcut + { + ColorScheme = colorScheme + }; + + Assert.Same (colorScheme, shortcut.ColorScheme); + } + + [Fact] + public void Subview_Visibility_Controlled_By_Removal () + { + var shortcut = new Shortcut (); + + Assert.True (shortcut.CommandView.Visible); + Assert.Contains (shortcut.CommandView, shortcut.Subviews); + Assert.True (shortcut.HelpView.Visible); + Assert.DoesNotContain (shortcut.HelpView, shortcut.Subviews); + Assert.True (shortcut.KeyView.Visible); + Assert.DoesNotContain (shortcut.KeyView, shortcut.Subviews); + + shortcut.HelpText = "help"; + Assert.True (shortcut.HelpView.Visible); + Assert.Contains (shortcut.HelpView, shortcut.Subviews); + Assert.True (shortcut.KeyView.Visible); + Assert.DoesNotContain (shortcut.KeyView, shortcut.Subviews); + + shortcut.Key = Key.A; + Assert.True (shortcut.HelpView.Visible); + Assert.Contains (shortcut.HelpView, shortcut.Subviews); + Assert.True (shortcut.KeyView.Visible); + Assert.Contains (shortcut.KeyView, shortcut.Subviews); + + shortcut.HelpView.Visible = false; + shortcut.ShowHide(); + Assert.False (shortcut.HelpView.Visible); + Assert.DoesNotContain (shortcut.HelpView, shortcut.Subviews); + Assert.True (shortcut.KeyView.Visible); + Assert.Contains (shortcut.KeyView, shortcut.Subviews); + + shortcut.KeyView.Visible = false; + shortcut.ShowHide (); + Assert.False (shortcut.HelpView.Visible); + Assert.DoesNotContain (shortcut.HelpView, shortcut.Subviews); + Assert.False (shortcut.KeyView.Visible); + Assert.DoesNotContain (shortcut.KeyView, shortcut.Subviews); + } } From 9f6ce8f599d2f24641a355d34d6e9f6574cc300a Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 08:35:17 -0700 Subject: [PATCH 67/85] Added more ShortcutTests --- UnitTests/Views/ShortcutTests.cs | 61 +++++++++++++++----------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index 0ab4d3792..939befc43 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -1,17 +1,14 @@ -using System.CommandLine; -using JetBrains.Annotations; -using UICatalog.Scenarios; +using JetBrains.Annotations; namespace Terminal.Gui.ViewsTests; [TestSubject (typeof (Shortcut))] public class ShortcutTests { - [Fact] public void Constructor_Defaults () { - Shortcut shortcut = new Shortcut (); + var shortcut = new Shortcut (); Assert.NotNull (shortcut); Assert.True (shortcut.CanFocus); @@ -32,18 +29,18 @@ public class ShortcutTests [InlineData ("C", "H", KeyCode.K, 9)] public void NaturalSize (string command, string help, Key key, int expectedWidth) { - Shortcut shortcut = new Shortcut () + var shortcut = new Shortcut { Title = command, HelpText = help, - Key = key, + Key = key }; Assert.IsType (shortcut.Width); Assert.IsType (shortcut.Height); shortcut.LayoutSubviews (); - shortcut.SetRelativeLayout (new Size (100, 100)); + shortcut.SetRelativeLayout (new (100, 100)); // |0123456789 // | C H K | @@ -60,7 +57,7 @@ public class ShortcutTests [InlineData (11, 0, 5, 8)] public void Set_Width_Layouts_Correctly (int width, int expectedCmdX, int expectedHelpX, int expectedKeyX) { - Shortcut shortcut = new Shortcut () + var shortcut = new Shortcut { Width = width, Title = "C", @@ -69,7 +66,7 @@ public class ShortcutTests }; shortcut.LayoutSubviews (); - shortcut.SetRelativeLayout (new Size (100, 100)); + shortcut.SetRelativeLayout (new (100, 100)); // 0123456789 // -C--H--K- @@ -81,17 +78,16 @@ public class ShortcutTests [Fact] public void CommandView_Text_And_Title_Track () { - Shortcut shortcut = new Shortcut () + var shortcut = new Shortcut { - Title = "T", + Title = "T" }; Assert.Equal (shortcut.Title, shortcut.CommandView.Text); - shortcut = new Shortcut () - { - }; - shortcut.CommandView = new View () + shortcut = new (); + + shortcut.CommandView = new() { Text = "T" }; @@ -101,16 +97,16 @@ public class ShortcutTests [Fact] public void HelpText_And_Text_Are_The_Same () { - Shortcut shortcut = new Shortcut () + var shortcut = new Shortcut { - Text = "H", + Text = "H" }; Assert.Equal (shortcut.Text, shortcut.HelpText); - shortcut = new Shortcut () + shortcut = new() { - HelpText = "H", + HelpText = "H" }; Assert.Equal (shortcut.Text, shortcut.HelpText); @@ -121,9 +117,9 @@ public class ShortcutTests [InlineData (KeyCode.F1, "F1")] public void KeyView_Text_Tracks_Key (Key key, string expected) { - Shortcut shortcut = new Shortcut () + var shortcut = new Shortcut { - Key = key, + Key = key }; Assert.Equal (expected, shortcut.KeyView.Text); @@ -133,7 +129,7 @@ public class ShortcutTests [Fact] public void Key_Defaults_To_Empty () { - Shortcut shortcut = new Shortcut (); + var shortcut = new Shortcut (); Assert.Equal (Key.Empty, shortcut.Key); } @@ -141,7 +137,7 @@ public class ShortcutTests [Fact] public void Key_Can_Be_Set () { - Shortcut shortcut = new Shortcut (); + var shortcut = new Shortcut (); shortcut.Key = Key.F1; @@ -151,7 +147,7 @@ public class ShortcutTests [Fact] public void Key_Can_Be_Set_To_Empty () { - Shortcut shortcut = new Shortcut (); + var shortcut = new Shortcut (); shortcut.Key = Key.Empty; @@ -164,7 +160,7 @@ public class ShortcutTests [Fact] public void KeyBindingScope_Defaults_To_HotKey () { - Shortcut shortcut = new Shortcut (); + var shortcut = new Shortcut (); Assert.Equal (KeyBindingScope.HotKey, shortcut.KeyBindingScope); } @@ -172,7 +168,7 @@ public class ShortcutTests [Fact] public void KeyBindingScope_Can_Be_Set () { - Shortcut shortcut = new Shortcut (); + var shortcut = new Shortcut (); shortcut.KeyBindingScope = KeyBindingScope.Application; @@ -182,14 +178,13 @@ public class ShortcutTests [Fact] public void Setting_Key_Binds_Key_To_CommandView_Accept () { - Shortcut shortcut = new Shortcut (); + var shortcut = new Shortcut (); shortcut.Key = Key.F1; // TODO: } - [Theory] [InlineData (Orientation.Horizontal)] [InlineData (Orientation.Vertical)] @@ -219,7 +214,8 @@ public class ShortcutTests [Fact] public void Action_SetsAndGetsCorrectly () { - bool actionInvoked = false; + var actionInvoked = false; + var shortcut = new Shortcut { Action = () => { actionInvoked = true; } @@ -234,6 +230,7 @@ public class ShortcutTests public void ColorScheme_SetsAndGetsCorrectly () { var colorScheme = new ColorScheme (); + var shortcut = new Shortcut { ColorScheme = colorScheme @@ -267,7 +264,7 @@ public class ShortcutTests Assert.Contains (shortcut.KeyView, shortcut.Subviews); shortcut.HelpView.Visible = false; - shortcut.ShowHide(); + shortcut.ShowHide (); Assert.False (shortcut.HelpView.Visible); Assert.DoesNotContain (shortcut.HelpView, shortcut.Subviews); Assert.True (shortcut.KeyView.Visible); @@ -279,7 +276,5 @@ public class ShortcutTests Assert.DoesNotContain (shortcut.HelpView, shortcut.Subviews); Assert.False (shortcut.KeyView.Visible); Assert.DoesNotContain (shortcut.KeyView, shortcut.Subviews); - } - } From 8d52a9a759b2ba5abfdd6a1fa1c1ad9e9818c6a1 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 09:03:32 -0700 Subject: [PATCH 68/85] Made Shortcut opinionated based on Orientation --- Terminal.Gui/Views/Shortcut.cs | 31 +++++++++++- UICatalog/Scenarios/Shortcuts.cs | 81 ++++++++++++-------------------- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index fc968cb23..4b38a50dd 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -132,13 +132,34 @@ public class Shortcut : View /// public Shortcut () : this (Key.Empty, string.Empty, null) { } + private Orientation _orientation = Orientation.Horizontal; + /// /// Gets or sets the for this . The default is /// , which is ideal for status bars and toolbars. If set to /// , /// the Shortcut will be configured for vertical layout, which is ideal for menus. /// - public Orientation Orientation { get; set; } = Orientation.Horizontal; + /// + /// When Horizontal, Key is first, then Help, then Command. When Vertical, Command is first, then Help, then Key. + /// + public Orientation Orientation + { + get => _orientation; + set + { + _orientation = value; + + if (value == Orientation.Vertical) + { + AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; + } + else + { + AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; + } + } + } private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; @@ -200,7 +221,13 @@ public class Shortcut : View int currentWidth = Frame.Width; - // If our width is smaller than the natural then reduce width of HelpView. + // If our width is smaller than the natural width then reduce width of HelpView first. + // Then KeyView. + // Don't ever reduce CommandView (it should spill). + // When Horizontal, Key is first, then Help, then Command. + // When Vertical, Command is first, then Help, then Key. + // BUGBUG: This does not do what the above says. + // TODO: Add Unit tests for this. if (currentWidth < _minimumDimAutoWidth) { int delta = _minimumDimAutoWidth.Value - currentWidth; diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 69720ac2c..8ba6551f8 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -49,23 +49,20 @@ public class Shortcuts : Scenario Width = 35, Title = "A_pp Shortcut", Key = Key.F1, - Text = "Width is 30", + Text = "Width is 35", KeyBindingScope = KeyBindingScope.Application, - BorderStyle = LineStyle.Dotted }; - vShortcut1.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (vShortcut1); var vShortcut2 = new Shortcut { Orientation = Orientation.Vertical, X = 0, - Y = Pos.Bottom (vShortcut1) - 1, - Width = Dim.Width (vShortcut1), + Y = Pos.Bottom (vShortcut1), + Width = 35, Key = Key.F2, - Text = "Width is ^", + Text = "Width is 35", KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Dotted, CommandView = new RadioGroup { Orientation = Orientation.Vertical, @@ -91,7 +88,6 @@ public class Shortcuts : Scenario ((RadioGroup)vShortcut2.CommandView).SelectedItem = 0; } }; - vShortcut2.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (vShortcut2); var vShortcut3 = new Shortcut @@ -104,34 +100,32 @@ public class Shortcuts : Scenario HelpText = "Width is Fill", Width = Dim.Fill () - Dim.Width (eventLog), KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Rounded }; - vShortcut3.Border.Thickness = new (1, 1, 1, 0); ((CheckBox)vShortcut3.CommandView).Toggled += (s, e) => - { - if (vShortcut3.CommandView is CheckBox cb) - { - eventSource.Add ($"Toggled: {cb.Text}"); - eventLog.MoveDown (); + { + if (vShortcut3.CommandView is CheckBox cb) + { + eventSource.Add ($"Toggled: {cb.Text}"); + eventLog.MoveDown (); - var max = 0; - var toAlign = Application.Top.Subviews.Where (v => v is Shortcut s && s.Orientation == Orientation.Vertical && s.BorderStyle == LineStyle.Rounded); + var max = 0; + var toAlign = Application.Top.Subviews.Where (v => v is Shortcut { Orientation: Orientation.Vertical, Width: not DimAbsolute }); - if (e.NewValue == true) - { - foreach (Shortcut peer in toAlign) - { - max = Math.Max (max, peer.KeyView.Text.GetColumns ()); - } - } + if (e.NewValue == true) + { + foreach (Shortcut peer in toAlign) + { + max = Math.Max (max, peer.KeyView.Text.GetColumns ()); + } + } - foreach (Shortcut peer in toAlign) - { - peer.MinimumKeyViewSize = max; - } - } - }; + foreach (Shortcut peer in toAlign) + { + peer.MinimumKeyViewSize = max; + } + } + }; Application.Top.Add (vShortcut3); var vShortcut4 = new Shortcut @@ -147,11 +141,9 @@ public class Shortcuts : Scenario HelpText = "Width is Fill", Key = Key.K, KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Rounded }; Button button = (Button)vShortcut4.CommandView; vShortcut4.CommandView.Accept += Button_Clicked; - vShortcut4.Border.Thickness = new (1, 0, 1, 0); Application.Top.Add (vShortcut4); @@ -165,10 +157,8 @@ public class Shortcuts : Scenario Key = Key.F4, HelpText = "CommandView.CanFocus", KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Rounded, CommandView = new CheckBox { Text = "_CanFocus" }, }; - vShortcut5.Border.Thickness = new (1, 0, 1, 1); ((CheckBox)vShortcut5.CommandView).Toggled += (s, e) => { @@ -193,13 +183,12 @@ public class Shortcuts : Scenario { Orientation = Orientation.Vertical, X = 0, - Y = Pos.Bottom (vShortcut5) - 1, + Y = Pos.Bottom (vShortcut5), Key = Key.F5, HelpText = "Width is Fill", Width = Dim.Width (vShortcut5), KeyBindingScope = KeyBindingScope.HotKey, - BorderStyle = LineStyle.Rounded, CommandView = new Slider { Orientation = Orientation.Vertical, @@ -209,7 +198,6 @@ public class Shortcuts : Scenario ((Slider)vShortcutSlider.CommandView).Options = new () { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; ((Slider)vShortcutSlider.CommandView).SetOption (0); - vShortcutSlider.Border.Thickness = new (1, 1, 1, 1); ((Slider)vShortcutSlider.CommandView).OptionsChanged += (o, args) => { @@ -223,14 +211,12 @@ public class Shortcuts : Scenario { Orientation = Orientation.Vertical, X = 0, - Y = Pos.Bottom (vShortcutSlider) - 1, + Y = Pos.Bottom (vShortcutSlider), Width = Dim.Width (vShortcutSlider), Title = "_No Key", HelpText = "Keyless", - BorderStyle = LineStyle.Rounded, }; - vShortcut6.Border.Thickness = new (1, 1, 1, 1); Application.Top.Add (vShortcut6); vShortcut6.SetFocus (); @@ -240,14 +226,13 @@ public class Shortcuts : Scenario // Horizontal var hShortcut1 = new Shortcut { + Orientation = Orientation.Horizontal, X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), Y = Pos.Bottom (eventLog) + 1, Key = Key.F7, HelpText = "Horizontal", - BorderStyle = LineStyle.Dashed, CanFocus = false }; - hShortcut1.Border.Thickness = new (0, 0, 1, 0); hShortcut1.CommandView = new ProgressBar { @@ -261,7 +246,6 @@ public class Shortcuts : Scenario hShortcut1.CommandView.Width = 10; hShortcut1.CommandView.Height = 1; hShortcut1.CommandView.CanFocus = false; - hShortcut1.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; Timer timer = new (10) { @@ -296,26 +280,24 @@ public class Shortcuts : Scenario var hShortcut2 = new Shortcut { + Orientation = Orientation.Horizontal, X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), Y = Pos.Top (hShortcut1), Key = Key.F8, HelpText = "TextField", CanFocus = true, CommandView = textField, - BorderStyle = LineStyle.Dashed, }; - hShortcut2.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; - hShortcut2.Border.Thickness = new (0, 0, 1, 0); Application.Top.Add (hShortcut2); var hShortcutBG = new Shortcut { + Orientation = Orientation.Horizontal, X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1) - 1, Y = Pos.Top (hShortcut2), Key = Key.F9, HelpText = "BG Color", - BorderStyle = LineStyle.Dashed, CanFocus = false }; @@ -333,23 +315,20 @@ public class Shortcuts : Scenario }; }; hShortcutBG.CommandView = bgColor; - hShortcutBG.Border.Thickness = new (1, 0, 1, 0); - hShortcutBG.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; Application.Top.Add (hShortcutBG); var hShortcut3 = new Shortcut { + Orientation = Orientation.Horizontal, X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), Y = Pos.Top (hShortcut2), Key = Key.Esc, KeyBindingScope = KeyBindingScope.Application, Title = "Quit", HelpText = "App Scope", - BorderStyle = LineStyle.Dashed, CanFocus = false }; - hShortcut3.Border.Thickness = new (0); hShortcut3.Accept += (o, args) => { Application.RequestStop (); From baf7e7f185fe5671a3c2af36f45ca03f708809ac Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 09:09:40 -0700 Subject: [PATCH 69/85] Made Shortcut opinionated based on Orientation --- Terminal.Gui/Views/Shortcut.cs | 21 ++++++++++++++++----- Terminal.Gui/Views/StatusBar.cs | 3 +-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 4b38a50dd..78250cf15 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -19,11 +19,13 @@ namespace Terminal.Gui; /// be invoked regardless of what View has focus, enabling an application-wide keyboard shortcut. /// /// -/// By default, a Shortcut displays the command text on the left side, the help text in the middle, and the key binding on the +/// By default, a Shortcut displays the command text on the left side, the help text in the middle, and the key +/// binding on the /// right side. Set to to reverse the order. /// /// -/// The command text can be set by setting the 's Text property or by setting . +/// The command text can be set by setting the 's Text property or by setting +/// . /// /// /// The help text can be set by setting the property or by setting . @@ -141,7 +143,12 @@ public class Shortcut : View /// the Shortcut will be configured for vertical layout, which is ideal for menus. /// /// - /// When Horizontal, Key is first, then Help, then Command. When Vertical, Command is first, then Help, then Key. + /// + /// When Horizontal, Key is first, then Help, then Command. When Vertical, Command is first, then Help, then Key. + /// + /// + /// Set to override the default layout. + /// /// public Orientation Orientation { @@ -164,9 +171,13 @@ public class Shortcut : View private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; /// - /// Gets or sets the for this . The default is - /// . + /// Gets or sets the for this . /// + /// + /// + /// Setting will set the to the appropriate value. + /// + /// public AlignmentModes AlignmentModes { get => _alignmentModes; diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 69cb264a6..0a6f19c4a 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -50,8 +50,7 @@ public class StatusBar : Bar if (barItem is Shortcut shortcut) { - shortcut.AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; - + shortcut.Orientation = Orientation.Horizontal; } } } From b6d9e6c14db3f0b6ea0bbef4d7e25cb00df31f38 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 09:13:06 -0700 Subject: [PATCH 70/85] Made Shortcut opinionated based on Orientation --- Terminal.Gui/Views/Shortcut.cs | 3 ++- UICatalog/Scenarios/Shortcuts.cs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 78250cf15..af7c84c21 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -168,7 +168,8 @@ public class Shortcut : View } } - private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; + // The default Orientation is Horizontal thus set this to EndToStart + private AlignmentModes _alignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; /// /// Gets or sets the for this . diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 8ba6551f8..05c7b6b26 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -226,7 +226,6 @@ public class Shortcuts : Scenario // Horizontal var hShortcut1 = new Shortcut { - Orientation = Orientation.Horizontal, X = Pos.Align (Alignment.Start, AlignmentModes.IgnoreFirstOrLast, 1), Y = Pos.Bottom (eventLog) + 1, Key = Key.F7, From 4cf6a2633301f0b26d65694b28a0720b0e72b5f0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 17 Jun 2024 18:33:09 +0100 Subject: [PATCH 71/85] Fixes #3545. Superview most focused view not sync with the overlapped view. --- Terminal.Gui/Application/Application.cs | 1 + Terminal.Gui/View/ViewSubViews.cs | 7 ++++++- Terminal.Gui/Views/ToplevelOverlapped.cs | 2 +- UnitTests/Application/KeyboardTests.cs | 6 +++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index f1b161118..ac887e95c 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -530,6 +530,7 @@ public static partial class Application toplevel.LayoutSubviews (); toplevel.PositionToplevels (); toplevel.FocusFirst (); + BringOverlappedTopToFront (); if (refreshDriver) { diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 05d332e30..4eaffdfa4 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -438,7 +438,7 @@ public partial class View SetHasFocus (false, this); SuperView?.EnsureFocus (); - if (SuperView is { } && SuperView.Focused is null) + if (SuperView is { Focused: null }) { SuperView.FocusNext (); @@ -477,6 +477,11 @@ public partial class View } } } + + if (this is Toplevel && Application.Current.Focused != this) + { + Application.BringOverlappedTopToFront (); + } } OnCanFocusChanged (); diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs index a1142b5df..0d93ef79f 100644 --- a/Terminal.Gui/Views/ToplevelOverlapped.cs +++ b/Terminal.Gui/Views/ToplevelOverlapped.cs @@ -67,7 +67,7 @@ public static partial class Application View top = FindTopFromView (Top?.MostFocused); - if (top is Toplevel && Top.Subviews.Count > 1 && Top.Subviews [Top.Subviews.Count - 1] != top) + if (top is Toplevel && Top.Subviews.Count > 1 && Top.Subviews [^1] != top) { Top.BringSubviewToFront (top); } diff --git a/UnitTests/Application/KeyboardTests.cs b/UnitTests/Application/KeyboardTests.cs index a293de45a..8bafc4771 100644 --- a/UnitTests/Application/KeyboardTests.cs +++ b/UnitTests/Application/KeyboardTests.cs @@ -193,7 +193,7 @@ public class KeyboardTests Assert.True (win.HasFocus); Assert.True (win2.CanFocus); Assert.False (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title); + Assert.Equal ("win", ((Window)top.Subviews [^1]).Title); win.CanFocus = false; Assert.False (win.CanFocus); @@ -220,7 +220,7 @@ public class KeyboardTests [Fact] [AutoInitShutdown] - public void EnsuresTopOnFront_CanFocus_True_By_Keyboard_ () + public void EnsuresTopOnFront_CanFocus_True_By_Keyboard () { Toplevel top = new (); @@ -253,7 +253,7 @@ public class KeyboardTests Assert.True (win.HasFocus); Assert.True (win2.CanFocus); Assert.False (win2.HasFocus); - Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title); + Assert.Equal ("win", ((Window)top.Subviews [^1]).Title); top.NewKeyDownEvent (Key.Tab.WithCtrl); Assert.True (win.CanFocus); From 434a2d453de9ca3ea0eaa1fba8f52ff1741eabf4 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 11:59:18 -0700 Subject: [PATCH 72/85] Made Shortcut opinionated based on Orientation --- Terminal.Gui/View/ViewSubViews.cs | 1 + Terminal.Gui/Views/Shortcut.cs | 34 ++-- Terminal.Gui/Views/StatusBar.cs | 10 +- UICatalog/Scenarios/Bars.cs | 291 +++++++++++------------------- UICatalog/UICatalog.cs | 170 +++++++++-------- 5 files changed, 219 insertions(+), 287 deletions(-) diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 0e02d5e28..ec5c68593 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -74,6 +74,7 @@ public partial class View SuperView._addingView = false; } + // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying. CanFocus = true; view._tabIndex = _tabIndexes.IndexOf (view); _addingView = false; diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index af7c84c21..daf661751 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -138,18 +138,10 @@ public class Shortcut : View /// /// Gets or sets the for this . The default is - /// , which is ideal for status bars and toolbars. If set to + /// , which is ideal for status bar, menu bar, and tool bar items If set to /// , - /// the Shortcut will be configured for vertical layout, which is ideal for menus. + /// the Shortcut will be configured for vertical layout, which is ideal for menu items. /// - /// - /// - /// When Horizontal, Key is first, then Help, then Command. When Vertical, Command is first, then Help, then Key. - /// - /// - /// Set to override the default layout. - /// - /// public Orientation Orientation { get => _orientation; @@ -157,26 +149,19 @@ public class Shortcut : View { _orientation = value; - if (value == Orientation.Vertical) - { - AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; - } - else - { - AlignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; - } + // TODO: Determine what, if anything, is opinionated about the orientation. } } - // The default Orientation is Horizontal thus set this to EndToStart - private AlignmentModes _alignmentModes = AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast; + private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; /// /// Gets or sets the for this . /// /// /// - /// Setting will set the to the appropriate value. + /// The default is . This means that the CommandView will be on the left, + /// HelpView in the middle, and KeyView on the right. /// /// public AlignmentModes AlignmentModes @@ -661,7 +646,12 @@ public class Shortcut : View { Action?.Invoke (); - return true; + if (CanFocus) + { + SetFocus (); + } + + return false; } } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 0a6f19c4a..b4df14e6b 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -55,17 +55,23 @@ public class StatusBar : Bar } } - /// public override View Add (View view) { + // Call base first, because otherwise it resets CanFocus to true + base.Add (view); + view.CanFocus = false; if (view is Shortcut shortcut) { shortcut.KeyBindingScope = KeyBindingScope.Application; + + // TODO: not happy about using AlignmentModes for this. Too implied. + // TODO: instead, add a property (a style enum?) to Shortcut to control this + shortcut.AlignmentModes = AlignmentModes.EndToStart; } - return base.Add (view); + return view; } } diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 73ef5a546..331bc1f0c 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -14,7 +14,7 @@ public class Bars : Scenario public override void Main () { Application.Init (); - Window app = new (); + Toplevel app = new (); app.Loaded += App_Loaded; @@ -28,31 +28,87 @@ public class Bars : Scenario // QuitKey and it only sticks if changed after init private void App_Loaded (object sender, EventArgs e) { - Application.QuitKey = Key.Z.WithCtrl; Application.Top.Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"; ObservableCollection eventSource = new (); ListView eventLog = new ListView () { + Title = "Event Log", X = Pos.AnchorEnd (), - Width = 50, - Height = Dim.Fill (6), + Width = Dim.Auto(), + Height = Dim.Fill (), // Make room for some wide things ColorScheme = Colors.ColorSchemes ["Toplevel"], Source = new ListWrapper (eventSource) }; + eventLog.Border.Thickness = new (0, 1, 0, 0); Application.Top.Add (eventLog); + FrameView menuBarLikeExamples = new () + { + Title = "MenuBar-Like Examples", + X = 0, + Y = 0, + Width = Dim.Fill () - Dim.Width (eventLog), + Height = 10, + }; + Application.Top.Add (menuBarLikeExamples); + + Label label = new Label () + { + Title = " Bar:", + X = 0, + Y = Pos.AnchorEnd () - 6 + }; + menuBarLikeExamples.Add (label); + + Bar bar = new Bar + { + Id = "menuBar-like", + X = Pos.Right (label), + Y = Pos.Top (label), + Width = Dim.Fill (), + Height = 1,//Dim.Auto (DimAutoStyle.Content), + Orientation = Orientation.Horizontal, + }; + + ConfigMenuBar (bar); + menuBarLikeExamples.Add (bar); + + label = new Label () + { + Title = " MenuBar:", + X = 0, + Y = Pos.Bottom(bar) + }; + menuBarLikeExamples.Add (label); + + //bar = new MenuBarv2 + //{ + // Id = "menuBar", + // Width = Dim.Fill (), + // Height = 1,//Dim.Auto (DimAutoStyle.Content), + // Orientation = Orientation.Horizontal, + //}; + + //ConfigMenuBar (bar); + //menuBarLikeExamples.Add (bar); + + FrameView menuLikeExamples = new () + { + Title = "Menu-Like Examples", + X = 0, + Y = Pos.Bottom (menuBarLikeExamples), + Width = Dim.Fill () - Dim.Width (eventLog), + Height = 10, + }; + Application.Top.Add (menuLikeExamples); + var shortcut1 = new Shortcut { Title = "_Zigzag", Key = Key.G.WithCtrl, Text = "Gonna zig zag", }; - shortcut1.Accept += (s, e) => - { - eventSource.Add ($"Accept: {s}"); - eventLog.MoveDown (); - }; var shortcut2 = new Shortcut { @@ -61,49 +117,6 @@ public class Bars : Scenario Key = Key.G.WithAlt, }; - //var shortcut3 = new Shortcut - //{ - // Title = "Shortcut3", - // Key = Key.D3.WithCtrl, - // Text = "Number Three", - // KeyBindingScope = KeyBindingScope.Application, - // Command = Command.Accept, - //}; - - //shortcut3.Accept += (s, e) => - // { - // eventSource.Add ($"Accept: {s}"); - // eventLog.MoveDown (); - // }; - - //var shortcut4 = new Shortcut - //{ - // Title = "Shortcut4", - // Text = "Number 4", - // Key = Key.F4, - // KeyBindingScope = KeyBindingScope.Application, - // Command = Command.Accept, - //}; - - //var cb = new CheckBox () - //{ - // Title = "Hello",// shortcut4.Text - //}; - - //cb.Toggled += (s, e) => - // { - // eventSource.Add ($"Toggled: {s}"); - // eventLog.MoveDown (); - // }; - - //shortcut4.CommandView = cb; - - //shortcut4.Accept += (s, e) => - // { - // eventSource.Add ($"Accept: {s}"); - // eventLog.MoveDown (); - // }; - var vBar = new Bar { X = 2, @@ -113,44 +126,41 @@ public class Bars : Scenario }; vBar.Add (shortcut1, shortcut2); - ////CheckBox hello = new () - ////{ - //// Title = "Hello", - //// X = 0, - //// Y = 1, - ////}; - ////Application.Top.Add (hello); - ////hello.Toggled += (s, e) => - //// { - //// eventSource.Add ($"Toggled: {s}"); - //// eventLog.MoveDown (); - //// }; - - - Application.Top.Add (vBar); + menuLikeExamples.Add (vBar); // BUGBUG: This should not be needed - Application.Top.LayoutSubviews (); + menuLikeExamples.LayoutSubviews (); // SetupMenuBar (); //SetupContentMenu (); - Label label = new Label () + + FrameView statusBarLikeExamples = new () + { + Title = "StatusBar-Like Examples", + X = 0, + Y = Pos.AnchorEnd (), + Width = Dim.Width (menuLikeExamples), + Height = 10, + }; + Application.Top.Add (statusBarLikeExamples); + + label = new Label () { Title = " Bar:", X = 0, Y = Pos.AnchorEnd () - 6 }; - Application.Top.Add (label); - var bar = new Bar - { - Id = "bar", - X = Pos.Right (label), - Y = Pos.Top (label), - Width = Dim.Fill (), - Orientation = Orientation.Horizontal, - }; - ConfigStatusBar (bar); - Application.Top.Add (bar); + statusBarLikeExamples.Add (label); + //bar = new Bar + //{ + // Id = "statusBar-like", + // X = Pos.Right (label), + // Y = Pos.Top (label), + // Width = Dim.Fill (), + // Orientation = Orientation.Horizontal, + //}; + //ConfigStatusBar (bar); + //statusBarLikeExamples.Add (bar); label = new Label () { @@ -158,8 +168,8 @@ public class Bars : Scenario X = 0, Y = Pos.AnchorEnd () - 3 }; - Application.Top.Add (label); - bar = new StatusBar() + statusBarLikeExamples.Add (label); + bar = new StatusBar () { Id = "statusBar", X = Pos.Right (label), @@ -168,17 +178,20 @@ public class Bars : Scenario Orientation = Orientation.Horizontal, }; ConfigStatusBar (bar); - Application.Top.Add (bar); + statusBarLikeExamples.Add (bar); - foreach (Bar barView in Application.Top.Subviews.Where (b => b is Bar)!) + foreach (FrameView frameView in Application.Top.Subviews.Where (f => f is FrameView)!) { - foreach (Shortcut sh in barView.Subviews.Where (s => s is Shortcut)!) + foreach (Bar barView in frameView.Subviews.Where (b => b is Bar)!) { - sh.Accept += (o, args) => - { - eventSource.Add ($"Accept: {sh!.SuperView.Id} {sh!.CommandView.Text}"); - eventLog.MoveDown (); - }; + foreach (Shortcut sh in barView.Subviews.Where (s => s is Shortcut)!) + { + sh.Accept += (o, args) => + { + eventSource.Add ($"Accept: {sh!.SuperView.Id} {sh!.CommandView.Text}"); + eventLog.MoveDown (); + }; + } } } } @@ -331,108 +344,20 @@ public class Bars : Scenario ((View)(sender)).LayoutSubviews (); } - private void SetupMenuBar () + private void ConfigMenuBar (Bar bar) { - var menuBar = new Bar - { - Id = "menuBar", - Width = Dim.Fill (), - Height = 1,//Dim.Auto (DimAutoStyle.Content), - Orientation = Orientation.Horizontal, - }; - var fileMenuBarItem = new Shortcut { Title = "_File", - KeyBindingScope = KeyBindingScope.Application, - Key = Key.F.WithAlt, }; fileMenuBarItem.KeyView.Visible = false; var editMenuBarItem = new Shortcut { Title = "_Edit", - - KeyBindingScope = KeyBindingScope.HotKey, - }; - - editMenuBarItem.Accept += (s, e) => { }; - //editMenu.HelpView.Visible = false; - //editMenu.KeyView.Visible = false; - - menuBar.Add (fileMenuBarItem, editMenuBarItem); - menuBar.Initialized += Menu_Initialized; - Application.Top.Add (menuBar); - - var fileMenu = new Bar - { - X = 1, - Y = 1, - Orientation = Orientation.Vertical, - // Modal = true, - Visible = false, - }; - - var newShortcut = new Shortcut - { - Title = "_New...", - Text = "Create a new file", - Key = Key.N.WithCtrl - }; - newShortcut.Border.Thickness = new Thickness (0, 1, 0, 0); - - var openShortcut = new Shortcut - { - Title = "_Open...", - Text = "Show the File Open Dialog", - Key = Key.O.WithCtrl - }; - - var saveShortcut = new Shortcut - { - Title = "_Save...", - Text = "Save", - Key = Key.S.WithCtrl, - Enabled = false - }; - - var exitShortcut = new Shortcut - { - Title = "E_xit", - Text = "Exit", - Key = Key.X.WithCtrl, - }; - exitShortcut.Border.Thickness = new Thickness (0, 1, 0, 1); - - fileMenu.Add (newShortcut, openShortcut, saveShortcut, exitShortcut); - - View prevFocus = null; - fileMenuBarItem.Accept += (s, e) => - { - if (fileMenu.Visible) - { - // fileMenu.RequestStop (); - prevFocus?.SetFocus (); - return; - } - - //fileMenu.Visible = !fileMenu.Visible; - var sender = s as Shortcut; - var screen = sender.FrameToScreen (); - fileMenu.X = screen.X; - fileMenu.Y = screen.Y + 1; - fileMenu.Visible = true; - prevFocus = Application.Top.Focused; - fileMenuBarItem.SetFocus (); - //Application.Run (fileMenu); - fileMenu.Visible = false; - }; - - Application.Top.Closed += (s, e) => - { - fileMenu.Dispose (); }; + bar.Add (fileMenuBarItem, editMenuBarItem); } private void ConfigStatusBar (Bar bar) @@ -442,8 +367,7 @@ public class Bars : Scenario Height = Dim.Auto (DimAutoStyle.Content, 3), Text = "Quit", Title = "Q_uit", - Key = Application.QuitKey, - KeyBindingScope = KeyBindingScope.Application, + Key = Key.Z.WithCtrl, }; bar.Add (shortcut); @@ -453,7 +377,6 @@ public class Bars : Scenario Text = "Help Text", Title = "Help", Key = Key.F1, - KeyBindingScope = KeyBindingScope.HotKey, }; bar.Add (shortcut); @@ -462,9 +385,9 @@ public class Bars : Scenario { Title = "_Show/Hide", Key = Key.F10, - KeyBindingScope = KeyBindingScope.Application, CommandView = new CheckBox { + CanFocus = false, Text = "_Show/Hide" }, }; diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index cf4c261d9..be453a66b 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -447,70 +447,73 @@ internal class UICatalogApp ] }; - ShVersion = new () - { - Title = "Version Info", - CanFocus = false, - - }; - - StatusBar = new () - { - Visible = ShowStatusBar, - }; - StatusBar.AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; - - Shortcut statusBarShortcut = new Shortcut () - { - Key = Key.F10, - Title = "Show/Hide Status Bar", - }; - statusBarShortcut.Accept += (sender, args) => - { - StatusBar.Visible = !StatusBar.Visible; - }; - - ShForce16Colors = new Shortcut () - { - CommandView = new CheckBox () - { - Title = "16 color mode", - Checked = Application.Force16Colors, - CanFocus = false, - }, HelpText = "", - Key = Key.F6, - }; - ShForce16Colors.Accept += (sender, args) => - { - ((CheckBox)ShForce16Colors.CommandView).Checked = Application.Force16Colors = (bool)!((CheckBox)ShForce16Colors.CommandView).Checked!; - MiForce16Colors.Checked = Application.Force16Colors; - Application.Refresh (); - - }; - - //ShDiagnostics = new Shortcut () + //StatusBar = new () //{ - // HelpText = "Diagnostic flags", - // CommandView = new RadioGroup() - // { - // RadioLabels = ["Off", "Ruler", "Padding", "MouseEnter"], - - // CanFocus = false, - // Orientation = Orientation.Vertical, - // } + // Visible = ShowStatusBar, //}; - StatusBar.Add ( - new Shortcut () + if (StatusBar is { }) + { + ShVersion = new () { - Title = "Quit", - Key = Application.QuitKey, - }, - statusBarShortcut, - ShForce16Colors, - //ShDiagnostics, - ShVersion - ); + Title = "Version Info", + CanFocus = false, + + }; + + Shortcut statusBarShortcut = new Shortcut () + { + Key = Key.F10, + Title = "Show/Hide Status Bar", + }; + statusBarShortcut.Accept += (sender, args) => { StatusBar.Visible = !StatusBar.Visible; }; + + ShForce16Colors = new Shortcut () + { + CommandView = new CheckBox () + { + Title = "16 color mode", + Checked = Application.Force16Colors, + CanFocus = false, + }, + HelpText = "", + Key = Key.F6, + }; + + ShForce16Colors.Accept += (sender, args) => + { + ((CheckBox)ShForce16Colors.CommandView).Checked = + Application.Force16Colors = (bool)!((CheckBox)ShForce16Colors.CommandView).Checked!; + MiForce16Colors.Checked = Application.Force16Colors; + Application.Refresh (); + + }; + + //ShDiagnostics = new Shortcut () + //{ + // HelpText = "Diagnostic flags", + // CommandView = new RadioGroup() + // { + // RadioLabels = ["Off", "Ruler", "Padding", "MouseEnter"], + + // CanFocus = false, + // Orientation = Orientation.Vertical, + // } + //}; + + StatusBar.Add ( + new Shortcut () + { + Title = "Quit", + Key = Application.QuitKey, + }, + statusBarShortcut, + ShForce16Colors, + + //ShDiagnostics, + ShVersion + ); + } // Create the Category list view. This list never changes. CategoryList = new () @@ -615,7 +618,11 @@ internal class UICatalogApp Add (ScenarioList); Add (MenuBar); - Add (StatusBar); + + if (StatusBar is { }) + { + Add (StatusBar); + } Loaded += LoadedHandler; Unloaded += UnloadedHandler; @@ -651,16 +658,14 @@ internal class UICatalogApp MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey; - ((Shortcut)StatusBar.Subviews [0]).Key = Application.QuitKey; + if (StatusBar is { }) + { + ((Shortcut)StatusBar.Subviews [0]).Key = Application.QuitKey; + StatusBar.Visible = ShowStatusBar; + } MiIsMouseDisabled!.Checked = Application.IsMouseDisabled; - int height = ShowStatusBar ? 1 : 0; // + (MenuBar.Visible ? 1 : 0); - - //ContentPane.Height = Dim.Fill (height); - - StatusBar.Visible = ShowStatusBar; - Application.Top.SetNeedsDisplay (); } @@ -1032,7 +1037,11 @@ internal class UICatalogApp ConfigChanged (); MiIsMouseDisabled!.Checked = Application.IsMouseDisabled; - ShVersion.Title = $"{RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}, {Driver.GetVersionInfo ()}"; + + if (ShVersion is { }) + { + ShVersion.Title = $"{RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}, {Driver.GetVersionInfo ()}"; + } if (_selectedScenario != null) { @@ -1045,18 +1054,21 @@ internal class UICatalogApp ScenarioList.SetFocus (); } - StatusBar.VisibleChanged += (s, e) => - { - ShowStatusBar = StatusBar.Visible; + if (StatusBar is { }) + { + StatusBar.VisibleChanged += (s, e) => + { + ShowStatusBar = StatusBar.Visible; - int height = StatusBar.Visible ? 1 : 0; - CategoryList.Height = Dim.Fill (height); - ScenarioList.Height = Dim.Fill (height); + int height = StatusBar.Visible ? 1 : 0; + CategoryList.Height = Dim.Fill (height); + ScenarioList.Height = Dim.Fill (height); - // ContentPane.Height = Dim.Fill (height); - LayoutSubviews (); - SetSubViewNeedsDisplay (); - }; + // ContentPane.Height = Dim.Fill (height); + LayoutSubviews (); + SetSubViewNeedsDisplay (); + }; + } Loaded -= LoadedHandler; CategoryList.EnsureSelectedItemVisible (); From 72e38a982584bcfc4795eb10777182726ed6e0cc Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 13:02:12 -0700 Subject: [PATCH 73/85] Tweaks --- Terminal.Gui/Views/Shortcut.cs | 6 +++--- UICatalog/Scenarios/Bars.cs | 21 ++++++++++----------- UICatalog/UICatalog.cs | 8 ++++---- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index daf661751..eeaddcd70 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -634,7 +634,7 @@ public class Shortcut : View break; case KeyBindingScope.HotKey: - _commandView.InvokeCommand (Command.HotKey); + CommandView.InvokeCommand (Command.HotKey); handled = false; break; @@ -648,10 +648,10 @@ public class Shortcut : View if (CanFocus) { - SetFocus (); + CommandView.SetFocus (); } - return false; + return true; } } diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 331bc1f0c..bba0ba615 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -151,16 +151,16 @@ public class Bars : Scenario Y = Pos.AnchorEnd () - 6 }; statusBarLikeExamples.Add (label); - //bar = new Bar - //{ - // Id = "statusBar-like", - // X = Pos.Right (label), - // Y = Pos.Top (label), - // Width = Dim.Fill (), - // Orientation = Orientation.Horizontal, - //}; - //ConfigStatusBar (bar); - //statusBarLikeExamples.Add (bar); + bar = new Bar + { + Id = "statusBar-like", + X = Pos.Right (label), + Y = Pos.Top (label), + Width = Dim.Fill (), + Orientation = Orientation.Horizontal, + }; + ConfigStatusBar (bar); + statusBarLikeExamples.Add (bar); label = new Label () { @@ -175,7 +175,6 @@ public class Bars : Scenario X = Pos.Right (label), Y = Pos.Top (label), Width = Dim.Fill (), - Orientation = Orientation.Horizontal, }; ConfigStatusBar (bar); statusBarLikeExamples.Add (bar); diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index be453a66b..12ccb9a3b 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -447,10 +447,10 @@ internal class UICatalogApp ] }; - //StatusBar = new () - //{ - // Visible = ShowStatusBar, - //}; + StatusBar = new () + { + Visible = ShowStatusBar, + }; if (StatusBar is { }) { From 4e0e5d1e7214f5bb9eb0b7da48e162c7cca28c10 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 17 Jun 2024 16:59:18 -0700 Subject: [PATCH 74/85] Beefed up Bars scenario. Added MenuBarv2 Added Menuv2 --- Terminal.Gui/Views/Bar.cs | 37 ++------- Terminal.Gui/Views/MenuBarv2.cs | 108 ++++++++++++++++++++++++++ Terminal.Gui/Views/Menuv2.cs | 109 +++++++++++++++++++++++++++ Terminal.Gui/Views/Shortcut.cs | 10 ++- UICatalog/Scenarios/Bars.cs | 129 +++++++++++++++++++++----------- 5 files changed, 314 insertions(+), 79 deletions(-) create mode 100644 Terminal.Gui/Views/MenuBarv2.cs create mode 100644 Terminal.Gui/Views/Menuv2.cs diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index aed8cd779..fbff2d814 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -159,21 +159,6 @@ public class Bar : View break; case Orientation.Vertical: - // CommandView is aligned left, HelpView is aligned right, KeyView is aligned right - // All CommandView's are the same width, all HelpView's are the same width, - // all KeyView's are the same width - - var minKeyWidth = 0; - - List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); - - foreach (Shortcut shortcut in shortcuts) - { - // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView - //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); - minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); - } - // Set the overall size of the Bar and arrange the views vertically var maxBarItemWidth = 0; var totalHeight = 0; @@ -182,10 +167,7 @@ public class Bar : View { View barItem = Subviews [index]; - if (barItem is Shortcut scBarItem) - { - scBarItem.MinimumKeyViewSize = minKeyWidth; - } + barItem.ColorScheme = ColorScheme; if (!barItem.Visible) { @@ -204,23 +186,16 @@ public class Bar : View prevBarItem = barItem; - if (barItem is Shortcut shortcut) - { - maxBarItemWidth = Math.Max (maxBarItemWidth, shortcut.Frame.Width); - } - else - { - maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); - } + //maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); barItem.X = 0; totalHeight += barItem.Frame.Height; } - foreach (Shortcut shortcut in shortcuts) - { - shortcut.Width = maxBarItemWidth; - } + //foreach (Shortcut shortcut in shortcuts) + //{ + // shortcut.Width = maxBarItemWidth; + //} Height = Dim.Auto (DimAutoStyle.Content, totalHeight); diff --git a/Terminal.Gui/Views/MenuBarv2.cs b/Terminal.Gui/Views/MenuBarv2.cs new file mode 100644 index 000000000..2b06ac1e1 --- /dev/null +++ b/Terminal.Gui/Views/MenuBarv2.cs @@ -0,0 +1,108 @@ +using System; +using System.Reflection; + +namespace Terminal.Gui; + +/// +/// A menu bar is a that snaps to the top of a displaying set of +/// s. +/// +public class MenuBarv2 : Bar +{ + /// + public MenuBarv2 () : this ([]) { } + + /// + public MenuBarv2 (IEnumerable shortcuts) : base (shortcuts) + { + Y = 0; + Width = Dim.Fill (); + Height = Dim.Auto (DimAutoStyle.Content, 1); + BorderStyle = LineStyle.Dashed; + ColorScheme = Colors.ColorSchemes ["Menu"]; + Orientation = Orientation.Horizontal; + + LayoutStarted += MenuBarv2_LayoutStarted; + } + + // MenuBarv2 arranges the items horizontally. + // The first item has no left border, the last item has no right border. + // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart). + private void MenuBarv2_LayoutStarted (object sender, LayoutEventArgs e) + { + var minKeyWidth = 0; + + List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); + + foreach (Shortcut shortcut in shortcuts) + { + // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView + //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); + } + + View prevBarItem = null; + var maxBarItemWidth = 0; + + for (int index = 0; index < Subviews.Count; index++) + { + View barItem = Subviews [index]; + + if (!barItem.Visible) + { + continue; + } + + if (barItem is Shortcut scBarItem) + { + scBarItem.MinimumKeyViewSize = minKeyWidth; + } + + if (index == Subviews.Count - 1) + { + barItem.Border.Thickness = new Thickness (0, 0, 0, 0); + } + else + { + barItem.Border.Thickness = new Thickness (0, 0, 0, 0); + } + + if (barItem is Shortcut shortcut) + { + // shortcut.Min + // shortcut.Orientation = Orientation.Vertical; + } + + maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); + + } + + foreach (Shortcut shortcut in shortcuts) + { + shortcut.Width = maxBarItemWidth; + } + } + + /// + public override View Add (View view) + { + // Call base first, because otherwise it resets CanFocus to true + base.Add (view); + + view.CanFocus = true; + + if (view is Shortcut shortcut) + { + shortcut.KeyBindingScope = KeyBindingScope.Application; + + // TODO: not happy about using AlignmentModes for this. Too implied. + // TODO: instead, add a property (a style enum?) to Shortcut to control this + //shortcut.AlignmentModes = AlignmentModes.EndToStart; + + shortcut.KeyView.Visible = false; + shortcut.HelpView.Visible = false; + } + + return view; + } +} diff --git a/Terminal.Gui/Views/Menuv2.cs b/Terminal.Gui/Views/Menuv2.cs new file mode 100644 index 000000000..d2f3eb4af --- /dev/null +++ b/Terminal.Gui/Views/Menuv2.cs @@ -0,0 +1,109 @@ +using System; +using System.Reflection; + +namespace Terminal.Gui; + +/// +/// +public class Menuv2 : Bar +{ + /// + public Menuv2 () : this ([]) { } + + /// + public Menuv2 (IEnumerable shortcuts) : base (shortcuts) + { + Orientation = Orientation.Vertical; + Width = Dim.Auto (); + Height = Dim.Auto (DimAutoStyle.Content, 1); + ColorScheme = Colors.ColorSchemes ["Menu"]; + + LayoutStarted += Menuv2_LayoutStarted; + } + + // Menuv2 arranges the items horizontally. + // The first item has no left border, the last item has no right border. + // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart). + private void Menuv2_LayoutStarted (object sender, LayoutEventArgs e) + { + var minKeyWidth = 0; + + List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); + + foreach (Shortcut shortcut in shortcuts) + { + // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView + //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); + } + + View prevBarItem = null; + var maxBarItemWidth = 0; + + for (int index = 0; index < Subviews.Count; index++) + { + View barItem = Subviews [index]; + + if (!barItem.Visible) + { + continue; + } + + if (barItem is Shortcut scBarItem) + { + scBarItem.MinimumKeyViewSize = minKeyWidth; + } + + if (index == Subviews.Count - 1) + { + barItem.Border.Thickness = new Thickness (1, 0, 1, 1); + } + else if (index == 0) + { + barItem.Border.Thickness = new Thickness (1, 1, 1, 0); + } + else + { + barItem.Border.Thickness = new Thickness (1, 0, 1, 0); + } + + if (barItem is Shortcut shortcut) + { + // shortcut.Min + // shortcut.Orientation = Orientation.Vertical; + } + + prevBarItem = barItem; + // HACK: This should not be needed + barItem.SetRelativeLayout (GetContentSize ()); + + maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); + } + + foreach (Shortcut shortcut in shortcuts) + { + shortcut.Width = maxBarItemWidth; + } + } + + /// + public override View Add (View view) + { + // Call base first, because otherwise it resets CanFocus to true + base.Add (view); + + view.CanFocus = true; + + if (view is Shortcut shortcut) + { + shortcut.KeyBindingScope = KeyBindingScope.Application; + + // TODO: not happy about using AlignmentModes for this. Too implied. + // TODO: instead, add a property (a style enum?) to Shortcut to control this + //shortcut.AlignmentModes = AlignmentModes.EndToStart; + + } + + return view; + } +} diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index eeaddcd70..9002c6ef0 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -634,9 +634,8 @@ public class Shortcut : View break; case KeyBindingScope.HotKey: - CommandView.InvokeCommand (Command.HotKey); - handled = false; - + CommandView.InvokeCommand (Command.HotKey); + handled = false; break; } @@ -648,7 +647,7 @@ public class Shortcut : View if (CanFocus) { - CommandView.SetFocus (); + SetFocus (); } return true; @@ -718,10 +717,12 @@ public class Shortcut : View } } + View _lastFocusedView; /// public override bool OnEnter (View view) { SetColors (); + _lastFocusedView = view; return base.OnEnter (view); } @@ -730,6 +731,7 @@ public class Shortcut : View public override bool OnLeave (View view) { SetColors (); + _lastFocusedView = this; return base.OnLeave (view); } diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index bba0ba615..4014d5b2f 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -35,7 +35,7 @@ public class Bars : Scenario { Title = "Event Log", X = Pos.AnchorEnd (), - Width = Dim.Auto(), + Width = Dim.Auto (), Height = Dim.Fill (), // Make room for some wide things ColorScheme = Colors.ColorSchemes ["Toplevel"], Source = new ListWrapper (eventSource) @@ -49,7 +49,7 @@ public class Bars : Scenario X = 0, Y = 0, Width = Dim.Fill () - Dim.Width (eventLog), - Height = 10, + Height = Dim.Percent(33), }; Application.Top.Add (menuBarLikeExamples); @@ -57,7 +57,7 @@ public class Bars : Scenario { Title = " Bar:", X = 0, - Y = Pos.AnchorEnd () - 6 + Y = 0, }; menuBarLikeExamples.Add (label); @@ -67,8 +67,6 @@ public class Bars : Scenario X = Pos.Right (label), Y = Pos.Top (label), Width = Dim.Fill (), - Height = 1,//Dim.Auto (DimAutoStyle.Content), - Orientation = Orientation.Horizontal, }; ConfigMenuBar (bar); @@ -78,61 +76,68 @@ public class Bars : Scenario { Title = " MenuBar:", X = 0, - Y = Pos.Bottom(bar) + Y = Pos.Bottom (bar) + 1 }; menuBarLikeExamples.Add (label); - //bar = new MenuBarv2 - //{ - // Id = "menuBar", - // Width = Dim.Fill (), - // Height = 1,//Dim.Auto (DimAutoStyle.Content), - // Orientation = Orientation.Horizontal, - //}; + bar = new MenuBarv2 + { + Id = "menuBar", + X = Pos.Right (label), + Y = Pos.Top (label), + }; - //ConfigMenuBar (bar); - //menuBarLikeExamples.Add (bar); + ConfigMenuBar (bar); + menuBarLikeExamples.Add (bar); FrameView menuLikeExamples = new () { Title = "Menu-Like Examples", X = 0, - Y = Pos.Bottom (menuBarLikeExamples), + Y = Pos.Center (), Width = Dim.Fill () - Dim.Width (eventLog), - Height = 10, + Height = Dim.Percent (33), }; Application.Top.Add (menuLikeExamples); - var shortcut1 = new Shortcut + label = new Label () { - Title = "_Zigzag", - Key = Key.G.WithCtrl, - Text = "Gonna zig zag", + Title = "Bar:", + X = 0, + Y = 0, }; + menuLikeExamples.Add (label); - var shortcut2 = new Shortcut + bar = new Bar { - Title = "Za_G", - Text = "Gonna zag", - Key = Key.G.WithAlt, - }; - - var vBar = new Bar - { - X = 2, - Y = 2, + Id = "menu-like", + X = 0, + Y = Pos.Bottom(label), + //Width = Dim.Percent (40), Orientation = Orientation.Vertical, - BorderStyle = LineStyle.Rounded }; - vBar.Add (shortcut1, shortcut2); + bar.Border.Thickness = new (1); + ConfigureMenu (bar); - menuLikeExamples.Add (vBar); + menuLikeExamples.Add (bar); - // BUGBUG: This should not be needed - menuLikeExamples.LayoutSubviews (); + label = new Label () + { + Title = "Menu:", + X = Pos.Right(bar) + 1, + Y = Pos.Top (label), + }; + menuLikeExamples.Add (label); - // SetupMenuBar (); - //SetupContentMenu (); + bar = new Menuv2 + { + Id = "menu", + X = Pos.Left (label), + Y = Pos.Bottom (label), + }; + ConfigureMenu (bar); + + menuLikeExamples.Add (bar); FrameView statusBarLikeExamples = new () { @@ -140,7 +145,7 @@ public class Bars : Scenario X = 0, Y = Pos.AnchorEnd (), Width = Dim.Width (menuLikeExamples), - Height = 10, + Height = Dim.Percent (33), }; Application.Top.Add (statusBarLikeExamples); @@ -148,7 +153,7 @@ public class Bars : Scenario { Title = " Bar:", X = 0, - Y = Pos.AnchorEnd () - 6 + Y = 0, }; statusBarLikeExamples.Add (label); bar = new Bar @@ -166,7 +171,7 @@ public class Bars : Scenario { Title = "StatusBar:", X = 0, - Y = Pos.AnchorEnd () - 3 + Y = Pos.Bottom (bar) + 1, }; statusBarLikeExamples.Add (label); bar = new StatusBar () @@ -348,22 +353,58 @@ public class Bars : Scenario var fileMenuBarItem = new Shortcut { Title = "_File", + HelpText = "File Menu", + Key = Key.D0.WithAlt, }; - fileMenuBarItem.KeyView.Visible = false; var editMenuBarItem = new Shortcut { Title = "_Edit", + HelpText = "Edit Menu", + Key = Key.D1.WithAlt }; - bar.Add (fileMenuBarItem, editMenuBarItem); + var helpMenuBarItem = new Shortcut + { + Title = "_Help", + HelpText = "Halp Menu", + Key = Key.D2.WithAlt + }; + + bar.Add (fileMenuBarItem, editMenuBarItem, helpMenuBarItem); + } + + private void ConfigureMenu (Bar bar) + { + + var shortcut1 = new Shortcut + { + Title = "Z_igzag", + Key = Key.I.WithCtrl, + Text = "Gonna zig zag", + }; + + var shortcut2 = new Shortcut + { + Title = "Za_G", + Text = "Gonna zag", + Key = Key.G.WithAlt, + }; + + var shortcut3 = new Shortcut + { + Title = "_Three", + Text = "The 3rd item", + Key = Key.D3.WithAlt, + }; + + bar.Add (shortcut1, shortcut2, shortcut3); } private void ConfigStatusBar (Bar bar) { var shortcut = new Shortcut { - Height = Dim.Auto (DimAutoStyle.Content, 3), Text = "Quit", Title = "Q_uit", Key = Key.Z.WithCtrl, From 8cfd218f8658b9d36508671973aad04cfaa75179 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 18 Jun 2024 17:33:18 -0700 Subject: [PATCH 75/85] Made Menuv2 work right --- Terminal.Gui/Views/Bar.cs | 40 ++++++++++++---- Terminal.Gui/Views/Line.cs | 61 +++++++++++++++++++++++-- Terminal.Gui/Views/MenuBarv2.cs | 52 +-------------------- Terminal.Gui/Views/Menuv2.cs | 63 ++++---------------------- UICatalog/Scenarios/Bars.cs | 12 +++-- UICatalog/Scenarios/LineViewExample.cs | 1 + UnitTests/UICatalog/ScenarioTests.cs | 3 ++ 7 files changed, 113 insertions(+), 119 deletions(-) diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index fbff2d814..b955d4262 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -24,7 +24,6 @@ public class Bar : View Width = Dim.Auto (); Height = Dim.Auto (); - LayoutStarted += Bar_LayoutStarted; Initialized += Bar_Initialized; if (shortcuts is null) @@ -137,8 +136,11 @@ public class Bar : View return toRemove as Shortcut; } - private void Bar_LayoutStarted (object sender, LayoutEventArgs e) + /// + internal override void OnLayoutStarted (LayoutEventArgs args) { + base.OnLayoutStarted (args); + View prevBarItem = null; switch (Orientation) @@ -160,6 +162,17 @@ public class Bar : View case Orientation.Vertical: // Set the overall size of the Bar and arrange the views vertically + + var minKeyWidth = 0; + + List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); + foreach (Shortcut shortcut in shortcuts) + { + // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView + //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); + minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); + } + var maxBarItemWidth = 0; var totalHeight = 0; @@ -174,6 +187,14 @@ public class Bar : View continue; } + if (barItem is Shortcut scBarItem) + { + scBarItem.MinimumKeyViewSize = minKeyWidth; + // HACK: This should not be needed + scBarItem.SetRelativeLayout (GetContentSize ()); + maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width); + } + if (prevBarItem == null) { barItem.Y = 0; @@ -186,16 +207,19 @@ public class Bar : View prevBarItem = barItem; - //maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); - barItem.X = 0; totalHeight += barItem.Frame.Height; } - //foreach (Shortcut shortcut in shortcuts) - //{ - // shortcut.Width = maxBarItemWidth; - //} + + foreach (View barItem in Subviews) + { + barItem.Width = maxBarItemWidth; + + if (barItem is Line line) + { + } + } Height = Dim.Auto (DimAutoStyle.Content, totalHeight); diff --git a/Terminal.Gui/Views/Line.cs b/Terminal.Gui/Views/Line.cs index 8ef294061..95a8287c8 100644 --- a/Terminal.Gui/Views/Line.cs +++ b/Terminal.Gui/Views/Line.cs @@ -8,26 +8,81 @@ public class Line : View { BorderStyle = LineStyle.Single; Border.Thickness = new Thickness (0); + SuperViewRendersLineCanvas = true; } + public Dim Length { get; set; } = Dim.Fill (); + + private Orientation _orientation; + /// /// The direction of the line. If you change this you will need to manually update the Width/Height of the /// control to cover a relevant area based on the new direction. /// - public Orientation Orientation { get; set; } + public Orientation Orientation + { + get => _orientation; + set + { + _orientation = value; + + switch (Orientation) + { + case Orientation.Horizontal: + Height = 1; + // Width = Length; + //Border.Thickness = new Thickness (1, 0, 1, 0); + + break; + case Orientation.Vertical: + Height = Length; + Width = 1; + + break; + + } + } + } + + /// + public override void SetBorderStyle (LineStyle value) + { + // The default changes the thickness. We don't want that. We just set the style. + Border.LineStyle = value; + } /// public override void OnDrawContent (Rectangle viewport) { LineCanvas lc = LineCanvas; + if (SuperViewRendersLineCanvas) + { + lc = SuperView.LineCanvas; + } + if (SuperView is Adornment adornment) { lc = adornment.Parent.LineCanvas; } + + Point pos = ViewportToScreen (viewport).Location; + int length = Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height; + + if (SuperViewRendersLineCanvas && Orientation == Orientation.Horizontal) + { + pos.Offset (-SuperView.Border.Thickness.Left, 0); + length += SuperView.Border.Thickness.Horizontal; + } + + if (SuperViewRendersLineCanvas && Orientation == Orientation.Vertical) + { + pos.Offset (0, -SuperView.Border.Thickness.Top); + length += SuperView.Border.Thickness.Vertical; + } lc.AddLine ( - ViewportToScreen (viewport).Location, - Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height, + pos, + length, Orientation, BorderStyle ); diff --git a/Terminal.Gui/Views/MenuBarv2.cs b/Terminal.Gui/Views/MenuBarv2.cs index 2b06ac1e1..12278a24c 100644 --- a/Terminal.Gui/Views/MenuBarv2.cs +++ b/Terminal.Gui/Views/MenuBarv2.cs @@ -30,57 +30,7 @@ public class MenuBarv2 : Bar // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart). private void MenuBarv2_LayoutStarted (object sender, LayoutEventArgs e) { - var minKeyWidth = 0; - - List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); - - foreach (Shortcut shortcut in shortcuts) - { - // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView - //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); - minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); - } - - View prevBarItem = null; - var maxBarItemWidth = 0; - - for (int index = 0; index < Subviews.Count; index++) - { - View barItem = Subviews [index]; - - if (!barItem.Visible) - { - continue; - } - - if (barItem is Shortcut scBarItem) - { - scBarItem.MinimumKeyViewSize = minKeyWidth; - } - - if (index == Subviews.Count - 1) - { - barItem.Border.Thickness = new Thickness (0, 0, 0, 0); - } - else - { - barItem.Border.Thickness = new Thickness (0, 0, 0, 0); - } - - if (barItem is Shortcut shortcut) - { - // shortcut.Min - // shortcut.Orientation = Orientation.Vertical; - } - - maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); - - } - - foreach (Shortcut shortcut in shortcuts) - { - shortcut.Width = maxBarItemWidth; - } + } /// diff --git a/Terminal.Gui/Views/Menuv2.cs b/Terminal.Gui/Views/Menuv2.cs index d2f3eb4af..f0024ae28 100644 --- a/Terminal.Gui/Views/Menuv2.cs +++ b/Terminal.Gui/Views/Menuv2.cs @@ -17,29 +17,19 @@ public class Menuv2 : Bar Width = Dim.Auto (); Height = Dim.Auto (DimAutoStyle.Content, 1); ColorScheme = Colors.ColorSchemes ["Menu"]; + Initialized += Menuv2_Initialized; + } - LayoutStarted += Menuv2_LayoutStarted; + private void Menuv2_Initialized (object sender, EventArgs e) + { + Border.Thickness = new Thickness (1, 1, 1, 1); } // Menuv2 arranges the items horizontally. // The first item has no left border, the last item has no right border. // The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart). - private void Menuv2_LayoutStarted (object sender, LayoutEventArgs e) + internal override void OnLayoutStarted (LayoutEventArgs args) { - var minKeyWidth = 0; - - List shortcuts = Subviews.Where (s => s is Shortcut && s.Visible).Cast ().ToList (); - - foreach (Shortcut shortcut in shortcuts) - { - // Let AutoSize do its thing to get the minimum width of each CommandView and HelpView - //shortcut.CommandView.SetRelativeLayout (new Size (int.MaxValue, int.MaxValue)); - minKeyWidth = int.Max (minKeyWidth, shortcut.KeyView.Text.GetColumns ()); - } - - View prevBarItem = null; - var maxBarItemWidth = 0; - for (int index = 0; index < Subviews.Count; index++) { View barItem = Subviews [index]; @@ -49,59 +39,24 @@ public class Menuv2 : Bar continue; } - if (barItem is Shortcut scBarItem) - { - scBarItem.MinimumKeyViewSize = minKeyWidth; - } - - if (index == Subviews.Count - 1) - { - barItem.Border.Thickness = new Thickness (1, 0, 1, 1); - } - else if (index == 0) - { - barItem.Border.Thickness = new Thickness (1, 1, 1, 0); - } - else - { - barItem.Border.Thickness = new Thickness (1, 0, 1, 0); - } - - if (barItem is Shortcut shortcut) - { - // shortcut.Min - // shortcut.Orientation = Orientation.Vertical; - } - - prevBarItem = barItem; - // HACK: This should not be needed - barItem.SetRelativeLayout (GetContentSize ()); - - maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); - } - - foreach (Shortcut shortcut in shortcuts) - { - shortcut.Width = maxBarItemWidth; } + base.OnLayoutStarted (args); } /// public override View Add (View view) { - // Call base first, because otherwise it resets CanFocus to true base.Add (view); - view.CanFocus = true; - if (view is Shortcut shortcut) { + shortcut.CanFocus = true; shortcut.KeyBindingScope = KeyBindingScope.Application; + shortcut.Orientation = Orientation.Vertical; // TODO: not happy about using AlignmentModes for this. Too implied. // TODO: instead, add a property (a style enum?) to Shortcut to control this //shortcut.AlignmentModes = AlignmentModes.EndToStart; - } return view; diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs index 4014d5b2f..baf98a65d 100644 --- a/UICatalog/Scenarios/Bars.cs +++ b/UICatalog/Scenarios/Bars.cs @@ -116,8 +116,7 @@ public class Bars : Scenario //Width = Dim.Percent (40), Orientation = Orientation.Vertical, }; - bar.Border.Thickness = new (1); - ConfigureMenu (bar); + ConfigureMenu (bar); menuLikeExamples.Add (bar); @@ -391,6 +390,13 @@ public class Bars : Scenario Key = Key.G.WithAlt, }; + var line = new Line () + { + BorderStyle = LineStyle.Dotted, + Orientation = Orientation.Horizontal, + CanFocus = false, + }; + var shortcut3 = new Shortcut { Title = "_Three", @@ -398,7 +404,7 @@ public class Bars : Scenario Key = Key.D3.WithAlt, }; - bar.Add (shortcut1, shortcut2, shortcut3); + bar.Add (shortcut1, shortcut2, line, shortcut3); } private void ConfigStatusBar (Bar bar) diff --git a/UICatalog/Scenarios/LineViewExample.cs b/UICatalog/Scenarios/LineViewExample.cs index 824e6e199..f846562c6 100644 --- a/UICatalog/Scenarios/LineViewExample.cs +++ b/UICatalog/Scenarios/LineViewExample.cs @@ -11,6 +11,7 @@ public class LineViewExample : Scenario { public override void Main () { + Application.Init (); // Setup - Create a top-level application window and configure it. Toplevel appWindow = new (); diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index 9e151878e..baff2be07 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -36,6 +36,9 @@ public class ScenarioTests : TestsAllViews Scenario scenario = (Scenario)Activator.CreateInstance (scenarioType); + // BUGBUG: Scenario.Main is supposed to call Init. This is a workaround for now. + // BUGBUG: To be able to test Scenarios we need Application to have an event like "Application.Started" + // BUGBUG: That tests like this could subscribe to. Application.Init (new FakeDriver ()); // Press QuitKey From b60b212dcbac65bc7e5078f0f92ad9684f2dcebd Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 18 Jun 2024 20:34:39 -0700 Subject: [PATCH 76/85] KeyBinding tweaks --- Terminal.Gui/View/ViewSubViews.cs | 2 +- Terminal.Gui/Views/Bar.cs | 2 +- Terminal.Gui/Views/Shortcut.cs | 91 +++++++++++++++++-------------- UICatalog/Scenarios/Shortcuts.cs | 6 +- 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index ec5c68593..e290d3def 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -528,7 +528,7 @@ public partial class View // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews. /// Returns the currently focused Subview inside this view, or null if nothing is focused. /// The focused. - public View Focused { get; private set; } + public View Focused { get; set; } // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews. /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus). diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index b955d4262..07447fba8 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -189,7 +189,7 @@ public class Bar : View if (barItem is Shortcut scBarItem) { - scBarItem.MinimumKeyViewSize = minKeyWidth; + scBarItem.MinimumKeyTextSize = minKeyWidth; // HACK: This should not be needed scBarItem.SetRelativeLayout (GetContentSize ()); maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width); diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 9002c6ef0..a54571bf8 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -3,20 +3,21 @@ namespace Terminal.Gui; /// -/// Displays a command, help text, and a key binding. When the key is pressed, the command will be invoked. Useful for +/// Displays a command, help text, and a key binding. When the key specified by is pressed, the command will be invoked. Useful for /// displaying a command in such as a /// menu, toolbar, or status bar. /// /// /// -/// When the user clicks on the or presses the key -/// specified by the command is invoked, causing the -/// event to be fired +/// The following user actions will invoke the , causing the +/// event to be fired: +/// - Clicking on the . +/// - Pressing the key specified by . +/// - Pressing the HotKey specified by . /// /// -/// If is , the -/// command -/// be invoked regardless of what View has focus, enabling an application-wide keyboard shortcut. +/// If is , will invoked +/// command regardless of what View has focus, enabling an application-wide keyboard shortcut. /// /// /// By default, a Shortcut displays the command text on the left side, the help text in the middle, and the key @@ -58,8 +59,8 @@ public class Shortcut : View Width = GetWidthDimAuto (); Height = Dim.Auto (DimAutoStyle.Content, 1); - AddCommand (Command.HotKey, OnAccept); - AddCommand (Command.Accept, OnAccept); + AddCommand (Command.HotKey, ctx => OnAccept(ctx)); + AddCommand (Command.Accept, ctx => OnAccept (ctx)); KeyBindings.Add (KeyCode.Space, Command.Accept); KeyBindings.Add (KeyCode.Enter, Command.Accept); @@ -430,10 +431,10 @@ public class Shortcut : View { // When the CommandView fires its Accept event, we want to act as though the // Shortcut was clicked. - if (base.OnAccept () == true) - { - e.Cancel = true; - } + //if (base.OnAccept () == true) + //{ + // e.Cancel = true; + //} } } } @@ -551,28 +552,28 @@ public class Shortcut : View } } - // TODO: Make internal once Bar is done /// /// Gets the subview that displays the key. Internal for unit testing. /// - public View KeyView { get; } = new (); + internal View KeyView { get; } = new (); - private int _minimumKeyViewSize; + private int _minimumKeyTextSize; /// + /// Gets or sets the minimum size of the key text. Useful for aligning the key text with other s. /// - public int MinimumKeyViewSize + public int MinimumKeyTextSize { - get => _minimumKeyViewSize; + get => _minimumKeyTextSize; set { - if (value == _minimumKeyViewSize) + if (value == _minimumKeyTextSize) { //return; } - _minimumKeyViewSize = value; + _minimumKeyTextSize = value; SetKeyViewDefaultLayout (); CommandView.SetNeedsLayout (); HelpView.SetNeedsLayout (); @@ -581,7 +582,7 @@ public class Shortcut : View } } - private int GetMinimumKeyViewSize () { return MinimumKeyViewSize; } + private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; } private void SetKeyViewDefaultLayout () { @@ -603,6 +604,9 @@ public class Shortcut : View { if (Key != null) { + // Disable the command view key bindings + CommandView.KeyBindings.Remove (Key); + CommandView.KeyBindings.Remove (CommandView.HotKey); KeyBindings.Remove (Key); KeyBindings.Add (Key, KeyBindingScope, Command.Accept); } @@ -614,47 +618,52 @@ public class Shortcut : View /// /// Called when the command is received. This - /// occurs if the user clicks on the Bar with the mouse or presses the key bound to - /// Command.Accept (Space by default). + /// occurs + /// - if the user clicks anywhere on the shortcut with the mouse + /// - if the user presses Key + /// - if the user presses the HotKey specified by CommandView + /// - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept). /// - protected new bool? OnAccept () + protected new bool? OnAccept (CommandContext ctx) { - var handled = true; + var cancel = false; - switch (KeyBindingScope) + switch (ctx.KeyBinding?.Scope) { case KeyBindingScope.Application: - handled = false; + cancel = base.OnAccept () == true; break; case KeyBindingScope.Focused: // TODO: Figure this out - handled = false; + cancel = false; break; + case KeyBindingScope.HotKey: - CommandView.InvokeCommand (Command.HotKey); - handled = false; - break; - } - - if (handled == false) - { - if (base.OnAccept () is false) - { - Action?.Invoke (); + cancel = base.OnAccept () == true; if (CanFocus) { SetFocus (); } - return true; - } + break; + + default: + cancel = base.OnAccept () == true; + break; } - return false; + CommandView.InvokeCommand (Command.Accept); + + if (!cancel) + { + Action?.Invoke (); + } + + return cancel; } /// diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 05c7b6b26..e51921805 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -116,13 +116,14 @@ public class Shortcuts : Scenario { foreach (Shortcut peer in toAlign) { - max = Math.Max (max, peer.KeyView.Text.GetColumns ()); + // DANGER: KeyView is internal so we can't access it. So we assume this is how it works. + max = Math.Max (max, peer.Key.ToString ().GetColumns ()); } } foreach (Shortcut peer in toAlign) { - peer.MinimumKeyViewSize = max; + peer.MinimumKeyTextSize = max; } } }; @@ -344,6 +345,7 @@ public class Shortcuts : Scenario { eventSource.Add ($"Accept: {shortcut!.CommandView.Text}"); eventLog.MoveDown (); + args.Cancel = true; }; } } From 9f09e030ae34dc355c2b0a19080bf944b15bc257 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 18 Jun 2024 22:27:57 -0700 Subject: [PATCH 77/85] Fixed Scenarios and unit tests --- Terminal.Gui/Application/Application.cs | 21 +++-- Terminal.Gui/View/Layout/Dim.cs | 7 +- Terminal.Gui/View/ViewSubViews.cs | 2 +- UICatalog/Scenario.cs | 2 + UICatalog/Scenarios/ASCIICustomButton.cs | 2 + UICatalog/Scenarios/BasicColors.cs | 1 + UICatalog/Scenarios/Buttons.cs | 1 + UICatalog/Scenarios/CharacterMap.cs | 1 + UICatalog/Scenarios/ColorPicker.cs | 1 + UICatalog/Scenarios/ComputedLayout.cs | 5 + UICatalog/Scenarios/DynamicStatusBar.cs | 5 +- UICatalog/Scenarios/HexEditor.cs | 1 + UICatalog/Scenarios/HotKeys.cs | 1 + UICatalog/Scenarios/InteractiveTree.cs | 1 + UICatalog/Scenarios/LineCanvasExperiment.cs | 1 + UICatalog/Scenarios/Mouse.cs | 7 +- UICatalog/Scenarios/RunTExample.cs | 5 +- .../Scenarios/RuneWidthGreaterThanOne.cs | 11 ++- UICatalog/Scenarios/Scrolling.cs | 1 + UICatalog/Scenarios/SingleBackgroundWorker.cs | 1 + .../Scenarios/TextAlignmentAndDirection.cs | 1 + UICatalog/Scenarios/TextFormatterDemo.cs | 1 + UICatalog/Scenarios/TrueColors.cs | 2 + UICatalog/Scenarios/ViewExperiments.cs | 1 + UICatalog/Scenarios/VkeyPacketSimulator.cs | 4 + UICatalog/Scenarios/WindowsAndFrameViews.cs | 1 + UICatalog/Scenarios/WizardAsView.cs | 19 ++-- UnitTests/UICatalog/ScenarioTests.cs | 92 +++++++++++-------- 28 files changed, 119 insertions(+), 79 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 741c7ab75..040d86a77 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -76,7 +76,7 @@ public static partial class Application // this in a function like this ensures we don't make mistakes in // guaranteeing that the state of this singleton is deterministic when Init // starts running and after Shutdown returns. - internal static void ResetState () + internal static void ResetState (bool ignoreDisposed = false) { // Shutdown is the bookend for Init. As such it needs to clean up all resources // Init created. Apps that do any threading will need to code defensively for this. @@ -84,11 +84,6 @@ public static partial class Application foreach (Toplevel t in _topLevels) { t.Running = false; -#if DEBUG_IDISPOSABLE - - // Don't dispose the toplevels. It's up to caller dispose them - //Debug.Assert (t.WasDisposed); -#endif } _topLevels.Clear (); @@ -96,7 +91,7 @@ public static partial class Application #if DEBUG_IDISPOSABLE // Don't dispose the Top. It's up to caller dispose it - if (Top is { }) + if (!ignoreDisposed && Top is { }) { Debug.Assert (Top.WasDisposed); @@ -313,6 +308,7 @@ public static partial class Application SupportedCultures = GetSupportedCultures (); _mainThreadId = Thread.CurrentThread.ManagedThreadId; _initialized = true; + InitializedChanged?.Invoke (null, new (false, _initialized)); } private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); } @@ -353,8 +349,17 @@ public static partial class Application // TODO: Throw an exception if Init hasn't been called. ResetState (); PrintJsonErrors (); + InitializedChanged?.Invoke (null, new (true, _initialized)); } + /// + /// This event is fired after the and methods have been called. + /// + /// + /// Intended to support unit tests that need to know when the application has been initialized. + /// + public static event EventHandler> InitializedChanged; + #endregion Initialization (Init/Shutdown) #region Run (Begin, Run, End, Stop) @@ -676,7 +681,7 @@ public static partial class Application /// /// The created T object. The caller is responsible for disposing this object. public static T Run (Func errorHandler = null, ConsoleDriver driver = null) - where T : Toplevel, new () + where T : Toplevel, new() { if (!_initialized) { diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index 259eed499..e765414cb 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -110,14 +110,9 @@ public abstract class Dim /// Specifies how will compute the dimension. The default is . /// /// The minimum dimension the View's ContentSize will be constrained to. - /// The maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. + /// The maximum dimension the View's ContentSize will be fit to. public static Dim? Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null) { - if (maximumContentDim is { }) - { - Debug.WriteLine (@"WARNING: maximumContentDim is not fully implemented."); - } - return new DimAuto () { MinimumContentDim = minimumContentDim, diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index e290d3def..ec5c68593 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -528,7 +528,7 @@ public partial class View // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews. /// Returns the currently focused Subview inside this view, or null if nothing is focused. /// The focused. - public View Focused { get; set; } + public View Focused { get; private set; } // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews. /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus). diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs index 47467caba..df8e510d9 100644 --- a/UICatalog/Scenario.cs +++ b/UICatalog/Scenario.cs @@ -188,6 +188,8 @@ public class Scenario : IDisposable { // Must explicitly call Application.Shutdown method to shutdown. Application.Run (Top); + Top.Dispose (); + Application.Shutdown (); } /// Override this to implement the setup logic (create controls, etc...). diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index 5eb586aa1..59e8924f0 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -59,6 +59,8 @@ public class ASCIICustomButtonTest : Scenario Application.Run (top); top.Dispose (); + Application.Shutdown (); + return; void ChangeWindowSize () diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs index b1b75c54f..b45cbec3c 100644 --- a/UICatalog/Scenarios/BasicColors.cs +++ b/UICatalog/Scenarios/BasicColors.cs @@ -111,5 +111,6 @@ public class BasicColors : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index d22704e5d..2b51fad17 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -396,6 +396,7 @@ public class Buttons : Scenario main.Ready += (s, e) => radioGroup.Refresh (); Application.Run (main); main.Dispose (); + Application.Shutdown (); } /// diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index 02c11da5c..9387ed835 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -180,6 +180,7 @@ public class CharacterMap : Scenario Application.Run (top); top.Dispose (); + Application.Shutdown (); } private void _categoryList_Initialized (object sender, EventArgs e) { _charMap.Width = Dim.Fill () - _categoryList.Width; } diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs index 5bc84b70d..9a76085c1 100644 --- a/UICatalog/Scenarios/ColorPicker.cs +++ b/UICatalog/Scenarios/ColorPicker.cs @@ -86,6 +86,7 @@ public class ColorPickers : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } /// Fired when background color is changed. diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index a28e866f8..dd5e4c376 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -53,6 +53,10 @@ public class ComputedLayout : Scenario app.LayoutComplete += (s, a) => { + if (horizontalRuler.Viewport.Width == 0 || horizontalRuler.Viewport.Height == 0) + { + return; + } horizontalRuler.Text = rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [ ..horizontalRuler.Viewport.Width]; @@ -409,5 +413,6 @@ public class ComputedLayout : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs index af29faa8e..3c93421ee 100644 --- a/UICatalog/Scenarios/DynamicStatusBar.cs +++ b/UICatalog/Scenarios/DynamicStatusBar.cs @@ -16,7 +16,8 @@ public class DynamicStatusBar : Scenario public override void Main () { - Application.Run ().Dispose(); + Application.Run ().Dispose (); + Application.Shutdown (); } public class Binding @@ -165,7 +166,7 @@ public class DynamicStatusBar : Scenario return true; } - TextShortcut.Text = k.ToString(); + TextShortcut.Text = k.ToString (); return true; } diff --git a/UICatalog/Scenarios/HexEditor.cs b/UICatalog/Scenarios/HexEditor.cs index 5f02806f1..b941378c6 100644 --- a/UICatalog/Scenarios/HexEditor.cs +++ b/UICatalog/Scenarios/HexEditor.cs @@ -21,6 +21,7 @@ public class HexEditor : Scenario public override void Main () { + Application.Init (); Toplevel app = new Toplevel () { ColorScheme = Colors.ColorSchemes ["Base"] diff --git a/UICatalog/Scenarios/HotKeys.cs b/UICatalog/Scenarios/HotKeys.cs index 926d743ca..fb50813fc 100644 --- a/UICatalog/Scenarios/HotKeys.cs +++ b/UICatalog/Scenarios/HotKeys.cs @@ -123,5 +123,6 @@ public class HotKeys : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/InteractiveTree.cs b/UICatalog/Scenarios/InteractiveTree.cs index 5f0344aa1..1480969f9 100644 --- a/UICatalog/Scenarios/InteractiveTree.cs +++ b/UICatalog/Scenarios/InteractiveTree.cs @@ -12,6 +12,7 @@ public class InteractiveTree : Scenario public override void Main () { + Application.Init (); var appWindow = new Toplevel () { Title = GetName (), diff --git a/UICatalog/Scenarios/LineCanvasExperiment.cs b/UICatalog/Scenarios/LineCanvasExperiment.cs index 2d097b317..8061e3e5d 100644 --- a/UICatalog/Scenarios/LineCanvasExperiment.cs +++ b/UICatalog/Scenarios/LineCanvasExperiment.cs @@ -139,5 +139,6 @@ public class LineCanvasExperiment : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/Mouse.cs b/UICatalog/Scenarios/Mouse.cs index f6ad84a51..84c256247 100644 --- a/UICatalog/Scenarios/Mouse.cs +++ b/UICatalog/Scenarios/Mouse.cs @@ -18,7 +18,7 @@ public class Mouse : Scenario Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" }; - Slider filterSlider = new() + Slider filterSlider = new () { Title = "_Filter", X = 0, @@ -57,7 +57,7 @@ public class Mouse : Scenario win.Add (clearButton); Label ml; var count = 0; - ml = new() { X = Pos.Right (filterSlider), Y = 0, Text = "Mouse: " }; + ml = new () { X = Pos.Right (filterSlider), Y = 0, Text = "Mouse: " }; win.Add (ml); @@ -138,7 +138,7 @@ public class Mouse : Scenario } }; - label = new() + label = new () { Text = "_Window Events:", X = Pos.Right (appLog) + 1, @@ -184,6 +184,7 @@ public class Mouse : Scenario Application.Run (win); win.Dispose (); + Application.Shutdown (); } public class MouseDemo : View diff --git a/UICatalog/Scenarios/RunTExample.cs b/UICatalog/Scenarios/RunTExample.cs index ae62eece6..4dcb69030 100644 --- a/UICatalog/Scenarios/RunTExample.cs +++ b/UICatalog/Scenarios/RunTExample.cs @@ -9,9 +9,8 @@ public class RunTExample : Scenario public override void Main () { // No need to call Init if Application.Run is used - Application.Run (); - - Application.Top.Dispose (); + Application.Run ().Dispose (); + Application.Shutdown (); } public class ExampleWindow : Window diff --git a/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs b/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs index 217d8838e..97c15c5a9 100644 --- a/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs +++ b/UICatalog/Scenarios/RuneWidthGreaterThanOne.cs @@ -17,11 +17,11 @@ public class RuneWidthGreaterThanOne : Scenario private TextField _text; private Window _win; - public override void Init () + public override void Main () { Application.Init (); - Top = new (); + Toplevel topLevel = new (); var menu = new MenuBar { @@ -87,16 +87,17 @@ public class RuneWidthGreaterThanOne : Scenario }; _win = new Window { X = 5, Y = 5, Width = Dim.Fill (22), Height = Dim.Fill (5) }; _win.Add (_label, _text, _button, _labelR, _labelV); - Top.Add (menu, _win); + topLevel.Add (menu, _win); WideRunes (); //NarrowRunes (); //MixedRunes (); - Application.Run (Top); + Application.Run (topLevel); + topLevel.Dispose (); + Application.Shutdown (); } - public override void Run () { } private void MixedMessage (object sender, EventArgs e) { MessageBox.Query ("Say Hello 你", $"Hello {_text.Text}", "Ok"); } private void MixedRunes () diff --git a/UICatalog/Scenarios/Scrolling.cs b/UICatalog/Scenarios/Scrolling.cs index c9af1da10..581b2693e 100644 --- a/UICatalog/Scenarios/Scrolling.cs +++ b/UICatalog/Scenarios/Scrolling.cs @@ -247,6 +247,7 @@ public class Scrolling : Scenario app.Loaded -= App_Loaded; app.Unloaded -= app_Unloaded; app.Dispose (); + Application.Shutdown (); return; diff --git a/UICatalog/Scenarios/SingleBackgroundWorker.cs b/UICatalog/Scenarios/SingleBackgroundWorker.cs index 035f72428..896cac096 100644 --- a/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -15,6 +15,7 @@ public class SingleBackgroundWorker : Scenario public override void Main () { Application.Run ().Dispose (); + Application.Shutdown (); } public class MainApp : Toplevel diff --git a/UICatalog/Scenarios/TextAlignmentAndDirection.cs b/UICatalog/Scenarios/TextAlignmentAndDirection.cs index eb4f08bf1..06f52f063 100644 --- a/UICatalog/Scenarios/TextAlignmentAndDirection.cs +++ b/UICatalog/Scenarios/TextAlignmentAndDirection.cs @@ -592,6 +592,7 @@ public class TextAlignmentAndDirection : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); void ToggleJustify (bool oldValue, bool wasJustOptions = false) { diff --git a/UICatalog/Scenarios/TextFormatterDemo.cs b/UICatalog/Scenarios/TextFormatterDemo.cs index 81bf6a839..a3f36bf1d 100644 --- a/UICatalog/Scenarios/TextFormatterDemo.cs +++ b/UICatalog/Scenarios/TextFormatterDemo.cs @@ -144,5 +144,6 @@ public class TextFormatterDemo : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/TrueColors.cs b/UICatalog/Scenarios/TrueColors.cs index 266213029..992ace392 100644 --- a/UICatalog/Scenarios/TrueColors.cs +++ b/UICatalog/Scenarios/TrueColors.cs @@ -125,6 +125,8 @@ public class TrueColors : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); + return; void SetupGradient (string name, int x, ref int y, Func colorFunc) diff --git a/UICatalog/Scenarios/ViewExperiments.cs b/UICatalog/Scenarios/ViewExperiments.cs index b28fbb8ba..a35aa3bed 100644 --- a/UICatalog/Scenarios/ViewExperiments.cs +++ b/UICatalog/Scenarios/ViewExperiments.cs @@ -248,5 +248,6 @@ public class ViewExperiments : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/VkeyPacketSimulator.cs b/UICatalog/Scenarios/VkeyPacketSimulator.cs index 43aee6bf3..8d920c8ee 100644 --- a/UICatalog/Scenarios/VkeyPacketSimulator.cs +++ b/UICatalog/Scenarios/VkeyPacketSimulator.cs @@ -260,6 +260,10 @@ public class VkeyPacketSimulator : Scenario void Win_LayoutComplete (object sender, LayoutEventArgs obj) { + if (inputHorizontalRuler.Viewport.Width == 0 || inputVerticalRuler.Viewport.Height == 0) + { + return; + } inputHorizontalRuler.Text = outputHorizontalRuler.Text = ruler.Repeat ( (int)Math.Ceiling ( diff --git a/UICatalog/Scenarios/WindowsAndFrameViews.cs b/UICatalog/Scenarios/WindowsAndFrameViews.cs index f522420c6..b8714d079 100644 --- a/UICatalog/Scenarios/WindowsAndFrameViews.cs +++ b/UICatalog/Scenarios/WindowsAndFrameViews.cs @@ -207,5 +207,6 @@ public class WindowsAndFrameViews : Scenario Application.Run (app); app.Dispose (); + Application.Shutdown (); } } diff --git a/UICatalog/Scenarios/WizardAsView.cs b/UICatalog/Scenarios/WizardAsView.cs index 817cccd3b..b4d704f34 100644 --- a/UICatalog/Scenarios/WizardAsView.cs +++ b/UICatalog/Scenarios/WizardAsView.cs @@ -6,7 +6,7 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Wizards")] public class WizardAsView : Scenario { - public override void Init () + public override void Main () { Application.Init (); @@ -52,8 +52,9 @@ public class WizardAsView : Scenario ) ] }; - Top = new (); - Top.Add (menu); + + Toplevel topLevel = new (); + topLevel.Add (menu); // No need for a Title because the border is disabled var wizard = new Wizard { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; @@ -139,13 +140,9 @@ public class WizardAsView : Scenario lastStep.HelpText = "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing Esc will cancel."; - Top.Add (wizard); - Application.Run (Top); - } - - public override void Run () - { - // Do nothing in the override because we call Application.Run above - // (just to make it clear how the Top is being run and not the Wizard). + topLevel.Add (wizard); + Application.Run (topLevel); + topLevel.Dispose (); + Application.Shutdown (); } } diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index baff2be07..b61d6df0a 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -30,63 +30,75 @@ public class ScenarioTests : TestsAllViews /// [Theory] [MemberData (nameof (AllScenarioTypes))] - public void Run_All_Scenarios (Type scenarioType) + public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType) { + Application.ResetState (true); + _output.WriteLine ($"Running Scenario '{scenarioType}'"); Scenario scenario = (Scenario)Activator.CreateInstance (scenarioType); - // BUGBUG: Scenario.Main is supposed to call Init. This is a workaround for now. - // BUGBUG: To be able to test Scenarios we need Application to have an event like "Application.Started" - // BUGBUG: That tests like this could subscribe to. - Application.Init (new FakeDriver ()); - - // Press QuitKey - Assert.Empty (FakeConsole.MockKeyPresses); - - FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey); - uint abortTime = 500; - // If the scenario doesn't close within 500ms, this will force it to quit - bool ForceCloseCallback () - { - if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) - { - Application.RequestStop (); + bool initialized = false; + bool shutdown = false; - // See #2474 for why this is commented out - Assert.Fail ( - $"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit."); - } + object timeout = null; - return false; - } + Application.InitializedChanged += (s, a) => + { + if (a.NewValue) + { + //output.WriteLine ($" Add timeout to force quit after {abortTime}ms"); + timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); - //output.WriteLine ($" Add timeout to force quit after {abortTime}ms"); - _ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); - - Application.Iteration += (s, a) => - { - // Press QuitKey - Assert.Empty (FakeConsole.MockKeyPresses); - FakeConsole.PushMockKeyPress ((KeyCode)Application.QuitKey); - - //output.WriteLine ($" iteration {++iterations}"); - if (Application.Top.Running && FakeConsole.MockKeyPresses.Count == 0) - { - Application.RequestStop (); - Assert.Fail ($"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey}. Force quit."); - } - }; + Application.Iteration += OnApplicationOnIteration; + initialized = true; + } + else + { + if (timeout is { }) + { + Application.RemoveTimeout (timeout); + timeout = null; + } + Application.Iteration -= OnApplicationOnIteration; + shutdown = true; + } + }; scenario.Main (); scenario.Dispose (); - Application.Shutdown (); + Assert.True (initialized); + Assert.True (shutdown); + #if DEBUG_IDISPOSABLE Assert.Empty (Responder.Instances); #endif + + return; + + // If the scenario doesn't close within 500ms, this will force it to quit + bool ForceCloseCallback () + { + if (timeout is { }) + { + Application.RemoveTimeout (timeout); + timeout = null; + } + Application.ResetState (true); + Assert.Fail ( + $"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit."); + + return false; + } + + void OnApplicationOnIteration (object s, IterationEventArgs a) + { + // Press QuitKey + Application.OnKeyDown (Application.QuitKey); + } } [Fact] From a89f0a82808536c1cbba33ba26017e479a646e07 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 18 Jun 2024 22:33:48 -0700 Subject: [PATCH 78/85] Fixed Scenarios and unit tests --- UnitTests/UICatalog/ScenarioTests.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index b61d6df0a..015707cdc 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -69,6 +69,7 @@ public class ScenarioTests : TestsAllViews scenario.Main (); scenario.Dispose (); + scenario = null; Assert.True (initialized); Assert.True (shutdown); @@ -76,7 +77,7 @@ public class ScenarioTests : TestsAllViews #if DEBUG_IDISPOSABLE Assert.Empty (Responder.Instances); #endif - + Application.Shutdown (); return; // If the scenario doesn't close within 500ms, this will force it to quit @@ -96,8 +97,11 @@ public class ScenarioTests : TestsAllViews void OnApplicationOnIteration (object s, IterationEventArgs a) { - // Press QuitKey - Application.OnKeyDown (Application.QuitKey); + if (scenario is { }) + { + // Press QuitKey + Application.OnKeyDown (Application.QuitKey); + } } } From 48c65bdfa1a015963901d45e3f7f28f326d9c795 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 19 Jun 2024 16:40:41 +0100 Subject: [PATCH 79/85] Fixes #3551. v2: Big performance impact on ListView/ComboBox's that contain a lot of items --- Terminal.Gui/Views/ComboBox.cs | 17 +++- Terminal.Gui/Views/ListView.cs | 54 ++++++++++++- UICatalog/Scenarios/ListViewWithSelection.cs | 1 + UnitTests/Views/ListViewTests.cs | 81 ++++++++++++++++++++ 4 files changed, 148 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 36af3853e..7bd2c6e99 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -187,7 +187,7 @@ public class ComboBox : View { SelectedItem = -1; _search.Text = string.Empty; - Search_Changed (this, new StateEventArgs (string.Empty, _search.Text)); + Search_Changed (this, new StateEventArgs (string.Empty, _search.Text)); SetNeedsDisplay (); } } @@ -645,7 +645,9 @@ public class ComboBox : View private void ResetSearchSet (bool noCopy = false) { + _listview.SuspendCollectionChangedEvent (); _searchSet.Clear (); + _listview.ResumeSuspendCollectionChangedEvent (); if (_autoHide || noCopy) { @@ -682,6 +684,8 @@ public class ComboBox : View if (!string.IsNullOrEmpty (_search.Text)) { + _listview.SuspendCollectionChangedEvent (); + foreach (object item in _source.ToList ()) { // Iterate to preserver object type and force deep copy @@ -694,6 +698,8 @@ public class ComboBox : View _searchSet.Add (item); } } + + _listview.ResumeSuspendCollectionChangedEvent (); } } @@ -738,11 +744,15 @@ public class ComboBox : View return; } + _listview.SuspendCollectionChangedEvent (); + // force deep copy foreach (object item in Source.ToList ()) { _searchSet.Add (item); } + + _listview.ResumeSuspendCollectionChangedEvent (); } private void SetSearchText (string value) { _search.Text = _text = value; } @@ -765,8 +775,11 @@ public class ComboBox : View /// Consider making public private void ShowList () { + _listview.SuspendCollectionChangedEvent (); _listview.SetSource (_searchSet); - _listview.Clear (); + _listview.ResumeSuspendCollectionChangedEvent (); + + _listview.Clear (); _listview.Height = CalculatetHeight (); SuperView?.BringSubviewToFront (this); } diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 296cb2717..5701c3a8b 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -19,6 +19,12 @@ public interface IListDataSource: IDisposable /// Returns the maximum length of elements to display int Length { get; } + /// + /// Allow suspending the event from being invoked, + /// if , otherwise is . + /// + bool SuspendCollectionChangedEvent { get; set; } + /// Should return whether the specified item is currently marked. /// , if marked, otherwise. /// Item index. @@ -893,6 +899,28 @@ public class ListView : View base.Dispose (disposing); } + + /// + /// Allow suspending the event from being invoked, + /// + public void SuspendCollectionChangedEvent () + { + if (Source is { }) + { + Source.SuspendCollectionChangedEvent = true; + } + } + + /// + /// Allow resume the event from being invoked, + /// + public void ResumeSuspendCollectionChangedEvent () + { + if (Source is { }) + { + Source.SuspendCollectionChangedEvent = false; + } + } } /// @@ -920,8 +948,11 @@ public class ListWrapper : IListDataSource, IDisposable private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e) { - CheckAndResizeMarksIfRequired (); - CollectionChanged?.Invoke (sender, e); + if (!SuspendCollectionChangedEvent) + { + CheckAndResizeMarksIfRequired (); + CollectionChanged?.Invoke (sender, e); + } } /// @@ -933,7 +964,24 @@ public class ListWrapper : IListDataSource, IDisposable /// public int Length { get; private set; } - void CheckAndResizeMarksIfRequired () + private bool _suspendCollectionChangedEvent; + + /// + public bool SuspendCollectionChangedEvent + { + get => _suspendCollectionChangedEvent; + set + { + _suspendCollectionChangedEvent = value; + + if (!_suspendCollectionChangedEvent) + { + CheckAndResizeMarksIfRequired (); + } + } + } + + private void CheckAndResizeMarksIfRequired () { if (_source != null && _count != _source.Count) { diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index 83ce63d4a..a11d6950d 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -207,6 +207,7 @@ public class ListViewWithSelection : Scenario public event NotifyCollectionChangedEventHandler CollectionChanged; public int Count => Scenarios != null ? Scenarios.Count : 0; public int Length { get; private set; } + public bool SuspendCollectionChangedEvent { get => throw new System.NotImplementedException (); set => throw new System.NotImplementedException (); } public void Render ( ListView container, diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs index 3f222b1dc..5f157a81d 100644 --- a/UnitTests/Views/ListViewTests.cs +++ b/UnitTests/Views/ListViewTests.cs @@ -679,6 +679,9 @@ Item 6", public int Count => 0; public int Length => 0; + + public bool SuspendCollectionChangedEvent { get => throw new NotImplementedException (); set => throw new NotImplementedException (); } + public bool IsMarked (int item) { throw new NotImplementedException (); } public void Render ( @@ -1075,4 +1078,82 @@ Item 6", } } } + + [Fact] + public void ListWrapper_SuspendCollectionChangedEvent_ResumeSuspendCollectionChangedEvent_Tests () + { + var added = 0; + ObservableCollection source = []; + ListWrapper lw = new (source); + + lw.CollectionChanged += Lw_CollectionChanged; + + lw.SuspendCollectionChangedEvent = true; + + for (int i = 0; i < 3; i++) + { + source.Add ($"Item{i}"); + } + Assert.Equal (0, added); + Assert.Equal (3, lw.Count); + Assert.Equal (3, source.Count); + + lw.SuspendCollectionChangedEvent = false; + + for (int i = 3; i < 6; i++) + { + source.Add ($"Item{i}"); + } + Assert.Equal (3, added); + Assert.Equal (6, lw.Count); + Assert.Equal (6, source.Count); + + + void Lw_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + added++; + } + } + } + + [Fact] + public void ListView_SuspendCollectionChangedEvent_ResumeSuspendCollectionChangedEvent_Tests () + { + var added = 0; + ObservableCollection source = []; + ListView lv = new ListView { Source = new ListWrapper (source) }; + + lv.CollectionChanged += Lw_CollectionChanged; + + lv.SuspendCollectionChangedEvent (); + + for (int i = 0; i < 3; i++) + { + source.Add ($"Item{i}"); + } + Assert.Equal (0, added); + Assert.Equal (3, lv.Source.Count); + Assert.Equal (3, source.Count); + + lv.ResumeSuspendCollectionChangedEvent (); + + for (int i = 3; i < 6; i++) + { + source.Add ($"Item{i}"); + } + Assert.Equal (3, added); + Assert.Equal (6, lv.Source.Count); + Assert.Equal (6, source.Count); + + + void Lw_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + added++; + } + } + } } \ No newline at end of file From 9372cce69c475aebe34cbd086b6c6e53f80b2c43 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 19 Jun 2024 11:35:46 -0700 Subject: [PATCH 80/85] Fixed Scenario tests --- .../Terminal.Gui.PowerShell.Analyzers.psd1 | 2 +- Scripts/Terminal.Gui.PowerShell.Build.psd1 | 2 +- Scripts/Terminal.Gui.PowerShell.Core.psd1 | 2 +- Scripts/Terminal.Gui.PowerShell.Git.psd1 | 2 +- Scripts/Terminal.Gui.PowerShell.psd1 | 2 +- UnitTests/Application/MainLoopTests.cs | 8 ++- .../Application/SynchronizatonContextTests.cs | 8 ++- UnitTests/UICatalog/ScenarioTests.cs | 68 +++++++++++-------- 8 files changed, 57 insertions(+), 37 deletions(-) diff --git a/Scripts/Terminal.Gui.PowerShell.Analyzers.psd1 b/Scripts/Terminal.Gui.PowerShell.Analyzers.psd1 index b5eced04c..c94a3e242 100644 --- a/Scripts/Terminal.Gui.PowerShell.Analyzers.psd1 +++ b/Scripts/Terminal.Gui.PowerShell.Analyzers.psd1 @@ -42,7 +42,7 @@ PowerShellHostName = 'ConsoleHost' # PowerShellHostVersion = '' # Processor architecture (None, X86, Amd64) required by this module -ProcessorArchitecture = 'Amd64' +ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @('Microsoft.PowerShell.Management','Microsoft.PowerShell.Utility','./Terminal.Gui.PowerShell.Core.psd1') diff --git a/Scripts/Terminal.Gui.PowerShell.Build.psd1 b/Scripts/Terminal.Gui.PowerShell.Build.psd1 index 9f367487a..97395c3a8 100644 --- a/Scripts/Terminal.Gui.PowerShell.Build.psd1 +++ b/Scripts/Terminal.Gui.PowerShell.Build.psd1 @@ -36,7 +36,7 @@ PowerShellHostVersion = '7.4.0' # Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only. # Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else. # Has nothing to do with runtime use of Terminal.Gui. -ProcessorArchitecture = 'Amd64' +ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @( diff --git a/Scripts/Terminal.Gui.PowerShell.Core.psd1 b/Scripts/Terminal.Gui.PowerShell.Core.psd1 index 302005999..541265ee8 100644 --- a/Scripts/Terminal.Gui.PowerShell.Core.psd1 +++ b/Scripts/Terminal.Gui.PowerShell.Core.psd1 @@ -44,7 +44,7 @@ PowerShellHostVersion = '7.4.0' # Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only. # Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else. # Has nothing to do with runtime use of Terminal.Gui. -ProcessorArchitecture = 'Amd64' +ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @( diff --git a/Scripts/Terminal.Gui.PowerShell.Git.psd1 b/Scripts/Terminal.Gui.PowerShell.Git.psd1 index 1bfcd58a1..afca6f69b 100644 --- a/Scripts/Terminal.Gui.PowerShell.Git.psd1 +++ b/Scripts/Terminal.Gui.PowerShell.Git.psd1 @@ -44,7 +44,7 @@ PowerShellHostVersion = '7.4.0' # Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only. # Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else. # Has nothing to do with runtime use of Terminal.Gui. -ProcessorArchitecture = 'AMD64' +ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @( diff --git a/Scripts/Terminal.Gui.PowerShell.psd1 b/Scripts/Terminal.Gui.PowerShell.psd1 index d90db54d9..ca182f375 100644 --- a/Scripts/Terminal.Gui.PowerShell.psd1 +++ b/Scripts/Terminal.Gui.PowerShell.psd1 @@ -49,7 +49,7 @@ PowerShellHostVersion = '7.4.0' # Processor architecture (None, MSIL, X86, IA64, Amd64, Arm, or an empty string) required by this module. One value only. # Set to AMD64 here because development on Terminal.Gui isn't really supported on anything else. # Has nothing to do with runtime use of Terminal.Gui. -ProcessorArchitecture = 'Amd64' +ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @( diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index 99c61e263..5fdc874b5 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -620,9 +620,9 @@ public class MainLoopTests } [Fact] - [AutoInitShutdown] public async Task InvokeLeakTest () { + Application.Init (); Random r = new (); TextField tf = new (); var top = new Toplevel (); @@ -641,10 +641,10 @@ public class MainLoopTests Assert.Equal (numIncrements * numPasses, tbCounter); top.Dispose (); + Application.Shutdown (); } [Theory] - [AutoInitShutdown] [MemberData (nameof (TestAddIdle))] public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions ( Action action, @@ -658,6 +658,8 @@ public class MainLoopTests int pfour ) { + Application.Init (); + total = 0; btn = null; clickMe = pclickMe; @@ -720,6 +722,8 @@ public class MainLoopTests Assert.True (taskCompleted); Assert.Equal (clickMe, btn.Text); Assert.Equal (four, total); + + Application.Shutdown (); } [Fact] diff --git a/UnitTests/Application/SynchronizatonContextTests.cs b/UnitTests/Application/SynchronizatonContextTests.cs index 3e7635580..b5a34976f 100644 --- a/UnitTests/Application/SynchronizatonContextTests.cs +++ b/UnitTests/Application/SynchronizatonContextTests.cs @@ -5,9 +5,9 @@ namespace Terminal.Gui.ApplicationTests; public class SyncrhonizationContextTests { [Fact] - [AutoInitShutdown] public void SynchronizationContext_CreateCopy () { + Application.Init (); SynchronizationContext context = SynchronizationContext.Current; Assert.NotNull (context); @@ -15,12 +15,13 @@ public class SyncrhonizationContextTests Assert.NotNull (contextCopy); Assert.NotEqual (context, contextCopy); + Application.Shutdown (); } [Fact] - [AutoInitShutdown] public void SynchronizationContext_Post () { + Application.Init (); SynchronizationContext context = SynchronizationContext.Current; var success = false; @@ -48,12 +49,14 @@ public class SyncrhonizationContextTests // blocks here until the RequestStop is processed at the end of the test Application.Run ().Dispose (); Assert.True (success); + Application.Shutdown (); } [Fact] [AutoInitShutdown] public void SynchronizationContext_Send () { + Application.Init (); SynchronizationContext context = SynchronizationContext.Current; var success = false; @@ -81,5 +84,6 @@ public class SyncrhonizationContextTests // blocks here until the RequestStop is processed at the end of the test Application.Run ().Dispose (); Assert.True (success); + Application.Shutdown (); } } diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index 015707cdc..20c855315 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -23,6 +23,7 @@ public class ScenarioTests : TestsAllViews .Where (type => type.IsClass && !type.IsAbstract && type.IsSubclassOf (typeof (Scenario))) .Select (type => new object [] { type }); + private readonly object _timeoutLock = new object (); /// /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run. @@ -32,61 +33,72 @@ public class ScenarioTests : TestsAllViews [MemberData (nameof (AllScenarioTypes))] public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType) { + // If a previous test failed, this will ensure that the Application is in a clean state Application.ResetState (true); _output.WriteLine ($"Running Scenario '{scenarioType}'"); - Scenario scenario = (Scenario)Activator.CreateInstance (scenarioType); uint abortTime = 500; - bool initialized = false; bool shutdown = false; - object timeout = null; - Application.InitializedChanged += (s, a) => - { - if (a.NewValue) - { - //output.WriteLine ($" Add timeout to force quit after {abortTime}ms"); - timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); + void OnApplicationOnInitializedChanged (object s, StateEventArgs a) + { + if (a.NewValue) + { + lock (_timeoutLock) + { + timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); + } - Application.Iteration += OnApplicationOnIteration; - initialized = true; - } - else - { - if (timeout is { }) - { - Application.RemoveTimeout (timeout); - timeout = null; - } - Application.Iteration -= OnApplicationOnIteration; - shutdown = true; - } - }; + Application.Iteration += OnApplicationOnIteration; + initialized = true; + } + else + { + Application.Iteration -= OnApplicationOnIteration; + shutdown = true; + } + } + + Application.InitializedChanged += OnApplicationOnInitializedChanged; scenario.Main (); scenario.Dispose (); scenario = null; + Application.InitializedChanged -= OnApplicationOnInitializedChanged; + + lock (_timeoutLock) + { + if (timeout is { }) + { + Application.RemoveTimeout (timeout); + timeout = null; + } + } + + Assert.True (initialized); Assert.True (shutdown); #if DEBUG_IDISPOSABLE Assert.Empty (Responder.Instances); #endif - Application.Shutdown (); return; // If the scenario doesn't close within 500ms, this will force it to quit bool ForceCloseCallback () { - if (timeout is { }) + lock (_timeoutLock) { - Application.RemoveTimeout (timeout); - timeout = null; + if (timeout is { }) + { + Application.RemoveTimeout (timeout); + timeout = null; + } } Application.ResetState (true); Assert.Fail ( @@ -97,7 +109,7 @@ public class ScenarioTests : TestsAllViews void OnApplicationOnIteration (object s, IterationEventArgs a) { - if (scenario is { }) + if (Application._initialized) { // Press QuitKey Application.OnKeyDown (Application.QuitKey); From 94242974232da269c222404365c9a324324563e2 Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 19 Jun 2024 14:45:17 -0700 Subject: [PATCH 81/85] Fixed Deep KeyBinding issues. Untested --- .../Application/ApplicationKeyboard.cs | 16 ++++++-- Terminal.Gui/View/ViewKeyboard.cs | 34 +++++++-------- Terminal.Gui/Views/Menu/Menu.cs | 6 +-- Terminal.Gui/Views/RadioGroup.cs | 17 ++++++++ Terminal.Gui/Views/ScrollView.cs | 2 +- Terminal.Gui/Views/Shortcut.cs | 26 ++++++++++-- Terminal.Gui/Views/Slider.cs | 23 +++++++---- Terminal.Gui/Views/TextField.cs | 4 +- Terminal.Gui/Views/TextView.cs | 4 +- UICatalog/Scenarios/Shortcuts.cs | 31 +++++++++----- UICatalog/Scenarios/Sliders.cs | 41 ++++++++++++++++--- UICatalog/Scenarios/VkeyPacketSimulator.cs | 2 +- UnitTests/View/KeyboardEventTests.cs | 6 +-- UnitTests/Views/ShortcutTests.cs | 37 +++++++++++++++++ UnitTests/Views/SliderTests.cs | 2 +- 15 files changed, 191 insertions(+), 60 deletions(-) diff --git a/Terminal.Gui/Application/ApplicationKeyboard.cs b/Terminal.Gui/Application/ApplicationKeyboard.cs index 0a56ce712..8447f76d0 100644 --- a/Terminal.Gui/Application/ApplicationKeyboard.cs +++ b/Terminal.Gui/Application/ApplicationKeyboard.cs @@ -141,12 +141,20 @@ partial class Application { foreach (View view in binding.Value) { - bool? handled = view?.OnInvokingKeyBindings (keyEvent); - - if (handled != null && (bool)handled) + if (view is {} && view.KeyBindings.TryGet (binding.Key, (KeyBindingScope)0xFFFF, out KeyBinding kb)) { - return true; + bool? handled = view.InvokeCommands (kb.Commands, binding.Key, kb); + if (handled != null && (bool)handled) + { + return true; + } } + //bool? handled = view?.OnInvokingKeyBindings (keyEvent, KeyBindingScope.Application); + + //if (handled != null && (bool)handled) + //{ + // return true; + //} } } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index e43103381..60cae2f5e 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -414,7 +414,7 @@ public partial class View return true; } - bool? handled = OnInvokingKeyBindings (keyEvent); + bool? handled = OnInvokingKeyBindings (keyEvent, KeyBindingScope.HotKey | KeyBindingScope.Focused); if (handled is { } && (bool)handled) { @@ -636,10 +636,10 @@ public partial class View /// if the key press was not handled. if the keypress was handled /// and no other view should see it. /// - public virtual bool? OnInvokingKeyBindings (Key keyEvent) + public virtual bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope) { - // fire event only if there's an app or hotkey binding for the key - if (KeyBindings.TryGet (keyEvent, KeyBindingScope.Application | KeyBindingScope.HotKey, out KeyBinding _)) + // fire event only if there's an hotkey binding for the key + if (KeyBindings.TryGet (keyEvent, scope, out KeyBinding _)) { InvokingKeyBindings?.Invoke (this, keyEvent); if (keyEvent.Handled) @@ -655,7 +655,7 @@ public partial class View // `InvokeKeyBindings` returns `false`. Continue passing the event (return `false` from `OnInvokeKeyBindings`).. // * If key bindings were found, and any handled the key (at least one `Command` returned `true`), // `InvokeKeyBindings` returns `true`. Continue passing the event (return `false` from `OnInvokeKeyBindings`). - bool? handled = InvokeKeyBindings (keyEvent); + bool? handled = InvokeKeyBindings (keyEvent, scope); if (handled is { } && (bool)handled) { @@ -664,22 +664,22 @@ public partial class View return true; } - if (Margin is { } && ProcessAdornmentKeyBindings (Margin, keyEvent, ref handled)) + if (Margin is { } && ProcessAdornmentKeyBindings (Margin, keyEvent, scope, ref handled)) { return true; } - if (Padding is { } && ProcessAdornmentKeyBindings (Padding, keyEvent, ref handled)) + if (Padding is { } && ProcessAdornmentKeyBindings (Padding, keyEvent, scope, ref handled)) { return true; } - if (Border is { } && ProcessAdornmentKeyBindings (Border, keyEvent, ref handled)) + if (Border is { } && ProcessAdornmentKeyBindings (Border, keyEvent, scope, ref handled)) { return true; } - if (ProcessSubViewKeyBindings (keyEvent, ref handled)) + if (ProcessSubViewKeyBindings (keyEvent, scope, ref handled)) { return true; } @@ -687,11 +687,11 @@ public partial class View return handled; } - private bool ProcessAdornmentKeyBindings (Adornment adornment, Key keyEvent, ref bool? handled) + private bool ProcessAdornmentKeyBindings (Adornment adornment, Key keyEvent, KeyBindingScope scope, ref bool? handled) { foreach (View subview in adornment?.Subviews) { - handled = subview.OnInvokingKeyBindings (keyEvent); + handled = subview.OnInvokingKeyBindings (keyEvent, scope); if (handled is { } && (bool)handled) { @@ -702,15 +702,15 @@ public partial class View return false; } - private bool ProcessSubViewKeyBindings (Key keyEvent, ref bool? handled) + private bool ProcessSubViewKeyBindings (Key keyEvent, KeyBindingScope scope, ref bool? handled) { // Now, process any key bindings in the subviews that are tagged to KeyBindingScope.HotKey. foreach (View subview in Subviews) { - if (subview.KeyBindings.TryGet (keyEvent, KeyBindingScope.HotKey, out KeyBinding binding)) + if (subview.KeyBindings.TryGet (keyEvent, scope, out KeyBinding binding)) { //keyEvent.Scope = KeyBindingScope.HotKey; - handled = subview.OnInvokingKeyBindings (keyEvent); + handled = subview.OnInvokingKeyBindings (keyEvent, scope); if (handled is { } && (bool)handled) { @@ -718,7 +718,7 @@ public partial class View } } - bool recurse = subview.ProcessSubViewKeyBindings (keyEvent, ref handled); + bool recurse = subview.ProcessSubViewKeyBindings (keyEvent, scope, ref handled); if (recurse || (handled is { } && (bool)handled)) { return true; @@ -744,11 +744,11 @@ public partial class View /// commands were invoked and at least one handled the command. if commands were invoked and at /// none handled the command. /// - protected bool? InvokeKeyBindings (Key key) + protected bool? InvokeKeyBindings (Key key, KeyBindingScope scope) { bool? toReturn = null; - if (!KeyBindings.TryGet (key, out KeyBinding binding)) + if (!KeyBindings.TryGet (key, scope, out KeyBinding binding)) { return null; } diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 89d8548e9..74bf85828 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -273,9 +273,9 @@ internal sealed class Menu : View } /// - public override bool? OnInvokingKeyBindings (Key keyEvent) + public override bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope) { - bool? handled = base.OnInvokingKeyBindings (keyEvent); + bool? handled = base.OnInvokingKeyBindings (keyEvent, scope); if (handled is { } && (bool)handled) { @@ -284,7 +284,7 @@ internal sealed class Menu : View // TODO: Determine if there's a cleaner way to handle this // This supports the case where the menu bar is a context menu - return _host.OnInvokingKeyBindings (keyEvent); + return _host.OnInvokingKeyBindings (keyEvent, scope); } private void Current_TerminalResized (object sender, SizeChangedEventArgs e) diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index 26ffaffde..c87ae9964 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -25,6 +25,11 @@ public class RadioGroup : View Command.LineUp, () => { + if (!HasFocus) + { + return false; + } + MoveUpLeft (); return true; @@ -35,6 +40,10 @@ public class RadioGroup : View Command.LineDown, () => { + if (!HasFocus) + { + return false; + } MoveDownRight (); return true; @@ -45,6 +54,10 @@ public class RadioGroup : View Command.TopHome, () => { + if (!HasFocus) + { + return false; + } MoveHome (); return true; @@ -55,6 +68,10 @@ public class RadioGroup : View Command.BottomEnd, () => { + if (!HasFocus) + { + return false; + } MoveEnd (); return true; diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index cfe50a955..673c9103e 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -392,7 +392,7 @@ public class ScrollView : View return true; } - bool? result = InvokeKeyBindings (a); + bool? result = InvokeKeyBindings (a, KeyBindingScope.HotKey); if (result is { }) { diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index a54571bf8..7290537a0 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -59,10 +59,11 @@ public class Shortcut : View Width = GetWidthDimAuto (); Height = Dim.Auto (DimAutoStyle.Content, 1); - AddCommand (Command.HotKey, ctx => OnAccept(ctx)); + AddCommand (Command.HotKey, ctx => OnAccept (ctx)); AddCommand (Command.Accept, ctx => OnAccept (ctx)); - KeyBindings.Add (KeyCode.Space, Command.Accept); + AddCommand (Command.Select, ctx => OnSelect (ctx)); KeyBindings.Add (KeyCode.Enter, Command.Accept); + KeyBindings.Add (KeyCode.Space, Command.Select); TitleChanged += Shortcut_TitleChanged; // This needs to be set before CommandView is set @@ -130,6 +131,7 @@ public class Shortcut : View } } + /// /// Creates a new instance of . /// @@ -403,6 +405,10 @@ public class Shortcut : View _commandView = value; _commandView.Id = "_commandView"; + // The default behavior is for CommandView to not get focus. I + // If you want it to get focus, you need to set it. + _commandView.CanFocus = false; + _commandView.MouseClick += Shortcut_MouseClick; _commandView.Accept += CommandViewAccept; @@ -608,7 +614,8 @@ public class Shortcut : View CommandView.KeyBindings.Remove (Key); CommandView.KeyBindings.Remove (CommandView.HotKey); KeyBindings.Remove (Key); - KeyBindings.Add (Key, KeyBindingScope, Command.Accept); + KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept); + //KeyBindings.Add (Key, KeyBindingScope.HotKey, Command.Accept); } } @@ -637,7 +644,7 @@ public class Shortcut : View case KeyBindingScope.Focused: // TODO: Figure this out - cancel = false; + cancel = base.OnAccept () == true; break; @@ -678,6 +685,17 @@ public class Shortcut : View #endregion Accept Handling + private bool? OnSelect (CommandContext ctx) + { + if (CommandView.GetSupportedCommands ().Contains (Command.Select)) + { + return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding); + } + return false; + + } + + #region Focus /// diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 076a2adce..6dca6f8c4 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui; +using System.Transactions; + +namespace Terminal.Gui; /// Slider control. public class Slider : Slider @@ -1418,7 +1420,8 @@ public class Slider : View AddCommand (Command.RightEnd, () => MoveEnd ()); AddCommand (Command.RightExtend, () => ExtendPlus ()); AddCommand (Command.LeftExtend, () => ExtendMinus ()); - AddCommand (Command.Accept, () => Set ()); + AddCommand (Command.Select, () => Select ()); + AddCommand (Command.Accept, () => Accept ()); SetKeyBindings (); } @@ -1454,7 +1457,7 @@ public class Slider : View KeyBindings.Add (Key.Home, Command.LeftHome); KeyBindings.Add (Key.End, Command.RightEnd); KeyBindings.Add (Key.Enter, Command.Accept); - KeyBindings.Add (Key.Space, Command.Accept); + KeyBindings.Add (Key.Space, Command.Select); } private Dictionary> GetSetOptionDictionary () { return _setOptions.ToDictionary (e => e, e => _options [e]); } @@ -1651,8 +1654,6 @@ public class Slider : View default: throw new ArgumentOutOfRangeException (_config._type.ToString ()); } - OnAccept (); - } internal bool ExtendPlus () @@ -1735,10 +1736,18 @@ public class Slider : View return true; } - internal bool Set () + internal bool Select () + { + SetFocusedOption(); + + return true; + } + + internal bool Accept () { SetFocusedOption (); - return true; + + return OnAccept () == true; } internal bool MovePlus () diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 3323e4541..7b808d1ea 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1034,7 +1034,7 @@ public class TextField : View } /// - public override bool? OnInvokingKeyBindings (Key a) + public override bool? OnInvokingKeyBindings (Key a, KeyBindingScope scope) { // Give autocomplete first opportunity to respond to key presses if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) @@ -1042,7 +1042,7 @@ public class TextField : View return true; } - return base.OnInvokingKeyBindings (a); + return base.OnInvokingKeyBindings (a, scope); } /// diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index f97254410..d9c67cf14 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -3634,7 +3634,7 @@ public class TextView : View } /// - public override bool? OnInvokingKeyBindings (Key a) + public override bool? OnInvokingKeyBindings (Key a, KeyBindingScope scope) { if (!a.IsValid) { @@ -3647,7 +3647,7 @@ public class TextView : View return true; } - return base.OnInvokingKeyBindings (a); + return base.OnInvokingKeyBindings (a, scope); } /// diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index e51921805..1b4a5848d 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -66,7 +66,7 @@ public class Shortcuts : Scenario CommandView = new RadioGroup { Orientation = Orientation.Vertical, - RadioLabels = ["One", "Two", "Three", "Four"] + RadioLabels = ["O_ne", "T_wo", "Th_ree", "Fo_ur"] } }; @@ -173,7 +173,6 @@ public class Shortcuts : Scenario if (peer.CanFocus) { peer.CommandView.CanFocus = e.NewValue == true; - //peer.SetColors (); } } } @@ -185,7 +184,6 @@ public class Shortcuts : Scenario Orientation = Orientation.Vertical, X = 0, Y = Pos.Bottom (vShortcut5), - Key = Key.F5, HelpText = "Width is Fill", Width = Dim.Width (vShortcut5), @@ -193,8 +191,9 @@ public class Shortcuts : Scenario CommandView = new Slider { Orientation = Orientation.Vertical, - AllowEmpty = false - } + AllowEmpty = true + }, + Key = Key.F5, }; ((Slider)vShortcutSlider.CommandView).Options = new () { new () { Legend = "A" }, new () { Legend = "B" }, new () { Legend = "C" } }; @@ -202,7 +201,7 @@ public class Shortcuts : Scenario ((Slider)vShortcutSlider.CommandView).OptionsChanged += (o, args) => { - eventSource.Add ($"OptionsChanged: {o.GetType ().Name} - {args.Options}"); + eventSource.Add ($"OptionsChanged: {o.GetType ().Name} - {string.Join (",", ((Slider)o).GetSetOptions ())}"); eventLog.MoveDown (); }; @@ -220,9 +219,22 @@ public class Shortcuts : Scenario }; Application.Top.Add (vShortcut6); - vShortcut6.SetFocus (); - ((CheckBox)vShortcut3.CommandView).OnToggled (); + + var vShortcut7 = new Shortcut + { + Orientation = Orientation.Vertical, + X = 0, + Y = Pos.Bottom (vShortcut6), + Width = Dim.Width (vShortcutSlider), + Key = Key.F6, + Title = "No _Help", + HelpText = "", + }; + + Application.Top.Add (vShortcut7); + vShortcut7.SetFocus (); + // Horizontal var hShortcut1 = new Shortcut @@ -350,8 +362,7 @@ public class Shortcuts : Scenario } } - ((CheckBox)vShortcut5.CommandView).OnToggled (); - ((CheckBox)vShortcut5.CommandView).OnToggled (); + //((CheckBox)vShortcut5.CommandView).OnToggled (); } private void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); } diff --git a/UICatalog/Scenarios/Sliders.cs b/UICatalog/Scenarios/Sliders.cs index fc4c1a9d5..6b52cb52e 100644 --- a/UICatalog/Scenarios/Sliders.cs +++ b/UICatalog/Scenarios/Sliders.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Tracing; using System.Linq; using System.Text; using Terminal.Gui; @@ -242,7 +244,7 @@ public class Sliders : Scenario } }; configView.Add (dimAutoUsesMin); - + #region Slider Orientation Slider Slider orientationSlider = new (new List { "Horizontal", "Vertical" }) @@ -393,7 +395,7 @@ public class Sliders : Scenario FrameView spacingOptions = new () { Title = "Spacing Options", - X = Pos.Right(orientationSlider), + X = Pos.Right (orientationSlider), Y = Pos.Top (orientationSlider), Width = Dim.Fill (), Height = Dim.Auto (), @@ -407,7 +409,7 @@ public class Sliders : Scenario Buttons.NumericUpDown innerSpacingUpDown = new () { - X = Pos.Right(label) + 1 + X = Pos.Right (label) + 1 }; innerSpacingUpDown.Value = app.Subviews.OfType ().First ().MinimumInnerSpacing; @@ -429,8 +431,8 @@ public class Sliders : Scenario - spacingOptions.Add(label, innerSpacingUpDown); - configView.Add(spacingOptions); + spacingOptions.Add (label, innerSpacingUpDown); + configView.Add (spacingOptions); #endregion @@ -564,6 +566,35 @@ public class Sliders : Scenario #endregion Config Slider + ObservableCollection eventSource = new (); + var eventLog = new ListView + { + X = Pos.Right (sliderBGColor), + Y = Pos.Bottom (spacingOptions), + Width = Dim.Fill (), + Height = Dim.Fill (), + ColorScheme = Colors.ColorSchemes ["Toplevel"], + Source = new ListWrapper (eventSource) + }; + configView.Add (eventLog); + + + foreach (Slider slider in app.Subviews.Where (v => v is Slider)!) + { + slider.Accept += (o, args) => + { + eventSource.Add ($"Accept: {string.Join(",", slider.GetSetOptions ())}"); + eventLog.MoveDown (); + args.Cancel = true; + }; + slider.OptionsChanged += (o, args) => + { + eventSource.Add ($"OptionsChanged: {string.Join (",", slider.GetSetOptions ())}"); + eventLog.MoveDown (); + args.Cancel = true; + }; + } + app.FocusFirst (); Application.Run (app); diff --git a/UICatalog/Scenarios/VkeyPacketSimulator.cs b/UICatalog/Scenarios/VkeyPacketSimulator.cs index 8d920c8ee..983fe2d29 100644 --- a/UICatalog/Scenarios/VkeyPacketSimulator.cs +++ b/UICatalog/Scenarios/VkeyPacketSimulator.cs @@ -104,7 +104,7 @@ public class VkeyPacketSimulator : Scenario if (_outputStarted) { // If the key wasn't handled by the TextView will popup a Dialog with the keys pressed. - bool? handled = tvOutput.OnInvokingKeyBindings (e); + bool? handled = tvOutput.OnInvokingKeyBindings (e, KeyBindingScope.HotKey | KeyBindingScope.Focused); if (handled == null || handled == false) { diff --git a/UnitTests/View/KeyboardEventTests.cs b/UnitTests/View/KeyboardEventTests.cs index 61e9de9d3..ec7eec6aa 100644 --- a/UnitTests/View/KeyboardEventTests.cs +++ b/UnitTests/View/KeyboardEventTests.cs @@ -421,7 +421,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews var view = new KeyBindingsTestView (); view.CommandReturns = toReturn; - bool? result = view.OnInvokingKeyBindings (Key.A); + bool? result = view.OnInvokingKeyBindings (Key.A, KeyBindingScope.HotKey | KeyBindingScope.Focused); Assert.Equal (expected, result); } @@ -449,9 +449,9 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews public bool OnKeyUpContinued { get; set; } public override string Text { get; set; } - public override bool? OnInvokingKeyBindings (Key keyEvent) + public override bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope) { - bool? handled = base.OnInvokingKeyBindings (keyEvent); + bool? handled = base.OnInvokingKeyBindings (keyEvent, scope); if (handled != null && (bool)handled) { diff --git a/UnitTests/Views/ShortcutTests.cs b/UnitTests/Views/ShortcutTests.cs index 939befc43..8cd448787 100644 --- a/UnitTests/Views/ShortcutTests.cs +++ b/UnitTests/Views/ShortcutTests.cs @@ -277,4 +277,41 @@ public class ShortcutTests Assert.False (shortcut.KeyView.Visible); Assert.DoesNotContain (shortcut.KeyView, shortcut.Subviews); } + + [Fact] + public void Focus_CanFocus_Default_Is_True () + { + Shortcut shortcut = new (); + shortcut.Key = Key.A; + shortcut.Text = "Help"; + shortcut.Title = "Command"; + Assert.True (shortcut.CanFocus); + Assert.False (shortcut.CommandView.CanFocus); + } + + [Fact] + public void Focus_CanFocus_CommandView_Add_Tracks () + { + Shortcut shortcut = new (); + Assert.True (shortcut.CanFocus); + Assert.False (shortcut.CommandView.CanFocus); + + shortcut.CommandView = new () { CanFocus = true }; + Assert.False (shortcut.CommandView.CanFocus); + + shortcut.CommandView.CanFocus = true; + Assert.True (shortcut.CommandView.CanFocus); + + shortcut.CanFocus = false; + Assert.False (shortcut.CanFocus); + Assert.True (shortcut.CommandView.CanFocus); + + shortcut.CommandView.CanFocus = false; + Assert.False (shortcut.CanFocus); + Assert.False (shortcut.CommandView.CanFocus); + + shortcut.CommandView.CanFocus = true; + Assert.False (shortcut.CanFocus); + Assert.True (shortcut.CommandView.CanFocus); + } } diff --git a/UnitTests/Views/SliderTests.cs b/UnitTests/Views/SliderTests.cs index 3f1298726..eaab6ba69 100644 --- a/UnitTests/Views/SliderTests.cs +++ b/UnitTests/Views/SliderTests.cs @@ -310,7 +310,7 @@ public class SliderTests // Act slider.FocusedOption = 2; - bool result = slider.Set (); + bool result = slider.Select (); // Assert Assert.True (result); From 64a84e602ed2706375c0ce1156d78d288e6f193e Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 19 Jun 2024 15:30:25 -0700 Subject: [PATCH 82/85] WIP --- .../Application/ApplicationKeyboard.cs | 11 +++----- Terminal.Gui/View/ViewKeyboard.cs | 7 +++-- UnitTests/View/HotKeyTests.cs | 26 ++++++++++++++++--- UnitTests/Views/RadioGroupTests.cs | 1 + 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Terminal.Gui/Application/ApplicationKeyboard.cs b/Terminal.Gui/Application/ApplicationKeyboard.cs index 8447f76d0..be737968f 100644 --- a/Terminal.Gui/Application/ApplicationKeyboard.cs +++ b/Terminal.Gui/Application/ApplicationKeyboard.cs @@ -143,18 +143,15 @@ partial class Application { if (view is {} && view.KeyBindings.TryGet (binding.Key, (KeyBindingScope)0xFFFF, out KeyBinding kb)) { - bool? handled = view.InvokeCommands (kb.Commands, binding.Key, kb); + //bool? handled = view.InvokeCommands (kb.Commands, binding.Key, kb); + bool? handled = view?.OnInvokingKeyBindings (keyEvent, kb.Scope); + if (handled != null && (bool)handled) { return true; } - } - //bool? handled = view?.OnInvokingKeyBindings (keyEvent, KeyBindingScope.Application); - //if (handled != null && (bool)handled) - //{ - // return true; - //} + } } } diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 60cae2f5e..84ca4841c 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -639,7 +639,7 @@ public partial class View public virtual bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope) { // fire event only if there's an hotkey binding for the key - if (KeyBindings.TryGet (keyEvent, scope, out KeyBinding _)) + if (KeyBindings.TryGet (keyEvent, scope, out KeyBinding kb)) { InvokingKeyBindings?.Invoke (this, keyEvent); if (keyEvent.Handled) @@ -709,7 +709,10 @@ public partial class View { if (subview.KeyBindings.TryGet (keyEvent, scope, out KeyBinding binding)) { - //keyEvent.Scope = KeyBindingScope.HotKey; + if (binding.Scope == KeyBindingScope.Focused && !subview.HasFocus) + { + continue; + } handled = subview.OnInvokingKeyBindings (keyEvent, scope); if (handled is { } && (bool)handled) diff --git a/UnitTests/View/HotKeyTests.cs b/UnitTests/View/HotKeyTests.cs index f1b3109f9..d284e9b01 100644 --- a/UnitTests/View/HotKeyTests.cs +++ b/UnitTests/View/HotKeyTests.cs @@ -81,7 +81,7 @@ public class HotKeyTests [InlineData (KeyCode.ShiftMask | KeyCode.AltMask, true)] [InlineData (KeyCode.CtrlMask, false)] [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, false)] - public void KeyPress_Runs_Default_HotKey_Command (KeyCode mask, bool expected) + public void NewKeyDownEvent_Runs_Default_HotKey_Command (KeyCode mask, bool expected) { var view = new View { HotKeySpecifier = (Rune)'^', Title = "^Test" }; view.CanFocus = true; @@ -91,10 +91,10 @@ public class HotKeyTests } [Fact] - public void ProcessKeyDown_Ignores_KeyBindings_Out_Of_Scope_SuperView () + public void NewKeyDownEvent_Ignores_Focus_KeyBindings_SuperView () { var view = new View (); - view.KeyBindings.Add (Key.A, Command.HotKey); + view.KeyBindings.Add (Key.A, Command.HotKey); // implies KeyBindingScope.Focused - so this should not be invoked view.InvokingKeyBindings += (s, e) => { Assert.Fail (); }; var superView = new View (); @@ -105,7 +105,25 @@ public class HotKeyTests } [Fact] - public void ProcessKeyDown_Invokes_HotKey_Command_With_SuperView () + public void NewKeyDownEvent_Honors_HotKey_KeyBindings_SuperView () + { + var view = new View (); + view.KeyBindings.Add (Key.A, KeyBindingScope.HotKey, Command.HotKey); + bool invoked = false; + view.InvokingKeyBindings += (s, e) => { invoked = true; }; + + var superView = new View (); + superView.Add (view); + + var ke = Key.A; + superView.NewKeyDownEvent (ke); + + Assert.True (invoked); + } + + + [Fact] + public void NewKeyDownEvent_InNewKeyDownEventvokes_HotKey_Command_With_SuperView () { var view = new View { HotKeySpecifier = (Rune)'^', Title = "^Test" }; diff --git a/UnitTests/Views/RadioGroupTests.cs b/UnitTests/Views/RadioGroupTests.cs index faf0275f2..4ddf68321 100644 --- a/UnitTests/Views/RadioGroupTests.cs +++ b/UnitTests/Views/RadioGroupTests.cs @@ -73,6 +73,7 @@ public class RadioGroupTests (ITestOutputHelper output) public void KeyBindings_Command () { var rg = new RadioGroup { RadioLabels = new [] { "Test", "New Test" } }; + rg.SetFocus(); Assert.True (rg.NewKeyDownEvent (Key.CursorUp)); Assert.True (rg.NewKeyDownEvent (Key.CursorDown)); From 303573bdea566fdf3ff03399829c8214bacbc3b0 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 20 Jun 2024 13:06:22 -0700 Subject: [PATCH 83/85] Fixed linux unit tests. Backed out change that registered quitkey as application scope. Beefed up tons of tests --- Terminal.Gui/Input/KeyBindings.cs | 2 +- Terminal.Gui/Views/ScrollView.cs | 2 +- Terminal.Gui/Views/Toplevel.cs | 4 +- UnitTests/Application/ApplicationTests.cs | 203 +++++++++++++++++- UnitTests/Application/KeyboardTests.cs | 120 ++++++++++- UnitTests/Application/MainLoopTests.cs | 18 +- .../Application/SynchronizatonContextTests.cs | 10 +- .../Configuration/ConfigurationMangerTests.cs | 9 + UnitTests/UICatalog/ScenarioTests.cs | 65 +++--- UnitTests/Views/ButtonTests.cs | 94 +++++++- UnitTests/Views/ToplevelTests.cs | 40 ++-- 11 files changed, 499 insertions(+), 68 deletions(-) diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs index 8ec38329e..cb0b079c2 100644 --- a/Terminal.Gui/Input/KeyBindings.cs +++ b/Terminal.Gui/Input/KeyBindings.cs @@ -38,7 +38,7 @@ public class KeyBindings else { Bindings.Add (key, binding); - if (binding.Scope.FastHasFlags (KeyBindingScope.Application)) + if (binding.Scope.HasFlag (KeyBindingScope.Application)) { Application.AddKeyBinding (key, BoundView); } diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 673c9103e..0448d5ef4 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -392,7 +392,7 @@ public class ScrollView : View return true; } - bool? result = InvokeKeyBindings (a, KeyBindingScope.HotKey); + bool? result = InvokeKeyBindings (a, KeyBindingScope.HotKey | KeyBindingScope.Focused); if (result is { }) { diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index d9bd4d1be..44d90ed3c 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -106,7 +106,7 @@ public partial class Toplevel : View ); // Default keybindings for this view - KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel); + KeyBindings.Add (Application.QuitKey, Command.QuitToplevel); KeyBindings.Add (Key.CursorRight, Command.NextView); KeyBindings.Add (Key.CursorDown, Command.NextView); @@ -119,7 +119,7 @@ public partial class Toplevel : View KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop); // TODO: Refresh Key should be configurable - KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh); + KeyBindings.Add (Key.F5, Command.Refresh); KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 2f0aa9b44..964fada3b 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -1,4 +1,5 @@ -using Xunit.Abstractions; +using Microsoft.VisualBasic; +using Xunit.Abstractions; // Alias Console to MockConsole so we don't accidentally use Console @@ -147,8 +148,12 @@ public class ApplicationTests Shutdown (); } - [Fact] - public void Init_ResetState_Resets_Properties () + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void Init_ResetState_Resets_Properties (Type driverType) { ConfigurationManager.ThrowOnJsonErrors = true; @@ -156,7 +161,7 @@ public class ApplicationTests // Set some values - Application.Init (); + Application.Init (driverName: driverType.Name); Application._initialized = true; // Reset @@ -228,7 +233,7 @@ public class ApplicationTests Application.AlternateBackwardKey = Key.A; Application.AlternateForwardKey = Key.B; Application.QuitKey = Key.C; - Application.AddKeyBinding(Key.A, new View ()); + Application.AddKeyBinding (Key.A, new View ()); //Application.OverlappedChildren = new List (); //Application.OverlappedTop = @@ -266,6 +271,71 @@ public class ApplicationTests #endif } + [Theory] + [InlineData (typeof (FakeDriver))] + [InlineData (typeof (NetDriver))] + [InlineData (typeof (WindowsDriver))] + [InlineData (typeof (CursesDriver))] + public void Init_Shutdown_Fire_InitializedChanged (Type driverType) + { + bool initialized = false; + bool shutdown = false; + + Application.InitializedChanged += OnApplicationOnInitializedChanged; + + Application.Init (driverName: driverType.Name); + Assert.True (initialized); + Assert.False (shutdown); + + Application.Shutdown (); + Assert.True (initialized); + Assert.True (shutdown); + + Application.InitializedChanged -= OnApplicationOnInitializedChanged; + + return; + + void OnApplicationOnInitializedChanged (object s, StateEventArgs a) + { + if (a.NewValue) + { + initialized = true; + } + else + { + shutdown = true; + } + } + } + + + [Fact] + public void Run_Iteration_Fires () + { + int iteration = 0; + + Application.Init (new FakeDriver ()); + + Application.Iteration += Application_Iteration; + Application.Run ().Dispose (); + + Assert.Equal (1, iteration); + Application.Shutdown (); + + return; + + void Application_Iteration (object sender, IterationEventArgs e) + { + if (iteration > 0) + { + Assert.Fail (); + } + iteration++; + Application.RequestStop (); + } + } + + [Fact] public void Init_Unbalanced_Throws () { @@ -826,10 +896,10 @@ public class ApplicationTests Application.OnMouseEvent (new () { Flags = MouseFlags.Button1Pressed }); Assert.Equal (w.Border, Application.MouseGrabView); - Assert.Equal (new Point (0,0), w.Frame.Location); + Assert.Equal (new Point (0, 0), w.Frame.Location); // Move down and to the right. - Application.OnMouseEvent (new () { Position = new (1,1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Application.OnMouseEvent (new () { Position = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (new Point (1, 1), w.Frame.Location); Application.End (rs); @@ -1024,4 +1094,123 @@ public class ApplicationTests } #endregion + + + private object _timeoutLock; + + [Fact] + public void AddTimeout_Fires () + { + Assert.Null (_timeoutLock); + _timeoutLock = new object (); + + uint timeoutTime = 250; + bool initialized = false; + int iteration = 0; + bool shutdown = false; + object timeout = null; + int timeoutCount = 0; + + Application.InitializedChanged += OnApplicationOnInitializedChanged; + + Application.Init (new FakeDriver ()); + Assert.True (initialized); + Assert.False (shutdown); + + _output.WriteLine ("Application.Run ().Dispose ().."); + Application.Run ().Dispose (); + _output.WriteLine ("Back from Application.Run ().Dispose ()"); + + Assert.True (initialized); + Assert.False (shutdown); + + Assert.Equal (1, timeoutCount); + Application.Shutdown (); + + Application.InitializedChanged -= OnApplicationOnInitializedChanged; + + lock (_timeoutLock) + { + if (timeout is { }) + { + Application.RemoveTimeout (timeout); + timeout = null; + } + } + + Assert.True (initialized); + Assert.True (shutdown); + +#if DEBUG_IDISPOSABLE + Assert.Empty (Responder.Instances); +#endif + lock (_timeoutLock) + { + _timeoutLock = null; + } + + return; + + void OnApplicationOnInitializedChanged (object s, StateEventArgs a) + { + if (a.NewValue) + { + Application.Iteration += OnApplicationOnIteration; + initialized = true; + + lock (_timeoutLock) + { + _output.WriteLine ($"Setting timeout for {timeoutTime}ms"); + timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (timeoutTime), TimeoutCallback); + } + + } + else + { + Application.Iteration -= OnApplicationOnIteration; + shutdown = true; + } + } + + bool TimeoutCallback () + { + lock (_timeoutLock) + { + _output.WriteLine ($"TimeoutCallback. Count: {++timeoutCount}. Application Iteration: {iteration}"); + if (timeout is { }) + { + _output.WriteLine ($" Nulling timeout."); + timeout = null; + } + } + + // False means "don't re-do timer and remove it" + return false; + } + + void OnApplicationOnIteration (object s, IterationEventArgs a) + { + lock (_timeoutLock) + { + if (timeoutCount > 0) + { + _output.WriteLine ($"Iteration #{iteration} - Timeout fired. Calling Application.RequestStop."); + Application.RequestStop (); + + return; + } + } + iteration++; + + // Simulate a delay + Thread.Sleep ((int)timeoutTime / 10); + + // Worst case scenario - something went wrong + if (Application._initialized && iteration > 25) + { + _output.WriteLine ($"Too many iterations ({iteration}): Calling Application.RequestStop."); + Application.RequestStop (); + } + } + } } diff --git a/UnitTests/Application/KeyboardTests.cs b/UnitTests/Application/KeyboardTests.cs index a293de45a..558619c3b 100644 --- a/UnitTests/Application/KeyboardTests.cs +++ b/UnitTests/Application/KeyboardTests.cs @@ -1,4 +1,5 @@ -using Xunit.Abstractions; +using UICatalog; +using Xunit.Abstractions; namespace Terminal.Gui.ApplicationTests; @@ -58,6 +59,123 @@ public class KeyboardTests top.Dispose (); } + [Fact] + public void QuitKey_Default_Is_CtrlQ () + { + Application.ResetState (true); + // Before Init + Assert.Equal (Key.Empty, Application.QuitKey); + + Application.Init (new FakeDriver ()); + // After Init + Assert.Equal (Key.Q.WithCtrl, Application.QuitKey); + + Application.Shutdown(); + } + + private object _timeoutLock; + + [Fact] + public void QuitKey_Quits () + { + Assert.Null (_timeoutLock); + _timeoutLock = new object (); + + uint abortTime = 500; + bool initialized = false; + int iteration = 0; + bool shutdown = false; + object timeout = null; + + Application.InitializedChanged += OnApplicationOnInitializedChanged; + + Application.Init (new FakeDriver ()); + Assert.True (initialized); + Assert.False (shutdown); + + _output.WriteLine ("Application.Run ().Dispose ().."); + Application.Run ().Dispose (); + _output.WriteLine ("Back from Application.Run ().Dispose ()"); + + Assert.True (initialized); + Assert.False (shutdown); + + Assert.Equal (1, iteration); + + Application.Shutdown (); + + Application.InitializedChanged -= OnApplicationOnInitializedChanged; + + lock (_timeoutLock) + { + if (timeout is { }) + { + Application.RemoveTimeout (timeout); + timeout = null; + } + } + + Assert.True (initialized); + Assert.True (shutdown); + +#if DEBUG_IDISPOSABLE + Assert.Empty (Responder.Instances); +#endif + lock (_timeoutLock) + { + _timeoutLock = null; + } + + return; + + void OnApplicationOnInitializedChanged (object s, StateEventArgs a) + { + _output.WriteLine ("OnApplicationOnInitializedChanged: {0}", a.NewValue); + if (a.NewValue) + { + Application.Iteration += OnApplicationOnIteration; + initialized = true; + lock (_timeoutLock) + { + timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); + } + } + else + { + Application.Iteration -= OnApplicationOnIteration; + shutdown = true; + } + } + + bool ForceCloseCallback () + { + lock (_timeoutLock) + { + _output.WriteLine ($"ForceCloseCallback. iteration: {iteration}"); + if (timeout is { }) + { + timeout = null; + } + } + Application.ResetState (true); + Assert.Fail ($"Failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit."); + + return false; + } + + void OnApplicationOnIteration (object s, IterationEventArgs a) + { + _output.WriteLine ("Iteration: {0}", iteration); + iteration++; + Assert.True (iteration < 2, "Too many iterations, something is wrong."); + if (Application._initialized) + { + _output.WriteLine (" Pressing QuitKey"); + Application.OnKeyDown (Application.QuitKey); + } + } + } + [Fact] public void AlternateForwardKey_AlternateBackwardKey_Tests () { diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index 5fdc874b5..71e8517a0 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.IO; // Alias Console to MockConsole so we don't accidentally use Console @@ -619,16 +620,22 @@ public class MainLoopTests ); } - [Fact] - public async Task InvokeLeakTest () + [Theory] + [InlineData (typeof (FakeDriver))] + //[InlineData (typeof (NetDriver))] // BUGBUG: NetDriver never exits in this test + + //[InlineData (typeof (ANSIDriver))] + //[InlineData (typeof (WindowsDriver))] // BUGBUG: NetDriver never exits in this test + //[InlineData (typeof (CursesDriver))] // BUGBUG: CursesDriver never exits in this test + public async Task InvokeLeakTest (Type driverType) { - Application.Init (); + Application.Init (driverName: driverType.Name); Random r = new (); TextField tf = new (); var top = new Toplevel (); top.Add (tf); - const int numPasses = 5; + const int numPasses = 2; const int numIncrements = 500; const int pollMs = 2500; @@ -658,7 +665,8 @@ public class MainLoopTests int pfour ) { - Application.Init (); + // TODO: Expand this test to test all drivers + Application.Init (new FakeDriver()); total = 0; btn = null; diff --git a/UnitTests/Application/SynchronizatonContextTests.cs b/UnitTests/Application/SynchronizatonContextTests.cs index b5a34976f..f0dd036d3 100644 --- a/UnitTests/Application/SynchronizatonContextTests.cs +++ b/UnitTests/Application/SynchronizatonContextTests.cs @@ -18,10 +18,14 @@ public class SyncrhonizationContextTests Application.Shutdown (); } - [Fact] - public void SynchronizationContext_Post () + [Theory] + [InlineData (typeof (FakeDriver))] + //[InlineData (typeof (NetDriver))] + [InlineData (typeof (WindowsDriver))] + //[InlineData (typeof (CursesDriver))] + public void SynchronizationContext_Post (Type driverType) { - Application.Init (); + Application.Init (driverName: driverType.Name); SynchronizationContext context = SynchronizationContext.Current; var success = false; diff --git a/UnitTests/Configuration/ConfigurationMangerTests.cs b/UnitTests/Configuration/ConfigurationMangerTests.cs index 553df407d..b1b7f2150 100644 --- a/UnitTests/Configuration/ConfigurationMangerTests.cs +++ b/UnitTests/Configuration/ConfigurationMangerTests.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Text.Json; +using Xunit.Abstractions; using static Terminal.Gui.ConfigurationManager; #pragma warning disable IDE1006 @@ -7,6 +8,13 @@ namespace Terminal.Gui.ConfigurationTests; public class ConfigurationManagerTests { + private readonly ITestOutputHelper _output; + + public ConfigurationManagerTests (ITestOutputHelper output) + { + _output = output; + } + public static readonly JsonSerializerOptions _jsonOptions = new () { Converters = { new AttributeJsonConverter (), new ColorJsonConverter () } @@ -402,6 +410,7 @@ public class ConfigurationManagerTests // Application is a static class PropertyInfo pi = typeof (Application).GetProperty ("QuitKey"); Assert.Equal (pi, Settings ["Application.QuitKey"].PropertyInfo); + // FrameView is not a static class and DefaultBorderStyle is Scope.Scheme pi = typeof (FrameView).GetProperty ("DefaultBorderStyle"); diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs index 20c855315..af6560799 100644 --- a/UnitTests/UICatalog/ScenarioTests.cs +++ b/UnitTests/UICatalog/ScenarioTests.cs @@ -23,7 +23,7 @@ public class ScenarioTests : TestsAllViews .Where (type => type.IsClass && !type.IsAbstract && type.IsSubclassOf (typeof (Scenario))) .Select (type => new object [] { type }); - private readonly object _timeoutLock = new object (); + private object _timeoutLock; /// /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run. @@ -33,41 +33,27 @@ public class ScenarioTests : TestsAllViews [MemberData (nameof (AllScenarioTypes))] public void All_Scenarios_Quit_And_Init_Shutdown_Properly (Type scenarioType) { + Assert.Null (_timeoutLock); + _timeoutLock = new object (); + // If a previous test failed, this will ensure that the Application is in a clean state Application.ResetState (true); _output.WriteLine ($"Running Scenario '{scenarioType}'"); Scenario scenario = (Scenario)Activator.CreateInstance (scenarioType); - uint abortTime = 500; + uint abortTime = 1500; bool initialized = false; bool shutdown = false; object timeout = null; - void OnApplicationOnInitializedChanged (object s, StateEventArgs a) - { - if (a.NewValue) - { - lock (_timeoutLock) - { - timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); - } - - Application.Iteration += OnApplicationOnIteration; - initialized = true; - } - else - { - Application.Iteration -= OnApplicationOnIteration; - shutdown = true; - } - } - Application.InitializedChanged += OnApplicationOnInitializedChanged; + Application.ForceDriver = "FakeDriver"; scenario.Main (); scenario.Dispose (); scenario = null; + Application.ForceDriver = string.Empty; Application.InitializedChanged -= OnApplicationOnInitializedChanged; @@ -75,7 +61,6 @@ public class ScenarioTests : TestsAllViews { if (timeout is { }) { - Application.RemoveTimeout (timeout); timeout = null; } } @@ -87,8 +72,40 @@ public class ScenarioTests : TestsAllViews #if DEBUG_IDISPOSABLE Assert.Empty (Responder.Instances); #endif + + lock (_timeoutLock) + { + _timeoutLock = null; + } + return; + + void OnApplicationOnInitializedChanged (object s, StateEventArgs a) + { + if (a.NewValue) + { + Assert.Equal (Key.Q.WithCtrl, Application.QuitKey); + + Application.Iteration += OnApplicationOnIteration; + initialized = true; + lock (_timeoutLock) + { + timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); + } + _output.WriteLine ($"Initialized '{Application.Driver}'"); + //Dictionary> bindings = Application.GetKeyBindings (); + //Assert.NotEmpty (bindings); + //_output.WriteLine ($"bindings: {string.Join (",", bindings.Keys)}"); + //Assert.True (bindings.ContainsKey (Application.QuitKey)); + } + else + { + Application.Iteration -= OnApplicationOnIteration; + shutdown = true; + } + } + // If the scenario doesn't close within 500ms, this will force it to quit bool ForceCloseCallback () { @@ -96,14 +113,13 @@ public class ScenarioTests : TestsAllViews { if (timeout is { }) { - Application.RemoveTimeout (timeout); timeout = null; } } - Application.ResetState (true); Assert.Fail ( $"'{scenario.GetName ()}' failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit."); + Application.ResetState (true); return false; } @@ -112,6 +128,7 @@ public class ScenarioTests : TestsAllViews if (Application._initialized) { // Press QuitKey + //_output.WriteLine ($"Forcing Quit with {Application.QuitKey}"); Application.OnKeyDown (Application.QuitKey); } } diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index d4d9c9cd0..03f0dcba0 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -258,6 +258,93 @@ public class ButtonTests (ITestOutputHelper output) Assert.True (clicked); } + [Theory] + [InlineData (false, 0)] + [InlineData (true, 1)] + public void Space_Fires_Accept (bool focused, int expected) + { + View superView = new View () + { + CanFocus = true, + }; + + Button button = new (); + + button.CanFocus = focused; + + int acceptInvoked = 0; + button.Accept += (s, e) => acceptInvoked++; + + superView.Add (button); + button.SetFocus (); + Assert.Equal (focused, button.HasFocus); + + superView.NewKeyDownEvent (Key.Space); + + Assert.Equal (expected, acceptInvoked); + + superView.Dispose (); + } + + [Theory] + [InlineData (false, 0)] + [InlineData (true, 1)] + public void Enter_Fires_Accept (bool focused, int expected) + { + View superView = new View () + { + CanFocus = true, + }; + + Button button = new (); + + button.CanFocus = focused; + + int acceptInvoked = 0; + button.Accept += (s, e) => acceptInvoked++; + + superView.Add (button); + button.SetFocus (); + Assert.Equal (focused, button.HasFocus); + + superView.NewKeyDownEvent (Key.Enter); + + Assert.Equal (expected, acceptInvoked); + + superView.Dispose (); + } + + [Theory] + [InlineData (false, 1)] + [InlineData (true, 1)] + public void HotKey_Fires_Accept (bool focused, int expected) + { + View superView = new View () + { + CanFocus = true, + }; + + Button button = new () + { + HotKey = Key.A + }; + + button.CanFocus = focused; + + int acceptInvoked = 0; + button.Accept += (s, e) => acceptInvoked++; + + superView.Add (button); + button.SetFocus (); + Assert.Equal (focused, button.HasFocus); + + superView.NewKeyDownEvent (Key.A); + + Assert.Equal (expected, acceptInvoked); + + superView.Dispose (); + } + /// /// This test demonstrates how to change the activation key for Button as described in the README.md keyboard /// handling section @@ -279,7 +366,9 @@ public class ButtonTests (ITestOutputHelper output) top.Add (btn); Application.Begin (top); - // default keybinding is Space which results in keypress + Assert.True (btn.HasFocus); + + // default keybinding is Space which results in Command.Accept (when focused) Application.OnKeyDown (new ((KeyCode)' ')); Assert.Equal (1, pressed); @@ -292,8 +381,7 @@ public class ButtonTests (ITestOutputHelper output) Assert.Equal (1, pressed); // Set a new binding of b for the click (Accept) event - btn.KeyBindings.Add (Key.B, Command.HotKey); - btn.KeyBindings.Add (Key.B, Command.Accept); + btn.KeyBindings.Add (Key.B, Command.HotKey); // b will now trigger the Accept command (when focused or not) // now pressing B should call the button click event Application.OnKeyDown (Key.B); diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index cce1f781f..f1a378cbf 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -627,42 +627,40 @@ public class ToplevelTests (ITestOutputHelper output) Assert.Equal (win1, Application.Current); Assert.Equal (win1, Application.OverlappedChildren [0]); win1.Running = true; - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Application.QuitKey)); + Assert.True (Application.OnKeyDown (Application.QuitKey)); Assert.False (isRunning); Assert.False (win1.Running); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.True ( - Application.OverlappedChildren [0].NewKeyDownEvent (Key.Z.WithCtrl) + Application.OnKeyDown (Key.Z.WithCtrl) ); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.F5)); // refresh + Assert.True (Application.OnKeyDown (Key.F5)); // refresh - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.Tab)); + Assert.True (Application.OnKeyDown (Key.Tab)); Assert.True (win1.IsCurrentTop); Assert.Equal (tvW1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.Tab)); + Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal ($"\tFirst line Win1{Environment.NewLine}Second line Win1", tvW1.Text); Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithShift) + Application.OnKeyDown (Key.Tab.WithShift) ); Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text); Assert.True ( - Application.OverlappedChildren [0] - .NewKeyDownEvent (Key.Tab.WithCtrl) + Application.OnKeyDown (Key.Tab.WithCtrl) ); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.Tab)); + Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorRight)); + Assert.True (Application.OnKeyDown (Key.CursorRight)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorDown)); + Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); #if UNIX_KEY_BINDINGS @@ -676,13 +674,13 @@ public class ToplevelTests (ITestOutputHelper output) ); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorLeft)); + Assert.True (Application.OnKeyDown (Key.CursorLeft)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorUp)); + Assert.True (Application.OnKeyDown (Key.CursorUp)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.Tab)); + Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); @@ -701,23 +699,23 @@ public class ToplevelTests (ITestOutputHelper output) ); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Application.AlternateForwardKey)); + Assert.True (Application.OnKeyDown (Application.AlternateForwardKey)); Assert.Equal (win2, Application.OverlappedChildren [0]); Assert.Equal (tf2W2, win2.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Application.AlternateBackwardKey)); + Assert.True (Application.OnKeyDown (Application.AlternateBackwardKey)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorDown)); + Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); #if UNIX_KEY_BINDINGS Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.B.WithCtrl))); #else - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorLeft)); + Assert.True (Application.OnKeyDown (Key.CursorLeft)); #endif Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf1W1, win1.MostFocused); - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorDown)); + Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tvW1, win1.MostFocused); Assert.Equal (Point.Empty, tvW1.CursorPosition); @@ -732,7 +730,7 @@ public class ToplevelTests (ITestOutputHelper output) #if UNIX_KEY_BINDINGS Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.F.WithCtrl))); #else - Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Key.CursorRight)); + Assert.True (Application.OnKeyDown (Key.CursorRight)); #endif Assert.Equal (win1, Application.OverlappedChildren [0]); Assert.Equal (tf2W1, win1.MostFocused); From 37e59dfcff838a150c609a7893d3120666f0b396 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 20 Jun 2024 22:25:33 +0100 Subject: [PATCH 84/85] PERF: At the request of @dodexahedron --- Terminal.Gui/Views/ComboBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 7bd2c6e99..a5d3ed4a2 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -744,6 +744,7 @@ public class ComboBox : View return; } + // PERF: At the request of @dodexahedron in the comment https://github.com/gui-cs/Terminal.Gui/pull/3552#discussion_r1648112410. _listview.SuspendCollectionChangedEvent (); // force deep copy From 625aa90503079bb621deb4440c1da40379d8ce49 Mon Sep 17 00:00:00 2001 From: Tig Date: Thu, 20 Jun 2024 14:34:26 -0700 Subject: [PATCH 85/85] Added debug spew --- Terminal.Gui/Input/KeyBindings.cs | 2 + Terminal.Gui/View/View.cs | 1 + Terminal.Gui/View/ViewKeyboard.cs | 62 ++++++++++++++++++++++++++++++- Terminal.Gui/View/ViewSubViews.cs | 2 + Terminal.Gui/Views/Toplevel.cs | 2 +- UICatalog/Scenarios/Shortcuts.cs | 9 +++-- UnitTests/View/NavigationTests.cs | 1 - 7 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs index cb0b079c2..a55169606 100644 --- a/Terminal.Gui/Input/KeyBindings.cs +++ b/Terminal.Gui/Input/KeyBindings.cs @@ -1,5 +1,7 @@ #nullable enable +using System.Diagnostics; + namespace Terminal.Gui; /// diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 441b0a3ac..654a4d915 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -230,6 +230,7 @@ public partial class View : Responder, ISupportInitializeNotification } Initialized?.Invoke (this, EventArgs.Empty); + } #endregion Constructors and Initialization diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs index 84ca4841c..d1f04c654 100644 --- a/Terminal.Gui/View/ViewKeyboard.cs +++ b/Terminal.Gui/View/ViewKeyboard.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Diagnostics; namespace Terminal.Gui; @@ -702,7 +703,7 @@ public partial class View return false; } - private bool ProcessSubViewKeyBindings (Key keyEvent, KeyBindingScope scope, ref bool? handled) + private bool ProcessSubViewKeyBindings (Key keyEvent, KeyBindingScope scope, ref bool? handled, bool invoke = true) { // Now, process any key bindings in the subviews that are tagged to KeyBindingScope.HotKey. foreach (View subview in Subviews) @@ -713,6 +714,12 @@ public partial class View { continue; } + + if (!invoke) + { + return true; + } + handled = subview.OnInvokingKeyBindings (keyEvent, scope); if (handled is { } && (bool)handled) @@ -721,7 +728,7 @@ public partial class View } } - bool recurse = subview.ProcessSubViewKeyBindings (keyEvent, scope, ref handled); + bool recurse = subview.ProcessSubViewKeyBindings (keyEvent, scope, ref handled, invoke); if (recurse || (handled is { } && (bool)handled)) { return true; @@ -731,6 +738,36 @@ public partial class View return false; } + // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful. + // TODO: A better approach would be have Application hold a list of bound Hotkeys, similar to + // TODO: how Application holds a list of Application Scoped key bindings and then check that list. + /// + /// Returns true if Key is bound in this view heirarchy. For debugging + /// + /// + /// + public bool IsHotKeyKeyBound (Key key, out View boundView) + { + // recurse through the subviews to find the views that has the key bound + boundView = null; + + foreach (View subview in Subviews) + { + if (subview.KeyBindings.TryGet (key, KeyBindingScope.HotKey, out _)) + { + boundView = subview; + return true; + } + + if (subview.IsHotKeyKeyBound (key, out boundView)) + { + return true; + } + + } + return false; + } + /// /// Invoked when a key is pressed that may be mapped to a key binding. Set to true to /// stop the key from being processed by other views. @@ -756,6 +793,27 @@ public partial class View return null; } +#if DEBUG + + // TODO: Determine if App scope bindings should be fired first or last (currently last). + if (Application.TryGetKeyBindings (key, out List views)) + { + var boundView = views [0]; + var commandBinding = boundView.KeyBindings.Get (key); + Debug.WriteLine ($"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.{commandBinding.Commands [0]}: {boundView}."); + } + + // TODO: This is a "prototype" debug check. It may be too annyoing vs. useful. + // Scour the bindings up our View heirarchy + // to ensure that the key is not already bound to a different set of commands. + if (SuperView?.IsHotKeyKeyBound (key, out View previouslyBoundView) ?? false) + { + Debug.WriteLine ($"WARNING: InvokeKeyBindings ({key}) - A subview or peer has bound this Key and will not see it: {previouslyBoundView}."); + } + +#endif + + foreach (Command command in binding.Commands) { if (!CommandImplementations.ContainsKey (command)) diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index ec5c68593..ad5e1016e 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Terminal.Gui; public partial class View diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 44d90ed3c..617dfa7fc 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -119,7 +119,7 @@ public partial class Toplevel : View KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop); // TODO: Refresh Key should be configurable - KeyBindings.Add (Key.F5, Command.Refresh); + KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh); KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix diff --git a/UICatalog/Scenarios/Shortcuts.cs b/UICatalog/Scenarios/Shortcuts.cs index 1b4a5848d..515ae96c9 100644 --- a/UICatalog/Scenarios/Shortcuts.cs +++ b/UICatalog/Scenarios/Shortcuts.cs @@ -1,6 +1,8 @@ using System; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading.Tasks; using System.Timers; using Terminal.Gui; @@ -38,7 +40,9 @@ public class Shortcuts : Scenario Width = 40, Height = Dim.Fill (4), ColorScheme = Colors.ColorSchemes ["Toplevel"], - Source = new ListWrapper (eventSource) + Source = new ListWrapper (eventSource), + BorderStyle = LineStyle.Double, + Title = "E_vents" }; Application.Top.Add (eventLog); @@ -228,7 +232,7 @@ public class Shortcuts : Scenario Y = Pos.Bottom (vShortcut6), Width = Dim.Width (vShortcutSlider), Key = Key.F6, - Title = "No _Help", + Title = "Not _very much help", HelpText = "", }; @@ -348,7 +352,6 @@ public class Shortcuts : Scenario Application.Top.Add (hShortcut3); - foreach (View sh in Application.Top.Subviews.Where (v => v is Shortcut)!) { if (sh is Shortcut shortcut) diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/NavigationTests.cs index 3ae1ac500..6c53aceb8 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/NavigationTests.cs @@ -1535,7 +1535,6 @@ public class NavigationTests (ITestOutputHelper output) } [Fact] - [AutoInitShutdown] public void WindowDispose_CanFocusProblem () { // Arrange