#nullable enable
using System.Collections.Concurrent;
using System.Diagnostics;
namespace Terminal.Gui;
///
public class MainLoop : IMainLoop
{
private ITimedEvents? _timedEvents;
private ConcurrentQueue? _inputBuffer;
private IInputProcessor? _inputProcessor;
private IConsoleOutput? _out;
private AnsiRequestScheduler? _ansiRequestScheduler;
private IWindowSizeMonitor? _windowSizeMonitor;
///
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
///
///
public ConcurrentQueue InputBuffer
{
get => _inputBuffer ?? throw new NotInitializedException (nameof (InputBuffer));
private set => _inputBuffer = value;
}
///
public IInputProcessor InputProcessor
{
get => _inputProcessor ?? throw new NotInitializedException (nameof (InputProcessor));
private set => _inputProcessor = value;
}
///
public IOutputBuffer OutputBuffer { get; } = new OutputBuffer ();
///
public IConsoleOutput Out
{
get => _out ?? throw new NotInitializedException (nameof (Out));
private set => _out = value;
}
///
public AnsiRequestScheduler AnsiRequestScheduler
{
get => _ansiRequestScheduler ?? throw new NotInitializedException (nameof (AnsiRequestScheduler));
private set => _ansiRequestScheduler = value;
}
///
public IWindowSizeMonitor WindowSizeMonitor
{
get => _windowSizeMonitor ?? throw new NotInitializedException (nameof (WindowSizeMonitor));
private set => _windowSizeMonitor = value;
}
///
/// Handles raising events and setting required draw status etc when changes
///
public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager ();
///
/// Determines how to get the current system type, adjust
/// in unit tests to simulate specific timings.
///
public Func Now { get; set; } = () => DateTime.Now;
///
/// Initializes the class with the provided subcomponents
///
///
///
///
///
public void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
{
InputBuffer = inputBuffer;
Out = consoleOutput;
InputProcessor = inputProcessor;
TimedEvents = timedEvents;
AnsiRequestScheduler = new (InputProcessor.GetParser ());
WindowSizeMonitor = new WindowSizeMonitor (Out, OutputBuffer);
}
///
public void Iteration ()
{
DateTime dt = Now ();
IterationImpl ();
TimeSpan took = Now () - dt;
TimeSpan sleepFor = TimeSpan.FromMilliseconds (50) - 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.Top);
bool sizeChanged = WindowSizeMonitor.Poll ();
if (needsDrawOrLayout || sizeChanged)
{
Logging.Redraws.Add (1);
Application.LayoutAndDrawImpl (true);
Out.Write (OutputBuffer);
Out.SetCursorVisibility (CursorVisibility.Default);
}
SetCursor ();
}
var swCallbacks = Stopwatch.StartNew ();
TimedEvents.LockAndRunTimers ();
TimedEvents.LockAndRunIdles ();
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.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;
}
///
public void Dispose ()
{
// TODO release managed resources here
}
}