Files
Terminal.Gui/Terminal.Gui/Application/Application.Overlapped.cs
2024-07-24 16:18:20 -06:00

385 lines
12 KiB
C#

#nullable enable
using System.Reflection;
namespace Terminal.Gui;
/// <summary>
/// Helper class for managing overlapped views in the application.
/// </summary>
public static class ApplicationOverlapped
{
/// <summary>
/// Gets or sets if <paramref name="top"/> is in overlapped mode within a Toplevel container.
/// </summary>
/// <param name="top"></param>
/// <returns></returns>
public static bool IsOverlapped (Toplevel? top)
{
return ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != top && !top!.Modal;
}
/// <summary>
/// Gets the list of the Overlapped children which are not modal <see cref="Toplevel"/> from the
/// <see cref="OverlappedTop"/>.
/// </summary>
public static List<Toplevel>? OverlappedChildren
{
get
{
if (OverlappedTop is { })
{
List<Toplevel> overlappedChildren = new ();
lock (Application.TopLevels)
{
foreach (Toplevel top in Application.TopLevels)
{
if (top != OverlappedTop && !top.Modal)
{
overlappedChildren.Add (top);
}
}
}
return overlappedChildren;
}
return null;
}
}
/// <summary>
/// The <see cref="Toplevel"/> object used for the application on startup which
/// <see cref="Toplevel.IsOverlappedContainer"/> is true.
/// </summary>
public static Toplevel? OverlappedTop
{
get
{
if (Application.Top is { IsOverlappedContainer: true })
{
return Application.Top;
}
return null;
}
}
/// <summary>Brings the superview of the most focused overlapped view is on front.</summary>
public static void BringOverlappedTopToFront ()
{
if (OverlappedTop is { })
{
return;
}
View? top = FindTopFromView (Application.Top?.MostFocused);
if (top is Toplevel && Application.Top?.Subviews.Count > 1 && Application.Top.Subviews [^1] != top)
{
Application.Top.BringSubviewToFront (top);
}
}
/// <summary>Gets the current visible Toplevel overlapped child that matches the arguments pattern.</summary>
/// <param name="type">The type.</param>
/// <param name="exclude">The strings to exclude.</param>
/// <returns>The matched view.</returns>
public static Toplevel? GetTopOverlappedChild (Type? type = null, string []? exclude = null)
{
if (OverlappedChildren is null || OverlappedTop is null)
{
return null;
}
foreach (Toplevel top in OverlappedChildren)
{
if (type is { } && top.GetType () == type && exclude?.Contains (top.Data.ToString ()) == false)
{
return top;
}
if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data.ToString ()) == true)
{
continue;
}
return top;
}
return null;
}
/// <summary>
/// Move to the next Overlapped child from the <see cref="OverlappedTop"/> and set it as the <see cref="Application.Top"/> if
/// it is not already.
/// </summary>
/// <param name="top"></param>
/// <returns></returns>
public static bool MoveToOverlappedChild (Toplevel? top)
{
ArgumentNullException.ThrowIfNull (top);
if (top.Visible && OverlappedTop is { } && Application.Current?.Modal == false)
{
lock (Application.TopLevels)
{
Application.TopLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
Application.Current = top;
}
return true;
}
return false;
}
/// <summary>Move to the next Overlapped child from the <see cref="OverlappedTop"/>.</summary>
public static void OverlappedMoveNext ()
{
if (OverlappedTop is { } && !Application.Current!.Modal)
{
lock (Application.TopLevels)
{
Application.TopLevels.MoveNext ();
var isOverlapped = false;
while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
{
if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
{
isOverlapped = true;
}
else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
{
MoveCurrent (Application.Top!);
break;
}
Application.TopLevels.MoveNext ();
}
Application.Current = Application.TopLevels.Peek ();
}
}
}
/// <summary>Move to the previous Overlapped child from the <see cref="OverlappedTop"/>.</summary>
public static void OverlappedMovePrevious ()
{
if (OverlappedTop is { } && !Application.Current!.Modal)
{
lock (Application.TopLevels)
{
Application.TopLevels.MovePrevious ();
var isOverlapped = false;
while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
{
if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
{
isOverlapped = true;
}
else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
{
MoveCurrent (Application.Top!);
break;
}
Application.TopLevels.MovePrevious ();
}
Application.Current = Application.TopLevels.Peek ();
}
}
}
internal static bool OverlappedChildNeedsDisplay ()
{
if (OverlappedTop is null)
{
return false;
}
lock (Application.TopLevels)
{
foreach (Toplevel top in Application.TopLevels)
{
if (top != Application.Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded))
{
OverlappedTop.SetSubViewNeedsDisplay ();
return true;
}
}
}
return false;
}
internal static bool SetCurrentOverlappedAsTop ()
{
if (OverlappedTop is null && Application.Current != Application.Top && Application.Current?.SuperView is null && Application.Current?.Modal == false)
{
Application.Top = Application.Current;
return true;
}
return false;
}
/// <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>
internal static Toplevel? FindDeepestTop (Toplevel start, in Point location)
{
if (!start.Frame.Contains (location))
{
return null;
}
lock (Application.TopLevels)
{
if (Application.TopLevels is not { Count: > 0 })
{
return start;
}
int rx = location.X - start.Frame.X;
int ry = location.Y - start.Frame.Y;
foreach (Toplevel t in Application.TopLevels)
{
if (t == Application.Current)
{
continue;
}
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="Application.Top"/>.
/// </summary>
internal static View? FindTopFromView (View? view)
{
if (view is null)
{
return null;
}
View top = view.SuperView is { } && view.SuperView != Application.Top
? view.SuperView
: view;
while (top?.SuperView is { } && top?.SuperView != Application.Top)
{
top = top!.SuperView;
}
return top;
}
/// <summary>
/// If the <see cref="Application.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>
internal 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 != Application.Current
&& Application.Current?.Modal == true
&& !Application.TopLevels.Peek ().Modal)
{
lock (Application.TopLevels)
{
Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
}
var index = 0;
Toplevel [] savedToplevels = Application.TopLevels.ToArray ();
foreach (Toplevel t in savedToplevels)
{
if (!t!.Modal && t != Application.Current && t != top && t != savedToplevels [index])
{
lock (Application.TopLevels)
{
Application.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 != Application.Current
&& Application.Current?.Running == false
&& top?.Running == false)
{
lock (Application.TopLevels)
{
Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
}
var index = 0;
foreach (Toplevel t in Application.TopLevels.ToArray ())
{
if (!t.Running && t != Application.Current && index > 0)
{
lock (Application.TopLevels)
{
Application.TopLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
}
}
index++;
}
return false;
}
if ((OverlappedTop is { } && top?.Modal == true && Application.TopLevels.Peek () != top)
|| (OverlappedTop is { } && Application.Current != OverlappedTop && Application.Current?.Modal == false && top == OverlappedTop)
|| (OverlappedTop is { } && Application.Current?.Modal == false && top != Application.Current)
|| (OverlappedTop is { } && Application.Current?.Modal == true && top == OverlappedTop))
{
lock (Application.TopLevels)
{
Application.TopLevels.MoveTo (top!, 0, new ToplevelEqualityComparer ());
Application.Current = top;
}
}
return true;
}
}