using System.ComponentModel; using System.Diagnostics; 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. View provides basic functionality for layout, positioning, and /// drawing. In addition, View provides keyboard and mouse event handling. /// /// /// /// /// TermDefinition /// /// /// 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. /// /// /// /// SuperViewThe View that is a container for SubViews. /// /// /// /// Focus is a concept that is used to describe which View 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. /// /// /// View supports two layout styles: or . /// The style is determined by the values of , , , and /// . If any of these is set to non-absolute or object, /// then the layout style is . Otherwise it is /// . /// /// /// To create a View using Absolute layout, call a constructor that takes a Rect parameter to specify the /// absolute position and size or simply set ). 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 to /// non-absolute values. Both approaches use coordinates that are relative to the of the /// the View is added to. /// /// /// 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 , , /// , and properties are and 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 adornments 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 Adornments for the views 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. /// /// See for an overview of View keyboard handling. /// /// /// #endregion API Docs public partial class View : Responder, ISupportInitializeNotification { #region 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; /// Initializes a new instance of . /// /// /// 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, changing it to . /// /// public View () { CreateAdornments (); HotKeySpecifier = (Rune)'_'; TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged; TextDirection = TextDirection.LeftRight_TopBottom; Text = string.Empty; CanFocus = false; TabIndex = -1; TabStop = false; AddCommands (); } /// /// Event called only once when the is being initialized for the first time. Allows /// configurations and assignments to be performed before the being shown. This derived from /// to allow notify all the views that are being initialized. /// public event EventHandler Initialized; /// /// 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 is initialized. /// /// /// 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) { throw new InvalidOperationException ("The view is already initialized."); } _oldCanFocus = CanFocus; _oldTabIndex = _tabIndex; BeginInitAdornments (); if (_subviews?.Count > 0) { foreach (View view in _subviews) { if (!view.IsInitialized) { view.BeginInit (); } } } } // TODO: Implement logic that allows EndInit to throw if BeginInit has not been called // TODO: See EndInit_Called_Without_BeginInit_Throws test. /// Signals the View that initialization is ending. See . /// /// Initializes all Subviews and Invokes the event. /// public virtual void EndInit () { if (IsInitialized) { throw new InvalidOperationException ("The view is already initialized."); } IsInitialized = true; EndInitAdornments (); // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate. // TODO: Verify UpdateTextDirection really needs to be called here. // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called. UpdateTextDirection (TextDirection); UpdateTextFormatterText (); OnResizeNeeded (); if (_subviews is { }) { foreach (View view in _subviews) { if (!view.IsInitialized) { view.EndInit (); } } } Initialized?.Invoke (this, EventArgs.Empty); } #endregion Constructors and Initialization /// 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; } = ""; /// Gets or sets arbitrary data for the view. /// This property is not used internally. public object Data { get; set; } /// /// Cancelable event fired when the command is invoked. Set /// /// to cancel the event. /// public event EventHandler Accept; /// /// Called when the command is invoked. Fires the /// event. /// /// If the event was canceled. protected bool? OnAccept () { var args = new CancelEventArgs (); Accept?.Invoke (this, args); return args.Cancel; } #region Visibility private bool _enabled = true; private bool _oldEnabled; /// Gets or sets a value indicating whether this can respond to user interaction. public virtual bool Enabled { get => _enabled; set { if (_enabled == value) { return; } _enabled = value; if (!_enabled && HasFocus) { SetHasFocus (false, this); } OnEnabledChanged (); SetNeedsDisplay (); if (_subviews is null) { return; } foreach (View view in _subviews) { if (!_enabled) { view._oldEnabled = view.Enabled; view.Enabled = _enabled; } else { view.Enabled = view._oldEnabled; view._addingView = _enabled; } } } } /// Event fired when the value is being changed. public event EventHandler EnabledChanged; /// Method invoked when the property from a view is changed. public virtual void OnEnabledChanged () { EnabledChanged?.Invoke (this, EventArgs.Empty); } private bool _visible = true; /// Gets or sets a value indicating whether this and all its child controls are displayed. public virtual bool Visible { get => _visible; set { if (_visible == value) { return; } _visible = value; if (!_visible) { if (HasFocus) { SetHasFocus (false, this); } if (IsInitialized && ClearOnVisibleFalse) { Clear (); } } OnVisibleChanged (); SetNeedsDisplay (); } } /// Method invoked when the property from a view is changed. public virtual 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; /// Event fired when the value is being changed. public event EventHandler VisibleChanged; private static bool CanBeVisible (View view) { if (!view.Visible) { return false; } for (View c = view.SuperView; c != null; c = c.SuperView) { if (!c.Visible) { return false; } } return true; } #endregion Visibility #region Title private string _title = string.Empty; /// Gets the used to format . internal TextFormatter TitleTextFormatter { get; init; } = new (); /// /// The title to be displayed for this . The title will be displayed if . /// is greater than 0. The title can be used to set the /// for the view by prefixing character with (e.g. "T_itle"). /// /// /// /// Set the to enable hotkey support. To disable Title-based hotkey support set /// to (Rune)0xffff. /// /// /// Only the first HotKey specifier found in is supported. /// /// /// To cause the hotkey to be rendered with , /// set View. to the desired character. /// /// /// The title. public string Title { get { #if DEBUG_IDISPOSABLE if (WasDisposed) { throw new ObjectDisposedException (GetType ().FullName); } #endif return _title; } set { #if DEBUG_IDISPOSABLE if (WasDisposed) { throw new ObjectDisposedException (GetType ().FullName); } #endif if (value == _title) { return; } if (!OnTitleChanging (_title, value)) { string old = _title; _title = value; TitleTextFormatter.Text = _title; TitleTextFormatter.Size = new ( TextFormatter.GetWidestLineLength (TitleTextFormatter.Text) - (TitleTextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0), 1); SetHotKeyFromTitle (); SetNeedsDisplay (); #if DEBUG if (_title is { } && string.IsNullOrEmpty (Id)) { Id = _title; } #endif // DEBUG OnTitleChanged (old, _title); } } } /// 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) { StateEventArgs args = new (oldTitle, newTitle); TitleChanged?.Invoke (this, args); } /// /// 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) { StateEventArgs args = new (oldTitle, newTitle); TitleChanging?.Invoke (this, args); return args.Cancel; } /// Event fired after the has been changed. public event EventHandler> TitleChanged; /// /// Event fired when the is changing. Set to `true` /// to cancel the Title change. /// public event EventHandler> TitleChanging; #endregion /// Pretty prints the View /// public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; } /// protected override void Dispose (bool disposing) { LineCanvas.Dispose (); DisposeAdornments (); for (int i = InternalSubviews.Count - 1; i >= 0; i--) { View subview = InternalSubviews [i]; Remove (subview); subview.Dispose (); } base.Dispose (disposing); Debug.Assert (InternalSubviews.Count == 0); } }