#nullable enable
using System.Diagnostics;
using System.Reflection;
namespace Terminal.Gui;
///
/// Helper class for managing overlapped views in the application.
///
public static class ApplicationOverlapped
{
///
/// Gets or sets if is in overlapped mode within a Toplevel container.
///
///
///
public static bool IsOverlapped (Toplevel? top)
{
return ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != top && !top!.Modal;
}
///
/// Gets the list of the Overlapped children which are not modal from the
/// .
///
public static List? OverlappedChildren
{
get
{
if (OverlappedTop is { })
{
List overlappedChildren = new ();
lock (Application.TopLevels)
{
foreach (Toplevel top in Application.TopLevels)
{
if (top != OverlappedTop && !top.Modal)
{
overlappedChildren.Add (top);
}
}
}
return overlappedChildren;
}
return null;
}
}
///
/// The object used for the application on startup which
/// is true.
///
public static Toplevel? OverlappedTop
{
get
{
if (Application.Top is { IsOverlappedContainer: true })
{
return Application.Top;
}
return null;
}
}
/// Brings the superview of the most focused overlapped view is on front.
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);
}
}
/// Gets the current visible Toplevel overlapped child that matches the arguments pattern.
/// The type.
/// The strings to exclude.
/// The matched view.
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;
}
///
/// Sets the focus to the next view in the specified direction within the provided list of views.
/// If the end of the list is reached, the focus wraps around to the first view in the list.
/// The method considers the current focused view (`Application.Current`) and attempts to move the focus
/// to the next view in the specified direction. If the focus cannot be set to the next view, it wraps around
/// to the first view in the list.
///
///
///
internal static void SetFocusToNextViewWithWrap (IEnumerable? viewsInTabIndexes, NavigationDirection direction)
{
if (viewsInTabIndexes is null)
{
return;
}
// This code-path only executes in obtuse IsOverlappedContainer scenarios.
Debug.Assert (Application.Current!.IsOverlappedContainer);
bool foundCurrentView = false;
bool focusSet = false;
IEnumerable indexes = viewsInTabIndexes as View [] ?? viewsInTabIndexes.ToArray ();
int viewCount = indexes.Count ();
int currentIndex = 0;
foreach (View view in indexes)
{
if (view == Application.Current)
{
foundCurrentView = true;
}
else if (foundCurrentView && !focusSet)
{
// One of the views is Current, but view is not. Attempt to Advance...
Application.Current!.SuperView?.AdvanceFocus (direction, null);
// QUESTION: AdvanceFocus returns false AND sets Focused to null if no view was found to advance to. Should't we only set focusProcessed if it returned true?
focusSet = true;
if (Application.Current.SuperView?.Focused != Application.Current)
{
return;
}
// Either AdvanceFocus didn't set focus or the view it set focus to is not current...
// continue...
}
currentIndex++;
if (foundCurrentView && !focusSet && currentIndex == viewCount)
{
// One of the views is Current AND AdvanceFocus didn't set focus AND we are at the last view in the list...
// This means we should wrap around to the first view in the list.
indexes.First ().SetFocus ();
}
}
}
///
/// Move to the next Overlapped child from the and set it as the if
/// it is not already.
///
///
///
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;
}
/// Move to the next Overlapped child from the .
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 ();
}
}
}
/// Move to the previous Overlapped child from the .
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;
}
///
/// Finds the first Toplevel in the stack that is Visible and who's Frame contains the .
///
///
///
///
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;
}
///
/// Given , returns the first Superview up the chain that is .
///
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;
}
///
/// If the is not the then is moved to the top of
/// the Toplevel stack and made Current.
///
///
///
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;
}
}