Fixes #4468 - MouseGrab regressions (#4469)

* Fixed mouse grab issue

* Fixed mouse grab regrssions.

* Update Terminal.Gui/ViewBase/View.Mouse.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Terminal.Gui/ViewBase/View.Mouse.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Terminal.Gui/ViewBase/View.Mouse.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Terminal.Gui/ViewBase/View.Mouse.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Terminal.Gui/ViewBase/View.Mouse.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Terminal.Gui/ViewBase/View.Mouse.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* code cleanup

* Update Terminal.Gui/ViewBase/View.Mouse.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Addressing pr feedback

* updated mouse.md

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Tig
2025-12-09 08:32:04 -07:00
committed by GitHub
parent aab90e1fbc
commit b2cf674e0b
5 changed files with 330 additions and 153 deletions

View File

@@ -126,12 +126,16 @@ Mouse events are processed through the following workflow using the [Cancellable
1. **Driver Level**: The driver captures platform-specific mouse events and converts them to `MouseEventArgs`
2. **Application Level**: `IApplication.Mouse.RaiseMouseEvent` determines the target view and routes the event
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) which invoke commands
- Command handlers (e.g., `OnSelecting` for `Command.Select`)
- High-level events like `MouseEnter`, `MouseLeave`
3. **View Level**: The target view processes the event through `View.NewMouseEvent()`:
1. **Pre-condition validation** - Checks if view is enabled, visible, and wants the event type
2. **Low-level MouseEvent** - Raises `OnMouseEvent()` and `MouseEvent` event
3. **Mouse grab handling** - If `HighlightStates` or `WantContinuousButtonPressed` are set:
- Automatically grabs mouse on button press
- Handles press/release/click lifecycle
- Sets focus if view is focusable
- Updates `MouseState` (Pressed, PressedOutside)
4. **Command invocation** - For click events, invokes commands via `MouseBindings` (default: `Command.Select` ? `Selecting` event)
5. **Mouse wheel handling** - Raises `OnMouseWheel()` and `MouseWheel` event
### Handling Mouse Events Directly
@@ -228,15 +232,17 @@ public class MultiButtonView : View
}
```
## Mouse State
## Mouse State and Mouse Grab
### 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
* **None** - No mouse interaction with the view
* **In** - Mouse is positioned over the view (inside the viewport)
* **Pressed** - Mouse button is pressed down while over the view
* **PressedOutside** - Mouse was pressed inside but moved outside the view
* **PressedOutside** - Mouse was pressed inside but moved outside the view (when not using `WantContinuousButtonPressed`)
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.
@@ -253,6 +259,9 @@ view.MouseStateChanged += (sender, e) =>
case MouseState.Pressed:
// Change appearance when pressed
break;
case MouseState.PressedOutside:
// Mouse was pressed inside but moved outside
break;
}
};
```
@@ -264,6 +273,59 @@ Configure which states should cause highlighting:
view.HighlightStates = MouseState.In | MouseState.Pressed;
```
### Mouse Grab
Views with `HighlightStates` or `WantContinuousButtonPressed` enabled automatically **grab the mouse** when a button is pressed. This means:
1. **Automatic Grab**: The view receives all mouse events until the button is released, even if the mouse moves outside the view's `Viewport`
2. **Focus Management**: If the view is focusable (`CanFocus = true`), it automatically receives focus on the first button press
3. **State Tracking**: The view's `MouseState` is updated to reflect press/release/outside states
4. **Automatic Ungrab**: The mouse is released when:
- The button is released (via `WhenGrabbedHandleClicked()`)
- The view is removed from its parent hierarchy (via `View.OnRemoved()`)
- The application ends (via `App.End()`)
#### Continuous Button Press
When `WantContinuousButtonPressed` is set to `true`, the view receives repeated click events while the button is held down:
```cs
view.WantContinuousButtonPressed = true;
view.Selecting += (s, e) =>
{
// This will be called repeatedly while the button is held down
// Useful for scroll buttons, increment/decrement buttons, etc.
DoRepeatAction();
e.Handled = true;
};
```
**Note**: With `WantContinuousButtonPressed`, the `MouseState.PressedOutside` flag has no effect - the view continues to receive events and maintains the pressed state even when the mouse moves outside.
#### Mouse Grab Lifecycle
```
Button Press (inside view)
?
Mouse Grabbed Automatically
?? View receives focus (if CanFocus)
?? MouseState |= MouseState.Pressed
?? All mouse events route to this view
Mouse Move (while grabbed)
?? Inside Viewport: MouseState remains Pressed
?? Outside Viewport: MouseState |= MouseState.PressedOutside
(unless WantContinuousButtonPressed is true)
Button Release
?
Mouse Ungrabbed Automatically
?? MouseState &= ~MouseState.Pressed
?? MouseState &= ~MouseState.PressedOutside
?? Click event raised (if still in bounds)
```
## Mouse Button and Movement Concepts
* **Down** - Indicates the user pushed a mouse button down.
@@ -355,12 +417,25 @@ view.MouseEvent += (s, e) =>
* **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
* **Access mouse details via CommandContext** when you need position or flags in `Selecting` handlers:
```cs
view.Selecting += (s, e) =>
{
if (e.Context is CommandContext<MouseBinding> { Binding.MouseEventArgs: { } mouseArgs })
{
Point position = mouseArgs.Position;
MouseFlags flags = mouseArgs.Flags;
// Handle with position and flags
}
};
```
* **Handle Mouse Events directly** only for complex interactions like drag-and-drop or custom gestures (override `OnMouseEvent` or subscribe to `MouseEvent`)
* **Use `HighlightStates`** to enable automatic mouse grab and visual feedback - views will automatically grab the mouse and update their appearance
* **Use `WantContinuousButtonPressed`** for repeating actions (scroll buttons, increment/decrement) - the view will receive repeated events while the button is held
* **Respect platform conventions** - use right-click for context menus, double-click for default actions
* **Provide keyboard alternatives** - ensure all mouse functionality has keyboard equivalents
* **Test with different terminals** - mouse support varies between terminal applications
* **Use Mouse State** to provide visual feedback when users hover or interact with views
* **Mouse grab is automatic** - you don't need to manually call `GrabMouse()`/`UngrabMouse()` when using `HighlightStates` or `WantContinuousButtonPressed`
## Limitations and Considerations