// 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)
{
}
}
}