using System.Collections.Concurrent; using System.Diagnostics; namespace Terminal.Gui.App; /// /// The main application loop that runs Terminal.Gui's UI rendering and event processing. /// /// /// This class coordinates the Terminal.Gui application lifecycle by: /// /// Processing buffered input events and translating them to UI events /// Executing user timeout callbacks at scheduled intervals /// Detecting which views need redrawing or layout updates /// Rendering UI changes to the console output buffer /// Managing cursor position and visibility /// Throttling iterations to respect /// /// /// Type of raw input events, e.g. for .NET driver public class ApplicationMainLoop : IApplicationMainLoop where TInputRecord : struct { private ITimedEvents? _timedEvents; private ConcurrentQueue? _inputQueue; private IInputProcessor? _inputProcessor; private IOutput? _output; private AnsiRequestScheduler? _ansiRequestScheduler; private ISizeMonitor? _sizeMonitor; /// public IApplication? App { get; private set; } /// public ITimedEvents TimedEvents { get => _timedEvents ?? throw new NotInitializedException (nameof (TimedEvents)); private set => _timedEvents = value; } // TODO: follow above pattern for others too /// /// The input events thread-safe collection. This is populated on separate /// thread by a . Is drained as part of each /// on the main loop thread. /// public ConcurrentQueue InputQueue { get => _inputQueue ?? throw new NotInitializedException (nameof (InputQueue)); private set => _inputQueue = value; } /// public IInputProcessor InputProcessor { get => _inputProcessor ?? throw new NotInitializedException (nameof (InputProcessor)); private set => _inputProcessor = value; } /// public IOutputBuffer OutputBuffer { get; } = new OutputBufferImpl (); /// public IOutput Output { get => _output ?? throw new NotInitializedException (nameof (Output)); private set => _output = value; } /// public AnsiRequestScheduler AnsiRequestScheduler { get => _ansiRequestScheduler ?? throw new NotInitializedException (nameof (AnsiRequestScheduler)); private set => _ansiRequestScheduler = value; } /// public ISizeMonitor SizeMonitor { get => _sizeMonitor ?? throw new NotInitializedException (nameof (SizeMonitor)); private set => _sizeMonitor = value; } /// /// Initializes the class with the provided subcomponents /// /// /// /// /// /// /// public void Initialize ( ITimedEvents timedEvents, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, IOutput consoleOutput, IComponentFactory componentFactory, IApplication? app ) { App = app; InputQueue = inputBuffer; Output = consoleOutput; InputProcessor = inputProcessor; TimedEvents = timedEvents; AnsiRequestScheduler = new (InputProcessor.GetParser ()); OutputBuffer.SetSize (consoleOutput.GetSize ().Width, consoleOutput.GetSize ().Height); SizeMonitor = componentFactory.CreateSizeMonitor (Output, OutputBuffer); } /// public void Iteration () { App?.RaiseIteration (); DateTime dt = DateTime.Now; int timeAllowed = 1000 / Math.Max (1, (int)Application.MaximumIterationsPerSecond); IterationImpl (); TimeSpan took = DateTime.Now - dt; TimeSpan sleepFor = TimeSpan.FromMilliseconds (timeAllowed) - took; Logging.TotalIterationMetric.Record (took.Milliseconds); if (sleepFor.Milliseconds > 0) { Task.Delay (sleepFor).Wait (); } } internal void IterationImpl () { // Pull any input events from the input queue and process them InputProcessor.ProcessQueue (); // Check for any size changes; this will cause SizeChanged events SizeMonitor.Poll (); // Layout and draw any views that need it App?.LayoutAndDraw (forceRedraw: false); // Update the cursor SetCursor (); Stopwatch swCallbacks = Stopwatch.StartNew (); // Run any timeout callbacks that are due TimedEvents.RunTimers (); Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds); } private void SetCursor () { View? mostFocused = App?.TopRunnableView?.MostFocused; if (mostFocused == null) { Output.SetCursorVisibility (CursorVisibility.Invisible); return; } Point? to = mostFocused.PositionCursor (); if (to.HasValue) { // Translate to screen coordinates Point screenPos = mostFocused.ViewportToScreen (to.Value); Output.SetCursorPosition (screenPos.X, screenPos.Y); Output.SetCursorVisibility (mostFocused.CursorVisibility); } else { Output.SetCursorVisibility (CursorVisibility.Invisible); } } /// public void Dispose () { // TODO release managed resources here } }