| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- #nullable enable
- using Terminal.Gui.Drivers;
- using System.Collections.Concurrent;
- using System.Diagnostics;
- namespace Terminal.Gui.App;
- /// <summary>
- /// The main application loop that runs Terminal.Gui's UI rendering and event processing.
- /// </summary>
- /// <remarks>
- /// This class coordinates the Terminal.Gui application lifecycle by:
- /// <list type="bullet">
- /// <item>Processing buffered input events and translating them to UI events</item>
- /// <item>Executing user timeout callbacks at scheduled intervals</item>
- /// <item>Detecting which views need redrawing or layout updates</item>
- /// <item>Rendering UI changes to the console output buffer</item>
- /// <item>Managing cursor position and visibility</item>
- /// <item>Throttling iterations to respect <see cref="Application.MaximumIterationsPerSecond"/></item>
- /// </list>
- /// </remarks>
- /// <typeparam name="T">Type of raw input events, e.g. <see cref="ConsoleKeyInfo"/> for .NET driver</typeparam>
- public class ApplicationMainLoop<T> : IApplicationMainLoop<T>
- {
- private ITimedEvents? _timedEvents;
- private ConcurrentQueue<T>? _inputBuffer;
- private IInputProcessor? _inputProcessor;
- private IConsoleOutput? _out;
- private AnsiRequestScheduler? _ansiRequestScheduler;
- private IConsoleSizeMonitor? _consoleSizeMonitor;
- /// <inheritdoc/>
- public ITimedEvents TimedEvents
- {
- get => _timedEvents ?? throw new NotInitializedException (nameof (TimedEvents));
- private set => _timedEvents = value;
- }
- // TODO: follow above pattern for others too
- /// <summary>
- /// The input events thread-safe collection. This is populated on separate
- /// thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
- /// <see cref="Iteration"/>
- /// </summary>
- public ConcurrentQueue<T> InputBuffer
- {
- get => _inputBuffer ?? throw new NotInitializedException (nameof (InputBuffer));
- private set => _inputBuffer = value;
- }
- /// <inheritdoc/>
- public IInputProcessor InputProcessor
- {
- get => _inputProcessor ?? throw new NotInitializedException (nameof (InputProcessor));
- private set => _inputProcessor = value;
- }
- /// <inheritdoc/>
- public IOutputBuffer OutputBuffer { get; } = new OutputBuffer ();
- /// <inheritdoc/>
- public IConsoleOutput Out
- {
- get => _out ?? throw new NotInitializedException (nameof (Out));
- private set => _out = value;
- }
- /// <inheritdoc/>
- public AnsiRequestScheduler AnsiRequestScheduler
- {
- get => _ansiRequestScheduler ?? throw new NotInitializedException (nameof (AnsiRequestScheduler));
- private set => _ansiRequestScheduler = value;
- }
- /// <inheritdoc/>
- public IConsoleSizeMonitor ConsoleSizeMonitor
- {
- get => _consoleSizeMonitor ?? throw new NotInitializedException (nameof (ConsoleSizeMonitor));
- private set => _consoleSizeMonitor = value;
- }
- /// <summary>
- /// Handles raising events and setting required draw status etc when <see cref="Application.Top"/> changes
- /// </summary>
- public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager ();
- /// <summary>
- /// Determines how to get the current system type, adjust
- /// in unit tests to simulate specific timings.
- /// </summary>
- public Func<DateTime> Now { get; set; } = () => DateTime.Now;
- /// <summary>
- /// Initializes the class with the provided subcomponents
- /// </summary>
- /// <param name="timedEvents"></param>
- /// <param name="inputBuffer"></param>
- /// <param name="inputProcessor"></param>
- /// <param name="consoleOutput"></param>
- /// <param name="componentFactory"></param>
- public void Initialize (
- ITimedEvents timedEvents,
- ConcurrentQueue<T> inputBuffer,
- IInputProcessor inputProcessor,
- IConsoleOutput consoleOutput,
- IComponentFactory<T> componentFactory
- )
- {
- InputBuffer = inputBuffer;
- Out = consoleOutput;
- InputProcessor = inputProcessor;
- TimedEvents = timedEvents;
- AnsiRequestScheduler = new (InputProcessor.GetParser ());
- ConsoleSizeMonitor = componentFactory.CreateConsoleSizeMonitor (Out, OutputBuffer);
- }
- /// <inheritdoc/>
- public void Iteration ()
- {
- Application.RaiseIteration ();
- DateTime dt = Now ();
- int timeAllowed = 1000 / Math.Max(1,(int)Application.MaximumIterationsPerSecond);
- IterationImpl ();
- TimeSpan took = Now () - dt;
- TimeSpan sleepFor = TimeSpan.FromMilliseconds (timeAllowed) - took;
- Logging.TotalIterationMetric.Record (took.Milliseconds);
- if (sleepFor.Milliseconds > 0)
- {
- Task.Delay (sleepFor).Wait ();
- }
- }
- internal void IterationImpl ()
- {
- InputProcessor.ProcessQueue ();
- ToplevelTransitionManager.RaiseReadyEventIfNeeded ();
- ToplevelTransitionManager.HandleTopMaybeChanging ();
- if (Application.Top != null)
- {
- bool needsDrawOrLayout = AnySubViewsNeedDrawn (Application.Popover?.GetActivePopover () as View)
- || AnySubViewsNeedDrawn (Application.Top)
- || (Application.Mouse.MouseGrabView != null && AnySubViewsNeedDrawn (Application.Mouse.MouseGrabView));
- bool sizeChanged = ConsoleSizeMonitor.Poll ();
- if (needsDrawOrLayout || sizeChanged)
- {
- Logging.Redraws.Add (1);
- Application.LayoutAndDraw (true);
- Out.Write (OutputBuffer);
- Out.SetCursorVisibility (CursorVisibility.Default);
- }
- SetCursor ();
- }
- var swCallbacks = Stopwatch.StartNew ();
- TimedEvents.RunTimers ();
- Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
- }
- private void SetCursor ()
- {
- View? mostFocused = Application.Top!.MostFocused;
- if (mostFocused == null)
- {
- return;
- }
- Point? to = mostFocused.PositionCursor ();
- if (to.HasValue)
- {
- // Translate to screen coordinates
- to = mostFocused.ViewportToScreen (to.Value);
- Out.SetCursorPosition (to.Value.X, to.Value.Y);
- Out.SetCursorVisibility (mostFocused.CursorVisibility);
- }
- else
- {
- Out.SetCursorVisibility (CursorVisibility.Invisible);
- }
- }
- private bool AnySubViewsNeedDrawn (View? v)
- {
- if (v is null)
- {
- return false;
- }
- if (v.NeedsDraw || v.NeedsLayout)
- {
- // Logging.Trace ($"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
- return true;
- }
- foreach (View subview in v.SubViews)
- {
- if (AnySubViewsNeedDrawn (subview))
- {
- return true;
- }
- }
- return false;
- }
- /// <inheritdoc/>
- public void Dispose ()
- {
- // TODO release managed resources here
- }
- }
|