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