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