using System.Diagnostics; namespace Terminal.Gui; public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.) { private static readonly IList _empty = new List (0).AsReadOnly (); private List _subviews; // This is null, and allocated on demand. private View _superView; /// Indicates whether the view was added to . public bool IsAdded { get; private set; } /// This returns a list of the subviews contained by this view. /// The subviews. public IList Subviews => _subviews?.AsReadOnly () ?? _empty; /// Returns the container for this view, or null if this view has not been added to a container. /// The super view. public virtual View SuperView { get => _superView; set => throw new NotImplementedException (); } // Internally, we use InternalSubviews rather than subviews, as we do not expect us // to make the same mistakes our users make when they poke at the Subviews. internal IList InternalSubviews => _subviews ?? _empty; /// Adds a subview (child) to this view. /// /// /// The Views that have been added to this view can be retrieved via the property. See also /// /// /// /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes /// the lifecycle of the subviews to be transferred to this View. /// /// /// The view to add. /// The view that was added. public virtual View Add (View view) { if (view is null) { return view; } if (_subviews is null) { _subviews = new (); } if (_tabIndexes is null) { _tabIndexes = new (); } Debug.WriteLineIf (_subviews.Contains (view), $"BUGBUG: {view} has already been added to {this}."); _subviews.Add (view); _tabIndexes.Add (view); view._superView = this; if (view.CanFocus) { view._tabIndex = _tabIndexes.IndexOf (view); } if (view.Enabled && view.Visible && view.CanFocus) { if (HasFocus) { view.SetFocus (); } #if AUTO_CANFOCUS // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want. _addingViewSoCanFocusAlsoUpdatesSuperView = true; if (SuperView?.CanFocus == false) { SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = true; SuperView.CanFocus = true; SuperView._addingViewSoCanFocusAlsoUpdatesSuperView = false; } // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying. CanFocus = true; _addingViewSoCanFocusAlsoUpdatesSuperView = false; #endif } if (view.Enabled && !Enabled) { view._oldEnabled = true; view.Enabled = false; } OnAdded (new (this, view)); if (IsInitialized && !view.IsInitialized) { view.BeginInit (); view.EndInit (); } CheckDimAuto (); SetNeedsLayout (); SetNeedsDisplay (); return view; } /// Adds the specified views (children) to the view. /// Array of one or more views (can be optional parameter). /// /// /// The Views that have been added to this view can be retrieved via the property. See also /// and . /// /// /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes /// the lifecycle of the subviews to be transferred to this View. /// /// public void Add (params View [] views) { if (views is null) { return; } foreach (View view in views) { Add (view); } } /// Event fired when this view is added to another. public event EventHandler Added; /// Get the top superview of a given . /// The superview view. public View GetTopSuperView (View view = null, View superview = null) { View top = superview ?? Application.Top; for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) { top = v; if (top == superview) { break; } } return top; } /// Method invoked when a subview is being added to this view. /// Event where is the subview being added. public virtual void OnAdded (SuperViewChangedEventArgs e) { View view = e.Child; view.IsAdded = true; view.OnResizeNeeded (); view.Added?.Invoke (this, e); } /// Method invoked when a subview is being removed from this view. /// Event args describing the subview being removed. public virtual void OnRemoved (SuperViewChangedEventArgs e) { View view = e.Child; view.IsAdded = false; view.Removed?.Invoke (this, e); } /// Removes a subview added via or from this View. /// /// /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the /// Subview's /// lifecycle to be transferred to the caller; the caller muse call . /// /// public virtual View Remove (View view) { if (view is null || _subviews is null) { return view; } Rectangle touched = view.Frame; _subviews.Remove (view); _tabIndexes!.Remove (view); view._superView = null; //view._tabIndex = -1; // If a view being removed is focused, it should lose focus. if (view.HasFocus) { view.HasFocus = false; } SetNeedsLayout (); SetNeedsDisplay (); foreach (View v in _subviews) { if (v.Frame.IntersectsWith (touched)) { view.SetNeedsDisplay (); } } if (HasFocus) { FocusDeepest (NavigationDirection.Forward, TabStop); } OnRemoved (new (this, view)); return view; } /// /// Removes all subviews (children) added via or from this View. /// /// /// /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the /// Subview's /// lifecycle to be transferred to the caller; the caller must call on any Views that were /// added. /// /// public virtual void RemoveAll () { if (_subviews is null) { return; } while (_subviews.Count > 0) { Remove (_subviews [0]); } } /// Event fired when this view is removed from another. public event EventHandler Removed; /// Moves one position towards the start of the list /// The subview to move forward. public void BringSubviewForward (View subview) { PerformActionForSubview ( subview, x => { int idx = _subviews.IndexOf (x); if (idx + 1 < _subviews.Count) { _subviews.Remove (x); _subviews.Insert (idx + 1, x); } } ); } /// Moves to the start of the list. /// The subview to send to the start. public void BringSubviewToFront (View subview) { PerformActionForSubview ( subview, x => { _subviews.Remove (x); _subviews.Add (x); } ); } /// Moves one position towards the end of the list /// The subview to move backwards. public void SendSubviewBackwards (View subview) { PerformActionForSubview ( subview, x => { int idx = _subviews.IndexOf (x); if (idx > 0) { _subviews.Remove (x); _subviews.Insert (idx - 1, x); } } ); } /// Moves to the end of the list. /// The subview to send to the end. public void SendSubviewToBack (View subview) { PerformActionForSubview ( subview, x => { _subviews.Remove (x); _subviews.Insert (0, subview); } ); } /// /// Internal API that runs on a subview if it is part of the list. /// /// /// private void PerformActionForSubview (View subview, Action action) { if (_subviews.Contains (subview)) { action (subview); } // BUGBUG: this is odd. Why is this needed? SetNeedsDisplay (); subview.SetNeedsDisplay (); } }