diff --git a/Terminal.Gui/Application.MainLoopSyncContext.cs b/Terminal.Gui/Application.MainLoopSyncContext.cs deleted file mode 100644 index 513608e8c..000000000 --- a/Terminal.Gui/Application.MainLoopSyncContext.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Terminal.Gui; - -public static partial class Application -{ - /// - /// provides the sync context set while executing code in Terminal.Gui, to let - /// users use async/await on their code - /// - private sealed class MainLoopSyncContext : SynchronizationContext - { - public override SynchronizationContext CreateCopy () { return new MainLoopSyncContext (); } - - public override void Post (SendOrPostCallback d, object state) - { - MainLoop?.AddIdle ( - () => - { - d (state); - - return false; - } - ); - } - - //_mainLoop.Driver.Wakeup (); - public override void Send (SendOrPostCallback d, object state) - { - if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) - { - d (state); - } - else - { - var wasExecuted = false; - - Invoke ( - () => - { - d (state); - wasExecuted = true; - } - ); - - while (!wasExecuted) - { - Thread.Sleep (15); - } - } - } - } -} diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application/Application.cs similarity index 72% rename from Terminal.Gui/Application.cs rename to Terminal.Gui/Application/Application.cs index 8e414969c..37358db72 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -1426,602 +1426,3 @@ public static partial class Application #endregion Toplevel handling - #region Mouse handling - - /// Disable or enable the mouse. The mouse is enabled by default. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - public static bool IsMouseDisabled { get; set; } - - /// The current object that wants continuous mouse button pressed events. - public static View WantContinuousButtonPressedView { get; private set; } - - /// - /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to - /// this view until the view calls or the mouse is released. - /// - public static View MouseGrabView { get; private set; } - - /// Invoked when a view wants to grab the mouse; can be canceled. - public static event EventHandler GrabbingMouse; - - /// Invoked when a view wants un-grab the mouse; can be canceled. - public static event EventHandler UnGrabbingMouse; - - /// Invoked after a view has grabbed the mouse. - public static event EventHandler GrabbedMouse; - - /// Invoked after a view has un-grabbed the mouse. - public static event EventHandler UnGrabbedMouse; - - /// - /// Grabs the mouse, forcing all mouse events to be routed to the specified view until - /// is called. - /// - /// View that will receive all mouse events until is invoked. - public static void GrabMouse (View view) - { - if (view is null) - { - return; - } - - if (!OnGrabbingMouse (view)) - { - OnGrabbedMouse (view); - MouseGrabView = view; - } - } - - /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. - public static void UngrabMouse () - { - if (MouseGrabView is null) - { - return; - } - - if (!OnUnGrabbingMouse (MouseGrabView)) - { - View view = MouseGrabView; - MouseGrabView = null; - OnUnGrabbedMouse (view); - } - } - - private static bool OnGrabbingMouse (View view) - { - if (view is null) - { - return false; - } - - var evArgs = new GrabMouseEventArgs (view); - GrabbingMouse?.Invoke (view, evArgs); - - return evArgs.Cancel; - } - - private static bool OnUnGrabbingMouse (View view) - { - if (view is null) - { - return false; - } - - var evArgs = new GrabMouseEventArgs (view); - UnGrabbingMouse?.Invoke (view, evArgs); - - return evArgs.Cancel; - } - - private static void OnGrabbedMouse (View view) - { - if (view is null) - { - return; - } - - GrabbedMouse?.Invoke (view, new (view)); - } - - private static void OnUnGrabbedMouse (View view) - { - if (view is null) - { - return; - } - - UnGrabbedMouse?.Invoke (view, new (view)); - } - -#nullable enable - - // Used by OnMouseEvent to track the last view that was clicked on. - internal static View? _mouseEnteredView; - - /// Event fired when a mouse move or click occurs. Coordinates are screen relative. - /// - /// - /// Use this event to receive mouse events in screen coordinates. Use to - /// receive mouse events relative to a . - /// - /// The will contain the that contains the mouse coordinates. - /// - public static event EventHandler? MouseEvent; - - /// Called when a mouse event occurs. Raises the event. - /// This method can be used to simulate a mouse event, e.g. in unit tests. - /// The mouse event with coordinates relative to the screen. - internal static void OnMouseEvent (MouseEvent mouseEvent) - { - if (IsMouseDisabled) - { - return; - } - - var view = View.FindDeepestView (Current, mouseEvent.Position); - - if (view is { }) - { - mouseEvent.View = view; - } - - MouseEvent?.Invoke (null, mouseEvent); - - if (mouseEvent.Handled) - { - return; - } - - if (MouseGrabView is { }) - { - // If the mouse is grabbed, send the event to the view that grabbed it. - // The coordinates are relative to the Bounds of the view that grabbed the mouse. - Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.Position); - - var viewRelativeMouseEvent = new MouseEvent - { - Position = frameLoc, - Flags = mouseEvent.Flags, - ScreenPosition = mouseEvent.Position, - View = MouseGrabView - }; - - if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false) - { - // The mouse has moved outside the bounds of the view that grabbed the mouse - _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent); - } - - //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); - if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true) - { - return; - } - } - - if (view is { WantContinuousButtonPressed: true }) - { - WantContinuousButtonPressedView = view; - } - else - { - WantContinuousButtonPressedView = null; - } - - if (view is not Adornment) - { - if ((view is null || view == OverlappedTop) - && Current is { Modal: false } - && OverlappedTop != null - && mouseEvent.Flags != MouseFlags.ReportMousePosition - && mouseEvent.Flags != 0) - { - // This occurs when there are multiple overlapped "tops" - // E.g. "Mdi" - in the Background Worker Scenario - View? top = FindDeepestTop (Top, mouseEvent.Position); - view = View.FindDeepestView (top, mouseEvent.Position); - - if (view is { } && view != OverlappedTop && top != Current && top is { }) - { - MoveCurrent ((Toplevel)top); - } - } - } - - if (view is null) - { - return; - } - - MouseEvent? me = null; - - if (view is Adornment adornment) - { - Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position); - - me = new () - { - Position = frameLoc, - Flags = mouseEvent.Flags, - ScreenPosition = mouseEvent.Position, - View = view - }; - } - else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position)) - { - Point viewportLocation = view.ScreenToViewport (mouseEvent.Position); - - me = new () - { - Position = viewportLocation, - Flags = mouseEvent.Flags, - ScreenPosition = mouseEvent.Position, - View = view - }; - } - - if (me is null) - { - return; - } - - if (_mouseEnteredView is null) - { - _mouseEnteredView = view; - view.NewMouseEnterEvent (me); - } - else if (_mouseEnteredView != view) - { - _mouseEnteredView.NewMouseLeaveEvent (me); - view.NewMouseEnterEvent (me); - _mouseEnteredView = view; - } - - if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition) - { - return; - } - - WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null; - - //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}"); - - while (view.NewMouseEvent (me) != true) - { - if (MouseGrabView is { }) - { - break; - } - - if (view is Adornment adornmentView) - { - view = adornmentView.Parent.SuperView; - } - else - { - view = view.SuperView; - } - - if (view is null) - { - break; - } - - Point boundsPoint = view.ScreenToViewport (mouseEvent.Position); - - me = new () - { - Position = boundsPoint, - Flags = mouseEvent.Flags, - ScreenPosition = mouseEvent.Position, - View = view - }; - } - - BringOverlappedTopToFront (); - } -#nullable restore - - #endregion Mouse handling - - #region Keyboard handling - - private static Key _alternateForwardKey = Key.Empty; // Defined in config.json - - /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] - public static Key AlternateForwardKey - { - get => _alternateForwardKey; - set - { - if (_alternateForwardKey != value) - { - Key oldKey = _alternateForwardKey; - _alternateForwardKey = value; - OnAlternateForwardKeyChanged (new (oldKey, value)); - } - } - } - - private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) - { - foreach (Toplevel top in _topLevels.ToArray ()) - { - top.OnAlternateForwardKeyChanged (e); - } - } - - private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json - - /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] - public static Key AlternateBackwardKey - { - get => _alternateBackwardKey; - set - { - if (_alternateBackwardKey != value) - { - Key oldKey = _alternateBackwardKey; - _alternateBackwardKey = value; - OnAlternateBackwardKeyChanged (new (oldKey, value)); - } - } - } - - private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) - { - foreach (Toplevel top in _topLevels.ToArray ()) - { - top.OnAlternateBackwardKeyChanged (oldKey); - } - } - - private static Key _quitKey = Key.Empty; // Defined in config.json - - /// Gets or sets the key to quit the application. - [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] - public static Key QuitKey - { - get => _quitKey; - set - { - if (_quitKey != value) - { - Key oldKey = _quitKey; - _quitKey = value; - OnQuitKeyChanged (new (oldKey, value)); - } - } - } - - private static void OnQuitKeyChanged (KeyChangedEventArgs e) - { - // Duplicate the list so if it changes during enumeration we're safe - foreach (Toplevel top in _topLevels.ToArray ()) - { - top.OnQuitKeyChanged (e); - } - } - - /// - /// Event fired when the user presses a key. Fired by . - /// - /// Set to to indicate the key was handled and to prevent - /// additional processing. - /// - /// - /// - /// All drivers support firing the event. Some drivers (Curses) do not support firing the - /// and events. - /// Fired after and before . - /// - public static event EventHandler KeyDown; - - /// - /// Called by the when the user presses a key. Fires the event - /// then calls on all top level views. Called after and - /// before . - /// - /// Can be used to simulate key press events. - /// - /// if the key was handled. - public static bool OnKeyDown (Key keyEvent) - { - if (!_initialized) - { - return true; - } - - KeyDown?.Invoke (null, keyEvent); - - if (keyEvent.Handled) - { - return true; - } - - foreach (Toplevel topLevel in _topLevels.ToList ()) - { - if (topLevel.NewKeyDownEvent (keyEvent)) - { - return true; - } - - if (topLevel.Modal) - { - break; - } - } - - // Invoke any global (Application-scoped) KeyBindings. - // The first view that handles the key will stop the loop. - foreach (KeyValuePair> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode)) - { - foreach (View view in binding.Value) - { - bool? handled = view?.OnInvokingKeyBindings (keyEvent); - - if (handled != null && (bool)handled) - { - return true; - } - } - } - - return false; - } - - /// - /// Event fired when the user releases a key. Fired by . - /// - /// Set to to indicate the key was handled and to prevent - /// additional processing. - /// - /// - /// - /// All drivers support firing the event. Some drivers (Curses) do not support firing the - /// and events. - /// Fired after . - /// - public static event EventHandler KeyUp; - - /// - /// Called by the when the user releases a key. Fires the event - /// then calls on all top level views. Called after . - /// - /// Can be used to simulate key press events. - /// - /// if the key was handled. - public static bool OnKeyUp (Key a) - { - if (!_initialized) - { - return true; - } - - KeyUp?.Invoke (null, a); - - if (a.Handled) - { - return true; - } - - foreach (Toplevel topLevel in _topLevels.ToList ()) - { - if (topLevel.NewKeyUpEvent (a)) - { - return true; - } - - if (topLevel.Modal) - { - break; - } - } - - return false; - } - - /// - /// The key bindings. - /// - private static readonly Dictionary> _keyBindings = new (); - - /// - /// Adds an scoped key binding. - /// - /// - /// This is an internal method used by the class to add Application key bindings. - /// - /// The key being bound. - /// The view that is bound to the key. - internal static void AddKeyBinding (Key key, View view) - { - if (!_keyBindings.ContainsKey (key)) - { - _keyBindings [key] = []; - } - _keyBindings [key].Add (view); - } - - /// - /// Gets the list of Views that have key bindings. - /// - /// - /// This is an internal method used by the class to add Application key bindings. - /// - /// The list of Views that have Application-scoped key bindings. - internal static List GetViewsWithKeyBindings () - { - return _keyBindings.Values.SelectMany (v => v).ToList (); - } - - /// - /// Gets the list of Views that have key bindings for the specified key. - /// - /// - /// This is an internal method used by the class to add Application key bindings. - /// - /// The key to check. - /// Outputs the list of views bound to - /// if successful. - internal static bool TryGetKeyBindings (Key key, out List views) - { - return _keyBindings.TryGetValue (key, out views); - } - - /// - /// Removes an scoped key binding. - /// - /// - /// This is an internal method used by the class to remove Application key bindings. - /// - /// The key that was bound. - /// The view that is bound to the key. - internal static void RemoveKeyBinding (Key key, View view) - { - if (_keyBindings.TryGetValue (key, out List views)) - { - views.Remove (view); - - if (views.Count == 0) - { - _keyBindings.Remove (key); - } - } - } - - /// - /// Removes all scoped key bindings for the specified view. - /// - /// - /// This is an internal method used by the class to remove Application key bindings. - /// - /// The view that is bound to the key. - internal static void ClearKeyBindings (View view) - { - foreach (Key key in _keyBindings.Keys) - { - _keyBindings [key].Remove (view); - } - } - - /// - /// Removes all scoped key bindings for the specified view. - /// - /// - /// This is an internal method used by the class to remove Application key bindings. - /// - /// The view that is bound to the key. - internal static void ClearKeyBindings () - { - _keyBindings.Clear (); - } - - #endregion Keyboard handling -} diff --git a/Terminal.Gui/Application/ApplicationKeyboard.cs b/Terminal.Gui/Application/ApplicationKeyboard.cs new file mode 100644 index 000000000..167e1dfb0 --- /dev/null +++ b/Terminal.Gui/Application/ApplicationKeyboard.cs @@ -0,0 +1,294 @@ +using System.Text.Json.Serialization; + +namespace Terminal.Gui; + +partial class Application +{ + private static Key _alternateForwardKey = Key.Empty; // Defined in config.json + + /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + [JsonConverter (typeof (KeyJsonConverter))] + public static Key AlternateForwardKey + { + get => _alternateForwardKey; + set + { + if (_alternateForwardKey != value) + { + Key oldKey = _alternateForwardKey; + _alternateForwardKey = value; + OnAlternateForwardKeyChanged (new (oldKey, value)); + } + } + } + + private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) + { + foreach (Toplevel top in _topLevels.ToArray ()) + { + top.OnAlternateForwardKeyChanged (e); + } + } + + private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json + + /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + [JsonConverter (typeof (KeyJsonConverter))] + public static Key AlternateBackwardKey + { + get => _alternateBackwardKey; + set + { + if (_alternateBackwardKey != value) + { + Key oldKey = _alternateBackwardKey; + _alternateBackwardKey = value; + OnAlternateBackwardKeyChanged (new (oldKey, value)); + } + } + } + + private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) + { + foreach (Toplevel top in _topLevels.ToArray ()) + { + top.OnAlternateBackwardKeyChanged (oldKey); + } + } + + private static Key _quitKey = Key.Empty; // Defined in config.json + + /// Gets or sets the key to quit the application. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + [JsonConverter (typeof (KeyJsonConverter))] + public static Key QuitKey + { + get => _quitKey; + set + { + if (_quitKey != value) + { + Key oldKey = _quitKey; + _quitKey = value; + OnQuitKeyChanged (new (oldKey, value)); + } + } + } + + private static void OnQuitKeyChanged (KeyChangedEventArgs e) + { + // Duplicate the list so if it changes during enumeration we're safe + foreach (Toplevel top in _topLevels.ToArray ()) + { + top.OnQuitKeyChanged (e); + } + } + + /// + /// Event fired when the user presses a key. Fired by . + /// + /// Set to to indicate the key was handled and to prevent + /// additional processing. + /// + /// + /// + /// All drivers support firing the event. Some drivers (Curses) do not support firing the + /// and events. + /// Fired after and before . + /// + public static event EventHandler KeyDown; + + /// + /// Called by the when the user presses a key. Fires the event + /// then calls on all top level views. Called after and + /// before . + /// + /// Can be used to simulate key press events. + /// + /// if the key was handled. + public static bool OnKeyDown (Key keyEvent) + { + if (!_initialized) + { + return true; + } + + KeyDown?.Invoke (null, keyEvent); + + if (keyEvent.Handled) + { + return true; + } + + foreach (Toplevel topLevel in _topLevels.ToList ()) + { + if (topLevel.NewKeyDownEvent (keyEvent)) + { + return true; + } + + if (topLevel.Modal) + { + break; + } + } + + // Invoke any global (Application-scoped) KeyBindings. + // The first view that handles the key will stop the loop. + foreach (KeyValuePair> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode)) + { + foreach (View view in binding.Value) + { + bool? handled = view?.OnInvokingKeyBindings (keyEvent); + + if (handled != null && (bool)handled) + { + return true; + } + } + } + + return false; + } + + /// + /// Event fired when the user releases a key. Fired by . + /// + /// Set to to indicate the key was handled and to prevent + /// additional processing. + /// + /// + /// + /// All drivers support firing the event. Some drivers (Curses) do not support firing the + /// and events. + /// Fired after . + /// + public static event EventHandler KeyUp; + + /// + /// Called by the when the user releases a key. Fires the event + /// then calls on all top level views. Called after . + /// + /// Can be used to simulate key press events. + /// + /// if the key was handled. + public static bool OnKeyUp (Key a) + { + if (!_initialized) + { + return true; + } + + KeyUp?.Invoke (null, a); + + if (a.Handled) + { + return true; + } + + foreach (Toplevel topLevel in _topLevels.ToList ()) + { + if (topLevel.NewKeyUpEvent (a)) + { + return true; + } + + if (topLevel.Modal) + { + break; + } + } + + return false; + } + + /// + /// The key bindings. + /// + private static readonly Dictionary> _keyBindings = new (); + + /// + /// Adds an scoped key binding. + /// + /// + /// This is an internal method used by the class to add Application key bindings. + /// + /// The key being bound. + /// The view that is bound to the key. + internal static void AddKeyBinding (Key key, View view) + { + if (!_keyBindings.ContainsKey (key)) + { + _keyBindings [key] = []; + } + + _keyBindings [key].Add (view); + } + + /// + /// Gets the list of Views that have key bindings. + /// + /// + /// This is an internal method used by the class to add Application key bindings. + /// + /// The list of Views that have Application-scoped key bindings. + internal static List GetViewsWithKeyBindings () { return _keyBindings.Values.SelectMany (v => v).ToList (); } + + /// + /// Gets the list of Views that have key bindings for the specified key. + /// + /// + /// This is an internal method used by the class to add Application key bindings. + /// + /// The key to check. + /// Outputs the list of views bound to + /// if successful. + internal static bool TryGetKeyBindings (Key key, out List views) { return _keyBindings.TryGetValue (key, out views); } + + /// + /// Removes an scoped key binding. + /// + /// + /// This is an internal method used by the class to remove Application key bindings. + /// + /// The key that was bound. + /// The view that is bound to the key. + internal static void RemoveKeyBinding (Key key, View view) + { + if (_keyBindings.TryGetValue (key, out List views)) + { + views.Remove (view); + + if (views.Count == 0) + { + _keyBindings.Remove (key); + } + } + } + + /// + /// Removes all scoped key bindings for the specified view. + /// + /// + /// This is an internal method used by the class to remove Application key bindings. + /// + /// The view that is bound to the key. + internal static void ClearKeyBindings (View view) + { + foreach (Key key in _keyBindings.Keys) + { + _keyBindings [key].Remove (view); + } + } + + /// + /// Removes all scoped key bindings for the specified view. + /// + /// + /// This is an internal method used by the class to remove Application key bindings. + /// + /// The view that is bound to the key. + internal static void ClearKeyBindings () { _keyBindings.Clear (); } +} diff --git a/Terminal.Gui/Application/ApplicationMouse.cs b/Terminal.Gui/Application/ApplicationMouse.cs new file mode 100644 index 000000000..9f2a95339 --- /dev/null +++ b/Terminal.Gui/Application/ApplicationMouse.cs @@ -0,0 +1,302 @@ +namespace Terminal.Gui; + +partial class Application +{ + #region Mouse handling + + /// Disable or enable the mouse. The mouse is enabled by default. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static bool IsMouseDisabled { get; set; } + + /// The current object that wants continuous mouse button pressed events. + public static View WantContinuousButtonPressedView { get; private set; } + + /// + /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to + /// this view until the view calls or the mouse is released. + /// + public static View MouseGrabView { get; private set; } + + /// Invoked when a view wants to grab the mouse; can be canceled. + public static event EventHandler GrabbingMouse; + + /// Invoked when a view wants un-grab the mouse; can be canceled. + public static event EventHandler UnGrabbingMouse; + + /// Invoked after a view has grabbed the mouse. + public static event EventHandler GrabbedMouse; + + /// Invoked after a view has un-grabbed the mouse. + public static event EventHandler UnGrabbedMouse; + + /// + /// Grabs the mouse, forcing all mouse events to be routed to the specified view until + /// is called. + /// + /// View that will receive all mouse events until is invoked. + public static void GrabMouse (View view) + { + if (view is null) + { + return; + } + + if (!OnGrabbingMouse (view)) + { + OnGrabbedMouse (view); + MouseGrabView = view; + } + } + + /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. + public static void UngrabMouse () + { + if (MouseGrabView is null) + { + return; + } + + if (!OnUnGrabbingMouse (MouseGrabView)) + { + View view = MouseGrabView; + MouseGrabView = null; + OnUnGrabbedMouse (view); + } + } + + private static bool OnGrabbingMouse (View view) + { + if (view is null) + { + return false; + } + + var evArgs = new GrabMouseEventArgs (view); + GrabbingMouse?.Invoke (view, evArgs); + + return evArgs.Cancel; + } + + private static bool OnUnGrabbingMouse (View view) + { + if (view is null) + { + return false; + } + + var evArgs = new GrabMouseEventArgs (view); + UnGrabbingMouse?.Invoke (view, evArgs); + + return evArgs.Cancel; + } + + private static void OnGrabbedMouse (View view) + { + if (view is null) + { + return; + } + + GrabbedMouse?.Invoke (view, new (view)); + } + + private static void OnUnGrabbedMouse (View view) + { + if (view is null) + { + return; + } + + UnGrabbedMouse?.Invoke (view, new (view)); + } + +#nullable enable + + // Used by OnMouseEvent to track the last view that was clicked on. + internal static View? _mouseEnteredView; + + /// Event fired when a mouse move or click occurs. Coordinates are screen relative. + /// + /// + /// Use this event to receive mouse events in screen coordinates. Use to + /// receive mouse events relative to a . + /// + /// The will contain the that contains the mouse coordinates. + /// + public static event EventHandler? MouseEvent; + + /// Called when a mouse event occurs. Raises the event. + /// This method can be used to simulate a mouse event, e.g. in unit tests. + /// The mouse event with coordinates relative to the screen. + internal static void OnMouseEvent (MouseEvent mouseEvent) + { + if (IsMouseDisabled) + { + return; + } + + var view = View.FindDeepestView (Current, mouseEvent.Position); + + if (view is { }) + { + mouseEvent.View = view; + } + + MouseEvent?.Invoke (null, mouseEvent); + + if (mouseEvent.Handled) + { + return; + } + + if (MouseGrabView is { }) + { + // If the mouse is grabbed, send the event to the view that grabbed it. + // The coordinates are relative to the Bounds of the view that grabbed the mouse. + Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.Position); + + var viewRelativeMouseEvent = new MouseEvent + { + Position = frameLoc, + Flags = mouseEvent.Flags, + ScreenPosition = mouseEvent.Position, + View = MouseGrabView + }; + + if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false) + { + // The mouse has moved outside the bounds of the view that grabbed the mouse + _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent); + } + + //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); + if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true) + { + return; + } + } + + if (view is { WantContinuousButtonPressed: true }) + { + WantContinuousButtonPressedView = view; + } + else + { + WantContinuousButtonPressedView = null; + } + + if (view is not Adornment) + { + if ((view is null || view == OverlappedTop) + && Current is { Modal: false } + && OverlappedTop != null + && mouseEvent.Flags != MouseFlags.ReportMousePosition + && mouseEvent.Flags != 0) + { + // This occurs when there are multiple overlapped "tops" + // E.g. "Mdi" - in the Background Worker Scenario + View? top = FindDeepestTop (Top, mouseEvent.Position); + view = View.FindDeepestView (top, mouseEvent.Position); + + if (view is { } && view != OverlappedTop && top != Current && top is { }) + { + MoveCurrent ((Toplevel)top); + } + } + } + + if (view is null) + { + return; + } + + MouseEvent? me = null; + + if (view is Adornment adornment) + { + Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position); + + me = new () + { + Position = frameLoc, + Flags = mouseEvent.Flags, + ScreenPosition = mouseEvent.Position, + View = view + }; + } + else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position)) + { + Point viewportLocation = view.ScreenToViewport (mouseEvent.Position); + + me = new () + { + Position = viewportLocation, + Flags = mouseEvent.Flags, + ScreenPosition = mouseEvent.Position, + View = view + }; + } + + if (me is null) + { + return; + } + + if (_mouseEnteredView is null) + { + _mouseEnteredView = view; + view.NewMouseEnterEvent (me); + } + else if (_mouseEnteredView != view) + { + _mouseEnteredView.NewMouseLeaveEvent (me); + view.NewMouseEnterEvent (me); + _mouseEnteredView = view; + } + + if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition) + { + return; + } + + WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null; + + //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}"); + + while (view.NewMouseEvent (me) != true) + { + if (MouseGrabView is { }) + { + break; + } + + if (view is Adornment adornmentView) + { + view = adornmentView.Parent.SuperView; + } + else + { + view = view.SuperView; + } + + if (view is null) + { + break; + } + + Point boundsPoint = view.ScreenToViewport (mouseEvent.Position); + + me = new () + { + Position = boundsPoint, + Flags = mouseEvent.Flags, + ScreenPosition = mouseEvent.Position, + View = view + }; + } + + BringOverlappedTopToFront (); + } + + #endregion Mouse handling +} diff --git a/Terminal.Gui/IterationEventArgs.cs b/Terminal.Gui/Application/IterationEventArgs.cs similarity index 100% rename from Terminal.Gui/IterationEventArgs.cs rename to Terminal.Gui/Application/IterationEventArgs.cs diff --git a/Terminal.Gui/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs similarity index 100% rename from Terminal.Gui/MainLoop.cs rename to Terminal.Gui/Application/MainLoop.cs diff --git a/Terminal.Gui/Application/MainLoopSyncContext.cs b/Terminal.Gui/Application/MainLoopSyncContext.cs new file mode 100644 index 000000000..5290a2076 --- /dev/null +++ b/Terminal.Gui/Application/MainLoopSyncContext.cs @@ -0,0 +1,48 @@ +namespace Terminal.Gui; + +/// +/// provides the sync context set while executing code in Terminal.Gui, to let +/// users use async/await on their code +/// +internal sealed class MainLoopSyncContext : SynchronizationContext +{ + public override SynchronizationContext CreateCopy () { return new MainLoopSyncContext (); } + + public override void Post (SendOrPostCallback d, object state) + { + Application.MainLoop?.AddIdle ( + () => + { + d (state); + + return false; + } + ); + } + + //_mainLoop.Driver.Wakeup (); + public override void Send (SendOrPostCallback d, object state) + { + if (Thread.CurrentThread.ManagedThreadId == Application._mainThreadId) + { + d (state); + } + else + { + var wasExecuted = false; + + Application.Invoke ( + () => + { + d (state); + wasExecuted = true; + } + ); + + while (!wasExecuted) + { + Thread.Sleep (15); + } + } + } +} diff --git a/Terminal.Gui/RunState.cs b/Terminal.Gui/Application/RunState.cs similarity index 100% rename from Terminal.Gui/RunState.cs rename to Terminal.Gui/Application/RunState.cs diff --git a/Terminal.Gui/RunStateEventArgs.cs b/Terminal.Gui/Application/RunStateEventArgs.cs similarity index 100% rename from Terminal.Gui/RunStateEventArgs.cs rename to Terminal.Gui/Application/RunStateEventArgs.cs diff --git a/Terminal.Gui/Timeout.cs b/Terminal.Gui/Application/Timeout.cs similarity index 100% rename from Terminal.Gui/Timeout.cs rename to Terminal.Gui/Application/Timeout.cs diff --git a/Terminal.Gui/TimeoutEventArgs.cs b/Terminal.Gui/Application/TimeoutEventArgs.cs similarity index 100% rename from Terminal.Gui/TimeoutEventArgs.cs rename to Terminal.Gui/Application/TimeoutEventArgs.cs