// Copyright (c) Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEngine.Assertions; namespace FlaxEngine.GUI { /// /// Base class for all GUI controls that can contain child controls. /// public class ContainerControl : Control { /// /// The children collection. /// [NoSerialize] protected readonly List _children = new List(); /// /// The contains focus cached flag. /// [NoSerialize] protected bool _containsFocus; /// /// The layout locking flag. /// [NoSerialize] protected bool _isLayoutLocked; private bool _clipChildren = true; private bool _cullChildren = true; /// /// Initializes a new instance of the class. /// public ContainerControl() { _isLayoutLocked = true; } /// /// Initializes a new instance of the class. /// public ContainerControl(float x, float y, float width, float height) : base(x, y, width, height) { _isLayoutLocked = true; } /// /// Initializes a new instance of the class. /// public ContainerControl(Float2 location, Float2 size) : base(location, size) { _isLayoutLocked = true; } /// public ContainerControl(Rectangle bounds) : base(bounds) { _isLayoutLocked = true; } /// /// Gets child controls list /// public List Children => _children; /// /// Gets amount of the children controls /// public int ChildrenCount => _children.Count; /// /// Checks if container has any child controls /// public bool HasChildren => _children.Count > 0; /// /// Gets a value indicating whether the control, or one of its child controls, currently has the input focus. /// public override bool ContainsFocus => _containsFocus; /// /// True if automatic updates for control layout are locked (useful when creating a lot of GUI control to prevent lags). /// [HideInEditor, NoSerialize, NoAnimate] public bool IsLayoutLocked { get => _isLayoutLocked; set => _isLayoutLocked = value; } /// /// Gets or sets a value indicating whether apply clipping mask on children during rendering. /// [EditorOrder(530), Tooltip("If checked, control will apply clipping mask on children during rendering.")] public bool ClipChildren { get => _clipChildren; set => _clipChildren = value; } /// /// Gets or sets a value indicating whether perform view culling on children during rendering. /// [EditorOrder(540), Tooltip("If checked, control will perform view culling on children during rendering.")] public bool CullChildren { get => _cullChildren; set => _cullChildren = value; } /// /// Locks all child controls layout and itself. /// [NoAnimate] public void LockChildrenRecursive() { _isLayoutLocked = true; for (int i = 0; i < _children.Count; i++) { if (_children[i] is ContainerControl child) child.LockChildrenRecursive(); } } /// /// Unlocks all the child controls layout and itself. /// [NoAnimate] public void UnlockChildrenRecursive() { _isLayoutLocked = false; for (int i = 0; i < _children.Count; i++) { if (_children[i] is ContainerControl child) child.UnlockChildrenRecursive(); } } /// /// Unlinks all the child controls. /// [NoAnimate] public virtual void RemoveChildren() { bool wasLayoutLocked = _isLayoutLocked; _isLayoutLocked = true; // Delete children while (_children.Count > 0) { _children[0].Parent = null; } _isLayoutLocked = wasLayoutLocked; PerformLayout(); } /// /// Removes and disposes all the child controls /// public virtual void DisposeChildren() { bool wasLayoutLocked = _isLayoutLocked; _isLayoutLocked = true; // Delete children while (_children.Count > 0) { _children[0].Dispose(); } _isLayoutLocked = wasLayoutLocked; PerformLayout(); } /// /// Creates a new control and adds it to the container. /// /// The added control. public T AddChild() where T : Control { var child = (T)Activator.CreateInstance(typeof(T)); child.Parent = this; return child; } /// /// Adds the control to the container. /// /// The control to add. /// The added control. public T AddChild(T child) where T : Control { if (child == null) throw new ArgumentNullException(); if (child.Parent == this && _children.Contains(child)) throw new InvalidOperationException("Argument child cannot be added, if current container is already its parent."); // Set child new parent child.Parent = this; return child; } /// /// Removes control from the container. /// /// The control to remove. public void RemoveChild(Control child) { if (child == null) throw new ArgumentNullException(); if (child.Parent != this) throw new InvalidOperationException("Argument child cannot be removed, if current container is not its parent."); // Unlink child.Parent = null; } /// /// Gets child control at given index. /// /// The control index. /// The child control. public Control GetChild(int index) { return _children[index]; } /// /// Searches for a child control of a specific type. If there are multiple controls matching the type, only the first one found is returned. /// /// The type of the control to search for. Includes any controls derived from the type. /// The control instance if found, otherwise null. public T GetChild() where T : Control { var type = typeof(T); for (int i = 0; i < _children.Count; i++) { var ct = _children[i].GetType(); if (type.IsAssignableFrom(ct)) return (T)_children[i]; } return null; } /// /// Gets zero-based index in the list of control children. /// /// The child control. /// The zero-based index in the list of control children. public int GetChildIndex(Control child) { return _children.IndexOf(child); } internal void ChangeChildIndex(Control child, int newIndex) { int oldIndex = _children.IndexOf(child); if (oldIndex == newIndex || oldIndex == -1) return; _children.RemoveAt(oldIndex); if (newIndex < 0 || newIndex >= _children.Count) { // Append at the end _children.Add(child); } else { // Change order _children.Insert(newIndex, child); } PerformLayout(); } /// /// Tries to find any child control at given point in control local coordinates. /// /// The local point to check. /// The found control index or -1 if failed. public int GetChildIndexAt(Float2 point) { int result = -1; for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; if (IntersectsChildContent(child, point, out var childLocation)) { result = i; break; } } return result; } /// /// Tries to find any child control at given point in control local coordinates /// /// The local point to check. /// The found control or null. public Control GetChildAt(Float2 point) { Control result = null; for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; if (IntersectsChildContent(child, point, out var childLocation)) { result = child; break; } } return result; } /// /// Tries to find valid child control at given point in control local coordinates. Uses custom callback method to test controls to pick. /// /// The local point to check. /// The control validation callback. /// The found control or null. public Control GetChildAt(Float2 point, Func isValid) { if (isValid == null) throw new ArgumentNullException(nameof(isValid)); Control result = null; for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; if (isValid(child) && IntersectsChildContent(child, point, out var childLocation)) { result = child; break; } } return result; } /// /// Tries to find lowest child control at given point in control local coordinates. /// /// The local point to check. /// The found control or null. public Control GetChildAtRecursive(Float2 point) { Control result = null; for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; if (child.Visible && IntersectsChildContent(child, point, out var childLocation)) { var containerControl = child as ContainerControl; var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation); if (childAtRecursive != null && childAtRecursive.Visible) { child = childAtRecursive; } result = child; break; } } return result; } /// /// Gets rectangle in local control coordinates with area for controls (without scroll bars, anchored controls, etc.). /// /// The rectangle in local control coordinates with area for controls (without scroll bars etc.). public Rectangle GetClientArea() { GetDesireClientArea(out var clientArea); return clientArea; } /// /// Sort child controls list /// [NoAnimate] public void SortChildren() { _children.Sort(); PerformLayout(); } /// /// Sort children using recursion /// [NoAnimate] public void SortChildrenRecursive() { SortChildren(); for (int i = 0; i < _children.Count; i++) { var child = _children[i] as ContainerControl; child?.SortChildrenRecursive(); } } /// /// Called when child control gets resized. /// /// The resized control. public virtual void OnChildResized(Control control) { } /// /// Called when children collection gets changed (child added or removed). /// [NoAnimate] public virtual void OnChildrenChanged() { // Check if control isn't during disposing state if (!IsDisposing) { // Arrange child controls PerformLayout(); } } #region Internal Events /// internal override void CacheRootHandle() { base.CacheRootHandle(); for (int i = 0; i < _children.Count; i++) { _children[i].CacheRootHandle(); } } /// /// Adds a child control to the container. /// /// The control to add. internal virtual void AddChildInternal(Control child) { Assert.IsNotNull(child, "Invalid control."); if (Parent == child) throw new InvalidOperationException(); // Add child _children.Add(child); OnChildrenChanged(); } /// /// Removes a child control from this container. /// /// The control to remove. internal virtual void RemoveChildInternal(Control child) { Assert.IsNotNull(child, "Invalid control."); // Remove child _children.Remove(child); OnChildrenChanged(); } /// /// Gets the desire client area rectangle for all the controls. /// /// The client area rectangle for child controls. public virtual void GetDesireClientArea(out Rectangle rect) { rect = new Rectangle(Float2.Zero, Size); } /// /// Checks if given point in this container control space intersects with the child control content. /// Also calculates result location in child control space which can be used to feed control with event at that point. /// /// The child control to check. /// The location in this container control space. /// The output location in child control space. /// True if point is over the control content, otherwise false. public virtual bool IntersectsChildContent(Control child, Float2 location, out Float2 childSpaceLocation) { return child.IntersectsContent(ref location, out childSpaceLocation); } #region Navigation /// public override Control OnNavigate(NavDirection direction, Float2 location, Control caller, List visited) { // Try to focus itself first (only if navigation focus can enter this container) if (AutoFocus && !ContainsFocus) return this; // Try to focus children if (_children.Count != 0 && !visited.Contains(this)) { visited.Add(this); // Perform automatic navigation based on the layout var result = NavigationRaycast(direction, location, visited); var rightMostLocation = location; if (result == null && (direction == NavDirection.Next || direction == NavDirection.Previous)) { // Try wrap the navigation over the layout based on the direction var visitedWrap = new List(visited); result = NavigationWrap(direction, location, visitedWrap, out rightMostLocation); } if (result != null) { // HACK: only the 'previous' direction needs the rightMostLocation so i used a ternary conditional operator. // The rightMostLocation can probably become a 'desired raycast origin' that gets calculated correctly in the NavigationWrap method. var useLocation = direction == NavDirection.Previous ? rightMostLocation : location; result = result.OnNavigate(direction, result.PointFromParent(useLocation), this, visited); if (result != null) return result; } } // Try to focus itself if (AutoFocus && !IsFocused || caller == this) return this; // Route navigation to parent var parent = Parent; if (AutoFocus && Visible) { // Focusable container controls use own nav origin instead of the provided one location = GetNavOrigin(direction); } return parent?.OnNavigate(direction, PointToParent(location), caller, visited); } /// /// Checks if this container control can more with focus navigation into the given child control. /// /// The child. /// True if can navigate to it, otherwise false. protected virtual bool CanNavigateChild(Control child) { return !child.IsFocused && child.Enabled && child.Visible && CanGetAutoFocus(child); } /// /// Wraps the navigation over the layout. /// /// The navigation direction. /// The navigation start location (in the control-space). /// The list with visited controls. Used to skip recursive navigation calls when doing traversal across the UI hierarchy. /// Returns the rightmost location of the parent container for the raycast used by the child container /// The target navigation control or null if didn't performed any navigation. protected virtual Control NavigationWrap(NavDirection direction, Float2 location, List visited, out Float2 rightMostLocation) { // This searches form a child that calls this navigation event (see Control.OnNavigate) to determinate the layout wrapping size based on that child size var currentChild = RootWindow?.FocusedControl; visited.Add(this); if (currentChild != null) { var layoutSize = currentChild.Size; var predictedLocation = Float2.Minimum; switch (direction) { case NavDirection.Next: predictedLocation = new Float2(0, location.Y + layoutSize.Y); break; case NavDirection.Previous: predictedLocation = new Float2(Size.X, location.Y - layoutSize.Y); break; } if (new Rectangle(Float2.Zero, Size).Contains(ref predictedLocation)) { var result = NavigationRaycast(direction, predictedLocation, visited); if (result != null) { rightMostLocation = predictedLocation; return result; } } } rightMostLocation = location; return Parent?.NavigationWrap(direction, PointToParent(ref location), visited, out rightMostLocation); } private static bool CanGetAutoFocus(Control c) { if (c.AutoFocus) return true; if (c is ContainerControl cc) { for (int i = 0; i < cc.Children.Count; i++) { if (cc.CanNavigateChild(cc.Children[i])) return true; } } return false; } private Control NavigationRaycast(NavDirection direction, Float2 location, List visited) { Float2 uiDir1 = Float2.Zero, uiDir2 = Float2.Zero; switch (direction) { case NavDirection.Up: uiDir1 = uiDir2 = new Float2(0, -1); break; case NavDirection.Down: uiDir1 = uiDir2 = new Float2(0, 1); break; case NavDirection.Left: uiDir1 = uiDir2 = new Float2(-1, 0); break; case NavDirection.Right: uiDir1 = uiDir2 = new Float2(1, 0); break; case NavDirection.Next: uiDir1 = new Float2(1, 0); uiDir2 = new Float2(0, 1); break; case NavDirection.Previous: uiDir1 = new Float2(-1, 0); uiDir2 = new Float2(0, -1); break; } Control result = null; var minDistance = float.MaxValue; for (var i = 0; i < _children.Count; i++) { var child = _children[i]; if (!CanNavigateChild(child) || visited.Contains(child)) continue; var childNavLocation = child.Center; var childBounds = child.Bounds; var childNavDirection = Float2.Normalize(childNavLocation - location); var childNavCoherence1 = Float2.Dot(ref uiDir1, ref childNavDirection); var childNavCoherence2 = Float2.Dot(ref uiDir2, ref childNavDirection); var distance = Rectangle.Distance(childBounds, location); if (childNavCoherence1 > Mathf.Epsilon && childNavCoherence2 > Mathf.Epsilon && distance < minDistance) { minDistance = distance; result = child; } } return result; } #endregion /// /// Update contain focus state and all it's children /// protected void UpdateContainsFocus() { // Get current state and update all children bool result = base.ContainsFocus; for (int i = 0; i < _children.Count; i++) { var control = _children[i]; if (control is ContainerControl child) child.UpdateContainsFocus(); if (control.ContainsFocus) result = true; } // Check if state has been changed if (result != _containsFocus) { _containsFocus = result; if (result) { OnStartContainsFocus(); } else { OnEndContainsFocus(); } } } /// /// Updates child controls bounds. /// protected void UpdateChildrenBounds() { for (int i = 0; i < _children.Count; i++) { _children[i].UpdateBounds(); } } /// /// Perform layout for that container control before performing it for child controls. /// protected virtual void PerformLayoutBeforeChildren() { UpdateChildrenBounds(); } /// /// Perform layout for that container control after performing it for child controls. /// protected virtual void PerformLayoutAfterChildren() { } #endregion #region Control /// public override void OnDestroy() { // Steal focus from children if (ContainsFocus) { Focus(); } // Disable layout if (!_isLayoutLocked) { LockChildrenRecursive(); } base.OnDestroy(); // Pass event further for (int i = 0; i < _children.Count; i++) { _children[i].OnDestroy(); } _children.Clear(); } /// public override bool IsTouchOver { get { if (base.IsTouchOver) return true; for (int i = 0; i < _children.Count; i++) { if (_children[i].IsTouchOver) return true; } return false; } } /// public override void Update(float deltaTime) { base.Update(deltaTime); // Update all enabled child controls for (int i = 0; i < _children.Count; i++) { if (_children[i].Enabled) { _children[i].Update(deltaTime); } } } /// public override void ClearState() { base.ClearState(); // Clear state for any nested controls for (int i = 0; i < _children.Count; i++) { var child = _children[i]; //if (child.Enabled && child.Enabled) child.ClearState(); } } /// /// Draw the control and the children. /// public override void Draw() { DrawSelf(); if (_clipChildren) { GetDesireClientArea(out var clientArea); Render2D.PushClip(ref clientArea); DrawChildren(); Render2D.PopClip(); } else { DrawChildren(); } } /// /// Draws the control. /// public virtual void DrawSelf() { base.Draw(); } /// /// Draws the children. Can be overridden to provide some customizations. Draw is performed with applied clipping mask for the client area. /// protected virtual void DrawChildren() { // Draw all visible child controls var children = _children; if (_cullChildren) { Render2D.PeekClip(out var globalClipping); Render2D.PeekTransform(out var globalTransform); for (int i = 0; i < children.Count; i++) { var child = children[i]; if (child.Visible) { Matrix3x3.Multiply(ref child._cachedTransform, ref globalTransform, out var globalChildTransform); var childGlobalRect = new Rectangle(globalChildTransform.M31, globalChildTransform.M32, child.Width * globalChildTransform.M11, child.Height * globalChildTransform.M22); if (globalClipping.Intersects(ref childGlobalRect)) { Render2D.PushTransform(ref child._cachedTransform); child.Draw(); Render2D.PopTransform(); } } } } else { for (int i = 0; i < children.Count; i++) { var child = children[i]; if (child.Visible) { Render2D.PushTransform(ref child._cachedTransform); child.Draw(); Render2D.PopTransform(); } } } } /// public override bool ContainsPoint(ref Float2 location, bool precise = false) { if (precise && this.GetType() == typeof(ContainerControl) && BackgroundColor.A <= 0.0f) // Go through transparency return false; return base.ContainsPoint(ref location, precise); } /// public override void PerformLayout(bool force = false) { if (_isLayoutLocked && !force) return; bool wasLocked = _isLayoutLocked; if (!wasLocked) LockChildrenRecursive(); PerformLayoutBeforeChildren(); for (int i = 0; i < _children.Count; i++) _children[i].PerformLayout(true); PerformLayoutAfterChildren(); if (!wasLocked) UnlockChildrenRecursive(); } /// public override bool RayCast(ref Float2 location, out Control hit) { if (RayCastChildren(ref location, out hit)) return true; return base.RayCast(ref location, out hit); } internal bool RayCastChildren(ref Float2 location, out Control hit) { for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible) { IntersectsChildContent(child, location, out var childLocation); if (child.RayCast(ref childLocation, out hit)) return true; } } hit = null; return false; } /// public override void OnMouseEnter(Float2 location) { // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { // Enter child.OnMouseEnter(childLocation); } } } base.OnMouseEnter(location); } /// public override void OnMouseMove(Float2 location) { // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.IsMouseOver) { // Move child.OnMouseMove(childLocation); } else { // Enter child.OnMouseEnter(childLocation); } } else if (child.IsMouseOver) { // Leave child.OnMouseLeave(); } } } base.OnMouseMove(location); } /// public override void OnMouseLeave() { // Check all children collisions with mouse and fire events for them for (int i = 0; i < _children.Count; i++) { var child = _children[i]; if (child.Visible && child.Enabled && child.IsMouseOver) { // Leave child.OnMouseLeave(); } } base.OnMouseLeave(); } /// public override bool OnMouseWheel(Float2 location, float delta) { // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.OnMouseWheel(childLocation, delta)) { return true; } } } } return false; } /// public override bool OnMouseDown(Float2 location, MouseButton button) { // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.OnMouseDown(childLocation, button)) { return true; } } } } return false; } /// public override bool OnMouseUp(Float2 location, MouseButton button) { // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.OnMouseUp(childLocation, button)) { return true; } } } } return false; } /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.OnMouseDoubleClick(childLocation, button)) { return true; } } } } return false; } /// public override bool IsTouchPointerOver(int pointerId) { if (base.IsTouchPointerOver(pointerId)) return true; for (int i = 0; i < _children.Count; i++) { if (_children[i].IsTouchPointerOver(pointerId)) return true; } return false; } /// public override void OnTouchEnter(Float2 location, int pointerId) { for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled && !child.IsTouchPointerOver(pointerId)) { if (IntersectsChildContent(child, location, out var childLocation)) { child.OnTouchEnter(childLocation, pointerId); } } } base.OnTouchEnter(location, pointerId); } /// public override bool OnTouchDown(Float2 location, int pointerId) { for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (!child.IsTouchPointerOver(pointerId)) { child.OnTouchEnter(location, pointerId); } if (child.OnTouchDown(childLocation, pointerId)) { return true; } } } } return base.OnTouchDown(location, pointerId); } /// public override void OnTouchMove(Float2 location, int pointerId) { for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.IsTouchPointerOver(pointerId)) { child.OnTouchMove(childLocation, pointerId); } else { child.OnTouchEnter(childLocation, pointerId); } } else if (child.IsTouchPointerOver(pointerId)) { child.OnTouchLeave(pointerId); } } } base.OnTouchMove(location, pointerId); } /// public override bool OnTouchUp(Float2 location, int pointerId) { for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled && child.IsTouchPointerOver(pointerId)) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.OnTouchUp(childLocation, pointerId)) { return true; } } } } return base.OnTouchUp(location, pointerId); } /// public override void OnTouchLeave(int pointerId) { for (int i = 0; i < _children.Count; i++) { var child = _children[i]; if (child.Visible && child.Enabled && child.IsTouchPointerOver(pointerId)) { child.OnTouchLeave(pointerId); } } base.OnTouchLeave(pointerId); } /// public override bool OnCharInput(char c) { for (int i = 0; i < _children.Count; i++) { var child = _children[i]; if (child.Enabled && child.ContainsFocus) { return child.OnCharInput(c); } } return false; } /// public override bool OnKeyDown(KeyboardKeys key) { for (int i = 0; i < _children.Count; i++) { var child = _children[i]; if (child.Enabled && child.ContainsFocus) { return child.OnKeyDown(key); } } return false; } /// public override void OnKeyUp(KeyboardKeys key) { for (int i = 0; i < _children.Count; i++) { var child = _children[i]; if (child.Enabled && child.ContainsFocus) { child.OnKeyUp(key); break; } } } /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { // Base var result = base.OnDragEnter(ref location, data); // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { // Enter result = child.OnDragEnter(ref childLocation, data); if (result != DragDropEffect.None) break; } } } return result; } /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { // Base var result = base.OnDragMove(ref location, data); // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { if (child.IsDragOver) { // Move var tmpResult = child.OnDragMove(ref childLocation, data); if (tmpResult != DragDropEffect.None) result = tmpResult; } else { // Enter var tmpResult = child.OnDragEnter(ref childLocation, data); if (tmpResult != DragDropEffect.None) result = tmpResult; } } else if (child.IsDragOver) { // Leave child.OnDragLeave(); } } } return result; } /// public override void OnDragLeave() { // Base base.OnDragLeave(); // Check all children collisions with mouse and fire events for them for (int i = 0; i < _children.Count; i++) { var child = _children[i]; if (child.IsDragOver) { // Leave child.OnDragLeave(); } } } /// public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) { // Base var result = base.OnDragDrop(ref location, data); // Check all children collisions with mouse and fire events for them for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = _children[i]; if (child.Visible && child.Enabled) { if (IntersectsChildContent(child, location, out var childLocation)) { // Enter result = child.OnDragDrop(ref childLocation, data); if (result != DragDropEffect.None) break; } } } return result; } /// protected override void OnSizeChanged() { // Lock updates to prevent additional layout calculations bool wasLayoutLocked = _isLayoutLocked; _isLayoutLocked = true; base.OnSizeChanged(); // Fire event for (int i = 0; i < _children.Count; i++) { _children[i].OnParentResized(); } // Restore state _isLayoutLocked = wasLayoutLocked; // Arrange child controls PerformLayout(); } #endregion } }