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