using Nova;
using System.Collections.Generic;
using UnityEngine;
namespace NovaSamples.UIControls
{
///
/// A simple positionable and resizable window component
///
///
///
[RequireComponent(typeof(UIBlock))]
public class Window : MonoBehaviour
{
///
/// The attached to this game object.
///
private UIBlock uiBlock = null;
///
/// The attached to this game object.
///
public UIBlock UIBlock
{
get
{
if (uiBlock == null)
{
uiBlock = GetComponent();
}
return uiBlock;
}
}
///
/// Cache the bounds of the window when the resize gesture begins
///
private Dictionary cachedBounds = new Dictionary();
private void OnEnable()
{
// Subscribe to gestures
UIBlock.AddGestureHandler(HandleWindowDragged);
UIBlock.AddGestureHandler(HandleResizeRegionDragged);
UIBlock.AddGestureHandler(HandleResizeRegionHovered);
UIBlock.AddGestureHandler(HandleResizeRegionUnhovered);
UIBlock.AddGestureHandler(HandleResizeRegionReleased);
UIBlock.AddGestureHandler(HandleResizeRegionGestureCanceled);
// Ensure known starting state
cachedBounds.Clear();
}
private void OnDisable()
{
// Unsubscribe from gestures
UIBlock.RemoveGestureHandler(HandleWindowDragged);
UIBlock.RemoveGestureHandler(HandleResizeRegionDragged);
UIBlock.RemoveGestureHandler(HandleResizeRegionHovered);
UIBlock.RemoveGestureHandler(HandleResizeRegionUnhovered);
UIBlock.RemoveGestureHandler(HandleResizeRegionReleased);
UIBlock.RemoveGestureHandler(HandleResizeRegionGestureCanceled);
// Restore the default cursor
SetCursor(null);
}
///
/// Move this window by the amount it was dragged
///
private void HandleWindowDragged(Gesture.OnDrag evt, WindowBar windowBar)
{
Vector3 positionDelta = evt.DragDeltaLocalSpace;
Transform parent = UIBlock.transform.parent;
if (parent != null)
{
// Transform drag delta into parent space
Matrix4x4 localToParent = Matrix4x4.TRS(UIBlock.transform.localPosition, UIBlock.transform.localRotation, UIBlock.transform.localScale);
positionDelta = localToParent.MultiplyVector(positionDelta);
}
UIBlock.TrySetLocalPosition(UIBlock.transform.localPosition + positionDelta);
}
///
/// Resize the window from the draggable region
///
private void HandleResizeRegionDragged(Gesture.OnDrag evt, WindowResizeRegion region)
{
// Determine which region was dragged based on its local position relative to the center of this window
Vector3 regionLocalPosition = UIBlock.transform.InverseTransformPoint(region.View.transform.position);
Vector3 regionVector = Sign(regionLocalPosition);
// Get the total translation along the region's draggable axes
Vector3 dragAxes = new Vector3(evt.DraggableAxes.X ? 1 : 0,
evt.DraggableAxes.Y ? 1 : 0,
evt.DraggableAxes.Z ? 1 : 0);
Vector3 dragTranslation = Vector3.Scale(evt.RawTranslationLocalSpace, dragAxes);
Vector3 sizeDelta = Vector3.Scale(dragTranslation, regionVector);
if (!cachedBounds.TryGetValue(evt.Interaction.ControlID, out Bounds startingBounds))
{
// Cache the initial bounds once the gesture starts,
// so we can track total size and position change
startingBounds = new Bounds(UIBlock.transform.localPosition, UIBlock.CalculatedSize.Value);
cachedBounds.Add(evt.Interaction.ControlID, startingBounds);
}
// Adjust the size of the window
UIBlock.Size = startingBounds.size + sizeDelta;
// Adjust the window's position such that the opposite side of the drag region remains in the same position
Vector3 rawPositionDelta = dragTranslation * 0.5f;
Vector3 positionDelta = ClampPositionDelta(rawPositionDelta, sizeDelta, startingBounds.size, UIBlock.SizeMinMax);
Transform parent = UIBlock.transform.parent;
if (parent != null)
{
// Transform drag delta into parent space
Matrix4x4 localToParent = Matrix4x4.TRS(startingBounds.center, UIBlock.transform.localRotation, UIBlock.transform.localScale);
positionDelta = localToParent.MultiplyVector(positionDelta);
}
UIBlock.TrySetLocalPosition(startingBounds.center + positionDelta);
}
///
/// Change the cursor to indicate how the window will resize if it's clicked and draggged
///
private void HandleResizeRegionHovered(Gesture.OnHover evt, WindowResizeRegion region)
{
SetCursor(region.Cursor);
}
///
/// Restore the cursor when a resize region is unhovered
///
private void HandleResizeRegionUnhovered(Gesture.OnUnhover evt, WindowResizeRegion region)
{
SetCursor(null);
}
///
/// Clear the cached size for the released pointer
///
private void HandleResizeRegionReleased(Gesture.OnRelease evt, WindowResizeRegion region) => cachedBounds.Remove(evt.Interaction.ControlID);
///
/// Clear the cached size for the canceled pointer
///
private void HandleResizeRegionGestureCanceled(Gesture.OnCancel evt, WindowResizeRegion region) => cachedBounds.Remove(evt.Interaction.ControlID);
private static Vector3 Sign(Vector3 v) => new Vector3(Sign(v.x), Sign(v.y), Sign(v.z));
///
/// Unity's method returns 1 when the provided float is 0.
///
/// The float to check the sign of.
/// The sign of .
private static float Sign(float f) => f < 0 ? -1 : f > 0 ? 1 : 0;
///
/// Clamp the such that the position only changes as much as the UIBlock is resized.
///
///
/// Handles the case where if the UIBlock has a minimum (or maximum) size, we don't want to continue moving the UIBlock once that minimum (or maximum) size is reached while the user is making the window smaller (or larger).
///
private static Vector3 ClampPositionDelta(Vector3 positionDelta, Vector3 sizeDelta, Vector3 currentSize, MinMax3 sizeRange)
{
return new Vector3(ClampPositionDelta(positionDelta.x, sizeDelta.x, currentSize.x, sizeRange.X),
ClampPositionDelta(positionDelta.y, sizeDelta.y, currentSize.y, sizeRange.Y),
ClampPositionDelta(positionDelta.z, sizeDelta.z, currentSize.z, sizeRange.Z));
}
///
/// Clamp the such that the position only changes as much as the UIBlock is resized.
///
///
/// Handles the case where if the UIBlock has a minimum (or maximum) size, we don't want to continue moving the UIBlock once that minimum (or maximum) size is reached while the user is making the window smaller (or larger).
///
private static float ClampPositionDelta(float rawPositionDelta, float sizeDelta, float currentSize, MinMax range)
{
float clampedSizeDelta = range.Clamp(sizeDelta + currentSize) - currentSize;
float clampedPositionDeltaRange = clampedSizeDelta * 0.5f;
float min = Mathf.Min(clampedPositionDeltaRange, -clampedPositionDeltaRange);
float max = Mathf.Max(clampedPositionDeltaRange, -clampedPositionDeltaRange);
return Mathf.Clamp(rawPositionDelta, min, max);
}
///
/// Assign the cursor to a custom texture, or set it to null to restore the default cursor.
///
/// The texture to assign to the cursor
private static void SetCursor(Texture2D cursorTexture)
{
// offset is 0 if the texture is null, otherwise we want to center the cursor texture
Vector2 offset = cursorTexture == null ? Vector2.zero : new Vector2(cursorTexture.width, cursorTexture.height) * 0.5f;
Cursor.SetCursor(cursorTexture, offset, CursorMode.Auto);
}
}
}