using System.ComponentModel; namespace Terminal.Gui; /// /// Describes the highlight style of a view. /// [Flags] public enum HighlightStyle { /// /// No highlight. /// None = 0, #if HOVER /// /// The mouse is hovering over the view. /// Hover = 1, #endif /// /// The mouse is pressed within the . /// Pressed = 2, /// /// The mouse is pressed but moved outside the . /// PressedOutside = 4 } /// /// Event arguments for the event. /// public class HighlightEventArgs : CancelEventArgs { /// /// Constructs a new instance of . /// /// public HighlightEventArgs (HighlightStyle style) { HighlightStyle = style; } /// /// The highlight style. /// public HighlightStyle HighlightStyle { get; } } public partial class View { /// /// Gets or sets whether the will be highlighted visually while the mouse button is /// pressed. /// public HighlightStyle HighlightStyle { get; set; } /// 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 virtual bool WantMousePositionReports { get; set; } /// /// Called by when the mouse enters . The view will /// then receive mouse events until is called indicating the mouse has left /// the view. /// /// /// /// A view must be both enabled and visible to receive mouse events. /// /// /// This method calls to fire the event. /// /// /// See for more information. /// /// /// /// if the event was handled, otherwise. internal bool? NewMouseEnterEvent (MouseEvent mouseEvent) { if (!Enabled) { return true; } if (!CanBeVisible (this)) { return false; } if (OnMouseEnter (mouseEvent) == true) { return true; } #if HOVER if (HighlightStyle.HasFlag(HighlightStyle.Hover)) { if (SetHighlight (HighlightStyle.Hover)) { return true; } } #endif return false; } /// /// Called by when the mouse enters . The view will /// then receive mouse events until is called indicating the mouse has left /// the view. /// /// /// /// Override this method or subscribe to to change the default enter behavior. /// /// /// The coordinates are relative to . /// /// /// /// , if the event was handled, otherwise. protected internal virtual bool? OnMouseEnter (MouseEvent mouseEvent) { var args = new MouseEventEventArgs (mouseEvent); MouseEnter?.Invoke (this, args); return args.Handled; } /// Event fired when the mouse moves into the View's . public event EventHandler MouseEnter; /// /// Called by when the mouse leaves . The view will /// then no longer receive mouse events. /// /// /// /// A view must be both enabled and visible to receive mouse events. /// /// /// This method calls to fire the event. /// /// /// See for more information. /// /// /// /// if the event was handled, otherwise. internal bool? NewMouseLeaveEvent (MouseEvent mouseEvent) { if (!Enabled) { return true; } if (!CanBeVisible (this)) { return false; } if (OnMouseLeave (mouseEvent) == true) { return true; } #if HOVER if (HighlightStyle.HasFlag (HighlightStyle.Hover)) { SetHighlight (HighlightStyle.None); } #endif return false; } /// /// Called by when a mouse leaves . The view will /// no longer receive mouse events. /// /// /// /// Override this method or subscribe to to change the default leave behavior. /// /// /// The coordinates are relative to . /// /// /// /// , if the event was handled, otherwise. protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) { if (!Enabled) { return true; } if (!CanBeVisible (this)) { return false; } var args = new MouseEventEventArgs (mouseEvent); MouseLeave?.Invoke (this, args); return args.Handled; } /// Event fired when the mouse leaves the View's . public event EventHandler MouseLeave; /// /// Processes a . This method is called by when a mouse /// event occurs. /// /// /// /// A view must be both enabled and visible to receive mouse events. /// /// /// This method calls to process the event. If the event is not handled, and one of the /// mouse buttons was clicked, it calls to process the click. /// /// /// See for more information. /// /// /// If is , the event /// will be invoked repeatedly while the button is pressed. /// /// /// /// if the event was handled, otherwise. public bool? NewMouseEvent (MouseEvent mouseEvent) { if (!Enabled) { // A disabled view should not eat mouse events return false; } if (!CanBeVisible (this)) { return false; } if (OnMouseEvent (mouseEvent)) { // Technically mouseEvent.Handled should already be true if implementers of OnMouseEvent // follow the rules. But we'll update it just in case. return mouseEvent.Handled = true; } if (HighlightStyle != Gui.HighlightStyle.None || WantContinuousButtonPressed) { if (HandlePressed (mouseEvent)) { return mouseEvent.Handled; } if (HandleReleased (mouseEvent)) { return mouseEvent.Handled; } if (HandleClicked (mouseEvent)) { return mouseEvent.Handled; } } if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button2DoubleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button3DoubleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button4DoubleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button1TripleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button2TripleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button3TripleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button4TripleClicked) ) { // If it's a click, and we didn't handle it, then we'll call OnMouseClick // 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 return OnMouseClick (new (mouseEvent)); } return false; } /// /// 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 HandlePressed (MouseEvent mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed) || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed) || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed)) { // The first time we get pressed event, grab the mouse and set focus if (Application.MouseGrabView != this) { Application.GrabMouse (this); if (CanFocus) { // Set the focus, but don't invoke Accept SetFocus (); } mouseEvent.Handled = true; } if (Viewport.Contains (mouseEvent.Position)) { if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None) == true) { return true; } } else { if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None) == true) { return true; } } if (WantContinuousButtonPressed && Application.MouseGrabView == this) { // If this is not the first pressed event, click return OnMouseClick (new (mouseEvent)); } return mouseEvent.Handled = true; } return false; } /// /// 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 HandleReleased (MouseEvent mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) || mouseEvent.Flags.HasFlag (MouseFlags.Button2Released) || mouseEvent.Flags.HasFlag (MouseFlags.Button3Released) || mouseEvent.Flags.HasFlag (MouseFlags.Button4Released)) { if (Application.MouseGrabView == this) { SetHighlight (HighlightStyle.None); } return mouseEvent.Handled = true; } return false; } /// /// 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 HandleClicked (MouseEvent mouseEvent) { if (Application.MouseGrabView == this && (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked))) { // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab Application.UngrabMouse (); if (SetHighlight (HighlightStyle.None)) { return true; } // If mouse is still in bounds, click if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.Position)) { return OnMouseClick (new (mouseEvent)); } return mouseEvent.Handled = true; } return false; } [CanBeNull] private ColorScheme _savedHighlightColorScheme; /// /// Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent. /// /// /// /// Set to have the view highlighted based on the mouse. /// /// /// Calls which fires the event. /// /// /// Marked internal just to support unit tests /// /// /// , if the Highlight event was handled, otherwise. internal bool SetHighlight (HighlightStyle style) { // TODO: Make the highlight colors configurable // Enable override via virtual method and/or event if (OnHighlight (style) == true) { return true; } #if HOVER if (style.HasFlag (HighlightStyle.Hover)) { if (_savedHighlightColorScheme is null && ColorScheme is { }) { _savedHighlightColorScheme ??= ColorScheme; var cs = new ColorScheme (ColorScheme) { Normal = GetFocusColor (), HotNormal = ColorScheme.HotFocus }; ColorScheme = cs; } return true; } #endif if (style.HasFlag (HighlightStyle.Pressed) || style.HasFlag (HighlightStyle.PressedOutside)) { if (_savedHighlightColorScheme is null && ColorScheme is { }) { _savedHighlightColorScheme ??= ColorScheme; if (CanFocus) { var cs = new ColorScheme (ColorScheme) { // Highlight the foreground focus color Focus = new (ColorScheme.Focus.Foreground.GetHighlightColor (), ColorScheme.Focus.Background.GetHighlightColor ()), }; ColorScheme = cs; } else { var cs = new ColorScheme (ColorScheme) { // Invert Focus color foreground/background. We can do this because we know the view is not going to be focused. Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground) }; ColorScheme = cs; } } // Return false since we don't want to eat the event return false; } if (style == HighlightStyle.None) { // Unhighlight if (_savedHighlightColorScheme is { }) { ColorScheme = _savedHighlightColorScheme; _savedHighlightColorScheme = null; } } return false; } /// /// Fired when the view is highlighted. Set to /// to implement a custom highlight scheme or prevent the view from being highlighted. /// public event EventHandler Highlight; /// /// Called when the view is to be highlighted. /// /// , if the event was handled, otherwise. protected virtual bool? OnHighlight (HighlightStyle highlight) { HighlightEventArgs args = new (highlight); Highlight?.Invoke (this, args); return args.Cancel; } /// Called when a mouse event occurs within the view's . /// /// /// The coordinates are relative to . /// /// /// /// , if the event was handled, otherwise. protected internal virtual bool OnMouseEvent (MouseEvent mouseEvent) { var args = new MouseEventEventArgs (mouseEvent); MouseEvent?.Invoke (this, args); return args.Handled; } /// Event fired when a mouse event occurs. /// /// /// The coordinates are relative to . /// /// public event EventHandler MouseEvent; /// Invokes the MouseClick event. /// /// /// Called when the mouse is either clicked or double-clicked. Check /// to see which button was clicked. /// /// /// , if the event was handled, otherwise. protected bool OnMouseClick (MouseEventEventArgs args) { if (!Enabled) { // QUESTION: Is this right? Should a disabled view eat mouse clicks? return args.Handled = true; } MouseClick?.Invoke (this, args); if (args.Handled) { return true; } if (!HasFocus && CanFocus) { args.Handled = true; SetFocus (); } return args.Handled; } /// Event fired when a mouse click occurs. /// /// /// Fired when the mouse is either clicked or double-clicked. Check /// to see which button was clicked. /// /// /// The coordinates are relative to . /// /// public event EventHandler MouseClick; }