1
1
#nullable enable
2
2
using System . Diagnostics ;
3
3
using System . Diagnostics . CodeAnalysis ;
4
+ using Microsoft . CodeAnalysis . Diagnostics ;
4
5
5
6
namespace Terminal . Gui ;
6
7
@@ -80,27 +81,20 @@ public static RunState Begin (Toplevel toplevel)
80
81
{
81
82
ArgumentNullException . ThrowIfNull ( toplevel ) ;
82
83
83
- //#if DEBUG_IDISPOSABLE
84
- // Debug.Assert (!toplevel.WasDisposed);
84
+ //#if DEBUG_IDISPOSABLE
85
+ // Debug.Assert (!toplevel.WasDisposed);
85
86
86
- // if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel)
87
- // {
88
- // Debug.Assert (_cachedRunStateToplevel.WasDisposed);
89
- // }
90
- //#endif
87
+ // if (_cachedRunStateToplevel is { } && _cachedRunStateToplevel != toplevel)
88
+ // {
89
+ // Debug.Assert (_cachedRunStateToplevel.WasDisposed);
90
+ // }
91
+ //#endif
91
92
92
93
// Ensure the mouse is ungrabbed.
93
94
MouseGrabView = null ;
94
95
95
96
var rs = new RunState ( toplevel ) ;
96
97
97
- // View implements ISupportInitializeNotification which is derived from ISupportInitialize
98
- if ( ! toplevel . IsInitialized )
99
- {
100
- toplevel . BeginInit ( ) ;
101
- toplevel . EndInit ( ) ;
102
- }
103
-
104
98
#if DEBUG_IDISPOSABLE
105
99
if ( Top is { } && toplevel != Top && ! TopLevels . Contains ( Top ) )
106
100
{
@@ -176,16 +170,26 @@ public static RunState Begin (Toplevel toplevel)
176
170
Top . HasFocus = false ;
177
171
}
178
172
173
+ // Force leave events for any entered views in the old Top
174
+ if ( GetLastMousePosition ( ) is { } )
175
+ {
176
+ RaiseMouseEnterLeaveEvents ( GetLastMousePosition ( ) ! . Value , new List < View ? > ( ) ) ;
177
+ }
178
+
179
179
Top ? . OnDeactivate ( toplevel ) ;
180
- Toplevel previousCurrent = Top ! ;
180
+ Toplevel previousTop = Top ! ;
181
181
182
182
Top = toplevel ;
183
- Top . OnActivate ( previousCurrent ) ;
183
+ Top . OnActivate ( previousTop ) ;
184
184
}
185
185
}
186
186
187
- toplevel . SetRelativeLayout ( Driver ! . Screen . Size ) ;
188
- toplevel . LayoutSubviews ( ) ;
187
+ // View implements ISupportInitializeNotification which is derived from ISupportInitialize
188
+ if ( ! toplevel . IsInitialized )
189
+ {
190
+ toplevel . BeginInit ( ) ;
191
+ toplevel . EndInit ( ) ; // Calls Layout
192
+ }
189
193
190
194
// Try to set initial focus to any TabStop
191
195
if ( ! toplevel . HasFocus )
@@ -195,15 +199,16 @@ public static RunState Begin (Toplevel toplevel)
195
199
196
200
toplevel . OnLoaded ( ) ;
197
201
198
- Refresh ( ) ;
199
-
200
202
if ( PositionCursor ( ) )
201
203
{
202
- Driver . UpdateCursor ( ) ;
204
+ Driver ? . UpdateCursor ( ) ;
203
205
}
204
206
205
207
NotifyNewRunState ? . Invoke ( toplevel , new ( rs ) ) ;
206
208
209
+ // Force an Idle event so that an Iteration (and Refresh) happen.
210
+ Application . Invoke ( ( ) => { } ) ;
211
+
207
212
return rs ;
208
213
}
209
214
@@ -225,11 +230,12 @@ internal static bool PositionCursor ()
225
230
// If the view is not visible or enabled, don't position the cursor
226
231
if ( mostFocused is null || ! mostFocused . Visible || ! mostFocused . Enabled )
227
232
{
228
- Driver ! . GetCursorVisibility ( out CursorVisibility current ) ;
233
+ CursorVisibility current = CursorVisibility . Invisible ;
234
+ Driver ? . GetCursorVisibility ( out current ) ;
229
235
230
236
if ( current != CursorVisibility . Invisible )
231
237
{
232
- Driver . SetCursorVisibility ( CursorVisibility . Invisible ) ;
238
+ Driver ? . SetCursorVisibility ( CursorVisibility . Invisible ) ;
233
239
}
234
240
235
241
return false ;
@@ -326,7 +332,7 @@ internal static bool PositionCursor ()
326
332
public static T Run < T > ( Func < Exception , bool > ? errorHandler = null , ConsoleDriver ? driver = null )
327
333
where T : Toplevel , new ( )
328
334
{
329
- if ( ! IsInitialized )
335
+ if ( ! Initialized )
330
336
{
331
337
// Init() has NOT been called.
332
338
InternalInit ( driver , null , true ) ;
@@ -381,7 +387,7 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
381
387
{
382
388
ArgumentNullException . ThrowIfNull ( view ) ;
383
389
384
- if ( IsInitialized )
390
+ if ( Initialized )
385
391
{
386
392
if ( Driver is null )
387
393
{
@@ -452,7 +458,10 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
452
458
/// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
453
459
/// token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
454
460
/// </remarks>
455
- public static object AddTimeout ( TimeSpan time , Func < bool > callback ) { return MainLoop ! . AddTimeout ( time , callback ) ; }
461
+ public static object ? AddTimeout ( TimeSpan time , Func < bool > callback )
462
+ {
463
+ return MainLoop ? . AddTimeout ( time , callback ) ?? null ;
464
+ }
456
465
457
466
/// <summary>Removes a previously scheduled timeout</summary>
458
467
/// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
@@ -486,20 +495,25 @@ public static void Invoke (Action action)
486
495
/// <summary>Wakes up the running application that might be waiting on input.</summary>
487
496
public static void Wakeup ( ) { MainLoop ? . Wakeup ( ) ; }
488
497
489
- /// <summary>Triggers a refresh of the entire display.</summary>
490
- public static void Refresh ( )
498
+ /// <summary>
499
+ /// Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
500
+ /// Only Views that need to be drawn (see <see cref="View.NeedsDraw"/>) will be drawn.
501
+ /// </summary>
502
+ /// <param name="forceDraw">If <see langword="true"/> the entire View hierarchy will be redrawn. The default is <see langword="false"/> and should only be overriden for testing.</param>
503
+ public static void LayoutAndDraw ( bool forceDraw = false )
491
504
{
492
- foreach ( Toplevel tl in TopLevels . Reverse ( ) )
493
- {
494
- if ( tl . LayoutNeeded )
495
- {
496
- tl . LayoutSubviews ( ) ;
497
- }
505
+ bool neededLayout = View . Layout ( TopLevels . Reverse ( ) , Screen . Size ) ;
498
506
499
- tl . Draw ( ) ;
507
+ if ( forceDraw )
508
+ {
509
+ Driver ? . ClearContents ( ) ;
500
510
}
501
511
502
- Driver ! . Refresh ( ) ;
512
+ View . SetClipToScreen ( ) ;
513
+ View . Draw ( TopLevels , neededLayout || forceDraw ) ;
514
+ View . SetClipToScreen ( ) ;
515
+
516
+ Driver ? . Refresh ( ) ;
503
517
}
504
518
505
519
/// <summary>This event is raised on each iteration of the main loop.</summary>
@@ -534,24 +548,25 @@ public static void RunLoop (RunState state)
534
548
return ;
535
549
}
536
550
537
- RunIteration ( ref state , ref firstIteration ) ;
551
+ firstIteration = RunIteration ( ref state , firstIteration ) ;
538
552
}
539
553
540
554
MainLoop ! . Running = false ;
541
555
542
556
// Run one last iteration to consume any outstanding input events from Driver
543
557
// This is important for remaining OnKeyUp events.
544
- RunIteration ( ref state , ref firstIteration ) ;
558
+ RunIteration ( ref state , firstIteration ) ;
545
559
}
546
560
547
561
/// <summary>Run one application iteration.</summary>
548
562
/// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
549
563
/// <param name="firstIteration">
550
- /// Set to <see langword="true"/> if this is the first run loop iteration. Upon return, it
551
- /// will be set to <see langword="false"/> if at least one iteration happened.
564
+ /// Set to <see langword="true"/> if this is the first run loop iteration.
552
565
/// </param>
553
- public static void RunIteration ( ref RunState state , ref bool firstIteration )
566
+ /// <returns><see langword="false"/> if at least one iteration happened.</returns>
567
+ public static bool RunIteration ( ref RunState state , bool firstIteration = false )
554
568
{
569
+ // If the driver has events pending do an iteration of the driver MainLoop
555
570
if ( MainLoop ! . Running && MainLoop . EventsPending ( ) )
556
571
{
557
572
// Notify Toplevel it's ready
@@ -561,23 +576,25 @@ public static void RunIteration (ref RunState state, ref bool firstIteration)
561
576
}
562
577
563
578
MainLoop . RunIteration ( ) ;
579
+
564
580
Iteration ? . Invoke ( null , new ( ) ) ;
565
581
}
566
582
567
583
firstIteration = false ;
568
584
569
585
if ( Top is null )
570
586
{
571
- return ;
587
+ return firstIteration ;
572
588
}
573
589
574
- Refresh ( ) ;
590
+ LayoutAndDraw ( ) ;
575
591
576
592
if ( PositionCursor ( ) )
577
593
{
578
594
Driver ! . UpdateCursor ( ) ;
579
595
}
580
596
597
+ return firstIteration ;
581
598
}
582
599
583
600
/// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
@@ -652,7 +669,7 @@ public static void End (RunState runState)
652
669
if ( TopLevels . Count > 0 )
653
670
{
654
671
Top = TopLevels . Peek ( ) ;
655
- Top . SetNeedsDisplay ( ) ;
672
+ Top . SetNeedsDraw ( ) ;
656
673
}
657
674
658
675
if ( runState . Toplevel is { HasFocus : true } )
@@ -670,6 +687,6 @@ public static void End (RunState runState)
670
687
runState . Toplevel = null ;
671
688
runState . Dispose ( ) ;
672
689
673
- Refresh ( ) ;
690
+ LayoutAndDraw ( ) ;
674
691
}
675
692
}
0 commit comments