using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; namespace Terminal.Gui { #region API Docs /// /// View is the base class for all views on the screen and represents a visible element that can render itself and /// contains zero or more nested views, called SubViews. /// /// /// /// The View defines the base functionality for user interface elements in Terminal.Gui. Views /// can contain one or more subviews, can respond to user input and render themselves on the screen. /// /// /// SubView - A View that is contained in another view and will be rendered as part of the containing view's ContentArea. /// SubViews are added to another view via the ` method. A View may only be a SubView of a single View. /// /// /// SuperView - The View that is a container for SubViews. /// /// /// Focus is a concept that is used to describe which Responder is currently receiving user input. Only views that are /// , , and will receive focus. /// /// /// Views that are focusable should implement the to make sure that /// the cursor is placed in a location that makes sense. Unix terminals do not have /// a way of hiding the cursor, so it can be distracting to have the cursor left at /// the last focused view. So views should make sure that they place the cursor /// in a visually sensible place. /// /// /// The View defines the base functionality for user interface elements in Terminal.Gui. Views /// can contain one or more subviews, can respond to user input and render themselves on the screen. /// /// /// Views supports two layout styles: or . /// The choice as to which layout style is used by the View /// is determined when the View is initialized. To create a View using Absolute layout, call a constructor that takes a /// Rect parameter to specify the absolute position and size (the View.). To create a View /// using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height /// properties on the view. Both approaches use coordinates that are relative to the container they are being added to. /// /// /// To switch between Absolute and Computed layout, use the property. /// /// /// Computed layout is more flexible and supports dynamic console apps where controls adjust layout /// as the terminal resizes or other Views change size or position. The X, Y, Width and Height /// properties are Dim and Pos objects that dynamically update the position of a view. /// The X and Y properties are of type /// and you can use either absolute positions, percentages or anchor /// points. The Width and Height properties are of type /// and can use absolute position, /// percentages and anchors. These are useful as they will take /// care of repositioning views when view's frames are resized or /// if the terminal size changes. /// /// /// Absolute layout requires specifying coordinates and sizes of Views explicitly, and the /// View will typically stay in a fixed position and size. To change the position and size use the /// property. /// /// /// Subviews (child views) can be added to a View by calling the method. /// The container of a View can be accessed with the property. /// /// /// To flag a region of the View's to be redrawn call . /// To flag the entire view for redraw call . /// /// /// The method is invoked when the size or layout of a view has /// changed. The default processing system will keep the size and dimensions /// for views that use the , and will recompute the /// frames for the vies that use . /// /// /// Views have a property that defines the default colors that subviews /// should use for rendering. This ensures that the views fit in the context where /// they are being used, and allows for themes to be plugged in. For example, the /// default colors for windows and Toplevels uses a blue background, while it uses /// a white background for dialog boxes and a red background for errors. /// /// /// Subclasses should not rely on being /// set at construction time. If a is not set on a view, the view will inherit the /// value from its and the value might only be valid once a view has been /// added to a SuperView. /// /// /// By using applications will work both /// in color as well as black and white displays. /// /// /// Views can also opt-in to more sophisticated initialization /// by implementing overrides to and /// which will be called /// when the view is added to a . /// /// /// If first-run-only initialization is preferred, overrides to /// can be implemented, 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. /// /// #endregion API Docs public partial class View : Responder, ISupportInitializeNotification { #region Constructors and Initialization /// /// Initializes a new instance of a class with the absolute /// dimensions specified in the parameter. /// /// The region covered by this view. /// /// This constructor initialize a View with a of . /// Use to initialize a View with of /// public View (Rect frame) : this (frame, null) { } /// /// Initializes a new instance of using layout. /// /// /// /// Use , , , and properties to dynamically control the size and location of the view. /// The will be created using /// coordinates. The initial size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// /// If is greater than one, word wrapping is provided. /// /// /// This constructor initialize a View with a of . /// Use , , , and properties to dynamically control the size and location of the view. /// /// public View () : this (text: string.Empty, direction: TextDirection.LeftRight_TopBottom) { } /// /// Initializes a new instance of using layout. /// /// /// /// The will be created at the given /// coordinates with the given string. The size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// /// No line wrapping is provided. /// /// /// column to locate the View. /// row to locate the View. /// text to initialize the property with. public View (int x, int y, string text) : this (TextFormatter.CalcRect (x, y, text), text) { } /// /// Initializes a new instance of using layout. /// /// /// /// The will be created at the given /// coordinates with the given string. The initial size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// /// If rect.Height is greater than one, word wrapping is provided. /// /// /// Location. /// text to initialize the property with. public View (Rect rect, string text) { SetInitialProperties (text, rect, LayoutStyle.Absolute, TextDirection.LeftRight_TopBottom); } /// /// Initializes a new instance of using layout. /// /// /// /// The will be created using /// coordinates with the given string. The initial size () will be /// adjusted to fit the contents of , including newlines ('\n') for multiple lines. /// /// /// If is greater than one, word wrapping is provided. /// /// /// text to initialize the property with. /// The text direction. public View (string text, TextDirection direction = TextDirection.LeftRight_TopBottom) { SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction); } // TODO: v2 - Remove constructors with parameters /// /// Private helper to set the initial properties of the View that were provided via constructors. /// /// /// /// /// void SetInitialProperties (string text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed, TextDirection direction = TextDirection.LeftRight_TopBottom) { TextFormatter = new TextFormatter (); TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged; TextDirection = direction; _shortcutHelper = new ShortcutHelper (); CanFocus = false; TabIndex = -1; TabStop = false; LayoutStyle = layoutStyle; Text = text == null ? string.Empty : text; LayoutStyle = layoutStyle; Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect; OnResizeNeeded (); CreateFrames (); LayoutFrames (); } /// /// Get or sets if the has been initialized (via /// and ). /// /// /// If first-run-only initialization is preferred, overrides to /// can be implemented, 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 virtual bool IsInitialized { get; set; } /// /// Signals the View that initialization is starting. See . /// /// /// /// Views can opt-in to more sophisticated initialization /// by implementing overrides to and /// which will be called /// when the view is added to a . /// /// /// If first-run-only initialization is preferred, overrides to /// 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 virtual void BeginInit () { if (!IsInitialized) { _oldCanFocus = CanFocus; _oldTabIndex = _tabIndex; UpdateTextDirection (TextDirection); UpdateTextFormatterText (); SetHotKey (); // TODO: Figure out why ScrollView and other tests fail if this call is put here // instead of the constructor. //InitializeFrames (); } else { //throw new InvalidOperationException ("The view is already initialized."); } if (_subviews?.Count > 0) { foreach (var view in _subviews) { if (!view.IsInitialized) { view.BeginInit (); } } } } /// /// Signals the View that initialization is ending. See . /// public void EndInit () { IsInitialized = true; OnResizeNeeded (); if (_subviews != null) { foreach (var view in _subviews) { if (!view.IsInitialized) { view.EndInit (); } } } Initialized?.Invoke (this, EventArgs.Empty); } #endregion Constructors and Initialization /// /// Points to the current driver in use by the view, it is a convenience property /// for simplifying the development of new views. /// public static ConsoleDriver Driver => Application.Driver; /// /// Gets or sets arbitrary data for the view. /// /// This property is not used internally. public object Data { get; set; } /// /// Gets or sets an identifier for the view; /// /// The identifier. /// The id should be unique across all Views that share a SuperView. public string Id { get; set; } = ""; string _title = string.Empty; /// /// The title to be displayed for this . The title will be displayed if . /// is greater than 0. /// /// The title. public string Title { get => _title; set { if (!OnTitleChanging (_title, value)) { var old = _title; _title = value; SetNeedsDisplay (); #if DEBUG if (_title != null && string.IsNullOrEmpty (Id)) { Id = _title.ToString (); } #endif // DEBUG OnTitleChanged (old, _title); } } } /// /// Called before the changes. Invokes the event, which can be cancelled. /// /// The that is/has been replaced. /// The new to be replaced. /// `true` if an event handler canceled the Title change. public virtual bool OnTitleChanging (string oldTitle, string newTitle) { var args = new TitleEventArgs (oldTitle, newTitle); TitleChanging?.Invoke (this, args); return args.Cancel; } /// /// Event fired when the is changing. Set to /// `true` to cancel the Title change. /// public event EventHandler TitleChanging; /// /// Called when the has been changed. Invokes the event. /// /// The that is/has been replaced. /// The new to be replaced. public virtual void OnTitleChanged (string oldTitle, string newTitle) { var args = new TitleEventArgs (oldTitle, newTitle); TitleChanged?.Invoke (this, args); } /// /// Event fired after the has been changed. /// public event EventHandler TitleChanged; /// /// Event fired when the value is being changed. /// public event EventHandler EnabledChanged; /// public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty); bool _oldEnabled; /// public override bool Enabled { get => base.Enabled; set { if (base.Enabled != value) { if (value) { if (SuperView == null || SuperView?.Enabled == true) { base.Enabled = value; } } else { base.Enabled = value; } if (!value && HasFocus) { SetHasFocus (false, this); } OnEnabledChanged (); SetNeedsDisplay (); if (_subviews != null) { foreach (var view in _subviews) { if (!value) { view._oldEnabled = view.Enabled; view.Enabled = false; } else { view.Enabled = view._oldEnabled; view._addingView = false; } } } } } } /// /// Event fired when the value is being changed. /// public event EventHandler VisibleChanged; /// public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty); /// /// Gets or sets whether a view is cleared if the property is . /// public bool ClearOnVisibleFalse { get; set; } = true; /// > public override bool Visible { get => base.Visible; set { if (base.Visible != value) { base.Visible = value; if (!value) { if (HasFocus) { SetHasFocus (false, this); } if (ClearOnVisibleFalse) { Clear (); } } OnVisibleChanged (); SetNeedsDisplay (); } } } bool CanBeVisible (View view) { if (!view.Visible) { return false; } for (var c = view.SuperView; c != null; c = c.SuperView) { if (!c.Visible) { return false; } } return true; } /// /// Pretty prints the View /// /// public override string ToString () { return $"{GetType ().Name}({Id})({Frame})"; } /// protected override void Dispose (bool disposing) { Margin?.Dispose (); Margin = null; Border?.Dispose (); Border = null; Padding?.Dispose (); Padding = null; for (var i = InternalSubviews.Count - 1; i >= 0; i--) { var subview = InternalSubviews [i]; Remove (subview); subview.Dispose (); } base.Dispose (disposing); } } }