A game framework written with osu! in mind.
at master 2934 lines 120 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 osuTK; 5using osuTK.Graphics; 6using osu.Framework.Allocation; 7using osu.Framework.Extensions.TypeExtensions; 8using osu.Framework.Graphics.Colour; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.Effects; 11using osu.Framework.Graphics.Primitives; 12using osu.Framework.Graphics.Transforms; 13using osu.Framework.Input; 14using osu.Framework.Logging; 15using osu.Framework.Statistics; 16using osu.Framework.Threading; 17using osu.Framework.Timing; 18using System; 19using System.Collections.Concurrent; 20using System.Collections.Generic; 21using System.ComponentModel; 22using System.Diagnostics; 23using System.Linq; 24using System.Reflection; 25using System.Threading; 26using JetBrains.Annotations; 27using osu.Framework.Bindables; 28using osu.Framework.Development; 29using osu.Framework.Extensions.EnumExtensions; 30using osu.Framework.Graphics.Cursor; 31using osu.Framework.Graphics.OpenGL; 32using osu.Framework.Input.Bindings; 33using osu.Framework.Input.Events; 34using osu.Framework.Input.States; 35using osu.Framework.Layout; 36using osu.Framework.Testing; 37using osu.Framework.Utils; 38using osuTK.Input; 39using Container = osu.Framework.Graphics.Containers.Container; 40 41namespace osu.Framework.Graphics 42{ 43 /// <summary> 44 /// Drawables are the basic building blocks of a scene graph in this framework. 45 /// Anything that is visible or that the user interacts with has to be a Drawable. 46 /// 47 /// For example: 48 /// - Boxes 49 /// - Sprites 50 /// - Collections of Drawables 51 /// 52 /// Drawables are always rectangular in shape in their local coordinate system, 53 /// which makes them quad-shaped in arbitrary (linearly transformed) coordinate systems. 54 /// </summary> 55 [ExcludeFromDynamicCompile] 56 public abstract partial class Drawable : Transformable, IDisposable, IDrawable 57 { 58 #region Construction and disposal 59 60 protected Drawable() 61 { 62 total_count.Value++; 63 64 AddLayout(drawInfoBacking); 65 AddLayout(drawSizeBacking); 66 AddLayout(screenSpaceDrawQuadBacking); 67 AddLayout(drawColourInfoBacking); 68 AddLayout(requiredParentSizeToFitBacking); 69 } 70 71 private static readonly GlobalStatistic<int> total_count = GlobalStatistics.Get<int>(nameof(Drawable), "Total constructed"); 72 73 internal bool IsLongRunning => GetType().GetCustomAttribute<LongRunningLoadAttribute>() != null; 74 75 /// <summary> 76 /// Disposes this drawable. 77 /// </summary> 78 public void Dispose() 79 { 80 //we can't dispose if we are mid-load, else our children may get in a bad state. 81 lock (LoadLock) Dispose(true); 82 83 GC.SuppressFinalize(this); 84 } 85 86 protected internal bool IsDisposed { get; private set; } 87 88 /// <summary> 89 /// Disposes this drawable. 90 /// </summary> 91 protected virtual void Dispose(bool isDisposing) 92 { 93 if (IsDisposed) 94 return; 95 96 UnbindAllBindables(); 97 98 // Bypass expensive operations as a result of setting the Parent property, by setting the field directly. 99 parent = null; 100 ChildID = 0; 101 102 OnUpdate = null; 103 Invalidated = null; 104 105 OnDispose?.Invoke(); 106 OnDispose = null; 107 108 for (int i = 0; i < drawNodes.Length; i++) 109 drawNodes[i]?.Dispose(); 110 111 IsDisposed = true; 112 } 113 114 /// <summary> 115 /// Whether this Drawable should be disposed when it is automatically removed from 116 /// its <see cref="Parent"/> due to <see cref="ShouldBeAlive"/> being false. 117 /// </summary> 118 public virtual bool DisposeOnDeathRemoval => RemoveCompletedTransforms; 119 120 private static readonly ConcurrentDictionary<Type, Action<object>> unbind_action_cache = new ConcurrentDictionary<Type, Action<object>>(); 121 122 /// <summary> 123 /// Recursively invokes <see cref="UnbindAllBindables"/> on this <see cref="Drawable"/> and all <see cref="Drawable"/>s further down the scene graph. 124 /// </summary> 125 internal virtual void UnbindAllBindablesSubTree() => UnbindAllBindables(); 126 127 private void cacheUnbindActions() 128 { 129 foreach (var type in GetType().EnumerateBaseTypes()) 130 { 131 if (unbind_action_cache.TryGetValue(type, out _)) 132 return; 133 134 // List containing all the delegates to perform the unbinds 135 var actions = new List<Action<object>>(); 136 137 // Generate delegates to unbind fields 138 actions.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) 139 .Where(f => typeof(IUnbindable).IsAssignableFrom(f.FieldType)) 140 .Select(f => new Action<object>(target => ((IUnbindable)f.GetValue(target))?.UnbindAll()))); 141 142 // Delegates to unbind properties are intentionally not generated. 143 // Properties with backing fields (including automatic properties) will be picked up by the field unbind delegate generation, 144 // while ones without backing fields (like get-only properties that delegate to another drawable's bindable) should not be unbound here. 145 146 unbind_action_cache[type] = target => 147 { 148 foreach (var a in actions) 149 { 150 try 151 { 152 a(target); 153 } 154 catch (Exception e) 155 { 156 Logger.Error(e, $"Failed to unbind a local bindable in {type.ReadableName()}"); 157 } 158 } 159 }; 160 } 161 } 162 163 private bool unbindComplete; 164 165 /// <summary> 166 /// Unbinds all <see cref="Bindable{T}"/>s stored as fields or properties in this <see cref="Drawable"/>. 167 /// </summary> 168 internal virtual void UnbindAllBindables() 169 { 170 if (unbindComplete) 171 return; 172 173 unbindComplete = true; 174 175 foreach (var type in GetType().EnumerateBaseTypes()) 176 { 177 if (unbind_action_cache.TryGetValue(type, out var existing)) 178 existing?.Invoke(this); 179 } 180 181 OnUnbindAllBindables?.Invoke(); 182 } 183 184 #endregion 185 186 #region Loading 187 188 /// <summary> 189 /// Whether this Drawable is fully loaded. 190 /// This is true iff <see cref="UpdateSubTree"/> has run once on this <see cref="Drawable"/>. 191 /// </summary> 192 public bool IsLoaded => loadState >= LoadState.Loaded; 193 194 private volatile LoadState loadState; 195 196 /// <summary> 197 /// Describes the current state of this Drawable within the loading pipeline. 198 /// </summary> 199 public LoadState LoadState => loadState; 200 201 /// <summary> 202 /// The thread on which the <see cref="Load"/> operation started, or null if <see cref="Drawable"/> has not started loading. 203 /// </summary> 204 internal Thread LoadThread { get; private set; } 205 206 internal readonly object LoadLock = new object(); 207 208 private static readonly StopwatchClock perf_clock = new StopwatchClock(true); 209 210 /// <summary> 211 /// Load this drawable from an async context. 212 /// Because we can't be sure of the disposal state, it is returned as a bool rather than thrown as in <see cref="Load"/>. 213 /// </summary> 214 /// <param name="clock">The clock we should use by default.</param> 215 /// <param name="dependencies">The dependency tree we will inherit by default. May be extended via <see cref="CompositeDrawable.CreateChildDependencies"/></param> 216 /// <param name="isDirectAsyncContext">Whether this call is being executed from a directly async context (not a parent).</param> 217 /// <returns>Whether the load was successful.</returns> 218 internal bool LoadFromAsync(IFrameBasedClock clock, IReadOnlyDependencyContainer dependencies, bool isDirectAsyncContext = false) 219 { 220 lock (LoadLock) 221 { 222 if (IsDisposed) 223 return false; 224 225 Load(clock, dependencies, isDirectAsyncContext); 226 return true; 227 } 228 } 229 230 /// <summary> 231 /// Loads this drawable, including the gathering of dependencies and initialisation of required resources. 232 /// </summary> 233 /// <param name="clock">The clock we should use by default.</param> 234 /// <param name="dependencies">The dependency tree we will inherit by default. May be extended via <see cref="CompositeDrawable.CreateChildDependencies"/></param> 235 /// <param name="isDirectAsyncContext">Whether this call is being executed from a directly async context (not a parent).</param> 236 internal void Load(IFrameBasedClock clock, IReadOnlyDependencyContainer dependencies, bool isDirectAsyncContext = false) 237 { 238 lock (LoadLock) 239 { 240 if (!isDirectAsyncContext && IsLongRunning) 241 throw new InvalidOperationException("Tried to load a long-running drawable in a non-direct async context. See https://git.io/Je1YF for more details."); 242 243 if (IsDisposed) 244 throw new ObjectDisposedException(ToString(), "Attempting to load an already disposed drawable."); 245 246 if (loadState == LoadState.NotLoaded) 247 { 248 Trace.Assert(loadState == LoadState.NotLoaded); 249 250 loadState = LoadState.Loading; 251 252 load(clock, dependencies); 253 254 loadState = LoadState.Ready; 255 } 256 } 257 } 258 259 private void load(IFrameBasedClock clock, IReadOnlyDependencyContainer dependencies) 260 { 261 LoadThread = Thread.CurrentThread; 262 263 UpdateClock(clock); 264 265 double timeBefore = DebugUtils.LogPerformanceIssues ? perf_clock.CurrentTime : 0; 266 267 RequestsNonPositionalInput = HandleInputCache.RequestsNonPositionalInput(this); 268 RequestsPositionalInput = HandleInputCache.RequestsPositionalInput(this); 269 270 RequestsNonPositionalInputSubTree = RequestsNonPositionalInput; 271 RequestsPositionalInputSubTree = RequestsPositionalInput; 272 273 InjectDependencies(dependencies); 274 275 cacheUnbindActions(); 276 277 LoadAsyncComplete(); 278 279 if (timeBefore > 1000) 280 { 281 double loadDuration = perf_clock.CurrentTime - timeBefore; 282 283 bool blocking = ThreadSafety.IsUpdateThread; 284 285 double allowedDuration = blocking ? 16 : 100; 286 287 if (loadDuration > allowedDuration) 288 { 289 Logger.Log($@"{ToString()} took {loadDuration:0.00}ms to load" + (blocking ? " (and blocked the update thread)" : " (async)"), LoggingTarget.Performance, 290 blocking ? LogLevel.Important : LogLevel.Verbose); 291 } 292 } 293 } 294 295 /// <summary> 296 /// Injects dependencies from an <see cref="IReadOnlyDependencyContainer"/> into this <see cref="Drawable"/>. 297 /// </summary> 298 /// <param name="dependencies">The dependencies to inject.</param> 299 protected virtual void InjectDependencies(IReadOnlyDependencyContainer dependencies) => dependencies.Inject(this); 300 301 /// <summary> 302 /// Runs once on the update thread after loading has finished. 303 /// </summary> 304 private bool loadComplete() 305 { 306 if (loadState < LoadState.Ready) return false; 307 308 loadState = LoadState.Loaded; 309 310 // From a synchronous point of view, this is the first time the Drawable receives a parent. 311 // If this Drawable calculated properties such as DrawInfo that depend on the parent state before this point, they must be re-validated in the now-correct state. 312 // A "parent" source is faked since Child+Self states are always assumed valid if they only access local Drawable states (e.g. Colour but not DrawInfo). 313 // Only layout flags are required, as non-layout flags are always propagated by the parent. 314 Invalidate(Invalidation.Layout, InvalidationSource.Parent); 315 316 LoadComplete(); 317 318 OnLoadComplete?.Invoke(this); 319 OnLoadComplete = null; 320 return true; 321 } 322 323 /// <summary> 324 /// Invoked after dependency injection has completed for this <see cref="Drawable"/> and all 325 /// children if this is a <see cref="CompositeDrawable"/>. 326 /// </summary> 327 /// <remarks> 328 /// This method is invoked in the potentially asynchronous context of <see cref="Load"/> prior to 329 /// this <see cref="Drawable"/> becoming <see cref="IsLoaded"/> = true. 330 /// </remarks> 331 protected virtual void LoadAsyncComplete() 332 { 333 } 334 335 /// <summary> 336 /// Invoked after this <see cref="Drawable"/> has finished loading. 337 /// </summary> 338 /// <remarks> 339 /// This method is invoked on the update thread inside this <see cref="Drawable"/>'s <see cref="UpdateSubTree"/>. 340 /// </remarks> 341 protected virtual void LoadComplete() 342 { 343 } 344 345 #endregion 346 347 #region Sorting (CreationID / Depth) 348 349 /// <summary> 350 /// Captures the order in which Drawables were added to a <see cref="CompositeDrawable"/>. Each Drawable 351 /// is assigned a monotonically increasing ID upon being added to a <see cref="CompositeDrawable"/>. This 352 /// ID is unique within the <see cref="Parent"/> <see cref="CompositeDrawable"/>. 353 /// The primary use case of this ID is stable sorting of Drawables with equal <see cref="Depth"/>. 354 /// </summary> 355 internal ulong ChildID { get; set; } 356 357 /// <summary> 358 /// Whether this drawable has been added to a parent <see cref="CompositeDrawable"/>. Note that this does NOT imply that 359 /// <see cref="Parent"/> has been set. 360 /// This is primarily used to block properties such as <see cref="Depth"/> that strictly rely on the value of <see cref="Parent"/> 361 /// to alert the user of an invalid operation. 362 /// </summary> 363 internal bool IsPartOfComposite => ChildID != 0; 364 365 /// <summary> 366 /// Whether this drawable is part of its parent's <see cref="CompositeDrawable.AliveInternalChildren"/>. 367 /// </summary> 368 public bool IsAlive { get; internal set; } 369 370 private float depth; 371 372 /// <summary> 373 /// Controls which Drawables are behind or in front of other Drawables. 374 /// This amounts to sorting Drawables by their <see cref="Depth"/>. 375 /// A Drawable with higher <see cref="Depth"/> than another Drawable is 376 /// drawn behind the other Drawable. 377 /// </summary> 378 public float Depth 379 { 380 get => depth; 381 set 382 { 383 if (IsPartOfComposite) 384 { 385 throw new InvalidOperationException( 386 $"May not change {nameof(Depth)} while inside a parent {nameof(CompositeDrawable)}." + 387 $"Use the parent's {nameof(CompositeDrawable.ChangeInternalChildDepth)} or {nameof(Container.ChangeChildDepth)} instead."); 388 } 389 390 depth = value; 391 } 392 } 393 394 #endregion 395 396 #region Periodic tasks (events, Scheduler, Transforms, Update) 397 398 /// <summary> 399 /// This event is fired after the <see cref="Update"/> method is called at the end of 400 /// <see cref="UpdateSubTree"/>. It should be used when a simple action should be performed 401 /// at the end of every update call which does not warrant overriding the Drawable. 402 /// </summary> 403 public event Action<Drawable> OnUpdate; 404 405 /// <summary> 406 /// This event is fired after the <see cref="LoadComplete"/> method is called. 407 /// It should be used when a simple action should be performed 408 /// when the Drawable is loaded which does not warrant overriding the Drawable. 409 /// This event is automatically cleared after being invoked. 410 /// </summary> 411 public event Action<Drawable> OnLoadComplete; 412 413 /// <summary>. 414 /// Fired after the <see cref="Invalidate"/> method is called. 415 /// </summary> 416 internal event Action<Drawable> Invalidated; 417 418 /// <summary> 419 /// Fired after the <see cref="Dispose(bool)"/> method is called. 420 /// </summary> 421 internal event Action OnDispose; 422 423 /// <summary> 424 /// Fired after the <see cref="UnbindAllBindables"/> method is called. 425 /// </summary> 426 internal event Action OnUnbindAllBindables; 427 428 /// <summary> 429 /// A lock exclusively used for initial acquisition/construction of the <see cref="Scheduler"/>. 430 /// </summary> 431 private static readonly object scheduler_acquisition_lock = new object(); 432 433 private Scheduler scheduler; 434 435 /// <summary> 436 /// A lazily-initialized scheduler used to schedule tasks to be invoked in future <see cref="Update"/>s calls. 437 /// The tasks are invoked at the beginning of the <see cref="Update"/> method before anything else. 438 /// </summary> 439 protected internal Scheduler Scheduler 440 { 441 get 442 { 443 if (scheduler != null) 444 return scheduler; 445 446 lock (scheduler_acquisition_lock) 447 return scheduler ??= new Scheduler(() => ThreadSafety.IsUpdateThread, Clock); 448 } 449 } 450 451 /// <summary> 452 /// Updates this Drawable and all Drawables further down the scene graph. 453 /// Called once every frame. 454 /// </summary> 455 /// <returns>False if the drawable should not be updated.</returns> 456 public virtual bool UpdateSubTree() 457 { 458 if (IsDisposed) 459 throw new ObjectDisposedException(ToString(), "Disposed Drawables may never be in the scene graph."); 460 461 if (ProcessCustomClock) 462 customClock?.ProcessFrame(); 463 464 if (loadState < LoadState.Ready) 465 return false; 466 467 if (loadState == LoadState.Ready) 468 loadComplete(); 469 470 Debug.Assert(loadState == LoadState.Loaded); 471 472 UpdateTransforms(); 473 474 if (!IsPresent) 475 return true; 476 477 if (scheduler != null) 478 { 479 int amountScheduledTasks = scheduler.Update(); 480 FrameStatistics.Add(StatisticsCounterType.ScheduleInvk, amountScheduledTasks); 481 } 482 483 Update(); 484 OnUpdate?.Invoke(this); 485 return true; 486 } 487 488 /// <summary> 489 /// Updates all masking calculations for this <see cref="Drawable"/>. 490 /// This occurs post-<see cref="UpdateSubTree"/> to ensure that all <see cref="Drawable"/> updates have taken place. 491 /// </summary> 492 /// <param name="source">The parent that triggered this update on this <see cref="Drawable"/>.</param> 493 /// <param name="maskingBounds">The <see cref="RectangleF"/> that defines the masking bounds.</param> 494 /// <returns>Whether masking calculations have taken place.</returns> 495 public virtual bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) 496 { 497 if (!IsPresent) 498 return false; 499 500 if (HasProxy && source != proxy) 501 return false; 502 503 IsMaskedAway = ComputeIsMaskedAway(maskingBounds); 504 return true; 505 } 506 507 /// <summary> 508 /// Computes whether this <see cref="Drawable"/> is masked away. 509 /// </summary> 510 /// <param name="maskingBounds">The <see cref="RectangleF"/> that defines the masking bounds.</param> 511 /// <returns>Whether this <see cref="Drawable"/> is currently masked away.</returns> 512 protected virtual bool ComputeIsMaskedAway(RectangleF maskingBounds) => !Precision.AlmostIntersects(maskingBounds, ScreenSpaceDrawQuad.AABBFloat); 513 514 /// <summary> 515 /// Performs a once-per-frame update specific to this Drawable. A more elegant alternative to 516 /// <see cref="OnUpdate"/> when deriving from <see cref="Drawable"/>. Note, that this 517 /// method is always called before Drawables further down the scene graph are updated. 518 /// </summary> 519 protected virtual void Update() 520 { 521 } 522 523 #endregion 524 525 #region Position / Size (with margin) 526 527 private Vector2 position 528 { 529 get => new Vector2(x, y); 530 set 531 { 532 x = value.X; 533 y = value.Y; 534 } 535 } 536 537 /// <summary> 538 /// Positional offset of <see cref="Origin"/> to <see cref="RelativeAnchorPosition"/> in the 539 /// <see cref="Parent"/>'s coordinate system. May be in absolute or relative units 540 /// (controlled by <see cref="RelativePositionAxes"/>). 541 /// </summary> 542 public Vector2 Position 543 { 544 get => position; 545 set 546 { 547 if (position == value) return; 548 549 if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(Position)} must be finite, but is {value}."); 550 551 Axes changedAxes = Axes.None; 552 553 if (position.X != value.X) 554 changedAxes |= Axes.X; 555 556 if (position.Y != value.Y) 557 changedAxes |= Axes.Y; 558 559 position = value; 560 561 invalidateParentSizeDependencies(Invalidation.MiscGeometry, changedAxes); 562 } 563 } 564 565 private float x; 566 private float y; 567 568 /// <summary> 569 /// X component of <see cref="Position"/>. 570 /// </summary> 571 public float X 572 { 573 get => x; 574 set 575 { 576 if (x == value) return; 577 578 if (!float.IsFinite(value)) throw new ArgumentException($@"{nameof(X)} must be finite, but is {value}."); 579 580 x = value; 581 582 invalidateParentSizeDependencies(Invalidation.MiscGeometry, Axes.X); 583 } 584 } 585 586 /// <summary> 587 /// Y component of <see cref="Position"/>. 588 /// </summary> 589 public float Y 590 { 591 get => y; 592 set 593 { 594 if (y == value) return; 595 596 if (!float.IsFinite(value)) throw new ArgumentException($@"{nameof(Y)} must be finite, but is {value}."); 597 598 y = value; 599 600 invalidateParentSizeDependencies(Invalidation.MiscGeometry, Axes.Y); 601 } 602 } 603 604 private Axes relativePositionAxes; 605 606 /// <summary> 607 /// Controls which <see cref="Axes"/> of <see cref="Position"/> are relative w.r.t. 608 /// <see cref="Parent"/>'s size (from 0 to 1) rather than absolute. 609 /// The <see cref="Axes"/> set in this property are ignored by automatically sizing 610 /// parents. 611 /// </summary> 612 /// <remarks> 613 /// When setting this property, the <see cref="Position"/> is converted such that 614 /// <see cref="DrawPosition"/> remains invariant. 615 /// </remarks> 616 public Axes RelativePositionAxes 617 { 618 get => relativePositionAxes; 619 set 620 { 621 if (value == relativePositionAxes) 622 return; 623 624 // Convert coordinates from relative to absolute or vice versa 625 Vector2 conversion = relativeToAbsoluteFactor; 626 if ((value & Axes.X) > (relativePositionAxes & Axes.X)) 627 X = Precision.AlmostEquals(conversion.X, 0) ? 0 : X / conversion.X; 628 else if ((relativePositionAxes & Axes.X) > (value & Axes.X)) 629 X *= conversion.X; 630 631 if ((value & Axes.Y) > (relativePositionAxes & Axes.Y)) 632 Y = Precision.AlmostEquals(conversion.Y, 0) ? 0 : Y / conversion.Y; 633 else if ((relativePositionAxes & Axes.Y) > (value & Axes.Y)) 634 Y *= conversion.Y; 635 636 relativePositionAxes = value; 637 638 updateBypassAutoSizeAxes(); 639 } 640 } 641 642 /// <summary> 643 /// Absolute positional offset of <see cref="Origin"/> to <see cref="RelativeAnchorPosition"/> 644 /// in the <see cref="Parent"/>'s coordinate system. 645 /// </summary> 646 public Vector2 DrawPosition 647 { 648 get 649 { 650 Vector2 offset = Vector2.Zero; 651 652 if (Parent != null && RelativePositionAxes != Axes.None) 653 { 654 offset = Parent.RelativeChildOffset; 655 656 if (!RelativePositionAxes.HasFlagFast(Axes.X)) 657 offset.X = 0; 658 659 if (!RelativePositionAxes.HasFlagFast(Axes.Y)) 660 offset.Y = 0; 661 } 662 663 return ApplyRelativeAxes(RelativePositionAxes, Position - offset, FillMode.Stretch); 664 } 665 } 666 667 private Vector2 size 668 { 669 get => new Vector2(width, height); 670 set 671 { 672 width = value.X; 673 height = value.Y; 674 } 675 } 676 677 /// <summary> 678 /// Size of this Drawable in the <see cref="Parent"/>'s coordinate system. 679 /// May be in absolute or relative units (controlled by <see cref="RelativeSizeAxes"/>). 680 /// </summary> 681 public virtual Vector2 Size 682 { 683 get => size; 684 set 685 { 686 if (size == value) return; 687 688 if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(Size)} must be finite, but is {value}."); 689 690 Axes changedAxes = Axes.None; 691 692 if (size.X != value.X) 693 changedAxes |= Axes.X; 694 695 if (size.Y != value.Y) 696 changedAxes |= Axes.Y; 697 698 size = value; 699 700 invalidateParentSizeDependencies(Invalidation.DrawSize, changedAxes); 701 } 702 } 703 704 private float width; 705 private float height; 706 707 /// <summary> 708 /// X component of <see cref="Size"/>. 709 /// </summary> 710 public virtual float Width 711 { 712 get => width; 713 set 714 { 715 if (width == value) return; 716 717 if (!float.IsFinite(value)) throw new ArgumentException($@"{nameof(Width)} must be finite, but is {value}."); 718 719 width = value; 720 721 invalidateParentSizeDependencies(Invalidation.DrawSize, Axes.X); 722 } 723 } 724 725 /// <summary> 726 /// Y component of <see cref="Size"/>. 727 /// </summary> 728 public virtual float Height 729 { 730 get => height; 731 set 732 { 733 if (height == value) return; 734 735 if (!float.IsFinite(value)) throw new ArgumentException($@"{nameof(Height)} must be finite, but is {value}."); 736 737 height = value; 738 739 invalidateParentSizeDependencies(Invalidation.DrawSize, Axes.Y); 740 } 741 } 742 743 private Axes relativeSizeAxes; 744 745 /// <summary> 746 /// Controls which <see cref="Axes"/> are relative sizes w.r.t. <see cref="Parent"/>'s size 747 /// (from 0 to 1) in the <see cref="Parent"/>'s coordinate system, rather than absolute sizes. 748 /// The <see cref="Axes"/> set in this property are ignored by automatically sizing 749 /// parents. 750 /// </summary> 751 /// <remarks> 752 /// If an axis becomes relatively sized and its component of <see cref="Size"/> was previously 0, 753 /// then it automatically becomes 1. In all other cases <see cref="Size"/> is converted such that 754 /// <see cref="DrawSize"/> remains invariant across changes of this property. 755 /// </remarks> 756 public virtual Axes RelativeSizeAxes 757 { 758 get => relativeSizeAxes; 759 set 760 { 761 if (value == relativeSizeAxes) 762 return; 763 764 // In some cases we cannot easily preserve our size, and so we simply invalidate and 765 // leave correct sizing to the user. 766 if (fillMode != FillMode.Stretch && (value == Axes.Both || relativeSizeAxes == Axes.Both)) 767 Invalidate(Invalidation.DrawSize); 768 else 769 { 770 // Convert coordinates from relative to absolute or vice versa 771 Vector2 conversion = relativeToAbsoluteFactor; 772 if ((value & Axes.X) > (relativeSizeAxes & Axes.X)) 773 Width = Precision.AlmostEquals(conversion.X, 0) ? 0 : Width / conversion.X; 774 else if ((relativeSizeAxes & Axes.X) > (value & Axes.X)) 775 Width *= conversion.X; 776 777 if ((value & Axes.Y) > (relativeSizeAxes & Axes.Y)) 778 Height = Precision.AlmostEquals(conversion.Y, 0) ? 0 : Height / conversion.Y; 779 else if ((relativeSizeAxes & Axes.Y) > (value & Axes.Y)) 780 Height *= conversion.Y; 781 } 782 783 relativeSizeAxes = value; 784 785 if (relativeSizeAxes.HasFlagFast(Axes.X) && Width == 0) Width = 1; 786 if (relativeSizeAxes.HasFlagFast(Axes.Y) && Height == 0) Height = 1; 787 788 updateBypassAutoSizeAxes(); 789 790 OnSizingChanged(); 791 } 792 } 793 794 private readonly LayoutValue<Vector2> drawSizeBacking = new LayoutValue<Vector2>(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); 795 796 /// <summary> 797 /// Absolute size of this Drawable in the <see cref="Parent"/>'s coordinate system. 798 /// </summary> 799 public Vector2 DrawSize => drawSizeBacking.IsValid ? drawSizeBacking : drawSizeBacking.Value = ApplyRelativeAxes(RelativeSizeAxes, Size, FillMode); 800 801 /// <summary> 802 /// X component of <see cref="DrawSize"/>. 803 /// </summary> 804 public float DrawWidth => DrawSize.X; 805 806 /// <summary> 807 /// Y component of <see cref="DrawSize"/>. 808 /// </summary> 809 public float DrawHeight => DrawSize.Y; 810 811 private MarginPadding margin; 812 813 /// <summary> 814 /// Size of an empty region around this Drawable used to manipulate 815 /// layout. Does not affect <see cref="DrawSize"/> or the region of accepted input, 816 /// but does affect <see cref="LayoutSize"/>. 817 /// </summary> 818 public MarginPadding Margin 819 { 820 get => margin; 821 set 822 { 823 if (margin.Equals(value)) return; 824 825 if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(Margin)} must be finite, but is {value}."); 826 827 margin = value; 828 829 Invalidate(Invalidation.MiscGeometry); 830 } 831 } 832 833 /// <summary> 834 /// Absolute size of this Drawable's layout rectangle in the <see cref="Parent"/>'s 835 /// coordinate system; i.e. <see cref="DrawSize"/> with the addition of <see cref="Margin"/>. 836 /// </summary> 837 public Vector2 LayoutSize => DrawSize + new Vector2(margin.TotalHorizontal, margin.TotalVertical); 838 839 /// <summary> 840 /// Absolutely sized rectangle for drawing in the <see cref="Parent"/>'s coordinate system. 841 /// Based on <see cref="DrawSize"/>. 842 /// </summary> 843 public RectangleF DrawRectangle 844 { 845 get 846 { 847 Vector2 s = DrawSize; 848 return new RectangleF(0, 0, s.X, s.Y); 849 } 850 } 851 852 /// <summary> 853 /// Absolutely sized rectangle for layout in the <see cref="Parent"/>'s coordinate system. 854 /// Based on <see cref="LayoutSize"/> and <see cref="margin"/>. 855 /// </summary> 856 public RectangleF LayoutRectangle 857 { 858 get 859 { 860 Vector2 s = LayoutSize; 861 return new RectangleF(-margin.Left, -margin.Top, s.X, s.Y); 862 } 863 } 864 865 /// <summary> 866 /// Helper function for converting potentially relative coordinates in the 867 /// <see cref="Parent"/>'s space to absolute coordinates based on which 868 /// axes are relative. <see cref="Axes"/> 869 /// </summary> 870 /// <param name="relativeAxes">Describes which axes are relative.</param> 871 /// <param name="v">The coordinates to convert.</param> 872 /// <param name="fillMode">The <see cref="FillMode"/> to be used.</param> 873 /// <returns>Absolute coordinates in <see cref="Parent"/>'s space.</returns> 874 protected Vector2 ApplyRelativeAxes(Axes relativeAxes, Vector2 v, FillMode fillMode) 875 { 876 if (relativeAxes != Axes.None) 877 { 878 Vector2 conversion = relativeToAbsoluteFactor; 879 880 if (relativeAxes.HasFlagFast(Axes.X)) 881 v.X *= conversion.X; 882 if (relativeAxes.HasFlagFast(Axes.Y)) 883 v.Y *= conversion.Y; 884 885 // FillMode only makes sense if both axes are relatively sized as the general rule 886 // for n-dimensional aspect preservation is to simply take the minimum or the maximum 887 // scale among all active axes. For single axes the minimum / maximum is just the 888 // value itself. 889 if (relativeAxes == Axes.Both && fillMode != FillMode.Stretch) 890 { 891 if (fillMode == FillMode.Fill) 892 v = new Vector2(Math.Max(v.X, v.Y * fillAspectRatio)); 893 else if (fillMode == FillMode.Fit) 894 v = new Vector2(Math.Min(v.X, v.Y * fillAspectRatio)); 895 v.Y /= fillAspectRatio; 896 } 897 } 898 899 return v; 900 } 901 902 /// <summary> 903 /// Conversion factor from relative to absolute coordinates in the <see cref="Parent"/>'s space. 904 /// </summary> 905 private Vector2 relativeToAbsoluteFactor => Parent?.RelativeToAbsoluteFactor ?? Vector2.One; 906 907 private Axes bypassAutoSizeAxes; 908 909 private void updateBypassAutoSizeAxes() 910 { 911 var value = RelativePositionAxes | RelativeSizeAxes | bypassAutoSizeAdditionalAxes; 912 913 if (bypassAutoSizeAxes != value) 914 { 915 var changedAxes = bypassAutoSizeAxes ^ value; 916 bypassAutoSizeAxes = value; 917 if (((Parent?.AutoSizeAxes ?? 0) & changedAxes) != 0) 918 Parent?.Invalidate(Invalidation.RequiredParentSizeToFit, InvalidationSource.Child); 919 } 920 } 921 922 private Axes bypassAutoSizeAdditionalAxes; 923 924 /// <summary> 925 /// Controls which <see cref="Axes"/> are ignored by parent <see cref="Parent"/> automatic sizing. 926 /// Most notably, <see cref="RelativePositionAxes"/> and <see cref="RelativeSizeAxes"/> do not affect 927 /// automatic sizing to avoid circular size dependencies. 928 /// </summary> 929 public Axes BypassAutoSizeAxes 930 { 931 get => bypassAutoSizeAxes; 932 set 933 { 934 bypassAutoSizeAdditionalAxes = value; 935 updateBypassAutoSizeAxes(); 936 } 937 } 938 939 /// <summary> 940 /// Computes the bounding box of this drawable in its parent's space. 941 /// </summary> 942 public virtual RectangleF BoundingBox => ToParentSpace(LayoutRectangle).AABBFloat; 943 944 /// <summary> 945 /// Called whenever the <see cref="RelativeSizeAxes"/> of this drawable is changed, or when the <see cref="Container{T}.AutoSizeAxes"/> are changed if this drawable is a <see cref="Container{T}"/>. 946 /// </summary> 947 protected virtual void OnSizingChanged() 948 { 949 } 950 951 #endregion 952 953 #region Scale / Shear / Rotation 954 955 private Vector2 scale = Vector2.One; 956 957 /// <summary> 958 /// Base relative scaling factor around <see cref="OriginPosition"/>. 959 /// </summary> 960 public Vector2 Scale 961 { 962 get => scale; 963 set 964 { 965 if (Math.Abs(value.X) < Precision.FLOAT_EPSILON) 966 value.X = Precision.FLOAT_EPSILON; 967 if (Math.Abs(value.Y) < Precision.FLOAT_EPSILON) 968 value.Y = Precision.FLOAT_EPSILON; 969 970 if (scale == value) 971 return; 972 973 if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(Scale)} must be finite, but is {value}."); 974 975 bool wasPresent = IsPresent; 976 977 scale = value; 978 979 if (IsPresent != wasPresent) 980 Invalidate(Invalidation.MiscGeometry | Invalidation.Presence); 981 else 982 Invalidate(Invalidation.MiscGeometry); 983 } 984 } 985 986 private float fillAspectRatio = 1; 987 988 /// <summary> 989 /// The desired ratio of width to height when under the effect of a non-stretching <see cref="FillMode"/> 990 /// and <see cref="RelativeSizeAxes"/> being <see cref="Axes.Both"/>. 991 /// </summary> 992 public float FillAspectRatio 993 { 994 get => fillAspectRatio; 995 set 996 { 997 if (fillAspectRatio == value) return; 998 999 if (!float.IsFinite(value)) throw new ArgumentException($@"{nameof(FillAspectRatio)} must be finite, but is {value}."); 1000 if (value == 0) throw new ArgumentException($@"{nameof(FillAspectRatio)} must be non-zero."); 1001 1002 fillAspectRatio = value; 1003 1004 if (fillMode != FillMode.Stretch && RelativeSizeAxes == Axes.Both) 1005 Invalidate(Invalidation.DrawSize); 1006 } 1007 } 1008 1009 private FillMode fillMode; 1010 1011 /// <summary> 1012 /// Controls the behavior of <see cref="RelativeSizeAxes"/> when it is set to <see cref="Axes.Both"/>. 1013 /// Otherwise, this member has no effect. By default, stretching is used, which simply scales 1014 /// this drawable's <see cref="Size"/> according to <see cref="Parent"/>'s <see cref="CompositeDrawable.RelativeToAbsoluteFactor"/> 1015 /// disregarding this drawable's <see cref="FillAspectRatio"/>. Other values of <see cref="FillMode"/> preserve <see cref="FillAspectRatio"/>. 1016 /// </summary> 1017 public FillMode FillMode 1018 { 1019 get => fillMode; 1020 set 1021 { 1022 if (fillMode == value) return; 1023 1024 fillMode = value; 1025 1026 Invalidate(Invalidation.DrawSize); 1027 } 1028 } 1029 1030 /// <summary> 1031 /// Relative scaling factor around <see cref="OriginPosition"/>. 1032 /// </summary> 1033 protected virtual Vector2 DrawScale => Scale; 1034 1035 private Vector2 shear = Vector2.Zero; 1036 1037 /// <summary> 1038 /// Relative shearing factor. The X dimension is relative w.r.t. <see cref="Height"/> 1039 /// and the Y dimension relative w.r.t. <see cref="Width"/>. 1040 /// </summary> 1041 public Vector2 Shear 1042 { 1043 get => shear; 1044 set 1045 { 1046 if (shear == value) return; 1047 1048 if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(Shear)} must be finite, but is {value}."); 1049 1050 shear = value; 1051 1052 Invalidate(Invalidation.MiscGeometry); 1053 } 1054 } 1055 1056 private float rotation; 1057 1058 /// <summary> 1059 /// Rotation in degrees around <see cref="OriginPosition"/>. 1060 /// </summary> 1061 public float Rotation 1062 { 1063 get => rotation; 1064 set 1065 { 1066 if (value == rotation) return; 1067 1068 if (!float.IsFinite(value)) throw new ArgumentException($@"{nameof(Rotation)} must be finite, but is {value}."); 1069 1070 rotation = value; 1071 1072 Invalidate(Invalidation.MiscGeometry); 1073 } 1074 } 1075 1076 #endregion 1077 1078 #region Origin / Anchor 1079 1080 private Anchor origin = Anchor.TopLeft; 1081 1082 /// <summary> 1083 /// The origin of this <see cref="Drawable"/>. 1084 /// </summary> 1085 /// <exception cref="ArgumentException">If the provided value does not exist in the <see cref="osu.Framework.Graphics.Anchor"/> enumeration.</exception> 1086 public virtual Anchor Origin 1087 { 1088 get => origin; 1089 set 1090 { 1091 if (origin == value) return; 1092 1093 if (value == 0) 1094 throw new ArgumentException("Cannot set origin to 0.", nameof(value)); 1095 1096 origin = value; 1097 Invalidate(Invalidation.MiscGeometry); 1098 } 1099 } 1100 1101 private Vector2 customOrigin; 1102 1103 /// <summary> 1104 /// The origin of this <see cref="Drawable"/> expressed in relative coordinates from the top-left corner of <see cref="DrawRectangle"/>. 1105 /// </summary> 1106 /// <exception cref="InvalidOperationException">If <see cref="Origin"/> is <see cref="osu.Framework.Graphics.Anchor.Custom"/>.</exception> 1107 public Vector2 RelativeOriginPosition 1108 { 1109 get 1110 { 1111 if (Origin == Anchor.Custom) 1112 throw new InvalidOperationException(@"Can not obtain relative origin position for custom origins."); 1113 1114 Vector2 result = Vector2.Zero; 1115 if (origin.HasFlagFast(Anchor.x1)) 1116 result.X = 0.5f; 1117 else if (origin.HasFlagFast(Anchor.x2)) 1118 result.X = 1; 1119 1120 if (origin.HasFlagFast(Anchor.y1)) 1121 result.Y = 0.5f; 1122 else if (origin.HasFlagFast(Anchor.y2)) 1123 result.Y = 1; 1124 1125 return result; 1126 } 1127 } 1128 1129 /// <summary> 1130 /// The origin of this <see cref="Drawable"/> expressed in absolute coordinates from the top-left corner of <see cref="DrawRectangle"/>. 1131 /// </summary> 1132 /// <exception cref="ArgumentException">If the provided value is not finite.</exception> 1133 public virtual Vector2 OriginPosition 1134 { 1135 get 1136 { 1137 Vector2 result; 1138 if (Origin == Anchor.Custom) 1139 result = customOrigin; 1140 else if (Origin == Anchor.TopLeft) 1141 result = Vector2.Zero; 1142 else 1143 result = computeAnchorPosition(LayoutSize, Origin); 1144 1145 return result - new Vector2(margin.Left, margin.Top); 1146 } 1147 1148 set 1149 { 1150 if (customOrigin == value && Origin == Anchor.Custom) 1151 return; 1152 1153 if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(OriginPosition)} must be finite, but is {value}."); 1154 1155 customOrigin = value; 1156 Origin = Anchor.Custom; 1157 1158 Invalidate(Invalidation.MiscGeometry); 1159 } 1160 } 1161 1162 private Anchor anchor = Anchor.TopLeft; 1163 1164 /// <summary> 1165 /// Specifies where <see cref="Origin"/> is attached to the <see cref="Parent"/> 1166 /// in the coordinate system with origin at the top left corner of the 1167 /// <see cref="Parent"/>'s <see cref="DrawRectangle"/>. 1168 /// Can either be one of 9 relative positions (0, 0.5, and 1 in x and y) 1169 /// or a fixed absolute position via <see cref="RelativeAnchorPosition"/>. 1170 /// </summary> 1171 public Anchor Anchor 1172 { 1173 get => anchor; 1174 set 1175 { 1176 if (anchor == value) return; 1177 1178 if (value == 0) 1179 throw new ArgumentException("Cannot set anchor to 0.", nameof(value)); 1180 1181 anchor = value; 1182 Invalidate(Invalidation.MiscGeometry); 1183 } 1184 } 1185 1186 private Vector2 customRelativeAnchorPosition; 1187 1188 /// <summary> 1189 /// Specifies in relative coordinates where <see cref="Origin"/> is attached 1190 /// to the <see cref="Parent"/> in the coordinate system with origin at the top 1191 /// left corner of the <see cref="Parent"/>'s <see cref="DrawRectangle"/>, and 1192 /// a value of <see cref="Vector2.One"/> referring to the bottom right corner of 1193 /// the <see cref="Parent"/>'s <see cref="DrawRectangle"/>. 1194 /// </summary> 1195 public Vector2 RelativeAnchorPosition 1196 { 1197 get 1198 { 1199 if (Anchor == Anchor.Custom) 1200 return customRelativeAnchorPosition; 1201 1202 Vector2 result = Vector2.Zero; 1203 if (anchor.HasFlagFast(Anchor.x1)) 1204 result.X = 0.5f; 1205 else if (anchor.HasFlagFast(Anchor.x2)) 1206 result.X = 1; 1207 1208 if (anchor.HasFlagFast(Anchor.y1)) 1209 result.Y = 0.5f; 1210 else if (anchor.HasFlagFast(Anchor.y2)) 1211 result.Y = 1; 1212 1213 return result; 1214 } 1215 1216 set 1217 { 1218 if (customRelativeAnchorPosition == value && Anchor == Anchor.Custom) 1219 return; 1220 1221 if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(RelativeAnchorPosition)} must be finite, but is {value}."); 1222 1223 customRelativeAnchorPosition = value; 1224 Anchor = Anchor.Custom; 1225 1226 Invalidate(Invalidation.MiscGeometry); 1227 } 1228 } 1229 1230 /// <summary> 1231 /// Specifies in absolute coordinates where <see cref="Origin"/> is attached 1232 /// to the <see cref="Parent"/> in the coordinate system with origin at the top 1233 /// left corner of the <see cref="Parent"/>'s <see cref="DrawRectangle"/>. 1234 /// </summary> 1235 public Vector2 AnchorPosition => RelativeAnchorPosition * Parent?.ChildSize ?? Vector2.Zero; 1236 1237 /// <summary> 1238 /// Helper function to compute an absolute position given an absolute size and 1239 /// a relative <see cref="Graphics.Anchor"/>. 1240 /// </summary> 1241 /// <param name="size">Absolute size</param> 1242 /// <param name="anchor">Relative <see cref="Graphics.Anchor"/></param> 1243 /// <returns>Absolute position</returns> 1244 private static Vector2 computeAnchorPosition(Vector2 size, Anchor anchor) 1245 { 1246 Vector2 result = Vector2.Zero; 1247 1248 if (anchor.HasFlagFast(Anchor.x1)) 1249 result.X = size.X / 2f; 1250 else if (anchor.HasFlagFast(Anchor.x2)) 1251 result.X = size.X; 1252 1253 if (anchor.HasFlagFast(Anchor.y1)) 1254 result.Y = size.Y / 2f; 1255 else if (anchor.HasFlagFast(Anchor.y2)) 1256 result.Y = size.Y; 1257 1258 return result; 1259 } 1260 1261 #endregion 1262 1263 #region Colour / Alpha / Blending 1264 1265 private ColourInfo colour = Color4.White; 1266 1267 /// <summary> 1268 /// Colour of this <see cref="Drawable"/> in sRGB space. Can contain individual colours for all four 1269 /// corners of this <see cref="Drawable"/>, which are then interpolated, but can also be assigned 1270 /// just a single colour. Implicit casts from <see cref="SRGBColour"/> and from <see cref="Color4"/> exist. 1271 /// </summary> 1272 public ColourInfo Colour 1273 { 1274 get => colour; 1275 set 1276 { 1277 if (colour.Equals(value)) return; 1278 1279 colour = value; 1280 1281 Invalidate(Invalidation.Colour); 1282 } 1283 } 1284 1285 private float alpha = 1.0f; 1286 1287 /// <summary> 1288 /// Multiplicative alpha factor applied on top of <see cref="ColourInfo"/> and its existing 1289 /// alpha channel(s). 1290 /// </summary> 1291 public float Alpha 1292 { 1293 get => alpha; 1294 set 1295 { 1296 if (alpha == value) 1297 return; 1298 1299 bool wasPresent = IsPresent; 1300 1301 alpha = value; 1302 1303 if (IsPresent != wasPresent) 1304 Invalidate(Invalidation.Colour | Invalidation.Presence); 1305 else 1306 Invalidate(Invalidation.Colour); 1307 } 1308 } 1309 1310 private const float visibility_cutoff = 0.0001f; 1311 1312 /// <summary> 1313 /// Determines whether this Drawable is present based on its <see cref="Alpha"/> value. 1314 /// Can be forced always on with <see cref="AlwaysPresent"/>. 1315 /// </summary> 1316 public virtual bool IsPresent => AlwaysPresent || Alpha > visibility_cutoff && Math.Abs(Scale.X) > Precision.FLOAT_EPSILON && Math.Abs(Scale.Y) > Precision.FLOAT_EPSILON; 1317 1318 private bool alwaysPresent; 1319 1320 /// <summary> 1321 /// If true, forces <see cref="IsPresent"/> to always be true. In other words, 1322 /// this drawable is always considered for layout, input, and drawing, regardless 1323 /// of alpha value. 1324 /// </summary> 1325 public bool AlwaysPresent 1326 { 1327 get => alwaysPresent; 1328 set 1329 { 1330 if (alwaysPresent == value) 1331 return; 1332 1333 bool wasPresent = IsPresent; 1334 1335 alwaysPresent = value; 1336 1337 if (IsPresent != wasPresent) 1338 Invalidate(Invalidation.Presence); 1339 } 1340 } 1341 1342 private BlendingParameters blending; 1343 1344 /// <summary> 1345 /// Determines how this Drawable is blended with other already drawn Drawables. 1346 /// Inherits the <see cref="Parent"/>'s <see cref="Blending"/> by default. 1347 /// </summary> 1348 public BlendingParameters Blending 1349 { 1350 get => blending; 1351 set 1352 { 1353 if (blending == value) 1354 return; 1355 1356 blending = value; 1357 1358 Invalidate(Invalidation.Colour); 1359 } 1360 } 1361 1362 #endregion 1363 1364 #region Timekeeping 1365 1366 private IFrameBasedClock customClock; 1367 private IFrameBasedClock clock; 1368 1369 /// <summary> 1370 /// The clock of this drawable. Used for keeping track of time across 1371 /// frames. By default is inherited from <see cref="Parent"/>. 1372 /// If set, then the provided value is used as a custom clock and the 1373 /// <see cref="Parent"/>'s clock is ignored. 1374 /// </summary> 1375 public override IFrameBasedClock Clock 1376 { 1377 get => clock; 1378 set 1379 { 1380 customClock = value; 1381 UpdateClock(customClock); 1382 } 1383 } 1384 1385 /// <summary> 1386 /// Updates the clock to be used. Has no effect if this drawable 1387 /// uses a custom clock. 1388 /// </summary> 1389 /// <param name="clock">The new clock to be used.</param> 1390 internal virtual void UpdateClock(IFrameBasedClock clock) 1391 { 1392 this.clock = customClock ?? clock; 1393 scheduler?.UpdateClock(this.clock); 1394 } 1395 1396 /// <summary> 1397 /// Whether <see cref="IFrameBasedClock.ProcessFrame"/> should be automatically invoked on this <see cref="Drawable"/>'s <see cref="Clock"/> 1398 /// in <see cref="UpdateSubTree"/>. This should only be set to false in scenarios where the clock is updated elsewhere. 1399 /// </summary> 1400 public bool ProcessCustomClock = true; 1401 1402 private double lifetimeStart = double.MinValue; 1403 private double lifetimeEnd = double.MaxValue; 1404 1405 /// <summary> 1406 /// Invoked after <see cref="lifetimeStart"/> or <see cref="LifetimeEnd"/> has changed. 1407 /// </summary> 1408 internal event Action<Drawable> LifetimeChanged; 1409 1410 /// <summary> 1411 /// The time at which this drawable becomes valid (and is considered for drawing). 1412 /// </summary> 1413 public virtual double LifetimeStart 1414 { 1415 get => lifetimeStart; 1416 set 1417 { 1418 if (lifetimeStart == value) return; 1419 1420 lifetimeStart = value; 1421 LifetimeChanged?.Invoke(this); 1422 } 1423 } 1424 1425 /// <summary> 1426 /// The time at which this drawable is no longer valid (and is considered for disposal). 1427 /// </summary> 1428 public virtual double LifetimeEnd 1429 { 1430 get => lifetimeEnd; 1431 set 1432 { 1433 if (lifetimeEnd == value) return; 1434 1435 lifetimeEnd = value; 1436 LifetimeChanged?.Invoke(this); 1437 } 1438 } 1439 1440 /// <summary> 1441 /// Whether this drawable should currently be alive. 1442 /// This is queried by the framework to decide the <see cref="IsAlive"/> state of this drawable for the next frame. 1443 /// </summary> 1444 protected internal virtual bool ShouldBeAlive 1445 { 1446 get 1447 { 1448 if (LifetimeStart == double.MinValue && LifetimeEnd == double.MaxValue) 1449 return true; 1450 1451 return Time.Current >= LifetimeStart && Time.Current < LifetimeEnd; 1452 } 1453 } 1454 1455 /// <summary> 1456 /// Whether to remove the drawable from its parent's children when it's not alive. 1457 /// </summary> 1458 public virtual bool RemoveWhenNotAlive => Parent == null || Time.Current > LifetimeStart; 1459 1460 #endregion 1461 1462 #region Parenting (scene graph operations, including ProxyDrawable) 1463 1464 /// <summary> 1465 /// Retrieve the first parent in the tree which derives from <see cref="InputManager"/>. 1466 /// As this is performing an upward tree traversal, avoid calling every frame. 1467 /// </summary> 1468 /// <returns>The first parent <see cref="InputManager"/>.</returns> 1469 protected InputManager GetContainingInputManager() => FindClosestParent<InputManager>(); 1470 1471 private CompositeDrawable parent; 1472 1473 /// <summary> 1474 /// The parent of this drawable in the scene graph. 1475 /// </summary> 1476 public CompositeDrawable Parent 1477 { 1478 get => parent; 1479 internal set 1480 { 1481 if (IsDisposed) 1482 throw new ObjectDisposedException(ToString(), "Disposed Drawables may never get a parent and return to the scene graph."); 1483 1484 if (value == null) 1485 ChildID = 0; 1486 1487 if (parent == value) return; 1488 1489 if (value != null && parent != null) 1490 throw new InvalidOperationException("May not add a drawable to multiple containers."); 1491 1492 parent = value; 1493 Invalidate(InvalidationFromParentSize | Invalidation.Colour | Invalidation.Presence | Invalidation.Parent); 1494 1495 if (parent != null) 1496 { 1497 //we should already have a clock at this point (from our LoadRequested invocation) 1498 //this just ensures we have the most recent parent clock. 1499 //we may want to consider enforcing that parent.Clock == clock here. 1500 UpdateClock(parent.Clock); 1501 } 1502 } 1503 } 1504 1505 /// <summary> 1506 /// Find the closest parent of a specified type. 1507 /// </summary> 1508 /// <remarks> 1509 /// This can be a potentially expensive operation and should be used with discretion. 1510 /// </remarks> 1511 /// <typeparam name="T">The type to match.</typeparam> 1512 /// <returns>The first matching parent, or null if no parent of type <typeparamref name="T"/> is found.</returns> 1513 internal T FindClosestParent<T>() where T : class, IDrawable 1514 { 1515 Drawable cursor = this; 1516 1517 while ((cursor = cursor.Parent) != null) 1518 { 1519 if (cursor is T match) 1520 return match; 1521 } 1522 1523 return default; 1524 } 1525 1526 /// <summary> 1527 /// Refers to the original if this drawable was created via 1528 /// <see cref="CreateProxy"/>. Otherwise refers to this. 1529 /// </summary> 1530 internal virtual Drawable Original => this; 1531 1532 /// <summary> 1533 /// True iff <see cref="CreateProxy"/> has been called before. 1534 /// </summary> 1535 public bool HasProxy => proxy != null; 1536 1537 /// <summary> 1538 /// True iff this <see cref="Drawable"/> is not a proxy of any <see cref="Drawable"/>. 1539 /// </summary> 1540 public bool IsProxy => Original != this; 1541 1542 private Drawable proxy; 1543 1544 /// <summary> 1545 /// Creates a proxy drawable which can be inserted elsewhere in the scene graph. 1546 /// Will cause the original instance to not render itself. 1547 /// Creating multiple proxies is not supported and will result in an 1548 /// <see cref="InvalidOperationException"/>. 1549 /// </summary> 1550 public Drawable CreateProxy() 1551 { 1552 if (proxy != null) 1553 throw new InvalidOperationException("Multiple proxies are not supported."); 1554 1555 return proxy = new ProxyDrawable(this); 1556 } 1557 1558 /// <summary> 1559 /// Validates a <see cref="DrawNode"/> for use by the proxy of this <see cref="Drawable"/>. 1560 /// This is used exclusively by <see cref="CompositeDrawable.addFromComposite"/>, and should not be used otherwise. 1561 /// </summary> 1562 /// <param name="treeIndex">The index of the <see cref="DrawNode"/> in <see cref="drawNodes"/> which the proxy should use.</param> 1563 /// <param name="frame">The frame for which the <see cref="DrawNode"/> was created. This is the parameter used for validation.</param> 1564 internal virtual void ValidateProxyDrawNode(int treeIndex, ulong frame) => proxy.ValidateProxyDrawNode(treeIndex, frame); 1565 1566 #endregion 1567 1568 #region Caching & invalidation (for things too expensive to compute every frame) 1569 1570 /// <summary> 1571 /// Was this Drawable masked away completely during the last frame? 1572 /// This is measured conservatively, i.e. it is only true when the Drawable was 1573 /// actually masked away, but it may be false, even if the Drawable was masked away. 1574 /// </summary> 1575 internal bool IsMaskedAway { get; private set; } 1576 1577 private readonly LayoutValue<Quad> screenSpaceDrawQuadBacking = new LayoutValue<Quad>(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); 1578 1579 protected virtual Quad ComputeScreenSpaceDrawQuad() => ToScreenSpace(DrawRectangle); 1580 1581 /// <summary> 1582 /// The screen-space quad this drawable occupies. 1583 /// </summary> 1584 public virtual Quad ScreenSpaceDrawQuad => screenSpaceDrawQuadBacking.IsValid ? screenSpaceDrawQuadBacking : screenSpaceDrawQuadBacking.Value = ComputeScreenSpaceDrawQuad(); 1585 1586 private readonly LayoutValue<DrawInfo> drawInfoBacking = new LayoutValue<DrawInfo>(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); 1587 1588 private DrawInfo computeDrawInfo() 1589 { 1590 DrawInfo di = Parent?.DrawInfo ?? new DrawInfo(null); 1591 1592 Vector2 pos = DrawPosition + AnchorPosition; 1593 Vector2 drawScale = DrawScale; 1594 1595 if (Parent != null) 1596 pos += Parent.ChildOffset; 1597 1598 di.ApplyTransform(pos, drawScale, Rotation, Shear, OriginPosition); 1599 1600 return di; 1601 } 1602 1603 /// <summary> 1604 /// Contains the linear transformation of this <see cref="Drawable"/> that is used during draw. 1605 /// </summary> 1606 public virtual DrawInfo DrawInfo => drawInfoBacking.IsValid ? drawInfoBacking : drawInfoBacking.Value = computeDrawInfo(); 1607 1608 private readonly LayoutValue<DrawColourInfo> drawColourInfoBacking = new LayoutValue<DrawColourInfo>( 1609 Invalidation.Colour | Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence, 1610 conditions: (s, i) => 1611 { 1612 if ((i & Invalidation.Colour) > 0) 1613 return true; 1614 1615 return !s.Colour.HasSingleColour || (s.drawColourInfoBacking.IsValid && !s.drawColourInfoBacking.Value.Colour.HasSingleColour); 1616 }); 1617 1618 /// <summary> 1619 /// Contains the colour and blending information of this <see cref="Drawable"/> that are used during draw. 1620 /// </summary> 1621 public virtual DrawColourInfo DrawColourInfo => drawColourInfoBacking.IsValid ? drawColourInfoBacking : drawColourInfoBacking.Value = computeDrawColourInfo(); 1622 1623 private DrawColourInfo computeDrawColourInfo() 1624 { 1625 DrawColourInfo ci = Parent?.DrawColourInfo ?? new DrawColourInfo(null); 1626 1627 BlendingParameters localBlending = Blending; 1628 1629 if (Parent != null) 1630 localBlending.CopyFromParent(ci.Blending); 1631 1632 localBlending.ApplyDefaultToInherited(); 1633 1634 ci.Blending = localBlending; 1635 1636 ColourInfo ourColour = alpha != 1 ? colour.MultiplyAlpha(alpha) : colour; 1637 1638 if (ci.Colour.HasSingleColour) 1639 ci.Colour.ApplyChild(ourColour); 1640 else 1641 { 1642 Debug.Assert(Parent != null, 1643 $"The {nameof(ci)} of null parents should always have the single colour white, and therefore this branch should never be hit."); 1644 1645 // Cannot use ToParentSpace here, because ToParentSpace depends on DrawInfo to be completed 1646 // ReSharper disable once PossibleNullReferenceException 1647 Quad interp = Quad.FromRectangle(DrawRectangle) * (DrawInfo.Matrix * Parent.DrawInfo.MatrixInverse); 1648 Vector2 parentSize = Parent.DrawSize; 1649 1650 ci.Colour.ApplyChild(ourColour, 1651 new Quad( 1652 Vector2.Divide(interp.TopLeft, parentSize), 1653 Vector2.Divide(interp.TopRight, parentSize), 1654 Vector2.Divide(interp.BottomLeft, parentSize), 1655 Vector2.Divide(interp.BottomRight, parentSize))); 1656 } 1657 1658 return ci; 1659 } 1660 1661 private readonly LayoutValue<Vector2> requiredParentSizeToFitBacking = new LayoutValue<Vector2>(Invalidation.RequiredParentSizeToFit); 1662 1663 private Vector2 computeRequiredParentSizeToFit() 1664 { 1665 // Auxiliary variables required for the computation 1666 Vector2 ap = AnchorPosition; 1667 Vector2 rap = RelativeAnchorPosition; 1668 1669 Vector2 ratio1 = new Vector2( 1670 rap.X <= 0 ? 0 : 1 / rap.X, 1671 rap.Y <= 0 ? 0 : 1 / rap.Y); 1672 1673 Vector2 ratio2 = new Vector2( 1674 rap.X >= 1 ? 0 : 1 / (1 - rap.X), 1675 rap.Y >= 1 ? 0 : 1 / (1 - rap.Y)); 1676 1677 RectangleF bbox = BoundingBox; 1678 1679 // Compute the required size of the parent such that we fit in snugly when positioned 1680 // at our relative anchor in the parent. 1681 Vector2 topLeftOffset = ap - bbox.TopLeft; 1682 Vector2 topLeftSize1 = topLeftOffset * ratio1; 1683 Vector2 topLeftSize2 = -topLeftOffset * ratio2; 1684 1685 Vector2 bottomRightOffset = ap - bbox.BottomRight; 1686 Vector2 bottomRightSize1 = bottomRightOffset * ratio1; 1687 Vector2 bottomRightSize2 = -bottomRightOffset * ratio2; 1688 1689 // Expand bounds according to clipped offset 1690 return Vector2.ComponentMax( 1691 Vector2.ComponentMax(topLeftSize1, topLeftSize2), 1692 Vector2.ComponentMax(bottomRightSize1, bottomRightSize2)); 1693 } 1694 1695 /// <summary> 1696 /// Returns the size of the smallest axis aligned box in parent space which 1697 /// encompasses this drawable while preserving this drawable's 1698 /// <see cref="RelativeAnchorPosition"/>. 1699 /// If a component of <see cref="RelativeAnchorPosition"/> is smaller than zero 1700 /// or larger than one, then it is impossible to preserve <see cref="RelativeAnchorPosition"/> 1701 /// while fitting into the parent, and thus <see cref="RelativeAnchorPosition"/> returns 1702 /// zero in that dimension; i.e. we no longer fit into the parent. 1703 /// This behavior is prominent with non-centre and non-custom <see cref="Anchor"/> values. 1704 /// </summary> 1705 internal Vector2 RequiredParentSizeToFit => requiredParentSizeToFitBacking.IsValid ? requiredParentSizeToFitBacking : requiredParentSizeToFitBacking.Value = computeRequiredParentSizeToFit(); 1706 1707 /// <summary> 1708 /// The flags which this <see cref="Drawable"/> has been invalidated with, grouped by <see cref="InvalidationSource"/>. 1709 /// </summary> 1710 private InvalidationList invalidationList = new InvalidationList(Invalidation.All); 1711 1712 private readonly List<LayoutMember> layoutMembers = new List<LayoutMember>(); 1713 1714 /// <summary> 1715 /// Adds a layout member that will be invalidated when its <see cref="LayoutMember.Invalidation"/> is invalidated. 1716 /// </summary> 1717 /// <param name="member">The layout member to add.</param> 1718 protected void AddLayout(LayoutMember member) 1719 { 1720 if (LoadState > LoadState.NotLoaded) 1721 throw new InvalidOperationException($"{nameof(LayoutMember)}s cannot be added after {nameof(Drawable)}s have started loading. Consider adding in the constructor."); 1722 1723 layoutMembers.Add(member); 1724 member.Parent = this; 1725 } 1726 1727 /// <summary> 1728 /// Validates the super-tree of this <see cref="Drawable"/> for the given <see cref="Invalidation"/> flags. 1729 /// </summary> 1730 /// <remarks> 1731 /// This is internally invoked by <see cref="LayoutMember"/>, and should not be invoked manually. 1732 /// </remarks> 1733 /// <param name="validationType">The <see cref="Invalidation"/> flags to validate with.</param> 1734 internal void ValidateSuperTree(Invalidation validationType) 1735 { 1736 if (invalidationList.Validate(validationType)) 1737 Parent?.ValidateSuperTree(validationType); 1738 } 1739 1740 // Make sure we start out with a value of 1 such that ApplyDrawNode is always called at least once 1741 public long InvalidationID { get; private set; } = 1; 1742 1743 /// <summary> 1744 /// Invalidates the layout of this <see cref="Drawable"/>. 1745 /// </summary> 1746 /// <param name="invalidation">The flags to invalidate with.</param> 1747 /// <param name="source">The source that triggered the invalidation.</param> 1748 /// <returns>If any layout was invalidated.</returns> 1749 public bool Invalidate(Invalidation invalidation = Invalidation.All, InvalidationSource source = InvalidationSource.Self) => invalidate(invalidation, source); 1750 1751 /// <summary> 1752 /// Invalidates the layout of this <see cref="Drawable"/>. 1753 /// </summary> 1754 /// <param name="invalidation">The flags to invalidate with.</param> 1755 /// <param name="source">The source that triggered the invalidation.</param> 1756 /// <param name="propagateToParent">Whether to propagate the invalidation to the parent of this <see cref="Drawable"/>. 1757 /// Only has an effect if <paramref name="source"/> is <see cref="InvalidationSource.Self"/>.</param> 1758 /// <returns>If any layout was invalidated.</returns> 1759 private bool invalidate(Invalidation invalidation = Invalidation.All, InvalidationSource source = InvalidationSource.Self, bool propagateToParent = true) 1760 { 1761 if (source != InvalidationSource.Child && source != InvalidationSource.Parent && source != InvalidationSource.Self) 1762 throw new InvalidOperationException($"A {nameof(Drawable)} can only be invalidated with a singular {nameof(source)} (child, parent, or self)."); 1763 1764 if (LoadState < LoadState.Ready) 1765 return false; 1766 1767 // Changes in the colour of children don't affect parents. 1768 if (source == InvalidationSource.Child) 1769 invalidation &= ~Invalidation.Colour; 1770 1771 if (invalidation == Invalidation.None) 1772 return false; 1773 1774 // If the invalidation originated locally, propagate to the immediate parent. 1775 // Note: This is done _before_ invalidation is blocked below, since the parent always needs to be aware of changes even if the Drawable's invalidation state hasn't changed. 1776 // This is for only propagating once, otherwise it would propagate all the way to the root Drawable. 1777 if (propagateToParent && source == InvalidationSource.Self) 1778 Parent?.Invalidate(invalidation, InvalidationSource.Child); 1779 1780 // Perform the invalidation. 1781 if (!invalidationList.Invalidate(source, invalidation)) 1782 return false; 1783 1784 // A DrawNode invalidation always invalidates. 1785 bool anyInvalidated = (invalidation & Invalidation.DrawNode) > 0; 1786 1787 // Invalidate all layout members 1788 foreach (var member in layoutMembers) 1789 { 1790 // Only invalidate layout members that accept the given source. 1791 if ((member.Source & source) == 0) 1792 continue; 1793 1794 // Remove invalidation flags that don't refer to the layout member. 1795 Invalidation memberInvalidation = invalidation & member.Invalidation; 1796 if (memberInvalidation == 0) 1797 continue; 1798 1799 if (member.Conditions?.Invoke(this, memberInvalidation) != false) 1800 anyInvalidated |= member.Invalidate(); 1801 } 1802 1803 // Allow any custom invalidation to take place. 1804 anyInvalidated |= OnInvalidate(invalidation, source); 1805 1806 if (anyInvalidated) 1807 InvalidationID++; 1808 1809 Invalidated?.Invoke(this); 1810 1811 return anyInvalidated; 1812 } 1813 1814 /// <summary> 1815 /// Invoked when the layout of this <see cref="Drawable"/> was invalidated. 1816 /// </summary> 1817 /// <remarks> 1818 /// This should be used to perform any custom invalidation logic that cannot be described as a layout. 1819 /// </remarks> 1820 /// <remarks> 1821 /// This does not ensure that the parent containers have been updated before us, thus operations involving 1822 /// parent states (e.g. <see cref="DrawInfo"/>) should not be executed in an overridden implementation. 1823 /// </remarks> 1824 /// <param name="invalidation">The flags that the this <see cref="Drawable"/> was invalidated with.</param> 1825 /// <param name="source">The source that triggered the invalidation.</param> 1826 /// <returns>Whether any custom invalidation was performed. Must be true if the <see cref="DrawNode"/> for this <see cref="Drawable"/> is to be invalidated.</returns> 1827 protected virtual bool OnInvalidate(Invalidation invalidation, InvalidationSource source) => false; 1828 1829 public Invalidation InvalidationFromParentSize 1830 { 1831 get 1832 { 1833 Invalidation result = Invalidation.DrawInfo; 1834 if (RelativeSizeAxes != Axes.None) 1835 result |= Invalidation.DrawSize; 1836 if (RelativePositionAxes != Axes.None) 1837 result |= Invalidation.MiscGeometry; 1838 return result; 1839 } 1840 } 1841 1842 /// <summary> 1843 /// A fast path for invalidating ourselves and our parent's children size dependencies whenever a size or position change occurs. 1844 /// </summary> 1845 /// <param name="invalidation">The <see cref="Invalidation"/> to invalidate with.</param> 1846 /// <param name="changedAxes">The <see cref="Axes"/> that were affected.</param> 1847 private void invalidateParentSizeDependencies(Invalidation invalidation, Axes changedAxes) 1848 { 1849 // We're invalidating the parent manually, so we should not propagate it upwards. 1850 invalidate(invalidation, InvalidationSource.Self, false); 1851 1852 // The fast path, which performs an invalidation on the parent along with optimisations for bypassed sizing axes. 1853 Parent?.InvalidateChildrenSizeDependencies(invalidation, changedAxes, this); 1854 } 1855 1856 #endregion 1857 1858 #region DrawNode 1859 1860 private readonly DrawNode[] drawNodes = new DrawNode[GLWrapper.MAX_DRAW_NODES]; 1861 1862 /// <summary> 1863 /// Generates the <see cref="DrawNode"/> for ourselves. 1864 /// </summary> 1865 /// <param name="frame">The frame which the <see cref="DrawNode"/> sub-tree should be generated for.</param> 1866 /// <param name="treeIndex">The index of the <see cref="DrawNode"/> to use.</param> 1867 /// <param name="forceNewDrawNode">Whether the creation of a new <see cref="DrawNode"/> should be forced, rather than re-using an existing <see cref="DrawNode"/>.</param> 1868 /// <returns>A complete and updated <see cref="DrawNode"/>, or null if the <see cref="DrawNode"/> would be invisible.</returns> 1869 internal virtual DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, bool forceNewDrawNode) 1870 { 1871 DrawNode node = drawNodes[treeIndex]; 1872 1873 if (node == null || forceNewDrawNode) 1874 { 1875 drawNodes[treeIndex] = node = CreateDrawNode(); 1876 FrameStatistics.Increment(StatisticsCounterType.DrawNodeCtor); 1877 } 1878 1879 if (InvalidationID != node.InvalidationID) 1880 { 1881 node.ApplyState(); 1882 FrameStatistics.Increment(StatisticsCounterType.DrawNodeAppl); 1883 } 1884 1885 return node; 1886 } 1887 1888 /// <summary> 1889 /// Creates a draw node capable of containing all information required to draw this drawable. 1890 /// </summary> 1891 /// <returns>The created draw node.</returns> 1892 protected virtual DrawNode CreateDrawNode() => new DrawNode(this); 1893 1894 #endregion 1895 1896 #region DrawInfo-based coordinate system conversions 1897 1898 /// <summary> 1899 /// Accepts a vector in local coordinates and converts it to coordinates in another Drawable's space. 1900 /// </summary> 1901 /// <param name="input">A vector in local coordinates.</param> 1902 /// <param name="other">The drawable in which space we want to transform the vector to.</param> 1903 /// <returns>The vector in other's coordinates.</returns> 1904 public Vector2 ToSpaceOfOtherDrawable(Vector2 input, IDrawable other) 1905 { 1906 if (other == this) 1907 return input; 1908 1909 return Vector2Extensions.Transform(Vector2Extensions.Transform(input, DrawInfo.Matrix), other.DrawInfo.MatrixInverse); 1910 } 1911 1912 /// <summary> 1913 /// Accepts a rectangle in local coordinates and converts it to coordinates in another Drawable's space. 1914 /// </summary> 1915 /// <param name="input">A rectangle in local coordinates.</param> 1916 /// <param name="other">The drawable in which space we want to transform the rectangle to.</param> 1917 /// <returns>The rectangle in other's coordinates.</returns> 1918 public Quad ToSpaceOfOtherDrawable(RectangleF input, IDrawable other) 1919 { 1920 if (other == this) 1921 return input; 1922 1923 return Quad.FromRectangle(input) * (DrawInfo.Matrix * other.DrawInfo.MatrixInverse); 1924 } 1925 1926 /// <summary> 1927 /// Accepts a vector in local coordinates and converts it to coordinates in Parent's space. 1928 /// </summary> 1929 /// <param name="input">A vector in local coordinates.</param> 1930 /// <returns>The vector in Parent's coordinates.</returns> 1931 public Vector2 ToParentSpace(Vector2 input) => ToSpaceOfOtherDrawable(input, Parent); 1932 1933 /// <summary> 1934 /// Accepts a rectangle in local coordinates and converts it to a quad in Parent's space. 1935 /// </summary> 1936 /// <param name="input">A rectangle in local coordinates.</param> 1937 /// <returns>The quad in Parent's coordinates.</returns> 1938 public Quad ToParentSpace(RectangleF input) => ToSpaceOfOtherDrawable(input, Parent); 1939 1940 /// <summary> 1941 /// Accepts a vector in local coordinates and converts it to coordinates in screen space. 1942 /// </summary> 1943 /// <param name="input">A vector in local coordinates.</param> 1944 /// <returns>The vector in screen coordinates.</returns> 1945 public Vector2 ToScreenSpace(Vector2 input) => Vector2Extensions.Transform(input, DrawInfo.Matrix); 1946 1947 /// <summary> 1948 /// Accepts a rectangle in local coordinates and converts it to a quad in screen space. 1949 /// </summary> 1950 /// <param name="input">A rectangle in local coordinates.</param> 1951 /// <returns>The quad in screen coordinates.</returns> 1952 public Quad ToScreenSpace(RectangleF input) => Quad.FromRectangle(input) * DrawInfo.Matrix; 1953 1954 /// <summary> 1955 /// Accepts a vector in screen coordinates and converts it to coordinates in local space. 1956 /// </summary> 1957 /// <param name="screenSpacePos">A vector in screen coordinates.</param> 1958 /// <returns>The vector in local coordinates.</returns> 1959 public Vector2 ToLocalSpace(Vector2 screenSpacePos) => Vector2Extensions.Transform(screenSpacePos, DrawInfo.MatrixInverse); 1960 1961 /// <summary> 1962 /// Accepts a quad in screen coordinates and converts it to coordinates in local space. 1963 /// </summary> 1964 /// <param name="screenSpaceQuad">A quad in screen coordinates.</param> 1965 /// <returns>The quad in local coordinates.</returns> 1966 public Quad ToLocalSpace(Quad screenSpaceQuad) => screenSpaceQuad * DrawInfo.MatrixInverse; 1967 1968 #endregion 1969 1970 #region Interaction / Input 1971 1972 /// <summary> 1973 /// Handle a UI event. 1974 /// </summary> 1975 /// <param name="e">The event to be handled.</param> 1976 /// <returns>If the event supports blocking, returning true will make the event to not propagating further.</returns> 1977 protected virtual bool Handle(UIEvent e) => false; 1978 1979 /// <summary> 1980 /// Trigger a UI event with <see cref="UIEvent.Target"/> set to this <see cref="Drawable"/>. 1981 /// </summary> 1982 /// <param name="e">The event. Its <see cref="UIEvent.Target"/> will be modified.</param> 1983 /// <returns>The result of event handler.</returns> 1984 public bool TriggerEvent(UIEvent e) 1985 { 1986 e.Target = this; 1987 1988 switch (e) 1989 { 1990 case MouseMoveEvent mouseMove: 1991 return OnMouseMove(mouseMove); 1992 1993 case HoverEvent hover: 1994 return OnHover(hover); 1995 1996 case HoverLostEvent hoverLost: 1997 OnHoverLost(hoverLost); 1998 return false; 1999 2000 case MouseDownEvent mouseDown: 2001 return OnMouseDown(mouseDown); 2002 2003 case MouseUpEvent mouseUp: 2004 OnMouseUp(mouseUp); 2005 return false; 2006 2007 case ClickEvent click: 2008 return OnClick(click); 2009 2010 case DoubleClickEvent doubleClick: 2011 return OnDoubleClick(doubleClick); 2012 2013 case DragStartEvent dragStart: 2014 return OnDragStart(dragStart); 2015 2016 case DragEvent drag: 2017 OnDrag(drag); 2018 return false; 2019 2020 case DragEndEvent dragEnd: 2021 OnDragEnd(dragEnd); 2022 return false; 2023 2024 case ScrollEvent scroll: 2025 return OnScroll(scroll); 2026 2027 case FocusEvent focus: 2028 OnFocus(focus); 2029 return false; 2030 2031 case FocusLostEvent focusLost: 2032 OnFocusLost(focusLost); 2033 return false; 2034 2035 case KeyDownEvent keyDown: 2036 return OnKeyDown(keyDown); 2037 2038 case KeyUpEvent keyUp: 2039 OnKeyUp(keyUp); 2040 return false; 2041 2042 case TouchDownEvent touchDown: 2043 return OnTouchDown(touchDown); 2044 2045 case TouchMoveEvent touchMove: 2046 OnTouchMove(touchMove); 2047 return false; 2048 2049 case TouchUpEvent touchUp: 2050 OnTouchUp(touchUp); 2051 return false; 2052 2053 case JoystickPressEvent joystickPress: 2054 return OnJoystickPress(joystickPress); 2055 2056 case JoystickReleaseEvent joystickRelease: 2057 OnJoystickRelease(joystickRelease); 2058 return false; 2059 2060 case JoystickAxisMoveEvent joystickAxisMove: 2061 return OnJoystickAxisMove(joystickAxisMove); 2062 2063 case MidiDownEvent midiDown: 2064 return OnMidiDown(midiDown); 2065 2066 case MidiUpEvent midiUp: 2067 OnMidiUp(midiUp); 2068 return false; 2069 2070 case TabletPenButtonPressEvent tabletPenButtonPress: 2071 return OnTabletPenButtonPress(tabletPenButtonPress); 2072 2073 case TabletPenButtonReleaseEvent tabletPenButtonRelease: 2074 OnTabletPenButtonRelease(tabletPenButtonRelease); 2075 return false; 2076 2077 case TabletAuxiliaryButtonPressEvent tabletAuxiliaryButtonPress: 2078 return OnTabletAuxiliaryButtonPress(tabletAuxiliaryButtonPress); 2079 2080 case TabletAuxiliaryButtonReleaseEvent tabletAuxiliaryButtonRelease: 2081 OnTabletAuxiliaryButtonRelease(tabletAuxiliaryButtonRelease); 2082 return false; 2083 2084 default: 2085 return Handle(e); 2086 } 2087 } 2088 2089 [Obsolete("Use TriggerClick instead.")] // Can be removed 20220203 2090 public bool Click() => TriggerClick(); 2091 2092 /// <summary> 2093 /// Triggers a left click event for this <see cref="Drawable"/>. 2094 /// </summary> 2095 /// <returns>Whether the click event is handled.</returns> 2096 public bool TriggerClick() => TriggerEvent(new ClickEvent(GetContainingInputManager()?.CurrentState ?? new InputState(), MouseButton.Left)); 2097 2098 #region Individual event handlers 2099 2100 /// <summary> 2101 /// An event that occurs every time the mouse is moved while hovering this <see cref="Drawable"/>. 2102 /// </summary> 2103 /// <param name="e">The <see cref="MouseMoveEvent"/> containing information about the input event.</param> 2104 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2105 protected virtual bool OnMouseMove(MouseMoveEvent e) => Handle(e); 2106 2107 /// <summary> 2108 /// An event that occurs when the mouse starts hovering this <see cref="Drawable"/>. 2109 /// </summary> 2110 /// <param name="e">The <see cref="HoverEvent"/> containing information about the input event.</param> 2111 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2112 protected virtual bool OnHover(HoverEvent e) => Handle(e); 2113 2114 /// <summary> 2115 /// An event that occurs when the mouse stops hovering this <see cref="Drawable"/>. 2116 /// </summary> 2117 /// <remarks> 2118 /// This is guaranteed to be invoked if <see cref="OnHover"/> was invoked. 2119 /// </remarks> 2120 /// <param name="e">The <see cref="HoverLostEvent"/> containing information about the input event.</param> 2121 protected virtual void OnHoverLost(HoverLostEvent e) => Handle(e); 2122 2123 /// <summary> 2124 /// An event that occurs when a <see cref="MouseButton"/> is pressed on this <see cref="Drawable"/>. 2125 /// </summary> 2126 /// <param name="e">The <see cref="MouseDownEvent"/> containing information about the input event.</param> 2127 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2128 protected virtual bool OnMouseDown(MouseDownEvent e) => Handle(e); 2129 2130 /// <summary> 2131 /// An event that occurs when a <see cref="MouseButton"/> that was pressed on this <see cref="Drawable"/> is released. 2132 /// </summary> 2133 /// <remarks> 2134 /// This is guaranteed to be invoked if <see cref="OnMouseDown"/> was invoked. 2135 /// </remarks> 2136 /// <param name="e">The <see cref="MouseUpEvent"/> containing information about the input event.</param> 2137 protected virtual void OnMouseUp(MouseUpEvent e) => Handle(e); 2138 2139 /// <summary> 2140 /// An event that occurs when a <see cref="MouseButton"/> is clicked on this <see cref="Drawable"/>. 2141 /// </summary> 2142 /// <remarks> 2143 /// This will only be invoked on the <see cref="Drawable"/>s that received a previous <see cref="OnMouseDown"/> invocation 2144 /// which are still present in the input queue (via <see cref="BuildPositionalInputQueue"/>) when the click occurs.<br /> 2145 /// This will not occur if a drag was started (<see cref="OnDragStart"/> was invoked) or a double-click occurred (<see cref="OnDoubleClick"/> was invoked). 2146 /// </remarks> 2147 /// <param name="e">The <see cref="ClickEvent"/> containing information about the input event.</param> 2148 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2149 protected virtual bool OnClick(ClickEvent e) => Handle(e); 2150 2151 /// <summary> 2152 /// An event that occurs when a <see cref="MouseButton"/> is double-clicked on this <see cref="Drawable"/>. 2153 /// </summary> 2154 /// <remarks> 2155 /// This will only be invoked on the <see cref="Drawable"/> that returned <code>true</code> from a previous <see cref="OnClick"/> invocation. 2156 /// </remarks> 2157 /// <param name="e">The <see cref="DoubleClickEvent"/> containing information about the input event.</param> 2158 /// <returns>Whether to block the next <see cref="OnClick"/> event from occurring.</returns> 2159 protected virtual bool OnDoubleClick(DoubleClickEvent e) => Handle(e); 2160 2161 /// <summary> 2162 /// An event that occurs when the mouse starts dragging on this <see cref="Drawable"/>. 2163 /// </summary> 2164 /// <param name="e">The <see cref="DragEvent"/> containing information about the input event.</param> 2165 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2166 protected virtual bool OnDragStart(DragStartEvent e) => Handle(e); 2167 2168 /// <summary> 2169 /// An event that occurs every time the mouse moves while dragging this <see cref="Drawable"/>. 2170 /// </summary> 2171 /// <remarks> 2172 /// This will only be invoked on the <see cref="Drawable"/> that returned <code>true</code> from a previous <see cref="OnDragStart"/> invocation. 2173 /// </remarks> 2174 /// <param name="e">The <see cref="DragEvent"/> containing information about the input event.</param> 2175 protected virtual void OnDrag(DragEvent e) => Handle(e); 2176 2177 /// <summary> 2178 /// An event that occurs when the mouse stops dragging this <see cref="Drawable"/>. 2179 /// </summary> 2180 /// <remarks> 2181 /// This will only be invoked on the <see cref="Drawable"/> that returned <code>true</code> from a previous <see cref="OnDragStart"/> invocation. 2182 /// </remarks> 2183 /// <param name="e">The <see cref="DragEndEvent"/> containing information about the input event.</param> 2184 protected virtual void OnDragEnd(DragEndEvent e) => Handle(e); 2185 2186 /// <summary> 2187 /// An event that occurs when the mouse wheel is scrolled on this <see cref="Drawable"/>. 2188 /// </summary> 2189 /// <param name="e">The <see cref="ScrollEvent"/> containing information about the input event.</param> 2190 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2191 protected virtual bool OnScroll(ScrollEvent e) => Handle(e); 2192 2193 /// <summary> 2194 /// An event that occurs when this <see cref="Drawable"/> gains focus. 2195 /// </summary> 2196 /// <remarks> 2197 /// This will only be invoked on the <see cref="Drawable"/> that returned <code>true</code> from both <see cref="AcceptsFocus"/> and a previous <see cref="OnClick"/> invocation. 2198 /// </remarks> 2199 /// <param name="e">The <see cref="FocusEvent"/> containing information about the input event.</param> 2200 protected virtual void OnFocus(FocusEvent e) => Handle(e); 2201 2202 /// <summary> 2203 /// An event that occurs when this <see cref="Drawable"/> loses focus. 2204 /// </summary> 2205 /// <remarks> 2206 /// This will only be invoked on the <see cref="Drawable"/> that previously had focus (<see cref="OnFocus"/> was invoked). 2207 /// </remarks> 2208 /// <param name="e">The <see cref="FocusLostEvent"/> containing information about the input event.</param> 2209 protected virtual void OnFocusLost(FocusLostEvent e) => Handle(e); 2210 2211 /// <summary> 2212 /// An event that occurs when a <see cref="Key"/> is pressed. 2213 /// </summary> 2214 /// <remarks> 2215 /// Repeat events can only be invoked on the <see cref="Drawable"/>s that received a previous non-repeat <see cref="OnKeyDown"/> invocation 2216 /// which are still present in the input queue (via <see cref="BuildNonPositionalInputQueue"/>) when the repeat occurs. 2217 /// </remarks> 2218 /// <param name="e">The <see cref="KeyDownEvent"/> containing information about the input event.</param> 2219 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2220 protected virtual bool OnKeyDown(KeyDownEvent e) => Handle(e); 2221 2222 /// <summary> 2223 /// An event that occurs when a <see cref="Key"/> is released. 2224 /// </summary> 2225 /// <remarks> 2226 /// This is guaranteed to be invoked if <see cref="OnKeyDown"/> was invoked. 2227 /// </remarks> 2228 /// <param name="e">The <see cref="KeyUpEvent"/> containing information about the input event.</param> 2229 protected virtual void OnKeyUp(KeyUpEvent e) => Handle(e); 2230 2231 /// <summary> 2232 /// An event that occurs when a <see cref="Touch"/> is active. 2233 /// </summary> 2234 /// <param name="e">The <see cref="TouchDownEvent"/> containing information about the input event.</param> 2235 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2236 protected virtual bool OnTouchDown(TouchDownEvent e) => Handle(e); 2237 2238 /// <summary> 2239 /// An event that occurs every time an active <see cref="Touch"/> has moved while hovering this <see cref="Drawable"/>. 2240 /// </summary> 2241 /// <remarks> 2242 /// This will only be invoked on the <see cref="Drawable"/>s that received a previous <see cref="OnTouchDown"/> invocation from the source of this touch. 2243 /// This will not occur if the touch has been activated then deactivated without moving from its initial position. 2244 /// </remarks> 2245 /// <param name="e">The <see cref="TouchMoveEvent"/> containing information about the input event.</param> 2246 protected virtual void OnTouchMove(TouchMoveEvent e) => Handle(e); 2247 2248 /// <summary> 2249 /// An event that occurs when a <see cref="Touch"/> is not active. 2250 /// </summary> 2251 /// <remarks> 2252 /// This is guaranteed to be invoked if <see cref="OnTouchDown"/> was invoked. 2253 /// </remarks> 2254 /// <param name="e">The <see cref="TouchUpEvent"/> containing information about the input event.</param> 2255 protected virtual void OnTouchUp(TouchUpEvent e) => Handle(e); 2256 2257 /// <summary> 2258 /// An event that occurs when a <see cref="JoystickButton"/> is pressed. 2259 /// </summary> 2260 /// <param name="e">The <see cref="JoystickPressEvent"/> containing information about the input event.</param> 2261 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2262 protected virtual bool OnJoystickPress(JoystickPressEvent e) => Handle(e); 2263 2264 /// <summary> 2265 /// An event that occurs when a <see cref="JoystickButton"/> is released. 2266 /// </summary> 2267 /// <remarks> 2268 /// This is guaranteed to be invoked if <see cref="OnJoystickPress"/> was invoked. 2269 /// </remarks> 2270 /// <param name="e">The <see cref="JoystickReleaseEvent"/> containing information about the input event.</param> 2271 protected virtual void OnJoystickRelease(JoystickReleaseEvent e) => Handle(e); 2272 2273 /// <summary> 2274 /// An event that occurs when a <see cref="JoystickAxis"/> is moved. 2275 /// </summary> 2276 /// <param name="e">The <see cref="JoystickAxisMoveEvent"/> containing information about the input event.</param> 2277 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2278 protected virtual bool OnJoystickAxisMove(JoystickAxisMoveEvent e) => Handle(e); 2279 2280 /// <summary> 2281 /// An event that occurs when a <see cref="MidiKey"/> is pressed. 2282 /// </summary> 2283 /// <param name="e">The <see cref="MidiDownEvent"/> containing information about the input event.</param> 2284 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2285 protected virtual bool OnMidiDown(MidiDownEvent e) => Handle(e); 2286 2287 /// <summary> 2288 /// An event that occurs when a <see cref="MidiKey"/> is released. 2289 /// </summary> 2290 /// <remarks> 2291 /// This is guaranteed to be invoked if <see cref="MidiDownEvent"/> was invoked. 2292 /// </remarks> 2293 /// <param name="e">The <see cref="MidiUpEvent"/> containing information about the input event.</param> 2294 protected virtual void OnMidiUp(MidiUpEvent e) => Handle(e); 2295 2296 /// <summary> 2297 /// An event that occurs when a <see cref="TabletPenButton"/> is pressed. 2298 /// </summary> 2299 /// <param name="e">The <see cref="TabletPenButtonPressEvent"/> containing information about the input event.</param> 2300 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2301 protected virtual bool OnTabletPenButtonPress(TabletPenButtonPressEvent e) => Handle(e); 2302 2303 /// <summary> 2304 /// An event that occurs when a <see cref="TabletPenButton"/> is released. 2305 /// </summary> 2306 /// <remarks> 2307 /// This is guaranteed to be invoked if <see cref="OnTabletPenButtonPress"/> was invoked. 2308 /// </remarks> 2309 /// <param name="e">The <see cref="TabletPenButtonReleaseEvent"/> containing information about the input event.</param> 2310 protected virtual void OnTabletPenButtonRelease(TabletPenButtonReleaseEvent e) => Handle(e); 2311 2312 /// <summary> 2313 /// An event that occurs when a <see cref="TabletAuxiliaryButton"/> is pressed. 2314 /// </summary> 2315 /// <param name="e">The <see cref="TabletAuxiliaryButtonPressEvent"/> containing information about the input event.</param> 2316 /// <returns>Whether to block the event from propagating to other <see cref="Drawable"/>s in the hierarchy.</returns> 2317 protected virtual bool OnTabletAuxiliaryButtonPress(TabletAuxiliaryButtonPressEvent e) => Handle(e); 2318 2319 /// <summary> 2320 /// An event that occurs when a <see cref="TabletAuxiliaryButton"/> is released. 2321 /// </summary> 2322 /// <remarks> 2323 /// This is guaranteed to be invoked if <see cref="OnTabletAuxiliaryButtonPress"/> was invoked. 2324 /// </remarks> 2325 /// <param name="e">The <see cref="TabletAuxiliaryButtonReleaseEvent"/> containing information about the input event.</param> 2326 protected virtual void OnTabletAuxiliaryButtonRelease(TabletAuxiliaryButtonReleaseEvent e) => Handle(e); 2327 2328 #endregion 2329 2330 /// <summary> 2331 /// Whether this drawable should receive non-positional input. This does not mean that the drawable will immediately handle the received input, but that it may handle it at some point. 2332 /// </summary> 2333 internal bool RequestsNonPositionalInput { get; private set; } 2334 2335 /// <summary> 2336 /// Whether this drawable should receive positional input. This does not mean that the drawable will immediately handle the received input, but that it may handle it at some point. 2337 /// </summary> 2338 internal bool RequestsPositionalInput { get; private set; } 2339 2340 /// <summary> 2341 /// Conservatively approximates whether there is a descendant which <see cref="RequestsNonPositionalInput"/> in the sub-tree rooted at this drawable 2342 /// to enable sub-tree skipping optimization for input handling. 2343 /// </summary> 2344 internal bool RequestsNonPositionalInputSubTree; 2345 2346 /// <summary> 2347 /// Conservatively approximates whether there is a descendant which <see cref="RequestsPositionalInput"/> in the sub-tree rooted at this drawable 2348 /// to enable sub-tree skipping optimization for input handling. 2349 /// </summary> 2350 internal bool RequestsPositionalInputSubTree; 2351 2352 /// <summary> 2353 /// Whether this <see cref="Drawable"/> handles non-positional input. 2354 /// This value is true by default if <see cref="Handle"/> or any non-positional (e.g. keyboard related) "On-" input methods are overridden. 2355 /// </summary> 2356 public virtual bool HandleNonPositionalInput => RequestsNonPositionalInput; 2357 2358 /// <summary> 2359 /// Whether this <see cref="Drawable"/> handles positional input. 2360 /// This value is true by default if <see cref="Handle"/> or any positional (i.e. mouse related) "On-" input methods are overridden. 2361 /// </summary> 2362 public virtual bool HandlePositionalInput => RequestsPositionalInput; 2363 2364 /// <summary> 2365 /// Nested class which is used for caching <see cref="Drawable.HandleNonPositionalInput"/>, <see cref="Drawable.HandlePositionalInput"/> values obtained via reflection. 2366 /// </summary> 2367 private static class HandleInputCache 2368 { 2369 private static readonly ConcurrentDictionary<Type, bool> positional_cached_values = new ConcurrentDictionary<Type, bool>(); 2370 private static readonly ConcurrentDictionary<Type, bool> non_positional_cached_values = new ConcurrentDictionary<Type, bool>(); 2371 2372 private static readonly string[] positional_input_methods = 2373 { 2374 nameof(Handle), 2375 nameof(OnMouseMove), 2376 nameof(OnHover), 2377 nameof(OnHoverLost), 2378 nameof(OnMouseDown), 2379 nameof(OnMouseUp), 2380 nameof(OnClick), 2381 nameof(OnDoubleClick), 2382 nameof(OnDragStart), 2383 nameof(OnDrag), 2384 nameof(OnDragEnd), 2385 nameof(OnScroll), 2386 nameof(OnFocus), 2387 nameof(OnFocusLost), 2388 nameof(OnTouchDown), 2389 nameof(OnTouchMove), 2390 nameof(OnTouchUp), 2391 nameof(OnTabletPenButtonPress), 2392 nameof(OnTabletPenButtonRelease) 2393 }; 2394 2395 private static readonly string[] non_positional_input_methods = 2396 { 2397 nameof(Handle), 2398 nameof(OnFocus), 2399 nameof(OnFocusLost), 2400 nameof(OnKeyDown), 2401 nameof(OnKeyUp), 2402 nameof(OnJoystickPress), 2403 nameof(OnJoystickRelease), 2404 nameof(OnJoystickAxisMove), 2405 nameof(OnTabletAuxiliaryButtonPress), 2406 nameof(OnTabletAuxiliaryButtonRelease), 2407 nameof(OnMidiDown), 2408 nameof(OnMidiUp) 2409 }; 2410 2411 private static readonly Type[] positional_input_interfaces = 2412 { 2413 typeof(IHasTooltip), 2414 typeof(IHasCustomTooltip), 2415 typeof(IHasContextMenu), 2416 typeof(IHasPopover), 2417 }; 2418 2419 private static readonly Type[] non_positional_input_interfaces = 2420 { 2421 typeof(IKeyBindingHandler), 2422 }; 2423 2424 private static readonly string[] positional_input_properties = 2425 { 2426 nameof(HandlePositionalInput), 2427 }; 2428 2429 private static readonly string[] non_positional_input_properties = 2430 { 2431 nameof(HandleNonPositionalInput), 2432 nameof(AcceptsFocus), 2433 }; 2434 2435 public static bool RequestsNonPositionalInput(Drawable drawable) => get(drawable, non_positional_cached_values, false); 2436 2437 public static bool RequestsPositionalInput(Drawable drawable) => get(drawable, positional_cached_values, true); 2438 2439 private static bool get(Drawable drawable, ConcurrentDictionary<Type, bool> cache, bool positional) 2440 { 2441 var type = drawable.GetType(); 2442 2443 if (!cache.TryGetValue(type, out var value)) 2444 { 2445 value = compute(type, positional); 2446 cache.TryAdd(type, value); 2447 } 2448 2449 return value; 2450 } 2451 2452 private static bool compute([NotNull] Type type, bool positional) 2453 { 2454 var inputMethods = positional ? positional_input_methods : non_positional_input_methods; 2455 2456 foreach (var inputMethod in inputMethods) 2457 { 2458 // check for any input method overrides which are at a higher level than drawable. 2459 var method = type.GetMethod(inputMethod, BindingFlags.Instance | BindingFlags.NonPublic); 2460 2461 Debug.Assert(method != null); 2462 2463 if (method.DeclaringType != typeof(Drawable)) 2464 return true; 2465 } 2466 2467 var inputInterfaces = positional ? positional_input_interfaces : non_positional_input_interfaces; 2468 2469 foreach (var inputInterface in inputInterfaces) 2470 { 2471 // check if this type implements any interface which requires a drawable to handle input. 2472 if (inputInterface.IsAssignableFrom(type)) 2473 return true; 2474 } 2475 2476 var inputProperties = positional ? positional_input_properties : non_positional_input_properties; 2477 2478 foreach (var inputProperty in inputProperties) 2479 { 2480 var property = type.GetProperty(inputProperty); 2481 2482 Debug.Assert(property != null); 2483 2484 if (property.DeclaringType != typeof(Drawable)) 2485 return true; 2486 } 2487 2488 return false; 2489 } 2490 } 2491 2492 /// <summary> 2493 /// Check whether we have active focus. 2494 /// </summary> 2495 public bool HasFocus { get; internal set; } 2496 2497 /// <summary> 2498 /// If true, we are eagerly requesting focus. If nothing else above us has (or is requesting focus) we will get it. 2499 /// </summary> 2500 /// <remarks>In order to get focused, <see cref="HandleNonPositionalInput"/> must be true.</remarks> 2501 public virtual bool RequestsFocus => false; 2502 2503 /// <summary> 2504 /// If true, we will gain focus (receiving priority on keyboard input) (and receive an <see cref="OnFocus"/> event) on returning true in <see cref="OnClick"/>. 2505 /// </summary> 2506 public virtual bool AcceptsFocus => false; 2507 2508 /// <summary> 2509 /// Whether this Drawable is currently hovered over. 2510 /// </summary> 2511 /// <remarks>This is updated only if <see cref="HandlePositionalInput"/> is true.</remarks> 2512 public bool IsHovered { get; internal set; } 2513 2514 /// <summary> 2515 /// Whether this Drawable is currently being dragged. 2516 /// </summary> 2517 public bool IsDragged { get; internal set; } 2518 2519 /// <summary> 2520 /// Determines whether this drawable receives positional input when the mouse is at the 2521 /// given screen-space position. 2522 /// </summary> 2523 /// <param name="screenSpacePos">The screen-space position where input could be received.</param> 2524 /// <returns>True if input is received at the given screen-space position.</returns> 2525 public virtual bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Contains(screenSpacePos); 2526 2527 /// <summary> 2528 /// Computes whether a given screen-space position is contained within this drawable. 2529 /// Mouse input events are only received when this function is true, or when the drawable 2530 /// is in focus. 2531 /// </summary> 2532 /// <param name="screenSpacePos">The screen space position to be checked against this drawable.</param> 2533 public virtual bool Contains(Vector2 screenSpacePos) => DrawRectangle.Contains(ToLocalSpace(screenSpacePos)); 2534 2535 /// <summary> 2536 /// Whether non-positional input should be propagated to the sub-tree rooted at this drawable. 2537 /// </summary> 2538 public virtual bool PropagateNonPositionalInputSubTree => IsPresent && RequestsNonPositionalInputSubTree; 2539 2540 /// <summary> 2541 /// Whether positional input should be propagated to the sub-tree rooted at this drawable. 2542 /// </summary> 2543 public virtual bool PropagatePositionalInputSubTree => IsPresent && RequestsPositionalInputSubTree && !IsMaskedAway; 2544 2545 /// <summary> 2546 /// Whether clicks should be blocked when this drawable is in a dragged state. 2547 /// </summary> 2548 /// <remarks> 2549 /// This is queried when a click is to be actuated. 2550 /// </remarks> 2551 public virtual bool DragBlocksClick => true; 2552 2553 /// <summary> 2554 /// This method is responsible for building a queue of Drawables to receive non-positional input in reverse order. 2555 /// </summary> 2556 /// <param name="queue">The input queue to be built.</param> 2557 /// <param name="allowBlocking">Whether blocking at <see cref="PassThroughInputManager"/>s should be allowed.</param> 2558 /// <returns>Returns false if we should skip this sub-tree.</returns> 2559 internal virtual bool BuildNonPositionalInputQueue(List<Drawable> queue, bool allowBlocking = true) 2560 { 2561 if (!PropagateNonPositionalInputSubTree) 2562 return false; 2563 2564 if (HandleNonPositionalInput) 2565 queue.Add(this); 2566 2567 return true; 2568 } 2569 2570 /// <summary> 2571 /// This method is responsible for building a queue of Drawables to receive positional input in reverse order. 2572 /// </summary> 2573 /// <param name="screenSpacePos">The screen space position of the positional input.</param> 2574 /// <param name="queue">The input queue to be built.</param> 2575 /// <returns>Returns false if we should skip this sub-tree.</returns> 2576 internal virtual bool BuildPositionalInputQueue(Vector2 screenSpacePos, List<Drawable> queue) 2577 { 2578 if (!PropagatePositionalInputSubTree) 2579 return false; 2580 2581 if (HandlePositionalInput && ReceivePositionalInputAt(screenSpacePos)) 2582 queue.Add(this); 2583 2584 return true; 2585 } 2586 2587 internal sealed override void EnsureTransformMutationAllowed() => EnsureMutationAllowed(nameof(Transforms)); 2588 2589 /// <summary> 2590 /// Check whether the current thread is valid for operating on thread-safe properties. 2591 /// </summary> 2592 /// <param name="member">The member to be operated on, used only for describing failures in exception messages.</param> 2593 /// <exception cref="InvalidThreadForMutationException">If the current thread is not valid.</exception> 2594 internal void EnsureMutationAllowed(string member) 2595 { 2596 switch (LoadState) 2597 { 2598 case LoadState.NotLoaded: 2599 break; 2600 2601 case LoadState.Loading: 2602 if (Thread.CurrentThread != LoadThread) 2603 throw new InvalidThreadForMutationException(LoadState, member, "not on the load thread"); 2604 2605 break; 2606 2607 case LoadState.Ready: 2608 // Allow mutating from the load thread since parenting containers may still be in the loading state 2609 if (Thread.CurrentThread != LoadThread && !ThreadSafety.IsUpdateThread) 2610 throw new InvalidThreadForMutationException(LoadState, member, "not on the load or update threads"); 2611 2612 break; 2613 2614 case LoadState.Loaded: 2615 if (!ThreadSafety.IsUpdateThread) 2616 throw new InvalidThreadForMutationException(LoadState, member, "not on the update thread"); 2617 2618 break; 2619 } 2620 } 2621 2622 #endregion 2623 2624 #region Transforms 2625 2626 protected internal ScheduledDelegate Schedule(Action action) => Scheduler.AddDelayed(action, TransformDelay); 2627 2628 /// <summary> 2629 /// Make this drawable automatically clean itself up after all transforms have finished playing. 2630 /// Can be delayed using Delay(). 2631 /// </summary> 2632 public void Expire(bool calculateLifetimeStart = false) 2633 { 2634 if (clock == null) 2635 { 2636 LifetimeEnd = double.MinValue; 2637 return; 2638 } 2639 2640 LifetimeEnd = LatestTransformEndTime; 2641 2642 if (calculateLifetimeStart) 2643 { 2644 double min = double.MaxValue; 2645 2646 foreach (Transform t in Transforms) 2647 { 2648 if (t.StartTime < min) 2649 min = t.StartTime; 2650 } 2651 2652 LifetimeStart = min < int.MaxValue ? min : int.MinValue; 2653 } 2654 } 2655 2656 /// <summary> 2657 /// Hide sprite instantly. 2658 /// </summary> 2659 public virtual void Hide() => this.FadeOut(); 2660 2661 /// <summary> 2662 /// Show sprite instantly. 2663 /// </summary> 2664 public virtual void Show() => this.FadeIn(); 2665 2666 #endregion 2667 2668 #region Effects 2669 2670 /// <summary> 2671 /// Returns the drawable created by applying the given effect to this drawable. This method may add this drawable to a container. 2672 /// If this drawable should be the child of another container, make sure to add the created drawable to the container instead of this drawable. 2673 /// </summary> 2674 /// <typeparam name="T">The type of the drawable that results from applying the given effect.</typeparam> 2675 /// <param name="effect">The effect to apply to this drawable.</param> 2676 /// <param name="initializationAction">The action that should get called to initialize the created drawable before it is returned.</param> 2677 /// <returns>The drawable created by applying the given effect to this drawable.</returns> 2678 public T WithEffect<T>(IEffect<T> effect, Action<T> initializationAction = null) where T : Drawable 2679 { 2680 var result = effect.ApplyTo(this); 2681 initializationAction?.Invoke(result); 2682 return result; 2683 } 2684 2685 #endregion 2686 2687 /// <summary> 2688 /// A name used to identify this Drawable internally. 2689 /// </summary> 2690 public string Name = string.Empty; 2691 2692 public override string ToString() 2693 { 2694 string shortClass = GetType().ReadableName(); 2695 2696 if (!string.IsNullOrEmpty(Name)) 2697 return $@"{Name} ({shortClass})"; 2698 else 2699 return shortClass; 2700 } 2701 2702 /// <summary> 2703 /// Creates a new instance of an empty <see cref="Drawable"/>. 2704 /// </summary> 2705 public static Drawable Empty() => new EmptyDrawable(); 2706 2707 private class EmptyDrawable : Drawable 2708 { 2709 } 2710 2711 public class InvalidThreadForMutationException : InvalidOperationException 2712 { 2713 public InvalidThreadForMutationException(LoadState loadState, string member, string invalidThreadContextDescription) 2714 : base($"Cannot mutate the {member} of a {loadState} {nameof(Drawable)} while {invalidThreadContextDescription}. " 2715 + $"Consider using {nameof(Schedule)} to schedule the mutation operation.") 2716 { 2717 } 2718 } 2719 } 2720 2721 /// <summary> 2722 /// Specifies which type of properties are being invalidated. 2723 /// </summary> 2724 [Flags] 2725 public enum Invalidation 2726 { 2727 /// <summary> 2728 /// <see cref="Drawable.DrawInfo"/> has changed. No change to <see cref="Drawable.RequiredParentSizeToFit"/> or <see cref="Drawable.DrawSize"/> 2729 /// is assumed unless indicated by additional flags. 2730 /// </summary> 2731 DrawInfo = 1, 2732 2733 /// <summary> 2734 /// <see cref="Drawable.DrawSize"/> has changed. 2735 /// </summary> 2736 DrawSize = 1 << 1, 2737 2738 /// <summary> 2739 /// Captures all other geometry changes than <see cref="Drawable.DrawSize"/>, such as 2740 /// <see cref="Drawable.Rotation"/>, <see cref="Drawable.Shear"/>, and <see cref="Drawable.DrawPosition"/>. 2741 /// </summary> 2742 MiscGeometry = 1 << 2, 2743 2744 /// <summary> 2745 /// <see cref="Drawable.Colour"/> has changed. 2746 /// </summary> 2747 Colour = 1 << 3, 2748 2749 /// <summary> 2750 /// <see cref="Graphics.DrawNode.ApplyState"/> has to be invoked on all old draw nodes. 2751 /// This <see cref="Invalidation"/> flag never propagates to children. 2752 /// </summary> 2753 DrawNode = 1 << 4, 2754 2755 /// <summary> 2756 /// <see cref="Drawable.IsPresent"/> has changed. 2757 /// </summary> 2758 Presence = 1 << 5, 2759 2760 /// <summary> 2761 /// A <see cref="Drawable.Parent"/> has changed. 2762 /// Unlike other <see cref="Invalidation"/> flags, this propagates to all children regardless of their <see cref="Drawable.IsAlive"/> state. 2763 /// </summary> 2764 Parent = 1 << 6, 2765 2766 /// <summary> 2767 /// No invalidation. 2768 /// </summary> 2769 None = 0, 2770 2771 /// <summary> 2772 /// <see cref="Drawable.RequiredParentSizeToFit"/> has to be recomputed. 2773 /// </summary> 2774 RequiredParentSizeToFit = MiscGeometry | DrawSize, 2775 2776 /// <summary> 2777 /// All possible things are affected. 2778 /// </summary> 2779 All = DrawNode | RequiredParentSizeToFit | Colour | DrawInfo | Presence, 2780 2781 /// <summary> 2782 /// Only the layout flags. 2783 /// </summary> 2784 Layout = All & ~(DrawNode | Parent) 2785 } 2786 2787 /// <summary> 2788 /// General enum to specify an "anchor" or "origin" point from the standard 9 points on a rectangle. 2789 /// x and y counterparts can be accessed using bitwise flags. 2790 /// </summary> 2791 [Flags] 2792 public enum Anchor 2793 { 2794 TopLeft = y0 | x0, 2795 TopCentre = y0 | x1, 2796 TopRight = y0 | x2, 2797 2798 CentreLeft = y1 | x0, 2799 Centre = y1 | x1, 2800 CentreRight = y1 | x2, 2801 2802 BottomLeft = y2 | x0, 2803 BottomCentre = y2 | x1, 2804 BottomRight = y2 | x2, 2805 2806 /// <summary> 2807 /// The vertical counterpart is at "Top" position. 2808 /// </summary> 2809 y0 = 1, 2810 2811 /// <summary> 2812 /// The vertical counterpart is at "Centre" position. 2813 /// </summary> 2814 y1 = 1 << 1, 2815 2816 /// <summary> 2817 /// The vertical counterpart is at "Bottom" position. 2818 /// </summary> 2819 y2 = 1 << 2, 2820 2821 /// <summary> 2822 /// The horizontal counterpart is at "Left" position. 2823 /// </summary> 2824 x0 = 1 << 3, 2825 2826 /// <summary> 2827 /// The horizontal counterpart is at "Centre" position. 2828 /// </summary> 2829 x1 = 1 << 4, 2830 2831 /// <summary> 2832 /// The horizontal counterpart is at "Right" position. 2833 /// </summary> 2834 x2 = 1 << 5, 2835 2836 /// <summary> 2837 /// The user is manually updating the outcome, so we shouldn't. 2838 /// </summary> 2839 Custom = 1 << 6, 2840 } 2841 2842 [Flags] 2843 public enum Axes 2844 { 2845 None = 0, 2846 2847 X = 1, 2848 Y = 1 << 1, 2849 2850 Both = X | Y, 2851 } 2852 2853 [Flags] 2854 public enum Edges 2855 { 2856 None = 0, 2857 2858 Top = 1, 2859 Left = 1 << 1, 2860 Bottom = 1 << 2, 2861 Right = 1 << 3, 2862 2863 Horizontal = Left | Right, 2864 Vertical = Top | Bottom, 2865 2866 All = Top | Left | Bottom | Right, 2867 } 2868 2869 public enum Direction 2870 { 2871 Horizontal, 2872 Vertical, 2873 } 2874 2875 public enum RotationDirection 2876 { 2877 [Description("Clockwise")] 2878 Clockwise, 2879 2880 [Description("Counterclockwise")] 2881 Counterclockwise, 2882 } 2883 2884 /// <summary> 2885 /// Possible states of a <see cref="Drawable"/> within the loading pipeline. 2886 /// </summary> 2887 public enum LoadState 2888 { 2889 /// <summary> 2890 /// Not loaded, and no load has been initiated yet. 2891 /// </summary> 2892 NotLoaded, 2893 2894 /// <summary> 2895 /// Currently loading (possibly and usually on a background thread via <see cref="CompositeDrawable.LoadComponentAsync{TLoadable}"/>). 2896 /// </summary> 2897 Loading, 2898 2899 /// <summary> 2900 /// Loading is complete, but has not yet been finalized on the update thread 2901 /// (<see cref="Drawable.LoadComplete"/> has not been called yet, which 2902 /// always runs on the update thread and requires <see cref="Drawable.IsAlive"/>). 2903 /// </summary> 2904 Ready, 2905 2906 /// <summary> 2907 /// Loading is fully completed and the Drawable is now part of the scene graph. 2908 /// </summary> 2909 Loaded 2910 } 2911 2912 /// <summary> 2913 /// Controls the behavior of <see cref="Drawable.RelativeSizeAxes"/> when it is set to <see cref="Axes.Both"/>. 2914 /// </summary> 2915 public enum FillMode 2916 { 2917 /// <summary> 2918 /// Completely fill the parent with a relative size of 1 at the cost of stretching the aspect ratio (default). 2919 /// </summary> 2920 Stretch, 2921 2922 /// <summary> 2923 /// Always maintains aspect ratio while filling the portion of the parent's size denoted by the relative size. 2924 /// A relative size of 1 results in completely filling the parent by scaling the smaller axis of the drawable to fill the parent. 2925 /// </summary> 2926 Fill, 2927 2928 /// <summary> 2929 /// Always maintains aspect ratio while fitting into the portion of the parent's size denoted by the relative size. 2930 /// A relative size of 1 results in fitting exactly into the parent by scaling the larger axis of the drawable to fit into the parent. 2931 /// </summary> 2932 Fit, 2933 } 2934}