Browse Source

Initial commit

Tig 10 months ago
parent
commit
700d869bed

+ 2 - 2
Terminal.Gui/Application/Application.Mouse.cs

@@ -159,7 +159,7 @@ public static partial class Application // Mouse handling
             return;
         }
 
-        if (GrabMouse (deepestViewUnderMouse, mouseEvent))
+        if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
         {
             return;
         }
@@ -245,7 +245,7 @@ public static partial class Application // Mouse handling
         }
     }
 
-    internal static bool GrabMouse (View? deepestViewUnderMouse, MouseEvent mouseEvent)
+    internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mouseEvent)
     {
         if (MouseGrabView is { })
         {

+ 3 - 1
Terminal.Gui/View/View.Keyboard.cs

@@ -14,6 +14,8 @@ public partial class View // Keyboard APIs
         HotKeySpecifier = (Rune)'_';
         TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
 
+        // TODO: It's incorrect to think of Commands as being Keyboard things. The code below should be moved to View.cs
+
         // By default, the HotKey command sets the focus
         AddCommand (Command.HotKey, OnHotKey);
 
@@ -614,7 +616,7 @@ public partial class View // Keyboard APIs
         // Now, process any key bindings in the subviews that are tagged to KeyBindingScope.HotKey.
         foreach (View subview in Subviews)
         {
-            if (subview == Focused)
+            if (subview.HasFocus)
             {
                 continue;
             }

+ 5 - 1
Terminal.Gui/View/View.Mouse.cs

@@ -406,7 +406,11 @@ public partial class View // Mouse APIs
             // If mouse is still in bounds, generate a click
             if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.Position))
             {
-                return OnMouseClick (new (mouseEvent));
+                var meea = new MouseEventEventArgs (mouseEvent);
+
+                // We can ignore the return value of OnMouseClick; if the click is handled
+                // meea.Handled and meea.MouseEvent.Handled will be true
+                OnMouseClick (meea);
             }
 
             return mouseEvent.Handled = true;

+ 1 - 1
Terminal.Gui/View/View.Navigation.cs

@@ -292,7 +292,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// </returns>
     internal bool RestoreFocus ()
     {
-        View [] indicies = GetFocusChain (NavigationDirection.Forward, TabStop);
+        View [] indicies = GetFocusChain (NavigationDirection.Forward, null);
 
         if (Focused is null && _previouslyFocused is { } && indicies.Contains (_previouslyFocused))
         {

+ 43 - 21
Terminal.Gui/Views/Button.cs

@@ -21,7 +21,8 @@ namespace Terminal.Gui;
 ///         be fired.
 ///     </para>
 ///     <para>
-///         Set <see cref="View.WantContinuousButtonPressed"/> to <see langword="true"/> to have the <see cref="View.Accept"/> event
+///         Set <see cref="View.WantContinuousButtonPressed"/> to <see langword="true"/> to have the
+///         <see cref="View.Accept"/> event
 ///         invoked repeatedly while the button is pressed.
 ///     </para>
 /// </remarks>
@@ -34,13 +35,13 @@ public class Button : View, IDesignable
     private bool _isDefault;
 
     /// <summary>
-    /// Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
+    ///     Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
     public static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None;
 
     /// <summary>
-    /// Gets or sets the default Highlight Style.
+    ///     Gets or sets the default Highlight Style.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
     public static HighlightStyle DefaultHighlightStyle { get; set; } = HighlightStyle.Pressed | HighlightStyle.Hover;
@@ -62,11 +63,31 @@ public class Button : View, IDesignable
         CanFocus = true;
 
         // Override default behavior of View
-        AddCommand (Command.HotKey, () =>
-        {
-            SetFocus ();
-            return !OnAccept ();
-        });
+        AddCommand (
+                    Command.HotKey,
+                    () =>
+                    {
+                        bool cachedIsDefault = IsDefault; // Supports "Swap Default" in Buttons scenario
+
+                        bool? handled = OnAccept ();
+
+                        if (handled == true)
+                        {
+                            return true;
+                        }
+
+                        SetFocus ();
+
+                        // TODO: If `IsDefault` were a property on `View` *any* View could work this way. That's theoretical as 
+                        // TODO: no use-case has been identified for any View other than Button to act like this.
+                        // If Accept was not handled...
+                        if (cachedIsDefault && SuperView is { })
+                        {
+                            return SuperView.InvokeCommand (Command.Accept);
+                        }
+
+                        return false;
+                    });
 
         KeyBindings.Add (Key.Space, Command.HotKey);
         KeyBindings.Add (Key.Enter, Command.HotKey);
@@ -80,7 +101,7 @@ public class Button : View, IDesignable
 
     private bool _wantContinuousButtonPressed;
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override bool WantContinuousButtonPressed
     {
         get => _wantContinuousButtonPressed;
@@ -104,10 +125,7 @@ public class Button : View, IDesignable
         }
     }
 
-    private void Button_MouseClick (object sender, MouseEventEventArgs e)
-    {
-        e.Handled = InvokeCommand (Command.HotKey) == true;
-    }
+    private void Button_MouseClick (object sender, MouseEventEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; }
 
     private void Button_TitleChanged (object sender, EventArgs<string> e)
     {
@@ -115,22 +133,24 @@ public class Button : View, IDesignable
         TextFormatter.HotKeySpecifier = HotKeySpecifier;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override string Text
     {
-        get => base.Title;
-        set => base.Text = base.Title = value;
+        get => Title;
+        set => base.Text = Title = value;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override Rune HotKeySpecifier
     {
         get => base.HotKeySpecifier;
         set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
     }
 
-    /// <summary>Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.</summary>
-    /// <value><c>true</c> if is default; otherwise, <c>false</c>.</value>
+    /// <summary>
+    ///     Gets or sets whether the <see cref="Button"/> will invoke the <see cref="Command.Accept"/>
+    ///     command on the <see cref="View.SuperView"/> if <see cref="View.Accept"/> is not handled by a subscriber.
+    /// </summary>
     public bool IsDefault
     {
         get => _isDefault;
@@ -158,6 +178,7 @@ public class Button : View, IDesignable
                 if (TextFormatter.Text [i] == Text [0])
                 {
                     Move (i, 0);
+
                     return null; // Don't show the cursor
                 }
             }
@@ -170,6 +191,7 @@ public class Button : View, IDesignable
     protected override void UpdateTextFormatterText ()
     {
         base.UpdateTextFormatterText ();
+
         if (NoDecorations)
         {
             TextFormatter.Text = Text;
@@ -191,11 +213,11 @@ public class Button : View, IDesignable
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public bool EnableForDesign ()
     {
         Title = "_Button";
 
         return true;
     }
-}
+}

+ 2 - 0
Terminal.Gui/Views/FrameView.cs

@@ -1,5 +1,6 @@
 namespace Terminal.Gui;
 
+// TODO: FrameView is mis-named, really. It's far more about it being a TabGroup than a frame. 
 /// <summary>
 ///     The FrameView is a container View with a border around it. 
 /// </summary>
@@ -23,6 +24,7 @@ public class FrameView : View
 
     private void FrameView_MouseClick (object sender, MouseEventEventArgs e)
     {
+        // base sets focus on HotKey
         e.Handled = InvokeCommand (Command.HotKey) == true;
     }
 

+ 0 - 19
Terminal.Gui/Views/Window.cs

@@ -32,25 +32,6 @@ public class Window : Toplevel
         BorderStyle = DefaultBorderStyle;
         ShadowStyle = DefaultShadow;
 
-        // This enables the default button to be activated by the Enter key.
-        AddCommand (
-                    Command.Accept,
-                    () =>
-                    {
-                        // TODO: Perhaps all views should support the concept of being default?
-                        // ReSharper disable once InvertIf
-                        if (Subviews.FirstOrDefault (v => v is Button { IsDefault: true, Enabled: true }) is Button
-                            defaultBtn)
-                        {
-                            defaultBtn.InvokeCommand (Command.Accept);
-
-                            return true;
-                        }
-
-                        return OnAccept ();
-                    }
-                   );
-
         KeyBindings.Add (Key.Enter, Command.Accept);
     }
 

+ 13 - 3
UICatalog/Scenarios/Buttons.cs

@@ -32,7 +32,7 @@ public class Buttons : Scenario
         // This is the default button (IsDefault = true); if user presses ENTER in the TextField
         // the scenario will quit
         var defaultButton = new Button { X = Pos.Center (), Y = Pos.AnchorEnd (), IsDefault = true, Text = "_Quit" };
-        defaultButton.Accept += (s, e) => Application.RequestStop ();
+        main.Accept += (s, e) => Application.RequestStop ();
         main.Add (defaultButton);
 
         var swapButton = new Button
@@ -46,6 +46,7 @@ public class Buttons : Scenario
 
         swapButton.Accept += (s, e) =>
                              {
+                                 e.Handled = !swapButton.IsDefault;
                                  defaultButton.IsDefault = !defaultButton.IsDefault;
                                  swapButton.IsDefault = !swapButton.IsDefault;
                              };
@@ -57,6 +58,7 @@ public class Buttons : Scenario
                              {
                                  string btnText = button.Text;
                                  MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
+                                 e.Handled = true;
                              };
         }
 
@@ -96,11 +98,19 @@ public class Buttons : Scenario
         main.Add (
                   button = new () { X = 2, Y = Pos.Bottom (button) + 1, Height = 2, Text = "a Newline\nin the button" }
                  );
-        button.Accept += (s, e) => MessageBox.Query ("Message", "Question?", "Yes", "No");
+        button.Accept += (s, e) =>
+                         {
+                             MessageBox.Query ("Message", "Question?", "Yes", "No");
+                             e.Handled = true;
+                         };
 
         var textChanger = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "Te_xt Changer" };
         main.Add (textChanger);
-        textChanger.Accept += (s, e) => textChanger.Text += "!";
+        textChanger.Accept += (s, e) =>
+                              {
+                                  textChanger.Text += "!";
+                                  e.Handled = true;
+                              };
 
         main.Add (
                   button = new ()

+ 3 - 1
UICatalog/Scenarios/Dialogs.cs

@@ -22,6 +22,7 @@ public class Dialogs : Scenario
 
         var frame = new FrameView
         {
+            TabStop = TabBehavior.TabStop, // FrameView normally sets to TabGroup
             X = Pos.Center (),
             Y = 1,
             Width = Dim.Percent (75),
@@ -181,7 +182,7 @@ public class Dialogs : Scenario
             X = Pos.Center (), Y = Pos.Bottom (frame) + 2, IsDefault = true, Text = "_Show Dialog"
         };
 
-        showDialogButton.Accept += (s, e) =>
+        app.Accept += (s, e) =>
                                    {
                                        Dialog dlg = CreateDemoDialog (
                                                                       widthEdit,
@@ -194,6 +195,7 @@ public class Dialogs : Scenario
                                                                      );
                                        Application.Run (dlg);
                                        dlg.Dispose ();
+                                       e.Handled = true;
                                    };
 
         app.Add (showDialogButton);

+ 13 - 1
docfx/docs/navigation.md

@@ -6,6 +6,7 @@
 - What are the visual cues that help the user know what keystrokes will change the focus?
 - What are the visual cues that help the user know what keystrokes will cause action in elements of the application that don't currently have focus?
 - What is the order in which UI elements are traversed when using keyboard navigation?
+- What are the default actions for standard key/mouse input (e.g. Hotkey, `Space`, `Enter`, `MouseClick`)?
 
 ## Lexicon & Taxonomy
 
@@ -208,7 +209,18 @@ These could also be named `Gain/Lose`. They could also be combined into a single
 
 QUESTION: Should we retain the same names as in v1 to simplify porting? Or, given the semantics of `Handled` v. `Cancel` are reversed would it be better to rename and/or combine?
 
-## `TabIndex` and `TabIndexes`
+## Built-In Views Interactivity
+
+|                |                         |            |               | **Keyboard** |                       |                              |                           | **Mouse**                    |                              |                              |                |               |
+|----------------|-------------------------|------------|---------------|--------------|-----------------------|------------------------------|---------------------------|------------------------------|------------------------------|------------------------------|----------------|---------------|
+|                | **Number<br>of States** | **Static** | **IsDefault** | **Hotkeys**  | **Select<br>Command** | **Accept<br>Command**        | **Hotkey<br>Command**     | **CanFocus<br>Click**        | **CanFocus<br>DblCLick**     | **!CanFocus<br>Click**       | **RightClick** | **GrabMouse** |
+| **View**       | 1                       | Yes        | No            | 1            | OnSelect              | OnAccept                     | Focus                     | Focus                        |                              |                              |                | No            |
+| **Label**      | 1                       | Yes        | No            | 1            | OnSelect              | OnAccept                     | FocusNext                 | Focus                        |                              | FocusNext                    |                | No            |
+| **Button**     | 1                       | No         | Yes           | 1            | OnSelect              | Focus<br>OnAccept            | Focus<br>OnAccept         | HotKey                       |                              | Select                       |                | No            |
+| **Checkbox**   | 3                       | No         | No            | 1            | OnSelect<br>Advance   | OnAccept                     | OnAccept                  | Select                       |                              | Select                       |                | No            |
+| **RadioGroup** | > 1                     | No         | No            | 2+           | Advance               | Set SelectedItem<br>OnAccept | Focus<br>Set SelectedItem | SetFocus<br>Set _cursor      |                              | SetFocus<br>Set _cursor      |                | No            |
+| **Slider**     | > 1                     | No         | No            | 1            | SetFocusedOption      | SetFocusedOption<br>OnAccept | Focus                     | SetFocus<br>SetFocusedOption |                              | SetFocus<br>SetFocusedOption |                | Yes           |
+| **ListView**   | > 1                     | No         | No            | 1            | MarkUnMarkRow         | OpenSelectedItem<br>OnAccept | OnAccept                  | SetMark<br>OnSelectedChanged | OpenSelectedItem<br>OnAccept |                              |                | No            |
 
 ### v1 Behavior