123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- #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
- /// <summary>Holds the stack of TopLevel views.</summary>
- // about TopLevels that are just a SubView of another View?
- internal static readonly Stack<Toplevel> _topLevels = new ();
- /// <summary>The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Top"/>)</summary>
- /// <value>The top.</value>
- public static Toplevel? Top { get; private set; }
- // TODO: Determine why this can't just return _topLevels.Peek()?
- /// <summary>
- /// The current <see cref="Toplevel"/> object. This is updated in <see cref="Application.Begin"/> enters and leaves to
- /// point to the current
- /// <see cref="Toplevel"/> .
- /// </summary>
- /// <remarks>
- /// This will only be distinct from <see cref="Application.Top"/> in scenarios where <see cref="Toplevel.IsOverlappedContainer"/> is <see langword="true"/>.
- /// </remarks>
- /// <value>The current.</value>
- public static Toplevel? Current { get; private set; }
- /// <summary>
- /// If <paramref name="topLevel"/> is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current.
- /// </summary>
- 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 ();
- }
- }
- /// <summary>
- /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the <paramref name="location"/>.
- /// </summary>
- /// <param name="start"></param>
- /// <param name="location"></param>
- /// <returns></returns>
- 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;
- }
- /// <summary>
- /// Given <paramref name="view"/>, returns the first Superview up the chain that is <see cref="Top"/>.
- /// </summary>
- 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;
- }
- /// <summary>
- /// If the <see cref="Current"/> is not the <paramref name="top"/> then <paramref name="top"/> is moved to the top of the Toplevel stack and made Current.
- /// </summary>
- /// <param name="top"></param>
- /// <returns></returns>
- 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;
- }
- /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
- /// <remarks>
- /// Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
- /// <see cref="Application"/> from changing it's size to match the new terminal size.
- /// </remarks>
- public static event EventHandler<SizeChangedEventArgs>? SizeChanging;
- /// <summary>
- /// Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
- /// <see cref="SizeChanging"/> event.
- /// </summary>
- /// <param name="args">The new size.</param>
- /// <returns><see lanword="true"/>if the size was changed.</returns>
- 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;
- }
- }
|