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
}
}