#nullable enable using System.ComponentModel; namespace Terminal.Gui.ViewBase; public partial class View // Mouse APIs { /// Gets the mouse bindings for this view. public MouseBindings MouseBindings { get; internal set; } = null!; private void SetupMouse () { MouseBindings = new (); // TODO: Should the default really work with any button or just button1? 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); } /// /// Invokes the Commands bound to the MouseFlags specified by . /// See for an overview of Terminal.Gui mouse APIs. /// /// The mouse event passed. /// /// if no command was invoked; input processing should continue. /// if at least one command was invoked and was not handled (or cancelled); input processing /// should continue. /// if at least one command was invoked and handled (or cancelled); input processing should /// stop. /// protected bool? InvokeCommandsBoundToMouse (MouseEventArgs mouseEventArgs) { if (!MouseBindings.TryGet (mouseEventArgs.Flags, out MouseBinding binding)) { return null; } binding.MouseEventArgs = mouseEventArgs; return InvokeCommands (binding.Commands, binding); } #region MouseEnterLeave /// /// Gets whether the mouse is currently hovering over the View's . Is after /// has been raised, and before is raised. /// public bool MouseHovering { get; internal set; } /// /// INTERNAL Called by when the mouse moves over the View's /// . /// will /// be raised when the mouse is no longer over the . If another View occludes this View, the /// that View will also receive MouseEnter/Leave events. /// /// /// /// if the event was canceled, if not, if the /// view is not visible. Cancelling the event /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events. /// internal bool? NewMouseEnterEvent (CancelEventArgs eventArgs) { // Pre-conditions if (!CanBeVisible (this)) { return null; } // Cancellable event if (OnMouseEnter (eventArgs)) { return true; } MouseEnter?.Invoke (this, eventArgs); if (eventArgs.Cancel) { return true; } MouseHovering = true; if (HighlightStyle != HighlightStyle.None) { SetNeedsDraw (); } return false; } /// /// Gets the to use when the view is highlighted. The highlight colorscheme /// is based on the current , using . /// /// The highlight scheme. public Scheme GetHighlightScheme () { Scheme cs = GetScheme (); return cs with { Normal = new ( GetAttributeForRole (VisualRole.Normal).Foreground.GetBrighterColor (), GetAttributeForRole (VisualRole.Normal).Background, GetAttributeForRole (VisualRole.Normal).Style), HotNormal = new ( GetAttributeForRole (VisualRole.HotNormal).Foreground.GetBrighterColor (), GetAttributeForRole (VisualRole.HotNormal).Background, GetAttributeForRole (VisualRole.HotNormal).Style), Focus = new ( GetAttributeForRole (VisualRole.Focus).Foreground.GetBrighterColor (), GetAttributeForRole (VisualRole.Focus).Background, GetAttributeForRole (VisualRole.Focus).Style), HotFocus = new ( GetAttributeForRole (VisualRole.HotFocus).Foreground.GetBrighterColor (), GetAttributeForRole (VisualRole.HotFocus).Background, GetAttributeForRole (VisualRole.HotFocus).Style) }; } /// /// Called when the mouse moves over the View's and no other non-SubView occludes it. /// will /// be raised when the mouse is no longer over the . /// /// /// /// A view must be visible to receive Enter events (Leave events are always received). /// /// /// If the event is cancelled, the mouse event will not be propagated to other views and /// will not be raised. /// /// /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . /// /// /// See for more information. /// /// /// /// /// if the event was canceled, if not. Cancelling the event /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events. /// protected virtual bool OnMouseEnter (CancelEventArgs eventArgs) { return false; } /// /// Raised when the mouse moves over the View's . will /// be raised when the mouse is no longer over the . If another View occludes this View, the /// that View will also receive MouseEnter/Leave events. /// /// /// /// A view must be visible to receive Enter events (Leave events are always received). /// /// /// If the event is cancelled, the mouse event will not be propagated to other views. /// /// /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . /// /// /// Set to if the event was canceled, /// if not. Cancelling the event /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events. /// /// /// See for more information. /// /// public event EventHandler? MouseEnter; /// /// INTERNAL Called by when the mouse leaves , or is /// occluded /// by another non-SubView. /// /// /// /// This method calls and raises the event. /// /// /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . /// /// /// See for more information. /// /// internal void NewMouseLeaveEvent () { // Pre-conditions // Non-cancellable event OnMouseLeave (); MouseLeave?.Invoke (this, EventArgs.Empty); MouseHovering = false; if (HighlightStyle != HighlightStyle.None) { SetNeedsDraw (); } } /// /// Called when the mouse moves outside View's , or is occluded by another non-SubView. /// /// /// /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . /// /// /// See for more information. /// /// protected virtual void OnMouseLeave () { } /// /// Raised when the mouse moves outside View's , or is occluded by another non-SubView. /// /// /// /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . /// /// /// See for more information. /// /// public event EventHandler? MouseLeave; #endregion MouseEnterLeave #region Low Level Mouse Events /// Gets or sets whether the wants continuous button pressed events. public virtual bool WantContinuousButtonPressed { get; set; } /// Gets or sets whether the wants mouse position reports. /// if mouse position reports are wanted; otherwise, . public bool WantMousePositionReports { get; set; } /// /// Processes a new . This method is called by when a /// mouse /// event occurs. /// /// /// /// A view must be both enabled and visible to receive mouse events. /// /// /// This method raises /; if not handled, and one of the /// mouse buttons was clicked, the / event will be raised /// /// /// See for more information. /// /// /// If is , the / /// event /// will be raised on any new mouse event where indicates a button /// is pressed. /// /// /// /// if the event was handled, otherwise. public bool? NewMouseEvent (MouseEventArgs mouseEvent) { // Pre-conditions if (!Enabled) { // A disabled view should not eat mouse events return false; } if (!CanBeVisible (this)) { return false; } if (!WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition) { return false; } // Cancellable event if (RaiseMouseEvent (mouseEvent) || mouseEvent.Handled) { return true; } // Post-Conditions if (HighlightStyle != HighlightStyle.None || WantContinuousButtonPressed) { if (WhenGrabbedHandlePressed (mouseEvent)) { return mouseEvent.Handled; } if (WhenGrabbedHandleReleased (mouseEvent)) { return mouseEvent.Handled; } if (WhenGrabbedHandleClicked (mouseEvent)) { return mouseEvent.Handled; } } // We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and // it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked if (mouseEvent.IsSingleDoubleOrTripleClicked) { return RaiseMouseClickEvent (mouseEvent); } if (mouseEvent.IsWheel) { return RaiseMouseWheelEvent (mouseEvent); } return false; } /// /// Raises the / event. /// /// /// , if the event was handled, otherwise. public bool RaiseMouseEvent (MouseEventArgs mouseEvent) { if (OnMouseEvent (mouseEvent) || mouseEvent.Handled) { return true; } MouseEvent?.Invoke (this, mouseEvent); return mouseEvent.Handled; } /// Called when a mouse event occurs within the view's . /// /// /// The coordinates are relative to . /// /// /// /// , if the event was handled, otherwise. protected virtual bool OnMouseEvent (MouseEventArgs mouseEvent) { return false; } /// Raised when a mouse event occurs. /// /// /// The coordinates are relative to . /// /// public event EventHandler? MouseEvent; #endregion Low Level Mouse Events #region Mouse Pressed Events /// /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event /// (typically /// when or are set). /// /// /// Marked internal just to support unit tests /// /// /// , if the event was handled, otherwise. internal bool WhenGrabbedHandleReleased (MouseEventArgs mouseEvent) { mouseEvent.Handled = false; if (mouseEvent.IsReleased) { if (Application.MouseGrabView == this) { SetPressedHighlight (HighlightStyle.None); } return mouseEvent.Handled = true; } return false; } /// /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event /// (typically /// when or are set). /// /// /// /// Marked internal just to support unit tests /// /// /// /// , if the event was handled, otherwise. private bool WhenGrabbedHandlePressed (MouseEventArgs mouseEvent) { mouseEvent.Handled = false; if (mouseEvent.IsPressed) { // The first time we get pressed event, grab the mouse and set focus if (Application.MouseGrabView != this) { Application.GrabMouse (this); if (!HasFocus && CanFocus) { // Set the focus, but don't invoke Accept SetFocus (); } mouseEvent.Handled = true; } if (Viewport.Contains (mouseEvent.Position)) { if (this is not Adornment && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None)) { return true; } } else { if (this is not Adornment && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None)) { return true; } } if (WantContinuousButtonPressed && Application.MouseGrabView == this) { return RaiseMouseClickEvent (mouseEvent); } return mouseEvent.Handled = true; } return false; } #endregion Mouse Pressed Events #region Mouse Click Events /// Raises the / event. /// /// /// Called when the mouse is either clicked or double-clicked. /// /// /// If is , will be invoked on every mouse event /// where /// the mouse button is pressed. /// /// /// , if the event was handled, otherwise. protected bool RaiseMouseClickEvent (MouseEventArgs args) { // Pre-conditions if (!Enabled) { // QUESTION: Is this right? Should a disabled view eat mouse clicks? return args.Handled = false; } // Cancellable event if (OnMouseClick (args) || args.Handled) { return args.Handled; } MouseClick?.Invoke (this, args); if (args.Handled) { return true; } // Post-conditions // By default, this will raise Selecting/OnSelecting - Subclasses can override this via AddCommand (Command.Select ...). args.Handled = InvokeCommandsBoundToMouse (args) == true; return args.Handled; } /// /// Called when a mouse click occurs. Check to see which button was clicked. /// /// /// /// Called when the mouse is either clicked or double-clicked. /// /// /// If is , will be called on every mouse event /// where /// the mouse button is pressed. /// /// /// /// , if the event was handled, otherwise. protected virtual bool OnMouseClick (MouseEventArgs args) { return false; } /// Raised when a mouse click occurs. /// /// /// Raised when the mouse is either clicked or double-clicked. /// /// /// If is , will be raised on every mouse event /// where /// the mouse button is pressed. /// /// public event EventHandler? MouseClick; /// /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the click event /// (typically /// when or are set). /// /// /// Marked internal just to support unit tests /// /// /// , if the event was handled, otherwise. internal bool WhenGrabbedHandleClicked (MouseEventArgs mouseEvent) { mouseEvent.Handled = false; if (Application.MouseGrabView == this && mouseEvent.IsSingleClicked) { // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab Application.UngrabMouse (); if (SetPressedHighlight (HighlightStyle.None)) { return true; } // If mouse is still in bounds, generate a click if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position)) { return RaiseMouseClickEvent (mouseEvent); } return mouseEvent.Handled = true; } return false; } #endregion Mouse Clicked Events #region Mouse Wheel Events /// Raises the / event. /// /// /// , if the event was handled, otherwise. protected bool RaiseMouseWheelEvent (MouseEventArgs args) { // Pre-conditions if (!Enabled) { // QUESTION: Is this right? Should a disabled view eat mouse? return args.Handled = false; } // Cancellable event if (OnMouseWheel (args) || args.Handled) { return args.Handled; } MouseWheel?.Invoke (this, args); if (args.Handled) { return true; } args.Handled = InvokeCommandsBoundToMouse (args) == true; return args.Handled; } /// /// Called when a mouse wheel event occurs. Check to see which wheel was moved was /// clicked. /// /// /// /// /// , if the event was handled, otherwise. protected virtual bool OnMouseWheel (MouseEventArgs args) { return false; } /// Raised when a mouse wheel event occurs. /// /// public event EventHandler? MouseWheel; #endregion Mouse Wheel Events #region Highlight Handling /// /// Gets or sets whether the will be highlighted visually by mouse interaction. /// public HighlightStyle HighlightStyle { get; set; } /// /// INTERNAL Raises the event. Returns if the event was handled, /// otherwise. /// /// /// private bool RaiseHighlight (CancelEventArgs args) { if (OnHighlight (args)) { return true; } Highlight?.Invoke (this, args); return args.Cancel; } /// /// Called when the view is to be highlighted. The passed in the event indicates the /// highlight style that will be applied. The view can modify the highlight style by setting the /// property. /// /// /// Set the property to , to cancel, indicating custom /// highlighting. /// /// , to cancel, indicating custom highlighting. protected virtual bool OnHighlight (CancelEventArgs args) { return false; } /// /// Raised when the view is to be highlighted. The passed in the event indicates the /// highlight style that will be applied. The view can modify the highlight style by setting the /// property. /// Set to , to cancel, indicating custom highlighting. /// public event EventHandler>? Highlight; /// /// INTERNAL Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent. /// /// /// /// Set to and/or /// to enable. /// /// /// Calls and raises the event. /// /// /// Marked internal just to support unit tests /// /// /// , if the Highlight event was handled, otherwise. internal bool SetPressedHighlight (HighlightStyle newHighlightStyle) { // TODO: Make the highlight colors configurable if (!CanFocus) { return false; } HighlightStyle copy = HighlightStyle; CancelEventArgs args = new (ref copy, ref newHighlightStyle); if (RaiseHighlight (args) || args.Cancel) { return true; } // For 3D Pressed Style - Note we don't care about canceling the event here Margin?.RaiseHighlight (args); return args.Cancel; } #endregion Highlight Handling private void DisposeMouse () { } }