// // Toplevel.cs: Toplevel views can be modally executed // // Authors: // Miguel de Icaza (miguel@gnome.org) // using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace Terminal.Gui { /// /// Toplevel views can be modally executed. /// /// /// /// Toplevels can be modally executing 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 initialzies Terminal.Gui by callling . /// 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 . /// /// /// Toplevels can also opt-in to more sophisticated initialization /// by implementing . When they do /// so, the and /// methods will be called /// before running the view. /// If first-run-only initialization is preferred, the /// can be implemented too, in which case the /// methods will only be called if /// is . This allows proper inheritance hierarchies /// to override base class layout code optimally by doing so only on first run, /// instead of on every run. /// /// public class Toplevel : View { /// /// Gets or sets whether the for this is running or not. /// /// /// Setting this property directly is discouraged. Use instead. /// public bool Running { get; set; } /// /// Fired once the Toplevel's 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 `(topLevel)`. /// public Action Ready; /// /// Called from after the has entered it's first iteration of the loop. /// internal virtual void OnReady () { Ready?.Invoke (); } /// /// Initializes a new instance of the class with the specified absolute layout. /// /// A superview-relative rectangle specifying the location and size for the new Toplevel public Toplevel (Rect frame) : base (frame) { Initialize (); } /// /// Initializes a new instance of the class with layout, defaulting to full screen. /// public Toplevel () : base () { Initialize (); Width = Dim.Fill (); Height = Dim.Fill (); } void Initialize () { ColorScheme = Colors.Base; } /// /// Convenience factory method that creates a new Toplevel with the current terminal dimensions. /// /// The create. public static Toplevel Create () { return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows)); } /// /// Gets or sets a value indicating whether this can focus. /// /// true if can focus; otherwise, false. public override bool CanFocus { get => true; } /// /// Determines whether the is modal or not. /// Causes to propagate keys upwards /// by default unless set to . /// public bool Modal { get; set; } /// /// Gets or sets the menu for this Toplevel /// public MenuBar MenuBar { get; set; } /// /// Gets or sets the status bar for this Toplevel /// public StatusBar StatusBar { get; set; } /// public override bool ProcessKey (KeyEvent keyEvent) { if (base.ProcessKey (keyEvent)) return true; switch (keyEvent.Key) { case Key.ControlQ: // FIXED: stop current execution of this container Application.RequestStop (); break; case Key.ControlZ: Driver.Suspend (); return true; #if false case Key.F5: Application.DebugDrawBounds = !Application.DebugDrawBounds; SetNeedsDisplay (); return true; #endif case Key.Tab: case Key.CursorRight: case Key.CursorDown: case Key.ControlI: // Unix var old = Focused; if (!FocusNext ()) FocusNext (); if (old != Focused) { old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { FocusNearestView (GetToplevelSubviews (true)); } return true; case Key.CursorLeft: case Key.CursorUp: case Key.BackTab: old = Focused; if (!FocusPrev ()) FocusPrev (); if (old != Focused) { old?.SetNeedsDisplay (); Focused?.SetNeedsDisplay (); } else { FocusNearestView (GetToplevelSubviews (false)); } return true; case Key.ControlL: Application.Refresh (); return true; } return false; } IEnumerable GetToplevelSubviews (bool isForward) { if (SuperView == null) { return null; } HashSet views = new HashSet (); foreach (var v in SuperView.Subviews) { views.Add (v); } return isForward ? views : views.Reverse (); } void FocusNearestView (IEnumerable views) { if (views == null) { return; } bool found = false; foreach (var v in views) { if (v == this) { found = true; } if (found && v != this) { v.EnsureFocus (); if (SuperView.Focused != null && SuperView.Focused != this) { return; } } } } /// public override void Add (View view) { if (this == Application.Top) { if (view is MenuBar) MenuBar = view as MenuBar; if (view is StatusBar) StatusBar = view as StatusBar; } base.Add (view); } /// public override void Remove (View view) { if (this is Toplevel && ((Toplevel)this).MenuBar != null) { if (view is MenuBar) { MenuBar?.Dispose (); MenuBar = null; } if (view is StatusBar) { StatusBar?.Dispose (); StatusBar = null; } } base.Remove (view); } /// public override void RemoveAll () { if (this == Application.Top) { MenuBar?.Dispose (); MenuBar = null; StatusBar?.Dispose (); StatusBar = null; } base.RemoveAll (); } internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny) { nx = Math.Max (x, 0); nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx; bool m, s; if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) { m = Application.Top.MenuBar != null; } else { m = ((Toplevel)SuperView).MenuBar != null; } int l; if (SuperView == null || SuperView is Toplevel) { l = m ? 1 : 0; } else { l = 0; } ny = Math.Max (y, l); if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) { s = Application.Top.StatusBar != null; } else { s = ((Toplevel)SuperView).StatusBar != null; } if (SuperView == null || SuperView is Toplevel) { l = s ? Driver.Rows - 1 : Driver.Rows; } else { l = s ? SuperView.Frame.Height - 1 : SuperView.Frame.Height; } ny = Math.Min (ny, l); ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; } internal void PositionToplevels () { PositionToplevel (this); foreach (var top in Subviews) { if (top is Toplevel) { PositionToplevel ((Toplevel)top); } } } private void PositionToplevel (Toplevel top) { EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny); if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { if (top.X is Pos.PosAbsolute && top.Bounds.X != nx) { top.X = nx; } if (top.Y is Pos.PosAbsolute && top.Bounds.Y != ny) { top.Y = ny; } } if (StatusBar != null) { if (ny + top.Frame.Height > top.Frame.Height - 1) { if (top.Height is Dim.DimFill) top.Height = Dim.Fill () - 1; } if (StatusBar.Frame.Y != Frame.Height - 1) { StatusBar.Y = Frame.Height - 1; SetNeedsDisplay (); } } } /// public override void Redraw (Rect bounds) { Application.CurrentView = this; if (IsCurrentTop || this == Application.Top) { if (NeedDisplay != null && !NeedDisplay.IsEmpty) { Driver.SetAttribute (Colors.TopLevel.Normal); // This is the Application.Top. Clear just the region we're being asked to redraw // (the bounds passed to us). Clear (bounds); Driver.SetAttribute (Colors.Base.Normal); PositionToplevels (); } foreach (var view in Subviews) { if (view.Frame.IntersectsWith (bounds)) { view.SetNeedsLayout (); view.SetNeedsDisplay (view.Bounds); } } ClearNeedsDisplay (); } base.Redraw (base.Bounds); } /// /// Invoked by as part of the after /// the views have been laid out, and before the views are drawn for the first time. /// public virtual void WillPresent () { FocusFirst (); } } }