diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 67b1a5825..b9d1fba24 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -30,7 +30,7 @@ namespace Terminal.Gui { IClipboard clipboard; int [,,] contents; - internal override int [,,] Contents => contents; + public override int [,,] Contents => contents; // Current row, and current col, tracked by Move/AddRune only int ccol, crow; @@ -488,7 +488,7 @@ namespace Terminal.Gui { Flags = mouseFlag }; - var view = Application.wantContinuousButtonPressedView; + var view = Application.WantContinuousButtonPressedView; if (view == null) break; if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index ffe45bc78..c865c24d6 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -35,7 +35,7 @@ namespace Terminal.Gui { /// /// Assists with testing, the format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag /// - internal override int [,,] Contents => contents; + public override int [,,] Contents => contents; //void UpdateOffscreen () //{ diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 0a75db93a..9ef1b41c1 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -947,7 +947,7 @@ namespace Terminal.Gui { await Task.Delay (200); while (isButtonPressed) { await Task.Delay (100); - var view = Application.wantContinuousButtonPressedView; + var view = Application.WantContinuousButtonPressedView; if (view == null) { break; } @@ -1187,7 +1187,7 @@ namespace Terminal.Gui { public NetWinVTConsole NetWinConsole { get; } public bool IsWinPlatform { get; } public override IClipboard Clipboard { get; } - internal override int [,,] Contents => contents; + public override int [,,] Contents => contents; int largestWindowHeight; diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index a185ec027..0a233c8a3 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -732,7 +732,7 @@ namespace Terminal.Gui { public override int Top => top; public override bool HeightAsBuffer { get; set; } public override IClipboard Clipboard => clipboard; - internal override int [,,] Contents => contents; + public override int [,,] Contents => contents; public WindowsConsole WinConsole { get; private set; } @@ -1181,7 +1181,7 @@ namespace Terminal.Gui { Flags = mouseFlag }; - var view = Application.wantContinuousButtonPressedView; + var view = Application.WantContinuousButtonPressedView; if (view == null) { break; } diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index e87f615ac..d79704308 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -105,6 +105,11 @@ namespace Terminal.Gui { /// The current. public static Toplevel Current { get; private set; } + /// + /// The current object that wants continuous mouse button pressed events. + /// + public static View WantContinuousButtonPressedView { get; private set; } + /// /// The current used in the terminal. /// @@ -210,6 +215,24 @@ namespace Terminal.Gui { /// public static bool IsMouseDisabled { get; set; } + /// + /// Set to true to cause the RunLoop method to exit after the first iterations. + /// Set to false (the default) to cause the RunLoop to continue running until Application.RequestStop() is called. + /// + public static bool ExitRunLoopAfterFirstIteration { get; set; } = false; + + /// + /// Notify that a new token was created, + /// used if is true. + /// + public static event Action NotifyNewRunState; + + /// + /// Notify that a existent token is stopping, + /// used if is true. + /// + public static event Action NotifyStopRunState; + /// /// This event is raised on each iteration of the /// @@ -297,12 +320,12 @@ namespace Terminal.Gui { } // Used only for start debugging on Unix. -//#if DEBUG -// while (!System.Diagnostics.Debugger.IsAttached) { -// System.Threading.Thread.Sleep (100); -// } -// System.Diagnostics.Debugger.Break (); -//#endif + //#if DEBUG + // while (!System.Diagnostics.Debugger.IsAttached) { + // System.Threading.Thread.Sleep (100); + // } + // System.Diagnostics.Debugger.Break (); + //#endif // Reset all class variables (Application is a singleton). ResetState (); @@ -352,7 +375,10 @@ namespace Terminal.Gui { { Toplevel = view; } - internal Toplevel Toplevel; + /// + /// The belong to this . + /// + public Toplevel Toplevel { get; internal set; } /// /// Releases alTop = l resource used by the object. @@ -385,7 +411,7 @@ namespace Terminal.Gui { static void ProcessKeyEvent (KeyEvent ke) { - if(RootKeyEvent?.Invoke(ke) ?? false) { + if (RootKeyEvent?.Invoke (ke) ?? false) { return; } @@ -580,9 +606,8 @@ namespace Terminal.Gui { /// /// Return true to suppress the KeyPress event /// - public static Func RootKeyEvent; + public static Func RootKeyEvent; - internal static View wantContinuousButtonPressedView; static View lastMouseOwnerView; static void ProcessMouseEvent (MouseEvent me) @@ -594,9 +619,9 @@ namespace Terminal.Gui { var view = FindDeepestView (Current, me.X, me.Y, out int rx, out int ry); if (view != null && view.WantContinuousButtonPressed) - wantContinuousButtonPressedView = view; + WantContinuousButtonPressedView = view; else - wantContinuousButtonPressedView = null; + WantContinuousButtonPressedView = null; if (view != null) { me.View = view; } @@ -655,9 +680,9 @@ namespace Terminal.Gui { return; if (view.WantContinuousButtonPressed) - wantContinuousButtonPressedView = view; + WantContinuousButtonPressedView = view; else - wantContinuousButtonPressedView = null; + WantContinuousButtonPressedView = null; // Should we bubbled up the event, if it is not handled? view.OnMouseEvent (nme); @@ -952,51 +977,65 @@ namespace Terminal.Gui { bool firstIteration = true; for (state.Toplevel.Running = true; state.Toplevel.Running;) { - if (MainLoop.EventsPending (wait)) { - // Notify Toplevel it's ready - if (firstIteration) { - state.Toplevel.OnReady (); - } - firstIteration = false; - - MainLoop.MainIteration (); - Iteration?.Invoke (); - - EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); - if ((state.Toplevel != Current && Current?.Modal == true) - || (state.Toplevel != Current && Current?.Modal == false)) { - MdiTop?.OnDeactivate (state.Toplevel); - state.Toplevel = Current; - MdiTop?.OnActivate (state.Toplevel); - Top.SetChildNeedsDisplay (); - Refresh (); - } - if (Driver.EnsureCursorVisibility ()) { - state.Toplevel.SetNeedsDisplay (); - } - } else if (!wait) { + if (ExitRunLoopAfterFirstIteration && !firstIteration) return; + RunMainLoopIteration (ref state, wait, ref firstIteration); + } + } + + /// + /// Run one iteration of the MainLoop. + /// + /// The state returned by the Begin method. + /// If will execute the runloop waiting for events. + /// If it's the first run loop iteration. + public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool firstIteration) + { + if (MainLoop.EventsPending (wait)) { + // Notify Toplevel it's ready + if (firstIteration) { + state.Toplevel.OnReady (); } - if (state.Toplevel != Top - && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { - Top.Redraw (Top.Bounds); - state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); + + MainLoop.MainIteration (); + Iteration?.Invoke (); + + EnsureModalOrVisibleAlwaysOnTop (state.Toplevel); + if ((state.Toplevel != Current && Current?.Modal == true) + || (state.Toplevel != Current && Current?.Modal == false)) { + MdiTop?.OnDeactivate (state.Toplevel); + state.Toplevel = Current; + MdiTop?.OnActivate (state.Toplevel); + Top.SetChildNeedsDisplay (); + Refresh (); } - if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded - || MdiChildNeedsDisplay ()) { - state.Toplevel.Redraw (state.Toplevel.Bounds); - if (DebugDrawBounds) { - DrawBounds (state.Toplevel); - } - state.Toplevel.PositionCursor (); - Driver.Refresh (); - } else { - Driver.UpdateCursor (); + if (Driver.EnsureCursorVisibility ()) { + state.Toplevel.SetNeedsDisplay (); } - if (state.Toplevel != Top && !state.Toplevel.Modal - && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { - Top.Redraw (Top.Bounds); + } else if (!wait) { + return; + } + firstIteration = false; + + if (state.Toplevel != Top + && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { + Top.Redraw (Top.Bounds); + state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds); + } + if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded + || MdiChildNeedsDisplay ()) { + state.Toplevel.Redraw (state.Toplevel.Bounds); + if (DebugDrawBounds) { + DrawBounds (state.Toplevel); } + state.Toplevel.PositionCursor (); + Driver.Refresh (); + } else { + Driver.UpdateCursor (); + } + if (state.Toplevel != Top && !state.Toplevel.Modal + && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) { + Top.Redraw (Top.Bounds); } } @@ -1107,7 +1146,12 @@ namespace Terminal.Gui { resume = false; var runToken = Begin (view); RunLoop (runToken); - End (runToken); + if (!ExitRunLoopAfterFirstIteration) + End (runToken); + else + // If ExitRunLoopAfterFirstIteration is true then the user must deal his disposing when it ends + // by using NotifyStopRunState event. + NotifyNewRunState?.Invoke (runToken); #if !DEBUG } catch (Exception error) @@ -1190,6 +1234,8 @@ namespace Terminal.Gui { return; } currentTop.Running = false; + if (ExitRunLoopAfterFirstIteration) + NotifyStopRunState?.Invoke (currentTop); } } diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index 9a29ab064..616a85475 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/Terminal.Gui/Core/ConsoleDriver.cs @@ -665,8 +665,10 @@ namespace Terminal.Gui { /// public abstract bool HeightAsBuffer { get; set; } - // The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag - internal abstract int [,,] Contents { get; } + /// + /// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag + /// + public virtual int [,,] Contents { get; } /// /// Initializes the driver diff --git a/Terminal.Gui/Core/MainLoop.cs b/Terminal.Gui/Core/MainLoop.cs index 493e3be48..4988a5a8e 100644 --- a/Terminal.Gui/Core/MainLoop.cs +++ b/Terminal.Gui/Core/MainLoop.cs @@ -45,8 +45,17 @@ namespace Terminal.Gui { /// does not seem to be a way of supporting this on Windows. /// public class MainLoop { - internal class Timeout { + /// + /// Provides data for timers running manipulation. + /// + public sealed class Timeout { + /// + /// Time to wait before invoke the callback. + /// public TimeSpan Span; + /// + /// The function that will be invoked. + /// public Func Callback; } @@ -54,12 +63,30 @@ namespace Terminal.Gui { object timeoutsLockToken = new object (); internal List> idleHandlers = new List> (); + /// + /// Gets the list of all timeouts sorted by the time ticks./>. + /// A shorter limit time can be added at the end, but it will be called before an + /// earlier addition that has a longer limit time. + /// + public SortedList Timeouts => timeouts; + + /// + /// Gets the list of all idle handlers. + /// + public List> IdleHandlers => idleHandlers; + /// /// The current IMainLoopDriver in use. /// /// The driver. public IMainLoopDriver Driver { get; } + /// + /// Invoked when a new timeout is added to be used on the case + /// if is true, + /// + public event Action TimeoutAdded; + /// /// Creates a new Mainloop. /// @@ -120,7 +147,8 @@ namespace Terminal.Gui { { lock (timeoutsLockToken) { var k = (DateTime.UtcNow + time).Ticks; - timeouts.Add (NudgeToUniqueKey(k), timeout); + timeouts.Add (NudgeToUniqueKey (k), timeout); + TimeoutAdded?.Invoke (k); } } @@ -188,11 +216,10 @@ namespace Terminal.Gui { AddTimeout (timeout.Span, timeout); } else { lock (timeoutsLockToken) { - timeouts.Add (NudgeToUniqueKey(k), timeout); + timeouts.Add (NudgeToUniqueKey (k), timeout); } } } - } /// @@ -203,7 +230,7 @@ namespace Terminal.Gui { /// private long NudgeToUniqueKey (long k) { - lock(timeoutsLockToken) { + lock (timeoutsLockToken) { while (timeouts.ContainsKey (k)) { k++; } diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs index e48b4aec8..9f70b66aa 100644 --- a/UnitTests/ApplicationTests.cs +++ b/UnitTests/ApplicationTests.cs @@ -1144,7 +1144,7 @@ namespace Terminal.Gui.Core { var rs = Application.Begin (Application.Top); Assert.Equal (Application.Top, rs.Toplevel); Assert.Null (Application.mouseGrabView); - Assert.Null (Application.wantContinuousButtonPressedView); + Assert.Null (Application.WantContinuousButtonPressedView); Assert.False (Application.DebugDrawBounds); Assert.False (Application.ShowChild (Application.Top)); Application.End (Application.Top);