1using System.Collections.ObjectModel;
30 private static readonly
object _parentSyncRoot =
new();
32 private static readonly Dictionary<IDirectCompositeChild, DirectComposite> _parents =
33 new(ReferenceEqualityComparer.Instance);
35 private readonly List<IDirectCompositeChild> _children = [];
36 private readonly Dictionary<IDirectCompositeChild, Vector2> _localOffsetPx =
37 new(ReferenceEqualityComparer.Instance);
39 private readonly ReadOnlyCollection<IDirectCompositeChild> _readOnlyChildren;
41 private PointF _anchor;
42 private long _lastTick = HighResTimer.GetCurrentTick();
43 private bool _disposed;
50 public event EventHandler<IDirectDrawable>?
Disposing;
65 PointF anchor =
default,
66 string? nickname =
null)
68 RenderSurfaceHost = renderSurfaceHost ??
throw new ArgumentNullException(nameof(renderSurfaceHost));
74 _readOnlyChildren =
new ReadOnlyCollection<IDirectCompositeChild>(_children);
81 #endregion constructors
83 #region public properties
88 public Guid
Id {
get; } = Guid.NewGuid();
129 public ReadOnlyCollection<IDirectCompositeChild>
Children => _readOnlyChildren;
146 get => _children.Any(
static child => child.Visible);
166 public Rectangle
ScreenBounds => GetBounds(
static child => child.ScreenBounds);
171 public Rectangle
WorldBounds => GetBounds(
static child => child.WorldBounds);
173 #endregion public properties
175 #region public methods
200 ObjectDisposedException.ThrowIf(_disposed,
this);
202 _anchor =
new PointF(x, y);
203 var anchor =
new Vector2(x, y);
207 Vector2 offset = _localOffsetPx.TryGetValue(child, out Vector2 value) ? value : Vector2.Zero;
235 bool keepCurrentOffset =
true,
236 Vector2? explicitLocalOffsetPx =
null)
238 ObjectDisposedException.ThrowIf(_disposed,
this);
239 ArgumentNullException.ThrowIfNull(child);
241 if (_children.Contains(child))
244 ValidateChildIdentity(child);
245 ValidateParentRelationship(child);
246 ValidateAndAssignTarget(child);
248 Vector2 offset = keepCurrentOffset ? child.
GetPosition() -
new Vector2(_anchor.X, _anchor.Y) : explicitLocalOffsetPx ?? Vector2.Zero;
250 RegisterParent(child);
254 _children.Add(child);
255 _localOffsetPx[child] = offset;
256 child.Disposing += OnChildDisposing;
258 child.
SetPosition(
new Vector2(_anchor.X, _anchor.Y) + offset);
264 child.Disposing -= OnChildDisposing;
265 _children.Remove(child);
266 _localOffsetPx.Remove(child);
267 UnregisterParent(child);
268 ResetTargetWhenEmpty();
282 ObjectDisposedException.ThrowIf(_disposed,
this);
283 ArgumentNullException.ThrowIfNull(child);
285 if (!_children.Remove(child))
288 child.Disposing -= OnChildDisposing;
289 _localOffsetPx.
Remove(child);
290 UnregisterParent(child);
291 ResetTargetWhenEmpty();
304 ObjectDisposedException.ThrowIf(_disposed,
this);
310 child.Disposing -= OnChildDisposing;
311 UnregisterParent(child);
315 _localOffsetPx.Clear();
316 ResetTargetWhenEmpty();
331 Vector2 newLocalOffsetPx)
333 ObjectDisposedException.ThrowIf(_disposed,
this);
334 ArgumentNullException.ThrowIfNull(child);
336 if (!_children.Contains(child))
339 _localOffsetPx[child] = newLocalOffsetPx;
341 child.
SetPosition(
new Vector2(_anchor.X, _anchor.Y) + newLocalOffsetPx);
392 child.
FadeTo(targetOpacity, durationSec);
418 ArgumentNullException.ThrowIfNull(view);
422 return RectangleF.Empty;
425 float minX =
float.MaxValue;
426 float minY =
float.MaxValue;
427 float maxX =
float.MinValue;
428 float maxY =
float.MinValue;
440 minX = Math.Min(minX, bounds.Left);
441 minY = Math.Min(minY, bounds.Top);
442 maxX = Math.Max(maxX, bounds.Right);
443 maxY = Math.Max(maxY, bounds.Bottom);
446 if (minX ==
float.MaxValue)
447 return RectangleF.Empty;
449 return RectangleF.FromLTRB(
450 (
float)Math.Floor(minX),
451 (
float)Math.Floor(minY),
452 (
float)Math.Ceiling(maxX),
453 (
float)Math.Ceiling(maxY));
473 if (_disposed || tick <= _lastTick)
477 HighResTimer.GetDuration(_lastTick, tick));
501 child.Disposing -= OnChildDisposing;
502 UnregisterParent(child);
506 _localOffsetPx.Clear();
507 ResetTargetWhenEmpty();
510 GC.SuppressFinalize(
this);
513 #endregion public methods
515 #region IDirectCompositeChild explicit interface implementations
525 #endregion IDirectCompositeChild explicit interface implementations
527 #region IDirectCompositeContainer explicit interface implementations
533 return Add(child, keepCurrentOffset, explicitLocalOffsetPx);
536 IDirectCompositeContainer IDirectCompositeContainer.
Remove(IDirectCompositeChild child)
541 IDirectCompositeContainer IDirectCompositeContainer.
Clear()
546 #endregion IDirectCompositeContainer explicit interface implementations
548 #region protected hooks
572 #endregion protected hooks
574 #region private methods
576 private Rectangle GetBounds(Func<IDirectCompositeChild, Rectangle> selector)
578 float minX =
float.MaxValue;
579 float minY =
float.MaxValue;
580 float maxX =
float.MinValue;
581 float maxY =
float.MinValue;
588 Rectangle bounds = selector(child);
593 minX = Math.Min(minX, bounds.Left);
594 minY = Math.Min(minY, bounds.Top);
595 maxX = Math.Max(maxX, bounds.Right);
596 maxY = Math.Max(maxY, bounds.Bottom);
599 if (minX ==
float.MaxValue)
600 return Rectangle.Empty;
602 return Rectangle.FromLTRB(
603 (
int)Math.Floor(minX),
604 (
int)Math.Floor(minY),
605 (
int)Math.Ceiling(maxX),
606 (
int)Math.Ceiling(maxY));
609 private void ValidateChildIdentity(IDirectCompositeChild child)
613 throw new ArgumentException(
"The child must belong to the same RenderSurfaceHost as the composite.", nameof(child));
616 if (child.Mode !=
Mode)
618 throw new ArgumentException($
"The child drawing mode '{child.Mode}' does not match the composite drawing mode '{Mode}'.", nameof(child));
622 private void ValidateAndAssignTarget(IDirectCompositeChild child)
626 SceneLayer childLayer = child.SceneLayer ??
throw new ArgumentException(
"A scene-layer child must reference a SceneLayer.", nameof(child));
632 else if (!ReferenceEquals(
SceneLayer, childLayer))
634 throw new ArgumentException(
"All scene-layer children in a composite must reference the same SceneLayer.", nameof(child));
640 if (child.View is
null)
643 compositeChild.Children.Count == 0)
645 throw new ArgumentException(
646 "An empty composite cannot be added in view mode because it has no View yet. Add at least one descendant first.",
650 throw new ArgumentException(
"A view child must reference a View.", nameof(child));
653 View childView = child.View;
658 else if (!ReferenceEquals(
View, childView))
660 throw new ArgumentException(
"All view children in a composite must reference the same View.", nameof(child));
664 private void ValidateParentRelationship(IDirectCompositeChild child)
666 lock (_parentSyncRoot)
670 throw new InvalidOperationException($
"The child already belongs to composite '{existingParent.Nickname}'.");
678 while (ancestor is not
null)
680 if (ReferenceEquals(ancestor, childComposite))
682 throw new InvalidOperationException(
"Adding the child would create a recursive composite cycle.");
685 _parents.TryGetValue(ancestor, out ancestor);
690 private void RegisterParent(IDirectCompositeChild child)
692 lock (_parentSyncRoot)
693 _parents.Add(child,
this);
696 private void UnregisterParent(IDirectCompositeChild child)
698 lock (_parentSyncRoot)
701 ReferenceEquals(parent,
this))
703 _parents.Remove(child);
708 private void ResetTargetWhenEmpty()
710 if (_children.Count != 0)
717 private void OnChildDisposing(
object? sender, IDirectDrawable drawing)
719 if (drawing is not IDirectCompositeChild child)
722 child.Disposing -= OnChildDisposing;
724 if (!_children.Remove(child))
727 _localOffsetPx.Remove(child);
728 UnregisterParent(child);
729 ResetTargetWhenEmpty();
734 #endregion private methods
bool Visible
Gets or sets the visibility of all descendants.
DirectComposite SetPosition(float x, float y)
Sets the composite anchor and repositions all children from their stored offsets.
virtual DirectComposite Remove(IDirectCompositeChild child)
Removes a child without disposing it.
string? Nickname
Gets the optional diagnostic nickname of the composite.
virtual void OnChildAdded(IDirectCompositeChild child)
Called after a child has been successfully added.
ReadOnlyCollection< IDirectCompositeChild > Children
Gets the children owned by the composite.
virtual void OnChildRemoved(IDirectCompositeChild child)
Called after a child has been removed from the composite.
DirectComposite FadeIn(float durationSec)
Fades every descendant to full opacity.
RenderSurfaceHostBase RenderSurfaceHost
Gets the render surface host shared by the composite and its children.
SceneLayer? SceneLayer
Gets the scene layer shared by all children in scene-layer mode.
DirectComposite SetZOrder(int zOrder)
Applies one Z-order to every descendant.
virtual void Update(long tick)
Advances composite movement.
DirectComposite FadeTo(float targetOpacity, float durationSec)
Fades every descendant to the requested opacity.
RectangleF GetDrawLocationScreen(View view)
Gets the union of all visible descendants as projected through a view.
DirectComposite FadeOut(float durationSec)
Fades every descendant to zero opacity.
DirectComposite SetOpacity(float opacity)
Applies one opacity to every descendant.
DirectComposite SetLocalOffset(IDirectCompositeChild child, Vector2 newLocalOffsetPx)
Changes one child's local offset and reapplies its position.
DirectComposite SetIsVisible(bool visible)
Applies one visibility value to every descendant.
Rectangle WorldBounds
Gets the union of all visible descendants' world-space bounds.
EventHandler< IDirectDrawable >? Disposing
Occurs when the composite is being disposed.
Guid Id
Gets the unique identifier of the composite.
Rectangle ScreenBounds
Gets the union of all visible descendants' screen-space bounds.
int ZOrder
Gets the composite Z-order.
virtual DirectComposite Clear()
Removes every child without disposing them.
View? View
Gets the view shared by all children in view mode.
virtual void Dispose()
Disposes the composite and every child it owns.
MovementController Movement
Gets the movement controller used to move the composite anchor.
DirectComposite(RenderSurfaceHostBase renderSurfaceHost, DirectDrawingMode mode, PointF anchor=default, string? nickname=null)
Initializes a new instance of the DirectComposite class.
void Draw(BackbufferBase backbuffer, RectangleF destRectScreen)
Performs no direct rendering because descendants render independently.
Vector2 GetPosition()
Gets the current composite anchor.
MovementSpace PositionSpace
Gets the coordinate space used by the movement controller.
virtual DirectComposite Add(IDirectCompositeChild child, bool keepCurrentOffset=true, Vector2? explicitLocalOffsetPx=null)
Adds a composite-compatible direct drawable as a child.
void SetPosition(Vector2 position)
Sets the composite anchor and repositions all children from their stored offsets.
DirectDrawingMode Mode
Gets the coordinate mode shared by the composite and its children.
Manages the lifecycle, registration, and retrieval of all direct drawing objects in the Gondwana engi...
static DirectDrawingManager Instance
Gets the singleton instance of the direct drawing manager.
Central controller responsible for managing all movement modes for a single IMovable object....
Represents a base class for managing a graphical backbuffer, and is the in-memory surface where rende...
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
Defines a movable direct drawable that may be owned by a DirectComposite.
void FadeTo(float targetOpacity, float durationSec)
Fades this child to the requested opacity.
void SetIsVisible(bool visible)
Applies a visibility value to this child.
void SetZOrder(int zOrder)
Applies a Z-order value to this child.
void SetOpacity(float opacity)
Applies an opacity value to this child.
Defines a container that can hold and manage a collection of direct composite children.
IDirectCompositeContainer Add(IDirectCompositeChild child, bool keepCurrentOffset=true, Vector2? explicitLocalOffsetPx=null)
Adds a child element to this container.
IReadOnlyCollection< IDirectCompositeChild > Children
Gets the read-only collection of child elements in this container.
IDirectCompositeContainer Remove(IDirectCompositeChild child)
Removes a child element from this container.
IDirectCompositeContainer Clear()
Removes all child elements from this container.
bool Visible
Gets a value indicating whether the object is visible.
RectangleF GetDrawLocationScreen(View view)
Computes the object's destination rectangle in SCREEN pixels for rendering on the Backbuffer,...
Vector2 GetPosition()
Get the current position in the mover's PositionSpace.
void SetPosition(Vector2 pos)
Set the position in the mover's PositionSpace.
DirectDrawingMode
Specifies where direct drawing operations are applied within the rendering pipeline.
MovementSpace
Specifies the coordinate system used for movement calculations and updates.
Represents the current motion state of an IMovable object, including velocity, acceleration,...