// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; namespace FlaxEngine.GUI { /// /// Root control implementation used by the actor. /// /// [HideInEditor] public sealed class CanvasRootControl : RootControl { private UICanvas _canvas; private Float2 _mousePosition; private float _navigationHeldTimeUp, _navigationHeldTimeDown, _navigationHeldTimeLeft, _navigationHeldTimeRight, _navigationHeldTimeSubmit; private float _navigationRateTimeUp, _navigationRateTimeDown, _navigationRateTimeLeft, _navigationRateTimeRight, _navigationRateTimeSubmit; /// /// Gets the owning canvas. /// public UICanvas Canvas => _canvas; /// /// Gets a value indicating whether canvas is 2D (screen-space). /// public bool Is2D => _canvas.RenderMode == CanvasRenderMode.ScreenSpace; /// /// Gets a value indicating whether canvas is 3D (world-space or camera-space). /// public bool Is3D => _canvas.RenderMode != CanvasRenderMode.ScreenSpace; /// /// Initializes a new instance of the class. /// /// The canvas. internal CanvasRootControl(UICanvas canvas) { _canvas = canvas; } /// /// Checks if the 3D canvas intersects with a given 3D mouse ray. /// /// The input ray to test (in world-space). /// Output canvas-space local position. /// True if canvas intersects with that point, otherwise false. public bool Intersects3D(ref Ray ray, out Float2 canvasLocation) { // Inline bounds calculations (it will reuse world matrix) var bounds = new OrientedBoundingBox { Extents = new Vector3(Size * 0.5f, Mathf.Epsilon) }; _canvas.GetWorldMatrix(out var world); Matrix.Translation((float)bounds.Extents.X, (float)bounds.Extents.Y, 0, out var offset); Matrix.Multiply(ref offset, ref world, out var boxWorld); boxWorld.Decompose(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); canvasLocation = new Float2(localHitPoint); return ContainsPoint(ref canvasLocation); } canvasLocation = Float2.Zero; return false; } /// public override CursorType Cursor { get => CursorType.Default; set { } } /// public override Control FocusedControl { get => Parent?.Root.FocusedControl; set { if (Parent != null) Parent.Root.FocusedControl = value; } } /// public override Float2 TrackingMouseOffset => Float2.Zero; /// public override Float2 MousePosition { get => _mousePosition; set { } } /// public override void StartTrackingMouse(Control control, bool useMouseScreenOffset) { var parent = Parent?.Root; parent?.StartTrackingMouse(control, useMouseScreenOffset); } /// public override void EndTrackingMouse() { var parent = Parent?.Root; parent?.EndTrackingMouse(); } /// public override bool GetKey(KeyboardKeys key) { return Input.GetKey(key); } /// public override bool GetKeyDown(KeyboardKeys key) { return Input.GetKeyDown(key); } /// public override bool GetKeyUp(KeyboardKeys key) { return Input.GetKeyUp(key); } /// public override bool GetMouseButton(MouseButton button) { return Input.GetMouseButton(button); } /// public override bool GetMouseButtonDown(MouseButton button) { return Input.GetMouseButtonDown(button); } /// public override bool GetMouseButtonUp(MouseButton button) { return Input.GetMouseButtonUp(button); } /// public override Float2 PointToParent(ref Float2 location) { if (Is2D) return base.PointToParent(ref location); var camera = Camera.MainCamera; if (!camera) return location; // Transform canvas local-space point to the game root location _canvas.GetWorldMatrix(out Matrix world); Vector3 locationCanvasSpace = new Vector3(location, 0.0f); Vector3.Transform(ref locationCanvasSpace, ref world, out Vector3 locationWorldSpace); camera.ProjectPoint(locationWorldSpace, out location); return location; } /// public override Float2 PointFromParent(ref Float2 locationParent) { if (Is2D) return base.PointFromParent(ref locationParent); var camera = Camera.MainCamera; if (!camera) return locationParent; // Use world-space ray to convert it to the local-space of the canvas UICanvas.CalculateRay(ref locationParent, out Ray ray); Intersects3D(ref ray, out var location); return location; } /// public override bool ContainsPoint(ref Float2 location) { return base.ContainsPoint(ref location) && (_canvas.TestCanvasIntersection == null || _canvas.TestCanvasIntersection(ref location)); } /// public override void Update(float deltaTime) { // UI navigation if (_canvas.ReceivesEvents) { UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp); UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown); UpdateNavigation(deltaTime, _canvas.NavigateLeft.Name, NavDirection.Left, ref _navigationHeldTimeLeft, ref _navigationRateTimeLeft); UpdateNavigation(deltaTime, _canvas.NavigateRight.Name, NavDirection.Right, ref _navigationHeldTimeRight, ref _navigationRateTimeRight); UpdateNavigation(deltaTime, _canvas.NavigateSubmit.Name, ref _navigationHeldTimeSubmit, ref _navigationRateTimeSubmit, SubmitFocused); } else { _navigationHeldTimeUp = _navigationHeldTimeDown = _navigationHeldTimeLeft = _navigationHeldTimeRight = 0; _navigationRateTimeUp = _navigationRateTimeDown = _navigationRateTimeLeft = _navigationRateTimeRight = 0; } base.Update(deltaTime); } private void UpdateNavigation(float deltaTime, string actionName, NavDirection direction, ref float heldTime, ref float rateTime) { if (Input.GetAction(actionName)) { if (heldTime <= Mathf.Epsilon) { Navigate(direction); } if (heldTime > _canvas.NavigationInputRepeatDelay) { rateTime += deltaTime; } if (rateTime > _canvas.NavigationInputRepeatRate) { Navigate(direction); rateTime = 0; } heldTime += deltaTime; } else { heldTime = rateTime = 0; } } private void UpdateNavigation(float deltaTime, string actionName, ref float heldTime, ref float rateTime, Action action) { if (Input.GetAction(actionName)) { if (heldTime <= Mathf.Epsilon) { action(); } if (heldTime > _canvas.NavigationInputRepeatDelay) { rateTime += deltaTime; } if (rateTime > _canvas.NavigationInputRepeatRate) { action(); rateTime = 0; } heldTime += deltaTime; } else { heldTime = rateTime = 0; } } /// public override bool OnCharInput(char c) { if (!_canvas.ReceivesEvents) return false; return base.OnCharInput(c); } /// public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) { if (!_canvas.ReceivesEvents) return DragDropEffect.None; return base.OnDragDrop(ref location, data); } /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { if (!_canvas.ReceivesEvents) return DragDropEffect.None; return base.OnDragEnter(ref location, data); } /// public override void OnDragLeave() { if (!_canvas.ReceivesEvents) return; base.OnDragLeave(); } /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { if (!_canvas.ReceivesEvents) return DragDropEffect.None; return base.OnDragMove(ref location, data); } /// public override bool OnKeyDown(KeyboardKeys key) { if (!_canvas.ReceivesEvents) return false; return base.OnKeyDown(key); } /// public override void OnKeyUp(KeyboardKeys key) { if (!_canvas.ReceivesEvents) return; base.OnKeyUp(key); } /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { if (!_canvas.ReceivesEvents) return false; return base.OnMouseDoubleClick(location, button); } /// public override bool OnMouseDown(Float2 location, MouseButton button) { if (!_canvas.ReceivesEvents) return false; return base.OnMouseDown(location, button); } /// public override void OnMouseEnter(Float2 location) { if (!_canvas.ReceivesEvents) return; _mousePosition = location; base.OnMouseEnter(location); } /// public override void OnMouseLeave() { _mousePosition = Float2.Zero; if (!_canvas.ReceivesEvents) return; base.OnMouseLeave(); } /// public override void OnMouseMove(Float2 location) { if (!_canvas.ReceivesEvents) return; _mousePosition = location; base.OnMouseMove(location); } /// public override bool OnMouseUp(Float2 location, MouseButton button) { if (!_canvas.ReceivesEvents) return false; return base.OnMouseUp(location, button); } /// public override bool OnMouseWheel(Float2 location, float delta) { if (!_canvas.ReceivesEvents) return false; return base.OnMouseWheel(location, delta); } /// public override void OnTouchEnter(Float2 location, int pointerId) { if (!_canvas.ReceivesEvents) return; base.OnTouchEnter(location, pointerId); } /// public override bool OnTouchDown(Float2 location, int pointerId) { if (!_canvas.ReceivesEvents) return false; return base.OnTouchDown(location, pointerId); } /// public override void OnTouchMove(Float2 location, int pointerId) { if (!_canvas.ReceivesEvents) return; base.OnTouchMove(location, pointerId); } /// public override bool OnTouchUp(Float2 location, int pointerId) { if (!_canvas.ReceivesEvents) return false; return base.OnTouchUp(location, pointerId); } /// public override void OnTouchLeave(int pointerId) { if (!_canvas.ReceivesEvents) return; base.OnTouchLeave(pointerId); } /// public override void Focus() { } /// public override void DoDragDrop(DragData data) { } } }