mathe/Library/PackageCache/com.unity.timeline@1.7.5/Editor/treeview/Snapping/SnapEngine.cs
2024-09-20 20:30:10 +02:00

289 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
enum ManipulateEdges
{
Left,
Right,
Both
}
class SnapEngine
{
static readonly float k_MagnetInfluenceInPixels = 10.0f;
class SnapInfo
{
public double time { get; set; }
public bool showSnapHint { get; set; }
public bool IsInInfluenceZone(double currentTime, WindowState state)
{
var pos = state.TimeToPixel(currentTime);
var magnetPos = state.TimeToPixel(time);
return Math.Abs(pos - magnetPos) < k_MagnetInfluenceInPixels;
}
}
struct TimeBoundaries
{
public TimeBoundaries(double l, double r)
{
left = l;
right = r;
}
public readonly double left;
public readonly double right;
public TimeBoundaries Translate(double d)
{
return new TimeBoundaries(left + d, right + d);
}
}
public static bool displayDebugLayout;
readonly IAttractable m_Attractable;
readonly IAttractionHandler m_AttractionHandler;
readonly ManipulateEdges m_ManipulateEdges;
readonly WindowState m_State;
double m_GrabbedTime;
TimeBoundaries m_GrabbedTimes;
TimeBoundaries m_CurrentTimes;
readonly List<SnapInfo> m_Magnets = new List<SnapInfo>();
bool m_SnapEnabled;
public SnapEngine(IAttractable attractable, IAttractionHandler attractionHandler, ManipulateEdges manipulateEdges, WindowState state,
Vector2 mousePosition, IEnumerable<ISnappable> snappables = null)
{
m_Attractable = attractable;
m_ManipulateEdges = manipulateEdges;
m_AttractionHandler = attractionHandler;
m_State = state;
m_CurrentTimes = m_GrabbedTimes = new TimeBoundaries(m_Attractable.start, m_Attractable.end);
m_GrabbedTime = m_State.PixelToTime(mousePosition.x);
// Add Time zero as Magnet
AddMagnet(0.0, true, state);
// Add current Time as Magnet
// case1157280 only add current time as magnet if visible
if (TimelineWindow.instance.currentMode.ShouldShowTimeCursor(m_State))
AddMagnet(state.editSequence.time, true, state);
if (state.IsEditingASubTimeline())
{
// Add start and end of evaluable range as Magnets
// This includes the case where the master timeline has a fixed length
var range = state.editSequence.GetEvaluableRange();
AddMagnet(range.start, true, state);
AddMagnet(range.end, true, state);
}
else if (state.masterSequence.asset.durationMode == TimelineAsset.DurationMode.FixedLength)
{
// Add end sequence Time as Magnet
AddMagnet(state.masterSequence.asset.duration, true, state);
}
if (snappables == null)
snappables = GetVisibleSnappables(m_State);
foreach (var snappable in snappables)
{
if (!attractable.ShouldSnapTo(snappable))
continue;
var edges = snappable.SnappableEdgesFor(attractable, manipulateEdges);
foreach (var edge in edges)
AddMagnet(edge.time, edge.showSnapHint, state);
}
}
public static IEnumerable<ISnappable> GetVisibleSnappables(WindowState state)
{
Rect rect = TimelineWindow.instance.state.timeAreaRect;
rect.height = float.MaxValue;
return state.spacePartitioner.GetItemsInArea<ISnappable>(rect).ToArray();
}
void AddMagnet(double magnetTime, bool showSnapHint, WindowState state)
{
var magnet = m_Magnets.FirstOrDefault(m => m.time.Equals(magnetTime));
if (magnet == null)
{
if (IsMagnetInShownArea(magnetTime, state))
m_Magnets.Add(new SnapInfo { time = magnetTime, showSnapHint = showSnapHint });
}
else
{
magnet.showSnapHint |= showSnapHint;
}
}
static bool IsMagnetInShownArea(double time, WindowState state)
{
var shownArea = state.timeAreaShownRange;
return time >= shownArea.x && time <= shownArea.y;
}
SnapInfo GetMagnetAt(double time)
{
return m_Magnets.FirstOrDefault(m => m.time.Equals(time));
}
SnapInfo ClosestMagnet(double time)
{
SnapInfo candidate = null;
var min = double.MaxValue;
foreach (var magnetInfo in m_Magnets)
{
var m = Math.Abs(magnetInfo.time - time);
if (m < min)
{
candidate = magnetInfo;
min = m;
}
}
if (candidate != null && candidate.IsInInfluenceZone(time, m_State))
return candidate;
return null;
}
public void Snap(Vector2 currentMousePosition, EventModifiers modifiers)
{
var d = m_State.PixelToTime(currentMousePosition.x) - m_GrabbedTime;
m_CurrentTimes = m_GrabbedTimes.Translate(d);
bool isLeft = m_ManipulateEdges == ManipulateEdges.Left || m_ManipulateEdges == ManipulateEdges.Both;
bool isRight = m_ManipulateEdges == ManipulateEdges.Right || m_ManipulateEdges == ManipulateEdges.Both;
bool attracted = false;
m_SnapEnabled = modifiers == ManipulatorsUtils.actionModifier ? !m_State.edgeSnaps : m_State.edgeSnaps;
if (m_SnapEnabled)
{
SnapInfo leftActiveMagnet = null;
SnapInfo rightActiveMagnet = null;
if (isLeft)
leftActiveMagnet = ClosestMagnet(m_CurrentTimes.left);
if (isRight)
rightActiveMagnet = ClosestMagnet(m_CurrentTimes.right);
if (leftActiveMagnet != null || rightActiveMagnet != null)
{
attracted = true;
bool leftAttraction = false;
if (rightActiveMagnet == null)
{
// Attracted by a left magnet only.
leftAttraction = true;
}
else
{
if (leftActiveMagnet != null)
{
// Attracted by both magnets, choose the closest one.
var leftDistance = Math.Abs(leftActiveMagnet.time - m_CurrentTimes.left);
var rightDistance = Math.Abs(rightActiveMagnet.time - m_CurrentTimes.right);
leftAttraction = leftDistance <= rightDistance;
}
// else, Attracted by right magnet only
}
if (leftAttraction)
{
m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Left, leftActiveMagnet.time);
}
else
{
m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Right, rightActiveMagnet.time);
}
}
}
if (!attracted)
{
var time = isLeft ? m_CurrentTimes.left : m_CurrentTimes.right;
time = TimeReferenceUtility.SnapToFrameIfRequired(time);
m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.None, time);
}
}
public void OnGUI(bool showLeft = true, bool showRight = true)
{
if (displayDebugLayout)
{
// Display Magnet influence zone
foreach (var m in m_Magnets)
{
var window = TimelineWindow.instance;
var rect = new Rect(m_State.TimeToPixel(m.time) - k_MagnetInfluenceInPixels, window.state.timeAreaRect.yMax, 2f * k_MagnetInfluenceInPixels, m_State.windowHeight);
EditorGUI.DrawRect(rect, new Color(1f, 0f, 0f, 0.4f));
}
// Display Cursor position
var mousePos = Event.current.mousePosition;
var time = m_State.PixelToTime(mousePos.x);
var p = new Vector2(m_State.TimeToPixel(time), TimelineWindow.instance.state.timeAreaRect.yMax);
var s = new Vector2(1f, m_State.windowHeight);
EditorGUI.DrawRect(new Rect(p, s), Color.blue);
p = new Vector2(m_State.TimeToPixel(m_GrabbedTime), TimelineWindow.instance.state.timeAreaRect.yMax);
s = new Vector2(1f, m_State.windowHeight);
EditorGUI.DrawRect(new Rect(p, s), Color.red);
p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.left), TimelineWindow.instance.state.timeAreaRect.yMax);
s = new Vector2(1f, m_State.windowHeight);
EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.right), TimelineWindow.instance.state.timeAreaRect.yMax);
EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
}
if (m_SnapEnabled)
{
if (showLeft)
DrawMagnetLineAt(m_Attractable.start);
if (showRight)
DrawMagnetLineAt(m_Attractable.end);
}
}
void DrawMagnetLineAt(double time)
{
var magnet = GetMagnetAt(time);
if (magnet != null && magnet.showSnapHint)
Graphics.DrawLineAtTime(m_State, magnet.time, Color.white);
}
}
}