#nullable enable
namespace Terminal.Gui;
///
/// Toplevel views are used for both an application's main view (filling the entire screen and for modal (pop-up)
/// views such as , , and ).
///
///
///
/// Toplevel views can run as modal (popup) views, started by calling
/// . They return control to the caller when
/// has been called (which sets the
/// property to false).
///
///
/// A Toplevel is created when an application initializes Terminal.Gui by calling .
/// The application Toplevel can be accessed via . Additional Toplevels can be created
/// and run (e.g. s). To run a Toplevel, create the and call
/// .
///
///
public partial class Toplevel : View
{
///
/// Initializes a new instance of the class,
/// defaulting to full screen. The and properties will be set to the
/// dimensions of the terminal using .
///
public Toplevel ()
{
CanFocus = true;
TabStop = TabBehavior.TabGroup;
Arrangement = ViewArrangement.Overlapped;
Width = Dim.Fill ();
Height = Dim.Fill ();
ColorScheme = Colors.ColorSchemes ["TopLevel"];
MouseClick += Toplevel_MouseClick;
}
#region Keyboard & Mouse
// TODO: IRunnable: Re-implement - Modal means IRunnable, ViewArrangement.Overlapped where modalView.Z > allOtherViews.Max (v = v.Z), and exclusive key/mouse input.
///
/// Determines whether the is modal or not. If set to false (the default):
///
/// -
/// events will propagate keys upwards.
///
/// -
/// The Toplevel will act as an embedded view (not a modal/pop-up).
///
///
/// If set to true:
///
/// -
/// events will NOT propagate keys upwards.
///
/// -
/// The Toplevel will and look like a modal (pop-up) (e.g. see .
///
///
///
public bool Modal { get; set; }
private void Toplevel_MouseClick (object? sender, MouseEventArgs e) { e.Handled = InvokeCommand (Command.HotKey) == true; }
#endregion
#region Subviews
// TODO: Deprecate - Any view can host a menubar in v2
/// Gets the latest added into this Toplevel.
public MenuBar? MenuBar => (MenuBar?)Subviews?.LastOrDefault (s => s is MenuBar);
//// TODO: Deprecate - Any view can host a statusbar in v2
///// Gets the latest added into this Toplevel.
//public StatusBar? StatusBar => (StatusBar?)Subviews?.LastOrDefault (s => s is StatusBar);
#endregion
#region Life Cycle
// TODO: IRunnable: Re-implement as a property on IRunnable
/// Gets or sets whether the main loop for this is running or not.
/// Setting this property directly is discouraged. Use instead.
public bool Running { get; set; }
// TODO: IRunnable: Re-implement in IRunnable
///
/// if was already loaded by the
/// , otherwise.
///
public bool IsLoaded { get; private set; }
// TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Activating/Activate
/// Invoked when the Toplevel active.
public event EventHandler? Activate;
// TODO: IRunnable: Re-implement as an event on IRunnable; IRunnable.Deactivating/Deactivate?
/// Invoked when the Toplevel ceases to be active.
public event EventHandler? Deactivate;
/// Invoked when the Toplevel's is closed by .
public event EventHandler? Closed;
///
/// Invoked when the Toplevel's is being closed by
/// .
///
public event EventHandler? Closing;
///
/// Invoked when the has begun to be loaded. A Loaded event handler
/// is a good place to finalize initialization before calling .
///
public event EventHandler? Loaded;
///
/// Called from before the redraws for the first
/// time.
///
///
/// Overrides must call base.OnLoaded() to ensure any Toplevel subviews are initialized properly and the
/// event is raised.
///
public virtual void OnLoaded ()
{
IsLoaded = true;
foreach (var view in Subviews.Where (v => v is Toplevel))
{
var tl = (Toplevel)view;
tl.OnLoaded ();
}
Loaded?.Invoke (this, EventArgs.Empty);
}
///
/// Invoked when the main loop has started it's first iteration. Subscribe to this event to
/// perform tasks when the has been laid out and focus has been set. changes.
///
/// A Ready event handler is a good place to finalize initialization after calling
/// on this .
///
///
public event EventHandler? Ready;
///
/// Stops and closes this . If this Toplevel is the top-most Toplevel,
/// will be called, causing the application to exit.
///
public virtual void RequestStop ()
{
Application.RequestStop (Application.Top);
}
///
/// Invoked when the Toplevel has been unloaded. A Unloaded event handler is a good place
/// to dispose objects after calling .
///
public event EventHandler? Unloaded;
internal virtual void OnActivate (Toplevel deactivated) { Activate?.Invoke (this, new (deactivated)); }
internal virtual void OnClosed (Toplevel top) { Closed?.Invoke (this, new (top)); }
internal virtual bool OnClosing (ToplevelClosingEventArgs ev)
{
Closing?.Invoke (this, ev);
return ev.Cancel;
}
internal virtual void OnDeactivate (Toplevel activated) { Deactivate?.Invoke (this, new (activated)); }
///
/// Called from after the has entered the first iteration
/// of the loop.
///
internal virtual void OnReady ()
{
foreach (var view in Subviews.Where (v => v is Toplevel))
{
var tl = (Toplevel)view;
tl.OnReady ();
}
Ready?.Invoke (this, EventArgs.Empty);
}
/// Called from before the is disposed.
internal virtual void OnUnloaded ()
{
foreach (var view in Subviews.Where (v => v is Toplevel))
{
var tl = (Toplevel)view;
tl.OnUnloaded ();
}
Unloaded?.Invoke (this, EventArgs.Empty);
}
#endregion
#region Size / Position Management
// TODO: Make cancelable?
internal void OnSizeChanging (SizeChangedEventArgs size) { SizeChanging?.Invoke (this, size); }
///
/// Adjusts the location and size of within this Toplevel. Virtual method enabling
/// implementation of specific positions for inherited views.
///
/// The Toplevel to adjust.
public virtual void PositionToplevel (Toplevel? top)
{
if (top is null)
{
return;
}
View? superView = GetLocationEnsuringFullVisibility (
top,
top.Frame.X,
top.Frame.Y,
out int nx,
out int ny
//,
// out StatusBar? sb
);
if (superView is null)
{
return;
}
//var layoutSubviews = false;
var maxWidth = 0;
if (superView.Margin is { } && superView == top.SuperView)
{
maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right;
}
// BUGBUG: The && true is a temp hack
if ((superView != top || top?.SuperView is { } || (top != Application.Top && top!.Modal) || (top == Application.Top && top?.SuperView is null))
&& (top!.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y))
{
if (top?.X is null or PosAbsolute && top?.Frame.X != nx)
{
top!.X = nx;
//layoutSubviews = true;
}
if (top?.Y is null or PosAbsolute && top?.Frame.Y != ny)
{
top!.Y = ny;
//layoutSubviews = true;
}
}
//if (superView.IsLayoutNeeded () || layoutSubviews)
//{
// superView.LayoutSubviews ();
//}
//if (IsLayoutNeeded ())
//{
// LayoutSubviews ();
//}
}
/// Invoked when the terminal has been resized. The new of the terminal is provided.
public event EventHandler? SizeChanging;
#endregion
}
///
/// Implements the for comparing two s used by
/// .
///
public class ToplevelEqualityComparer : IEqualityComparer
{
/// Determines whether the specified objects are equal.
/// The first object of type to compare.
/// The second object of type to compare.
/// if the specified objects are equal; otherwise, .
public bool Equals (Toplevel? x, Toplevel? y)
{
if (y is null && x is null)
{
return true;
}
if (x is null || y is null)
{
return false;
}
if (x.Id == y.Id)
{
return true;
}
return false;
}
/// Returns a hash code for the specified object.
/// The for which a hash code is to be returned.
/// A hash code for the specified object.
///
/// The type of is a reference type and
/// is .
///
public int GetHashCode (Toplevel obj)
{
if (obj is null)
{
throw new ArgumentNullException ();
}
var hCode = 0;
if (int.TryParse (obj.Id, out int result))
{
hCode = result;
}
return hCode.GetHashCode ();
}
}