From 6402f3a6a38bf490fb7f229903bbc79df717041a Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 3 Jun 2025 19:34:38 +0100 Subject: [PATCH 01/56] Remove continous press code from Application --- Terminal.Gui/App/Application.Mouse.cs | 17 ------ Terminal.Gui/App/Application.cs | 1 - .../Drivers/EscSeqUtils/EscSeqUtils.cs | 39 +------------ .../Drivers/WindowsDriver/WindowsDriver.cs | 57 ------------------- Terminal.Gui/ViewBase/View.cs | 5 -- .../UnitTests/Application/ApplicationTests.cs | 2 - Tests/UnitTests/Input/EscSeqUtilsTests.cs | 2 - Tests/UnitTestsParallelizable/TestSetup.cs | 1 - 8 files changed, 1 insertion(+), 123 deletions(-) diff --git a/Terminal.Gui/App/Application.Mouse.cs b/Terminal.Gui/App/Application.Mouse.cs index b92818d2d0..7c7ac48891 100644 --- a/Terminal.Gui/App/Application.Mouse.cs +++ b/Terminal.Gui/App/Application.Mouse.cs @@ -19,9 +19,6 @@ public static partial class Application // Mouse handling [ConfigurationProperty (Scope = typeof (SettingsScope))] public static bool IsMouseDisabled { get; set; } - /// Gets that has registered to get continuous mouse button pressed events. - public static View? WantContinuousButtonPressedView { get; internal 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. @@ -202,15 +199,6 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent) return; } - if (Initialized) - { - WantContinuousButtonPressedView = deepestViewUnderMouse switch - { - { WantContinuousButtonPressed: true } => deepestViewUnderMouse, - _ => null - }; - } - // May be null before the prior condition or the condition may set it as null. // So, the checking must be outside the prior condition. if (deepestViewUnderMouse is null) @@ -262,11 +250,6 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent) RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse); - if (Initialized) - { - WantContinuousButtonPressedView = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null; - } - while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { }) { if (deepestViewUnderMouse is Adornment adornmentView) diff --git a/Terminal.Gui/App/Application.cs b/Terminal.Gui/App/Application.cs index 3b59b2e13b..85216f3858 100644 --- a/Terminal.Gui/App/Application.cs +++ b/Terminal.Gui/App/Application.cs @@ -229,7 +229,6 @@ internal static void ResetState (bool ignoreDisposed = false) // last mouse pos. //_lastMousePosition = null; CachedViewsUnderMouse.Clear (); - WantContinuousButtonPressedView = null; MouseEvent = null; GrabbedMouse = null; UnGrabbingMouse = null; diff --git a/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs index b035a2335d..919b851a89 100644 --- a/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs @@ -900,22 +900,8 @@ Action continuousButtonPressedHandler _point = pos; - if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) - { - Application.MainLoop?.AddIdle ( - () => - { - // INTENT: What's this trying to do? - // The task itself is not awaited. - Task.Run ( - async () => await ProcessContinuousButtonPressedAsync ( - buttonState, - continuousButtonPressedHandler)); - return false; - }); - } - else if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition)) + if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition)) { _point = pos; @@ -1498,29 +1484,6 @@ private static async Task ProcessButtonDoubleClickedAsync () _isButtonDoubleClicked = false; } - private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action continuousButtonPressedHandler) - { - // PERF: Pause and poll in a hot loop. - // This should be replaced with event dispatch and a synchronization primitive such as AutoResetEvent. - // Will make a massive difference in responsiveness. - while (_isButtonPressed) - { - await Task.Delay (100); - - View view = Application.WantContinuousButtonPressedView; - - if (view is null) - { - break; - } - - if (_isButtonPressed && _lastMouseButtonPressed is { } && (mouseFlag & MouseFlags.ReportMousePosition) == 0) - { - Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, _point ?? Point.Empty)); - } - } - } - private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag) { if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0) diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index 3683f0231c..c49fba23c9 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs @@ -895,51 +895,6 @@ private async Task ProcessButtonDoubleClickedAsync () //buttonPressedCount = 0; } - private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag) - { - // When a user presses-and-holds, start generating pressed events every `startDelay` - // After `iterationsUntilFast` iterations, speed them up to `fastDelay` ms - const int START_DELAY = 500; - const int ITERATIONS_UNTIL_FAST = 4; - const int FAST_DELAY = 50; - - int iterations = 0; - int delay = START_DELAY; - while (_isButtonPressed) - { - // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - View? view = Application.WantContinuousButtonPressedView; - - if (view is null) - { - break; - } - - if (iterations++ >= ITERATIONS_UNTIL_FAST) - { - delay = FAST_DELAY; - } - await Task.Delay (delay); - - //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}"); - if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) - { - Point pointMove = _pointMove; - // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - Application.Invoke (() => - { - var me = new MouseEventArgs - { - ScreenPosition = pointMove, - Position = pointMove, - Flags = mouseFlag - }; - OnMouseEvent (me); - }); - } - } - } - private void ResizeScreen () { _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; @@ -1058,18 +1013,6 @@ private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent _lastMouseButtonPressed = mouseEvent.ButtonState; _isButtonPressed = true; - - if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) - { - // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - Application.MainLoop!.AddIdle ( - () => - { - Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag)); - - return false; - }); - } } else if (_lastMouseButtonPressed != null && mouseEvent.EventFlags == 0 diff --git a/Terminal.Gui/ViewBase/View.cs b/Terminal.Gui/ViewBase/View.cs index 3dd4c9c994..29d6cd4d02 100644 --- a/Terminal.Gui/ViewBase/View.cs +++ b/Terminal.Gui/ViewBase/View.cs @@ -76,11 +76,6 @@ protected virtual void Dispose (bool disposing) Application.UngrabMouse (); } - if (Application.WantContinuousButtonPressedView == this) - { - Application.WantContinuousButtonPressedView = null; - } - for (int i = InternalSubViews.Count - 1; i >= 0; i--) { View subview = InternalSubViews [i]; diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index eb9e8f908f..5d6775dfa6 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -308,7 +308,6 @@ void CheckReset () // Public Properties Assert.Null (Application.Top); Assert.Null (Application.MouseGrabView); - Assert.Null (Application.WantContinuousButtonPressedView); // Don't check Application.ForceDriver // Assert.Empty (Application.ForceDriver); @@ -570,7 +569,6 @@ public void Internal_Properties_Correct () RunState rs = Application.Begin (new ()); Assert.Equal (Application.Top, rs.Toplevel); Assert.Null (Application.MouseGrabView); // public - Assert.Null (Application.WantContinuousButtonPressedView); // public Application.Top!.Dispose (); } diff --git a/Tests/UnitTests/Input/EscSeqUtilsTests.cs b/Tests/UnitTests/Input/EscSeqUtilsTests.cs index 73ec0998ea..9a527bee4d 100644 --- a/Tests/UnitTests/Input/EscSeqUtilsTests.cs +++ b/Tests/UnitTests/Input/EscSeqUtilsTests.cs @@ -750,8 +750,6 @@ public void DecodeEscSeq_Multiple_Tests () Application.Run (top); top.Dispose (); - Assert.Null (Application.WantContinuousButtonPressedView); - Assert.Equal (MouseFlags.Button1Pressed, _arg1); Assert.Equal (new (1, 2), _arg2); diff --git a/Tests/UnitTestsParallelizable/TestSetup.cs b/Tests/UnitTestsParallelizable/TestSetup.cs index b85638e932..d17cbc1710 100644 --- a/Tests/UnitTestsParallelizable/TestSetup.cs +++ b/Tests/UnitTestsParallelizable/TestSetup.cs @@ -41,7 +41,6 @@ private void CheckDefaultState () // Public Properties Assert.Null (Application.Top); Assert.Null (Application.MouseGrabView); - Assert.Null (Application.WantContinuousButtonPressedView); // Don't check Application.ForceDriver // Assert.Empty (Application.ForceDriver); From a7a5d1a5388793d48f5e1a26a29a9758429e2e48 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 3 Jun 2025 19:51:10 +0100 Subject: [PATCH 02/56] WIP prototype code to handle continuous press as subcomponent of View --- Terminal.Gui/ViewBase/View.Mouse.cs | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index a59513ef01..2612357965 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -5,11 +5,18 @@ namespace Terminal.Gui.ViewBase; public partial class View // Mouse APIs { + /// + /// Handles , we have detected a button + /// down in the view and have grabbed the mouse. + /// + private IMouseHeldDown? _mouseHeldDown; + /// Gets the mouse bindings for this view. public MouseBindings MouseBindings { get; internal set; } = null!; private void SetupMouse () { + _mouseHeldDown = new MouseHeldDown (this); MouseBindings = new (); // TODO: Should the default really work with any button or just button1? @@ -681,3 +688,71 @@ protected virtual void OnMouseStateChanged (EventArgs args) { } private void DisposeMouse () { } } + +internal interface IMouseHeldDown : IDisposable +{ + +} + +class MouseHeldDown : IMouseHeldDown +{ + private readonly View _host; + private bool _down; + private object? _timeout; + + public MouseHeldDown (View host) { _host = host; } + + public event Action MouseIsHeldDownTick; + + public void Start () + { + _down = true; + Application.GrabMouse (_host); + + + // Give first tick + TickWhileMouseIsHeldDown (); + + // Then periodic ticks + _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); + + } + + private bool TickWhileMouseIsHeldDown () + { + if (_down) + { + MouseIsHeldDownTick?.Invoke (); + } + else + { + Stop (); + } + + return _down; + } + + public void Stop () + { + if (Application.MouseGrabView == _host) + { + Application.UngrabMouse (); + } + + if (_timeout != null) + { + Application.RemoveTimeout (_timeout); + } + + _down = false; + } + + public void Dispose () + { + if (Application.MouseGrabView == _host) + { + Stop(); + } + } +} + From 8f43909af4215f477a69f9f847f3a8cc2dc5011a Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 3 Jun 2025 20:01:40 +0100 Subject: [PATCH 03/56] Prototype with Button --- Terminal.Gui/ViewBase/View.Mouse.cs | 35 ++++++++++++++++++++++++++--- Terminal.Gui/Views/Button.cs | 5 +++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 2612357965..27943be8e4 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -9,14 +9,14 @@ public partial class View // Mouse APIs /// Handles , we have detected a button /// down in the view and have grabbed the mouse. /// - private IMouseHeldDown? _mouseHeldDown; + protected IMouseHeldDown? MouseHeldDown { get; private set; } /// Gets the mouse bindings for this view. public MouseBindings MouseBindings { get; internal set; } = null!; private void SetupMouse () { - _mouseHeldDown = new MouseHeldDown (this); + MouseHeldDown = new MouseHeldDown (this); MouseBindings = new (); // TODO: Should the default really work with any button or just button1? @@ -314,6 +314,19 @@ protected virtual void OnMouseLeave () { } /// , if the event was handled, otherwise. public bool RaiseMouseEvent (MouseEventArgs mouseEvent) { + // TODO: probably this should be moved elsewhere, please advise + if (WantContinuousButtonPressed && MouseHeldDown != null) + { + if (mouseEvent.IsPressed) + { + MouseHeldDown.Start (); + } + else + { + MouseHeldDown.Stop (); + } + } + if (OnMouseEvent (mouseEvent) || mouseEvent.Handled) { return true; @@ -689,9 +702,25 @@ protected virtual void OnMouseStateChanged (EventArgs args) { } private void DisposeMouse () { } } -internal interface IMouseHeldDown : IDisposable + +/// +/// +/// Handler for raising periodic events while the mouse is held down. +/// Typically, mouse pointer only needs to be pressed down in a view +/// to begin this event after which it can be moved elsewhere. +/// +/// +/// Common use cases for this includes holding a button down to increase +/// a counter (e.g. in ). +/// +/// +public interface IMouseHeldDown : IDisposable { + // TODO: Guess this should follow the established events type - need to double check what that is. + public event Action MouseIsHeldDownTick; + void Start (); + void Stop (); } class MouseHeldDown : IMouseHeldDown diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 894af07d68..a3e033b5d6 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -69,6 +69,11 @@ public Button () base.ShadowStyle = DefaultShadow; HighlightStates = DefaultHighlightStates; + + if (MouseHeldDown != null) + { + MouseHeldDown.MouseIsHeldDownTick += () => RaiseAccepting (null); + } } private bool? HandleHotKeyCommand (ICommandContext commandContext) From cbb20ffe3cc22836fd78bac25c4923924d00f483 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 3 Jun 2025 20:34:01 +0100 Subject: [PATCH 04/56] Implement CWP --- Terminal.Gui/ViewBase/View.Mouse.cs | 32 ++++++++++++++++++++++++++--- Terminal.Gui/Views/Button.cs | 2 +- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 27943be8e4..6bc1677601 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -717,7 +717,7 @@ private void DisposeMouse () { } public interface IMouseHeldDown : IDisposable { // TODO: Guess this should follow the established events type - need to double check what that is. - public event Action MouseIsHeldDownTick; + public event EventHandler MouseIsHeldDownTick; void Start (); void Stop (); @@ -731,7 +731,33 @@ class MouseHeldDown : IMouseHeldDown public MouseHeldDown (View host) { _host = host; } - public event Action MouseIsHeldDownTick; + public event EventHandler? MouseIsHeldDownTick; + + + public bool RaiseMouseIsHeldDownTick () + { + CancelEventArgs args = new (); + + args.Cancel = OnMouseIsHeldDownTick (args) || args.Cancel; + + if (!args.Cancel && MouseIsHeldDownTick is { }) + { + MouseIsHeldDownTick?.Invoke (this, args); + } + + // User event cancelled the mouse held down status so + // stop the currently running operation. + if (args.Cancel) + { + Stop (); + } + + return args.Cancel; + } + protected virtual bool OnMouseIsHeldDownTick (CancelEventArgs eventArgs) + { + return false; + } public void Start () { @@ -751,7 +777,7 @@ private bool TickWhileMouseIsHeldDown () { if (_down) { - MouseIsHeldDownTick?.Invoke (); + RaiseMouseIsHeldDownTick (); } else { diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index a3e033b5d6..942e97b118 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -72,7 +72,7 @@ public Button () if (MouseHeldDown != null) { - MouseHeldDown.MouseIsHeldDownTick += () => RaiseAccepting (null); + MouseHeldDown.MouseIsHeldDownTick += (_,_) => RaiseAccepting (null); } } From b91a9ca9a42ed5ef9cd5c1a819f5d75b2a4b446f Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 4 Jun 2025 06:27:44 +0100 Subject: [PATCH 05/56] Move to seperate classes and prevent double entry to Start --- Terminal.Gui/ViewBase/IMouseHeldDown.cs | 24 +++++ Terminal.Gui/ViewBase/MouseHeldDown.cs | 93 ++++++++++++++++++++ Terminal.Gui/ViewBase/View.Mouse.cs | 112 +----------------------- 3 files changed, 118 insertions(+), 111 deletions(-) create mode 100644 Terminal.Gui/ViewBase/IMouseHeldDown.cs create mode 100644 Terminal.Gui/ViewBase/MouseHeldDown.cs diff --git a/Terminal.Gui/ViewBase/IMouseHeldDown.cs b/Terminal.Gui/ViewBase/IMouseHeldDown.cs new file mode 100644 index 0000000000..6f007f0b17 --- /dev/null +++ b/Terminal.Gui/ViewBase/IMouseHeldDown.cs @@ -0,0 +1,24 @@ +#nullable enable +using System.ComponentModel; + +namespace Terminal.Gui.ViewBase; + +/// +/// +/// Handler for raising periodic events while the mouse is held down. +/// Typically, mouse pointer only needs to be pressed down in a view +/// to begin this event after which it can be moved elsewhere. +/// +/// +/// Common use cases for this includes holding a button down to increase +/// a counter (e.g. in ). +/// +/// +public interface IMouseHeldDown : IDisposable +{ + // TODO: Guess this should follow the established events type - need to double check what that is. + public event EventHandler MouseIsHeldDownTick; + + void Start (); + void Stop (); +} diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs new file mode 100644 index 0000000000..2ab6bd9faa --- /dev/null +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -0,0 +1,93 @@ +#nullable enable +using System.ComponentModel; + +namespace Terminal.Gui.ViewBase; + +internal class MouseHeldDown : IMouseHeldDown +{ + private readonly View _host; + private bool _down; + private object? _timeout; + + public MouseHeldDown (View host) { _host = host; } + + public event EventHandler? MouseIsHeldDownTick; + + public bool RaiseMouseIsHeldDownTick () + { + CancelEventArgs args = new (); + + args.Cancel = OnMouseIsHeldDownTick (args) || args.Cancel; + + if (!args.Cancel && MouseIsHeldDownTick is { }) + { + MouseIsHeldDownTick?.Invoke (this, args); + } + + // User event cancelled the mouse held down status so + // stop the currently running operation. + if (args.Cancel) + { + Stop (); + } + + return args.Cancel; + } + + protected virtual bool OnMouseIsHeldDownTick (CancelEventArgs eventArgs) { return false; } + + public void Start () + { + if (_down) + { + return; + } + + _down = true; + Application.GrabMouse (_host); + + // Give first tick + TickWhileMouseIsHeldDown (); + + // Then periodic ticks + _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); + } + + private bool TickWhileMouseIsHeldDown () + { + Logging.Debug ("Raising TickWhileMouseIsHeldDown..."); + if (_down) + { + RaiseMouseIsHeldDownTick (); + } + else + { + Stop (); + } + + return _down; + } + + public void Stop () + { + if (Application.MouseGrabView == _host) + { + Application.UngrabMouse (); + } + + if (_timeout != null) + { + Application.RemoveTimeout (_timeout); + } + + _down = false; + } + + public void Dispose () + { + if (Application.MouseGrabView == _host) + { + Stop (); + } + } +} diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 6bc1677601..5d4c34cec6 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -700,114 +700,4 @@ protected virtual void OnMouseStateChanged (EventArgs args) { } #endregion MouseState Handling private void DisposeMouse () { } -} - - -/// -/// -/// Handler for raising periodic events while the mouse is held down. -/// Typically, mouse pointer only needs to be pressed down in a view -/// to begin this event after which it can be moved elsewhere. -/// -/// -/// Common use cases for this includes holding a button down to increase -/// a counter (e.g. in ). -/// -/// -public interface IMouseHeldDown : IDisposable -{ - // TODO: Guess this should follow the established events type - need to double check what that is. - public event EventHandler MouseIsHeldDownTick; - - void Start (); - void Stop (); -} - -class MouseHeldDown : IMouseHeldDown -{ - private readonly View _host; - private bool _down; - private object? _timeout; - - public MouseHeldDown (View host) { _host = host; } - - public event EventHandler? MouseIsHeldDownTick; - - - public bool RaiseMouseIsHeldDownTick () - { - CancelEventArgs args = new (); - - args.Cancel = OnMouseIsHeldDownTick (args) || args.Cancel; - - if (!args.Cancel && MouseIsHeldDownTick is { }) - { - MouseIsHeldDownTick?.Invoke (this, args); - } - - // User event cancelled the mouse held down status so - // stop the currently running operation. - if (args.Cancel) - { - Stop (); - } - - return args.Cancel; - } - protected virtual bool OnMouseIsHeldDownTick (CancelEventArgs eventArgs) - { - return false; - } - - public void Start () - { - _down = true; - Application.GrabMouse (_host); - - - // Give first tick - TickWhileMouseIsHeldDown (); - - // Then periodic ticks - _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); - - } - - private bool TickWhileMouseIsHeldDown () - { - if (_down) - { - RaiseMouseIsHeldDownTick (); - } - else - { - Stop (); - } - - return _down; - } - - public void Stop () - { - if (Application.MouseGrabView == _host) - { - Application.UngrabMouse (); - } - - if (_timeout != null) - { - Application.RemoveTimeout (_timeout); - } - - _down = false; - } - - public void Dispose () - { - if (Application.MouseGrabView == _host) - { - Stop(); - } - } -} - +} \ No newline at end of file From a19145afd41856eb31e3f60d11895daada3ea0f1 Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 4 Jun 2025 06:38:03 +0100 Subject: [PATCH 06/56] Fix repeat clicking when moving mouse by removing phantom click code (old implementation of WantContinuousButtonPressed) --- Terminal.Gui/ViewBase/View.Mouse.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 5d4c34cec6..5cdb62fbcb 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -445,11 +445,6 @@ private bool WhenGrabbedHandlePressed (MouseEventArgs mouseEvent) } } - if (WantContinuousButtonPressed && Application.MouseGrabView == this) - { - return RaiseMouseClickEvent (mouseEvent); - } - return mouseEvent.Handled = true; } From d8335c4627bd2b2d0e8d3b98948eaf42fc280c8f Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 4 Jun 2025 06:42:35 +0100 Subject: [PATCH 07/56] Remove initial tick because it results in double activation e.g. button firing twice immediately as mouse is pressed down. --- Terminal.Gui/ViewBase/MouseHeldDown.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index 2ab6bd9faa..247904e4df 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -46,9 +46,6 @@ public void Start () _down = true; Application.GrabMouse (_host); - // Give first tick - TickWhileMouseIsHeldDown (); - // Then periodic ticks _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); } From 98947bacc890835d468140ad8523b4030ae34de2 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 14 Jun 2025 22:38:43 +0100 Subject: [PATCH 08/56] Refactor DatePicker lamdas --- Terminal.Gui/Views/DatePicker.cs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs index 2e2ffa4bc9..a3253f1221 100644 --- a/Terminal.Gui/Views/DatePicker.cs +++ b/Terminal.Gui/Views/DatePicker.cs @@ -227,13 +227,7 @@ private void SetInitialProperties (DateTime date) NoDecorations = true, ShadowStyle = ShadowStyle.None }; - - _previousMonthButton.Accepting += (sender, e) => - { - Date = _date.AddMonths (-1); - CreateCalendar (); - _dateField.Date = Date; - }; + _previousMonthButton.Accepting += (_, _) => AdjustMonth (-1); _nextMonthButton = new Button { @@ -248,12 +242,7 @@ private void SetInitialProperties (DateTime date) ShadowStyle = ShadowStyle.None }; - _nextMonthButton.Accepting += (sender, e) => - { - Date = _date.AddMonths (1); - CreateCalendar (); - _dateField.Date = Date; - }; + _nextMonthButton.Accepting += (_, _) => AdjustMonth (1); CreateCalendar (); SelectDayOnCalendar (_date.Day); @@ -287,6 +276,13 @@ private void SetInitialProperties (DateTime date) Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton); } + private void AdjustMonth (int offset) + { + Date = _date.AddMonths (offset); + CreateCalendar (); + _dateField.Date = Date; + } + /// protected override bool OnDrawingText () { return true; } From 550ef033c3b140e9497b7e8d32538eb1f03f0780 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 14 Jun 2025 23:32:35 +0100 Subject: [PATCH 09/56] WIP investigate subcomponents instead of statics --- Terminal.Gui/ViewBase/IMouseHeldDown.cs | 14 +++++++++++++- Terminal.Gui/ViewBase/MouseHeldDown.cs | 2 +- Terminal.Gui/ViewBase/View.Mouse.cs | 2 +- Tests/UnitTests/View/Mouse/MouseTests.cs | 16 ++++++++++++++-- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/ViewBase/IMouseHeldDown.cs b/Terminal.Gui/ViewBase/IMouseHeldDown.cs index 6f007f0b17..088d7f258d 100644 --- a/Terminal.Gui/ViewBase/IMouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/IMouseHeldDown.cs @@ -16,9 +16,21 @@ namespace Terminal.Gui.ViewBase; /// public interface IMouseHeldDown : IDisposable { - // TODO: Guess this should follow the established events type - need to double check what that is. + /// + /// Periodically raised when the mouse is pressed down inside the view . + /// public event EventHandler MouseIsHeldDownTick; + /// + /// Call to indicate that the mouse has been pressed down and any relevant actions should + /// be undertaken (start timers, etc). + /// void Start (); + + + /// + /// Call to indicate that the mouse has been released and any relevant actions should + /// be undertaken (stop timers, etc). + /// void Stop (); } diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index 247904e4df..df9dfb72f8 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -9,7 +9,7 @@ internal class MouseHeldDown : IMouseHeldDown private bool _down; private object? _timeout; - public MouseHeldDown (View host) { _host = host; } + public MouseHeldDown (View host, ITimedEvents timedEvents, IGrabMouse mouseGrabber) { _host = host; } public event EventHandler? MouseIsHeldDownTick; diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 5cdb62fbcb..85f67a0c33 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -9,7 +9,7 @@ public partial class View // Mouse APIs /// Handles , we have detected a button /// down in the view and have grabbed the mouse. /// - protected IMouseHeldDown? MouseHeldDown { get; private set; } + public IMouseHeldDown? MouseHeldDown { get; private set; } /// Gets the mouse bindings for this view. public MouseBindings MouseBindings { get; internal set; } = null!; diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index f32e208f61..483760c6b5 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -268,7 +268,9 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M var clickedCount = 0; - view.MouseClick += (s, e) => clickedCount++; + view.MouseHeldDown!.MouseIsHeldDownTick += (_, _) => clickedCount++; + + Assert.Empty (Application.MainLoop.TimedEvents.Timeouts); // Start in Viewport me.Flags = MouseFlags.Button1Pressed; @@ -277,11 +279,21 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M Assert.Equal (0, clickedCount); me.Handled = false; + // Mouse is held down so timer should be ticking + Assert.NotEmpty (Application.MainLoop.TimedEvents.Timeouts); + Assert.Equal (clickedCount,0); + + // Don't wait, just force it to expire + Application.MainLoop.TimedEvents.Timeouts.Single ().Value.Callback.Invoke (); + Assert.Equal (clickedCount, 1); + // Move out of Viewport me.Flags = MouseFlags.Button1Pressed; me.Position = me.Position with { X = 1 }; view.NewMouseEvent (me); - Assert.Equal (1, clickedCount); + + Application.MainLoop.TimedEvents.Timeouts.Single ().Value.Callback.Invoke (); + Assert.Equal (clickedCount, 2); me.Handled = false; // Move into Viewport From a28cc6c08b57e0f0e5e67c81c35e459fafb982bf Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 14 Jun 2025 23:42:22 +0100 Subject: [PATCH 10/56] Add IMouseGrabHandler to IApplication --- Terminal.Gui/App/Application.Mouse.cs | 112 ------------------------- Terminal.Gui/App/Application.cd | 66 ++++++++------- Terminal.Gui/App/ApplicationImpl.cs | 2 + Terminal.Gui/App/IApplication.cs | 5 ++ Terminal.Gui/App/IGrabMouse.cs | 32 +++++++ Terminal.Gui/App/MouseGrabHandler.cs | 115 ++++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 140 deletions(-) create mode 100644 Terminal.Gui/App/IGrabMouse.cs create mode 100644 Terminal.Gui/App/MouseGrabHandler.cs diff --git a/Terminal.Gui/App/Application.Mouse.cs b/Terminal.Gui/App/Application.Mouse.cs index 37d009533c..d6275fb614 100644 --- a/Terminal.Gui/App/Application.Mouse.cs +++ b/Terminal.Gui/App/Application.Mouse.cs @@ -19,118 +19,6 @@ public static partial class Application // Mouse handling [ConfigurationProperty (Scope = typeof (SettingsScope))] public static bool IsMouseDisabled { get; 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 || RaiseGrabbingMouseEvent (view)) - { - return; - } - - RaiseGrabbedMouseEvent (view); - - if (Initialized) - { - // MouseGrabView is a static; only set if the application is initialized. - 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 DEBUG_IDISPOSABLE - if (View.EnableDebugIDisposableAsserts) - { - ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView); - } -#endif - - if (!RaiseUnGrabbingMouseEvent (MouseGrabView)) - { - View view = MouseGrabView; - MouseGrabView = null; - RaiseUnGrabbedMouseEvent (view); - } - } - - /// A delegate callback throws an exception. - private static bool RaiseGrabbingMouseEvent (View? view) - { - if (view is null) - { - return false; - } - - var evArgs = new GrabMouseEventArgs (view); - GrabbingMouse?.Invoke (view, evArgs); - - return evArgs.Cancel; - } - - /// A delegate callback throws an exception. - private static bool RaiseUnGrabbingMouseEvent (View? view) - { - if (view is null) - { - return false; - } - - var evArgs = new GrabMouseEventArgs (view); - UnGrabbingMouse?.Invoke (view, evArgs); - - return evArgs.Cancel; - } - - /// A delegate callback throws an exception. - private static void RaiseGrabbedMouseEvent (View? view) - { - if (view is null) - { - return; - } - - GrabbedMouse?.Invoke (view, new (view)); - } - - /// A delegate callback throws an exception. - private static void RaiseUnGrabbedMouseEvent (View? view) - { - if (view is null) - { - return; - } - - UnGrabbedMouse?.Invoke (view, new (view)); - } - /// /// INTERNAL API: Called when a mouse event is raised by the driver. Determines the view under the mouse and diff --git a/Terminal.Gui/App/Application.cd b/Terminal.Gui/App/Application.cd index 9c22dd77ba..f031126212 100644 --- a/Terminal.Gui/App/Application.cd +++ b/Terminal.Gui/App/Application.cd @@ -1,90 +1,100 @@  - + - hEI4FAgAqARIspQfBQo0gTGiACNL0AICESJKoggBSg8= - Application\Application.cs + gEK4FIgQOAQIuhQeBwoUgSCgAAJL0AACESIKoAiBSw8= + App\Application.cs - + AABAAAAAAABCAAAAAAAAAAAAAAAAIgIAAAAAAAAAAAA= - Application\ApplicationNavigation.cs + App\ApplicationNavigation.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - Application\IterationEventArgs.cs + App\IterationEventArgs.cs - + - CAAAIAAAASAAAQAQAAAAAIBADQAAEAAYIgIIwAAAAAI= - Application\MainLoop.cs + AAAAAAAAACAAAAAAAAAAAAAACBAAEAAIIAIAgAAAEAI= + App\MainLoop.cs - + AAAAAgAAAAAAAAAAAEAAAAAACAAAAAAAAAAAAAAAAAA= - Application\MainLoopSyncContext.cs + App\MainLoopSyncContext.cs - + AAAAAAAAACACAgAAAAAAAAAAAAAAAAACQAAAAAAAAAA= - Application\RunState.cs + App\RunState.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA= - Application\RunStateEventArgs.cs + App\RunStateEventArgs.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAQAA= - Application\Timeout.cs + App\Timeout.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAACAIAAAAAAAAAAAA= - Application\TimeoutEventArgs.cs + App\TimeoutEventArgs.cs - + - AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAQAACAACAAAI= - Application\ApplicationImpl.cs + AABgAAAAIAAIAgQUAAAAAQAAAAAAAAAAQAAKgAAAAAI= + App\ApplicationImpl.cs - + AAAAAAAACAAAAAQAAAAABAAAAAAAEAAAAAAAAAAAAAA= - Application\MainLoop.cs + App\MainLoop.cs - + - AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAACAAAAAAI= - Application\IApplication.cs + AAAgAAAAAAAIAgQUAAAAAQAAAAAAAAAAAAAKgAAAAAI= + App\IApplication.cs + + + + + + + + + BAAgAAAAAAAAAgAAAAAAABAAACEAAAAAAAAAAgAAAAA= + App\IGrabMouse.cs diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index 111b9783dc..8909d1c6ce 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -18,6 +18,8 @@ public class ApplicationImpl : IApplication /// public static IApplication Instance => _lazyInstance.Value; + public IMouseGrabHandler MouseGrabHandler { get; } = new MouseGrabHandler (); + /// /// Change the singleton implementation, should not be called except before application /// startup. This method lets you provide alternative implementations of core static gateway diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index d8df8d5528..0f409d8e1c 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -9,6 +9,11 @@ namespace Terminal.Gui.App; /// public interface IApplication { + /// + /// Handles grabbing the mouse (only a single can grab the mouse at once). + /// + IMouseGrabHandler MouseGrabHandler { get; } + /// Initializes a new instance of Application. /// Call this method once per instance (or after has been called). /// diff --git a/Terminal.Gui/App/IGrabMouse.cs b/Terminal.Gui/App/IGrabMouse.cs new file mode 100644 index 0000000000..3bdb555c16 --- /dev/null +++ b/Terminal.Gui/App/IGrabMouse.cs @@ -0,0 +1,32 @@ +namespace Terminal.Gui.App; + +public interface IMouseGrabHandler +{ + /// + /// 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 View? MouseGrabView { get; } + + /// Invoked when a view wants to grab the mouse; can be canceled. + public event EventHandler? GrabbingMouse; + + /// Invoked when a view wants un-grab the mouse; can be canceled. + public event EventHandler? UnGrabbingMouse; + + /// Invoked after a view has grabbed the mouse. + public event EventHandler? GrabbedMouse; + + /// Invoked after a view has un-grabbed the mouse. + public 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 void GrabMouse (View? view); + + /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. + public void UngrabMouse (); +} \ No newline at end of file diff --git a/Terminal.Gui/App/MouseGrabHandler.cs b/Terminal.Gui/App/MouseGrabHandler.cs new file mode 100644 index 0000000000..170e45c247 --- /dev/null +++ b/Terminal.Gui/App/MouseGrabHandler.cs @@ -0,0 +1,115 @@ +namespace Terminal.Gui.App; + +internal class MouseGrabHandler : IMouseGrabHandler +{ + + /// + /// 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 View? MouseGrabView { get; private set; } + + /// Invoked when a view wants to grab the mouse; can be canceled. + public event EventHandler? GrabbingMouse; + + /// Invoked when a view wants un-grab the mouse; can be canceled. + public event EventHandler? UnGrabbingMouse; + + /// Invoked after a view has grabbed the mouse. + public event EventHandler? GrabbedMouse; + + /// Invoked after a view has un-grabbed the mouse. + public 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 void GrabMouse(View? view) + { + if (view is null || RaiseGrabbingMouseEvent(view)) + { + return; + } + + RaiseGrabbedMouseEvent(view); + + // MouseGrabView is a static; only set if the application is initialized. + MouseGrabView = view; + } + + /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. + public void UngrabMouse() + { + if (MouseGrabView is null) + { + return; + } + +#if DEBUG_IDISPOSABLE + if (View.EnableDebugIDisposableAsserts) + { + ObjectDisposedException.ThrowIf(MouseGrabView.WasDisposed, MouseGrabView); + } +#endif + + if (!RaiseUnGrabbingMouseEvent(MouseGrabView)) + { + View view = MouseGrabView; + MouseGrabView = null; + RaiseUnGrabbedMouseEvent(view); + } + } + + /// A delegate callback throws an exception. + private bool RaiseGrabbingMouseEvent(View? view) + { + if (view is null) + { + return false; + } + + var evArgs = new GrabMouseEventArgs(view); + GrabbingMouse?.Invoke(view, evArgs); + + return evArgs.Cancel; + } + + /// A delegate callback throws an exception. + private bool RaiseUnGrabbingMouseEvent(View? view) + { + if (view is null) + { + return false; + } + + var evArgs = new GrabMouseEventArgs(view); + UnGrabbingMouse?.Invoke(view, evArgs); + + return evArgs.Cancel; + } + + /// A delegate callback throws an exception. + private void RaiseGrabbedMouseEvent(View? view) + { + if (view is null) + { + return; + } + + GrabbedMouse?.Invoke(view, new(view)); + } + + /// A delegate callback throws an exception. + private void RaiseUnGrabbedMouseEvent(View? view) + { + if (view is null) + { + return; + } + + UnGrabbedMouse?.Invoke(view, new(view)); + } + +} From 6d794f2cdcdae3350c51cf5e4eb49d840a990dee Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 14 Jun 2025 23:58:02 +0100 Subject: [PATCH 11/56] Make mouse grabbing non static activity --- Terminal.Gui/App/Application.Mouse.cs | 25 ++++--- Terminal.Gui/App/Application.Run.cs | 5 +- Terminal.Gui/App/Application.cs | 6 +- Terminal.Gui/App/ApplicationImpl.cs | 5 +- Terminal.Gui/App/IApplication.cs | 2 +- .../ViewBase/Adornment/Border.Arrangment.cs | 18 ++--- Terminal.Gui/ViewBase/Adornment/Border.cs | 4 +- Terminal.Gui/ViewBase/IMouseHeldDown.cs | 4 +- Terminal.Gui/ViewBase/MouseHeldDown.cs | 10 +-- Terminal.Gui/ViewBase/View.Mouse.cs | 12 ++-- Terminal.Gui/ViewBase/View.cs | 4 +- .../Views/Autocomplete/PopupAutocomplete.cs | 2 +- Terminal.Gui/Views/CharMap/CharMap.cs | 2 +- Terminal.Gui/Views/ComboBox.cs | 4 +- Terminal.Gui/Views/Menuv1/Menu.cs | 8 +-- Terminal.Gui/Views/Menuv1/MenuBar.cs | 66 +++++++++---------- Terminal.Gui/Views/ScrollBar/ScrollSlider.cs | 8 +-- Terminal.Gui/Views/Slider/Slider.cs | 4 +- Terminal.Gui/Views/TextInput/TextField.cs | 12 ++-- Terminal.Gui/Views/TextInput/TextView.cs | 12 ++-- Terminal.Gui/Views/TileView.cs | 4 +- .../UnitTests/Application/ApplicationTests.cs | 6 +- .../Mouse/ApplicationMouseTests.cs | 62 ++++++++--------- .../View/Adornment/ShadowStyleTests.cs | 2 +- Tests/UnitTests/View/Mouse/MouseTests.cs | 22 +++---- .../UnitTests/Views/Menuv1/MenuBarv1Tests.cs | 4 +- Tests/UnitTests/Views/ToplevelTests.cs | 56 ++++++++-------- Tests/UnitTestsParallelizable/TestSetup.cs | 2 +- 28 files changed, 189 insertions(+), 182 deletions(-) diff --git a/Terminal.Gui/App/Application.Mouse.cs b/Terminal.Gui/App/Application.Mouse.cs index d6275fb614..cab426f98a 100644 --- a/Terminal.Gui/App/Application.Mouse.cs +++ b/Terminal.Gui/App/Application.Mouse.cs @@ -19,6 +19,15 @@ public static partial class Application // Mouse handling [ConfigurationProperty (Scope = typeof (SettingsScope))] public static bool IsMouseDisabled { get; set; } + /// + /// Static reference to the current . + /// + public static IMouseGrabHandler MouseGrabHandler + { + get => ApplicationImpl.Instance.MouseGrabHandler; + set => ApplicationImpl.Instance.MouseGrabHandler = value ?? + throw new ArgumentNullException(nameof(value)); + } /// /// INTERNAL API: Called when a mouse event is raised by the driver. Determines the view under the mouse and @@ -134,7 +143,7 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent) RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse); - while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { }) + while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabHandler.MouseGrabView is not { }) { if (deepestViewUnderMouse is Adornment adornmentView) { @@ -186,35 +195,35 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent) internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent) { - if (MouseGrabView is { }) + if (MouseGrabHandler.MouseGrabView is { }) { #if DEBUG_IDISPOSABLE - if (View.EnableDebugIDisposableAsserts && MouseGrabView.WasDisposed) + if (View.EnableDebugIDisposableAsserts && MouseGrabHandler.MouseGrabView.WasDisposed) { - throw new ObjectDisposedException (MouseGrabView.GetType ().FullName); + throw new ObjectDisposedException (MouseGrabHandler.MouseGrabView.GetType ().FullName); } #endif // 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.ScreenPosition); + Point frameLoc = MouseGrabHandler.MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition); var viewRelativeMouseEvent = new MouseEventArgs { Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.ScreenPosition, - View = deepestViewUnderMouse ?? MouseGrabView + View = deepestViewUnderMouse ?? MouseGrabHandler.MouseGrabView }; //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); - if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true) + if (MouseGrabHandler.MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true) { return true; } // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (MouseGrabView is null && deepestViewUnderMouse is Adornment) + if (MouseGrabHandler.MouseGrabView is null && deepestViewUnderMouse is Adornment) { // The view that grabbed the mouse has been disposed return true; diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index d20336e5f8..4f2693f80d 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -89,10 +89,9 @@ public static RunState Begin (Toplevel toplevel) //#endif // Ensure the mouse is ungrabbed. - if (MouseGrabView is { }) + if (MouseGrabHandler.MouseGrabView is { }) { - UngrabMouse (); - MouseGrabView = null; + MouseGrabHandler.UngrabMouse (); } var rs = new RunState (toplevel); diff --git a/Terminal.Gui/App/Application.cs b/Terminal.Gui/App/Application.cs index 85216f3858..bfbeb83495 100644 --- a/Terminal.Gui/App/Application.cs +++ b/Terminal.Gui/App/Application.cs @@ -221,7 +221,7 @@ internal static void ResetState (bool ignoreDisposed = false) // Run State stuff NotifyNewRunState = null; NotifyStopRunState = null; - MouseGrabView = null; + MouseGrabHandler = new MouseGrabHandler (); Initialized = false; // Mouse @@ -230,10 +230,6 @@ internal static void ResetState (bool ignoreDisposed = false) //_lastMousePosition = null; CachedViewsUnderMouse.Clear (); MouseEvent = null; - GrabbedMouse = null; - UnGrabbingMouse = null; - GrabbedMouse = null; - UnGrabbedMouse = null; // Keyboard KeyDown = null; diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index 8909d1c6ce..99dda85884 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -18,7 +18,10 @@ public class ApplicationImpl : IApplication /// public static IApplication Instance => _lazyInstance.Value; - public IMouseGrabHandler MouseGrabHandler { get; } = new MouseGrabHandler (); + /// + /// Handles which (if any) has captured the mouse + /// + public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler (); /// /// Change the singleton implementation, should not be called except before application diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index 0f409d8e1c..ec5cd1ce84 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -12,7 +12,7 @@ public interface IApplication /// /// Handles grabbing the mouse (only a single can grab the mouse at once). /// - IMouseGrabHandler MouseGrabHandler { get; } + IMouseGrabHandler MouseGrabHandler { get; set; } /// Initializes a new instance of Application. /// Call this method once per instance (or after has been called). diff --git a/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs b/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs index a76005138d..546c871c75 100644 --- a/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs +++ b/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs @@ -431,9 +431,9 @@ private void ApplicationOnMouseEvent (object? sender, MouseEventArgs e) Application.MouseEvent -= ApplicationOnMouseEvent; - if (Application.MouseGrabView == this && _dragPosition.HasValue) + if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } // Clean up all arrangement buttons @@ -498,7 +498,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) // Set the start grab point to the Frame coords _startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y); _dragPosition = mouseEvent.Position; - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); // Determine the mode based on where the click occurred ViewArrangement arrangeMode = DetermineArrangeModeFromClick (); @@ -511,7 +511,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) return true; } - if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.MouseGrabView == this) + if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.MouseGrabHandler.MouseGrabView == this) { if (_dragPosition.HasValue) { @@ -523,7 +523,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) { _dragPosition = null; - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); EndArrangeMode (); @@ -763,7 +763,7 @@ internal void HandleDragOperation (MouseEventArgs mouseEvent) private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e) { - if (Application.MouseGrabView == this && _dragPosition.HasValue) + if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue) { e.Cancel = true; } @@ -771,7 +771,7 @@ private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e) private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e) { - if (Application.MouseGrabView == this && _dragPosition.HasValue) + if (Application.MouseGrabHandler.MouseGrabView == this && _dragPosition.HasValue) { e.Cancel = true; } @@ -784,8 +784,8 @@ private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e) /// protected override void Dispose (bool disposing) { - Application.GrabbingMouse -= Application_GrabbingMouse; - Application.UnGrabbingMouse -= Application_UnGrabbingMouse; + Application.MouseGrabHandler.GrabbingMouse -= Application_GrabbingMouse; + Application.MouseGrabHandler.UnGrabbingMouse -= Application_UnGrabbingMouse; _dragPosition = null; base.Dispose (disposing); diff --git a/Terminal.Gui/ViewBase/Adornment/Border.cs b/Terminal.Gui/ViewBase/Adornment/Border.cs index 259a459987..b03645bb6f 100644 --- a/Terminal.Gui/ViewBase/Adornment/Border.cs +++ b/Terminal.Gui/ViewBase/Adornment/Border.cs @@ -50,8 +50,8 @@ public Border (View parent) : base (parent) CanFocus = false; TabStop = TabBehavior.TabGroup; - Application.GrabbingMouse += Application_GrabbingMouse; - Application.UnGrabbingMouse += Application_UnGrabbingMouse; + Application.MouseGrabHandler.GrabbingMouse += Application_GrabbingMouse; + Application.MouseGrabHandler.UnGrabbingMouse += Application_UnGrabbingMouse; ThicknessChanged += OnThicknessChanged; } diff --git a/Terminal.Gui/ViewBase/IMouseHeldDown.cs b/Terminal.Gui/ViewBase/IMouseHeldDown.cs index 088d7f258d..0ce933f3d8 100644 --- a/Terminal.Gui/ViewBase/IMouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/IMouseHeldDown.cs @@ -23,14 +23,14 @@ public interface IMouseHeldDown : IDisposable /// /// Call to indicate that the mouse has been pressed down and any relevant actions should - /// be undertaken (start timers, etc). + /// be undertaken (start timers, etc). /// void Start (); /// /// Call to indicate that the mouse has been released and any relevant actions should - /// be undertaken (stop timers, etc). + /// be undertaken (stop timers, etc). /// void Stop (); } diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index df9dfb72f8..0be88c1f7e 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -9,7 +9,7 @@ internal class MouseHeldDown : IMouseHeldDown private bool _down; private object? _timeout; - public MouseHeldDown (View host, ITimedEvents timedEvents, IGrabMouse mouseGrabber) { _host = host; } + public MouseHeldDown (View host, ITimedEvents timedEvents, IMouseGrabHandler mouseGrabber) { _host = host; } public event EventHandler? MouseIsHeldDownTick; @@ -44,7 +44,7 @@ public void Start () } _down = true; - Application.GrabMouse (_host); + Application.MouseGrabHandler.GrabMouse (_host); // Then periodic ticks _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); @@ -67,9 +67,9 @@ private bool TickWhileMouseIsHeldDown () public void Stop () { - if (Application.MouseGrabView == _host) + if (Application.MouseGrabHandler.MouseGrabView == _host) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } if (_timeout != null) @@ -82,7 +82,7 @@ public void Stop () public void Dispose () { - if (Application.MouseGrabView == _host) + if (Application.MouseGrabHandler.MouseGrabView == _host) { Stop (); } diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 85f67a0c33..efe3ec16a5 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -16,7 +16,7 @@ public partial class View // Mouse APIs private void SetupMouse () { - MouseHeldDown = new MouseHeldDown (this); + MouseHeldDown = new MouseHeldDown (this, Application.MainLoop!.TimedEvents,Application.MouseGrabHandler); MouseBindings = new (); // TODO: Should the default really work with any button or just button1? @@ -375,7 +375,7 @@ internal bool WhenGrabbedHandleReleased (MouseEventArgs mouseEvent) if (mouseEvent.IsReleased) { - if (Application.MouseGrabView == this) + if (Application.MouseGrabHandler.MouseGrabView == this) { //Logging.Debug ($"{Id} - {MouseState}"); MouseState &= ~MouseState.Pressed; @@ -407,9 +407,9 @@ private bool WhenGrabbedHandlePressed (MouseEventArgs mouseEvent) if (mouseEvent.IsPressed) { // The first time we get pressed event, grab the mouse and set focus - if (Application.MouseGrabView != this) + if (Application.MouseGrabHandler.MouseGrabView != this) { - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); if (!HasFocus && CanFocus) { @@ -541,10 +541,10 @@ internal bool WhenGrabbedHandleClicked (MouseEventArgs mouseEvent) { mouseEvent.Handled = false; - if (Application.MouseGrabView == this && mouseEvent.IsSingleClicked) + if (Application.MouseGrabHandler.MouseGrabView == this && mouseEvent.IsSingleClicked) { // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); // TODO: Prove we need to unset MouseState.Pressed and MouseState.PressedOutside here // TODO: There may be perf gains if we don't unset these flags here diff --git a/Terminal.Gui/ViewBase/View.cs b/Terminal.Gui/ViewBase/View.cs index 29d6cd4d02..2061ef4828 100644 --- a/Terminal.Gui/ViewBase/View.cs +++ b/Terminal.Gui/ViewBase/View.cs @@ -71,9 +71,9 @@ protected virtual void Dispose (bool disposing) DisposeAdornments (); DisposeScrollBars (); - if (Application.MouseGrabView == this) + if (Application.MouseGrabHandler.MouseGrabView == this) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } for (int i = InternalSubViews.Count - 1; i >= 0; i--) diff --git a/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs index 1c977715e9..2c07f1abab 100644 --- a/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs @@ -125,7 +125,7 @@ public override bool OnMouseEvent (MouseEventArgs me, bool fromHost = false) { Visible = true; HostControl?.SetNeedsDraw (); - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); return false; } diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs index d62e9df930..683ee0601a 100644 --- a/Terminal.Gui/Views/CharMap/CharMap.cs +++ b/Terminal.Gui/Views/CharMap/CharMap.cs @@ -767,7 +767,7 @@ private void ShowDetails () } // BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); } #endregion Details Dialog diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 158c7cf23c..369de9e8a6 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -958,7 +958,7 @@ protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View pr { _isFocusing = true; _highlighted = _container.SelectedItem; - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); } } else @@ -967,7 +967,7 @@ protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View pr { _isFocusing = false; _highlighted = _container.SelectedItem; - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } } } diff --git a/Terminal.Gui/Views/Menuv1/Menu.cs b/Terminal.Gui/Views/Menuv1/Menu.cs index 54bd6ed6d1..8969595acd 100644 --- a/Terminal.Gui/Views/Menuv1/Menu.cs +++ b/Terminal.Gui/Views/Menuv1/Menu.cs @@ -19,7 +19,7 @@ public Menu () } Application.MouseEvent += Application_RootMouseEvent; - Application.UnGrabbedMouse += Application_UnGrabbedMouse; + Application.MouseGrabHandler.UnGrabbedMouse += Application_UnGrabbedMouse; // Things this view knows how to do AddCommand (Command.Up, () => MoveUp ()); @@ -220,7 +220,7 @@ public void Run (Action? action) return; } - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); _host.CloseAllMenus (); Application.LayoutAndDraw (true); @@ -238,7 +238,7 @@ protected override void Dispose (bool disposing) } Application.MouseEvent -= Application_RootMouseEvent; - Application.UnGrabbedMouse -= Application_UnGrabbedMouse; + Application.MouseGrabHandler.UnGrabbedMouse -= Application_UnGrabbedMouse; base.Dispose (disposing); } @@ -535,7 +535,7 @@ private void Application_UnGrabbedMouse (object? sender, ViewEventArgs a) private void CloseAllMenus () { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); _host.CloseAllMenus (); } diff --git a/Terminal.Gui/Views/Menuv1/MenuBar.cs b/Terminal.Gui/Views/Menuv1/MenuBar.cs index b9d233e70e..a923784496 100644 --- a/Terminal.Gui/Views/Menuv1/MenuBar.cs +++ b/Terminal.Gui/Views/Menuv1/MenuBar.cs @@ -442,12 +442,12 @@ out OpenCurrentMenu._currentChild if (_isContextMenuLoading) { - Application.GrabMouse (_openMenu); + Application.MouseGrabHandler.GrabMouse (_openMenu); _isContextMenuLoading = false; } else { - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); } } @@ -524,16 +524,16 @@ internal void CleanUp () SetNeedsDraw (); - if (Application.MouseGrabView is { } && Application.MouseGrabView is MenuBar && Application.MouseGrabView != this) + if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView is MenuBar && Application.MouseGrabHandler.MouseGrabView != this) { - var menuBar = Application.MouseGrabView as MenuBar; + var menuBar = Application.MouseGrabHandler.MouseGrabView as MenuBar; if (menuBar!.IsMenuOpen) { menuBar.CleanUp (); } } - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); _isCleaning = false; } @@ -556,7 +556,7 @@ internal void CloseAllMenus () _selected = -1; } - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } if (OpenCurrentMenu is { }) @@ -622,9 +622,9 @@ internal bool CloseMenu (bool reopen, bool isSubMenu, bool ignoreUseSubMenusSing _previousFocused.SetFocus (); } - if (Application.MouseGrabView == _openMenu) + if (Application.MouseGrabHandler.MouseGrabView == _openMenu) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } _openMenu?.Dispose (); _openMenu = null; @@ -652,9 +652,9 @@ internal bool CloseMenu (bool reopen, bool isSubMenu, bool ignoreUseSubMenusSing if (OpenCurrentMenu is { }) { SuperView?.Remove (OpenCurrentMenu); - if (Application.MouseGrabView == OpenCurrentMenu) + if (Application.MouseGrabHandler.MouseGrabView == OpenCurrentMenu) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } OpenCurrentMenu.Dispose (); OpenCurrentMenu = null; @@ -845,9 +845,9 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null! if (_openMenu is { }) { SuperView?.Remove (_openMenu); - if (Application.MouseGrabView == _openMenu) + if (Application.MouseGrabHandler.MouseGrabView == _openMenu) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } _openMenu.Dispose (); _openMenu = null; @@ -935,7 +935,7 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null! Host = this, X = first!.Frame.Left, Y = first.Frame.Top, BarItems = newSubMenu }; last!.Visible = false; - Application.GrabMouse (OpenCurrentMenu); + Application.MouseGrabHandler.GrabMouse (OpenCurrentMenu); } OpenCurrentMenu._previousSubFocused = last._previousSubFocused; @@ -1029,9 +1029,9 @@ internal void RemoveAllOpensSubMenus () foreach (Menu item in _openSubMenu) { SuperView?.Remove (item); - if (Application.MouseGrabView == item) + if (Application.MouseGrabHandler.MouseGrabView == item) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } item.Dispose (); } @@ -1137,7 +1137,7 @@ internal bool SelectItem (MenuItem? item) return false; } - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); CloseAllMenus (); Application.LayoutAndDraw (true); _openedByAltKey = true; @@ -1209,15 +1209,15 @@ private bool ProcessMenu (int i, MenuBarItem mi) Point screen = ViewportToScreen (new Point (0, i)); var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi }; menu.Run (mi.Action); - if (Application.MouseGrabView == menu) + if (Application.MouseGrabHandler.MouseGrabView == menu) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } menu.Dispose (); } else { - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); _selected = i; OpenMenu (i); @@ -1280,9 +1280,9 @@ private void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false SuperView!.Remove (menu); _openSubMenu.Remove (menu); - if (Application.MouseGrabView == menu) + if (Application.MouseGrabHandler.MouseGrabView == menu) { - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); } menu.Dispose (); @@ -1458,9 +1458,9 @@ protected override bool OnMouseEvent (MouseEventArgs me) Point screen = ViewportToScreen (new Point (0, i)); var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] }; menu.Run (Menus [i].Action); - if (Application.MouseGrabView == menu) + if (Application.MouseGrabHandler.MouseGrabView == menu) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } menu.Dispose (); @@ -1535,7 +1535,7 @@ protected override bool OnMouseEvent (MouseEventArgs me) internal bool HandleGrabView (MouseEventArgs me, View current) { - if (Application.MouseGrabView is { }) + if (Application.MouseGrabHandler.MouseGrabView is { }) { if (me.View is MenuBar or Menu) { @@ -1546,7 +1546,7 @@ internal bool HandleGrabView (MouseEventArgs me, View current) if (me.Flags == MouseFlags.Button1Clicked) { mbar.CleanUp (); - Application.GrabMouse (me.View); + Application.MouseGrabHandler.GrabMouse (me.View); } else { @@ -1556,10 +1556,10 @@ internal bool HandleGrabView (MouseEventArgs me, View current) } } - if (Application.MouseGrabView != me.View) + if (Application.MouseGrabHandler.MouseGrabView != me.View) { View v = me.View; - Application.GrabMouse (v); + Application.MouseGrabHandler.GrabMouse (v); return true; } @@ -1567,7 +1567,7 @@ internal bool HandleGrabView (MouseEventArgs me, View current) if (me.View != current) { View v = me.View; - Application.GrabMouse (v); + Application.MouseGrabHandler.GrabMouse (v); MouseEventArgs nme; if (me.Position.Y > -1) @@ -1599,7 +1599,7 @@ internal bool HandleGrabView (MouseEventArgs me, View current) && me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); if (IsMenuOpen) { @@ -1625,11 +1625,11 @@ internal bool HandleGrabView (MouseEventArgs me, View current) MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition ))) { - Application.GrabMouse (current); + Application.MouseGrabHandler.GrabMouse (current); } else if (IsMenuOpen && (me.View is MenuBar || me.View is Menu)) { - Application.GrabMouse (me.View); + Application.MouseGrabHandler.GrabMouse (me.View); } else { @@ -1645,7 +1645,7 @@ internal bool HandleGrabView (MouseEventArgs me, View current) private MenuBar? GetMouseGrabViewInstance (View? view) { - if (view is null || Application.MouseGrabView is null) + if (view is null || Application.MouseGrabHandler.MouseGrabView is null) { return null; } @@ -1661,7 +1661,7 @@ internal bool HandleGrabView (MouseEventArgs me, View current) hostView = ((Menu)view).Host; } - View grabView = Application.MouseGrabView; + View grabView = Application.MouseGrabHandler.MouseGrabView; MenuBar? hostGrabView = null; if (grabView is MenuBar bar) diff --git a/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs b/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs index c0558192ad..6cb7d5433a 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs @@ -307,9 +307,9 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1) { - if (Application.MouseGrabView != this) + if (Application.MouseGrabHandler.MouseGrabView != this) { - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); _lastLocation = location; } } @@ -333,9 +333,9 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { _lastLocation = -1; - if (Application.MouseGrabView == this) + if (Application.MouseGrabHandler.MouseGrabView == this) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } } diff --git a/Terminal.Gui/Views/Slider/Slider.cs b/Terminal.Gui/Views/Slider/Slider.cs index 4dff5965ea..4808a939f6 100644 --- a/Terminal.Gui/Views/Slider/Slider.cs +++ b/Terminal.Gui/Views/Slider/Slider.cs @@ -1311,7 +1311,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { _dragPosition = mouseEvent.Position; _moveRenderPosition = ClampMovePosition ((Point)_dragPosition); - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); } SetNeedsDraw (); @@ -1357,7 +1357,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) || mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) { // End Drag - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); _dragPosition = null; _moveRenderPosition = null; diff --git a/Terminal.Gui/Views/TextInput/TextField.cs b/Terminal.Gui/Views/TextInput/TextField.cs index 559b7d8a22..7d171c0936 100644 --- a/Terminal.Gui/Views/TextInput/TextField.cs +++ b/Terminal.Gui/Views/TextInput/TextField.cs @@ -855,16 +855,16 @@ protected override bool OnMouseEvent (MouseEventArgs ev) _isButtonReleased = false; PrepareSelection (x); - if (Application.MouseGrabView is null) + if (Application.MouseGrabHandler.MouseGrabView is null) { - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); } } else if (ev.Flags == MouseFlags.Button1Released) { _isButtonReleased = true; _isButtonPressed = false; - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } else if (ev.Flags == MouseFlags.Button1DoubleClicked) { @@ -1007,12 +1007,12 @@ protected override bool OnDrawingContent () /// protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view) { - if (Application.MouseGrabView is { } && Application.MouseGrabView == this) + if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView == this) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } - //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar)) + //if (SelectedLength != 0 && !(Application.MouseGrabHandler.MouseGrabView is MenuBar)) // ClearAllSelection (); } diff --git a/Terminal.Gui/Views/TextInput/TextView.cs b/Terminal.Gui/Views/TextInput/TextView.cs index 6d4d7b7c65..5450835d83 100644 --- a/Terminal.Gui/Views/TextInput/TextView.cs +++ b/Terminal.Gui/Views/TextInput/TextView.cs @@ -1677,15 +1677,15 @@ protected override bool OnMouseEvent (MouseEventArgs ev) _lastWasKill = false; _columnTrack = CurrentColumn; - if (Application.MouseGrabView is null) + if (Application.MouseGrabHandler.MouseGrabView is null) { - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); } } else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) { _isButtonReleased = true; - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) { @@ -1893,9 +1893,9 @@ protected override bool OnDrawingContent () /// protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view) { - if (Application.MouseGrabView is { } && Application.MouseGrabView == this) + if (Application.MouseGrabHandler.MouseGrabView is { } && Application.MouseGrabHandler.MouseGrabView == this) { - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); } } @@ -2039,7 +2039,7 @@ public void Paste () return null; } - if (Application.MouseGrabView == this && IsSelecting) + if (Application.MouseGrabHandler.MouseGrabView == this && IsSelecting) { // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height); diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 6a993ae577..0e929ac960 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -916,7 +916,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { dragPosition = mouseEvent.Position; dragOrignalPos = Orientation == Orientation.Horizontal ? Y : X; - Application.GrabMouse (this); + Application.MouseGrabHandler.GrabMouse (this); if (Orientation == Orientation.Horizontal) { } @@ -960,7 +960,7 @@ protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { // End Drag - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); //Driver.UncookMouse (); FinalisePosition ( diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index adc6221a02..75d63f0e14 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -307,7 +307,7 @@ void CheckReset () // Public Properties Assert.Null (Application.Top); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Don't check Application.ForceDriver // Assert.Empty (Application.ForceDriver); @@ -568,7 +568,7 @@ public void Internal_Properties_Correct () Assert.Null (Application.Top); RunState rs = Application.Begin (new ()); Assert.Equal (Application.Top, rs.Toplevel); - Assert.Null (Application.MouseGrabView); // public + Assert.Null (Application.MouseGrabHandler.MouseGrabView); // public Application.Top!.Dispose (); } @@ -950,7 +950,7 @@ public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving () Assert.Equal (new (0, 0), w.Frame.Location); Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed }); - Assert.Equal (w.Border, Application.MouseGrabView); + Assert.Equal (w.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (0, 0), w.Frame.Location); // Move down and to the right. diff --git a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs b/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs index 3ae825d94d..6851cb6384 100644 --- a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs +++ b/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs @@ -260,39 +260,39 @@ public void MouseGrabView_WithNullMouseEventView () // if (iterations == 0) // { // Assert.True (tf.HasFocus); - // Assert.Null (Application.MouseGrabView); + // Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.ReportMousePosition }); - // Assert.Equal (sv, Application.MouseGrabView); + // Assert.Equal (sv, Application.MouseGrabHandler.MouseGrabView); // MessageBox.Query ("Title", "Test", "Ok"); - // Assert.Null (Application.MouseGrabView); + // Assert.Null (Application.MouseGrabHandler.MouseGrabView); // } // else if (iterations == 1) // { - // // Application.MouseGrabView is null because + // // Application.MouseGrabHandler.MouseGrabView is null because // // another toplevel (Dialog) was opened - // Assert.Null (Application.MouseGrabView); + // Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.ReportMousePosition }); - // Assert.Null (Application.MouseGrabView); + // Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Application.RaiseMouseEvent (new () { ScreenPosition = new (40, 12), Flags = MouseFlags.ReportMousePosition }); - // Assert.Null (Application.MouseGrabView); + // Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }); - // Assert.Null (Application.MouseGrabView); + // Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Application.RequestStop (); // } // else if (iterations == 2) // { - // Assert.Null (Application.MouseGrabView); + // Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Application.RequestStop (); // } @@ -313,33 +313,33 @@ public void MouseGrabView_GrabbedMouse_UnGrabbedMouse () var view2 = new View { Id = "view2" }; var view3 = new View { Id = "view3" }; - Application.GrabbedMouse += Application_GrabbedMouse; - Application.UnGrabbedMouse += Application_UnGrabbedMouse; + Application.MouseGrabHandler.GrabbedMouse += Application_GrabbedMouse; + Application.MouseGrabHandler.UnGrabbedMouse += Application_UnGrabbedMouse; - Application.GrabMouse (view1); + Application.MouseGrabHandler.GrabMouse (view1); Assert.Equal (0, count); Assert.Equal (grabView, view1); - Assert.Equal (view1, Application.MouseGrabView); + Assert.Equal (view1, Application.MouseGrabHandler.MouseGrabView); - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); Assert.Equal (1, count); Assert.Equal (grabView, view1); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); - Application.GrabbedMouse += Application_GrabbedMouse; - Application.UnGrabbedMouse += Application_UnGrabbedMouse; + Application.MouseGrabHandler.GrabbedMouse += Application_GrabbedMouse; + Application.MouseGrabHandler.UnGrabbedMouse += Application_UnGrabbedMouse; - Application.GrabMouse (view2); + Application.MouseGrabHandler.GrabMouse (view2); Assert.Equal (1, count); Assert.Equal (grabView, view2); - Assert.Equal (view2, Application.MouseGrabView); + Assert.Equal (view2, Application.MouseGrabHandler.MouseGrabView); - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); Assert.Equal (2, count); Assert.Equal (grabView, view2); - Assert.Equal (view3, Application.MouseGrabView); - Application.UngrabMouse (); - Assert.Null (Application.MouseGrabView); + Assert.Equal (view3, Application.MouseGrabHandler.MouseGrabView); + Application.MouseGrabHandler.UngrabMouse (); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); void Application_GrabbedMouse (object sender, ViewEventArgs e) { @@ -354,7 +354,7 @@ void Application_GrabbedMouse (object sender, ViewEventArgs e) grabView = view2; } - Application.GrabbedMouse -= Application_GrabbedMouse; + Application.MouseGrabHandler.GrabbedMouse -= Application_GrabbedMouse; } void Application_UnGrabbedMouse (object sender, ViewEventArgs e) @@ -375,10 +375,10 @@ void Application_UnGrabbedMouse (object sender, ViewEventArgs e) if (count > 1) { // It's possible to grab another view after the previous was ungrabbed - Application.GrabMouse (view3); + Application.MouseGrabHandler.GrabMouse (view3); } - Application.UnGrabbedMouse -= Application_UnGrabbedMouse; + Application.MouseGrabHandler.UnGrabbedMouse -= Application_UnGrabbedMouse; } } @@ -393,18 +393,18 @@ public void View_Is_Responsible_For_Calling_UnGrabMouse_Before_Being_Disposed () top.Add (view); Application.Begin (top); - Assert.Null (Application.MouseGrabView); - Application.GrabMouse (view); - Assert.Equal (view, Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); + Application.MouseGrabHandler.GrabMouse (view); + Assert.Equal (view, Application.MouseGrabHandler.MouseGrabView); top.Remove (view); - Application.UngrabMouse (); + Application.MouseGrabHandler.UngrabMouse (); view.Dispose (); #if DEBUG_IDISPOSABLE Assert.True (view.WasDisposed); #endif Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); Assert.Equal (0, count); top.Dispose (); } diff --git a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs index 6866ffe473..6d45ac9b75 100644 --- a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs +++ b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs @@ -160,7 +160,7 @@ public void ShadowStyle_Button1Pressed_Causes_Movement (ShadowStyle style, int e view.NewMouseEvent (new () { Flags = MouseFlags.Button1Released, Position = new (0, 0) }); Assert.Equal (origThickness, view.Margin.Thickness); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } } diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index 483760c6b5..8634d1dc3e 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -95,7 +95,7 @@ public void WantContinuousButtonPressed_False_Button_Press_Release_DoesNotClick view.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -125,7 +125,7 @@ public void WantContinuousButtonPressed_True_Button_Clicked_Raises_MouseClick (M view.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -155,7 +155,7 @@ public void WantContinuousButtonPressed_True_Button_Clicked_Raises_Selecting (Mo view.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -197,7 +197,7 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_B view.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -248,7 +248,7 @@ MouseFlags clicked view.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -312,7 +312,7 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M view.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -347,7 +347,7 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M // testView.Dispose (); - // // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set // Application.ResetState (true); //} @@ -412,7 +412,7 @@ public void MouseState_None_Button1_Pressed_Move_No_Changes (int x) testView.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -474,7 +474,7 @@ public void MouseState_Pressed_Button1_Pressed_Move_Keeps_Pressed (int x) testView.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -537,7 +537,7 @@ public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside testView.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } @@ -601,7 +601,7 @@ public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside testView.Dispose (); - // Button1Pressed, Button1Released cause Application.MouseGrabView to be set + // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set Application.ResetState (true); } private class MouseEventTestView : View diff --git a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs index acc93a2197..087823380e 100644 --- a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs +++ b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs @@ -2571,11 +2571,11 @@ public void MouseEvent_Test () if (i is < 0 or > 0) { - Assert.Equal (menu, Application.MouseGrabView); + Assert.Equal (menu, Application.MouseGrabHandler.MouseGrabView); } else { - Assert.Equal (menuBar, Application.MouseGrabView); + Assert.Equal (menuBar, Application.MouseGrabHandler.MouseGrabView); } Assert.Equal ("_Edit", miCurrent.Parent.Title); diff --git a/Tests/UnitTests/Views/ToplevelTests.cs b/Tests/UnitTests/Views/ToplevelTests.cs index 862ae44f70..78b2ba3a0b 100644 --- a/Tests/UnitTests/Views/ToplevelTests.cs +++ b/Tests/UnitTests/Views/ToplevelTests.cs @@ -305,17 +305,17 @@ public void Mouse_Drag_On_Top_With_Superview_Null () } else if (iterations == 2) { - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Grab the mouse Application.RaiseMouseEvent (new () { ScreenPosition = new (3, 2), Flags = MouseFlags.Button1Pressed }); - Assert.Equal (Application.Top!.Border, Application.MouseGrabView); + Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (2, 2, 10, 3), Application.Top.Frame); } else if (iterations == 3) { - Assert.Equal (Application.Top!.Border, Application.MouseGrabView); + Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView); // Drag to left Application.RaiseMouseEvent ( @@ -326,19 +326,19 @@ public void Mouse_Drag_On_Top_With_Superview_Null () }); Application.LayoutAndDraw (); - Assert.Equal (Application.Top.Border, Application.MouseGrabView); + Assert.Equal (Application.Top.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (1, 2, 10, 3), Application.Top.Frame); } else if (iterations == 4) { - Assert.Equal (Application.Top!.Border, Application.MouseGrabView); + Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (1, 2), Application.Top.Frame.Location); - Assert.Equal (Application.Top.Border, Application.MouseGrabView); + Assert.Equal (Application.Top.Border, Application.MouseGrabHandler.MouseGrabView); } else if (iterations == 5) { - Assert.Equal (Application.Top!.Border, Application.MouseGrabView); + Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView); // Drag up Application.RaiseMouseEvent ( @@ -349,26 +349,26 @@ public void Mouse_Drag_On_Top_With_Superview_Null () }); Application.LayoutAndDraw (); - Assert.Equal (Application.Top!.Border, Application.MouseGrabView); + Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (1, 1, 10, 3), Application.Top.Frame); } else if (iterations == 6) { - Assert.Equal (Application.Top!.Border, Application.MouseGrabView); + Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (1, 1), Application.Top.Frame.Location); - Assert.Equal (Application.Top.Border, Application.MouseGrabView); + Assert.Equal (Application.Top.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (1, 1, 10, 3), Application.Top.Frame); } else if (iterations == 7) { - Assert.Equal (Application.Top!.Border, Application.MouseGrabView); + Assert.Equal (Application.Top!.Border, Application.MouseGrabHandler.MouseGrabView); // Ungrab the mouse Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 1), Flags = MouseFlags.Button1Released }); Application.LayoutAndDraw (); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); } else if (iterations == 8) { @@ -411,7 +411,7 @@ public void Mouse_Drag_On_Top_With_Superview_Not_Null () { location = win.Frame; - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Grab the mouse Application.RaiseMouseEvent ( @@ -420,11 +420,11 @@ public void Mouse_Drag_On_Top_With_Superview_Not_Null () ScreenPosition = new (win.Frame.X, win.Frame.Y), Flags = MouseFlags.Button1Pressed }); - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); } else if (iterations == 2) { - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); // Drag to left movex = 1; @@ -438,18 +438,18 @@ public void Mouse_Drag_On_Top_With_Superview_Not_Null () | MouseFlags.ReportMousePosition }); - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); } else if (iterations == 3) { // we should have moved +1, +0 - Assert.Equal (win.Border, Application.MouseGrabView); - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); location.Offset (movex, movey); } else if (iterations == 4) { - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); // Drag up movex = 0; @@ -463,18 +463,18 @@ public void Mouse_Drag_On_Top_With_Superview_Not_Null () | MouseFlags.ReportMousePosition }); - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); } else if (iterations == 5) { // we should have moved +0, -1 - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); location.Offset (movex, movey); Assert.Equal (location, win.Frame); } else if (iterations == 6) { - Assert.Equal (win.Border, Application.MouseGrabView); + Assert.Equal (win.Border, Application.MouseGrabHandler.MouseGrabView); // Ungrab the mouse movex = 0; @@ -487,7 +487,7 @@ public void Mouse_Drag_On_Top_With_Superview_Not_Null () Flags = MouseFlags.Button1Released }); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); } else if (iterations == 7) { @@ -602,11 +602,11 @@ public void Window_Viewport_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_L Assert.Equal (new (0, 0, 40, 10), top.Frame); Assert.Equal (new (0, 0, 20, 3), window.Frame); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }); - Assert.Equal (window.Border, Application.MouseGrabView); + Assert.Equal (window.Border, Application.MouseGrabHandler.MouseGrabView); Application.RaiseMouseEvent ( new () @@ -694,14 +694,14 @@ public void Modal_As_Top_Will_Drag_Cleanly () RunState rs = Application.Begin (window); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (0, 0, 10, 3), window.Frame); Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed }); var firstIteration = false; Application.RunIteration (ref rs, firstIteration); - Assert.Equal (window.Border, Application.MouseGrabView); + Assert.Equal (window.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (0, 0, 10, 3), window.Frame); @@ -713,7 +713,7 @@ public void Modal_As_Top_Will_Drag_Cleanly () firstIteration = false; Application.RunIteration (ref rs, firstIteration); - Assert.Equal (window.Border, Application.MouseGrabView); + Assert.Equal (window.Border, Application.MouseGrabHandler.MouseGrabView); Assert.Equal (new (1, 1, 10, 3), window.Frame); Application.End (rs); diff --git a/Tests/UnitTestsParallelizable/TestSetup.cs b/Tests/UnitTestsParallelizable/TestSetup.cs index d17cbc1710..bddfea00fd 100644 --- a/Tests/UnitTestsParallelizable/TestSetup.cs +++ b/Tests/UnitTestsParallelizable/TestSetup.cs @@ -40,7 +40,7 @@ private void CheckDefaultState () // Public Properties Assert.Null (Application.Top); - Assert.Null (Application.MouseGrabView); + Assert.Null (Application.MouseGrabHandler.MouseGrabView); // Don't check Application.ForceDriver // Assert.Empty (Application.ForceDriver); From ee7ea86fb8bd2f9212a3212ea407c57b870c7fb7 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:02:56 +0100 Subject: [PATCH 12/56] Make MouseHeldDown suppress when null fields e.g. app not initialized in tests --- Terminal.Gui/ViewBase/MouseHeldDown.cs | 21 ++++++++++++++------- Terminal.Gui/ViewBase/View.Mouse.cs | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index 0be88c1f7e..16696ab49c 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -8,8 +8,15 @@ internal class MouseHeldDown : IMouseHeldDown private readonly View _host; private bool _down; private object? _timeout; + private readonly ITimedEvents? _timedEvents; + private readonly IMouseGrabHandler? _mouseGrabber; - public MouseHeldDown (View host, ITimedEvents timedEvents, IMouseGrabHandler mouseGrabber) { _host = host; } + public MouseHeldDown (View host, ITimedEvents? timedEvents, IMouseGrabHandler? mouseGrabber) + { + _host = host; + _timedEvents = timedEvents; + _mouseGrabber = mouseGrabber; + } public event EventHandler? MouseIsHeldDownTick; @@ -44,10 +51,10 @@ public void Start () } _down = true; - Application.MouseGrabHandler.GrabMouse (_host); + _mouseGrabber?.GrabMouse (_host); // Then periodic ticks - _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); + _timeout = _timedEvents?.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); } private bool TickWhileMouseIsHeldDown () @@ -67,14 +74,14 @@ private bool TickWhileMouseIsHeldDown () public void Stop () { - if (Application.MouseGrabHandler.MouseGrabView == _host) + if (_mouseGrabber?.MouseGrabView == _host) { - Application.MouseGrabHandler.UngrabMouse (); + _mouseGrabber?.UngrabMouse (); } if (_timeout != null) { - Application.RemoveTimeout (_timeout); + _timedEvents?.RemoveTimeout (_timeout); } _down = false; @@ -82,7 +89,7 @@ public void Stop () public void Dispose () { - if (Application.MouseGrabHandler.MouseGrabView == _host) + if (_mouseGrabber?.MouseGrabView == _host) { Stop (); } diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index efe3ec16a5..d99f1b1384 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -16,7 +16,7 @@ public partial class View // Mouse APIs private void SetupMouse () { - MouseHeldDown = new MouseHeldDown (this, Application.MainLoop!.TimedEvents,Application.MouseGrabHandler); + MouseHeldDown = new MouseHeldDown (this, Application.MainLoop?.TimedEvents,Application.MouseGrabHandler); MouseBindings = new (); // TODO: Should the default really work with any button or just button1? From 887631d2bf513d426d6fa9bf5c509fa0c9d9182c Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:11:33 +0100 Subject: [PATCH 13/56] Update test and remove dependency on Application --- Terminal.Gui/ViewBase/View.Mouse.cs | 2 +- Tests/UnitTests/View/Mouse/MouseTests.cs | 33 +++++++++++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index d99f1b1384..5ecc19299b 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -9,7 +9,7 @@ public partial class View // Mouse APIs /// Handles , we have detected a button /// down in the view and have grabbed the mouse. /// - public IMouseHeldDown? MouseHeldDown { get; private set; } + public IMouseHeldDown? MouseHeldDown { get; set; } /// Gets the mouse bindings for this view. public MouseBindings MouseBindings { get; internal set; } = null!; diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index 8634d1dc3e..43958721a7 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -1,4 +1,5 @@ -using UnitTests; +using Moq; +using UnitTests; namespace Terminal.Gui.ViewMouseTests; @@ -166,7 +167,6 @@ public void WantContinuousButtonPressed_True_Button_Clicked_Raises_Selecting (Mo [InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)] public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released) { - Application.Init (new FakeDriver ()); var me = new MouseEventArgs (); var view = new View @@ -177,28 +177,43 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_B WantMousePositionReports = true }; + // Setup components for mouse held down + var timed = new TimedEvents (); + var grab = new MouseGrabHandler (); + view.MouseHeldDown = new MouseHeldDown (view, timed, grab); + + // Register callback for what to do when the mouse is held down var clickedCount = 0; + view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++; - view.MouseClick += (s, e) => clickedCount++; + // Mouse is currently not held down so should be no timers running + Assert.Empty(timed.Timeouts); + // When mouse is held down me.Flags = pressed; view.NewMouseEvent (me); Assert.Equal (0, clickedCount); me.Handled = false; - me.Flags = pressed; - view.NewMouseEvent (me); + // A timer should begin + var t = Assert.Single (timed.Timeouts); + + // Invoke the timer + t.Value.Callback.Invoke (); + + // Event should have been raised Assert.Equal (1, clickedCount); - me.Handled = false; + Assert.NotEmpty(timed.Timeouts); + // When mouse is released me.Flags = released; view.NewMouseEvent (me); + + // timer should stop + Assert.Empty (timed.Timeouts); Assert.Equal (1, clickedCount); view.Dispose (); - - // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set - Application.ResetState (true); } [Theory] From 0d75ef07ef44b78f243cd13e277030f60ac4749f Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:17:40 +0100 Subject: [PATCH 14/56] Fix other mouse click and hold tests --- Tests/UnitTests/View/Mouse/MouseTests.cs | 47 ++++++++++++++++-------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/Tests/UnitTests/View/Mouse/MouseTests.cs b/Tests/UnitTests/View/Mouse/MouseTests.cs index 43958721a7..6d8a68eab3 100644 --- a/Tests/UnitTests/View/Mouse/MouseTests.cs +++ b/Tests/UnitTests/View/Mouse/MouseTests.cs @@ -227,7 +227,6 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_B MouseFlags clicked ) { - Application.Init (new FakeDriver ()); var me = new MouseEventArgs (); var view = new View @@ -238,39 +237,49 @@ MouseFlags clicked WantMousePositionReports = true }; + // Setup components for mouse held down + var timed = new TimedEvents (); + var grab = new MouseGrabHandler (); + view.MouseHeldDown = new MouseHeldDown (view, timed, grab); + + // Register callback for what to do when the mouse is held down var clickedCount = 0; + view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++; - view.MouseClick += (s, e) => clickedCount++; + Assert.Empty (timed.Timeouts); me.Flags = pressed; view.NewMouseEvent (me); Assert.Equal (0, clickedCount); me.Handled = false; + Assert.NotEmpty(timed.Timeouts); + Assert.Single (timed.Timeouts).Value.Callback.Invoke (); + me.Flags = pressed; view.NewMouseEvent (me); Assert.Equal (1, clickedCount); me.Handled = false; + Assert.NotEmpty (timed.Timeouts); + me.Flags = released; view.NewMouseEvent (me); Assert.Equal (1, clickedCount); me.Handled = false; + Assert.Empty (timed.Timeouts); + me.Flags = clicked; view.NewMouseEvent (me); Assert.Equal (1, clickedCount); view.Dispose (); - - // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set - Application.ResetState (true); } [Fact] public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Move_InViewport_OutOfViewport_Keeps_Counting () { - Application.Init (new FakeDriver ()); var me = new MouseEventArgs (); var view = new View @@ -281,11 +290,14 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M WantMousePositionReports = true }; - var clickedCount = 0; - - view.MouseHeldDown!.MouseIsHeldDownTick += (_, _) => clickedCount++; + // Setup components for mouse held down + var timed = new TimedEvents (); + var grab = new MouseGrabHandler (); + view.MouseHeldDown = new MouseHeldDown (view, timed, grab); - Assert.Empty (Application.MainLoop.TimedEvents.Timeouts); + // Register callback for what to do when the mouse is held down + var clickedCount = 0; + view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++; // Start in Viewport me.Flags = MouseFlags.Button1Pressed; @@ -295,11 +307,11 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M me.Handled = false; // Mouse is held down so timer should be ticking - Assert.NotEmpty (Application.MainLoop.TimedEvents.Timeouts); + Assert.NotEmpty (timed.Timeouts); Assert.Equal (clickedCount,0); // Don't wait, just force it to expire - Application.MainLoop.TimedEvents.Timeouts.Single ().Value.Callback.Invoke (); + Assert.Single (timed.Timeouts).Value.Callback.Invoke (); Assert.Equal (clickedCount, 1); // Move out of Viewport @@ -307,14 +319,17 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M me.Position = me.Position with { X = 1 }; view.NewMouseEvent (me); - Application.MainLoop.TimedEvents.Timeouts.Single ().Value.Callback.Invoke (); + Assert.Single (timed.Timeouts).Value.Callback.Invoke (); Assert.Equal (clickedCount, 2); + me.Handled = false; // Move into Viewport me.Flags = MouseFlags.Button1Pressed; me.Position = me.Position with { X = 0 }; view.NewMouseEvent (me); + + Assert.NotEmpty (timed.Timeouts); Assert.Equal (2, clickedCount); me.Handled = false; @@ -322,13 +337,13 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M me.Flags = MouseFlags.Button1Pressed; me.Position = me.Position with { X = 0 }; view.NewMouseEvent (me); + + Assert.Single (timed.Timeouts).Value.Callback.Invoke (); + Assert.Equal (3, clickedCount); me.Handled = false; view.Dispose (); - - // Button1Pressed, Button1Released cause Application.MouseGrabHandler.MouseGrabView to be set - Application.ResetState (true); } //[Theory] From c3c6c707a678a517ec8c225eca87ba62fc370f81 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:20:56 +0100 Subject: [PATCH 15/56] Code cleanup --- Terminal.Gui/App/Application.cd | 2 +- .../{IGrabMouse.cs => IMouseGrabHandler.cs} | 8 ++++- Terminal.Gui/App/MouseGrabHandler.cs | 36 +++++++++---------- 3 files changed, 25 insertions(+), 21 deletions(-) rename Terminal.Gui/App/{IGrabMouse.cs => IMouseGrabHandler.cs} (80%) diff --git a/Terminal.Gui/App/Application.cd b/Terminal.Gui/App/Application.cd index f031126212..67f3aa8b19 100644 --- a/Terminal.Gui/App/Application.cd +++ b/Terminal.Gui/App/Application.cd @@ -94,7 +94,7 @@ BAAgAAAAAAAAAgAAAAAAABAAACEAAAAAAAAAAgAAAAA= - App\IGrabMouse.cs + App\IMouseGrabHandler.cs diff --git a/Terminal.Gui/App/IGrabMouse.cs b/Terminal.Gui/App/IMouseGrabHandler.cs similarity index 80% rename from Terminal.Gui/App/IGrabMouse.cs rename to Terminal.Gui/App/IMouseGrabHandler.cs index 3bdb555c16..3e4712413a 100644 --- a/Terminal.Gui/App/IGrabMouse.cs +++ b/Terminal.Gui/App/IMouseGrabHandler.cs @@ -1,5 +1,11 @@ namespace Terminal.Gui.App; +/// +/// Interface for class that tracks which (if any) has 'grabbed' the mouse +/// and wants priority updates about its activity e.g. where it moves to, when it is released +/// etc. Example use case is a button on a scroll bar being held down by the mouse - resulting +/// in continuous scrolling. +/// public interface IMouseGrabHandler { /// @@ -29,4 +35,4 @@ public interface IMouseGrabHandler /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. public void UngrabMouse (); -} \ No newline at end of file +} diff --git a/Terminal.Gui/App/MouseGrabHandler.cs b/Terminal.Gui/App/MouseGrabHandler.cs index 170e45c247..8432647a08 100644 --- a/Terminal.Gui/App/MouseGrabHandler.cs +++ b/Terminal.Gui/App/MouseGrabHandler.cs @@ -2,7 +2,6 @@ internal class MouseGrabHandler : IMouseGrabHandler { - /// /// 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. @@ -26,21 +25,21 @@ internal class MouseGrabHandler : IMouseGrabHandler /// is called. /// /// View that will receive all mouse events until is invoked. - public void GrabMouse(View? view) + public void GrabMouse (View? view) { - if (view is null || RaiseGrabbingMouseEvent(view)) + if (view is null || RaiseGrabbingMouseEvent (view)) { return; } - RaiseGrabbedMouseEvent(view); + RaiseGrabbedMouseEvent (view); // MouseGrabView is a static; only set if the application is initialized. MouseGrabView = view; } /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. - public void UngrabMouse() + public void UngrabMouse () { if (MouseGrabView is null) { @@ -50,66 +49,65 @@ public void UngrabMouse() #if DEBUG_IDISPOSABLE if (View.EnableDebugIDisposableAsserts) { - ObjectDisposedException.ThrowIf(MouseGrabView.WasDisposed, MouseGrabView); + ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView); } #endif - if (!RaiseUnGrabbingMouseEvent(MouseGrabView)) + if (!RaiseUnGrabbingMouseEvent (MouseGrabView)) { View view = MouseGrabView; MouseGrabView = null; - RaiseUnGrabbedMouseEvent(view); + RaiseUnGrabbedMouseEvent (view); } } /// A delegate callback throws an exception. - private bool RaiseGrabbingMouseEvent(View? view) + private bool RaiseGrabbingMouseEvent (View? view) { if (view is null) { return false; } - var evArgs = new GrabMouseEventArgs(view); - GrabbingMouse?.Invoke(view, evArgs); + var evArgs = new GrabMouseEventArgs (view); + GrabbingMouse?.Invoke (view, evArgs); return evArgs.Cancel; } /// A delegate callback throws an exception. - private bool RaiseUnGrabbingMouseEvent(View? view) + private bool RaiseUnGrabbingMouseEvent (View? view) { if (view is null) { return false; } - var evArgs = new GrabMouseEventArgs(view); - UnGrabbingMouse?.Invoke(view, evArgs); + var evArgs = new GrabMouseEventArgs (view); + UnGrabbingMouse?.Invoke (view, evArgs); return evArgs.Cancel; } /// A delegate callback throws an exception. - private void RaiseGrabbedMouseEvent(View? view) + private void RaiseGrabbedMouseEvent (View? view) { if (view is null) { return; } - GrabbedMouse?.Invoke(view, new(view)); + GrabbedMouse?.Invoke (view, new (view)); } /// A delegate callback throws an exception. - private void RaiseUnGrabbedMouseEvent(View? view) + private void RaiseUnGrabbedMouseEvent (View? view) { if (view is null) { return; } - UnGrabbedMouse?.Invoke(view, new(view)); + UnGrabbedMouse?.Invoke (view, new (view)); } - } From f717be515d4981b2ad31ea37cbc87a73a3724f61 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:22:41 +0100 Subject: [PATCH 16/56] Update class diagram --- Terminal.Gui/App/Application.cd | 35 ++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/App/Application.cd b/Terminal.Gui/App/Application.cd index 67f3aa8b19..86ca827c18 100644 --- a/Terminal.Gui/App/Application.cd +++ b/Terminal.Gui/App/Application.cd @@ -8,21 +8,21 @@ - + AABAAAAAAABCAAAAAAAAAAAAAAAAIgIAAAAAAAAAAAA= App\ApplicationNavigation.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= App\IterationEventArgs.cs - + AAAAAAAAACAAAAAAAAAAAAAACBAAEAAIIAIAgAAAEAI= App\MainLoop.cs @@ -30,14 +30,14 @@ - + AAAAAgAAAAAAAAAAAEAAAAAACAAAAAAAAAAAAAAAAAA= App\MainLoopSyncContext.cs - + AAAAAAAAACACAgAAAAAAAAAAAAAAAAACQAAAAAAAAAA= App\RunState.cs @@ -45,43 +45,54 @@ - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA= App\RunStateEventArgs.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAQAA= App\Timeout.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAACAIAAAAAAAAAAAA= App\TimeoutEventArgs.cs - + AABgAAAAIAAIAgQUAAAAAQAAAAAAAAAAQAAKgAAAAAI= App\ApplicationImpl.cs + + + + + + + + + BAAgAAAAgABAAoAAAAAAABAAACEAAAAAAABAAgAAAAA= + App\MouseGrabHandler.cs + - + AAAAAAAACAAAAAQAAAAABAAAAAAAEAAAAAAAAAAAAAA= App\MainLoop.cs - + AAAgAAAAAAAIAgQUAAAAAQAAAAAAAAAAAAAKgAAAAAI= App\IApplication.cs @@ -91,7 +102,7 @@ - + BAAgAAAAAAAAAgAAAAAAABAAACEAAAAAAAAAAgAAAAA= App\IMouseGrabHandler.cs From 32d747a69311e2c6a23dda544c9d728ddacfdef5 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:25:00 +0100 Subject: [PATCH 17/56] Fix bad xml doc references --- Terminal.Gui/ViewBase/IMouseHeldDown.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ViewBase/IMouseHeldDown.cs b/Terminal.Gui/ViewBase/IMouseHeldDown.cs index 0ce933f3d8..d7b57b413a 100644 --- a/Terminal.Gui/ViewBase/IMouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/IMouseHeldDown.cs @@ -23,14 +23,14 @@ public interface IMouseHeldDown : IDisposable /// /// Call to indicate that the mouse has been pressed down and any relevant actions should - /// be undertaken (start timers, etc). + /// be undertaken (start timers, etc). /// void Start (); /// /// Call to indicate that the mouse has been released and any relevant actions should - /// be undertaken (stop timers, etc). + /// be undertaken (stop timers, etc). /// void Stop (); } From e9a33cbde1668eb59b4b81b45aec972331c38845 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:38:34 +0100 Subject: [PATCH 18/56] Fix timed events not getting passed through in v2 applications --- Terminal.Gui/App/Application.cd | 39 +++++++++++++----------- Terminal.Gui/App/Application.cs | 9 ++++++ Terminal.Gui/App/ApplicationImpl.cs | 4 +++ Terminal.Gui/App/IApplication.cs | 6 ++++ Terminal.Gui/Drivers/V2/ApplicationV2.cs | 3 ++ Terminal.Gui/ViewBase/View.Mouse.cs | 2 +- 6 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Terminal.Gui/App/Application.cd b/Terminal.Gui/App/Application.cd index 86ca827c18..294a90411b 100644 --- a/Terminal.Gui/App/Application.cd +++ b/Terminal.Gui/App/Application.cd @@ -3,26 +3,26 @@ - gEK4FIgQOAQIuhQeBwoUgSCgAAJL0AACESIKoAiBSw8= + gEK4FIgQOAQIuhQeBwoUgSCgAAJL0AACESIKoAiBWw8= App\Application.cs - + AABAAAAAAABCAAAAAAAAAAAAAAAAIgIAAAAAAAAAAAA= App\ApplicationNavigation.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= App\IterationEventArgs.cs - + AAAAAAAAACAAAAAAAAAAAAAACBAAEAAIIAIAgAAAEAI= App\MainLoop.cs @@ -30,14 +30,14 @@ - + AAAAAgAAAAAAAAAAAEAAAAAACAAAAAAAAAAAAAAAAAA= App\MainLoopSyncContext.cs - + AAAAAAAAACACAgAAAAAAAAAAAAAAAAACQAAAAAAAAAA= App\RunState.cs @@ -45,35 +45,32 @@ - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA= App\RunStateEventArgs.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAQAA= App\Timeout.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAACAIAAAAAAAAAAAA= App\TimeoutEventArgs.cs - + - AABgAAAAIAAIAgQUAAAAAQAAAAAAAAAAQAAKgAAAAAI= + AABgAAAAIAAIAgQUAAAAAQAAAAAAAAAAQAAKgAAAEAI= App\ApplicationImpl.cs - - - @@ -85,7 +82,7 @@ - + AAAAAAAACAAAAAQAAAAABAAAAAAAEAAAAAAAAAAAAAA= App\MainLoop.cs @@ -94,19 +91,27 @@ - AAAgAAAAAAAIAgQUAAAAAQAAAAAAAAAAAAAKgAAAAAI= + AAAgAAAAAAAIAgQUAAAAAQAAAAAAAAAAAAAKgAAAEAI= App\IApplication.cs + - + BAAgAAAAAAAAAgAAAAAAABAAACEAAAAAAAAAAgAAAAA= App\IMouseGrabHandler.cs + + + + BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA= + App\ITimedEvents.cs + + \ No newline at end of file diff --git a/Terminal.Gui/App/Application.cs b/Terminal.Gui/App/Application.cs index bfbeb83495..26d29a7d41 100644 --- a/Terminal.Gui/App/Application.cs +++ b/Terminal.Gui/App/Application.cs @@ -42,6 +42,15 @@ public static partial class Application /// Gets all cultures supported by the application without the invariant language. public static List? SupportedCultures { get; private set; } = GetSupportedCultures (); + + /// + /// + /// Handles recurring events. These are invoked on the main UI thread - allowing for + /// safe updates to instances. + /// + /// + public static ITimedEvents TimedEvents => ApplicationImpl.Instance.TimedEvents; + /// /// Gets a string representation of the Application as rendered by . /// diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index 99dda85884..4beec2a752 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -18,6 +18,10 @@ public class ApplicationImpl : IApplication /// public static IApplication Instance => _lazyInstance.Value; + + /// + public virtual ITimedEvents TimedEvents => Application.MainLoop.TimedEvents; + /// /// Handles which (if any) has captured the mouse /// diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index ec5cd1ce84..42c8882896 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -9,6 +9,12 @@ namespace Terminal.Gui.App; /// public interface IApplication { + /// + /// Handles recurring events. These are invoked on the main UI thread - allowing for + /// safe updates to instances. + /// + ITimedEvents TimedEvents { get; } + /// /// Handles grabbing the mouse (only a single can grab the mouse at once). /// diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index e10a621b78..c960ce4356 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -21,6 +21,9 @@ public class ApplicationV2 : ApplicationImpl private readonly ITimedEvents _timedEvents = new TimedEvents (); + /// + public override ITimedEvents TimedEvents => _timedEvents; + /// /// Creates anew instance of the Application backend. The provided /// factory methods will be used on Init calls to get things booted. diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 5ecc19299b..47de711d59 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -16,7 +16,7 @@ public partial class View // Mouse APIs private void SetupMouse () { - MouseHeldDown = new MouseHeldDown (this, Application.MainLoop?.TimedEvents,Application.MouseGrabHandler); + MouseHeldDown = new MouseHeldDown (this, Application.TimedEvents,Application.MouseGrabHandler); MouseBindings = new (); // TODO: Should the default really work with any button or just button1? From 48e80f3da295567d44fae8cd1aa91b92c0825b63 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 00:47:19 +0100 Subject: [PATCH 19/56] Make timed events nullable for tests that dont create an Application --- Terminal.Gui/App/Application.cs | 2 +- Terminal.Gui/App/ApplicationImpl.cs | 2 +- Terminal.Gui/App/IApplication.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/App/Application.cs b/Terminal.Gui/App/Application.cs index 26d29a7d41..f29f8049e6 100644 --- a/Terminal.Gui/App/Application.cs +++ b/Terminal.Gui/App/Application.cs @@ -49,7 +49,7 @@ public static partial class Application /// safe updates to instances. /// /// - public static ITimedEvents TimedEvents => ApplicationImpl.Instance.TimedEvents; + public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents; /// /// Gets a string representation of the Application as rendered by . diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index 4beec2a752..cd9c0e9f36 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -20,7 +20,7 @@ public class ApplicationImpl : IApplication /// - public virtual ITimedEvents TimedEvents => Application.MainLoop.TimedEvents; + public virtual ITimedEvents? TimedEvents => Application.MainLoop?.TimedEvents; /// /// Handles which (if any) has captured the mouse diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index 42c8882896..33f6832f32 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -13,7 +13,7 @@ public interface IApplication /// Handles recurring events. These are invoked on the main UI thread - allowing for /// safe updates to instances. /// - ITimedEvents TimedEvents { get; } + ITimedEvents? TimedEvents { get; } /// /// Handles grabbing the mouse (only a single can grab the mouse at once). From 06e45e1f3e80fcbfc1e87e0ad10f7a0d2ff520c4 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 15 Jun 2025 13:30:46 +0100 Subject: [PATCH 20/56] Remove strange blocking test --- Tests/UnitTests/Input/EscSeqUtilsTests.cs | 29 +++-------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/Tests/UnitTests/Input/EscSeqUtilsTests.cs b/Tests/UnitTests/Input/EscSeqUtilsTests.cs index 9a527bee4d..6daf4bd1ec 100644 --- a/Tests/UnitTests/Input/EscSeqUtilsTests.cs +++ b/Tests/UnitTests/Input/EscSeqUtilsTests.cs @@ -679,14 +679,7 @@ public void DecodeEscSeq_Multiple_Tests () Assert.Equal (new () { MouseFlags.Button1TripleClicked }, _mouseFlags); Assert.Equal (new (1, 2), _pos); Assert.False (_isResponse); - - var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), WantContinuousButtonPressed = true }; - var top = new Toplevel (); - top.Add (view); - Application.Begin (top); - - Application.RaiseMouseEvent (new () { Position = new (0, 0), Flags = 0 }); - + ClearAll (); _cki = new ConsoleKeyInfo [] @@ -734,24 +727,8 @@ public void DecodeEscSeq_Multiple_Tests () Assert.Equal (new (1, 2), _pos); Assert.False (_isResponse); - Application.Iteration += (s, a) => - { - if (_actionStarted) - { - // set Application.WantContinuousButtonPressedView to null - view.WantContinuousButtonPressed = false; - - Application.RaiseMouseEvent (new () { Position = new (0, 0), Flags = 0 }); - - Application.RequestStop (); - } - }; - - Application.Run (top); - top.Dispose (); - - Assert.Equal (MouseFlags.Button1Pressed, _arg1); - Assert.Equal (new (1, 2), _arg2); + Assert.Equal (MouseFlags.None, _arg1); + Assert.Equal (new (0, 0), _arg2); ClearAll (); From c106ff035fbc73c247c17c44f633a01e4fac1f17 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 19 Jun 2025 20:33:14 +0100 Subject: [PATCH 21/56] WIP remove all idles and replace with zero timeouts --- Terminal.Gui/App/Application.cs | 6 -- Terminal.Gui/App/ApplicationImpl.cs | 16 +--- Terminal.Gui/App/IApplication.cs | 8 +- Terminal.Gui/App/ITimedEvents.cs | 54 +++--------- Terminal.Gui/App/MainLoop.cs | 28 ------ Terminal.Gui/App/MainLoopSyncContext.cs | 2 +- Terminal.Gui/App/TimedEvents.cs | 86 +------------------ .../Drivers/CursesDriver/UnixMainLoop.cs | 2 +- .../Drivers/EscSeqUtils/EscSeqUtils.cs | 8 +- Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs | 4 +- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 12 +-- Terminal.Gui/Drivers/V2/MainLoop.cs | 4 +- .../Drivers/WindowsDriver/WindowsDriver.cs | 4 +- .../Drivers/WindowsDriver/WindowsMainLoop.cs | 6 +- Terminal.Gui/Views/Menuv1/MenuBar.cs | 2 +- Tests/UnitTests/Application/MainLoopTests.cs | 54 ++++++------ .../ConsoleDrivers/MainLoopDriverTests.cs | 25 +++--- .../ConsoleDrivers/V2/ApplicationV2Tests.cs | 4 +- 18 files changed, 74 insertions(+), 251 deletions(-) diff --git a/Terminal.Gui/App/Application.cs b/Terminal.Gui/App/Application.cs index 3b59b2e13b..ee69cec35c 100644 --- a/Terminal.Gui/App/Application.cs +++ b/Terminal.Gui/App/Application.cs @@ -252,10 +252,4 @@ internal static void ResetState (bool ignoreDisposed = false) // (https://github.com/gui-cs/Terminal.Gui/issues/1084). SynchronizationContext.SetSynchronizationContext (null); } - - /// - /// Adds specified idle handler function to main iteration processing. The handler function will be called - /// once per iteration of the main loop after other events have been handled. - /// - public static void AddIdle (Func func) { ApplicationImpl.Instance.AddIdle (func); } } diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index 111b9783dc..7cd36eee4b 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -261,7 +261,7 @@ public virtual void RequestStop (Toplevel? top) /// public virtual void Invoke (Action action) { - Application.MainLoop?.AddIdle ( + Application.AddTimeout (TimeSpan.Zero, () => { action (); @@ -274,20 +274,6 @@ public virtual void Invoke (Action action) /// public bool IsLegacy { get; protected set; } = true; - /// - public virtual void AddIdle (Func func) - { - if (Application.MainLoop is null) - { - throw new NotInitializedException ("Cannot add idle before main loop is initialized"); - } - - // Yes in this case we cannot go direct via TimedEvents because legacy main loop - // has established behaviour to do other stuff too e.g. 'wake up'. - Application.MainLoop.AddIdle (func); - - } - /// public virtual object AddTimeout (TimeSpan time, Func callback) { diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index d8df8d5528..4c4ad97a8b 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -156,13 +156,7 @@ public T Run (Func? errorHandler = null, IConsoleDriver? dri /// is cutting edge. /// bool IsLegacy { get; } - - /// - /// Adds specified idle handler function to main iteration processing. The handler function will be called - /// once per iteration of the main loop after other events have been handled. - /// - void AddIdle (Func func); - + /// Adds a timeout to the application. /// /// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be diff --git a/Terminal.Gui/App/ITimedEvents.cs b/Terminal.Gui/App/ITimedEvents.cs index 1fd867a8c8..a590e74090 100644 --- a/Terminal.Gui/App/ITimedEvents.cs +++ b/Terminal.Gui/App/ITimedEvents.cs @@ -8,34 +8,11 @@ namespace Terminal.Gui.App; /// public interface ITimedEvents { - /// - /// Adds specified idle handler function to main iteration processing. The handler function will be called - /// once per iteration of the main loop after other events have been handled. - /// - /// - void AddIdle (Func idleHandler); - - /// - /// Runs all idle hooks - /// - void LockAndRunIdles (); - /// /// Runs all timeouts that are due /// void LockAndRunTimers (); - /// - /// Called from to check if there are any outstanding timers or idle - /// handlers. - /// - /// - /// Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if - /// there are no active timers. - /// - /// if there is a timer or idle handler active. - bool CheckTimersAndIdleHandlers (out int waitTimeout); - /// Adds a timeout to the application. /// /// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be @@ -58,33 +35,28 @@ public interface ITimedEvents /// bool RemoveTimeout (object token); - /// - /// Returns all currently registered idles. May not include - /// actively executing idles. - /// - ReadOnlyCollection> IdleHandlers { get;} - /// /// Returns the next planned execution time (key - UTC ticks) /// for each timeout that is not actively executing. /// SortedList Timeouts { get; } - - /// Removes an idle handler added with from processing. - /// - /// - /// if the idle handler is successfully removed; otherwise, - /// - /// . - /// This method also returns - /// - /// if the idle handler is not found. - bool RemoveIdle (Func fnTrue); - /// /// Invoked when a new timeout is added. To be used in the case when /// is . /// event EventHandler? TimeoutAdded; + + + + /// + /// Called from to check if there are any outstanding timers + /// handlers. + /// + /// + /// Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if + /// there are no active timers. + /// + /// if there is a timer active. + bool CheckTimers (out int waitTimeout); } diff --git a/Terminal.Gui/App/MainLoop.cs b/Terminal.Gui/App/MainLoop.cs index 65f4ed599f..83beb3f848 100644 --- a/Terminal.Gui/App/MainLoop.cs +++ b/Terminal.Gui/App/MainLoop.cs @@ -75,32 +75,6 @@ public void Dispose () MainLoopDriver = null; } - /// - /// Adds specified idle handler function to processing. The handler function will be called - /// once per iteration of the main loop after other events have been handled. - /// - /// - /// Remove an idle handler by calling with the token this method returns. - /// - /// If the returns it will be removed and not called - /// subsequently. - /// - /// - /// Token that can be used to remove the idle handler with . - // QUESTION: Why are we re-inventing the event wheel here? - // PERF: This is heavy. - // CONCURRENCY: Race conditions exist here. - // CONCURRENCY: null delegates will hose this. - // - internal Func AddIdle (Func idleHandler) - { - TimedEvents.AddIdle (idleHandler); - - MainLoopDriver?.Wakeup (); - - return idleHandler; - } - /// Determines whether there are pending events to be processed. /// @@ -139,8 +113,6 @@ internal void RunIteration () MainLoopDriver?.Iteration (); TimedEvents.LockAndRunTimers (); - - TimedEvents.LockAndRunIdles (); } private void RunAnsiScheduler () diff --git a/Terminal.Gui/App/MainLoopSyncContext.cs b/Terminal.Gui/App/MainLoopSyncContext.cs index d2268c5ad9..69ddc9af1b 100644 --- a/Terminal.Gui/App/MainLoopSyncContext.cs +++ b/Terminal.Gui/App/MainLoopSyncContext.cs @@ -10,7 +10,7 @@ internal sealed class MainLoopSyncContext : SynchronizationContext public override void Post (SendOrPostCallback d, object state) { - Application.MainLoop?.AddIdle ( + Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, () => { d (state); diff --git a/Terminal.Gui/App/TimedEvents.cs b/Terminal.Gui/App/TimedEvents.cs index 13553a43b2..225865d62b 100644 --- a/Terminal.Gui/App/TimedEvents.cs +++ b/Terminal.Gui/App/TimedEvents.cs @@ -8,42 +8,15 @@ namespace Terminal.Gui.App; /// public class TimedEvents : ITimedEvents { - internal List> _idleHandlers = new (); internal SortedList _timeouts = new (); - - /// The idle handlers and lock that must be held while manipulating them - private readonly object _idleHandlersLock = new (); - private readonly object _timeoutsLockToken = new (); - - /// Gets a copy of the list of all idle handlers. - public ReadOnlyCollection> IdleHandlers - { - get - { - lock (_idleHandlersLock) - { - return new List> (_idleHandlers).AsReadOnly (); - } - } - } - /// /// Gets the list of all timeouts sorted by the time ticks. A shorter limit time can be /// added at the end, but it will be called before an earlier addition that has a longer limit time. /// public SortedList Timeouts => _timeouts; - /// - public void AddIdle (Func idleHandler) - { - lock (_idleHandlersLock) - { - _idleHandlers.Add (idleHandler); - } - } - /// public event EventHandler? TimeoutAdded; @@ -77,32 +50,6 @@ private long NudgeToUniqueKey (long k) return k; } - - // PERF: This is heavier than it looks. - // CONCURRENCY: Potential deadlock city here. - // CONCURRENCY: Multiple concurrency pitfalls on the delegates themselves. - // INTENT: It looks like the general architecture here is trying to be a form of publisher/consumer pattern. - private void RunIdle () - { - Func [] iterate; - lock (_idleHandlersLock) - { - iterate = _idleHandlers.ToArray (); - _idleHandlers = new List> (); - } - - foreach (Func idle in iterate) - { - if (idle ()) - { - lock (_idleHandlersLock) - { - _idleHandlers.Add (idle); - } - } - } - } - /// public void LockAndRunTimers () { @@ -116,21 +63,6 @@ public void LockAndRunTimers () } - /// - public void LockAndRunIdles () - { - bool runIdle; - - lock (_idleHandlersLock) - { - runIdle = _idleHandlers.Count > 0; - } - - if (runIdle) - { - RunIdle (); - } - } private void RunTimers () { long now = DateTime.UtcNow.Ticks; @@ -165,15 +97,6 @@ private void RunTimers () } } - /// - public bool RemoveIdle (Func token) - { - lock (_idleHandlersLock) - { - return _idleHandlers.Remove (token); - } - } - /// Removes a previously scheduled timeout /// The token parameter is the value returned by AddTimeout. /// Returns @@ -219,7 +142,7 @@ public object AddTimeout (TimeSpan time, Func callback) } /// - public bool CheckTimersAndIdleHandlers (out int waitTimeout) + public bool CheckTimers(out int waitTimeout) { long now = DateTime.UtcNow.Ticks; @@ -247,11 +170,6 @@ public bool CheckTimersAndIdleHandlers (out int waitTimeout) waitTimeout = -1; } - // There are no timers set, check if there are any idle handlers - - lock (_idleHandlersLock) - { - return _idleHandlers.Count > 0; - } + return false; } } \ No newline at end of file diff --git a/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs index c3486e7027..50053f4bb0 100644 --- a/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs @@ -104,7 +104,7 @@ bool IMainLoopDriver.EventsPending () UpdatePollMap (); - bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int pollTimeout); + bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimers (out int pollTimeout); int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout); diff --git a/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs index b035a2335d..6dc1a8d5dc 100644 --- a/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs @@ -902,7 +902,7 @@ Action continuousButtonPressedHandler if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) { - Application.MainLoop?.AddIdle ( + Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, () => { // INTENT: What's this trying to do? @@ -945,7 +945,7 @@ Action continuousButtonPressedHandler _isButtonClicked = false; _isButtonDoubleClicked = true; - Application.MainLoop?.AddIdle ( + Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, () => { Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); @@ -959,7 +959,7 @@ Action continuousButtonPressedHandler // lastMouseButtonReleased = null; // isButtonReleased = false; // isButtonClicked = true; - // Application.MainLoop.AddIdle (() => { + // Application.MainLoop.AddTimeout (() => { // Task.Run (async () => await ProcessButtonClickedAsync ()); // return false; // }); @@ -984,7 +984,7 @@ Action continuousButtonPressedHandler mouseFlags.Add (GetButtonClicked (buttonState)); _isButtonClicked = true; - Application.MainLoop?.AddIdle ( + Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, () => { Task.Run (async () => await ProcessButtonClickedAsync ()); diff --git a/Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs index b33964be42..96aae80390 100644 --- a/Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs @@ -57,7 +57,7 @@ bool IMainLoopDriver.EventsPending () _waitForProbe.Set (); - if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout)) + if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimers (out int waitTimeout)) { return true; } @@ -84,7 +84,7 @@ bool IMainLoopDriver.EventsPending () if (!_eventReadyTokenSource.IsCancellationRequested) { - return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _); + return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimers (out _); } // If cancellation was requested then always return true diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index e10a621b78..9ddbcfe722 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -225,7 +225,7 @@ public override void RequestStop (Toplevel? top) /// public override void Invoke (Action action) { - _timedEvents.AddIdle ( + _timedEvents.AddTimeout (TimeSpan.Zero, () => { action (); @@ -235,16 +235,6 @@ public override void Invoke (Action action) ); } - /// - public override void AddIdle (Func func) { _timedEvents.AddIdle (func); } - - /// - /// Removes an idle function added by - /// - /// Function to remove - /// True if it was found and removed - public bool RemoveIdle (Func fnTrue) { return _timedEvents.RemoveIdle (fnTrue); } - /// public override object AddTimeout (TimeSpan time, Func callback) { return _timedEvents.AddTimeout (time, callback); } diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index e40dfc66b6..9059217078 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -144,9 +144,7 @@ internal void IterationImpl () var swCallbacks = Stopwatch.StartNew (); TimedEvents.LockAndRunTimers (); - - TimedEvents.LockAndRunIdles (); - + Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds); } diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index 3683f0231c..49ecf344eb 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs @@ -990,7 +990,7 @@ private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent if (_isButtonDoubleClicked || _isOneFingerDoubleClicked) { // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - Application.MainLoop!.AddIdle ( + Application.MainLoop!.TimedEvents.AddTimeout (TimeSpan.Zero, () => { Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); @@ -1062,7 +1062,7 @@ private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - Application.MainLoop!.AddIdle ( + Application.MainLoop!.TimedEvents.AddTimeout (TimeSpan.Zero, () => { Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag)); diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs index 0e10a99b9a..73308ae85e 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs @@ -73,7 +73,7 @@ bool IMainLoopDriver.EventsPending () #if HACK_CHECK_WINCHANGED _winChange.Set (); #endif - if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout)) + if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimers (out int waitTimeout)) { return true; } @@ -102,9 +102,9 @@ bool IMainLoopDriver.EventsPending () if (!_eventReadyTokenSource.IsCancellationRequested) { #if HACK_CHECK_WINCHANGED - return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _) || _winChanged; + return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimers (out _) || _winChanged; #else - return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _); + return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimers (out _); #endif } diff --git a/Terminal.Gui/Views/Menuv1/MenuBar.cs b/Terminal.Gui/Views/Menuv1/MenuBar.cs index b9d233e70e..ca339df68b 100644 --- a/Terminal.Gui/Views/Menuv1/MenuBar.cs +++ b/Terminal.Gui/Views/Menuv1/MenuBar.cs @@ -1045,7 +1045,7 @@ internal bool Run (Action? action) return false; } - Application.AddIdle ( + Application.AddTimeout (TimeSpan.Zero, () => { action (); diff --git a/Tests/UnitTests/Application/MainLoopTests.cs b/Tests/UnitTests/Application/MainLoopTests.cs index b40f362b08..804759e565 100644 --- a/Tests/UnitTests/Application/MainLoopTests.cs +++ b/Tests/UnitTests/Application/MainLoopTests.cs @@ -29,19 +29,19 @@ public class MainLoopTests // Idle Handler tests [Fact] - public void AddIdle_Adds_And_Removes () + public void AddTimeout_Adds_And_Removes () { var ml = new MainLoop (new FakeMainLoop ()); Func fnTrue = () => true; Func fnFalse = () => false; - ml.AddIdle (fnTrue); - ml.AddIdle (fnFalse); + ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnTrue); + ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnFalse); - Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count); - Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]); - Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers [0]); + Assert.Equal (2, ml.TimedEvents.Timeouts.Count); + Assert.Equal (fnTrue, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback); + Assert.NotEqual (fnFalse, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback); Assert.True (ml.TimedEvents.RemoveIdle (fnTrue)); Assert.Single (ml.TimedEvents.IdleHandlers); @@ -59,8 +59,8 @@ public void AddIdle_Adds_And_Removes () Assert.False (ml.TimedEvents.RemoveIdle (fnFalse)); // Add again, but with dupe - ml.AddIdle (fnTrue); - ml.AddIdle (fnTrue); + ml.AddTimeout (fnTrue); + ml.AddTimeout (fnTrue); Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count); Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]); @@ -83,7 +83,7 @@ public void AddIdle_Adds_And_Removes () } [Fact] - public void AddIdle_Function_GetsCalled_OnIteration () + public void AddTimeout_Function_GetsCalled_OnIteration () { var ml = new MainLoop (new FakeMainLoop ()); @@ -96,13 +96,13 @@ public void AddIdle_Function_GetsCalled_OnIteration () return true; }; - ml.AddIdle (fn); + ml.AddTimeout (fn); ml.RunIteration (); Assert.Equal (1, functionCalled); } [Fact] - public void AddIdle_Twice_Returns_False_Called_Twice () + public void AddTimeout_Twice_Returns_False_Called_Twice () { var ml = new MainLoop (new FakeMainLoop ()); @@ -130,9 +130,9 @@ public void AddIdle_Twice_Returns_False_Called_Twice () return true; }; - ml.AddIdle (fnStop); - ml.AddIdle (fn1); - ml.AddIdle (fn1); + ml.AddTimeout (fnStop); + ml.AddTimeout (fn1); + ml.AddTimeout (fn1); ml.Run (); Assert.True (ml.TimedEvents.RemoveIdle (fnStop)); Assert.False (ml.TimedEvents.RemoveIdle (fn1)); @@ -142,7 +142,7 @@ public void AddIdle_Twice_Returns_False_Called_Twice () } [Fact] - public void AddIdleTwice_Function_CalledTwice () + public void AddTimeoutTwice_Function_CalledTwice () { var ml = new MainLoop (new FakeMainLoop ()); @@ -155,8 +155,8 @@ public void AddIdleTwice_Function_CalledTwice () return true; }; - ml.AddIdle (fn); - ml.AddIdle (fn); + ml.AddTimeout (fn); + ml.AddTimeout (fn); ml.RunIteration (); Assert.Equal (2, functionCalled); Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count); @@ -189,7 +189,7 @@ public void AddThenRemoveIdle_Function_NotCalled () return true; }; - ml.AddIdle (fn); + ml.AddTimeout (fn); Assert.True (ml.TimedEvents.RemoveIdle (fn)); ml.RunIteration (); Assert.Equal (0, functionCalled); @@ -345,7 +345,7 @@ public void AddTimer_Remove_NotCalled () return true; }; - ml.AddIdle (fnStop); + ml.AddTimeout (fnStop); var callbackCount = 0; @@ -383,7 +383,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled () return true; }; - ml.AddIdle (fnStop); + ml.AddTimeout (fnStop); var callbackCount = 0; @@ -503,7 +503,7 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True () var ml = new MainLoop (new FakeMainLoop ()); Func fnTrue = () => true; - ml.AddIdle (fnTrue); + ml.AddTimeout (fnTrue); bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut); Assert.True (retVal); Assert.Equal (-1, waitTimeOut); @@ -578,8 +578,8 @@ public void False_Idle_Stops_It_Being_Called_Again () return true; }; - ml.AddIdle (fnStop); - ml.AddIdle (fn1); + ml.AddTimeout (fnStop); + ml.AddTimeout (fn1); ml.Run (); Assert.True (ml.TimedEvents.RemoveIdle (fnStop)); Assert.False (ml.TimedEvents.RemoveIdle (fn1)); @@ -602,8 +602,8 @@ public void Internal_Tests () } [Theory] - [MemberData (nameof (TestAddIdle))] - public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions ( + [MemberData (nameof (TestAddTimeout))] + public void Mainloop_Invoke_Or_AddTimeout_Can_Be_Used_For_Events_Or_Actions ( Action action, string pclickMe, string pcancel, @@ -722,14 +722,14 @@ public void Run_Runs_Idle_Stop_Stops_Idle () return true; }; - ml.AddIdle (fn); + ml.AddTimeout (fn); ml.Run (); Assert.True (ml.TimedEvents.RemoveIdle (fn)); Assert.Equal (10, functionCalled); } - public static IEnumerable TestAddIdle + public static IEnumerable TestAddTimeout { get { diff --git a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs index 228d9e5726..5c49822ff6 100644 --- a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs @@ -15,7 +15,7 @@ public class MainLoopDriverTests [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] - public void MainLoop_AddIdle_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType) + public void MainLoop_AddTimeout_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType) { var driver = (IConsoleDriver)Activator.CreateInstance (driverType); var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); @@ -29,7 +29,7 @@ bool IdleHandler () return false; } - Func token = mainLoop.AddIdle (IdleHandler); + var token = mainLoop.TimedEvents.AddTimeout(TimeSpan.Zero, IdleHandler); Assert.NotNull (token); Assert.False (idleHandlerInvoked); // Idle handler should not be invoked immediately @@ -87,8 +87,8 @@ Type mainLoopDriverType var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - mainLoop.AddIdle (() => false); - bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout); + mainLoop.TimedEvents.AddTimeout (TimeSpan.Zero, () => false); + bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout); Assert.True (result); Assert.Equal (-1, waitTimeout); @@ -102,7 +102,7 @@ Type mainLoopDriverType [InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))] //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))] - public void MainLoop_CheckTimersAndIdleHandlers_NoTimersOrIdleHandlers_ReturnsFalse ( + public void MainLoop_CheckTimers_NoTimersOrIdleHandlers_ReturnsFalse ( Type driverType, Type mainLoopDriverType ) @@ -111,7 +111,7 @@ Type mainLoopDriverType var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout); + bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout); Assert.False (result); Assert.Equal (-1, waitTimeout); @@ -135,7 +135,7 @@ Type mainLoopDriverType var mainLoop = new MainLoop (mainLoopDriver); mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); - bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout); + bool result = mainLoop.TimedEvents.CheckTimers(out int waitTimeout); Assert.True (result); Assert.True (waitTimeout >= 0); @@ -158,7 +158,6 @@ public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverTy // Check default values Assert.NotNull (mainLoop); Assert.Equal (mainLoopDriver, mainLoop.MainLoopDriver); - Assert.Empty (mainLoop.TimedEvents.IdleHandlers); Assert.Empty (mainLoop.TimedEvents.Timeouts); Assert.False (mainLoop.Running); @@ -168,7 +167,6 @@ public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverTy // TODO: It'd be nice if we could really verify IMainLoopDriver.TearDown was called // and that it was actually cleaned up. Assert.Null (mainLoop.MainLoopDriver); - Assert.Empty (mainLoop.TimedEvents.IdleHandlers); Assert.Empty (mainLoop.TimedEvents.Timeouts); Assert.False (mainLoop.Running); } @@ -186,7 +184,7 @@ public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - bool result = mainLoop.TimedEvents.RemoveIdle (() => false); + bool result = mainLoop.TimedEvents.RemoveTimeout("flibble"); Assert.False (result); mainLoop.Dispose (); @@ -207,8 +205,9 @@ public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type ma bool IdleHandler () { return false; } - Func token = mainLoop.AddIdle (IdleHandler); - bool result = mainLoop.TimedEvents.RemoveIdle (token); + + var token = mainLoop.TimedEvents.AddTimeout (TimeSpan.Zero, IdleHandler); + bool result = mainLoop.TimedEvents.RemoveTimeout (token); Assert.True (result); mainLoop.Dispose (); @@ -273,7 +272,7 @@ public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driver return false; }; - mainLoop.AddIdle (idleHandler); + mainLoop.TimedEvents.AddTimeout (TimeSpan.Zero, idleHandler); mainLoop.RunIteration (); // Run an iteration to process the idle handler Assert.True (idleHandlerInvoked); diff --git a/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs index 0a34417fac..564f32a420 100644 --- a/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs +++ b/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs @@ -401,7 +401,7 @@ public void InitRunShutdown_Generic_IdleForExit () v2.Init (); - v2.AddIdle (IdleExit); + v2.AddTimeout (IdleExit); Assert.Null (Application.Top); // Blocks until the timeout call is hit @@ -448,7 +448,7 @@ public void Shutdown_Closing_Closed_Raised () Assert.Same (t, a.Toplevel); }; - v2.AddIdle (IdleExit); + v2.AddTimeout (IdleExit); // Blocks until the timeout call is hit From 6f11fd61b2cecafd88df9feb6c62040e02100d4f Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 19 Jun 2025 20:46:13 +0100 Subject: [PATCH 22/56] Fix build of tests --- Tests/UnitTests/Application/MainLoopTests.cs | 102 +++++++----------- .../ConsoleDrivers/V2/ApplicationV2Tests.cs | 4 +- 2 files changed, 41 insertions(+), 65 deletions(-) diff --git a/Tests/UnitTests/Application/MainLoopTests.cs b/Tests/UnitTests/Application/MainLoopTests.cs index 804759e565..a5df8d4b2b 100644 --- a/Tests/UnitTests/Application/MainLoopTests.cs +++ b/Tests/UnitTests/Application/MainLoopTests.cs @@ -36,50 +36,27 @@ public void AddTimeout_Adds_And_Removes () Func fnTrue = () => true; Func fnFalse = () => false; - ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnTrue); - ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnFalse); + var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnTrue); + var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnFalse); Assert.Equal (2, ml.TimedEvents.Timeouts.Count); Assert.Equal (fnTrue, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback); Assert.NotEqual (fnFalse, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback); - Assert.True (ml.TimedEvents.RemoveIdle (fnTrue)); - Assert.Single (ml.TimedEvents.IdleHandlers); + Assert.True (ml.TimedEvents.RemoveTimeout (a)); + Assert.Single (ml.TimedEvents.Timeouts); // BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either // throw an exception in this case, or return an error. // No. Only need to return a boolean. - Assert.False (ml.TimedEvents.RemoveIdle (fnTrue)); + Assert.False (ml.TimedEvents.RemoveTimeout (a)); - Assert.True (ml.TimedEvents.RemoveIdle (fnFalse)); + Assert.True (ml.TimedEvents.RemoveTimeout (b)); // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either // throw an exception in this case, or return an error. // No. Only need to return a boolean. - Assert.False (ml.TimedEvents.RemoveIdle (fnFalse)); - - // Add again, but with dupe - ml.AddTimeout (fnTrue); - ml.AddTimeout (fnTrue); - - Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count); - Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]); - Assert.True (ml.TimedEvents.IdleHandlers [0] ()); - Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [1]); - Assert.True (ml.TimedEvents.IdleHandlers [1] ()); - - Assert.True (ml.TimedEvents.RemoveIdle (fnTrue)); - Assert.Single (ml.TimedEvents.IdleHandlers); - Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]); - Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers [0]); - - Assert.True (ml.TimedEvents.RemoveIdle (fnTrue)); - Assert.Empty (ml.TimedEvents.IdleHandlers); - - // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either - // throw an exception in this case, or return an error. - // No. Only need to return a boolean. - Assert.False (ml.TimedEvents.RemoveIdle (fnTrue)); + Assert.False (ml.TimedEvents.RemoveTimeout(b)); } [Fact] @@ -96,7 +73,7 @@ public void AddTimeout_Function_GetsCalled_OnIteration () return true; }; - ml.AddTimeout (fn); + ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); ml.RunIteration (); Assert.Equal (1, functionCalled); } @@ -130,15 +107,15 @@ public void AddTimeout_Twice_Returns_False_Called_Twice () return true; }; - ml.AddTimeout (fnStop); - ml.AddTimeout (fn1); - ml.AddTimeout (fn1); + var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); + var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn1); ml.Run (); - Assert.True (ml.TimedEvents.RemoveIdle (fnStop)); - Assert.False (ml.TimedEvents.RemoveIdle (fn1)); - Assert.False (ml.TimedEvents.RemoveIdle (fn1)); - Assert.Equal (2, functionCalled); + Assert.True (ml.TimedEvents.RemoveTimeout(a)); + Assert.False (ml.TimedEvents.RemoveTimeout (a)); + Assert.True (ml.TimedEvents.RemoveTimeout (b)); + + Assert.Equal (3, functionCalled); } [Fact] @@ -155,24 +132,24 @@ public void AddTimeoutTwice_Function_CalledTwice () return true; }; - ml.AddTimeout (fn); - ml.AddTimeout (fn); + var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); + var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); ml.RunIteration (); Assert.Equal (2, functionCalled); - Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count); + Assert.Equal (2, ml.TimedEvents.Timeouts.Count); functionCalled = 0; - Assert.True (ml.TimedEvents.RemoveIdle (fn)); - Assert.Single (ml.TimedEvents.IdleHandlers); + Assert.True (ml.TimedEvents.RemoveTimeout (a)); + Assert.Single (ml.TimedEvents.Timeouts); ml.RunIteration (); Assert.Equal (1, functionCalled); functionCalled = 0; - Assert.True (ml.TimedEvents.RemoveIdle (fn)); - Assert.Empty (ml.TimedEvents.IdleHandlers); + Assert.True (ml.TimedEvents.RemoveTimeout (b)); + Assert.Empty (ml.TimedEvents.Timeouts); ml.RunIteration (); Assert.Equal (0, functionCalled); - Assert.False (ml.TimedEvents.RemoveIdle (fn)); + Assert.False (ml.TimedEvents.RemoveTimeout (b)); } [Fact] @@ -189,8 +166,8 @@ public void AddThenRemoveIdle_Function_NotCalled () return true; }; - ml.AddTimeout (fn); - Assert.True (ml.TimedEvents.RemoveIdle (fn)); + var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); + Assert.True (ml.TimedEvents.RemoveTimeout (a)); ml.RunIteration (); Assert.Equal (0, functionCalled); } @@ -345,7 +322,7 @@ public void AddTimer_Remove_NotCalled () return true; }; - ml.AddTimeout (fnStop); + ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); var callbackCount = 0; @@ -383,7 +360,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled () return true; }; - ml.AddTimeout (fnStop); + ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); var callbackCount = 0; @@ -492,7 +469,7 @@ public void AddTimer_Run_CalledTwiceApproximatelyRightTime () public void CheckTimersAndIdleHandlers_NoTimers_Returns_False () { var ml = new MainLoop (new FakeMainLoop ()); - bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut); + bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut); Assert.False (retVal); Assert.Equal (-1, waitTimeOut); } @@ -503,8 +480,8 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True () var ml = new MainLoop (new FakeMainLoop ()); Func fnTrue = () => true; - ml.AddTimeout (fnTrue); - bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut); + ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnTrue); + bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut); Assert.True (retVal); Assert.Equal (-1, waitTimeOut); } @@ -518,7 +495,7 @@ public void CheckTimersAndIdleHandlers_With1Timer_Returns_Timer () static bool Callback () { return false; } _ = ml.TimedEvents.AddTimeout (ms, Callback); - bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut); + bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut); Assert.True (retVal); @@ -536,7 +513,7 @@ public void CheckTimersAndIdleHandlers_With2Timers_Returns_Timer () _ = ml.TimedEvents.AddTimeout (ms, Callback); _ = ml.TimedEvents.AddTimeout (ms, Callback); - bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut); + bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut); Assert.True (retVal); @@ -578,11 +555,11 @@ public void False_Idle_Stops_It_Being_Called_Again () return true; }; - ml.AddTimeout (fnStop); - ml.AddTimeout (fn1); + var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); + var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn1); ml.Run (); - Assert.True (ml.TimedEvents.RemoveIdle (fnStop)); - Assert.False (ml.TimedEvents.RemoveIdle (fn1)); + Assert.True (ml.TimedEvents.RemoveTimeout (a)); + Assert.False (ml.TimedEvents.RemoveTimeout (a)); Assert.Equal (10, functionCalled); Assert.Equal (20, stopCount); @@ -594,7 +571,6 @@ public void Internal_Tests () var testMainloop = new TestMainloop (); var mainloop = new MainLoop (testMainloop); Assert.Empty (mainloop.TimedEvents.Timeouts); - Assert.Empty (mainloop.TimedEvents.IdleHandlers); Assert.NotNull ( new App.Timeout { Span = new (), Callback = () => true } @@ -698,7 +674,7 @@ public void RemoveIdle_Function_NotCalled () return true; }; - Assert.False (ml.TimedEvents.RemoveIdle (fn)); + Assert.False (ml.TimedEvents.RemoveTimeout ("flibble")); ml.RunIteration (); Assert.Equal (0, functionCalled); } @@ -722,9 +698,9 @@ public void Run_Runs_Idle_Stop_Stops_Idle () return true; }; - ml.AddTimeout (fn); + var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); ml.Run (); - Assert.True (ml.TimedEvents.RemoveIdle (fn)); + Assert.True (ml.TimedEvents.RemoveTimeout (a)); Assert.Equal (10, functionCalled); } diff --git a/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs index 564f32a420..d0165a45ae 100644 --- a/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs +++ b/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs @@ -401,7 +401,7 @@ public void InitRunShutdown_Generic_IdleForExit () v2.Init (); - v2.AddTimeout (IdleExit); + v2.AddTimeout (TimeSpan.Zero, IdleExit); Assert.Null (Application.Top); // Blocks until the timeout call is hit @@ -448,7 +448,7 @@ public void Shutdown_Closing_Closed_Raised () Assert.Same (t, a.Toplevel); }; - v2.AddTimeout (IdleExit); + v2.AddTimeout(TimeSpan.Zero, IdleExit); // Blocks until the timeout call is hit From c59745442d174ac0d808bffd714812cc24c8827d Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 20 Jun 2025 03:04:50 +0100 Subject: [PATCH 23/56] Fix unit tests --- Terminal.Gui/App/ApplicationImpl.cs | 6 ++++++ Tests/UnitTests/Views/SpinnerViewTests.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index 7cd36eee4b..36c21f36c5 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -261,6 +261,12 @@ public virtual void RequestStop (Toplevel? top) /// public virtual void Invoke (Action action) { + if (Application.MainLoop == null) + { + Logging.Warning ("Ignored Invoke because MainLoop is not initialized yet"); + return; + } + Application.AddTimeout (TimeSpan.Zero, () => { diff --git a/Tests/UnitTests/Views/SpinnerViewTests.cs b/Tests/UnitTests/Views/SpinnerViewTests.cs index 24dc88a422..6382f82971 100644 --- a/Tests/UnitTests/Views/SpinnerViewTests.cs +++ b/Tests/UnitTests/Views/SpinnerViewTests.cs @@ -106,6 +106,9 @@ private SpinnerView GetSpinnerView () top.Add (view); Application.Begin (top); + // Required to clear the initial 'Invoke nothing' that Begin does + Application.MainLoop.TimedEvents.Timeouts.Clear (); + Assert.Equal (1, view.Frame.Width); Assert.Equal (1, view.Frame.Height); From aba2e43a45d11f2f9aea9b6cc6ed37aba139274a Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 20 Jun 2025 04:05:39 +0100 Subject: [PATCH 24/56] Add wakeup call back in --- Terminal.Gui/App/MainLoopSyncContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Terminal.Gui/App/MainLoopSyncContext.cs b/Terminal.Gui/App/MainLoopSyncContext.cs index 69ddc9af1b..45fc14af7a 100644 --- a/Terminal.Gui/App/MainLoopSyncContext.cs +++ b/Terminal.Gui/App/MainLoopSyncContext.cs @@ -18,6 +18,7 @@ public override void Post (SendOrPostCallback d, object state) return false; } ); + Application.MainLoop?.Wakeup (); } //_mainLoop.Driver.Wakeup (); From 2dfe6b3c30d20a16f54aff4d61801d7332aedbb0 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 20 Jun 2025 04:47:14 +0100 Subject: [PATCH 25/56] Comment out incredibly complicated test and fix others --- Tests/UnitTests/Application/MainLoopTests.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Tests/UnitTests/Application/MainLoopTests.cs b/Tests/UnitTests/Application/MainLoopTests.cs index a5df8d4b2b..2d867cca00 100644 --- a/Tests/UnitTests/Application/MainLoopTests.cs +++ b/Tests/UnitTests/Application/MainLoopTests.cs @@ -113,9 +113,11 @@ public void AddTimeout_Twice_Returns_False_Called_Twice () Assert.True (ml.TimedEvents.RemoveTimeout(a)); Assert.False (ml.TimedEvents.RemoveTimeout (a)); - Assert.True (ml.TimedEvents.RemoveTimeout (b)); - Assert.Equal (3, functionCalled); + // Cannot remove b because it returned false i.e. auto removes itself + Assert.False (ml.TimedEvents.RemoveTimeout (b)); + + Assert.Equal (1, functionCalled); } [Fact] @@ -483,7 +485,7 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True () ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnTrue); bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut); Assert.True (retVal); - Assert.Equal (-1, waitTimeOut); + Assert.Equal (0, waitTimeOut); } [Fact] @@ -576,7 +578,7 @@ public void Internal_Tests () new App.Timeout { Span = new (), Callback = () => true } ); } - + /* [Theory] [MemberData (nameof (TestAddTimeout))] public void Mainloop_Invoke_Or_AddTimeout_Can_Be_Used_For_Events_Or_Actions ( @@ -659,7 +661,7 @@ int pfour Application.Shutdown (); } - + */ [Fact] public void RemoveIdle_Function_NotCalled () { From f18b7521f537c5af830f6bb4412c5fac8b68cbae Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 20 Jun 2025 04:51:54 +0100 Subject: [PATCH 26/56] Fix test --- Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs index 5c49822ff6..26f083a29a 100644 --- a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs @@ -91,7 +91,7 @@ Type mainLoopDriverType bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout); Assert.True (result); - Assert.Equal (-1, waitTimeout); + Assert.Equal (0, waitTimeout); mainLoop.Dispose (); } @@ -114,7 +114,7 @@ Type mainLoopDriverType bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout); Assert.False (result); - Assert.Equal (-1, waitTimeout); + Assert.Equal (0, waitTimeout); mainLoop.Dispose (); } From 34bc316128a89de7fd1e77d8808c252262b01f57 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 20 Jun 2025 04:54:23 +0100 Subject: [PATCH 27/56] test fix --- Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs index 26f083a29a..0ee2662c6a 100644 --- a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs @@ -114,7 +114,7 @@ Type mainLoopDriverType bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout); Assert.False (result); - Assert.Equal (0, waitTimeout); + Assert.Equal (-1, waitTimeout); mainLoop.Dispose (); } From 0016a1892290a754497fab0a06ece8393b7e4642 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 20 Jun 2025 05:00:45 +0100 Subject: [PATCH 28/56] Make Post execute immediately if already on UI thread --- Terminal.Gui/App/MainLoopSyncContext.cs | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Terminal.Gui/App/MainLoopSyncContext.cs b/Terminal.Gui/App/MainLoopSyncContext.cs index 45fc14af7a..69b673fcea 100644 --- a/Terminal.Gui/App/MainLoopSyncContext.cs +++ b/Terminal.Gui/App/MainLoopSyncContext.cs @@ -10,15 +10,25 @@ internal sealed class MainLoopSyncContext : SynchronizationContext public override void Post (SendOrPostCallback d, object state) { - Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, - () => - { - d (state); - return false; - } - ); - Application.MainLoop?.Wakeup (); + // If we are already on the main UI thread + if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId) + { + d (state); + } + else + { + // Queue the task + Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, + () => + { + d (state); + + return false; + } + ); + Application.MainLoop?.Wakeup (); + } } //_mainLoop.Driver.Wakeup (); From e85cccd2e8d61f3ed9ef4e25bb73f1341414b807 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 20 Jun 2025 19:38:17 +0100 Subject: [PATCH 29/56] Re enable test and simplify Invoke to just execute if in UI thread (up front) --- Terminal.Gui/App/ApplicationImpl.cs | 9 +++++++ Terminal.Gui/App/MainLoopSyncContext.cs | 27 +++++++------------- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 7 +++++ Tests/UnitTests/Application/MainLoopTests.cs | 4 +-- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index 36c21f36c5..fc16b53552 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -261,12 +261,21 @@ public virtual void RequestStop (Toplevel? top) /// public virtual void Invoke (Action action) { + + // If we are already on the main UI thread + if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId) + { + action (); + return; + } + if (Application.MainLoop == null) { Logging.Warning ("Ignored Invoke because MainLoop is not initialized yet"); return; } + Application.AddTimeout (TimeSpan.Zero, () => { diff --git a/Terminal.Gui/App/MainLoopSyncContext.cs b/Terminal.Gui/App/MainLoopSyncContext.cs index 69b673fcea..200ecb34ed 100644 --- a/Terminal.Gui/App/MainLoopSyncContext.cs +++ b/Terminal.Gui/App/MainLoopSyncContext.cs @@ -10,25 +10,16 @@ internal sealed class MainLoopSyncContext : SynchronizationContext public override void Post (SendOrPostCallback d, object state) { + // Queue the task + Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, + () => + { + d (state); - // If we are already on the main UI thread - if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId) - { - d (state); - } - else - { - // Queue the task - Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, - () => - { - d (state); - - return false; - } - ); - Application.MainLoop?.Wakeup (); - } + return false; + } + ); + Application.MainLoop?.Wakeup (); } //_mainLoop.Driver.Wakeup (); diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index 9ddbcfe722..2162e7db00 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -225,6 +225,13 @@ public override void RequestStop (Toplevel? top) /// public override void Invoke (Action action) { + // If we are already on the main UI thread + if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId) + { + action (); + return; + } + _timedEvents.AddTimeout (TimeSpan.Zero, () => { diff --git a/Tests/UnitTests/Application/MainLoopTests.cs b/Tests/UnitTests/Application/MainLoopTests.cs index 2d867cca00..ee6dbffbc4 100644 --- a/Tests/UnitTests/Application/MainLoopTests.cs +++ b/Tests/UnitTests/Application/MainLoopTests.cs @@ -578,7 +578,7 @@ public void Internal_Tests () new App.Timeout { Span = new (), Callback = () => true } ); } - /* + [Theory] [MemberData (nameof (TestAddTimeout))] public void Mainloop_Invoke_Or_AddTimeout_Can_Be_Used_For_Events_Or_Actions ( @@ -661,7 +661,7 @@ int pfour Application.Shutdown (); } - */ + [Fact] public void RemoveIdle_Function_NotCalled () { From e4c7f0faec4452f5ed5660dad4bfe843956fe81a Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 04:40:48 +0100 Subject: [PATCH 30/56] Remove xml doc references to idles --- Terminal.Gui/App/Application.Run.cs | 2 +- Terminal.Gui/App/ApplicationImpl.cs | 2 +- Terminal.Gui/App/IApplication.cs | 2 +- Terminal.Gui/App/MainLoop.cs | 6 +++--- Terminal.Gui/App/TimedEvents.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index d20336e5f8..12bcb6cdb4 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -366,7 +366,7 @@ public static T Run (Func? errorHandler = null, IConsoleDriv /// Alternatively, to have a program control the main loop and process events manually, call /// to set things up manually and then repeatedly call /// with the wait parameter set to false. By doing this the - /// method will only process any pending events, timers, idle handlers and then + /// method will only process any pending events, timers handlers and then /// return control immediately. /// /// diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index fc16b53552..ee6db8b2c1 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -119,7 +119,7 @@ public virtual T Run (Func? errorHandler = null, IConsoleDri /// Alternatively, to have a program control the main loop and process events manually, call /// to set things up manually and then repeatedly call /// with the wait parameter set to false. By doing this the - /// method will only process any pending events, timers, idle handlers and then + /// method will only process any pending events, timers handlers and then /// return control immediately. /// /// When using or diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index 4c4ad97a8b..ef2e843e91 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -106,7 +106,7 @@ public T Run (Func? errorHandler = null, IConsoleDriver? dri /// Alternatively, to have a program control the main loop and process events manually, call /// to set things up manually and then repeatedly call /// with the wait parameter set to false. By doing this the - /// method will only process any pending events, timers, idle handlers and then + /// method will only process any pending events, timers handlers and then /// return control immediately. /// /// When using or diff --git a/Terminal.Gui/App/MainLoop.cs b/Terminal.Gui/App/MainLoop.cs index 83beb3f848..6a9d435460 100644 --- a/Terminal.Gui/App/MainLoop.cs +++ b/Terminal.Gui/App/MainLoop.cs @@ -32,7 +32,7 @@ internal interface IMainLoopDriver void Wakeup (); } -/// The MainLoop monitors timers and idle handlers. +/// The MainLoop monitors timers handlers. /// /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this /// on Windows. @@ -40,7 +40,7 @@ internal interface IMainLoopDriver public class MainLoop : IDisposable { /// - /// Gets the class responsible for handling idles and timeouts + /// Gets the class responsible for handling timeouts /// public ITimedEvents TimedEvents { get; } = new TimedEvents(); @@ -101,7 +101,7 @@ internal void Run () /// Runs one iteration of timers and file watches /// - /// Use this to process all pending events (timers, idle handlers and file watches). + /// Use this to process all pending events (timers handlers and file watches). /// /// while (main.EventsPending ()) RunIteration (); /// diff --git a/Terminal.Gui/App/TimedEvents.cs b/Terminal.Gui/App/TimedEvents.cs index 225865d62b..76c8e103c4 100644 --- a/Terminal.Gui/App/TimedEvents.cs +++ b/Terminal.Gui/App/TimedEvents.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui.App; /// -/// Handles timeouts and idles +/// Handles timeouts /// public class TimedEvents : ITimedEvents { From e13ed63731f9213bb03be58323968250bdd5efbe Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 04:48:27 +0100 Subject: [PATCH 31/56] Remove more references to idles --- Terminal.Gui/App/ITimedEvents.cs | 2 +- Terminal.Gui/Drivers/V2/IMainLoop.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/App/ITimedEvents.cs b/Terminal.Gui/App/ITimedEvents.cs index a590e74090..42459d2935 100644 --- a/Terminal.Gui/App/ITimedEvents.cs +++ b/Terminal.Gui/App/ITimedEvents.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui.App; /// -/// Manages timers and idles +/// Manages timers /// public interface ITimedEvents { diff --git a/Terminal.Gui/Drivers/V2/IMainLoop.cs b/Terminal.Gui/Drivers/V2/IMainLoop.cs index 2638e4074b..460acb5745 100644 --- a/Terminal.Gui/Drivers/V2/IMainLoop.cs +++ b/Terminal.Gui/Drivers/V2/IMainLoop.cs @@ -10,7 +10,7 @@ namespace Terminal.Gui.Drivers; public interface IMainLoop : IDisposable { /// - /// Gets the class responsible for servicing user timeouts and idles + /// Gets the class responsible for servicing user timeouts /// public ITimedEvents TimedEvents { get; } From c588e04912a29b8a146c2b885b0e4dd19133ab5a Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 05:03:34 +0100 Subject: [PATCH 32/56] Make Screen initialization threadsafe --- Terminal.Gui/App/Application.Screen.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/App/Application.Screen.cs b/Terminal.Gui/App/Application.Screen.cs index bd57585eab..c486477db7 100644 --- a/Terminal.Gui/App/Application.Screen.cs +++ b/Terminal.Gui/App/Application.Screen.cs @@ -4,6 +4,7 @@ namespace Terminal.Gui.App; public static partial class Application // Screen related stuff { + private static readonly object _lock = new (); private static Rectangle? _screen; /// @@ -18,11 +19,15 @@ public static Rectangle Screen { get { - if (_screen == null) + lock (_lock) { - _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048)); + if (_screen == null) + { + _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048)); + } + + return _screen.Value; } - return _screen.Value; } set { @@ -30,7 +35,11 @@ public static Rectangle Screen { throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported"); } - _screen = value; + + lock (_lock) + { + _screen = value; + } } } From 49dc89768d8c9b2bfc19a3babe95c88036b1e17a Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 06:00:39 +0100 Subject: [PATCH 33/56] Add more exciting timeouts --- Examples/UICatalog/Scenarios/Threading.cs | 88 +++++++++++++++++++ Terminal.Gui/App/ITimedEvents.cs | 3 + Terminal.Gui/App/LogarithmicTimeout.cs | 43 +++++++++ Terminal.Gui/App/SmoothAcceleratingTimeout.cs | 58 ++++++++++++ Terminal.Gui/App/TimedEvents.cs | 7 ++ Terminal.Gui/App/Timeout.cs | 7 +- 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 Terminal.Gui/App/LogarithmicTimeout.cs create mode 100644 Terminal.Gui/App/SmoothAcceleratingTimeout.cs diff --git a/Examples/UICatalog/Scenarios/Threading.cs b/Examples/UICatalog/Scenarios/Threading.cs index 1adde0320f..a9d8ea0ea1 100644 --- a/Examples/UICatalog/Scenarios/Threading.cs +++ b/Examples/UICatalog/Scenarios/Threading.cs @@ -19,6 +19,16 @@ public class Threading : Scenario private ListView _logJob; private Action _sync; + private LogarithmicTimeout _logarithmicTimeout; + private NumericUpDown _numberLog; + private Button _btnLogarithmic; + private object _timeoutObj; + + private SmoothAcceleratingTimeout _smoothTimeout; + private NumericUpDown _numberSmooth; + private Button _btnSmooth; + private object _timeoutObjSmooth; + public override void Main () { Application.Init (); @@ -82,6 +92,35 @@ public override void Main () var text = new TextField { X = 1, Y = 3, Width = 100, Text = "Type anything after press the button" }; + _btnLogarithmic = new Button () + { + X = 50, + Y = 4, + Text = "Start Log Counter" + }; + _btnLogarithmic.Accepting += StartStopLogTimeout; + + _numberLog = new NumericUpDown () + { + X = Pos.Right (_btnLogarithmic), + Y = 4, + }; + + _btnSmooth = new Button () + { + X = Pos.Right (_numberLog), + Y = 4, + Text = "Start Smooth Counter" + }; + _btnSmooth.Accepting += StartStopSmoothTimeout; + + _numberSmooth = new NumericUpDown () + { + X = Pos.Right (_btnSmooth), + Y = 4, + }; + + var btnAction = new Button { X = 80, Y = 10, Text = "Load Data Action" }; btnAction.Accepting += (s, e) => _action.Invoke (); var btnLambda = new Button { X = 80, Y = 12, Text = "Load Data Lambda" }; @@ -107,6 +146,10 @@ public override void Main () _btnActionCancel, _logJob, text, + _btnLogarithmic, + _numberLog, + _btnSmooth, + _numberSmooth, btnAction, btnLambda, btnHandler, @@ -129,6 +172,51 @@ void Win_Loaded (object sender, EventArgs args) Application.Shutdown (); } + private bool LogTimeout () + { + _numberLog.Value++; + _logarithmicTimeout.AdvanceStage (); + return true; + } + private bool SmoothTimeout () + { + _numberSmooth.Value++; + _smoothTimeout.AdvanceStage (); + return true; + } + + private void StartStopLogTimeout (object sender, CommandEventArgs e) + { + if (_timeoutObj != null) + { + _btnLogarithmic.Text = "Start Log Counter"; + Application.TimedEvents.RemoveTimeout (_timeoutObj); + _timeoutObj = null; + } + else + { + _btnLogarithmic.Text = "Stop Log Counter"; + _logarithmicTimeout = new LogarithmicTimeout (TimeSpan.FromMilliseconds (500), LogTimeout); + _timeoutObj = Application.TimedEvents.AddTimeout (_logarithmicTimeout); + } + } + + private void StartStopSmoothTimeout (object sender, CommandEventArgs e) + { + if (_timeoutObjSmooth != null) + { + _btnSmooth.Text = "Start Smooth Counter"; + Application.TimedEvents.RemoveTimeout (_timeoutObjSmooth); + _timeoutObjSmooth = null; + } + else + { + _btnSmooth.Text = "Stop Smooth Counter"; + _smoothTimeout = new SmoothAcceleratingTimeout (TimeSpan.FromMilliseconds (500), TimeSpan.FromMilliseconds (50), 0.5, SmoothTimeout); + _timeoutObjSmooth = Application.TimedEvents.AddTimeout (_smoothTimeout); + } + } + private async void CallLoadItemsAsync () { _cancellationTokenSource = new CancellationTokenSource (); diff --git a/Terminal.Gui/App/ITimedEvents.cs b/Terminal.Gui/App/ITimedEvents.cs index 42459d2935..591c8e3694 100644 --- a/Terminal.Gui/App/ITimedEvents.cs +++ b/Terminal.Gui/App/ITimedEvents.cs @@ -21,6 +21,9 @@ public interface ITimedEvents /// object AddTimeout (TimeSpan time, Func callback); + /// + object AddTimeout (Timeout timeout); + /// Removes a previously scheduled timeout /// The token parameter is the value returned by AddTimeout. /// diff --git a/Terminal.Gui/App/LogarithmicTimeout.cs b/Terminal.Gui/App/LogarithmicTimeout.cs new file mode 100644 index 0000000000..cd27050800 --- /dev/null +++ b/Terminal.Gui/App/LogarithmicTimeout.cs @@ -0,0 +1,43 @@ +namespace Terminal.Gui.App; + +/// Implements a logarithmic increasing timeout. +public class LogarithmicTimeout : Timeout +{ + private int stage = 0; + private readonly TimeSpan baseDelay; + + public LogarithmicTimeout (TimeSpan baseDelay, Func callback) + { + this.baseDelay = baseDelay; + this.Callback = callback; + } + + /// Gets the current calculated Span based on the stage. + public override TimeSpan Span + { + get + { + // For stage 0, return base delay directly + if (stage == 0) + { + return baseDelay; + } + + // Calculate logarithmic increase + double multiplier = Math.Log (stage + 1); // ln(stage + 1) + return TimeSpan.FromMilliseconds (baseDelay.TotalMilliseconds * multiplier); + } + } + + /// Increments the stage to increase the timeout. + public void AdvanceStage () + { + stage++; + } + + /// Resets the stage back to zero. + public void Reset () + { + stage = 0; + } +} \ No newline at end of file diff --git a/Terminal.Gui/App/SmoothAcceleratingTimeout.cs b/Terminal.Gui/App/SmoothAcceleratingTimeout.cs new file mode 100644 index 0000000000..792a0835bd --- /dev/null +++ b/Terminal.Gui/App/SmoothAcceleratingTimeout.cs @@ -0,0 +1,58 @@ +namespace Terminal.Gui.App; + +/// +/// Timeout which accelerates slowly at first then fast up to a maximum speed. +/// Use to increment the stage of the timer (e.g. in +/// your timer callback code). +/// +public class SmoothAcceleratingTimeout : Timeout +{ + private int stage = 0; + private readonly TimeSpan initialDelay; + private readonly TimeSpan minDelay; + private readonly double decayFactor; + + /// + /// Creates a new instance of the smooth acceleration timeout. + /// + /// Delay before first tick, the longest it will ever take + /// The fastest the timer can get no matter how long it runs + /// Controls how fast the timer accelerates + /// Method to call when timer ticks + public SmoothAcceleratingTimeout (TimeSpan initialDelay, TimeSpan minDelay, double decayFactor, Func callback) + { + this.initialDelay = initialDelay; + this.minDelay = minDelay; + this.decayFactor = decayFactor; + this.Callback = callback; + } + + /// + public override TimeSpan Span + { + get + { + double initialMs = initialDelay.TotalMilliseconds; + double minMs = minDelay.TotalMilliseconds; + double delayMs = minMs + (initialMs - minMs) * Math.Pow (decayFactor, stage); + return TimeSpan.FromMilliseconds (delayMs); + } + } + + /// + /// Advances the timer stage, this should be called from your timer callback or whenever + /// you want to advance the speed. + /// + public void AdvanceStage () + { + stage++; + } + + /// + /// Resets the timer to original speed. + /// + public void Reset () + { + stage = 0; + } +} diff --git a/Terminal.Gui/App/TimedEvents.cs b/Terminal.Gui/App/TimedEvents.cs index 76c8e103c4..37466ecc99 100644 --- a/Terminal.Gui/App/TimedEvents.cs +++ b/Terminal.Gui/App/TimedEvents.cs @@ -141,6 +141,13 @@ public object AddTimeout (TimeSpan time, Func callback) return timeout; } + /// + public object AddTimeout (Timeout timeout) + { + AddTimeout (timeout.Span, timeout); + return timeout; + } + /// public bool CheckTimers(out int waitTimeout) { diff --git a/Terminal.Gui/App/Timeout.cs b/Terminal.Gui/App/Timeout.cs index 615ca2d9f6..966243d837 100644 --- a/Terminal.Gui/App/Timeout.cs +++ b/Terminal.Gui/App/Timeout.cs @@ -7,12 +7,13 @@ namespace Terminal.Gui.App; + /// Provides data for timers running manipulation. -public sealed class Timeout +public class Timeout { /// The function that will be invoked. public Func Callback; /// Time to wait before invoke the callback. - public TimeSpan Span; -} + public virtual TimeSpan Span { get; set; } +} \ No newline at end of file From 394794ae36a5c8e999736dbc4c5c1f4aaa5befc2 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 06:12:47 +0100 Subject: [PATCH 34/56] WIP add tests --- Terminal.Gui/App/LogarithmicTimeout.cs | 2 +- .../Application/LogarithmicTimeoutTests.cs | 81 +++++++++++++++++++ .../SmoothAcceleratingTimeoutTests.cs | 70 ++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs create mode 100644 Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs diff --git a/Terminal.Gui/App/LogarithmicTimeout.cs b/Terminal.Gui/App/LogarithmicTimeout.cs index cd27050800..5a6695efb4 100644 --- a/Terminal.Gui/App/LogarithmicTimeout.cs +++ b/Terminal.Gui/App/LogarithmicTimeout.cs @@ -24,7 +24,7 @@ public override TimeSpan Span } // Calculate logarithmic increase - double multiplier = Math.Log (stage + 1); // ln(stage + 1) + double multiplier = 1 + Math.Log (stage + 1); // ln(stage + 1) return TimeSpan.FromMilliseconds (baseDelay.TotalMilliseconds * multiplier); } } diff --git a/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs new file mode 100644 index 0000000000..139b66103a --- /dev/null +++ b/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs @@ -0,0 +1,81 @@ +namespace Terminal.Gui.ApplicationTests; + +public class LogarithmicTimeoutTests +{ + [Fact] + public void Span_Should_Return_BaseDelay_When_Stage_Is_Zero () + { + var baseDelay = TimeSpan.FromMilliseconds (1000); + var timeout = new LogarithmicTimeout (baseDelay, () => true); + + Assert.Equal (baseDelay, timeout.Span); + } + + [Fact] + public void Span_Should_Increase_Logarithmically () + { + var baseDelay = TimeSpan.FromMilliseconds (1000); + var timeout = new LogarithmicTimeout (baseDelay, () => true); + + var stage0 = timeout.Span; + + timeout.AdvanceStage (); // stage = 1 + var stage1 = timeout.Span; + + timeout.AdvanceStage (); // stage = 2 + var stage2 = timeout.Span; + + timeout.AdvanceStage (); // stage = 3 + var stage3 = timeout.Span; + + Assert.True (stage1 > stage0, "Stage 1 should be greater than stage 0"); + Assert.True (stage2 > stage1, "Stage 2 should be greater than stage 1"); + Assert.True (stage3 > stage2, "Stage 3 should be greater than stage 2"); + } + + [Theory] + [MemberData (nameof (GetLogarithmicTestData))] + public void Span_Should_Match_Expected_Logarithmic_Value ( + double baseDelayMs, int stage, double expectedMs) + { + var baseDelay = TimeSpan.FromMilliseconds (baseDelayMs); + var timeout = new LogarithmicTimeout (baseDelay, () => true); + + for (int i = 0; i < stage; i++) + { + timeout.AdvanceStage (); + } + + double actualMs = timeout.Span.TotalMilliseconds; + double tolerance = 0.001; // Allow minor rounding error + + Assert.InRange (actualMs, expectedMs - tolerance, expectedMs + tolerance); + } + + public static IEnumerable GetLogarithmicTestData () + { + // baseDelayMs, stage, expectedSpanMs + double baseMs = 1000; + + yield return new object [] { baseMs, 0, baseMs }; + yield return new object [] { baseMs, 1, baseMs * Math.Log (2) }; + yield return new object [] { baseMs, 2, baseMs * Math.Log (3) }; + yield return new object [] { baseMs, 5, baseMs * Math.Log (6) }; + yield return new object [] { baseMs, 10, baseMs * Math.Log (11) }; + } + + + [Fact] + public void Reset_Should_Set_Stage_Back_To_Zero () + { + var baseDelay = TimeSpan.FromMilliseconds (1000); + var timeout = new LogarithmicTimeout (baseDelay, () => true); + + timeout.AdvanceStage (); + timeout.AdvanceStage (); + Assert.NotEqual (baseDelay, timeout.Span); + + timeout.Reset (); + Assert.Equal (baseDelay, timeout.Span); + } +} \ No newline at end of file diff --git a/Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs new file mode 100644 index 0000000000..c6fd8b91c9 --- /dev/null +++ b/Tests/UnitTestsParallelizable/Application/SmoothAcceleratingTimeoutTests.cs @@ -0,0 +1,70 @@ +namespace Terminal.Gui.ApplicationTests; + + +public class SmoothAcceleratingTimeoutTests +{ + [Fact] + public void Span_Should_Return_InitialDelay_On_StageZero () + { + var initialDelay = TimeSpan.FromMilliseconds (500); + var minDelay = TimeSpan.FromMilliseconds (50); + double decayFactor = 0.7; + + var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true); + + Assert.Equal (initialDelay, timeout.Span); + } + + [Fact] + public void Span_Should_Decrease_As_Stage_Increases () + { + var initialDelay = TimeSpan.FromMilliseconds (500); + var minDelay = TimeSpan.FromMilliseconds (50); + double decayFactor = 0.7; + + var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true); + + var previousSpan = timeout.Span; + for (int i = 0; i < 10; i++) + { + timeout.AdvanceStage (); + var currentSpan = timeout.Span; + Assert.True (currentSpan <= previousSpan, $"Stage {i + 1}: {currentSpan} should be <= {previousSpan}"); + previousSpan = currentSpan; + } + } + + [Fact] + public void Span_Should_Not_Go_Below_MinDelay () + { + var initialDelay = TimeSpan.FromMilliseconds (500); + var minDelay = TimeSpan.FromMilliseconds (50); + double decayFactor = 0.5; + + var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true); + + for (int i = 0; i < 100; i++) + { + timeout.AdvanceStage (); + } + + Assert.Equal (minDelay, timeout.Span); + } + + [Fact] + public void Reset_Should_Set_Stage_Back_To_Zero () + { + var initialDelay = TimeSpan.FromMilliseconds (500); + var minDelay = TimeSpan.FromMilliseconds (50); + double decayFactor = 0.7; + + var timeout = new SmoothAcceleratingTimeout (initialDelay, minDelay, decayFactor, () => true); + + timeout.AdvanceStage (); + timeout.AdvanceStage (); + Assert.NotEqual (initialDelay, timeout.Span); + + timeout.Reset (); + Assert.Equal (initialDelay, timeout.Span); + } +} From 308b26f431ec89e083b4a8e1612aba84e42c997a Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 07:46:25 +0100 Subject: [PATCH 35/56] fix log --- Terminal.Gui/App/LogarithmicTimeout.cs | 8 +------- .../Application/LogarithmicTimeoutTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/App/LogarithmicTimeout.cs b/Terminal.Gui/App/LogarithmicTimeout.cs index 5a6695efb4..590e659878 100644 --- a/Terminal.Gui/App/LogarithmicTimeout.cs +++ b/Terminal.Gui/App/LogarithmicTimeout.cs @@ -17,14 +17,8 @@ public override TimeSpan Span { get { - // For stage 0, return base delay directly - if (stage == 0) - { - return baseDelay; - } - // Calculate logarithmic increase - double multiplier = 1 + Math.Log (stage + 1); // ln(stage + 1) + double multiplier = Math.Log (stage + 1); // ln(stage + 1) return TimeSpan.FromMilliseconds (baseDelay.TotalMilliseconds * multiplier); } } diff --git a/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs index 139b66103a..99ccad60cc 100644 --- a/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs +++ b/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs @@ -8,7 +8,7 @@ public void Span_Should_Return_BaseDelay_When_Stage_Is_Zero () var baseDelay = TimeSpan.FromMilliseconds (1000); var timeout = new LogarithmicTimeout (baseDelay, () => true); - Assert.Equal (baseDelay, timeout.Span); + Assert.Equal (TimeSpan.Zero, timeout.Span); } [Fact] @@ -57,7 +57,7 @@ public static IEnumerable GetLogarithmicTestData () // baseDelayMs, stage, expectedSpanMs double baseMs = 1000; - yield return new object [] { baseMs, 0, baseMs }; + yield return new object [] { baseMs, 0, 0 }; yield return new object [] { baseMs, 1, baseMs * Math.Log (2) }; yield return new object [] { baseMs, 2, baseMs * Math.Log (3) }; yield return new object [] { baseMs, 5, baseMs * Math.Log (6) }; From e20b489112a4df83dc179c0e7fb7d8de69825ae2 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 07:47:50 +0100 Subject: [PATCH 36/56] fix test --- .../Application/LogarithmicTimeoutTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs index 99ccad60cc..bb95dbc6d7 100644 --- a/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs +++ b/Tests/UnitTestsParallelizable/Application/LogarithmicTimeoutTests.cs @@ -76,6 +76,6 @@ public void Reset_Should_Set_Stage_Back_To_Zero () Assert.NotEqual (baseDelay, timeout.Span); timeout.Reset (); - Assert.Equal (baseDelay, timeout.Span); + Assert.Equal (TimeSpan.Zero, timeout.Span); } } \ No newline at end of file From 78c5d0dc5b2385542b29153a33b3d0e8188317d5 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 07:57:03 +0100 Subject: [PATCH 37/56] make continuous key press use smoth acceleration --- Terminal.Gui/ViewBase/MouseHeldDown.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index 16696ab49c..621ea089b8 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -11,11 +11,14 @@ internal class MouseHeldDown : IMouseHeldDown private readonly ITimedEvents? _timedEvents; private readonly IMouseGrabHandler? _mouseGrabber; + private readonly SmoothAcceleratingTimeout _smoothTimeout; + public MouseHeldDown (View host, ITimedEvents? timedEvents, IMouseGrabHandler? mouseGrabber) { _host = host; _timedEvents = timedEvents; _mouseGrabber = mouseGrabber; + _smoothTimeout = new (TimeSpan.FromMilliseconds (500), TimeSpan.FromMilliseconds (50), 0.5, TickWhileMouseIsHeldDown); } public event EventHandler? MouseIsHeldDownTick; @@ -53,8 +56,9 @@ public void Start () _down = true; _mouseGrabber?.GrabMouse (_host); + // Then periodic ticks - _timeout = _timedEvents?.AddTimeout (TimeSpan.FromMilliseconds (500), TickWhileMouseIsHeldDown); + _timeout = _timedEvents?.AddTimeout (_smoothTimeout); } private bool TickWhileMouseIsHeldDown () @@ -62,10 +66,12 @@ private bool TickWhileMouseIsHeldDown () Logging.Debug ("Raising TickWhileMouseIsHeldDown..."); if (_down) { + _smoothTimeout.AdvanceStage (); RaiseMouseIsHeldDownTick (); } else { + _smoothTimeout.Reset (); Stop (); } @@ -74,6 +80,8 @@ private bool TickWhileMouseIsHeldDown () public void Stop () { + _smoothTimeout.Reset (); + if (_mouseGrabber?.MouseGrabView == _host) { _mouseGrabber?.UngrabMouse (); From 966178e33e53b0588766a63c7b2c80298a8be5ac Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 17:12:08 +0100 Subject: [PATCH 38/56] Rename _lock to _lockScreen --- Terminal.Gui/App/Application.Screen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/App/Application.Screen.cs b/Terminal.Gui/App/Application.Screen.cs index c486477db7..cc9dcb0b66 100644 --- a/Terminal.Gui/App/Application.Screen.cs +++ b/Terminal.Gui/App/Application.Screen.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui.App; public static partial class Application // Screen related stuff { - private static readonly object _lock = new (); + private static readonly object _lockScreen = new (); private static Rectangle? _screen; /// @@ -19,7 +19,7 @@ public static Rectangle Screen { get { - lock (_lock) + lock (_lockScreen) { if (_screen == null) { @@ -36,7 +36,7 @@ public static Rectangle Screen throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported"); } - lock (_lock) + lock (_lockScreen) { _screen = value; } From 8721ea3e35d2090bf7fdc3073fca8e1654d1efe4 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 17:17:05 +0100 Subject: [PATCH 39/56] Remove section on idles, they are not a thing anymore - and they kinda never were. --- docfx/docs/multitasking.md | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/docfx/docs/multitasking.md b/docfx/docs/multitasking.md index 42142b71e2..a85f01b288 100644 --- a/docfx/docs/multitasking.md +++ b/docfx/docs/multitasking.md @@ -119,44 +119,6 @@ public class ClockView : View - **Keep timer callbacks fast** - they run on the main thread - **Use appropriate intervals** - too frequent updates can impact performance -## Idle Processing - -Idle handlers run when the application has no events to process, useful for background maintenance: - -```csharp -public class AutoSaveView : View -{ - private object idleToken; - private DateTime lastSave = DateTime.Now; - - public AutoSaveView() - { - idleToken = Application.MainLoop.AddIdle(CheckAutoSave); - } - - private bool CheckAutoSave() - { - if (DateTime.Now - lastSave > TimeSpan.FromMinutes(5)) - { - if (HasUnsavedChanges()) - { - SaveDocument(); - lastSave = DateTime.Now; - } - } - return true; // Continue idle processing - } - - protected override void Dispose(bool disposing) - { - if (disposing && idleToken != null) - { - Application.MainLoop.RemoveIdle(idleToken); - } - base.Dispose(disposing); - } -} -``` ## Common Patterns From 53de140b68f34a8e3ada906209bac5ca12129124 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 17:20:58 +0100 Subject: [PATCH 40/56] Add nullable enable --- Terminal.Gui/App/IMouseGrabHandler.cs | 4 +++- Terminal.Gui/App/MouseGrabHandler.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/App/IMouseGrabHandler.cs b/Terminal.Gui/App/IMouseGrabHandler.cs index 3e4712413a..7e59da5094 100644 --- a/Terminal.Gui/App/IMouseGrabHandler.cs +++ b/Terminal.Gui/App/IMouseGrabHandler.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui.App; +#nullable enable +namespace Terminal.Gui.App; + /// /// Interface for class that tracks which (if any) has 'grabbed' the mouse diff --git a/Terminal.Gui/App/MouseGrabHandler.cs b/Terminal.Gui/App/MouseGrabHandler.cs index 8432647a08..08df602574 100644 --- a/Terminal.Gui/App/MouseGrabHandler.cs +++ b/Terminal.Gui/App/MouseGrabHandler.cs @@ -1,4 +1,5 @@ -namespace Terminal.Gui.App; +#nullable enable +namespace Terminal.Gui.App; internal class MouseGrabHandler : IMouseGrabHandler { From 7fb15fa24450709b346a63831cd64d72ee536136 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 17:22:41 +0100 Subject: [PATCH 41/56] Add xml comment --- Terminal.Gui/App/LogarithmicTimeout.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Terminal.Gui/App/LogarithmicTimeout.cs b/Terminal.Gui/App/LogarithmicTimeout.cs index 590e659878..5a1a1b41ed 100644 --- a/Terminal.Gui/App/LogarithmicTimeout.cs +++ b/Terminal.Gui/App/LogarithmicTimeout.cs @@ -6,6 +6,12 @@ public class LogarithmicTimeout : Timeout private int stage = 0; private readonly TimeSpan baseDelay; + /// + /// Creates a new instance where stages are the logarithm multiplied by the + /// (starts fast then slows). + /// + /// Multiple for the logarithm + /// Method to invoke public LogarithmicTimeout (TimeSpan baseDelay, Func callback) { this.baseDelay = baseDelay; From 889e071fb5fd5edd39a45904ae7952230910a594 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 21 Jun 2025 17:23:31 +0100 Subject: [PATCH 42/56] Fix namings and cleanup code --- Terminal.Gui/App/LogarithmicTimeout.cs | 29 +++++------- Terminal.Gui/App/SmoothAcceleratingTimeout.cs | 47 +++++++++---------- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/Terminal.Gui/App/LogarithmicTimeout.cs b/Terminal.Gui/App/LogarithmicTimeout.cs index 5a1a1b41ed..ea2ca920c3 100644 --- a/Terminal.Gui/App/LogarithmicTimeout.cs +++ b/Terminal.Gui/App/LogarithmicTimeout.cs @@ -3,19 +3,19 @@ /// Implements a logarithmic increasing timeout. public class LogarithmicTimeout : Timeout { - private int stage = 0; - private readonly TimeSpan baseDelay; + private int _stage; + private readonly TimeSpan _baseDelay; /// - /// Creates a new instance where stages are the logarithm multiplied by the - /// (starts fast then slows). + /// Creates a new instance where stages are the logarithm multiplied by the + /// (starts fast then slows). /// /// Multiple for the logarithm /// Method to invoke public LogarithmicTimeout (TimeSpan baseDelay, Func callback) { - this.baseDelay = baseDelay; - this.Callback = callback; + this._baseDelay = baseDelay; + Callback = callback; } /// Gets the current calculated Span based on the stage. @@ -24,20 +24,15 @@ public override TimeSpan Span get { // Calculate logarithmic increase - double multiplier = Math.Log (stage + 1); // ln(stage + 1) - return TimeSpan.FromMilliseconds (baseDelay.TotalMilliseconds * multiplier); + double multiplier = Math.Log (_stage + 1); // ln(stage + 1) + + return TimeSpan.FromMilliseconds (_baseDelay.TotalMilliseconds * multiplier); } } /// Increments the stage to increase the timeout. - public void AdvanceStage () - { - stage++; - } + public void AdvanceStage () { _stage++; } /// Resets the stage back to zero. - public void Reset () - { - stage = 0; - } -} \ No newline at end of file + public void Reset () { _stage = 0; } +} diff --git a/Terminal.Gui/App/SmoothAcceleratingTimeout.cs b/Terminal.Gui/App/SmoothAcceleratingTimeout.cs index 792a0835bd..3668392703 100644 --- a/Terminal.Gui/App/SmoothAcceleratingTimeout.cs +++ b/Terminal.Gui/App/SmoothAcceleratingTimeout.cs @@ -1,19 +1,19 @@ namespace Terminal.Gui.App; /// -/// Timeout which accelerates slowly at first then fast up to a maximum speed. -/// Use to increment the stage of the timer (e.g. in -/// your timer callback code). +/// Timeout which accelerates slowly at first then fast up to a maximum speed. +/// Use to increment the stage of the timer (e.g. in +/// your timer callback code). /// public class SmoothAcceleratingTimeout : Timeout { - private int stage = 0; - private readonly TimeSpan initialDelay; - private readonly TimeSpan minDelay; - private readonly double decayFactor; + private int _stage; + private readonly TimeSpan _initialDelay; + private readonly TimeSpan _minDelay; + private readonly double _decayFactor; /// - /// Creates a new instance of the smooth acceleration timeout. + /// Creates a new instance of the smooth acceleration timeout. /// /// Delay before first tick, the longest it will ever take /// The fastest the timer can get no matter how long it runs @@ -21,10 +21,10 @@ public class SmoothAcceleratingTimeout : Timeout /// Method to call when timer ticks public SmoothAcceleratingTimeout (TimeSpan initialDelay, TimeSpan minDelay, double decayFactor, Func callback) { - this.initialDelay = initialDelay; - this.minDelay = minDelay; - this.decayFactor = decayFactor; - this.Callback = callback; + this._initialDelay = initialDelay; + this._minDelay = minDelay; + this._decayFactor = decayFactor; + Callback = callback; } /// @@ -32,27 +32,22 @@ public override TimeSpan Span { get { - double initialMs = initialDelay.TotalMilliseconds; - double minMs = minDelay.TotalMilliseconds; - double delayMs = minMs + (initialMs - minMs) * Math.Pow (decayFactor, stage); + double initialMs = _initialDelay.TotalMilliseconds; + double minMs = _minDelay.TotalMilliseconds; + double delayMs = minMs + (initialMs - minMs) * Math.Pow (_decayFactor, _stage); + return TimeSpan.FromMilliseconds (delayMs); } } /// - /// Advances the timer stage, this should be called from your timer callback or whenever - /// you want to advance the speed. + /// Advances the timer stage, this should be called from your timer callback or whenever + /// you want to advance the speed. /// - public void AdvanceStage () - { - stage++; - } + public void AdvanceStage () { _stage++; } /// - /// Resets the timer to original speed. + /// Resets the timer to original speed. /// - public void Reset () - { - stage = 0; - } + public void Reset () { _stage = 0; } } From f6b9fe769dfae82b03ca505a051424a51a67bb1d Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 5 Jul 2025 02:54:42 +0100 Subject: [PATCH 43/56] xmldoc fix --- Terminal.Gui/ViewBase/IMouseHeldDown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ViewBase/IMouseHeldDown.cs b/Terminal.Gui/ViewBase/IMouseHeldDown.cs index d7b57b413a..635e4d14ec 100644 --- a/Terminal.Gui/ViewBase/IMouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/IMouseHeldDown.cs @@ -6,7 +6,7 @@ namespace Terminal.Gui.ViewBase; /// /// /// Handler for raising periodic events while the mouse is held down. -/// Typically, mouse pointer only needs to be pressed down in a view +/// Typically, mouse button only needs to be pressed down in a view /// to begin this event after which it can be moved elsewhere. /// /// From 65207ad150b606328500d47728f5a58d6332b759 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 5 Jul 2025 02:55:17 +0100 Subject: [PATCH 44/56] Rename LockAndRunTimers to just RunTimers --- Terminal.Gui/App/ITimedEvents.cs | 2 +- Terminal.Gui/App/MainLoop.cs | 2 +- Terminal.Gui/App/TimedEvents.cs | 6 +++--- Terminal.Gui/Drivers/V2/MainLoop.cs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/App/ITimedEvents.cs b/Terminal.Gui/App/ITimedEvents.cs index 591c8e3694..c13a9929bb 100644 --- a/Terminal.Gui/App/ITimedEvents.cs +++ b/Terminal.Gui/App/ITimedEvents.cs @@ -11,7 +11,7 @@ public interface ITimedEvents /// /// Runs all timeouts that are due /// - void LockAndRunTimers (); + void RunTimers (); /// Adds a timeout to the application. /// diff --git a/Terminal.Gui/App/MainLoop.cs b/Terminal.Gui/App/MainLoop.cs index 6a9d435460..bdf714aaf2 100644 --- a/Terminal.Gui/App/MainLoop.cs +++ b/Terminal.Gui/App/MainLoop.cs @@ -112,7 +112,7 @@ internal void RunIteration () MainLoopDriver?.Iteration (); - TimedEvents.LockAndRunTimers (); + TimedEvents.RunTimers (); } private void RunAnsiScheduler () diff --git a/Terminal.Gui/App/TimedEvents.cs b/Terminal.Gui/App/TimedEvents.cs index 37466ecc99..b3c0a4ddf5 100644 --- a/Terminal.Gui/App/TimedEvents.cs +++ b/Terminal.Gui/App/TimedEvents.cs @@ -51,19 +51,19 @@ private long NudgeToUniqueKey (long k) } /// - public void LockAndRunTimers () + public void RunTimers () { lock (_timeoutsLockToken) { if (_timeouts.Count > 0) { - RunTimers (); + RunTimersImpl (); } } } - private void RunTimers () + private void RunTimersImpl () { long now = DateTime.UtcNow.Ticks; SortedList copy; diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index 9059217078..5b6d9fdde7 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -143,8 +143,8 @@ internal void IterationImpl () var swCallbacks = Stopwatch.StartNew (); - TimedEvents.LockAndRunTimers (); - + TimedEvents.RunTimers (); + Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds); } From 079a4c5c00ba8596fa1070469266c52a7af79c8d Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 5 Jul 2025 02:59:25 +0100 Subject: [PATCH 45/56] Rename AddTimeout and RemoveTimeout (and event) to just Add/Remove --- Examples/UICatalog/Scenarios/Threading.cs | 8 +- Terminal.Gui/App/ApplicationImpl.cs | 4 +- Terminal.Gui/App/ITimedEvents.cs | 14 +-- Terminal.Gui/App/MainLoopSyncContext.cs | 2 +- Terminal.Gui/App/TimedEvents.cs | 12 +- Terminal.Gui/App/TimeoutEventArgs.cs | 2 +- .../Drivers/EscSeqUtils/EscSeqUtils.cs | 4 +- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 6 +- .../Drivers/WindowsDriver/WindowsDriver.cs | 2 +- Terminal.Gui/ViewBase/MouseHeldDown.cs | 4 +- Tests/UnitTests/Application/MainLoopTests.cs | 110 +++++++++--------- .../ConsoleDrivers/MainLoopDriverTests.cs | 22 ++-- 12 files changed, 94 insertions(+), 96 deletions(-) diff --git a/Examples/UICatalog/Scenarios/Threading.cs b/Examples/UICatalog/Scenarios/Threading.cs index a9d8ea0ea1..dc97292b20 100644 --- a/Examples/UICatalog/Scenarios/Threading.cs +++ b/Examples/UICatalog/Scenarios/Threading.cs @@ -190,14 +190,14 @@ private void StartStopLogTimeout (object sender, CommandEventArgs e) if (_timeoutObj != null) { _btnLogarithmic.Text = "Start Log Counter"; - Application.TimedEvents.RemoveTimeout (_timeoutObj); + Application.TimedEvents.Remove (_timeoutObj); _timeoutObj = null; } else { _btnLogarithmic.Text = "Stop Log Counter"; _logarithmicTimeout = new LogarithmicTimeout (TimeSpan.FromMilliseconds (500), LogTimeout); - _timeoutObj = Application.TimedEvents.AddTimeout (_logarithmicTimeout); + _timeoutObj = Application.TimedEvents.Add (_logarithmicTimeout); } } @@ -206,14 +206,14 @@ private void StartStopSmoothTimeout (object sender, CommandEventArgs e) if (_timeoutObjSmooth != null) { _btnSmooth.Text = "Start Smooth Counter"; - Application.TimedEvents.RemoveTimeout (_timeoutObjSmooth); + Application.TimedEvents.Remove (_timeoutObjSmooth); _timeoutObjSmooth = null; } else { _btnSmooth.Text = "Stop Smooth Counter"; _smoothTimeout = new SmoothAcceleratingTimeout (TimeSpan.FromMilliseconds (500), TimeSpan.FromMilliseconds (50), 0.5, SmoothTimeout); - _timeoutObjSmooth = Application.TimedEvents.AddTimeout (_smoothTimeout); + _timeoutObjSmooth = Application.TimedEvents.Add (_smoothTimeout); } } diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index efcb3a3f99..4eadc2ad61 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -306,13 +306,13 @@ public virtual object AddTimeout (TimeSpan time, Func callback) throw new NotInitializedException ("Cannot add timeout before main loop is initialized", null); } - return Application.MainLoop.TimedEvents.AddTimeout (time, callback); + return Application.MainLoop.TimedEvents.Add (time, callback); } /// public virtual bool RemoveTimeout (object token) { - return Application.MainLoop?.TimedEvents.RemoveTimeout (token) ?? false; + return Application.MainLoop?.TimedEvents.Remove (token) ?? false; } /// diff --git a/Terminal.Gui/App/ITimedEvents.cs b/Terminal.Gui/App/ITimedEvents.cs index c13a9929bb..5f472766f7 100644 --- a/Terminal.Gui/App/ITimedEvents.cs +++ b/Terminal.Gui/App/ITimedEvents.cs @@ -17,12 +17,12 @@ public interface ITimedEvents /// /// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be /// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a - /// token that can be used to stop the timeout by calling . + /// token that can be used to stop the timeout by calling . /// - object AddTimeout (TimeSpan time, Func callback); + object Add (TimeSpan time, Func callback); - /// - object AddTimeout (Timeout timeout); + /// + object Add (Timeout timeout); /// Removes a previously scheduled timeout /// The token parameter is the value returned by AddTimeout. @@ -36,7 +36,7 @@ public interface ITimedEvents /// /// if the timeout is not found. /// - bool RemoveTimeout (object token); + bool Remove (object token); /// /// Returns the next planned execution time (key - UTC ticks) @@ -48,9 +48,7 @@ public interface ITimedEvents /// Invoked when a new timeout is added. To be used in the case when /// is . /// - event EventHandler? TimeoutAdded; - - + event EventHandler? Added; /// /// Called from to check if there are any outstanding timers diff --git a/Terminal.Gui/App/MainLoopSyncContext.cs b/Terminal.Gui/App/MainLoopSyncContext.cs index 200ecb34ed..548722d6e2 100644 --- a/Terminal.Gui/App/MainLoopSyncContext.cs +++ b/Terminal.Gui/App/MainLoopSyncContext.cs @@ -11,7 +11,7 @@ internal sealed class MainLoopSyncContext : SynchronizationContext public override void Post (SendOrPostCallback d, object state) { // Queue the task - Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, + Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero, () => { d (state); diff --git a/Terminal.Gui/App/TimedEvents.cs b/Terminal.Gui/App/TimedEvents.cs index b3c0a4ddf5..e76276dacf 100644 --- a/Terminal.Gui/App/TimedEvents.cs +++ b/Terminal.Gui/App/TimedEvents.cs @@ -18,7 +18,7 @@ public class TimedEvents : ITimedEvents public SortedList Timeouts => _timeouts; /// - public event EventHandler? TimeoutAdded; + public event EventHandler? Added; private void AddTimeout (TimeSpan time, Timeout timeout) @@ -27,7 +27,7 @@ private void AddTimeout (TimeSpan time, Timeout timeout) { long k = (DateTime.UtcNow + time).Ticks; _timeouts.Add (NudgeToUniqueKey (k), timeout); - TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k)); + Added?.Invoke (this, new TimeoutEventArgs (timeout, k)); } } @@ -107,7 +107,7 @@ private void RunTimersImpl () /// This method also returns /// /// if the timeout is not found. - public bool RemoveTimeout (object token) + public bool Remove (object token) { lock (_timeoutsLockToken) { @@ -129,9 +129,9 @@ public bool RemoveTimeout (object token) /// /// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be /// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a - /// token that can be used to stop the timeout by calling . + /// token that can be used to stop the timeout by calling . /// - public object AddTimeout (TimeSpan time, Func callback) + public object Add (TimeSpan time, Func callback) { ArgumentNullException.ThrowIfNull (callback); @@ -142,7 +142,7 @@ public object AddTimeout (TimeSpan time, Func callback) } /// - public object AddTimeout (Timeout timeout) + public object Add (Timeout timeout) { AddTimeout (timeout.Span, timeout); return timeout; diff --git a/Terminal.Gui/App/TimeoutEventArgs.cs b/Terminal.Gui/App/TimeoutEventArgs.cs index b77741db43..9ad014fa43 100644 --- a/Terminal.Gui/App/TimeoutEventArgs.cs +++ b/Terminal.Gui/App/TimeoutEventArgs.cs @@ -1,6 +1,6 @@ namespace Terminal.Gui.App; -/// for timeout events (e.g. ) +/// for timeout events (e.g. ) public class TimeoutEventArgs : EventArgs { /// Creates a new instance of the class. diff --git a/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs index cbb08269a5..0a99fa8b5d 100644 --- a/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs @@ -931,7 +931,7 @@ Action continuousButtonPressedHandler _isButtonClicked = false; _isButtonDoubleClicked = true; - Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, + Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero, () => { Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); @@ -970,7 +970,7 @@ Action continuousButtonPressedHandler mouseFlags.Add (GetButtonClicked (buttonState)); _isButtonClicked = true; - Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero, + Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero, () => { Task.Run (async () => await ProcessButtonClickedAsync ()); diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index 8e3ccd53f0..ca94ebe575 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -235,7 +235,7 @@ public override void Invoke (Action action) return; } - _timedEvents.AddTimeout (TimeSpan.Zero, + _timedEvents.Add (TimeSpan.Zero, () => { action (); @@ -246,10 +246,10 @@ public override void Invoke (Action action) } /// - public override object AddTimeout (TimeSpan time, Func callback) { return _timedEvents.AddTimeout (time, callback); } + public override object AddTimeout (TimeSpan time, Func callback) { return _timedEvents.Add (time, callback); } /// - public override bool RemoveTimeout (object token) { return _timedEvents.RemoveTimeout (token); } + public override bool RemoveTimeout (object token) { return _timedEvents.Remove (token); } /// public override void LayoutAndDraw (bool forceDraw) diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index c2c335b804..59941924fc 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs @@ -945,7 +945,7 @@ private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent if (_isButtonDoubleClicked || _isOneFingerDoubleClicked) { // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. - Application.MainLoop!.TimedEvents.AddTimeout (TimeSpan.Zero, + Application.MainLoop!.TimedEvents.Add (TimeSpan.Zero, () => { Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index 621ea089b8..e25cc8bdb0 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -58,7 +58,7 @@ public void Start () // Then periodic ticks - _timeout = _timedEvents?.AddTimeout (_smoothTimeout); + _timeout = _timedEvents?.Add (_smoothTimeout); } private bool TickWhileMouseIsHeldDown () @@ -89,7 +89,7 @@ public void Stop () if (_timeout != null) { - _timedEvents?.RemoveTimeout (_timeout); + _timedEvents?.Remove (_timeout); } _down = false; diff --git a/Tests/UnitTests/Application/MainLoopTests.cs b/Tests/UnitTests/Application/MainLoopTests.cs index ee6dbffbc4..5c7866aa55 100644 --- a/Tests/UnitTests/Application/MainLoopTests.cs +++ b/Tests/UnitTests/Application/MainLoopTests.cs @@ -36,27 +36,27 @@ public void AddTimeout_Adds_And_Removes () Func fnTrue = () => true; Func fnFalse = () => false; - var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnTrue); - var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnFalse); + var a = ml.TimedEvents.Add (TimeSpan.Zero, fnTrue); + var b = ml.TimedEvents.Add (TimeSpan.Zero, fnFalse); Assert.Equal (2, ml.TimedEvents.Timeouts.Count); Assert.Equal (fnTrue, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback); Assert.NotEqual (fnFalse, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback); - Assert.True (ml.TimedEvents.RemoveTimeout (a)); + Assert.True (ml.TimedEvents.Remove (a)); Assert.Single (ml.TimedEvents.Timeouts); // BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either // throw an exception in this case, or return an error. // No. Only need to return a boolean. - Assert.False (ml.TimedEvents.RemoveTimeout (a)); + Assert.False (ml.TimedEvents.Remove (a)); - Assert.True (ml.TimedEvents.RemoveTimeout (b)); + Assert.True (ml.TimedEvents.Remove (b)); // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either // throw an exception in this case, or return an error. // No. Only need to return a boolean. - Assert.False (ml.TimedEvents.RemoveTimeout(b)); + Assert.False (ml.TimedEvents.Remove(b)); } [Fact] @@ -73,7 +73,7 @@ public void AddTimeout_Function_GetsCalled_OnIteration () return true; }; - ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); + ml.TimedEvents.Add (TimeSpan.Zero, fn); ml.RunIteration (); Assert.Equal (1, functionCalled); } @@ -107,15 +107,15 @@ public void AddTimeout_Twice_Returns_False_Called_Twice () return true; }; - var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); - var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn1); + var a = ml.TimedEvents.Add (TimeSpan.Zero, fnStop); + var b = ml.TimedEvents.Add (TimeSpan.Zero, fn1); ml.Run (); - Assert.True (ml.TimedEvents.RemoveTimeout(a)); - Assert.False (ml.TimedEvents.RemoveTimeout (a)); + Assert.True (ml.TimedEvents.Remove(a)); + Assert.False (ml.TimedEvents.Remove (a)); // Cannot remove b because it returned false i.e. auto removes itself - Assert.False (ml.TimedEvents.RemoveTimeout (b)); + Assert.False (ml.TimedEvents.Remove (b)); Assert.Equal (1, functionCalled); } @@ -134,24 +134,24 @@ public void AddTimeoutTwice_Function_CalledTwice () return true; }; - var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); - var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); + var a = ml.TimedEvents.Add (TimeSpan.Zero, fn); + var b = ml.TimedEvents.Add (TimeSpan.Zero, fn); ml.RunIteration (); Assert.Equal (2, functionCalled); Assert.Equal (2, ml.TimedEvents.Timeouts.Count); functionCalled = 0; - Assert.True (ml.TimedEvents.RemoveTimeout (a)); + Assert.True (ml.TimedEvents.Remove (a)); Assert.Single (ml.TimedEvents.Timeouts); ml.RunIteration (); Assert.Equal (1, functionCalled); functionCalled = 0; - Assert.True (ml.TimedEvents.RemoveTimeout (b)); + Assert.True (ml.TimedEvents.Remove (b)); Assert.Empty (ml.TimedEvents.Timeouts); ml.RunIteration (); Assert.Equal (0, functionCalled); - Assert.False (ml.TimedEvents.RemoveTimeout (b)); + Assert.False (ml.TimedEvents.Remove (b)); } [Fact] @@ -168,8 +168,8 @@ public void AddThenRemoveIdle_Function_NotCalled () return true; }; - var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); - Assert.True (ml.TimedEvents.RemoveTimeout (a)); + var a = ml.TimedEvents.Add (TimeSpan.Zero, fn); + Assert.True (ml.TimedEvents.Remove (a)); ml.RunIteration (); Assert.Equal (0, functionCalled); } @@ -190,13 +190,13 @@ public void AddTimer_Adds_Removes_NoFaults () return true; }; - object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); + object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback); - Assert.True (ml.TimedEvents.RemoveTimeout (token)); + Assert.True (ml.TimedEvents.Remove (token)); // BUGBUG: This should probably fault? // Must return a boolean. - Assert.False (ml.TimedEvents.RemoveTimeout (token)); + Assert.False (ml.TimedEvents.Remove (token)); } [Fact] @@ -220,8 +220,8 @@ public async Task AddTimer_Duplicate_Keys_Not_Allowed () return true; }; - var task1 = new Task (() => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)); - var task2 = new Task (() => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)); + var task1 = new Task (() => token1 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback)); + var task2 = new Task (() => token2 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback)); Assert.Null (token1); Assert.Null (token2); task1.Start (); @@ -230,8 +230,8 @@ public async Task AddTimer_Duplicate_Keys_Not_Allowed () Assert.NotNull (token1); Assert.NotNull (token2); await Task.WhenAll (task1, task2); - Assert.True (ml.TimedEvents.RemoveTimeout (token1)); - Assert.True (ml.TimedEvents.RemoveTimeout (token2)); + Assert.True (ml.TimedEvents.Remove (token1)); + Assert.True (ml.TimedEvents.Remove (token2)); Assert.Equal (2, callbackCount); } @@ -257,13 +257,13 @@ public void AddTimer_EventFired () object sender = null; TimeoutEventArgs args = null; - ml.TimedEvents.TimeoutAdded += (s, e) => + ml.TimedEvents.Added += (s, e) => { sender = s; args = e; }; - object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); + object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback); Assert.Same (ml.TimedEvents, sender); Assert.NotNull (args.Timeout); @@ -292,14 +292,14 @@ public void AddTimer_In_Parallel_Wont_Throw () }; Parallel.Invoke ( - () => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback), - () => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback) + () => token1 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback), + () => token2 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback) ); ml.Run (); Assert.NotNull (token1); Assert.NotNull (token2); - Assert.True (ml.TimedEvents.RemoveTimeout (token1)); - Assert.True (ml.TimedEvents.RemoveTimeout (token2)); + Assert.True (ml.TimedEvents.Remove (token1)); + Assert.True (ml.TimedEvents.Remove (token2)); Assert.Equal (2, callbackCount); } @@ -324,7 +324,7 @@ public void AddTimer_Remove_NotCalled () return true; }; - ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); + ml.TimedEvents.Add (TimeSpan.Zero, fnStop); var callbackCount = 0; @@ -335,8 +335,8 @@ public void AddTimer_Remove_NotCalled () return true; }; - object token = ml.TimedEvents.AddTimeout (ms, callback); - Assert.True (ml.TimedEvents.RemoveTimeout (token)); + object token = ml.TimedEvents.Add (ms, callback); + Assert.True (ml.TimedEvents.Remove (token)); ml.Run (); Assert.Equal (0, callbackCount); } @@ -362,7 +362,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled () return true; }; - ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); + ml.TimedEvents.Add (TimeSpan.Zero, fnStop); var callbackCount = 0; @@ -373,11 +373,11 @@ public void AddTimer_ReturnFalse_StopsBeingCalled () return false; }; - object token = ml.TimedEvents.AddTimeout (ms, callback); + object token = ml.TimedEvents.Add (ms, callback); ml.Run (); Assert.Equal (1, callbackCount); Assert.Equal (10, stopCount); - Assert.False (ml.TimedEvents.RemoveTimeout (token)); + Assert.False (ml.TimedEvents.Remove (token)); } [Fact] @@ -396,9 +396,9 @@ public void AddTimer_Run_Called () return true; }; - object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback); + object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback); ml.Run (); - Assert.True (ml.TimedEvents.RemoveTimeout (token)); + Assert.True (ml.TimedEvents.Remove (token)); Assert.Equal (1, callbackCount); } @@ -421,7 +421,7 @@ public void AddTimer_Run_CalledAtApproximatelyRightTime () return true; }; - object token = ml.TimedEvents.AddTimeout (ms, callback); + object token = ml.TimedEvents.Add (ms, callback); watch.Start (); ml.Run (); @@ -429,7 +429,7 @@ public void AddTimer_Run_CalledAtApproximatelyRightTime () // https://github.com/xunit/assert.xunit/pull/25 Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100)); - Assert.True (ml.TimedEvents.RemoveTimeout (token)); + Assert.True (ml.TimedEvents.Remove (token)); Assert.Equal (1, callbackCount); } @@ -455,7 +455,7 @@ public void AddTimer_Run_CalledTwiceApproximatelyRightTime () return true; }; - object token = ml.TimedEvents.AddTimeout (ms, callback); + object token = ml.TimedEvents.Add (ms, callback); watch.Start (); ml.Run (); @@ -463,7 +463,7 @@ public void AddTimer_Run_CalledTwiceApproximatelyRightTime () // https://github.com/xunit/assert.xunit/pull/25 Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100)); - Assert.True (ml.TimedEvents.RemoveTimeout (token)); + Assert.True (ml.TimedEvents.Remove (token)); Assert.Equal (2, callbackCount); } @@ -482,7 +482,7 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True () var ml = new MainLoop (new FakeMainLoop ()); Func fnTrue = () => true; - ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnTrue); + ml.TimedEvents.Add (TimeSpan.Zero, fnTrue); bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut); Assert.True (retVal); Assert.Equal (0, waitTimeOut); @@ -496,7 +496,7 @@ public void CheckTimersAndIdleHandlers_With1Timer_Returns_Timer () static bool Callback () { return false; } - _ = ml.TimedEvents.AddTimeout (ms, Callback); + _ = ml.TimedEvents.Add (ms, Callback); bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut); Assert.True (retVal); @@ -513,8 +513,8 @@ public void CheckTimersAndIdleHandlers_With2Timers_Returns_Timer () static bool Callback () { return false; } - _ = ml.TimedEvents.AddTimeout (ms, Callback); - _ = ml.TimedEvents.AddTimeout (ms, Callback); + _ = ml.TimedEvents.Add (ms, Callback); + _ = ml.TimedEvents.Add (ms, Callback); bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut); Assert.True (retVal); @@ -557,11 +557,11 @@ public void False_Idle_Stops_It_Being_Called_Again () return true; }; - var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fnStop); - var b = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn1); + var a = ml.TimedEvents.Add (TimeSpan.Zero, fnStop); + var b = ml.TimedEvents.Add (TimeSpan.Zero, fn1); ml.Run (); - Assert.True (ml.TimedEvents.RemoveTimeout (a)); - Assert.False (ml.TimedEvents.RemoveTimeout (a)); + Assert.True (ml.TimedEvents.Remove (a)); + Assert.False (ml.TimedEvents.Remove (a)); Assert.Equal (10, functionCalled); Assert.Equal (20, stopCount); @@ -676,7 +676,7 @@ public void RemoveIdle_Function_NotCalled () return true; }; - Assert.False (ml.TimedEvents.RemoveTimeout ("flibble")); + Assert.False (ml.TimedEvents.Remove ("flibble")); ml.RunIteration (); Assert.Equal (0, functionCalled); } @@ -700,9 +700,9 @@ public void Run_Runs_Idle_Stop_Stops_Idle () return true; }; - var a = ml.TimedEvents.AddTimeout (TimeSpan.Zero, fn); + var a = ml.TimedEvents.Add (TimeSpan.Zero, fn); ml.Run (); - Assert.True (ml.TimedEvents.RemoveTimeout (a)); + Assert.True (ml.TimedEvents.Remove (a)); Assert.Equal (10, functionCalled); } diff --git a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs index 0ee2662c6a..2648ef2688 100644 --- a/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs @@ -29,7 +29,7 @@ bool IdleHandler () return false; } - var token = mainLoop.TimedEvents.AddTimeout(TimeSpan.Zero, IdleHandler); + var token = mainLoop.TimedEvents.Add(TimeSpan.Zero, IdleHandler); Assert.NotNull (token); Assert.False (idleHandlerInvoked); // Idle handler should not be invoked immediately @@ -52,7 +52,7 @@ public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, T var mainLoop = new MainLoop (mainLoopDriver); var callbackInvoked = false; - object token = mainLoop.TimedEvents.AddTimeout ( + object token = mainLoop.TimedEvents.Add ( TimeSpan.FromMilliseconds (100), () => { @@ -87,7 +87,7 @@ Type mainLoopDriverType var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - mainLoop.TimedEvents.AddTimeout (TimeSpan.Zero, () => false); + mainLoop.TimedEvents.Add (TimeSpan.Zero, () => false); bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout); Assert.True (result); @@ -134,7 +134,7 @@ Type mainLoopDriverType var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); + mainLoop.TimedEvents.Add (TimeSpan.FromMilliseconds (100), () => false); bool result = mainLoop.TimedEvents.CheckTimers(out int waitTimeout); Assert.True (result); @@ -184,7 +184,7 @@ public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - bool result = mainLoop.TimedEvents.RemoveTimeout("flibble"); + bool result = mainLoop.TimedEvents.Remove("flibble"); Assert.False (result); mainLoop.Dispose (); @@ -206,8 +206,8 @@ public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type ma bool IdleHandler () { return false; } - var token = mainLoop.TimedEvents.AddTimeout (TimeSpan.Zero, IdleHandler); - bool result = mainLoop.TimedEvents.RemoveTimeout (token); + var token = mainLoop.TimedEvents.Add (TimeSpan.Zero, IdleHandler); + bool result = mainLoop.TimedEvents.Remove (token); Assert.True (result); mainLoop.Dispose (); @@ -226,7 +226,7 @@ public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, T var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - bool result = mainLoop.TimedEvents.RemoveTimeout (new object ()); + bool result = mainLoop.TimedEvents.Remove (new object ()); Assert.False (result); } @@ -244,8 +244,8 @@ public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver); var mainLoop = new MainLoop (mainLoopDriver); - object token = mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false); - bool result = mainLoop.TimedEvents.RemoveTimeout (token); + object token = mainLoop.TimedEvents.Add (TimeSpan.FromMilliseconds (100), () => false); + bool result = mainLoop.TimedEvents.Remove (token); Assert.True (result); mainLoop.Dispose (); @@ -272,7 +272,7 @@ public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driver return false; }; - mainLoop.TimedEvents.AddTimeout (TimeSpan.Zero, idleHandler); + mainLoop.TimedEvents.Add (TimeSpan.Zero, idleHandler); mainLoop.RunIteration (); // Run an iteration to process the idle handler Assert.True (idleHandlerInvoked); From 768e5bdee48fb2aa1a8e3954d48f58a3da52f974 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 5 Jul 2025 03:09:19 +0100 Subject: [PATCH 46/56] Update description of MainLoop --- Terminal.Gui/App/MainLoop.cs | 2 +- Terminal.Gui/Drivers/V2/IMainLoop.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/App/MainLoop.cs b/Terminal.Gui/App/MainLoop.cs index bdf714aaf2..64aadf73cf 100644 --- a/Terminal.Gui/App/MainLoop.cs +++ b/Terminal.Gui/App/MainLoop.cs @@ -32,7 +32,7 @@ internal interface IMainLoopDriver void Wakeup (); } -/// The MainLoop monitors timers handlers. +/// The main event loop of v1 driver based applications. /// /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this /// on Windows. diff --git a/Terminal.Gui/Drivers/V2/IMainLoop.cs b/Terminal.Gui/Drivers/V2/IMainLoop.cs index 460acb5745..647776cbe3 100644 --- a/Terminal.Gui/Drivers/V2/IMainLoop.cs +++ b/Terminal.Gui/Drivers/V2/IMainLoop.cs @@ -6,7 +6,7 @@ namespace Terminal.Gui.Drivers; /// /// Interface for main loop that runs the core Terminal.Gui UI loop. /// -/// +/// Type of raw input events processed by the loop e.g. public interface IMainLoop : IDisposable { /// From a1ea54552bcc0b8de6d15bfcf32a6041a4675277 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 7 Jul 2025 11:34:50 -0600 Subject: [PATCH 47/56] Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver --- Tests/UnitTests/Application/ApplicationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index 75d63f0e14..d5d6c8991f 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -1114,7 +1114,7 @@ public void Run_t_Does_Not_Creates_Top_Without_Init () private class TestToplevel : Toplevel { } - [Theory] + [Theory(Skip = "MacOS fail")] [InlineData ("v2win", typeof (ConsoleDriverFacade))] [InlineData ("v2net", typeof (ConsoleDriverFacade))] [InlineData ("FakeDriver", typeof (FakeDriver))] From 8abb13c3c049938b2b38e3fd9cb09ed6a754021d Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 7 Jul 2025 11:44:53 -0600 Subject: [PATCH 48/56] Again? Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver --- .../UnitTests/Application/ApplicationTests.cs | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index d5d6c8991f..cfdcf7904f 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -1114,44 +1114,44 @@ public void Run_t_Does_Not_Creates_Top_Without_Init () private class TestToplevel : Toplevel { } - [Theory(Skip = "MacOS fail")] - [InlineData ("v2win", typeof (ConsoleDriverFacade))] - [InlineData ("v2net", typeof (ConsoleDriverFacade))] - [InlineData ("FakeDriver", typeof (FakeDriver))] - [InlineData ("NetDriver", typeof (NetDriver))] - [InlineData ("WindowsDriver", typeof (WindowsDriver))] - [InlineData ("CursesDriver", typeof (CursesDriver))] - public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string driverName, Type expectedType) - { - Assert.True (ConsoleDriver.RunningUnitTests); - - var result = false; - - Task.Run (() => - { - Task.Delay (300).Wait (); - }).ContinueWith ( - (t, _) => - { - // no longer loading - Application.Invoke (() => - { - result = true; - Application.RequestStop (); - }); - }, - TaskScheduler.FromCurrentSynchronizationContext ()); - - Application.ForceDriver = driverName; - Application.Run (); - Assert.NotNull (Application.Driver); - Assert.Equal (expectedType, Application.Driver?.GetType ()); - Assert.NotNull (Application.Top); - Assert.False (Application.Top!.Running); - Application.Top!.Dispose (); - Shutdown (); - Assert.True (result); - } + //[Theory(Skip = "MacOS fail")] + //[InlineData ("v2win", typeof (ConsoleDriverFacade))] + //[InlineData ("v2net", typeof (ConsoleDriverFacade))] + //[InlineData ("FakeDriver", typeof (FakeDriver))] + //[InlineData ("NetDriver", typeof (NetDriver))] + //[InlineData ("WindowsDriver", typeof (WindowsDriver))] + //[InlineData ("CursesDriver", typeof (CursesDriver))] + //public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string driverName, Type expectedType) + //{ + // Assert.True (ConsoleDriver.RunningUnitTests); + + // var result = false; + + // Task.Run (() => + // { + // Task.Delay (300).Wait (); + // }).ContinueWith ( + // (t, _) => + // { + // // no longer loading + // Application.Invoke (() => + // { + // result = true; + // Application.RequestStop (); + // }); + // }, + // TaskScheduler.FromCurrentSynchronizationContext ()); + + // Application.ForceDriver = driverName; + // Application.Run (); + // Assert.NotNull (Application.Driver); + // Assert.Equal (expectedType, Application.Driver?.GetType ()); + // Assert.NotNull (Application.Top); + // Assert.False (Application.Top!.Running); + // Application.Top!.Dispose (); + // Shutdown (); + // Assert.True (result); + //} [Fact] public void Run_T_With_Legacy_Driver_Does_Not_Call_ResetState_After_Init () From 6515d88bbd3e5700bdc1414aa869142cea8b2686 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 7 Jul 2025 11:45:38 -0600 Subject: [PATCH 49/56] Revert Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver --- .../UnitTests/Application/ApplicationTests.cs | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index cfdcf7904f..d5d6c8991f 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -1114,44 +1114,44 @@ public void Run_t_Does_Not_Creates_Top_Without_Init () private class TestToplevel : Toplevel { } - //[Theory(Skip = "MacOS fail")] - //[InlineData ("v2win", typeof (ConsoleDriverFacade))] - //[InlineData ("v2net", typeof (ConsoleDriverFacade))] - //[InlineData ("FakeDriver", typeof (FakeDriver))] - //[InlineData ("NetDriver", typeof (NetDriver))] - //[InlineData ("WindowsDriver", typeof (WindowsDriver))] - //[InlineData ("CursesDriver", typeof (CursesDriver))] - //public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string driverName, Type expectedType) - //{ - // Assert.True (ConsoleDriver.RunningUnitTests); - - // var result = false; - - // Task.Run (() => - // { - // Task.Delay (300).Wait (); - // }).ContinueWith ( - // (t, _) => - // { - // // no longer loading - // Application.Invoke (() => - // { - // result = true; - // Application.RequestStop (); - // }); - // }, - // TaskScheduler.FromCurrentSynchronizationContext ()); - - // Application.ForceDriver = driverName; - // Application.Run (); - // Assert.NotNull (Application.Driver); - // Assert.Equal (expectedType, Application.Driver?.GetType ()); - // Assert.NotNull (Application.Top); - // Assert.False (Application.Top!.Running); - // Application.Top!.Dispose (); - // Shutdown (); - // Assert.True (result); - //} + [Theory(Skip = "MacOS fail")] + [InlineData ("v2win", typeof (ConsoleDriverFacade))] + [InlineData ("v2net", typeof (ConsoleDriverFacade))] + [InlineData ("FakeDriver", typeof (FakeDriver))] + [InlineData ("NetDriver", typeof (NetDriver))] + [InlineData ("WindowsDriver", typeof (WindowsDriver))] + [InlineData ("CursesDriver", typeof (CursesDriver))] + public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string driverName, Type expectedType) + { + Assert.True (ConsoleDriver.RunningUnitTests); + + var result = false; + + Task.Run (() => + { + Task.Delay (300).Wait (); + }).ContinueWith ( + (t, _) => + { + // no longer loading + Application.Invoke (() => + { + result = true; + Application.RequestStop (); + }); + }, + TaskScheduler.FromCurrentSynchronizationContext ()); + + Application.ForceDriver = driverName; + Application.Run (); + Assert.NotNull (Application.Driver); + Assert.Equal (expectedType, Application.Driver?.GetType ()); + Assert.NotNull (Application.Top); + Assert.False (Application.Top!.Running); + Application.Top!.Dispose (); + Shutdown (); + Assert.True (result); + } [Fact] public void Run_T_With_Legacy_Driver_Does_Not_Call_ResetState_After_Init () From a938fc13d1344e08ff87f66c023f9ecad707142f Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 10 Jul 2025 00:58:37 +0100 Subject: [PATCH 50/56] When mouse is released from MouseHeldDown reset host MouseState --- Terminal.Gui/ViewBase/MouseHeldDown.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index e25cc8bdb0..4c5670c399 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -92,6 +92,7 @@ public void Stop () _timedEvents?.Remove (_timeout); } + _host.MouseState = MouseState.None; _down = false; } From ad8248a84997a9d4fe41722397692ad7984bdd1a Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 10 Jul 2025 01:15:51 +0100 Subject: [PATCH 51/56] Fix namespaces in class diagram --- Terminal.Gui/Drivers/V2/V2.cd | 333 ++++++++++++++++------------------ 1 file changed, 159 insertions(+), 174 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/V2.cd b/Terminal.Gui/Drivers/V2/V2.cd index f5004db3ce..440e918849 100644 --- a/Terminal.Gui/Drivers/V2/V2.cd +++ b/Terminal.Gui/Drivers/V2/V2.cd @@ -6,7 +6,7 @@ - + @@ -18,7 +18,7 @@ - + @@ -27,44 +27,33 @@ - + QIAACAAAACAEAAAAAAAAAAAkAAAAAAAAAwAAAAAAABA= - ConsoleDrivers\V2\WindowsInput.cs + Drivers\V2\WindowsInput.cs - + AAAAAAAAACAEAAAAQAAAAAAgAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\NetInput.cs + Drivers\V2\NetInput.cs - + AAAAAAAAACAEAQAAAAAAAAAgACAAAAAAAAAAAAAAAAo= - ConsoleDrivers\V2\ConsoleInput.cs + Drivers\V2\ConsoleInput.cs - + - - - - - - - - - - - - + @@ -73,7 +62,7 @@ - + @@ -82,7 +71,7 @@ - + @@ -93,7 +82,7 @@ - + @@ -105,7 +94,7 @@ - + @@ -119,12 +108,11 @@ - QQQAAAAQACABJQQAABAAAQAAACAAAAACAAEAAACAEgg= - ConsoleDrivers\V2\MainLoop.cs + QQQAAAAQACABJQQAABAAAQAAACAAAAACAIEAAAAAEgg= + Drivers\V2\MainLoop.cs - @@ -133,25 +121,25 @@ - + IAAAIAEiCAIABAAAABQAAAAAABAAAQQAIQIABAAACgg= - ConsoleDrivers\V2\MainLoopCoordinator.cs + Drivers\V2\MainLoopCoordinator.cs - + AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAACBAAAAA= - ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs + Drivers\AnsiResponseParser\AnsiResponseParser.cs - + @@ -159,29 +147,29 @@ AwAAAAAAAIAAAECIBgAEQIAAAAEMRgAACAAAKABAgAA= - ConsoleDrivers\V2\OutputBuffer.cs + Drivers\V2\OutputBuffer.cs - + - AEAAAAAAACAAAAAAAAAAAAAAAAAAQAAAMACAAAEAgAk= - ConsoleDrivers\V2\NetOutput.cs + AEAAAAAAACAAAAAAAAAQAAAAAAAAQAAAMACAAAEAgAk= + Drivers\V2\NetOutput.cs - + - AEAAABACACAAhAAAAAAAACCAAAgAQAAIMAAAAAEAgAQ= - ConsoleDrivers\V2\WindowsOutput.cs + AEAAABACACAAhAAAAAAQACCAAAgAYAAIMAAAAAEAgAQ= + Drivers\V2\WindowsOutput.cs - + - + @@ -192,7 +180,7 @@ AQAkEAAAAASAiAAEAgwgAAAABAIAAAAAAAAAAAAEAAA= - ConsoleDrivers\V2\InputProcessor.cs + Drivers\V2\InputProcessor.cs @@ -201,28 +189,28 @@ - + AAAAAAAAAAAACBAAAgAAAEAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\NetInputProcessor.cs + Drivers\V2\NetInputProcessor.cs - + AQAAAAAAAAAACAAAAgAAAAAAAgAEAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\WindowsInputProcessor.cs + Drivers\V2\WindowsInputProcessor.cs - + BAAAAAAAAAgAAAAAAAAAAAAAIAAAAAAAQAAAAAAAAAA= - ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs + Drivers\AnsiResponseParser\AnsiMouseParser.cs - + @@ -230,41 +218,41 @@ AQcgAAAAAKBAgFEIBBgAQJEAAjkaQiIAGQADKABDgAQ= - ConsoleDrivers\V2\ConsoleDriverFacade.cs + Drivers\V2\ConsoleDriverFacade.cs - + AAQAACAAIAAAIAACAESQAAQAACGAAAAAAAAAAAAAQQA= - ConsoleDrivers\AnsiResponseParser\AnsiRequestScheduler.cs + Drivers\AnsiResponseParser\AnsiRequestScheduler.cs - + - + - + - UAiASAAAEICQALAAQAAAKAAAoAIAAABAAQIAJiAQASQ= - ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs + UAiASAAAEICQALCAQAAAKAAAoAIAAABAAQIAJiAQASQ= + Drivers\AnsiResponseParser\AnsiResponseParser.cs @@ -273,296 +261,293 @@ - + AAAABAAAAAAAAAAAAgAAAAAAACAAAAAAAAUAAAAIAAA= - ConsoleDrivers\V2\MouseInterpreter.cs + Drivers\V2\MouseInterpreter.cs - + AAAAAAAAAMwAIAAAAAAAAAAAABCAAAAAAAAABAAEAAg= - ConsoleDrivers\V2\MouseButtonStateEx.cs + Drivers\V2\MouseButtonStateEx.cs - + AAAAAAAAAAIAACAAAAAAAIBAAAAAAACAAAAAAAgAAAA= - ConsoleDrivers\AnsiResponseParser\StringHeld.cs + Drivers\AnsiResponseParser\StringHeld.cs - + AAAAAAAAgAIAACAAAAAAAIBAAAAAAACAAAAAAAAAAAA= - ConsoleDrivers\AnsiResponseParser\GenericHeld.cs + Drivers\AnsiResponseParser\GenericHeld.cs - + AAAAAAAAAEAAAAAAAEAAAAACAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\AnsiEscapeSequenceRequest.cs + Drivers\AnsiEscapeSequenceRequest.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAgAAEAAAA= - ConsoleDrivers\AnsiEscapeSequence.cs + Drivers\AnsiEscapeSequence.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAgACBAAAAACBAAAAA= - ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs - - - - - - hEK4FAgAqARIspQeBwoUgTGgACNL0AIAESLKoggBSw8= - Application\Application.cs + Drivers\AnsiResponseParser\AnsiResponseParser.cs - - - - AABAAAAAIAAIAgQQAAAAAQAAAAAAAAAAQAAKgAAAAAI= - Application\ApplicationImpl.cs - - - - - - - + - QAAAAAgABAEIBgAQAAAAAQBAAAAAgAEAAAAKgIAAAgI= - ConsoleDrivers\V2\ApplicationV2.cs + QAAgAAgABAEIBgAQAAAAAQAAAAAAgAEAAAAKAIAAEgI= + Drivers\V2\ApplicationV2.cs - - - - 3/v2dzPLvbb/5+LOHuv1x0dem3Y57v/8c6afz2/e/Y8= - View\View.Adornments.cs - - - - + AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\WindowsKeyConverter.cs + Drivers\V2\WindowsKeyConverter.cs - + AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\NetKeyConverter.cs + Drivers\V2\NetKeyConverter.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAE= - ConsoleDrivers\AnsiResponseParser\Keyboard\AnsiKeyboardParser.cs + Drivers\AnsiResponseParser\Keyboard\AnsiKeyboardParser.cs - + AIAAAAAAAAAAAAEAAAAAAAAAAEIAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\ToplevelTransitionManager.cs + Drivers\V2\ToplevelTransitionManager.cs - - - - AAAAAAAAAAIgAAAAAAEQAAAAAAAAABAAgAAAAAAAEAA= - ConsoleDrivers\V2\Logging.cs - - - + AAAAgAAAAAAAAAAEAAAAABAAAAAACAAAAAAAAAAAACA= - ConsoleDrivers\V2\WindowSizeMonitor.cs + Drivers\V2\WindowSizeMonitor.cs - + AAACIAAAAAAAAAAAAAAAAAQQAAAAAAAAAAAAAAAACAA= - ConsoleDrivers\AnsiResponseParser\Keyboard\AnsiKeyboardParserPattern.cs + Drivers\AnsiResponseParser\Keyboard\AnsiKeyboardParserPattern.cs - + - AAACAAAAAAAAABAAAAAAAAAQAACAAAAAAAAAAAAAAAA= - ConsoleDrivers\AnsiResponseParser\Keyboard\CsiKeyPattern.cs + AAACQAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA= + Drivers\AnsiResponseParser\Keyboard\CsiKeyPattern.cs - + AAACAAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA= - ConsoleDrivers\AnsiResponseParser\Keyboard\EscAsAltPattern.cs + Drivers\AnsiResponseParser\Keyboard\EscAsAltPattern.cs - + AAACAAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA= - ConsoleDrivers\AnsiResponseParser\Keyboard\Ss3Pattern.cs + Drivers\AnsiResponseParser\Keyboard\Ss3Pattern.cs - + + + + AABgAAAAIAAIAgQUAAAAAQAAAAAAAAAAQAAKAAAAEAI= + App\ApplicationImpl.cs + + + + + + + gEK4FIgYOAQIuhQeBwoUgSCgAAJL0AACESIKoAiBWw8= + App\Application.cs + + + + + + 27u2V3Pfvf7/x/LOXur1x0de3zZt7v/8c+bfzX/e/c8= + ViewBase\View.Adornments.cs + + + + + + + AAAIEAAAAAIgAYAAAAEQABAAAAAAABAAgAAAAAAAEAA= + App\Logging.cs + + + AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAI= - ConsoleDrivers\V2\IConsoleInput.cs + Drivers\V2\IConsoleInput.cs - + QAQAAAAAAAABIQQAAAAAAAAAAAAAAAACAAAAAAAAEAA= - ConsoleDrivers\V2\IMainLoop.cs + Drivers\V2\IMainLoop.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA= - ConsoleDrivers\V2\IConsoleOutput.cs + Drivers\V2\IConsoleOutput.cs - + AQAAAAAAAIAAAEAIAAAAQIAAAAEMRgAACAAAKABAgAA= - ConsoleDrivers\V2\IOutputBuffer.cs + Drivers\V2\IOutputBuffer.cs - + AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAAAAAAEAAA= - ConsoleDrivers\V2\IInputProcessor.cs + Drivers\V2\IInputProcessor.cs - + AAAAAAAAAAIAACAAAAAAAIBAAAAAAACAAAAAAAAAAAA= - ConsoleDrivers\AnsiResponseParser\IHeld.cs + Drivers\AnsiResponseParser\IHeld.cs - + AAAAQAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAJAAAAAA= - ConsoleDrivers\AnsiResponseParser\IAnsiResponseParser.cs + Drivers\AnsiResponseParser\IAnsiResponseParser.cs - - - - AAAAAAAAAAAIAgQQAAAAAQAAAAAAAAAAAAAKgAAAAAI= - Application\IApplication.cs - - - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQIAAAAAAAA= - ConsoleDrivers\V2\IMainLoopCoordinator.cs + Drivers\V2\IMainLoopCoordinator.cs - + AAAAAAAAAAAAAAAEAAAAAAAAAAAACAAAAAAAAAAAAAA= - ConsoleDrivers\V2\IWindowSizeMonitor.cs - - - - - - - - - BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA= - Application\ITimedEvents.cs + Drivers\V2\IWindowSizeMonitor.cs - + AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\IKeyConverter.cs + Drivers\V2\IKeyConverter.cs - + AIAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\IToplevelTransitionManager.cs + Drivers\V2\IToplevelTransitionManager.cs - + AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\IConsoleDriverFacade.cs + Drivers\V2\IConsoleDriverFacade.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\INetInput.cs + Drivers\V2\INetInput.cs - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ConsoleDrivers\V2\IWindowsInput.cs + Drivers\V2\IWindowsInput.cs + + + + + + + + + CAIAAAAAAQAAAAAAAAAABEAAAAAABAAAAAAAAAAAAAA= + App\ITimedEvents.cs + + + + + + AAAgAAAAAAAIAgQUAAAAAQAAAAAAAAAAAAAKAAAAEAI= + App\IApplication.cs - + AAAAAAAAAAAAAAAAAAAACAAAAAAIAAIAAAAAAAAAAAA= - ConsoleDrivers\AnsiResponseParser\AnsiResponseParserState.cs + Drivers\AnsiResponseParser\AnsiResponseParserState.cs From ee22ef880af241a9ac7e6613195a93790df5d0e7 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 10 Jul 2025 01:17:09 +0100 Subject: [PATCH 52/56] Apply @BDisp suggested fix --- Tests/UnitTests/Application/ApplicationTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index 75d63f0e14..c92dcedfed 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -1129,11 +1129,15 @@ public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string drive Task.Run (() => { - Task.Delay (300).Wait (); + while (!Application.Initialized) + { + Task.Delay (300).Wait (); + } }).ContinueWith ( (t, _) => { // no longer loading + Assert.True (Application.Initialized); Application.Invoke (() => { result = true; From d47defc98e8a057ffee5cb3a78cf2c32a3285b2f Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 10 Jul 2025 01:29:40 +0100 Subject: [PATCH 53/56] Fix class diagrams --- .../Views/Autocomplete/Autocomplete.cd | 20 +- .../CollectionNavigation.cd | 58 +++--- Terminal.Gui/Views/FileDialogs/FileDialog.cd | 197 ++++++++---------- 3 files changed, 123 insertions(+), 152 deletions(-) diff --git a/Terminal.Gui/Views/Autocomplete/Autocomplete.cd b/Terminal.Gui/Views/Autocomplete/Autocomplete.cd index ca47d6435b..8b0f16b4f0 100644 --- a/Terminal.Gui/Views/Autocomplete/Autocomplete.cd +++ b/Terminal.Gui/Views/Autocomplete/Autocomplete.cd @@ -1,13 +1,13 @@  - + AAAgAABAAQIAAAAAAAAAAAAABAAAIAQAgAEIAggAIAA= Core\Autocomplete\AppendAutocomplete.cs - + AAQgAAAAAUAAIAAAAAIAAAAAAAEAIAQIgQAIQAAAMBA= @@ -15,10 +15,10 @@ - + - + Core\Autocomplete\PopupAutocomplete.cs @@ -29,7 +29,7 @@ Core\Autocomplete\PopupAutocomplete.cs - + CEAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAIAA= @@ -37,28 +37,28 @@ - + AAAAAAAAAAAAAEAAAAAABAAAAAAAAAAAAAAAAAAAAAE= Core\Autocomplete\Suggestion.cs - + AAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAgAAAAAAAAAA= Views\TextField.cs - + AAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAgAAAAAAAAAA= Views\TextView.cs - + AAQgAAAAAUAAIAAAAAAAAAAAAAEAIAQIgQAIQAAAMBA= @@ -68,7 +68,7 @@ - + AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA= diff --git a/Terminal.Gui/Views/CollectionNavigation/CollectionNavigation.cd b/Terminal.Gui/Views/CollectionNavigation/CollectionNavigation.cd index 7236c3adfe..02bb4adb16 100644 --- a/Terminal.Gui/Views/CollectionNavigation/CollectionNavigation.cd +++ b/Terminal.Gui/Views/CollectionNavigation/CollectionNavigation.cd @@ -9,7 +9,7 @@ - + AAgEAAAAAAAQAAAIAAEAAgAAAAAABAAEAAAAACwAAAA= @@ -20,7 +20,7 @@ - + AAAAAAAAAAAAQAAAAAAAAgAAAAAAAAAEAAAAAAAAAAA= @@ -28,7 +28,7 @@ - + AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQA= @@ -36,14 +36,14 @@ - + AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAEAAAAIAAAAAA= Views\CollectionNavigation\TableCollectionNavigator.cs - + AAE+ASAkEnAAABAAKGAggYAZJAIAABEAcBAaAwAQIAA= @@ -54,25 +54,7 @@ - - - - - - - iIY4LQFUHDKVIHIESBgigQcFT6GxhBDABGJItBQAwAQ= - Views\FileDialog.cs - - - - - - - AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAA= - Views\FileDialogCollectionNavigator.cs - - - + QwUeAxwgICIAcABIABeR0oBAkhoFGGOBDABgAN3oPEI= @@ -80,7 +62,7 @@ - + AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAA= @@ -88,7 +70,7 @@ - + UwAGySBgBSBGMAQgIiCaBDUItJIBSAWwRMQOSgQCwJI= @@ -99,21 +81,39 @@ - + + + + + + + iIY4LQFUHDKVIHIESBoigQcFT6GxhBDABGJItBQAwAQ= + Views\FileDialogs\FileDialog.cs + + + + + + + AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAA= + Views\FileDialogs\FileDialogCollectionNavigator.cs + + + AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA= Views\CollectionNavigation\ICollectionNavigatorMatcher.cs - + AAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= Views\CollectionNavigation\IListCollectionNavigator.cs - + AAgAAAAAAAAAAAAIAAAAAAAAAAAABAAAAAAAACgAAAA= diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cd b/Terminal.Gui/Views/FileDialogs/FileDialog.cd index a8174bf7e7..22893eb03a 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cd +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cd @@ -1,6 +1,21 @@  - + + + + AAACAAAAAAgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + FileServices\DefaultSearchMatcher.cs + + + + + + + ABAIQAIIIAAAAAACQAAAAIQAAAQAAIAAAQAAAAAIAAI= + FileServices\FileSystemInfoStats.cs + + + @@ -8,190 +23,146 @@ - - - Windows\FileDialog.cs - - - + - Views\FileDialog.cs + Views\FileDialogs\FileDialog.cs - g4YYDAEXEDKZgHMFyFAikQCFSKUQhRDABqJIlBSAwgw= - Views\FileDialog.cs - - - - - - - - - - - - - - AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAA= - FileServices\AllowedType.cs + iIY4LQFUHDKVIHIESBoigQcFT6GxhBDABGJItBQAwAQ= + Views\FileDialogs\FileDialog.cs - - - - AAAAAAAAAAAgAAAEAAAAAAAAAAAAAAAAAAgAAAAABAA= - FileServices\AllowedType.cs - - - - - + + - AAAAAAAAAAAAACAAAAAAACAAAAEAAAAAAAAAAAAAgAA= - FileServices\DefaultFileOperations.cs + GgBAAAFHAAAAuAAAAAAAEAQQBYAAKREAAAAYQCCAAAA= + Views\FileDialogs\FileDialogStyle.cs - - - + + + + + - AAACAAAAAAgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - FileServices\DefaultSearchMatcher.cs + AAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAA= + Views\FileDialogs\FilesSelectedEventArgs.cs - - + AQABAgEAAAAAAAAAIACAAAAAAAAAAQAAAAAAAAAADAI= - FileServices\FileDialogHistory.cs + Views\FileDialogs\FileDialogHistory.cs - - - - - - - AAAAEAAAAAAAAAIEAAAAAAAAAAAAAAAAAAAAAAAAAAA= - FileServices\FileDialogRootTreeNode.cs - - - + AABAABAAAAAAAAIAAAAEQAAAAAAAQAAAAgAAAAAAAAI= - FileServices\FileDialogState.cs + Views\FileDialogs\FileDialogState.cs - - + + - GgBIAAFEAAAAuAAAAgAEEASABQACKRkAAAEYACCAAAA= - FileServices\FileDialogStyle.cs + AAAAAAAAAAAgAAAEAAAAAAAAAAAAAAAAAAgAAAAABAA= + Views\FileDialogs\AllowedType.cs + - - + + - EAAACAAAAAAAAAAAQAAAAAQAAAAAQAAAAAAAAAQACAA= - FileServices\FileDialogTreeBuilder.cs + AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAA= + Views\FileDialogs\AllowedType.cs - - + + - + - AAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAA= - FileServices\FilesSelectedEventArgs.cs + AIACAAABQAAAAAAAAAAACAAAIACAAAAAAAIAAAAAAAA= + Text\NerdFonts.cs - - + + - ABAIQAIIIAAAAAACQAAAAIQAAAQAAIAAAQAAAAAIAAI= - FileServices\FileSystemInfoStats.cs + EAAAAAAAAAAAAAAAAAAABAAwAAAAQAAAAABAAAAACAA= + FileServices\FileSystemTreeBuilder.cs + - - - - - + + - AIACAAABQAAAAAAAAAAAAAAAIACAAAAAAAIAAAQAAAA= - Views\NerdFonts.cs + AAAAAAAAAAAAACAAAAAAACAAAAEAAAAAAAAAAAAAgAA= + Views\FileDialogs\DefaultFileOperations.cs + - - + + - AAAAAAAAAgAAAAAAAAAQAAAAAAAEAAAAAAAAAAAAAAA= - FileServices\FileDialogIconGetterArgs.cs + AgAAAAAAAEAAAAAAAAAAAAEAAAAAAACAAAAAAAAAAAA= + FileServices\FileSystemColorProvider.cs - - + + + + ABAAAAAAAACAQAAAAAAAAEAgAAAAAQAAAAAAAAAAAiA= + FileServices\FileSystemIconProvider.cs + + + + AQAAAAAAIAACAEAACAAAAAACAAAEAAAEAAAAgAgBBAA= - Views\FileDialogTableSource.cs + Views\FileDialogs\FileDialogTableSource.cs - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAA= - FileServices\AllowedType.cs - - - + AAAAAAAAAAAAACAAAAAAAAAAAAEAAAAAAAAAAAAAgAA= FileServices\IFileOperations.cs - + AAACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= FileServices\ISearchMatcher.cs - - + + - AAAAABAAAAAAACAAAAAAAAAAAAAEAAAAAAAAAAAAAAA= - Views\OpenDialog.cs + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAA= + Views\FileDialogs\AllowedType.cs - - - + + + - AAAAAAAAAACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAA= - FileServices\FileDialogIconGetterContext.cs + AAAAABAAAAAAACAAAAAAAAAAAAAEAAAAAAAAAAAAAAA= + Views\FileDialogs\OpenMode.cs - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAA= - FileServices\FileDialogRootTreeNode.cs - - \ No newline at end of file From c477cce3a30a15ac86a4117c06bb80f96cb6168f Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 10 Jul 2025 01:47:09 +0100 Subject: [PATCH 54/56] Add lock --- .../GuiTestContext.cs | 2 +- .../UnitTests/Application/ApplicationTests.cs | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index 773a4c97dd..6321a86cbc 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -88,7 +88,7 @@ internal GuiTestContext (Func topLevelBuilder, int width, int height, } // Wait for booting to complete with a timeout to avoid hangs - if (!booting.WaitAsync (TimeSpan.FromSeconds (5)).Result) + if (!booting.WaitAsync (TimeSpan.FromSeconds (10)).Result) { throw new TimeoutException ("Application failed to start within the allotted time."); } diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index c92dcedfed..b504b94846 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -1114,6 +1114,8 @@ public void Run_t_Does_Not_Creates_Top_Without_Init () private class TestToplevel : Toplevel { } + private readonly object _forceDriverLock = new (); + [Theory] [InlineData ("v2win", typeof (ConsoleDriverFacade))] [InlineData ("v2net", typeof (ConsoleDriverFacade))] @@ -1127,24 +1129,29 @@ public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string drive var result = false; - Task.Run (() => - { - while (!Application.Initialized) - { - Task.Delay (300).Wait (); - } - }).ContinueWith ( - (t, _) => + lock (_forceDriverLock) + { + Task.Run (() => + { + while (!Application.Initialized) + { + Task.Delay (300).Wait (); + } + }) + .ContinueWith ( + (t, _) => + { + // no longer loading + Assert.True (Application.Initialized); + + Application.Invoke (() => { - // no longer loading - Assert.True (Application.Initialized); - Application.Invoke (() => - { - result = true; - Application.RequestStop (); - }); - }, - TaskScheduler.FromCurrentSynchronizationContext ()); + result = true; + Application.RequestStop (); + }); + }, + TaskScheduler.FromCurrentSynchronizationContext ()); + } Application.ForceDriver = driverName; Application.Run (); From 28787e81021335a551b3ac259b77eab634eee94b Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 10 Jul 2025 01:59:27 +0100 Subject: [PATCH 55/56] Make TimeSpan.Zero definetly run --- Terminal.Gui/App/TimedEvents.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Terminal.Gui/App/TimedEvents.cs b/Terminal.Gui/App/TimedEvents.cs index e76276dacf..073da90596 100644 --- a/Terminal.Gui/App/TimedEvents.cs +++ b/Terminal.Gui/App/TimedEvents.cs @@ -26,6 +26,13 @@ private void AddTimeout (TimeSpan time, Timeout timeout) lock (_timeoutsLockToken) { long k = (DateTime.UtcNow + time).Ticks; + + // if user wants to run as soon as possible set timer such that it expires right away (no race conditions) + if (time == TimeSpan.Zero) + { + k -= 100; + } + _timeouts.Add (NudgeToUniqueKey (k), timeout); Added?.Invoke (this, new TimeoutEventArgs (timeout, k)); } From 5fc2e48d92a5dee08d134721502405a6a5173d14 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 10 Jul 2025 02:05:48 +0100 Subject: [PATCH 56/56] Fix duplicate entry in package props --- Directory.Packages.props | 3 --- 1 file changed, 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2dc59e0df3..3d57c73d30 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,9 +16,6 @@ - - -