11#nullable enable
22using System . Diagnostics ;
33using System . Diagnostics . CodeAnalysis ;
4+ using Microsoft . CodeAnalysis . Diagnostics ;
45
56namespace Terminal . Gui ;
67
@@ -80,27 +81,20 @@ public static RunState Begin (Toplevel toplevel)
8081 {
8182 ArgumentNullException . ThrowIfNull ( toplevel ) ;
8283
83- //#if DEBUG_IDISPOSABLE
84- // Debug.Assert (!toplevel.WasDisposed);
84+ //#if DEBUG_IDISPOSABLE
85+ // Debug.Assert (!toplevel.WasDisposed);
8586
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
9192
9293 // Ensure the mouse is ungrabbed.
9394 MouseGrabView = null ;
9495
9596 var rs = new RunState ( toplevel ) ;
9697
97- // View implements ISupportInitializeNotification which is derived from ISupportInitialize
98- if ( ! toplevel . IsInitialized )
99- {
100- toplevel . BeginInit ( ) ;
101- toplevel . EndInit ( ) ;
102- }
103-
10498#if DEBUG_IDISPOSABLE
10599 if ( Top is { } && toplevel != Top && ! TopLevels . Contains ( Top ) )
106100 {
@@ -176,16 +170,26 @@ public static RunState Begin (Toplevel toplevel)
176170 Top . HasFocus = false ;
177171 }
178172
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+
179179 Top ? . OnDeactivate ( toplevel ) ;
180- Toplevel previousCurrent = Top ! ;
180+ Toplevel previousTop = Top ! ;
181181
182182 Top = toplevel ;
183- Top . OnActivate ( previousCurrent ) ;
183+ Top . OnActivate ( previousTop ) ;
184184 }
185185 }
186186
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+ }
189193
190194 // Try to set initial focus to any TabStop
191195 if ( ! toplevel . HasFocus )
@@ -195,15 +199,16 @@ public static RunState Begin (Toplevel toplevel)
195199
196200 toplevel . OnLoaded ( ) ;
197201
198- Refresh ( ) ;
199-
200202 if ( PositionCursor ( ) )
201203 {
202- Driver . UpdateCursor ( ) ;
204+ Driver ? . UpdateCursor ( ) ;
203205 }
204206
205207 NotifyNewRunState ? . Invoke ( toplevel , new ( rs ) ) ;
206208
209+ // Force an Idle event so that an Iteration (and Refresh) happen.
210+ Application . Invoke ( ( ) => { } ) ;
211+
207212 return rs ;
208213 }
209214
@@ -225,11 +230,12 @@ internal static bool PositionCursor ()
225230 // If the view is not visible or enabled, don't position the cursor
226231 if ( mostFocused is null || ! mostFocused . Visible || ! mostFocused . Enabled )
227232 {
228- Driver ! . GetCursorVisibility ( out CursorVisibility current ) ;
233+ CursorVisibility current = CursorVisibility . Invisible ;
234+ Driver ? . GetCursorVisibility ( out current ) ;
229235
230236 if ( current != CursorVisibility . Invisible )
231237 {
232- Driver . SetCursorVisibility ( CursorVisibility . Invisible ) ;
238+ Driver ? . SetCursorVisibility ( CursorVisibility . Invisible ) ;
233239 }
234240
235241 return false ;
@@ -326,7 +332,7 @@ internal static bool PositionCursor ()
326332 public static T Run < T > ( Func < Exception , bool > ? errorHandler = null , ConsoleDriver ? driver = null )
327333 where T : Toplevel , new ( )
328334 {
329- if ( ! IsInitialized )
335+ if ( ! Initialized )
330336 {
331337 // Init() has NOT been called.
332338 InternalInit ( driver , null , true ) ;
@@ -381,7 +387,7 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
381387 {
382388 ArgumentNullException . ThrowIfNull ( view ) ;
383389
384- if ( IsInitialized )
390+ if ( Initialized )
385391 {
386392 if ( Driver is null )
387393 {
@@ -452,7 +458,10 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
452458 /// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
453459 /// token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
454460 /// </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+ }
456465
457466 /// <summary>Removes a previously scheduled timeout</summary>
458467 /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
@@ -486,20 +495,25 @@ public static void Invoke (Action action)
486495 /// <summary>Wakes up the running application that might be waiting on input.</summary>
487496 public static void Wakeup ( ) { MainLoop ? . Wakeup ( ) ; }
488497
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 )
491504 {
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 ) ;
498506
499- tl . Draw ( ) ;
507+ if ( forceDraw )
508+ {
509+ Driver ? . ClearContents ( ) ;
500510 }
501511
502- Driver ! . Refresh ( ) ;
512+ View . SetClipToScreen ( ) ;
513+ View . Draw ( TopLevels , neededLayout || forceDraw ) ;
514+ View . SetClipToScreen ( ) ;
515+
516+ Driver ? . Refresh ( ) ;
503517 }
504518
505519 /// <summary>This event is raised on each iteration of the main loop.</summary>
@@ -534,24 +548,25 @@ public static void RunLoop (RunState state)
534548 return ;
535549 }
536550
537- RunIteration ( ref state , ref firstIteration ) ;
551+ firstIteration = RunIteration ( ref state , firstIteration ) ;
538552 }
539553
540554 MainLoop ! . Running = false ;
541555
542556 // Run one last iteration to consume any outstanding input events from Driver
543557 // This is important for remaining OnKeyUp events.
544- RunIteration ( ref state , ref firstIteration ) ;
558+ RunIteration ( ref state , firstIteration ) ;
545559 }
546560
547561 /// <summary>Run one application iteration.</summary>
548562 /// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
549563 /// <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.
552565 /// </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 )
554568 {
569+ // If the driver has events pending do an iteration of the driver MainLoop
555570 if ( MainLoop ! . Running && MainLoop . EventsPending ( ) )
556571 {
557572 // Notify Toplevel it's ready
@@ -561,23 +576,25 @@ public static void RunIteration (ref RunState state, ref bool firstIteration)
561576 }
562577
563578 MainLoop . RunIteration ( ) ;
579+
564580 Iteration ? . Invoke ( null , new ( ) ) ;
565581 }
566582
567583 firstIteration = false ;
568584
569585 if ( Top is null )
570586 {
571- return ;
587+ return firstIteration ;
572588 }
573589
574- Refresh ( ) ;
590+ LayoutAndDraw ( ) ;
575591
576592 if ( PositionCursor ( ) )
577593 {
578594 Driver ! . UpdateCursor ( ) ;
579595 }
580596
597+ return firstIteration ;
581598 }
582599
583600 /// <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)
652669 if ( TopLevels . Count > 0 )
653670 {
654671 Top = TopLevels . Peek ( ) ;
655- Top . SetNeedsDisplay ( ) ;
672+ Top . SetNeedsDraw ( ) ;
656673 }
657674
658675 if ( runState . Toplevel is { HasFocus : true } )
@@ -670,6 +687,6 @@ public static void End (RunState runState)
670687 runState . Toplevel = null ;
671688 runState . Dispose ( ) ;
672689
673- Refresh ( ) ;
690+ LayoutAndDraw ( ) ;
674691 }
675692}
0 commit comments