A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using osu.Framework.Lists;
5using System.Collections.Generic;
6using System;
7using osu.Framework.Extensions.TypeExtensions;
8using osu.Framework.Graphics.Colour;
9using osuTK;
10using System.Collections;
11using System.Diagnostics;
12using osu.Framework.Graphics.Effects;
13
14namespace osu.Framework.Graphics.Containers
15{
16 /// <summary>
17 /// A drawable which can have children added to it. Transformations applied to
18 /// a container are also applied to its children.
19 /// Additionally, containers support various effects, such as masking, edge effect,
20 /// padding, and automatic sizing depending on their children.
21 /// If all children are of a specific non-<see cref="Drawable"/> type, use the
22 /// generic version <see cref="Container{T}"/>.
23 /// </summary>
24 public class Container : Container<Drawable>
25 {
26 }
27
28 /// <summary>
29 /// A drawable which can have children added to it. Transformations applied to
30 /// a container are also applied to its children.
31 /// Additionally, containers support various effects, such as masking, edge effect,
32 /// padding, and automatic sizing depending on their children.
33 /// </summary>
34 public class Container<T> : CompositeDrawable, IContainerEnumerable<T>, IContainerCollection<T>, ICollection<T>, IReadOnlyList<T>
35 where T : Drawable
36 {
37 /// <summary>
38 /// Constructs a <see cref="Container"/> that stores children.
39 /// </summary>
40 public Container()
41 {
42 if (typeof(T) == typeof(Drawable))
43 internalChildrenAsT = (IReadOnlyList<T>)InternalChildren;
44 else
45 internalChildrenAsT = new LazyList<Drawable, T>(InternalChildren, c => (T)c);
46
47 if (typeof(T) == typeof(Drawable))
48 aliveInternalChildrenAsT = (IReadOnlyList<T>)AliveInternalChildren;
49 else
50 aliveInternalChildrenAsT = new LazyList<Drawable, T>(AliveInternalChildren, c => (T)c);
51 }
52
53 /// <summary>
54 /// The content of this container. <see cref="Children"/> and all methods that mutate
55 /// <see cref="Children"/> (e.g. <see cref="Add(T)"/> and <see cref="Remove(T)"/>) are
56 /// forwarded to the content. By default a container's content is itself, in which case
57 /// <see cref="Children"/> refers to <see cref="CompositeDrawable.InternalChildren"/>.
58 /// This property is useful for containers that require internal children that should
59 /// not be exposed to the outside world, e.g. <see cref="ScrollContainer{T}"/>.
60 /// </summary>
61 protected virtual Container<T> Content => this;
62
63 /// <summary>
64 /// The publicly accessible list of children. Forwards to the children of <see cref="Content"/>.
65 /// If <see cref="Content"/> is this container, then returns <see cref="CompositeDrawable.InternalChildren"/>.
66 /// Assigning to this property will dispose all existing children of this Container.
67 /// <remarks>
68 /// If a foreach loop is used, iterate over the <see cref="Container"/> directly rather than its <see cref="Children"/>.
69 /// </remarks>
70 /// </summary>
71 public IReadOnlyList<T> Children
72 {
73 get
74 {
75 if (Content != this)
76 return Content.Children;
77
78 return internalChildrenAsT;
79 }
80 set => ChildrenEnumerable = value;
81 }
82
83 /// <summary>
84 /// The publicly accessible list of alive children. Forwards to the alive children of <see cref="Content"/>.
85 /// If <see cref="Content"/> is this container, then returns <see cref="CompositeDrawable.AliveInternalChildren"/>.
86 /// </summary>
87 public IReadOnlyList<T> AliveChildren
88 {
89 get
90 {
91 if (Content != this)
92 return Content.AliveChildren;
93
94 return aliveInternalChildrenAsT;
95 }
96 }
97
98 /// <summary>
99 /// Accesses the <paramref name="index"/>-th child.
100 /// </summary>
101 /// <param name="index">The index of the child to access.</param>
102 /// <returns>The <paramref name="index"/>-th child.</returns>
103 public T this[int index] => Children[index];
104
105 /// <summary>
106 /// The amount of elements in <see cref="Children"/>.
107 /// </summary>
108 public int Count => Children.Count;
109
110 /// <summary>
111 /// Whether this <see cref="Container{T}"/> can have elements added and removed. Always false.
112 /// </summary>
113 public bool IsReadOnly => false;
114
115 /// <summary>
116 /// Copies the elements of the <see cref="Container{T}"/> to an Array, starting at a particular Array index.
117 /// </summary>
118 /// <param name="array">The Array into which all children should be copied.</param>
119 /// <param name="arrayIndex">The starting index in the Array.</param>
120 public void CopyTo(T[] array, int arrayIndex)
121 {
122 foreach (var c in Children)
123 array[arrayIndex++] = c;
124 }
125
126 public Enumerator GetEnumerator() => new Enumerator(this);
127
128 IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
129
130 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
131
132 /// <summary>
133 /// Sets all children of this container to the elements contained in the enumerable.
134 /// </summary>
135 public IEnumerable<T> ChildrenEnumerable
136 {
137 set
138 {
139 Clear();
140 AddRange(value);
141 }
142 }
143
144 /// <summary>
145 /// Gets or sets the only child of this container.
146 /// </summary>
147 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
148 public T Child
149 {
150 get
151 {
152 if (Children.Count != 1)
153 throw new InvalidOperationException($"Cannot call {nameof(InternalChild)} unless there's exactly one {nameof(Drawable)} in {nameof(Children)} (currently {Children.Count})!");
154
155 return Children[0];
156 }
157 set
158 {
159 Clear();
160 Add(value);
161 }
162 }
163
164 private readonly IReadOnlyList<T> internalChildrenAsT;
165 private readonly IReadOnlyList<T> aliveInternalChildrenAsT;
166
167 /// <summary>
168 /// The index of a given child within <see cref="Children"/>.
169 /// </summary>
170 /// <returns>
171 /// If the child is found, its index. Otherwise, the negated index it would obtain
172 /// if it were added to <see cref="Children"/>.
173 /// </returns>
174 public int IndexOf(T drawable)
175 {
176 if (Content != this)
177 return Content.IndexOf(drawable);
178
179 return IndexOfInternal(drawable);
180 }
181
182 /// <summary>
183 /// Checks whether a given child is contained within <see cref="Children"/>.
184 /// </summary>
185 public bool Contains(T drawable)
186 {
187 int index = IndexOf(drawable);
188 return index >= 0 && this[index] == drawable;
189 }
190
191 /// <summary>
192 /// Adds a child to this container. This amounts to adding a child to <see cref="Content"/>'s
193 /// <see cref="Children"/>, recursing until <see cref="Content"/> == this.
194 /// </summary>
195 public virtual void Add(T drawable)
196 {
197 if (drawable == Content)
198 throw new InvalidOperationException("Content may not be added to itself.");
199
200 if (Content == this)
201 AddInternal(drawable);
202 else
203 Content.Add(drawable);
204 }
205
206 /// <summary>
207 /// Adds a range of children. This is equivalent to calling <see cref="Add(T)"/> on
208 /// each element of the range in order.
209 /// </summary>
210 public void AddRange(IEnumerable<T> range)
211 {
212 if (range is IContainerEnumerable<Drawable>)
213 {
214 throw new InvalidOperationException($"Attempting to add a {nameof(IContainer)} as a range of children to {this}."
215 + $"If intentional, consider using the {nameof(IContainerEnumerable<Drawable>.Children)} property instead.");
216 }
217
218 foreach (T d in range)
219 Add(d);
220 }
221
222 protected internal override void AddInternal(Drawable drawable)
223 {
224 if (Content == this && drawable != null && !(drawable is T))
225 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)}.");
226
227 base.AddInternal(drawable);
228 }
229
230 /// <summary>
231 /// Removes a given child from this container.
232 /// </summary>
233 public virtual bool Remove(T drawable) => Content != this ? Content.Remove(drawable) : RemoveInternal(drawable);
234
235 /// <summary>
236 /// Removes all children which match the given predicate.
237 /// This is equivalent to calling <see cref="Remove(T)"/> for each child that
238 /// matches the given predicate.
239 /// </summary>
240 /// <returns>The amount of removed children.</returns>
241 public int RemoveAll(Predicate<T> pred)
242 {
243 if (Content != this)
244 return Content.RemoveAll(pred);
245
246 int removedCount = 0;
247
248 for (int i = 0; i < InternalChildren.Count; i++)
249 {
250 var tChild = (T)InternalChildren[i];
251
252 if (pred.Invoke(tChild))
253 {
254 RemoveInternal(tChild);
255 removedCount++;
256 i--;
257 }
258 }
259
260 return removedCount;
261 }
262
263 /// <summary>
264 /// Removes a range of children. This is equivalent to calling <see cref="Remove(T)"/> on
265 /// each element of the range in order.
266 /// </summary>
267 public void RemoveRange(IEnumerable<T> range)
268 {
269 if (range == null)
270 return;
271
272 foreach (T p in range)
273 Remove(p);
274 }
275
276 /// <summary>
277 /// Removes all children.
278 /// </summary>
279 public void Clear() => Clear(true);
280
281 /// <summary>
282 /// Removes all children.
283 /// </summary>
284 /// <param name="disposeChildren">
285 /// Whether removed children should also get disposed.
286 /// Disposal will be recursive.
287 /// </param>
288 public virtual void Clear(bool disposeChildren)
289 {
290 if (Content != null && Content != this)
291 Content.Clear(disposeChildren);
292 else
293 ClearInternal(disposeChildren);
294 }
295
296 /// <summary>
297 /// Changes the depth of a child. This affects ordering of children within this container.
298 /// </summary>
299 /// <param name="child">The child whose depth is to be changed.</param>
300 /// <param name="newDepth">The new depth value to be set.</param>
301 public void ChangeChildDepth(T child, float newDepth)
302 {
303 if (Content != this)
304 Content.ChangeChildDepth(child, newDepth);
305 else
306 ChangeInternalChildDepth(child, newDepth);
307 }
308
309 /// <summary>
310 /// If enabled, only the portion of children that falls within this <see cref="Container"/>'s
311 /// shape is drawn to the screen.
312 /// </summary>
313 public new bool Masking
314 {
315 get => base.Masking;
316 set => base.Masking = value;
317 }
318
319 /// <summary>
320 /// Determines over how many pixels the alpha component smoothly fades out when an inner <see cref="EdgeEffect"/> or <see cref="BorderThickness"/> is present.
321 /// Only has an effect when <see cref="Masking"/> is true.
322 /// </summary>
323 public new float MaskingSmoothness
324 {
325 get => base.MaskingSmoothness;
326 set => base.MaskingSmoothness = value;
327 }
328
329 /// <summary>
330 /// Determines how large of a radius is masked away around the corners.
331 /// Only has an effect when <see cref="Masking"/> is true.
332 /// </summary>
333 public new float CornerRadius
334 {
335 get => base.CornerRadius;
336 set => base.CornerRadius = value;
337 }
338
339 /// <summary>
340 /// Determines how gentle the curve of the corner straightens. A value of 2 results in
341 /// circular arcs, a value of 2.5 (default) results in something closer to apple's "continuous corner".
342 /// Values between 2 and 10 result in varying degrees of "continuousness", where larger values are smoother.
343 /// Values between 1 and 2 result in a "flatter" appearance than round corners.
344 /// Values between 0 and 1 result in a concave, round corner as opposed to a convex round corner,
345 /// where a value of 0.5 is a circular concave arc.
346 /// Only has an effect when <see cref="Masking"/> is true and <see cref="CornerRadius"/> is non-zero.
347 /// </summary>
348 public new float CornerExponent
349 {
350 get => base.CornerExponent;
351 set => base.CornerExponent = value;
352 }
353
354 /// <summary>
355 /// Determines how thick of a border to draw around the inside of the masked region.
356 /// Only has an effect when <see cref="Masking"/> is true.
357 /// The border only is drawn on top of children using a sprite shader.
358 /// </summary>
359 /// <remarks>
360 /// Drawing borders is optimized heavily into our sprite shaders. As a consequence
361 /// borders are only drawn correctly on top of quad-shaped children using our sprite
362 /// shaders.
363 /// </remarks>
364 public new float BorderThickness
365 {
366 get => base.BorderThickness;
367 set => base.BorderThickness = value;
368 }
369
370 /// <summary>
371 /// Determines the color of the border controlled by <see cref="BorderThickness"/>.
372 /// Only has an effect when <see cref="Masking"/> is true.
373 /// </summary>
374 public new SRGBColour BorderColour
375 {
376 get => base.BorderColour;
377 set => base.BorderColour = value;
378 }
379
380 /// <summary>
381 /// Determines an edge effect of this <see cref="Container"/>.
382 /// Edge effects are e.g. glow or a shadow.
383 /// Only has an effect when <see cref="Masking"/> is true.
384 /// </summary>
385 public new EdgeEffectParameters EdgeEffect
386 {
387 get => base.EdgeEffect;
388 set => base.EdgeEffect = value;
389 }
390
391 /// <summary>
392 /// Shrinks the space children may occupy within this <see cref="Container"/>
393 /// by the specified amount on each side.
394 /// </summary>
395 public new MarginPadding Padding
396 {
397 get => base.Padding;
398 set => base.Padding = value;
399 }
400
401 /// <summary>
402 /// Whether to use a local vertex batch for rendering. If false, a parenting vertex batch will be used.
403 /// </summary>
404 public new bool ForceLocalVertexBatch
405 {
406 get => base.ForceLocalVertexBatch;
407 set => base.ForceLocalVertexBatch = value;
408 }
409
410 /// <summary>
411 /// The size of the relative position/size coordinate space of children of this <see cref="Container"/>.
412 /// Children positioned at this size will appear as if they were positioned at <see cref="Drawable.Position"/> = <see cref="Vector2.One"/> in this <see cref="Container"/>.
413 /// </summary>
414 public new Vector2 RelativeChildSize
415 {
416 get => base.RelativeChildSize;
417 set => base.RelativeChildSize = value;
418 }
419
420 /// <summary>
421 /// The offset of the relative position/size coordinate space of children of this <see cref="Container"/>.
422 /// Children positioned at this offset will appear as if they were positioned at <see cref="Drawable.Position"/> = <see cref="Vector2.Zero"/> in this <see cref="Container"/>.
423 /// </summary>
424 public new Vector2 RelativeChildOffset
425 {
426 get => base.RelativeChildOffset;
427 set => base.RelativeChildOffset = value;
428 }
429
430 /// <summary>
431 /// Controls which <see cref="Axes"/> are automatically sized w.r.t. <see cref="CompositeDrawable.InternalChildren"/>.
432 /// Children's <see cref="Drawable.BypassAutoSizeAxes"/> are ignored for automatic sizing.
433 /// Most notably, <see cref="Drawable.RelativePositionAxes"/> and <see cref="Drawable.RelativeSizeAxes"/> of children
434 /// do not affect automatic sizing to avoid circular size dependencies.
435 /// It is not allowed to manually set <see cref="Drawable.Size"/> (or <see cref="Drawable.Width"/> / <see cref="Drawable.Height"/>)
436 /// on any <see cref="Axes"/> which are automatically sized.
437 /// </summary>
438 public new Axes AutoSizeAxes
439 {
440 get => base.AutoSizeAxes;
441 set => base.AutoSizeAxes = value;
442 }
443
444 /// <summary>
445 /// The duration which automatic sizing should take. If zero, then it is instantaneous.
446 /// Otherwise, this is equivalent to applying an automatic size via a resize transform.
447 /// </summary>
448 public new float AutoSizeDuration
449 {
450 get => base.AutoSizeDuration;
451 set => base.AutoSizeDuration = value;
452 }
453
454 /// <summary>
455 /// The type of easing which should be used for smooth automatic sizing when <see cref="AutoSizeDuration"/>
456 /// is non-zero.
457 /// </summary>
458 public new Easing AutoSizeEasing
459 {
460 get => base.AutoSizeEasing;
461 set => base.AutoSizeEasing = value;
462 }
463
464 public struct Enumerator : IEnumerator<T>
465 {
466 private Container<T> container;
467 private int currentIndex;
468
469 internal Enumerator(Container<T> container)
470 {
471 this.container = container;
472 currentIndex = -1; // The first MoveNext() should bring the iterator to 0
473 }
474
475 public bool MoveNext() => ++currentIndex < container.Count;
476
477 public void Reset() => currentIndex = -1;
478
479 public readonly T Current => container[currentIndex];
480
481 readonly object IEnumerator.Current => Current;
482
483 public void Dispose()
484 {
485 container = null;
486 }
487 }
488 }
489}