using Sandbox.UI.Construct; namespace Sandbox.UI; public partial class Panel { /// /// Quickly add common panels with certain values as children. /// [Hide] public PanelCreator Add => new( this ); /// internal List _children; internal bool _renderChildrenDirty; internal List _renderChildren; internal HashSet _childrenHash; /// internal Panel _parent; /// /// List of panels that are attached/parented directly to this one. /// [Hide] public IEnumerable Children => _children == null ? Enumerable.Empty() : _children.Where( x => x is not null ); /// /// Whether this panel has any child panels at all. /// [Hide] public bool HasChildren => _children is not null && _children.Count > 0; /// /// The panel we are directly attached to. This panel will be positioned relative to the given parent, and therefore move with it, typically also be hidden by the parents bounds. /// [Hide] public Panel Parent { get => _parent; set { if ( this is RootPanel && value != null ) throw new Exception( "Can't parent a RootPanel" ); if ( value == this ) throw new Exception( "Can't parent a panel to itself" ); if ( _parent == value ) return; // // These types can't have children, so set them as // siblings of us. // if ( value is Label || value is Image ) { Parent = value.Parent; Parent.SetChildIndex( this, Parent.GetChildIndex( value ) + 1 ); return; } var oldParent = _parent; _parent = null; if ( oldParent != null ) { oldParent.RemoveChild( this ); } _parent = value; if ( _parent != null ) { _parent.InternalAddChild( this ); if ( oldParent == null ) { AddToLists(); } // // We can set some inherited stuff here to get us started // ScaleToScreen = _parent.ScaleToScreen; } ParentHasChanged = true; Scene = FindRootPanel()?.Scene; // Dirty } } bool ParentHasChanged; bool IndexesDirty; /// /// Called internally when a child is removed, to remove from our Children list /// private void RemoveChild( Panel p ) { if ( IsDeleted ) return; if ( _children == null ) throw new System.Exception( "RemoveChild but no children!" ); if ( _childrenHash.Remove( p ) ) { _children.Remove( p ); _renderChildren.Remove( p ); _renderChildrenDirty = true; if ( p.YogaNode is not null ) { YogaNode?.RemoveChild( p.YogaNode ); } OnChildRemoved( p ); IndexesDirty = true; SetNeedsPreLayout(); } else { throw new Exception( "Removed Child but didn't have child!" ); } } /// /// A child panel has been removed from this panel. /// protected virtual void OnChildRemoved( Panel child ) { } /// /// Deletes all child panels via . /// /// public void DeleteChildren( bool immediate = false ) { foreach ( var child in Children.ToArray() ) { child.Delete( immediate ); } } /// /// Add given panel as a child to this panel. /// public T AddChild( T p ) where T : Panel { Assert.False( IsDeleted ); p.Parent = this; return p; } /// /// Called internally when a child is added, to add to our children list. /// private void InternalAddChild( Panel child ) { if ( YogaNode?.IsMeasureDefined == true ) throw new Exception( $"{this} can not have children." ); _children ??= new( 4 ); _renderChildren ??= new( 4 ); _childrenHash ??= new( 4 ); if ( _childrenHash.Contains( child ) ) throw new Exception( "AddChild but already have child!" ); YogaNode?.AddChild( child.YogaNode ); _childrenHash.Add( child ); _children.Add( child ); _renderChildren.Add( child ); _renderChildrenDirty = true; child.UpdateSiblingIndex( _children.Count - 1, _children.Count ); OnChildAdded( child ); SetNeedsPreLayout(); IndexesDirty = true; } /// /// A child panel has been added to this panel. /// protected virtual void OnChildAdded( Panel child ) { } /// /// Sort the children using given comparison function. /// public void SortChildren( Comparison sorter ) { if ( _children == null || _children.Count <= 0 ) return; _children.RemoveAll( x => x is null ); _children.Sort( sorter ); int i = 0; foreach ( var child in _children ) { child.UpdateSiblingIndex( i++, _children.Count ); YogaNode.RemoveChild( child.YogaNode ); YogaNode.AddChild( child.YogaNode ); } IndexesDirty = true; } /// /// Sort the children using given comparison function. /// public void SortChildren( Func sorter ) { if ( _children == null || _children.Count <= 0 ) return; _children.RemoveAll( x => x is null ); var sorted = _children.OrderBy( x => { if ( x is TargetType tt ) { return sorter( tt ); } return 0; } ).ToArray(); _children.Clear(); _children.AddRange( sorted ); foreach ( var child in _children ) { YogaNode.RemoveChild( child.YogaNode ); YogaNode.AddChild( child.YogaNode ); } IndexesDirty = true; } /// /// Sort the children using given comparison function. /// public void SortChildren( Func sorter ) => SortChildren( sorter ); /// /// Can be overridden by children to determine whether the panel is empty, and the :empty pseudo-class should be applied. /// protected virtual bool IsPanelEmpty() { return ChildrenCount == 0; } /// /// Should be called if overriding IsEmpty to notify the panel that its empty state has changed. /// protected void EmptyStateChanged() { UpdateChildrenIndexes(); } void UpdateChildrenIndexes() { IndexesDirty = false; Switch( PseudoClass.Empty, IsPanelEmpty() ); var count = ChildrenCount; if ( count == 0 ) return; for ( int i = 0; i < count; i++ ) { _children[i].UpdateSiblingIndex( i, count ); } } internal void UpdateSiblingIndex( int index, int siblings ) { Switch( PseudoClass.FirstChild, index == 0 ); Switch( PseudoClass.LastChild, index == siblings - 1 ); Switch( PseudoClass.OnlyChild, index == 0 && siblings == 1 ); SiblingIndex = index; } /// /// The index of this panel in its parent's child list. /// [Hide] public int SiblingIndex { get; internal set; } = -1; /// /// Creates a panel of given type and makes it our child. /// /// The panel to create. /// Optional CSS class names to apply to the newly created panel. /// The created panel. public T AddChild( string classnames = null ) where T : Panel, new() { var t = new T(); t.Parent = this; if ( classnames != null ) t.AddClass( classnames ); return t; } /// /// Creates a panel of given type and makes it our child, returning it as an out argument. /// /// The panel to create. /// The created panel. /// Optional CSS class names to apply to the newly created panel. /// Always returns . public bool AddChild( out T outPanel, string classnames = null ) where T : Panel, new() { var t = new T(); t.Parent = this; if ( classnames != null ) t.AddClass( classnames ); outPanel = t; return true; } /// /// Returns this panel and all its ancestors, i.e. the Parent, parent of its parent, etc. /// [Hide] public IEnumerable AncestorsAndSelf { get { var p = this; while ( p != null ) { yield return p; p = p.Parent; } } } /// /// Returns all ancestors, i.e. the parent, parent of our parent, etc. /// [Hide] public IEnumerable Ancestors { get { var p = this.Parent; while ( p != null ) { yield return p; p = p.Parent; } } } /// /// List of all panels that are attached to this panel, recursively, i.e. all children of this panel, children of those children, etc. /// [Hide] public IEnumerable Descendants { get { foreach ( var child in Children ) { yield return child; foreach ( var descendant in child.Descendants ) { yield return descendant; } } } } /// /// Is the given panel a parent, grandparent, etc. /// public bool IsAncestor( Panel panel ) { if ( panel == this ) return true; if ( Parent != null ) return Parent.IsAncestor( panel ); return false; } /// /// Returns the we are ultimately attached to, if any. /// public RootPanel FindRootPanel() { if ( this is RootPanel rp ) return rp; return Parent?.FindRootPanel(); } /// /// Returns the first ancestor panel that has no parent. /// public virtual Panel FindPopupPanel() { if ( Parent == null ) return this; return Parent?.FindPopupPanel(); } /// /// Returns the scene that this panel belongs to /// public Scene Scene { get; set; } /// /// Returns the index at which the given panel is parented to this panel, or -1 if it is not. /// public int GetChildIndex( Panel panel ) { if ( panel == null || panel.Parent != this ) return -1; if ( _children == null || _children.Count == 0 ) return -1; return _children.IndexOf( panel ); } /// /// Return a child at given index. /// /// Index at which to look. /// Whether to loop indices when out of bounds, i.e. -1 becomes last child, 11 becomes second child in a list of 10, etc. /// Returns the requested child, or if it was not found. public Panel GetChild( int index, bool loop = false ) { if ( _children == null || _children.Count == 0 ) return null; if ( loop ) { index = index.UnsignedMod( Children.Count() ); } else { if ( index < 0 ) return null; if ( index >= _children.Count ) return null; } return _children[index]; } /// /// Amount of panels directly parented to this panel. /// [Hide] public int ChildrenCount => _children?.Count ?? 0; /// /// Returns a list of child panels of given type. /// /// The type of panels to retrieve. public IEnumerable ChildrenOfType() where T : Panel { if ( _children == null || _children.Count == 0 ) yield break; var c = _children.Count; for ( int i = c - 1; i >= 0; i-- ) { var child = _children[i]; if ( child is T t ) { yield return t; } } } }