// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. namespace FlaxEngine.GUI { /// /// The root container control used to sort and manage child UICanvas controls. Helps with sending input events. /// /// [HideInEditor] internal sealed class CanvasContainer : ContainerControl { internal CanvasContainer() { AnchorPreset = AnchorPresets.StretchAll; Offsets = Margin.Zero; AutoFocus = false; } /// /// Sorts the canvases by order. /// public void SortCanvases() { Children.Sort(SortCanvas); } private int SortCanvas(Control a, Control b) { return ((CanvasRootControl)a).Canvas.Order - ((CanvasRootControl)b).Canvas.Order; } private bool IntersectsChildContent(CanvasRootControl child, ref Ray ray, out Vector2 childSpaceLocation) { // Inline bounds calculations (it will reuse world matrix) OrientedBoundingBox bounds = new OrientedBoundingBox(); bounds.Extents = new Vector3(child.Size * 0.5f, Mathf.Epsilon); child.Canvas.GetWorldMatrix(out var world); Matrix.Translation(bounds.Extents.X, bounds.Extents.Y, 0, out var offset); Matrix.Multiply(ref offset, ref world, out bounds.Transformation); // Hit test if (bounds.Intersects(ref ray, out Vector3 hitPoint)) { // Transform world-space hit point to canvas local-space world.Invert(); Vector3.Transform(ref hitPoint, ref world, out Vector3 localHitPoint); childSpaceLocation = new Vector2(localHitPoint); return child.ContainsPoint(ref childSpaceLocation); } childSpaceLocation = Vector2.Zero; return false; } /// public override void OnChildrenChanged() { SortCanvases(); base.OnChildrenChanged(); } /// protected override void DrawChildren() { // Draw all screen space canvases for (int i = 0; i < _children.Count; i++) { var child = (CanvasRootControl)_children[i]; if (child.Visible && child.Is2D) { child.Draw(); } } } /// public override bool IntersectsChildContent(Control child, Vector2 location, out Vector2 childSpaceLocation) { childSpaceLocation = Vector2.Zero; return ((CanvasRootControl)child).Is2D && base.IntersectsChildContent(child, location, out childSpaceLocation); } /// public override void OnMouseEnter(Vector2 location) { // 2D GUI first base.OnMouseEnter(location); // Calculate 3D mouse ray UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; if (child.Visible && child.Enabled && child.Is3D) { if (IntersectsChildContent(child, ref ray, out var childLocation)) { child.OnMouseEnter(childLocation); return; } } } } /// public override void OnMouseMove(Vector2 location) { // Calculate 3D mouse ray UICanvas.CalculateRay(ref location, out Ray ray); // Check all children collisions with mouse and fire events for them bool isFirst3DHandled = false; for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; if (child.Visible && child.Enabled) { // Fire events if (child.Is2D) { 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(); } } else { if (!isFirst3DHandled && IntersectsChildContent(child, ref ray, out var childLocation)) { isFirst3DHandled = true; if (child.IsMouseOver) { // Move child.OnMouseMove(childLocation); } else { // Enter child.OnMouseEnter(childLocation); } } else if (child.IsMouseOver) { // Leave child.OnMouseLeave(); } } } } } /// public override bool OnMouseWheel(Vector2 location, float delta) { // 2D GUI first if (base.OnMouseWheel(location, delta)) return true; // Calculate 3D mouse ray UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; if (child.Visible && child.Enabled && child.Is3D) { if (IntersectsChildContent(child, ref ray, out var childLocation)) { child.OnMouseWheel(childLocation, delta); return true; } } } return false; } /// public override bool OnMouseDown(Vector2 location, MouseButton button) { // 2D GUI first if (base.OnMouseDown(location, button)) return true; // Calculate 3D mouse ray UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; if (child.Visible && child.Enabled && child.Is3D) { if (IntersectsChildContent(child, ref ray, out var childLocation)) { child.OnMouseDown(childLocation, button); return true; } } } return false; } /// public override bool OnMouseUp(Vector2 location, MouseButton button) { // 2D GUI first if (base.OnMouseUp(location, button)) return true; // Calculate 3D mouse ray UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; if (child.Visible && child.Enabled && child.Is3D) { if (IntersectsChildContent(child, ref ray, out var childLocation)) { child.OnMouseUp(childLocation, button); return true; } } } return false; } /// public override bool OnMouseDoubleClick(Vector2 location, MouseButton button) { // 2D GUI first if (base.OnMouseDoubleClick(location, button)) return true; // Calculate 3D mouse ray UICanvas.CalculateRay(ref location, out Ray ray); // Test 3D for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--) { var child = (CanvasRootControl)_children[i]; if (child.Visible && child.Enabled && child.Is3D) { if (IntersectsChildContent(child, ref ray, out var childLocation)) { child.OnMouseDoubleClick(childLocation, button); return true; } } } return false; } } }