A game framework written with osu! in mind.
at master 489 lines 19 kB view raw
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}