mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 07:47:54 +01:00
* Initial plan * Add comprehensive MouseClick removal analysis Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix code review feedback on analysis document Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add actual line numbers to analysis document Co-authored-by: tig <585482+tig@users.noreply.github.com> * Final polish on analysis document - clarify TextView Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 3: Port 6 simple Views from MouseClick to Selecting/MouseBindings Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 2: Port 10 Scenarios from MouseClick to Selecting/remove Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update analysis document - Phases 2 & 3 complete Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix failing tests - Button multi-button support and Label focus handling Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 4: Port ScrollBar from OnMouseClick to OnSelecting; remove analysis doc Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 5: Update/remove tests that explicitly test MouseClick API Co-authored-by: tig <585482+tig@users.noreply.github.com> * Code cleanup * Remove MouseClick event Consolidated mouse event handling by removing the `MouseClick` event and integrating its functionality into the `MouseEvent` event. Simplified `MouseEventArgs` documentation and added support for invoking commands bound to mouse events. Reorganized code by removing `Mouse Pressed Events` and `Mouse Click Events` regions, introducing a new `WhenGrabbed Handlers` region. Updated tests to replace `MouseClick` with `MouseEvent`, adjusted test logic, and improved variable naming for clarity. Removed redundant assertions and unused code related to `MouseClick`. Improved event propagation logic to ensure proper handling of unhandled events. Performed general code cleanup to enhance readability and maintainability. * Updated deep dives. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig <tig@users.noreply.github.com>
This commit is contained in:
@@ -148,7 +148,7 @@ See the [Mouse Deep Dive](mouse.md).
|
||||
- [View.WantContinuousButtonPresses](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_WantContinuousButtonPresses) - Enables continuous button press events
|
||||
- [View.Highlight](~/api/Terminal.Gui.ViewBase.View.yml) - Event for visual feedback on mouse hover/click
|
||||
- [View.HighlightStyle](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_HighlightStyle) - Visual style when highlighted
|
||||
- Events: `MouseEnter`, `MouseLeave`, `MouseClick`, `MouseEvent`
|
||||
- Events: `MouseEnter`, `MouseLeave`, `MouseEvent`
|
||||
|
||||
### Layout and Arrangement
|
||||
|
||||
@@ -368,7 +368,6 @@ The mouse subsystem processes mouse events through:
|
||||
3. [View.MouseEnter](~/api/Terminal.Gui.ViewBase.View.yml) / [View.MouseLeave](~/api/Terminal.Gui.ViewBase.View.yml) events
|
||||
4. [View.MouseBindings](~/api/Terminal.Gui.ViewBase.View.yml) - Converts mouse actions to commands
|
||||
5. Command handlers
|
||||
6. [View.MouseClick](~/api/Terminal.Gui.ViewBase.View.yml) event (high-level)
|
||||
|
||||
### Layout
|
||||
|
||||
|
||||
@@ -555,7 +555,7 @@ void HandleMouse(object? sender, MouseEventArgs args) { }
|
||||
|
||||
```csharp
|
||||
// v2 - Viewport-relative coordinates
|
||||
view.MouseClick += (s, e) =>
|
||||
view.MouseEvent += (s, e) =>
|
||||
{
|
||||
// e.Position is relative to view's Viewport
|
||||
var x = e.Position.X; // 0 = left edge of viewport
|
||||
@@ -563,16 +563,120 @@ view.MouseClick += (s, e) =>
|
||||
};
|
||||
```
|
||||
|
||||
### Highlight Event
|
||||
### Mouse Click Handling
|
||||
|
||||
v2 adds a `Highlight` event for visual feedback:
|
||||
**v1:**
|
||||
```csharp
|
||||
// v1 - MouseClick event
|
||||
view.MouseClick += (mouseEvent) =>
|
||||
{
|
||||
// Handle click
|
||||
DoSomething();
|
||||
};
|
||||
```
|
||||
|
||||
**v2:**
|
||||
```csharp
|
||||
// v2 - Use MouseBindings + Commands + Selecting event
|
||||
view.MouseBindings.Add(MouseFlags.Button1Clicked, Command.Select);
|
||||
view.Selecting += (s, e) =>
|
||||
{
|
||||
// Handle selection (called when Button1Clicked)
|
||||
DoSomething();
|
||||
};
|
||||
|
||||
// Alternative: Use MouseEvent for low-level handling
|
||||
view.MouseEvent += (s, e) =>
|
||||
{
|
||||
if (e.Flags.HasFlag(MouseFlags.Button1Clicked))
|
||||
{
|
||||
DoSomething();
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Key Changes:**
|
||||
- `View.MouseClick` event has been **removed**
|
||||
- Use `MouseBindings` to map mouse events to `Command`s
|
||||
- Default mouse bindings invoke `Command.Select` which raises the `Selecting` event
|
||||
- For custom behavior, override `OnSelecting` or subscribe to the `Selecting` event
|
||||
- For low-level mouse handling, use `MouseEvent` directly
|
||||
|
||||
**Migration Pattern:**
|
||||
```csharp
|
||||
// ❌ v1 - OnMouseClick override
|
||||
protected override bool OnMouseClick(MouseEventArgs mouseEvent)
|
||||
{
|
||||
if (mouseEvent.Flags.HasFlag(MouseFlags.Button1Clicked))
|
||||
{
|
||||
PerformAction();
|
||||
return true;
|
||||
}
|
||||
return base.OnMouseClick(mouseEvent);
|
||||
}
|
||||
|
||||
// ✅ v2 - OnSelecting override
|
||||
protected override bool OnSelecting(CommandEventArgs args)
|
||||
{
|
||||
if (args.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
|
||||
{
|
||||
// Access mouse position and flags via context
|
||||
if (mouseArgs.Flags.HasFlag(MouseFlags.Button1Clicked))
|
||||
{
|
||||
PerformAction();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return base.OnSelecting(args);
|
||||
}
|
||||
|
||||
// ✅ v2 - Selecting event (simpler)
|
||||
view.Selecting += (s, e) =>
|
||||
{
|
||||
PerformAction();
|
||||
e.Handled = true;
|
||||
};
|
||||
```
|
||||
|
||||
**Accessing Mouse Position in Selecting Event:**
|
||||
```csharp
|
||||
view.Selecting += (s, e) =>
|
||||
{
|
||||
// Extract mouse event args from command context
|
||||
if (e.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
|
||||
{
|
||||
Point position = mouseArgs.Position;
|
||||
MouseFlags flags = mouseArgs.Flags;
|
||||
|
||||
// Use position and flags for custom logic
|
||||
HandleClick(position, flags);
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Mouse State and Highlighting
|
||||
|
||||
v2 adds enhanced mouse state tracking:
|
||||
|
||||
```csharp
|
||||
view.Highlight += (s, e) =>
|
||||
// Configure which mouse states trigger highlighting
|
||||
view.HighlightStates = MouseState.In | MouseState.Pressed;
|
||||
|
||||
// React to mouse state changes
|
||||
view.MouseStateChanged += (s, e) =>
|
||||
{
|
||||
// Provide visual feedback on mouse hover
|
||||
switch (e.Value)
|
||||
{
|
||||
case MouseState.In:
|
||||
// Mouse entered view
|
||||
break;
|
||||
case MouseState.Pressed:
|
||||
// Mouse button pressed in view
|
||||
break;
|
||||
}
|
||||
};
|
||||
view.HighlightStyle = HighlightStyle.Hover;
|
||||
```
|
||||
|
||||
See [Mouse Deep Dive](mouse.md) for complete details.
|
||||
|
||||
@@ -44,8 +44,12 @@ public class MyView : View
|
||||
AddCommand (Command.ScrollDown, () => ScrollVertical (1));
|
||||
MouseBindings.Add (MouseFlags.WheelDown, Command.ScrollDown);
|
||||
|
||||
AddCommand (Command.Select, () => SelectItem());
|
||||
MouseBindings.Add (MouseFlags.Button1Clicked, Command.Select);
|
||||
// Mouse clicks invoke Command.Select by default
|
||||
// Override to customize click behavior
|
||||
AddCommand (Command.Select, () => {
|
||||
SelectItem();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -56,17 +60,65 @@ The @Terminal.Gui.Input.Command enum lists generic operations that are implement
|
||||
|
||||
Here are some common mouse binding patterns used throughout Terminal.Gui:
|
||||
|
||||
* **Click Events**: `MouseFlags.Button1Clicked` for primary selection/activation
|
||||
* **Click Events**: `MouseFlags.Button1Clicked` for primary selection/activation - maps to `Command.Select` by default
|
||||
* **Double-Click Events**: `MouseFlags.Button1DoubleClicked` for default actions (like opening/accepting)
|
||||
* **Right-Click Events**: `MouseFlags.Button3Clicked` for context menus
|
||||
* **Scroll Events**: `MouseFlags.WheelUp` and `MouseFlags.WheelDown` for scrolling content
|
||||
* **Drag Events**: `MouseFlags.Button1Pressed` combined with mouse move tracking for drag operations
|
||||
|
||||
### Default Mouse Bindings
|
||||
|
||||
By default, all views have the following mouse bindings configured:
|
||||
|
||||
```cs
|
||||
MouseBindings.Add (MouseFlags.Button1Clicked, Command.Select);
|
||||
MouseBindings.Add (MouseFlags.Button2Clicked, Command.Select);
|
||||
MouseBindings.Add (MouseFlags.Button3Clicked, Command.Select);
|
||||
MouseBindings.Add (MouseFlags.Button4Clicked, Command.Select);
|
||||
MouseBindings.Add (MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl, Command.Select);
|
||||
```
|
||||
|
||||
When a mouse click occurs, the `Command.Select` is invoked, which raises the `Selecting` event. Views can override `OnSelecting` or subscribe to the `Selecting` event to handle clicks:
|
||||
|
||||
```cs
|
||||
public class MyView : View
|
||||
{
|
||||
public MyView()
|
||||
{
|
||||
// Option 1: Subscribe to Selecting event
|
||||
Selecting += (s, e) =>
|
||||
{
|
||||
if (e.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
|
||||
{
|
||||
// Access mouse position and flags
|
||||
HandleSelection(mouseArgs.Position, mouseArgs.Flags);
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Option 2: Override OnSelecting
|
||||
protected override bool OnSelecting(CommandEventArgs args)
|
||||
{
|
||||
if (args.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
|
||||
{
|
||||
// Custom selection logic with mouse position
|
||||
if (mouseArgs.Position.Y == 0)
|
||||
{
|
||||
HandleHeaderClick();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return base.OnSelecting(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mouse Events
|
||||
|
||||
At the core of *Terminal.Gui*'s mouse API is the @Terminal.Gui.Input.MouseEventArgs class. The @Terminal.Gui.Input.MouseEventArgs class provides a platform-independent abstraction for common mouse events. Every mouse event can be fully described in a @Terminal.Gui.Input.MouseEventArgs instance, and most of the mouse-related APIs are simply helper functions for decoding a @Terminal.Gui.Input.MouseEventArgs.
|
||||
|
||||
When the user does something with the mouse, the driver maps the platform-specific mouse event into a `MouseEventArgs` and calls `IApplication.Mouse.RaiseMouseEvent`. Then, `IApplication.Mouse.RaiseMouseEvent` determines which `View` the event should go to. The `View.OnMouseEvent` method can be overridden or the `View.MouseEvent` event can be subscribed to, to handle the low-level mouse event. If the low-level event is not handled by a view, `IApplication` will then call the appropriate high-level helper APIs. For example, if the user double-clicks the mouse, `View.OnMouseClick` will be called/`View.MouseClick` will be raised with the event arguments indicating which mouse button was double-clicked.
|
||||
When the user does something with the mouse, the driver maps the platform-specific mouse event into a `MouseEventArgs` and calls `IApplication.Mouse.RaiseMouseEvent`. Then, `IApplication.Mouse.RaiseMouseEvent` determines which `View` the event should go to. The `View.OnMouseEvent` method can be overridden or the `View.MouseEvent` event can be subscribed to, to handle the low-level mouse event. If the low-level event is not handled by a view, `IApplication` will then call the appropriate high-level helper APIs.
|
||||
|
||||
### Mouse Event Processing Flow
|
||||
|
||||
@@ -77,8 +129,9 @@ Mouse events are processed through the following workflow using the [Cancellable
|
||||
3. **View Level**: The target view processes the event through:
|
||||
- `OnMouseEvent` (virtual method that can be overridden)
|
||||
- `MouseEvent` event (for event subscribers)
|
||||
- Mouse bindings (if the event wasn't handled)
|
||||
- High-level events like `OnMouseClick`, `MouseEnter`, `MouseLeave`
|
||||
- Mouse bindings (if the event wasn't handled) which invoke commands
|
||||
- Command handlers (e.g., `OnSelecting` for `Command.Select`)
|
||||
- High-level events like `MouseEnter`, `MouseLeave`
|
||||
|
||||
### Handling Mouse Events Directly
|
||||
|
||||
@@ -114,15 +167,76 @@ public class CustomView : View
|
||||
}
|
||||
```
|
||||
|
||||
### Handling Mouse Clicks
|
||||
|
||||
The recommended pattern for handling mouse clicks is to use the `Selecting` event or override `OnSelecting`. This integrates with the command system and provides access to mouse event details through the command context:
|
||||
|
||||
```cs
|
||||
public class ClickableView : View
|
||||
{
|
||||
public ClickableView()
|
||||
{
|
||||
Selecting += OnSelecting;
|
||||
}
|
||||
|
||||
private void OnSelecting(object sender, CommandEventArgs e)
|
||||
{
|
||||
// Extract mouse event information from command context
|
||||
if (e.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
|
||||
{
|
||||
// Access mouse position (viewport-relative)
|
||||
Point clickPosition = mouseArgs.Position;
|
||||
|
||||
// Check which button was clicked
|
||||
if (mouseArgs.Flags.HasFlag(MouseFlags.Button1Clicked))
|
||||
{
|
||||
HandleLeftClick(clickPosition);
|
||||
}
|
||||
else if (mouseArgs.Flags.HasFlag(MouseFlags.Button3Clicked))
|
||||
{
|
||||
ShowContextMenu(clickPosition);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For views that need different behavior for different mouse buttons, configure custom mouse bindings:
|
||||
|
||||
```cs
|
||||
public class MultiButtonView : View
|
||||
{
|
||||
public MultiButtonView()
|
||||
{
|
||||
// Clear default bindings
|
||||
MouseBindings.Clear();
|
||||
|
||||
// Map different buttons to different commands
|
||||
MouseBindings.Add(MouseFlags.Button1Clicked, Command.Select);
|
||||
MouseBindings.Add(MouseFlags.Button3Clicked, Command.ContextMenu);
|
||||
|
||||
AddCommand(Command.ContextMenu, HandleContextMenu);
|
||||
}
|
||||
|
||||
private bool HandleContextMenu()
|
||||
{
|
||||
// Show context menu
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mouse State
|
||||
|
||||
The @Terminal.Gui.ViewBase.View.MouseState property provides an abstraction for the current state of the mouse, enabling views to do interesting things like change their appearance based on the mouse state.
|
||||
|
||||
Mouse states include:
|
||||
* **Normal** - Default state when mouse is not interacting with the view
|
||||
* **Over** - Mouse is positioned over the view
|
||||
* **In** - Mouse is positioned over the view (inside the viewport)
|
||||
* **Pressed** - Mouse button is pressed down while over the view
|
||||
* **Clicked** - Mouse was clicked on the view
|
||||
* **PressedOutside** - Mouse was pressed inside but moved outside the view
|
||||
|
||||
It works in conjunction with the @Terminal.Gui.ViewBase.View.HighlightStates which is a list of mouse states that will cause a view to become highlighted.
|
||||
|
||||
@@ -131,9 +245,9 @@ Subscribe to the @Terminal.Gui.ViewBase.View.MouseStateChanged event to be notif
|
||||
```cs
|
||||
view.MouseStateChanged += (sender, e) =>
|
||||
{
|
||||
switch (e.NewState)
|
||||
switch (e.Value)
|
||||
{
|
||||
case MouseState.Over:
|
||||
case MouseState.In:
|
||||
// Change appearance when mouse hovers
|
||||
break;
|
||||
case MouseState.Pressed:
|
||||
@@ -143,6 +257,13 @@ view.MouseStateChanged += (sender, e) =>
|
||||
};
|
||||
```
|
||||
|
||||
Configure which states should cause highlighting:
|
||||
|
||||
```cs
|
||||
// Highlight when mouse is over the view or when pressed
|
||||
view.HighlightStates = MouseState.In | MouseState.Pressed;
|
||||
```
|
||||
|
||||
## Mouse Button and Movement Concepts
|
||||
|
||||
* **Down** - Indicates the user pushed a mouse button down.
|
||||
@@ -150,6 +271,7 @@ view.MouseStateChanged += (sender, e) =>
|
||||
* **Released** - Indicates the user released a mouse button.
|
||||
* **Clicked** - Indicates the user pressed then released the mouse button while over a particular View.
|
||||
* **Double-Clicked** - Indicates the user clicked twice in rapid succession.
|
||||
* **Triple-Clicked** - Indicates the user clicked three times in rapid succession.
|
||||
* **Moved** - Indicates the mouse moved to a new location since the last mouse event.
|
||||
* **Wheel** - Indicates the mouse wheel was scrolled up or down.
|
||||
|
||||
@@ -179,7 +301,7 @@ public class MyView : View
|
||||
if (mouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked))
|
||||
{
|
||||
// Access application mouse functionality through View.App
|
||||
App?.MouseEvent?.Invoke(this, mouseEvent);
|
||||
App?.Mouse?.RaiseMouseEvent(mouseEvent);
|
||||
return true;
|
||||
}
|
||||
return base.OnMouseEvent(mouseEvent);
|
||||
@@ -209,16 +331,31 @@ view.MouseLeave += (sender, e) =>
|
||||
|
||||
Mouse coordinates in Terminal.Gui are provided in multiple coordinate systems:
|
||||
|
||||
* **Screen Coordinates** - Relative to the entire terminal screen (0,0 is top-left of terminal)
|
||||
* **View Coordinates** - Relative to the view's content area (0,0 is top-left of view's viewport)
|
||||
* **Screen Coordinates** - Relative to the entire terminal screen (0,0 is top-left of terminal) - available via `MouseEventArgs.ScreenPosition`
|
||||
* **View Coordinates** - Relative to the view's viewport (0,0 is top-left of view's viewport) - available via `MouseEventArgs.Position`
|
||||
|
||||
The `MouseEventArgs` provides both coordinate systems:
|
||||
* `MouseEventArgs.Position` - Screen coordinates
|
||||
* `MouseEventArgs.ViewPosition` - View-relative coordinates (when available)
|
||||
* `MouseEventArgs.ScreenPosition` - Screen coordinates (absolute position on screen)
|
||||
* `MouseEventArgs.Position` - Viewport-relative coordinates (position within the view's content area)
|
||||
|
||||
When handling mouse events in views, use `Position` for viewport-relative coordinates:
|
||||
|
||||
```cs
|
||||
view.MouseEvent += (s, e) =>
|
||||
{
|
||||
// e.Position is viewport-relative
|
||||
if (e.Position.X < 10 && e.Position.Y < 5)
|
||||
{
|
||||
// Click in top-left corner of viewport
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
* **Use Mouse Bindings** when possible for simple mouse interactions - they integrate well with the Command system
|
||||
* **Use Mouse Bindings and Commands** for simple mouse interactions - they integrate well with the Command system and work alongside keyboard bindings
|
||||
* **Use the `Selecting` event** to handle mouse clicks - it's raised by the default `Command.Select` binding for all mouse buttons
|
||||
* **Access mouse details via CommandContext** when you need position or flags in `Selecting` handlers
|
||||
* **Handle Mouse Events directly** for complex interactions like drag-and-drop or custom gestures
|
||||
* **Respect platform conventions** - use right-click for context menus, double-click for default actions
|
||||
* **Provide keyboard alternatives** - ensure all mouse functionality has keyboard equivalents
|
||||
@@ -231,9 +368,4 @@ The `MouseEventArgs` provides both coordinate systems:
|
||||
* Mouse wheel support may vary between platforms and terminals
|
||||
* Some terminals may not support all mouse buttons or modifier keys
|
||||
* Mouse coordinates are limited to character cell boundaries - sub-character precision is not available
|
||||
* Performance can be impacted by excessive mouse move event handling - use mouse enter/leave events when appropriate rather than tracking all mouse moves
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
* Performance can be impacted by excessive mouse move event handling - use mouse enter/leave events when appropriate rather than tracking all mouse moves
|
||||
@@ -7,7 +7,7 @@ This document covers Terminal.Gui's navigation system, which determines:
|
||||
- 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`)?
|
||||
- What are the default actions for standard key/mouse input (e.g. Hotkey, `Space`, `Enter`, or a mouse click)?
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
Reference in New Issue
Block a user