#nullable enable namespace Terminal.Gui; public static partial class Application // Toplevel handling { // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What /// Holds the stack of TopLevel views. // about TopLevels that are just a SubView of another View? internal static readonly Stack _topLevels = new (); /// The object used for the application on startup () /// The top. public static Toplevel? Top { get; private set; } // TODO: Determine why this can't just return _topLevels.Peek()? /// /// The current object. This is updated in enters and leaves to /// point to the current /// . /// /// /// This will only be distinct from in scenarios where is . /// /// The current. public static Toplevel? Current { get; private set; } /// /// If is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current. /// private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel) { if (!topLevel.Running || (topLevel == Current && topLevel.Visible) || OverlappedTop == null || _topLevels.Peek ().Modal) { return; } foreach (Toplevel top in _topLevels.Reverse ()) { if (top.Modal && top != Current) { MoveCurrent (top); return; } } if (!topLevel.Visible && topLevel == Current) { OverlappedMoveNext (); } } /// /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the . /// /// /// /// private static Toplevel FindDeepestTop (Toplevel start, in Point location) { if (!start.Frame.Contains (location)) { return null; } if (_topLevels is { Count: > 0 }) { int rx = location.X - start.Frame.X; int ry = location.Y - start.Frame.Y; foreach (Toplevel t in _topLevels) { if (t != Current) { if (t != start && t.Visible && t.Frame.Contains (rx, ry)) { start = t; break; } } } } return start; } /// /// Given , returns the first Superview up the chain that is . /// private static View FindTopFromView (View view) { View top = view?.SuperView is { } && view?.SuperView != Top ? view.SuperView : view; while (top?.SuperView is { } && top?.SuperView != Top) { top = top.SuperView; } return top; } /// /// If the is not the then is moved to the top of the Toplevel stack and made Current. /// /// /// private static bool MoveCurrent (Toplevel top) { // The Current is modal and the top is not modal Toplevel then // the Current must be moved above the first not modal Toplevel. if (OverlappedTop is { } && top != OverlappedTop && top != Current && Current?.Modal == true && !_topLevels.Peek ().Modal) { lock (_topLevels) { _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); } var index = 0; Toplevel [] savedToplevels = _topLevels.ToArray (); foreach (Toplevel t in savedToplevels) { if (!t!.Modal && t != Current && t != top && t != savedToplevels [index]) { lock (_topLevels) { _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ()); } } index++; } return false; } // The Current and the top are both not running Toplevel then // the top must be moved above the first not running Toplevel. if (OverlappedTop is { } && top != OverlappedTop && top != Current && Current?.Running == false && top?.Running == false) { lock (_topLevels) { _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ()); } var index = 0; foreach (Toplevel t in _topLevels.ToArray ()) { if (!t.Running && t != Current && index > 0) { lock (_topLevels) { _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ()); } } index++; } return false; } if ((OverlappedTop is { } && top?.Modal == true && _topLevels.Peek () != top) || (OverlappedTop is { } && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop) || (OverlappedTop is { } && Current?.Modal == false && top != Current) || (OverlappedTop is { } && Current?.Modal == true && top == OverlappedTop)) { lock (_topLevels) { _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ()); Current = top; } } return true; } /// Invoked when the terminal's size changed. The new size of the terminal is provided. /// /// Event handlers can set to to prevent /// from changing it's size to match the new terminal size. /// public static event EventHandler? SizeChanging; /// /// Called when the application's size changes. Sets the size of all s and fires the /// event. /// /// The new size. /// if the size was changed. public static bool OnSizeChanging (SizeChangedEventArgs args) { SizeChanging?.Invoke (null, args); if (args.Cancel || args.Size is null) { return false; } foreach (Toplevel t in _topLevels) { t.SetRelativeLayout (args.Size.Value); t.LayoutSubviews (); t.PositionToplevels (); t.OnSizeChanging (new (args.Size)); if (PositionCursor (t)) { Driver.UpdateCursor (); } } Refresh (); return true; } }