Skip to content

Commit ec827e9

Browse files
tznindtig
andauthored
Fixes #4172 Timeout revamp and remove continuous mouse (#4173)
* Remove continous press code from Application * WIP prototype code to handle continuous press as subcomponent of View * Prototype with Button * Implement CWP * Move to seperate classes and prevent double entry to Start * Fix repeat clicking when moving mouse by removing phantom click code (old implementation of WantContinuousButtonPressed) * Remove initial tick because it results in double activation e.g. button firing twice immediately as mouse is pressed down. * Refactor DatePicker lamdas * WIP investigate subcomponents instead of statics * Add IMouseGrabHandler to IApplication * Make mouse grabbing non static activity * Make MouseHeldDown suppress when null fields e.g. app not initialized in tests * Update test and remove dependency on Application * Fix other mouse click and hold tests * Code cleanup * Update class diagram * Fix bad xml doc references * Fix timed events not getting passed through in v2 applications * Make timed events nullable for tests that dont create an Application * Remove strange blocking test * WIP remove all idles and replace with zero timeouts * Fix build of tests * Fix unit tests * Add wakeup call back in * Comment out incredibly complicated test and fix others * Fix test * test fix * Make Post execute immediately if already on UI thread * Re enable test and simplify Invoke to just execute if in UI thread (up front) * Remove xml doc references to idles * Remove more references to idles * Make Screen initialization threadsafe * Add more exciting timeouts * WIP add tests * fix log * fix test * make continuous key press use smoth acceleration * Rename _lock to _lockScreen * Remove section on idles, they are not a thing anymore - and they kinda never were. * Add nullable enable * Add xml comment * Fix namings and cleanup code * xmldoc fix * Rename LockAndRunTimers to just RunTimers * Rename AddTimeout and RemoveTimeout (and event) to just Add/Remove * Update description of MainLoop * Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver * Again? Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver * Revert Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver * When mouse is released from MouseHeldDown reset host MouseState * Fix namespaces in class diagram * Apply @BDisp suggested fix * Fix class diagrams * Add lock * Make TimeSpan.Zero definetly run * Fix duplicate entry in package props --------- Co-authored-by: Tig <tig@users.noreply.github.com>
1 parent 23cacae commit ec827e9

File tree

68 files changed

+1736
-1396
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1736
-1396
lines changed

Directory.Packages.props

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
1717
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
1818
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
19-
20-
<PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
21-
2219
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.2,10)" />
2320
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
2421
<PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" />

Examples/UICatalog/Scenarios/Threading.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ public class Threading : Scenario
1919
private ListView _logJob;
2020
private Action _sync;
2121

22+
private LogarithmicTimeout _logarithmicTimeout;
23+
private NumericUpDown _numberLog;
24+
private Button _btnLogarithmic;
25+
private object _timeoutObj;
26+
27+
private SmoothAcceleratingTimeout _smoothTimeout;
28+
private NumericUpDown _numberSmooth;
29+
private Button _btnSmooth;
30+
private object _timeoutObjSmooth;
31+
2232
public override void Main ()
2333
{
2434
Application.Init ();
@@ -82,6 +92,35 @@ public override void Main ()
8292

8393
var text = new TextField { X = 1, Y = 3, Width = 100, Text = "Type anything after press the button" };
8494

95+
_btnLogarithmic = new Button ()
96+
{
97+
X = 50,
98+
Y = 4,
99+
Text = "Start Log Counter"
100+
};
101+
_btnLogarithmic.Accepting += StartStopLogTimeout;
102+
103+
_numberLog = new NumericUpDown ()
104+
{
105+
X = Pos.Right (_btnLogarithmic),
106+
Y = 4,
107+
};
108+
109+
_btnSmooth = new Button ()
110+
{
111+
X = Pos.Right (_numberLog),
112+
Y = 4,
113+
Text = "Start Smooth Counter"
114+
};
115+
_btnSmooth.Accepting += StartStopSmoothTimeout;
116+
117+
_numberSmooth = new NumericUpDown ()
118+
{
119+
X = Pos.Right (_btnSmooth),
120+
Y = 4,
121+
};
122+
123+
85124
var btnAction = new Button { X = 80, Y = 10, Text = "Load Data Action" };
86125
btnAction.Accepting += (s, e) => _action.Invoke ();
87126
var btnLambda = new Button { X = 80, Y = 12, Text = "Load Data Lambda" };
@@ -107,6 +146,10 @@ public override void Main ()
107146
_btnActionCancel,
108147
_logJob,
109148
text,
149+
_btnLogarithmic,
150+
_numberLog,
151+
_btnSmooth,
152+
_numberSmooth,
110153
btnAction,
111154
btnLambda,
112155
btnHandler,
@@ -129,6 +172,51 @@ void Win_Loaded (object sender, EventArgs args)
129172
Application.Shutdown ();
130173
}
131174

175+
private bool LogTimeout ()
176+
{
177+
_numberLog.Value++;
178+
_logarithmicTimeout.AdvanceStage ();
179+
return true;
180+
}
181+
private bool SmoothTimeout ()
182+
{
183+
_numberSmooth.Value++;
184+
_smoothTimeout.AdvanceStage ();
185+
return true;
186+
}
187+
188+
private void StartStopLogTimeout (object sender, CommandEventArgs e)
189+
{
190+
if (_timeoutObj != null)
191+
{
192+
_btnLogarithmic.Text = "Start Log Counter";
193+
Application.TimedEvents.Remove (_timeoutObj);
194+
_timeoutObj = null;
195+
}
196+
else
197+
{
198+
_btnLogarithmic.Text = "Stop Log Counter";
199+
_logarithmicTimeout = new LogarithmicTimeout (TimeSpan.FromMilliseconds (500), LogTimeout);
200+
_timeoutObj = Application.TimedEvents.Add (_logarithmicTimeout);
201+
}
202+
}
203+
204+
private void StartStopSmoothTimeout (object sender, CommandEventArgs e)
205+
{
206+
if (_timeoutObjSmooth != null)
207+
{
208+
_btnSmooth.Text = "Start Smooth Counter";
209+
Application.TimedEvents.Remove (_timeoutObjSmooth);
210+
_timeoutObjSmooth = null;
211+
}
212+
else
213+
{
214+
_btnSmooth.Text = "Stop Smooth Counter";
215+
_smoothTimeout = new SmoothAcceleratingTimeout (TimeSpan.FromMilliseconds (500), TimeSpan.FromMilliseconds (50), 0.5, SmoothTimeout);
216+
_timeoutObjSmooth = Application.TimedEvents.Add (_smoothTimeout);
217+
}
218+
}
219+
132220
private async void CallLoadItemsAsync ()
133221
{
134222
_cancellationTokenSource = new CancellationTokenSource ();

Terminal.Gui/App/Application.Mouse.cs

Lines changed: 13 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -19,122 +19,16 @@ public static partial class Application // Mouse handling
1919
[ConfigurationProperty (Scope = typeof (SettingsScope))]
2020
public static bool IsMouseDisabled { get; set; }
2121

22-
/// <summary>Gets <see cref="View"/> that has registered to get continuous mouse button pressed events.</summary>
23-
public static View? WantContinuousButtonPressedView { get; internal set; }
24-
2522
/// <summary>
26-
/// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to
27-
/// this view until the view calls <see cref="UngrabMouse"/> or the mouse is released.
23+
/// Static reference to the current <see cref="IApplication"/> <see cref="IMouseGrabHandler"/>.
2824
/// </summary>
29-
public static View? MouseGrabView { get; private set; }
30-
31-
/// <summary>Invoked when a view wants to grab the mouse; can be canceled.</summary>
32-
public static event EventHandler<GrabMouseEventArgs>? GrabbingMouse;
33-
34-
/// <summary>Invoked when a view wants un-grab the mouse; can be canceled.</summary>
35-
public static event EventHandler<GrabMouseEventArgs>? UnGrabbingMouse;
36-
37-
/// <summary>Invoked after a view has grabbed the mouse.</summary>
38-
public static event EventHandler<ViewEventArgs>? GrabbedMouse;
39-
40-
/// <summary>Invoked after a view has un-grabbed the mouse.</summary>
41-
public static event EventHandler<ViewEventArgs>? UnGrabbedMouse;
42-
43-
/// <summary>
44-
/// Grabs the mouse, forcing all mouse events to be routed to the specified view until <see cref="UngrabMouse"/>
45-
/// is called.
46-
/// </summary>
47-
/// <param name="view">View that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.</param>
48-
public static void GrabMouse (View? view)
25+
public static IMouseGrabHandler MouseGrabHandler
4926
{
50-
if (view is null || RaiseGrabbingMouseEvent (view))
51-
{
52-
return;
53-
}
54-
55-
RaiseGrabbedMouseEvent (view);
56-
57-
if (Initialized)
58-
{
59-
// MouseGrabView is a static; only set if the application is initialized.
60-
MouseGrabView = view;
61-
}
27+
get => ApplicationImpl.Instance.MouseGrabHandler;
28+
set => ApplicationImpl.Instance.MouseGrabHandler = value ??
29+
throw new ArgumentNullException(nameof(value));
6230
}
6331

64-
/// <summary>Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.</summary>
65-
public static void UngrabMouse ()
66-
{
67-
if (MouseGrabView is null)
68-
{
69-
return;
70-
}
71-
72-
#if DEBUG_IDISPOSABLE
73-
if (View.EnableDebugIDisposableAsserts)
74-
{
75-
ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
76-
}
77-
#endif
78-
79-
if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
80-
{
81-
View view = MouseGrabView;
82-
MouseGrabView = null;
83-
RaiseUnGrabbedMouseEvent (view);
84-
}
85-
}
86-
87-
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
88-
private static bool RaiseGrabbingMouseEvent (View? view)
89-
{
90-
if (view is null)
91-
{
92-
return false;
93-
}
94-
95-
var evArgs = new GrabMouseEventArgs (view);
96-
GrabbingMouse?.Invoke (view, evArgs);
97-
98-
return evArgs.Cancel;
99-
}
100-
101-
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
102-
private static bool RaiseUnGrabbingMouseEvent (View? view)
103-
{
104-
if (view is null)
105-
{
106-
return false;
107-
}
108-
109-
var evArgs = new GrabMouseEventArgs (view);
110-
UnGrabbingMouse?.Invoke (view, evArgs);
111-
112-
return evArgs.Cancel;
113-
}
114-
115-
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
116-
private static void RaiseGrabbedMouseEvent (View? view)
117-
{
118-
if (view is null)
119-
{
120-
return;
121-
}
122-
123-
GrabbedMouse?.Invoke (view, new (view));
124-
}
125-
126-
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
127-
private static void RaiseUnGrabbedMouseEvent (View? view)
128-
{
129-
if (view is null)
130-
{
131-
return;
132-
}
133-
134-
UnGrabbedMouse?.Invoke (view, new (view));
135-
}
136-
137-
13832
/// <summary>
13933
/// INTERNAL API: Called when a mouse event is raised by the driver. Determines the view under the mouse and
14034
/// calls the appropriate View mouse event handlers.
@@ -198,15 +92,6 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
19892
return;
19993
}
20094

201-
if (Initialized)
202-
{
203-
WantContinuousButtonPressedView = deepestViewUnderMouse switch
204-
{
205-
{ WantContinuousButtonPressed: true } => deepestViewUnderMouse,
206-
_ => null
207-
};
208-
}
209-
21095
// May be null before the prior condition or the condition may set it as null.
21196
// So, the checking must be outside the prior condition.
21297
if (deepestViewUnderMouse is null)
@@ -258,12 +143,7 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
258143

259144
RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);
260145

261-
if (Initialized)
262-
{
263-
WantContinuousButtonPressedView = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null;
264-
}
265-
266-
while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { })
146+
while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabHandler.MouseGrabView is not { })
267147
{
268148
if (deepestViewUnderMouse is Adornment adornmentView)
269149
{
@@ -315,35 +195,35 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
315195

316196
internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
317197
{
318-
if (MouseGrabView is { })
198+
if (MouseGrabHandler.MouseGrabView is { })
319199
{
320200
#if DEBUG_IDISPOSABLE
321-
if (View.EnableDebugIDisposableAsserts && MouseGrabView.WasDisposed)
201+
if (View.EnableDebugIDisposableAsserts && MouseGrabHandler.MouseGrabView.WasDisposed)
322202
{
323-
throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
203+
throw new ObjectDisposedException (MouseGrabHandler.MouseGrabView.GetType ().FullName);
324204
}
325205
#endif
326206

327207
// If the mouse is grabbed, send the event to the view that grabbed it.
328208
// The coordinates are relative to the Bounds of the view that grabbed the mouse.
329-
Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
209+
Point frameLoc = MouseGrabHandler.MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
330210

331211
var viewRelativeMouseEvent = new MouseEventArgs
332212
{
333213
Position = frameLoc,
334214
Flags = mouseEvent.Flags,
335215
ScreenPosition = mouseEvent.ScreenPosition,
336-
View = deepestViewUnderMouse ?? MouseGrabView
216+
View = deepestViewUnderMouse ?? MouseGrabHandler.MouseGrabView
337217
};
338218

339219
//System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
340-
if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
220+
if (MouseGrabHandler.MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
341221
{
342222
return true;
343223
}
344224

345225
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
346-
if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
226+
if (MouseGrabHandler.MouseGrabView is null && deepestViewUnderMouse is Adornment)
347227
{
348228
// The view that grabbed the mouse has been disposed
349229
return true;

Terminal.Gui/App/Application.Run.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,9 @@ public static RunState Begin (Toplevel toplevel)
8989
//#endif
9090

9191
// Ensure the mouse is ungrabbed.
92-
if (MouseGrabView is { })
92+
if (MouseGrabHandler.MouseGrabView is { })
9393
{
94-
UngrabMouse ();
95-
MouseGrabView = null;
94+
MouseGrabHandler.UngrabMouse ();
9695
}
9796

9897
var rs = new RunState (toplevel);
@@ -366,7 +365,7 @@ public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriv
366365
/// Alternatively, to have a program control the main loop and process events manually, call
367366
/// <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
368367
/// <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
369-
/// <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
368+
/// <see cref="RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
370369
/// return control immediately.
371370
/// </para>
372371
/// <para>

Terminal.Gui/App/Application.Screen.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Terminal.Gui.App;
44

55
public static partial class Application // Screen related stuff
66
{
7+
private static readonly object _lockScreen = new ();
78
private static Rectangle? _screen;
89

910
/// <summary>
@@ -18,19 +19,27 @@ public static Rectangle Screen
1819
{
1920
get
2021
{
21-
if (_screen == null)
22+
lock (_lockScreen)
2223
{
23-
_screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
24+
if (_screen == null)
25+
{
26+
_screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
27+
}
28+
29+
return _screen.Value;
2430
}
25-
return _screen.Value;
2631
}
2732
set
2833
{
2934
if (value is {} && (value.X != 0 || value.Y != 0))
3035
{
3136
throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
3237
}
33-
_screen = value;
38+
39+
lock (_lockScreen)
40+
{
41+
_screen = value;
42+
}
3443
}
3544
}
3645

0 commit comments

Comments
 (0)