// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Lists; using System.Collections.Generic; using System; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics.Colour; using osuTK; using System.Collections; using System.Diagnostics; using osu.Framework.Graphics.Effects; namespace osu.Framework.Graphics.Containers { /// /// A drawable which can have children added to it. Transformations applied to /// a container are also applied to its children. /// Additionally, containers support various effects, such as masking, edge effect, /// padding, and automatic sizing depending on their children. /// If all children are of a specific non- type, use the /// generic version . /// public class Container : Container { } /// /// A drawable which can have children added to it. Transformations applied to /// a container are also applied to its children. /// Additionally, containers support various effects, such as masking, edge effect, /// padding, and automatic sizing depending on their children. /// public class Container : CompositeDrawable, IContainerEnumerable, IContainerCollection, ICollection, IReadOnlyList where T : Drawable { /// /// Constructs a that stores children. /// public Container() { if (typeof(T) == typeof(Drawable)) internalChildrenAsT = (IReadOnlyList)InternalChildren; else internalChildrenAsT = new LazyList(InternalChildren, c => (T)c); if (typeof(T) == typeof(Drawable)) aliveInternalChildrenAsT = (IReadOnlyList)AliveInternalChildren; else aliveInternalChildrenAsT = new LazyList(AliveInternalChildren, c => (T)c); } /// /// The content of this container. and all methods that mutate /// (e.g. and ) are /// forwarded to the content. By default a container's content is itself, in which case /// refers to . /// This property is useful for containers that require internal children that should /// not be exposed to the outside world, e.g. . /// protected virtual Container Content => this; /// /// The publicly accessible list of children. Forwards to the children of . /// If is this container, then returns . /// Assigning to this property will dispose all existing children of this Container. /// /// If a foreach loop is used, iterate over the directly rather than its . /// /// public IReadOnlyList Children { get { if (Content != this) return Content.Children; return internalChildrenAsT; } set => ChildrenEnumerable = value; } /// /// The publicly accessible list of alive children. Forwards to the alive children of . /// If is this container, then returns . /// public IReadOnlyList AliveChildren { get { if (Content != this) return Content.AliveChildren; return aliveInternalChildrenAsT; } } /// /// Accesses the -th child. /// /// The index of the child to access. /// The -th child. public T this[int index] => Children[index]; /// /// The amount of elements in . /// public int Count => Children.Count; /// /// Whether this can have elements added and removed. Always false. /// public bool IsReadOnly => false; /// /// Copies the elements of the to an Array, starting at a particular Array index. /// /// The Array into which all children should be copied. /// The starting index in the Array. public void CopyTo(T[] array, int arrayIndex) { foreach (var c in Children) array[arrayIndex++] = c; } public Enumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Sets all children of this container to the elements contained in the enumerable. /// public IEnumerable ChildrenEnumerable { set { Clear(); AddRange(value); } } /// /// Gets or sets the only child of this container. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] public T Child { get { if (Children.Count != 1) throw new InvalidOperationException($"Cannot call {nameof(InternalChild)} unless there's exactly one {nameof(Drawable)} in {nameof(Children)} (currently {Children.Count})!"); return Children[0]; } set { Clear(); Add(value); } } private readonly IReadOnlyList internalChildrenAsT; private readonly IReadOnlyList aliveInternalChildrenAsT; /// /// The index of a given child within . /// /// /// If the child is found, its index. Otherwise, the negated index it would obtain /// if it were added to . /// public int IndexOf(T drawable) { if (Content != this) return Content.IndexOf(drawable); return IndexOfInternal(drawable); } /// /// Checks whether a given child is contained within . /// public bool Contains(T drawable) { int index = IndexOf(drawable); return index >= 0 && this[index] == drawable; } /// /// Adds a child to this container. This amounts to adding a child to 's /// , recursing until == this. /// public virtual void Add(T drawable) { if (drawable == Content) throw new InvalidOperationException("Content may not be added to itself."); if (Content == this) AddInternal(drawable); else Content.Add(drawable); } /// /// Adds a range of children. This is equivalent to calling on /// each element of the range in order. /// public void AddRange(IEnumerable range) { if (range is IContainerEnumerable) { throw new InvalidOperationException($"Attempting to add a {nameof(IContainer)} as a range of children to {this}." + $"If intentional, consider using the {nameof(IContainerEnumerable.Children)} property instead."); } foreach (T d in range) Add(d); } protected internal override void AddInternal(Drawable drawable) { if (Content == this && drawable != null && !(drawable is T)) throw new InvalidOperationException($"Only {typeof(T).ReadableName()} type drawables may be added to a container of type {GetType().ReadableName()} which does not redirect {nameof(Content)}."); base.AddInternal(drawable); } /// /// Removes a given child from this container. /// public virtual bool Remove(T drawable) => Content != this ? Content.Remove(drawable) : RemoveInternal(drawable); /// /// Removes all children which match the given predicate. /// This is equivalent to calling for each child that /// matches the given predicate. /// /// The amount of removed children. public int RemoveAll(Predicate pred) { if (Content != this) return Content.RemoveAll(pred); int removedCount = 0; for (int i = 0; i < InternalChildren.Count; i++) { var tChild = (T)InternalChildren[i]; if (pred.Invoke(tChild)) { RemoveInternal(tChild); removedCount++; i--; } } return removedCount; } /// /// Removes a range of children. This is equivalent to calling on /// each element of the range in order. /// public void RemoveRange(IEnumerable range) { if (range == null) return; foreach (T p in range) Remove(p); } /// /// Removes all children. /// public void Clear() => Clear(true); /// /// Removes all children. /// /// /// Whether removed children should also get disposed. /// Disposal will be recursive. /// public virtual void Clear(bool disposeChildren) { if (Content != null && Content != this) Content.Clear(disposeChildren); else ClearInternal(disposeChildren); } /// /// Changes the depth of a child. This affects ordering of children within this container. /// /// The child whose depth is to be changed. /// The new depth value to be set. public void ChangeChildDepth(T child, float newDepth) { if (Content != this) Content.ChangeChildDepth(child, newDepth); else ChangeInternalChildDepth(child, newDepth); } /// /// If enabled, only the portion of children that falls within this 's /// shape is drawn to the screen. /// public new bool Masking { get => base.Masking; set => base.Masking = value; } /// /// Determines over how many pixels the alpha component smoothly fades out when an inner or is present. /// Only has an effect when is true. /// public new float MaskingSmoothness { get => base.MaskingSmoothness; set => base.MaskingSmoothness = value; } /// /// Determines how large of a radius is masked away around the corners. /// Only has an effect when is true. /// public new float CornerRadius { get => base.CornerRadius; set => base.CornerRadius = value; } /// /// Determines how gentle the curve of the corner straightens. A value of 2 results in /// circular arcs, a value of 2.5 (default) results in something closer to apple's "continuous corner". /// Values between 2 and 10 result in varying degrees of "continuousness", where larger values are smoother. /// Values between 1 and 2 result in a "flatter" appearance than round corners. /// Values between 0 and 1 result in a concave, round corner as opposed to a convex round corner, /// where a value of 0.5 is a circular concave arc. /// Only has an effect when is true and is non-zero. /// public new float CornerExponent { get => base.CornerExponent; set => base.CornerExponent = value; } /// /// Determines how thick of a border to draw around the inside of the masked region. /// Only has an effect when is true. /// The border only is drawn on top of children using a sprite shader. /// /// /// Drawing borders is optimized heavily into our sprite shaders. As a consequence /// borders are only drawn correctly on top of quad-shaped children using our sprite /// shaders. /// public new float BorderThickness { get => base.BorderThickness; set => base.BorderThickness = value; } /// /// Determines the color of the border controlled by . /// Only has an effect when is true. /// public new SRGBColour BorderColour { get => base.BorderColour; set => base.BorderColour = value; } /// /// Determines an edge effect of this . /// Edge effects are e.g. glow or a shadow. /// Only has an effect when is true. /// public new EdgeEffectParameters EdgeEffect { get => base.EdgeEffect; set => base.EdgeEffect = value; } /// /// Shrinks the space children may occupy within this /// by the specified amount on each side. /// public new MarginPadding Padding { get => base.Padding; set => base.Padding = value; } /// /// Whether to use a local vertex batch for rendering. If false, a parenting vertex batch will be used. /// public new bool ForceLocalVertexBatch { get => base.ForceLocalVertexBatch; set => base.ForceLocalVertexBatch = value; } /// /// The size of the relative position/size coordinate space of children of this . /// Children positioned at this size will appear as if they were positioned at = in this . /// public new Vector2 RelativeChildSize { get => base.RelativeChildSize; set => base.RelativeChildSize = value; } /// /// The offset of the relative position/size coordinate space of children of this . /// Children positioned at this offset will appear as if they were positioned at = in this . /// public new Vector2 RelativeChildOffset { get => base.RelativeChildOffset; set => base.RelativeChildOffset = value; } /// /// Controls which are automatically sized w.r.t. . /// Children's are ignored for automatic sizing. /// Most notably, and of children /// do not affect automatic sizing to avoid circular size dependencies. /// It is not allowed to manually set (or / ) /// on any which are automatically sized. /// public new Axes AutoSizeAxes { get => base.AutoSizeAxes; set => base.AutoSizeAxes = value; } /// /// The duration which automatic sizing should take. If zero, then it is instantaneous. /// Otherwise, this is equivalent to applying an automatic size via a resize transform. /// public new float AutoSizeDuration { get => base.AutoSizeDuration; set => base.AutoSizeDuration = value; } /// /// The type of easing which should be used for smooth automatic sizing when /// is non-zero. /// public new Easing AutoSizeEasing { get => base.AutoSizeEasing; set => base.AutoSizeEasing = value; } public struct Enumerator : IEnumerator { private Container container; private int currentIndex; internal Enumerator(Container container) { this.container = container; currentIndex = -1; // The first MoveNext() should bring the iterator to 0 } public bool MoveNext() => ++currentIndex < container.Count; public void Reset() => currentIndex = -1; public readonly T Current => container[currentIndex]; readonly object IEnumerator.Current => Current; public void Dispose() { container = null; } } } }