Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposes APIs to support upcoming Web console feature #1770

Merged
merged 9 commits into from
Jun 8, 2022
4 changes: 2 additions & 2 deletions Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal class CursesDriver : ConsoleDriver {
IClipboard clipboard;
int [,,] contents;

internal override int [,,] Contents => contents;
public override int [,,] Contents => contents;
tig marked this conversation as resolved.
Show resolved Hide resolved

// Current row, and current col, tracked by Move/AddRune only
int ccol, crow;
Expand Down Expand Up @@ -491,7 +491,7 @@ async Task ProcessContinuousButtonPressedAsync (Curses.MouseEvent cev, MouseFlag
Flags = mouseFlag
};

var view = Application.wantContinuousButtonPressedView;
var view = Application.WantContinuousButtonPressedView;
if (view == null)
break;
if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class FakeDriver : ConsoleDriver {
/// <summary>
/// Assists with testing, the format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
/// </summary>
internal override int [,,] Contents => contents;
public override int [,,] Contents => contents;

//void UpdateOffscreen ()
//{
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/ConsoleDrivers/NetDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,7 @@ async Task ProcessContinuousButtonPressedAsync ()
await Task.Delay (200);
while (isButtonPressed) {
await Task.Delay (100);
var view = Application.wantContinuousButtonPressedView;
var view = Application.WantContinuousButtonPressedView;
if (view == null) {
break;
}
Expand Down Expand Up @@ -1187,7 +1187,7 @@ internal class NetDriver : ConsoleDriver {
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;

Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ internal class WindowsDriver : ConsoleDriver {
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; }

Expand Down Expand Up @@ -1186,7 +1186,7 @@ async Task ProcessContinuousButtonPressedAsync (WindowsConsole.MouseEventRecord
Flags = mouseFlag
};

var view = Application.wantContinuousButtonPressedView;
var view = Application.WantContinuousButtonPressedView;
if (view == null) {
break;
}
Expand Down
156 changes: 101 additions & 55 deletions Terminal.Gui/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public static Toplevel MdiTop {
/// <value>The current.</value>
public static Toplevel Current { get; private set; }

/// <summary>
/// The current <see cref="View"/> object that wants continuous mouse button pressed events.
/// </summary>
public static View WantContinuousButtonPressedView { get; private set; }

/// <summary>
/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
/// </summary>
Expand Down Expand Up @@ -210,6 +215,24 @@ static void OnQuitKeyChanged (Key oldKey)
/// </summary>
public static bool IsMouseDisabled { get; set; }

/// <summary>
/// By default this is false which will continue executing the <see cref="RunLoop(RunState, bool)"/> method,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poor English phrasing. I think you mean:

"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."

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done thanks.

/// if you pass true, the method will exit after the first iteration.
/// </summary>
public static bool ExitRunLoopAfterFirstIteration { get; set; } = false;

/// <summary>
/// Notify that a new <see cref="RunState"/> token was created,
/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
/// </summary>
public static event Action<RunState> NotifyNewRunState;

/// <summary>
/// Notify that a existent <see cref="RunState"/> token is stopping,
/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
/// </summary>
public static event Action<Toplevel> NotifyStopRunState;

/// <summary>
/// This event is raised on each iteration of the <see cref="MainLoop"/>
/// </summary>
Expand Down Expand Up @@ -297,12 +320,12 @@ static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, I
}

// 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 ();
Expand Down Expand Up @@ -352,7 +375,10 @@ public RunState (Toplevel view)
{
Toplevel = view;
}
internal Toplevel Toplevel;
/// <summary>
/// The <see cref="Toplevel"/> belong to this <see cref="RunState"/>.
/// </summary>
public Toplevel Toplevel { get; internal set; }

/// <summary>
/// Releases alTop = l resource used by the <see cref="Application.RunState"/> object.
Expand Down Expand Up @@ -385,7 +411,7 @@ protected virtual void Dispose (bool disposing)

static void ProcessKeyEvent (KeyEvent ke)
{
if(RootKeyEvent?.Invoke(ke) ?? false) {
if (RootKeyEvent?.Invoke (ke) ?? false) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the space after invoke may be accidental?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's how .editorconfig is configured.

return;
}

Expand Down Expand Up @@ -580,9 +606,8 @@ public static void UngrabMouse ()
/// </para>
/// <para>Return true to suppress the KeyPress event</para>
/// </summary>
public static Func<KeyEvent,bool> RootKeyEvent;
public static Func<KeyEvent, bool> RootKeyEvent;

internal static View wantContinuousButtonPressedView;
static View lastMouseOwnerView;

static void ProcessMouseEvent (MouseEvent me)
Expand All @@ -594,9 +619,9 @@ static void ProcessMouseEvent (MouseEvent me)
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;
}
Expand Down Expand Up @@ -655,9 +680,9 @@ static void ProcessMouseEvent (MouseEvent me)
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);
Expand Down Expand Up @@ -952,51 +977,65 @@ public static void RunLoop (RunState state, bool wait = true)

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;
RunIteration (ref state, wait, ref firstIteration);
}
}

/// <summary>
/// Run the <see cref="RunLoop(RunState, bool)"/> iteration.
tig marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="state">The state returned by the Begin method.</param>
/// <param name="wait">If will execute the runloop waiting for events.</param>
/// <param name="firstIteration">If it's the first run loop iteration.</param>
public static void RunIteration (ref RunState state, bool wait, ref bool firstIteration)
tig marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}
}

Expand Down Expand Up @@ -1107,7 +1146,12 @@ public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null
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)
Expand Down Expand Up @@ -1190,6 +1234,8 @@ public static void RequestStop (Toplevel top = null)
return;
}
currentTop.Running = false;
if (ExitRunLoopAfterFirstIteration)
NotifyStopRunState?.Invoke (currentTop);
}
}

Expand Down
6 changes: 4 additions & 2 deletions Terminal.Gui/Core/ConsoleDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -665,8 +665,10 @@ public abstract class ConsoleDriver {
/// </summary>
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; }
/// <summary>
/// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
/// </summary>
public virtual int [,,] Contents { get; }

/// <summary>
/// Initializes the driver
Expand Down
29 changes: 27 additions & 2 deletions Terminal.Gui/Core/MainLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,46 @@ public interface IMainLoopDriver {
/// does not seem to be a way of supporting this on Windows.
/// </remarks>
public class MainLoop {
internal class Timeout {
/// <summary>
/// Provides data for timers running manipulation.
/// </summary>
public sealed class Timeout {
/// <summary>
/// Time to wait before invoke the callback.
/// </summary>
public TimeSpan Span;
/// <summary>
/// The function that will be invoked.
/// </summary>
public Func<MainLoop, bool> Callback;
}

internal SortedList<long, Timeout> timeouts = new SortedList<long, Timeout> ();
object timeoutsLockToken = new object ();
internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();

/// <summary>
/// Gets the list of all timeouts.
/// </summary>
public SortedList<long, Timeout> Timeouts => timeouts;
tig marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets the list of all idle handlers.
/// </summary>
public List<Func<bool>> IdleHandlers => idleHandlers;

/// <summary>
/// The current IMainLoopDriver in use.
/// </summary>
/// <value>The driver.</value>
public IMainLoopDriver Driver { get; }

/// <summary>
/// Invoked when a new timeout is added to be used on the case
/// if <see cref="Application.ExitRunLoopAfterFirstIteration"/> is true,
/// </summary>
public event Action<long> TimeoutAdded;
tig marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Creates a new Mainloop.
/// </summary>
Expand Down Expand Up @@ -121,6 +146,7 @@ void AddTimeout (TimeSpan time, Timeout timeout)
lock (timeoutsLockToken) {
var k = (DateTime.UtcNow + time).Ticks;
timeouts.Add (NudgeToUniqueKey(k), timeout);
TimeoutAdded?.Invoke (k);
}
}

Expand Down Expand Up @@ -192,7 +218,6 @@ void RunTimers ()
}
}
}

}

/// <summary>
Expand Down