Net.Like.Xue.Tokyo/Assets/Plugins/KINEMATION/MotionWarping/Editor/Widgets/WarpWindowWidget.cs

388 lines
12 KiB
C#

// Designed by KINEMATION, 2024.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace KINEMATION.MotionWarping.Editor.Widgets
{
public class WarpWindowWidget : IWarpWidgetInterface
{
class DraggableArea
{
private const float MinAreaWidth = 10f;
private const float BorderTolerance = 5f;
private const float BorderWidth = 2f;
public Rect Parent;
public float LocalStart;
public float LocalEnd;
private Color _color;
// -2: not hovered, -1: left border, 0: body, 1: right border.
public int GetHoveredPart(Vector2 mousePosition)
{
if (mousePosition.x >= GetRange().Item1 - BorderTolerance
&& mousePosition.x <= GetRange().Item1 + BorderTolerance)
{
return -1;
}
if (mousePosition.x >= GetRange().Item2 - BorderTolerance
&& mousePosition.x <= GetRange().Item2 + BorderTolerance)
{
return 1;
}
return 0;
}
public DraggableArea(float start, float end, Rect parent)
{
LocalStart = start;
LocalEnd = end;
_color = new Color(0f,0.5f,0.5f);
this.Parent = parent;
}
public float GetThickness()
{
float worldStart = Mathf.Lerp(Parent.xMin, Parent.xMax, LocalStart);
float worldEnd = Mathf.Lerp(Parent.xMin, Parent.xMax, LocalEnd);
return worldEnd - worldStart;
}
public Rect GetRect()
{
Rect rect = Parent;
rect.x = GetRange().Item1;
rect.width = GetThickness();
return rect;
}
public (float, float) GetRange()
{
float worldStart = Mathf.LerpUnclamped(Parent.xMin, Parent.xMax, LocalStart);
float worldEnd = Mathf.LerpUnclamped(Parent.xMin, Parent.xMax, LocalEnd);
return (worldStart, worldEnd);
}
public float GetLocal(float value)
{
if (Mathf.Approximately(Parent.xMin, Parent.xMax)) return 0f;
return (value - Parent.xMin) / (Parent.xMax - Parent.xMin);
}
public void RenderArea(float opacity)
{
// Draw body.
Rect areaRect = Parent;
areaRect.x = GetRange().Item1;
areaRect.width = GetThickness();
_color.a = opacity;
EditorGUI.DrawRect(areaRect, _color);
// Draw borders.
areaRect.x -= BorderWidth / 2f;
areaRect.width = BorderWidth;
EditorGUI.DrawRect(areaRect, new Color(1f, 1f, 1f, opacity));
areaRect.x = GetRange().Item2 - BorderWidth / 2f;
EditorGUI.DrawRect(areaRect, new Color(1f, 1f, 1f, opacity));
}
public bool Contains(Vector2 checkPosition)
{
bool x = checkPosition.x >= GetRange().Item1 && checkPosition.x <= GetRange().Item2;
bool y = checkPosition.y >= Parent.yMin && checkPosition.y <= Parent.yMax;
return x && y;
}
public void Resize(float mouseDelta, int part)
{
// Cache the values
float start = LocalStart;
float end = LocalEnd;
float left = GetRange().Item1;
float right = GetRange().Item2;
if (part == -1)
{
left -= part * mouseDelta;
}
if (part == 0)
{
left += mouseDelta;
right += mouseDelta;
}
if (part == 1)
{
right += mouseDelta;
}
LocalStart = GetLocal(left);
LocalEnd = GetLocal(right);
if (GetThickness() - BorderTolerance * 2f < MinAreaWidth)
{
LocalStart = start;
LocalEnd = end;
}
}
}
public delegate void WarpWindowCallback(int modifiedArea);
public WarpWindowCallback OnAreaModified;
private const float TimelineHeight = 20f;
private const float PlaybackTolerance = 8f;
private const float PlaybackWidth = 2f;
private Rect _timelineRect;
private List<DraggableArea> _draggableAreas = new List<DraggableArea>();
private DraggableArea _activeArea;
private int _resizeAction;
private bool _mousePressed;
private float _playbackPosition;
private bool _movingPlayback;
private bool IsAreaColliding(float proposedPosition, int areaIndex)
{
int areasCount = _draggableAreas.Count;
if (areasCount == 0)
{
return false;
}
int leftIndex = areaIndex - 1;
int rightIndex = areaIndex + 1;
// Check left border
var leftBorderCollision = proposedPosition <
(leftIndex < 0 ? _timelineRect.xMin : _draggableAreas[leftIndex].GetRange().Item2);
// Check right border
var rightBorderCollision = proposedPosition >
(rightIndex > areasCount - 1
? _timelineRect.xMax
: _draggableAreas[rightIndex].GetRange().Item1);
return leftBorderCollision || rightBorderCollision;
}
private void UpdateArea(int areaIndex, bool mouseAction)
{
Vector2 mousePosition = Event.current.mousePosition;
Vector2 mouseDelta = Event.current.delta;
DraggableArea area = _draggableAreas[areaIndex];
area.Parent = _timelineRect;
// Enable editing if the cursor is within the bounds.
if (mouseAction && _mousePressed && area.Contains(mousePosition))
{
_activeArea = area;
_resizeAction = area.GetHoveredPart(mousePosition);
Event.current.Use();
}
if (_activeArea != null && mouseAction && !_mousePressed)
{
_activeArea = null;
_resizeAction = -2;
Event.current.Use();
}
if (_activeArea == null || area != _activeArea)
{
area.RenderArea(1f);
return;
}
RenderCursorIcon(area, _resizeAction);
if (Event.current.type == EventType.MouseDrag)
{
// Cache the area current size
float areaStart = area.LocalStart;
float areaEnd = area.LocalEnd;
// Resize the area
area.Resize(mouseDelta.x, _resizeAction);
float start = area.GetRange().Item1;
float end = area.GetRange().Item2;
bool collideLeft = IsAreaColliding(start, areaIndex);
bool collideRight = IsAreaColliding(end, areaIndex);
// Check for any collisions
if (collideLeft || collideRight)
{
area.LocalStart = areaStart;
area.LocalEnd = areaEnd;
}
else
{
OnAreaModified?.Invoke(areaIndex);
}
Event.current.Use();
}
area.RenderArea(0.7f);
}
private void RenderCursorIcon(DraggableArea area, int hoveredPart)
{
if (hoveredPart == 0)
{
EditorGUIUtility.AddCursorRect(area.GetRect(), MouseCursor.Pan);
return;
}
EditorGUIUtility.AddCursorRect(area.GetRect(), MouseCursor.SlideArrow);
}
private void UpdateDraggableAreas()
{
bool prevMousePressed = _mousePressed;
if (Event.current.type == EventType.MouseDown)
{
_mousePressed = true;
}
if (Event.current.type == EventType.MouseUp)
{
_mousePressed = false;
}
for (int i = 0; i < _draggableAreas.Count; i++)
{
UpdateArea(i, prevMousePressed != _mousePressed);
}
}
public void ClearPhases()
{
_draggableAreas.Clear();
}
public void AddWarpPhase(float start, float end)
{
DraggableArea newArea = new DraggableArea(start, end, _timelineRect);
_draggableAreas.Add(newArea);
}
public (float, float) GetAreaSize(int areaIndex)
{
(float, float) size = (0f, 0f);
if (_draggableAreas.Count == 0 || areaIndex < 0 || areaIndex > _draggableAreas.Count - 1) return size;
var area = _draggableAreas[areaIndex];
size.Item1 = area.LocalStart;
size.Item2 = area.LocalEnd;
return size;
}
private void RenderPlayback(bool bAction = false)
{
float worldPlayback = Mathf.Lerp(_timelineRect.xMin, _timelineRect.xMax, _playbackPosition);
var playbackRect = _timelineRect;
playbackRect.y -= TimelineHeight;
Vector2 mousePosition = Event.current.mousePosition;
if (bAction && _mousePressed && playbackRect.Contains(mousePosition))
{
if (mousePosition.x >= worldPlayback - PlaybackTolerance
&& mousePosition.x <= worldPlayback + PlaybackTolerance)
{
_movingPlayback = true;
}
}
if (bAction && !_mousePressed)
{
_movingPlayback = false;
}
if (_movingPlayback)
{
playbackRect.height *= 2f;
EditorGUIUtility.AddCursorRect(playbackRect, MouseCursor.SlideArrow);
playbackRect.height /= 2f;
if (Event.current.type == EventType.MouseDrag)
{
worldPlayback += Event.current.delta.x;
_playbackPosition = Mathf.InverseLerp(_timelineRect.xMin, _timelineRect.xMax, worldPlayback);
Event.current.Use();
}
}
if(_activeArea != null && Event.current.shift)
{
if (_resizeAction is 0 or -1)
{
_playbackPosition = _activeArea.LocalStart;
}
if (_resizeAction == 1)
{
_playbackPosition = _activeArea.LocalEnd;
}
}
playbackRect.x = Mathf.Lerp(_timelineRect.xMin, _timelineRect.xMax, _playbackPosition);
playbackRect.x -= PlaybackWidth / 2f;
playbackRect.width = PlaybackWidth;
playbackRect.height = _timelineRect.yMax - playbackRect.y;
EditorGUI.DrawRect(playbackRect, Color.red);
}
public float GetPlayback()
{
return _playbackPosition;
}
public void Render()
{
float width = EditorGUIUtility.currentViewWidth;
EditorGUI.DrawRect(GUILayoutUtility.GetRect(width, TimelineHeight), new Color(0.15f, 0.15f, 0.15f));
var cacheRect = _timelineRect;
_timelineRect = GUILayoutUtility.GetRect(width, TimelineHeight);
if (Mathf.Approximately(_timelineRect.width, 1f))
{
_timelineRect = cacheRect;
}
EditorGUI.DrawRect(_timelineRect, new Color(0.15f, 0.15f, 0.15f));
bool action = _mousePressed;
UpdateDraggableAreas();
RenderPlayback(action != _mousePressed);
}
}
}