using System;
using System.Collections.Generic;
using System.Linq;
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
/// ).
///
///
///
/// Toplevels 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 {
internal static Point? _dragPosition;
Point _startGrabPoint;
// BUGBUG: Remove; Toplevel should be ComputedLayout
///
/// Initializes a new instance of the class with the specified
/// layout.
///
///
/// A Superview-relative rectangle specifying the location and size for the new
/// Toplevel
///
public Toplevel (Rect frame) : base (frame) => SetInitialProperties ();
///
/// Initializes a new instance of the class with
/// layout, defaulting to full screen. The and properties
/// will be set to the dimensions of the terminal using .
///
public Toplevel ()
{
SetInitialProperties ();
Width = Dim.Fill ();
Height = Dim.Fill ();
}
///
/// 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; }
///
/// Gets or sets a value indicating whether this can focus.
///
/// true if can focus; otherwise, false.
public override bool CanFocus => SuperView == null ? true : base.CanFocus;
///
/// 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; }
///
/// Gets or sets the menu for this Toplevel.
///
public virtual MenuBar MenuBar { get; set; }
///
/// Gets or sets the status bar for this Toplevel.
///
public virtual StatusBar StatusBar { get; set; }
///
/// if was already loaded by the
/// , otherwise.
///
public bool IsLoaded { get; private set; }
///
/// 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;
///
/// 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;
///
/// Invoked when the Toplevel has been unloaded.
/// A Unloaded event handler is a good place to dispose objects after calling
/// .
///
public event EventHandler Unloaded;
///
/// Invoked when the Toplevel becomes the
/// Toplevel.
///
public event EventHandler Activate;
///
/// Invoked when the Toplevel ceases to be the
/// Toplevel.
///
public event EventHandler Deactivate;
///
/// Invoked when a child of the Toplevel is closed by
/// .
///
public event EventHandler ChildClosed;
///
/// Invoked when the last child of the Toplevel is closed from
/// by .
///
public event EventHandler AllChildClosed;
///
/// Invoked when the Toplevel's is being closed by
/// .
///
public event EventHandler Closing;
///
/// Invoked when the Toplevel's is closed by
/// .
///
public event EventHandler Closed;
///
/// Invoked when a child Toplevel's has been loaded.
///
public event EventHandler ChildLoaded;
///
/// Invoked when a cjhild Toplevel's has been unloaded.
///
public event EventHandler ChildUnloaded;
///
/// Invoked when the terminal has been resized. The new of the terminal is provided.
///
public event EventHandler SizeChanging;
// TODO: Make cancelable?
internal virtual void OnSizeChanging (SizeChangedEventArgs size) => SizeChanging?.Invoke (this, size);
internal virtual void OnChildUnloaded (Toplevel top) => ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top));
internal virtual void OnChildLoaded (Toplevel top) => ChildLoaded?.Invoke (this, new ToplevelEventArgs (top));
internal virtual void OnClosed (Toplevel top) => Closed?.Invoke (this, new ToplevelEventArgs (top));
internal virtual bool OnClosing (ToplevelClosingEventArgs ev)
{
Closing?.Invoke (this, ev);
return ev.Cancel;
}
internal virtual void OnAllChildClosed () => AllChildClosed?.Invoke (this, EventArgs.Empty);
internal virtual void OnChildClosed (Toplevel top)
{
if (IsOverlappedContainer) {
SetSubViewNeedsDisplay ();
}
ChildClosed?.Invoke (this, new ToplevelEventArgs (top));
}
internal virtual void OnDeactivate (Toplevel activated) => Deactivate?.Invoke (this, new ToplevelEventArgs (activated));
internal virtual void OnActivate (Toplevel deactivated) => Activate?.Invoke (this, new ToplevelEventArgs (deactivated));
///
/// Called from before the redraws for
/// the first time.
///
public virtual void OnLoaded ()
{
IsLoaded = true;
foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
tl.OnLoaded ();
}
Loaded?.Invoke (this, EventArgs.Empty);
}
///
/// Called from after the has entered the
/// first iteration of the loop.
///
internal virtual void OnReady ()
{
foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
tl.OnReady ();
}
Ready?.Invoke (this, EventArgs.Empty);
}
///
/// Called from before the is disposed.
///
internal virtual void OnUnloaded ()
{
foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
tl.OnUnloaded ();
}
Unloaded?.Invoke (this, EventArgs.Empty);
}
void SetInitialProperties ()
{
ColorScheme = Colors.TopLevel;
Application.GrabbingMouse += Application_GrabbingMouse;
Application.UnGrabbingMouse += Application_UnGrabbingMouse;
// TODO: v2 - ALL Views (Responders??!?!) should support the commands related to
// - Focus
// Move the appropriate AddCommand calls to `Responder`
// Things this view knows how to do
AddCommand (Command.QuitToplevel, () => {
QuitToplevel ();
return true;
});
AddCommand (Command.Suspend, () => {
Driver.Suspend ();
;
return true;
});
AddCommand (Command.NextView, () => {
MoveNextView ();
return true;
});
AddCommand (Command.PreviousView, () => {
MovePreviousView ();
return true;
});
AddCommand (Command.NextViewOrTop, () => {
MoveNextViewOrTop ();
return true;
});
AddCommand (Command.PreviousViewOrTop, () => {
MovePreviousViewOrTop ();
return true;
});
AddCommand (Command.Refresh, () => {
Application.Refresh ();
return true;
});
AddCommand (Command.Accept, () => {
// TODO: Perhaps all views should support the concept of being default?
// TODO: It's bad that Toplevel is tightly coupled with Button
if (Subviews.FirstOrDefault (v => v is Button && ((Button)v).IsDefault && ((Button)v).Enabled) is Button defaultBtn) {
defaultBtn.InvokeCommand (Command.Accept);
return true;
}
return false;
});
// Default keybindings for this view
KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel);
KeyBindings.Add (KeyCode.CursorRight, Command.NextView);
KeyBindings.Add (KeyCode.CursorDown, Command.NextView);
KeyBindings.Add (KeyCode.CursorLeft, Command.PreviousView);
KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView);
KeyBindings.Add (KeyCode.Tab, Command.NextView);
KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.PreviousView);
KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextViewOrTop);
KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop);
KeyBindings.Add (KeyCode.F5, Command.Refresh);
KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
#if UNIX_KEY_BINDINGS
KeyBindings.Add (Key.Z | Key.CtrlMask, Command.Suspend);
KeyBindings.Add (Key.L | Key.CtrlMask, Command.Refresh);// Unix
KeyBindings.Add (Key.F | Key.CtrlMask, Command.NextView);// Unix
KeyBindings.Add (Key.I | Key.CtrlMask, Command.NextView); // Unix
KeyBindings.Add (Key.B | Key.CtrlMask, Command.PreviousView);// Unix
#endif
// This enables the default button to be activated by the Enter key.
KeyBindings.Add (KeyCode.Enter, Command.Accept);
}
void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
{
if (Application.MouseGrabView == this && _dragPosition.HasValue) {
e.Cancel = true;
}
}
void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
{
if (Application.MouseGrabView == this && _dragPosition.HasValue) {
e.Cancel = true;
}
}
///
/// Invoked when the is changed.
///
public event EventHandler AlternateForwardKeyChanged;
///
/// Virtual method to invoke the event.
///
///
public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
{
KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
AlternateForwardKeyChanged?.Invoke (this, e);
}
///
/// Invoked when the is changed.
///
public event EventHandler AlternateBackwardKeyChanged;
///
/// Virtual method to invoke the event.
///
///
public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
{
KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
AlternateBackwardKeyChanged?.Invoke (this, e);
}
///
/// Invoked when the is changed.
///
public event EventHandler QuitKeyChanged;
///
/// Virtual method to invoke the event.
///
///
public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
{
KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
QuitKeyChanged?.Invoke (this, e);
}
void MovePreviousViewOrTop ()
{
if (Application.OverlappedTop == null) {
var top = Modal ? this : Application.Top;
top.FocusPrev ();
if (top.Focused == null) {
top.FocusPrev ();
}
top.SetNeedsDisplay ();
Application.BringOverlappedTopToFront ();
} else {
Application.OverlappedMovePrevious ();
}
}
void MoveNextViewOrTop ()
{
if (Application.OverlappedTop == null) {
var top = Modal ? this : Application.Top;
top.FocusNext ();
if (top.Focused == null) {
top.FocusNext ();
}
top.SetNeedsDisplay ();
Application.BringOverlappedTopToFront ();
} else {
Application.OverlappedMoveNext ();
}
}
void MovePreviousView ()
{
var old = GetDeepestFocusedSubview (Focused);
if (!FocusPrev ()) {
FocusPrev ();
}
if (old != Focused && old != Focused?.Focused) {
old?.SetNeedsDisplay ();
Focused?.SetNeedsDisplay ();
} else {
FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward);
}
}
void MoveNextView ()
{
var old = GetDeepestFocusedSubview (Focused);
if (!FocusNext ()) {
FocusNext ();
}
if (old != Focused && old != Focused?.Focused) {
old?.SetNeedsDisplay ();
Focused?.SetNeedsDisplay ();
} else {
FocusNearestView (SuperView?.TabIndexes, Direction.Forward);
}
}
void QuitToplevel ()
{
if (Application.OverlappedTop != null) {
Application.OverlappedTop.RequestStop ();
} else {
Application.RequestStop ();
}
}
View GetDeepestFocusedSubview (View view)
{
if (view == null) {
return null;
}
foreach (var v in view.Subviews) {
if (v.HasFocus) {
return GetDeepestFocusedSubview (v);
}
}
return view;
}
void FocusNearestView (IEnumerable views, Direction direction)
{
if (views == null) {
return;
}
var found = false;
var focusProcessed = false;
var idx = 0;
foreach (var v in views) {
if (v == this) {
found = true;
}
if (found && v != this) {
if (direction == Direction.Forward) {
SuperView?.FocusNext ();
} else {
SuperView?.FocusPrev ();
}
focusProcessed = true;
if (SuperView.Focused != null && SuperView.Focused != this) {
return;
}
} else if (found && !focusProcessed && idx == views.Count () - 1) {
views.ToList () [0].SetFocus ();
}
idx++;
}
}
///
public override void Add (View view)
{
CanFocus = true;
AddMenuStatusBar (view);
base.Add (view);
}
internal void AddMenuStatusBar (View view)
{
if (view is MenuBar) {
MenuBar = view as MenuBar;
}
if (view is StatusBar) {
StatusBar = view as StatusBar;
}
}
///
public override void Remove (View view)
{
if (this is Toplevel Toplevel && Toplevel.MenuBar != null) {
RemoveMenuStatusBar (view);
}
base.Remove (view);
}
///
public override void RemoveAll ()
{
if (this == Application.Top) {
MenuBar?.Dispose ();
MenuBar = null;
StatusBar?.Dispose ();
StatusBar = null;
}
base.RemoveAll ();
}
internal void RemoveMenuStatusBar (View view)
{
if (view is MenuBar) {
MenuBar?.Dispose ();
MenuBar = null;
}
if (view is StatusBar) {
StatusBar?.Dispose ();
StatusBar = null;
}
}
///
/// Gets a new location of the that is within the Bounds of the
/// 's
/// (e.g. for dragging a Window).
/// The `out` parameters are the new X and Y coordinates.
///
///
/// If does not have a or it's SuperView is not
///
/// the position will be bound by the and
/// .
///
/// The Toplevel that is to be moved.
/// The target x location.
/// The target y location.
/// The x location that will ensure will be visible.
/// The y location that will ensure will be visible.
/// The new top most menuBar
/// The new top most statusBar
///
/// Either (if does not have a Super View) or
/// 's SuperView. This can be used to ensure LayoutSubviews is called on the
/// correct View.
///
internal View GetLocationThatFits (Toplevel top,
int targetX,
int targetY,
out int nx,
out int ny,
out MenuBar menuBar,
out StatusBar statusBar)
{
int maxWidth;
View superView;
if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
maxWidth = Driver.Cols;
superView = Application.Top;
} else {
// Use the SuperView's Bounds, not Frame
maxWidth = top.SuperView.Bounds.Width;
superView = top.SuperView;
}
if (superView.Margin != null && superView == top.SuperView) {
maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
}
if (top.Frame.Width <= maxWidth) {
nx = Math.Max (targetX, 0);
nx = nx + top.Frame.Width > maxWidth ? Math.Max (maxWidth - top.Frame.Width, 0) : nx;
if (nx > top.Frame.X + top.Frame.Width) {
nx = Math.Max (top.Frame.Right, 0);
}
} else {
nx = targetX;
}
//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
bool menuVisible, statusVisible;
if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
menuVisible = Application.Top.MenuBar?.Visible == true;
menuBar = Application.Top.MenuBar;
} else {
var t = top.SuperView;
while (t is not Toplevel) {
t = t.SuperView;
}
menuVisible = ((Toplevel)t).MenuBar?.Visible == true;
menuBar = ((Toplevel)t).MenuBar;
}
if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
maxWidth = menuVisible ? 1 : 0;
} else {
maxWidth = 0;
}
ny = Math.Max (targetY, maxWidth);
if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
statusVisible = Application.Top.StatusBar?.Visible == true;
statusBar = Application.Top.StatusBar;
} else {
var t = top.SuperView;
while (t is not Toplevel) {
t = t.SuperView;
}
statusVisible = ((Toplevel)t).StatusBar?.Visible == true;
statusBar = ((Toplevel)t).StatusBar;
}
if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
maxWidth = statusVisible ? Driver.Rows - 1 : Driver.Rows;
} else {
maxWidth = statusVisible ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height;
}
if (superView.Margin != null && superView == top.SuperView) {
maxWidth -= superView.GetFramesThickness ().Top + superView.GetFramesThickness ().Bottom;
}
ny = Math.Min (ny, maxWidth);
if (top.Frame.Height <= maxWidth) {
ny = ny + top.Frame.Height > maxWidth ? Math.Max (maxWidth - top.Frame.Height, menuVisible ? 1 : 0) : ny;
if (ny > top.Frame.Y + top.Frame.Height) {
ny = Math.Max (top.Frame.Bottom, 0);
}
}
//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
return superView;
}
// TODO: v2 - Not sure this is needed anymore.
internal void PositionToplevels ()
{
PositionToplevel (this);
foreach (var top in Subviews) {
if (top is Toplevel) {
PositionToplevel ((Toplevel)top);
}
}
}
///
/// 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)
{
var superView = GetLocationThatFits (top, top.Frame.X, top.Frame.Y,
out var nx, out var ny, out _, out var sb);
var layoutSubviews = false;
var maxWidth = 0;
if (superView.Margin != null && superView == top.SuperView) {
maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
}
if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal || top?.SuperView == null && top.IsOverlapped) &&
(top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) &&
top.LayoutStyle == LayoutStyle.Computed) {
if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) {
top.X = nx;
layoutSubviews = true;
}
if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) {
top.Y = ny;
layoutSubviews = true;
}
}
// TODO: v2 - This is a hack to get the StatusBar to be positioned correctly.
if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) && top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) {
top.Height = Dim.Fill (sb.Visible ? 1 : 0);
layoutSubviews = true;
}
if (superView.LayoutNeeded || layoutSubviews) {
superView.LayoutSubviews ();
}
if (LayoutNeeded) {
LayoutSubviews ();
}
}
///
public override void OnDrawContent (Rect contentArea)
{
if (!Visible) {
return;
}
if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) {
//Driver.SetAttribute (GetNormalColor ());
// TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc...
Clear ();
LayoutSubviews ();
PositionToplevels ();
if (this == Application.OverlappedTop) {
foreach (var top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) {
if (top.Frame.IntersectsWith (Bounds)) {
if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) {
top.SetNeedsLayout ();
top.SetNeedsDisplay (top.Bounds);
top.Draw ();
top.OnRenderLineCanvas ();
}
}
}
}
// This should not be here, but in base
foreach (var view in Subviews) {
if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) {
//view.SetNeedsLayout ();
view.SetNeedsDisplay (view.Bounds);
view.SetSubViewNeedsDisplay ();
}
}
base.OnDrawContent (contentArea);
// This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true
//if (this.MenuBar != null && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu != null) {
// // TODO: Hack until we can get compositing working right.
// this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds);
//}
}
}
bool OutsideTopFrame (Toplevel top)
{
if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) {
return true;
}
return false;
}
///
public override bool MouseEvent (MouseEvent mouseEvent)
{
if (!CanFocus) {
return true;
}
//System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}");
int nx, ny;
if (!_dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed || mouseEvent.Flags == MouseFlags.Button2Pressed || mouseEvent.Flags == MouseFlags.Button3Pressed)) {
SetFocus ();
Application.BringOverlappedTopToFront ();
// Only start grabbing if the user clicks on the title bar.
// BUGBUG: Assumes Frame == Border and Title is always at Y == 0
if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) {
_startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y);
_dragPosition = new Point ();
nx = mouseEvent.X - mouseEvent.OfX;
ny = mouseEvent.Y - mouseEvent.OfY;
_dragPosition = new Point (nx, ny);
Application.GrabMouse (this);
}
//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
return true;
}
if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
mouseEvent.Flags == MouseFlags.Button3Pressed) {
if (_dragPosition.HasValue) {
if (SuperView == null) {
// Redraw the entire app window using just our Frame. Since we are
// Application.Top, and our Frame always == our Bounds (Location is always (0,0))
// our Frame is actually view-relative (which is what Redraw takes).
// We need to pass all the view bounds because since the windows was
// moved around, we don't know exactly what was the affected region.
Application.Top.SetNeedsDisplay ();
} else {
SuperView.SetNeedsDisplay ();
}
// BUGBUG: Assumes Frame == Border?
GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X),
mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y),
out nx, out ny, out _, out _);
_dragPosition = new Point (nx, ny);
X = nx;
Y = ny;
//System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}");
SetNeedsDisplay ();
return true;
}
}
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) {
_dragPosition = null;
Application.UngrabMouse ();
}
//System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}");
//System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}");
return false;
}
///
/// Stops and closes this . If this Toplevel is the top-most Toplevel,
/// will be called, causing the application to exit.
///
public virtual void RequestStop ()
{
if (IsOverlappedContainer &&
Running &&
(Application.Current == this || Application.Current?.Modal == false || Application.Current?.Modal == true && Application.Current?.Running == false)) {
foreach (var child in Application.OverlappedChildren) {
var ev = new ToplevelClosingEventArgs (this);
if (child.OnClosing (ev)) {
return;
}
child.Running = false;
Application.RequestStop (child);
}
Running = false;
Application.RequestStop (this);
} else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) {
var ev = new ToplevelClosingEventArgs (Application.Current);
if (OnClosing (ev)) {
return;
}
Application.RequestStop (Application.Current);
} else if (!IsOverlappedContainer && Running && (!Modal || Modal && Application.Current != this)) {
var ev = new ToplevelClosingEventArgs (this);
if (OnClosing (ev)) {
return;
}
Running = false;
Application.RequestStop (this);
} else {
Application.RequestStop (Application.Current);
}
}
///
/// Stops and closes the specified by . If
/// is the top-most Toplevel,
/// will be called, causing the application to exit.
///
/// The Toplevel to request stop.
public virtual void RequestStop (Toplevel top) => top.RequestStop ();
///
public override void PositionCursor ()
{
if (!IsOverlappedContainer) {
base.PositionCursor ();
if (Focused == null) {
EnsureFocus ();
if (Focused == null) {
Driver.SetCursorVisibility (CursorVisibility.Invisible);
}
}
return;
}
if (Focused == null) {
foreach (var top in Application.OverlappedChildren) {
if (top != this && top.Visible) {
top.SetFocus ();
return;
}
}
}
base.PositionCursor ();
if (Focused == null) {
Driver.SetCursorVisibility (CursorVisibility.Invisible);
}
}
///
public override bool OnEnter (View view) => MostFocused?.OnEnter (view) ?? base.OnEnter (view);
///
public override bool OnLeave (View view) => MostFocused?.OnLeave (view) ?? base.OnLeave (view);
///
protected override void Dispose (bool disposing)
{
Application.GrabbingMouse -= Application_GrabbingMouse;
Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
_dragPosition = null;
base.Dispose (disposing);
}
}
///
/// 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 == null && x == null) {
return true;
}
if (x == null || y == 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 == null) {
throw new ArgumentNullException ();
}
var hCode = 0;
if (int.TryParse (obj.Id, out var result)) {
hCode = result;
}
return hCode.GetHashCode ();
}
}
///
/// Implements the to sort the
/// from the if needed.
///
public sealed class ToplevelComparer : IComparer {
///
/// Compares two objects and returns a value indicating whether one is less than, equal to, or
/// greater than the other.
///
/// The first object to compare.
/// The second object to compare.
///
/// A signed integer that indicates the relative values of and
/// , as shown in the following table.Value Meaning Less than zero
/// is less than .Zero
/// equals .Greater than zero
/// is greater than .
///
public int Compare (Toplevel x, Toplevel y)
{
if (ReferenceEquals (x, y)) {
return 0;
}
if (x == null) {
return -1;
}
if (y == null) {
return 1;
}
return string.Compare (x.Id, y.Id);
}
}