|
|
@@ -63,9 +63,12 @@ namespace BansheeEditor
|
|
|
private const int EVENTS_HEIGHT = 15;
|
|
|
private const int SIDEBAR_WIDTH = 30;
|
|
|
private const int DRAG_START_DISTANCE = 3;
|
|
|
+ private const float DRAG_SCALE = 3.0f;
|
|
|
+ private const float ZOOM_SCALE = 0.1f/120.0f; // One scroll step is usually 120 units, we want 1/10 of that
|
|
|
|
|
|
private EditorWindow window;
|
|
|
private GUILayout gui;
|
|
|
+ private GUILayout mainPanel;
|
|
|
private GUIPanel drawingPanel;
|
|
|
private GUIPanel eventsPanel;
|
|
|
|
|
|
@@ -77,6 +80,12 @@ namespace BansheeEditor
|
|
|
private GUICurveDrawing guiCurveDrawing;
|
|
|
private GUIGraphValues guiSidebar;
|
|
|
|
|
|
+ private int scrollBarWidth;
|
|
|
+ private int scrollBarHeight;
|
|
|
+
|
|
|
+ private GUIResizeableScrollBarH horzScrollBar;
|
|
|
+ private GUIResizeableScrollBarV vertScrollBar;
|
|
|
+
|
|
|
private ContextMenu blankContextMenu;
|
|
|
private ContextMenu keyframeContextMenu;
|
|
|
private ContextMenu blankEventContextMenu;
|
|
|
@@ -256,9 +265,6 @@ namespace BansheeEditor
|
|
|
this.gui = gui;
|
|
|
this.showEvents = showEvents;
|
|
|
|
|
|
- this.width = width;
|
|
|
- this.height = height;
|
|
|
-
|
|
|
blankContextMenu = new ContextMenu();
|
|
|
blankContextMenu.AddItem("Add keyframe", AddKeyframeAtPosition);
|
|
|
|
|
|
@@ -283,991 +289,1402 @@ namespace BansheeEditor
|
|
|
eventContextMenu.AddItem("Delete", DeleteSelectedEvents);
|
|
|
eventContextMenu.AddItem("Edit", EditSelectedEvent);
|
|
|
|
|
|
- GUIPanel timelinePanel = gui.AddPanel();
|
|
|
- guiTimeline = new GUIGraphTime(timelinePanel, width, TIMELINE_HEIGHT);
|
|
|
+ horzScrollBar = new GUIResizeableScrollBarH();
|
|
|
+ horzScrollBar.OnScrollOrResize += OnHorzScrollOrResize;
|
|
|
+
|
|
|
+ vertScrollBar = new GUIResizeableScrollBarV();
|
|
|
+ vertScrollBar.OnScrollOrResize += OnVertScrollOrResize;
|
|
|
+
|
|
|
+ GUILayout curveLayoutHorz = gui.AddLayoutX();
|
|
|
+ GUILayout horzScrollBarLayout = gui.AddLayoutX();
|
|
|
+ horzScrollBarLayout.AddElement(horzScrollBar);
|
|
|
+ horzScrollBarLayout.AddFlexibleSpace();
|
|
|
+
|
|
|
+ mainPanel = curveLayoutHorz.AddPanel();
|
|
|
+ curveLayoutHorz.AddElement(vertScrollBar);
|
|
|
+ curveLayoutHorz.AddFlexibleSpace();
|
|
|
+
|
|
|
+ scrollBarHeight = horzScrollBar.Bounds.height;
|
|
|
+ scrollBarWidth = vertScrollBar.Bounds.width;
|
|
|
+
|
|
|
+ this.width = Math.Max(0, width - scrollBarWidth);
|
|
|
+ this.height = Math.Max(0, height - scrollBarHeight);
|
|
|
|
|
|
- GUIPanel timelineBgPanel = gui.AddPanel(1);
|
|
|
+ GUIPanel timelinePanel = mainPanel.AddPanel();
|
|
|
+ guiTimeline = new GUIGraphTime(timelinePanel, this.width, TIMELINE_HEIGHT);
|
|
|
+
|
|
|
+ GUIPanel timelineBgPanel = mainPanel.AddPanel(1);
|
|
|
|
|
|
timelineBackground = new GUITexture(null, EditorStyles.Header);
|
|
|
- timelineBackground.Bounds = new Rect2I(0, 0, width, TIMELINE_HEIGHT + VERT_PADDING);
|
|
|
+ timelineBackground.Bounds = new Rect2I(0, 0, this.width, TIMELINE_HEIGHT + VERT_PADDING);
|
|
|
timelineBgPanel.AddElement(timelineBackground);
|
|
|
|
|
|
int eventsHeaderHeight = 0;
|
|
|
if (showEvents)
|
|
|
{
|
|
|
- eventsPanel = gui.AddPanel();
|
|
|
+ eventsPanel = mainPanel.AddPanel();
|
|
|
eventsPanel.SetPosition(0, TIMELINE_HEIGHT + VERT_PADDING);
|
|
|
- guiEvents = new GUIAnimEvents(eventsPanel, width, EVENTS_HEIGHT);
|
|
|
+ guiEvents = new GUIAnimEvents(eventsPanel, this.width, EVENTS_HEIGHT);
|
|
|
|
|
|
GUIPanel eventsBgPanel = eventsPanel.AddPanel(1);
|
|
|
|
|
|
eventsBackground = new GUITexture(null, EditorStyles.Header);
|
|
|
- eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
|
|
|
+ eventsBackground.Bounds = new Rect2I(0, 0, this.width, EVENTS_HEIGHT + VERT_PADDING);
|
|
|
eventsBgPanel.AddElement(eventsBackground);
|
|
|
|
|
|
eventsHeaderHeight = EVENTS_HEIGHT;
|
|
|
}
|
|
|
|
|
|
- drawingPanel = gui.AddPanel();
|
|
|
+ drawingPanel = mainPanel.AddPanel();
|
|
|
drawingPanel.SetPosition(0, TIMELINE_HEIGHT + eventsHeaderHeight + VERT_PADDING);
|
|
|
|
|
|
- guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT - eventsHeaderHeight - VERT_PADDING * 2, curveInfos);
|
|
|
+ guiCurveDrawing = new GUICurveDrawing(drawingPanel, this.width, this.height - TIMELINE_HEIGHT -
|
|
|
+ eventsHeaderHeight - VERT_PADDING * 2, curveInfos);
|
|
|
guiCurveDrawing.SetRange(60.0f, 20.0f);
|
|
|
|
|
|
- GUIPanel sidebarPanel = gui.AddPanel(-10);
|
|
|
+ GUIPanel sidebarPanel = mainPanel.AddPanel(-10);
|
|
|
sidebarPanel.SetPosition(0, TIMELINE_HEIGHT + eventsHeaderHeight + VERT_PADDING);
|
|
|
|
|
|
- guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - eventsHeaderHeight - VERT_PADDING * 2);
|
|
|
+ guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, this.height - TIMELINE_HEIGHT -
|
|
|
+ eventsHeaderHeight - VERT_PADDING * 2);
|
|
|
guiSidebar.SetRange(-10.0f, 10.0f);
|
|
|
+
|
|
|
+ horzScrollBar.SetWidth(this.width);
|
|
|
+ vertScrollBar.SetHeight(this.height);
|
|
|
+
|
|
|
+ UpdateScrollBarSize();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Converts coordinate in curve space (time, value) into pixel coordinates relative to the curve drawing area
|
|
|
- /// origin.
|
|
|
+ /// Change the set of curves to display.
|
|
|
/// </summary>
|
|
|
- /// <param name="curveCoords">Time and value of the location to convert.</param>
|
|
|
- /// <returns>Coordinates relative to curve drawing area's origin, in pixels.</returns>
|
|
|
- public Vector2I CurveToPixelSpace(Vector2 curveCoords)
|
|
|
+ /// <param name="curveInfos">New set of curves to draw on the GUI element.</param>
|
|
|
+ public void SetCurves(CurveDrawInfo[] curveInfos)
|
|
|
{
|
|
|
- return guiCurveDrawing.CurveToPixelSpace(curveCoords);
|
|
|
+ this.curveInfos = curveInfos;
|
|
|
+ guiCurveDrawing.SetCurves(curveInfos);
|
|
|
+
|
|
|
+ Redraw();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Converts coordinates in window space (relative to the parent window origin) into coordinates in curve space.
|
|
|
+ /// Changes curve rendering mode. Normally the curves are drawn individually, but when range rendering is enabled
|
|
|
+ /// the area between the first two curves is drawn instead. This setting is ignored if less than two curves are
|
|
|
+ /// present. More than two curves are also ignored.
|
|
|
/// </summary>
|
|
|
- /// <param name="windowPos">Coordinates relative to parent editor window, in pixels.</param>
|
|
|
- /// <param name="curveCoord">Curve coordinates within the range as specified by <see cref="Range"/>. Only
|
|
|
- /// valid when function returns true.</param>
|
|
|
- /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
|
|
|
- public bool WindowToCurveSpace(Vector2I windowPos, out Vector2 curveCoord)
|
|
|
+ /// <param name="drawRange">True to enable range rendering mode, false to enable individual curve rendering.</param>
|
|
|
+ public void SetDrawRange(bool drawRange)
|
|
|
{
|
|
|
- Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
|
|
|
- Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
-
|
|
|
- Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
- Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
+ drawCurveRange = drawRange;
|
|
|
+ guiCurveDrawing.SetDrawRange(drawRange);
|
|
|
|
|
|
- return guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord);
|
|
|
+ Redraw();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Handles input. Should be called by the owning window whenever a pointer is pressed.
|
|
|
+ /// Change the physical size of the GUI element.
|
|
|
/// </summary>
|
|
|
- /// <param name="ev">Object containing pointer press event information.</param>
|
|
|
- internal void OnPointerPressed(PointerEvent ev)
|
|
|
+ /// <param name="width">Width of the element in pixels.</param>
|
|
|
+ /// <param name="height">Height of the element in pixels.</param>
|
|
|
+ public void SetSize(int width, int height)
|
|
|
{
|
|
|
- if (ev.IsUsed)
|
|
|
- return;
|
|
|
-
|
|
|
- Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
|
|
|
-
|
|
|
- Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
|
|
|
- Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
-
|
|
|
- bool isOverEditor = pointerPos.x >= 0 && pointerPos.x < width && pointerPos.y >= 0 && pointerPos.y < height;
|
|
|
- if (!isOverEditor)
|
|
|
- return;
|
|
|
- else
|
|
|
- OnClicked?.Invoke();
|
|
|
-
|
|
|
- Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
- Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
-
|
|
|
- Rect2I eventBounds = eventsPanel.Bounds;
|
|
|
- Vector2I eventPos = pointerPos - new Vector2I(eventBounds.x, eventBounds.y);
|
|
|
+ this.width = Math.Max(0, width - scrollBarWidth);
|
|
|
+ this.height = Math.Max(0, height - scrollBarHeight);
|
|
|
|
|
|
- if (ev.Button == PointerButton.Left)
|
|
|
+ int eventsHeaderHeight = 0;
|
|
|
+ if (showEvents)
|
|
|
{
|
|
|
- Vector2 curveCoord;
|
|
|
- if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord, true))
|
|
|
- {
|
|
|
- KeyframeRef keyframeRef;
|
|
|
- if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
|
|
|
- {
|
|
|
- TangentRef tangentRef;
|
|
|
- if (guiCurveDrawing.FindTangent(drawingPos, out tangentRef))
|
|
|
- {
|
|
|
- isMousePressedOverTangent = true;
|
|
|
- dragStart = drawingPos;
|
|
|
- draggedTangent = tangentRef;
|
|
|
- }
|
|
|
- else
|
|
|
- ClearSelection();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- if (!IsSelected(keyframeRef))
|
|
|
- {
|
|
|
- if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
|
|
|
- ClearSelection();
|
|
|
-
|
|
|
- SelectKeyframe(keyframeRef);
|
|
|
- }
|
|
|
-
|
|
|
- isMousePressedOverKey = true;
|
|
|
- dragStart = drawingPos;
|
|
|
- }
|
|
|
+ eventsHeaderHeight = EVENTS_HEIGHT;
|
|
|
+ guiEvents.SetSize(this.width, EVENTS_HEIGHT);
|
|
|
+ eventsBackground.Bounds = new Rect2I(0, 0, this.width, EVENTS_HEIGHT + VERT_PADDING);
|
|
|
+ }
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- int frameIdx = guiTimeline.GetFrame(pointerPos);
|
|
|
+ guiTimeline.SetSize(this.width, TIMELINE_HEIGHT);
|
|
|
+ guiCurveDrawing.SetSize(this.width, this.height - TIMELINE_HEIGHT - eventsHeaderHeight);
|
|
|
+ guiSidebar.SetSize(SIDEBAR_WIDTH, this.height - TIMELINE_HEIGHT - eventsHeaderHeight);
|
|
|
|
|
|
- if (frameIdx != -1)
|
|
|
- SetMarkedFrame(frameIdx);
|
|
|
- else
|
|
|
- {
|
|
|
- int eventIdx;
|
|
|
- if (showEvents && guiEvents.FindEvent(eventPos, out eventIdx))
|
|
|
- {
|
|
|
- if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
|
|
|
- ClearSelection();
|
|
|
+ timelineBackground.Bounds = new Rect2I(0, 0, this.width, TIMELINE_HEIGHT + VERT_PADDING);
|
|
|
|
|
|
- events[eventIdx].selected = true;
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
+ horzScrollBar.SetWidth(this.width);
|
|
|
+ vertScrollBar.SetHeight(this.height);
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
- }
|
|
|
+ UpdateScrollBarSize();
|
|
|
+ UpdateScrollBarPosition();
|
|
|
+ }
|
|
|
|
|
|
- OnFrameSelected?.Invoke(frameIdx);
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Number of frames per second, used for frame selection and marking.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="fps">Number of prames per second.</param>
|
|
|
+ public void SetFPS(int fps)
|
|
|
+ {
|
|
|
+ guiTimeline.SetFPS(fps);
|
|
|
+ guiCurveDrawing.SetFPS(fps);
|
|
|
|
|
|
- isPointerHeld = true;
|
|
|
- }
|
|
|
- else if (ev.Button == PointerButton.Right)
|
|
|
- {
|
|
|
- Vector2 curveCoord;
|
|
|
- if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord, true))
|
|
|
- {
|
|
|
- contextClickPosition = drawingPos;
|
|
|
+ if(showEvents)
|
|
|
+ guiEvents.SetFPS(fps);
|
|
|
|
|
|
- KeyframeRef keyframeRef;
|
|
|
- if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
+ Redraw();
|
|
|
+ }
|
|
|
|
|
|
- blankContextMenu.Open(pointerPos, gui);
|
|
|
+ /// <summary>
|
|
|
+ /// Sets a scene object that will be used for enumerating components/methods used for adding events.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="so">Scene object containing the animation component.</param>
|
|
|
+ public void SetEventSceneObject(SceneObject so)
|
|
|
+ {
|
|
|
+ eventsSO = so;
|
|
|
+ }
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // If clicked outside of current selection, just select the one keyframe
|
|
|
- if (!IsSelected(keyframeRef))
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
- SelectKeyframe(keyframeRef);
|
|
|
+ /// <summary>
|
|
|
+ /// Returns time for a frame with the specified index. Depends on set range and FPS.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="frameIdx">Index of the frame (not a key-frame) to get the time for.</param>
|
|
|
+ /// <returns>Time of the frame with the provided index. </returns>
|
|
|
+ public float GetTimeForFrame(int frameIdx)
|
|
|
+ {
|
|
|
+ return guiCurveDrawing.GetTimeForFrame(frameIdx);
|
|
|
+ }
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Sets the frame at which to display the frame marker.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
|
|
|
+ public void SetMarkedFrame(int frameIdx)
|
|
|
+ {
|
|
|
+ markedFrameIdx = frameIdx;
|
|
|
|
|
|
- keyframeContextMenu.Open(pointerPos, gui);
|
|
|
- }
|
|
|
- }
|
|
|
- else if (showEvents && guiEvents.GetFrame(eventPos) != -1) // Clicked over events bar
|
|
|
- {
|
|
|
- contextClickPosition = eventPos;
|
|
|
+ guiTimeline.SetMarkedFrame(frameIdx);
|
|
|
+ guiCurveDrawing.SetMarkedFrame(frameIdx);
|
|
|
|
|
|
- int eventIdx;
|
|
|
- if (!guiEvents.FindEvent(eventPos, out eventIdx))
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
+ if(showEvents)
|
|
|
+ guiEvents.SetMarkedFrame(frameIdx);
|
|
|
|
|
|
- blankEventContextMenu.Open(pointerPos, gui);
|
|
|
+ Redraw();
|
|
|
+ }
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // If clicked outside of current selection, just select the one event
|
|
|
- if (!events[eventIdx].selected)
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
- events[eventIdx].selected = true;
|
|
|
+ /// <summary>
|
|
|
+ /// Adds a new keyframe at the currently selected frame.
|
|
|
+ /// </summary>
|
|
|
+ public void AddKeyFrameAtMarker()
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
+ if (!disableCurveEdit)
|
|
|
+ {
|
|
|
+ foreach (var curveInfo in curveInfos)
|
|
|
+ {
|
|
|
+ float t = guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
|
|
|
+ float value = curveInfo.curve.Evaluate(t);
|
|
|
|
|
|
- eventContextMenu.Open(pointerPos, gui);
|
|
|
- }
|
|
|
+ curveInfo.curve.AddOrUpdateKeyframe(t, value);
|
|
|
+ curveInfo.curve.Apply();
|
|
|
}
|
|
|
}
|
|
|
+ else
|
|
|
+ ShowReadOnlyMessage();
|
|
|
+
|
|
|
+ OnCurveModified?.Invoke();
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Handles input. Should be called by the owning window whenever a pointer is double-clicked.
|
|
|
+ /// Adds a new event at the currently selected event.
|
|
|
/// </summary>
|
|
|
- /// <param name="ev">Object containing pointer press event information.</param>
|
|
|
- internal void OnPointerDoubleClicked(PointerEvent ev)
|
|
|
+ public void AddEventAtMarker()
|
|
|
{
|
|
|
- if (ev.IsUsed)
|
|
|
+ ClearSelection();
|
|
|
+
|
|
|
+ if (!showEvents)
|
|
|
return;
|
|
|
|
|
|
- Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
|
|
|
+ float eventTime = guiEvents.GetTimeForFrame(markedFrameIdx);
|
|
|
+ EventInfo eventInfo = new EventInfo();
|
|
|
+ eventInfo.animEvent = new AnimationEvent("", eventTime);
|
|
|
|
|
|
- Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
|
|
|
- Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
+ events.Add(eventInfo);
|
|
|
+ OnEventAdded?.Invoke();
|
|
|
|
|
|
- bool isOverEditor = pointerPos.x >= 0 && pointerPos.x < width && pointerPos.y >= 0 && pointerPos.y < height;
|
|
|
- if (!isOverEditor)
|
|
|
+ UpdateEventsGUI();
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+
|
|
|
+ StartEventEdit(events.Count - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Rebuilds GUI displaying the animation events list.
|
|
|
+ /// </summary>
|
|
|
+ private void UpdateEventsGUI()
|
|
|
+ {
|
|
|
+ if (!showEvents)
|
|
|
return;
|
|
|
|
|
|
- Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
- Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
+ AnimationEvent[] animEvents = new AnimationEvent[events.Count];
|
|
|
+ bool[] selected = new bool[events.Count];
|
|
|
|
|
|
- if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out var curveCoord, true))
|
|
|
+ for (int i = 0; i < events.Count; i++)
|
|
|
{
|
|
|
- int curveIdx = guiCurveDrawing.FindCurve(drawingPos);
|
|
|
- if (curveIdx == -1)
|
|
|
- return;
|
|
|
-
|
|
|
- AddKeyframe(curveIdx, curveCoord.x);
|
|
|
+ animEvents[i] = events[i].animEvent;
|
|
|
+ selected[i] = events[i].selected;
|
|
|
}
|
|
|
+
|
|
|
+ guiEvents.SetEvents(animEvents, selected);
|
|
|
+ guiEvents.Rebuild();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Handles input. Should be called by the owning window whenever a pointer is moved.
|
|
|
+ /// Rebuilds the entire curve editor GUI.
|
|
|
/// </summary>
|
|
|
- /// <param name="ev">Object containing pointer move event information.</param>
|
|
|
- internal void OnPointerMoved(PointerEvent ev)
|
|
|
+ public void Redraw()
|
|
|
{
|
|
|
- if (ev.Button != PointerButton.Left)
|
|
|
- return;
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ guiTimeline.Rebuild();
|
|
|
+ guiSidebar.Rebuild();
|
|
|
|
|
|
- if (isPointerHeld)
|
|
|
+ if(showEvents)
|
|
|
+ guiEvents.Rebuild();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Changes the tangent mode for all currently selected keyframes.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="mode">Tangent mode to set. If only in or out tangent mode is provided, the mode for the opposite
|
|
|
+ /// tangent will be kept as is.</param>
|
|
|
+ private void ChangeSelectionTangentMode(TangentMode mode)
|
|
|
+ {
|
|
|
+ if (disableCurveEdit)
|
|
|
{
|
|
|
- Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
|
|
|
+ ShowReadOnlyMessage();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
|
|
|
- Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
+ foreach (var selectedEntry in selectedKeyframes)
|
|
|
+ {
|
|
|
+ EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
|
|
|
|
|
|
- if (isMousePressedOverKey || isMousePressedOverTangent)
|
|
|
+ foreach (var keyframeIdx in selectedEntry.keyIndices)
|
|
|
{
|
|
|
- Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
- Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
-
|
|
|
- if (!isDragInProgress)
|
|
|
+ if (mode == TangentMode.Auto || mode == TangentMode.Free)
|
|
|
+ curve.SetTangentMode(keyframeIdx, mode);
|
|
|
+ else
|
|
|
{
|
|
|
- int distance = Vector2I.Distance(drawingPos, dragStart);
|
|
|
- if (distance >= DRAG_START_DISTANCE)
|
|
|
- {
|
|
|
- if (isMousePressedOverKey && !disableCurveEdit)
|
|
|
- {
|
|
|
- draggedKeyframes.Clear();
|
|
|
- foreach (var selectedEntry in selectedKeyframes)
|
|
|
- {
|
|
|
- EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
|
|
|
- KeyFrame[] keyFrames = curve.KeyFrames;
|
|
|
+ TangentMode newMode = curve.TangentModes[keyframeIdx];
|
|
|
|
|
|
- DraggedKeyframes newEntry = new DraggedKeyframes();
|
|
|
- newEntry.curveIdx = selectedEntry.curveIdx;
|
|
|
- draggedKeyframes.Add(newEntry);
|
|
|
+ if (mode.HasFlag((TangentMode) TangentType.In))
|
|
|
+ {
|
|
|
+ // Replace only the in tangent mode, keeping the out tangent as is
|
|
|
+ TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
|
|
|
+ TangentMode.InStep);
|
|
|
|
|
|
- foreach (var keyframeIdx in selectedEntry.keyIndices)
|
|
|
- newEntry.keys.Add(new DraggedKeyframe(keyframeIdx, keyFrames[keyframeIdx]));
|
|
|
- }
|
|
|
- }
|
|
|
+ newMode &= ~inFlags;
|
|
|
+ newMode |= (mode & inFlags);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Replace only the out tangent mode, keeping the in tangent as is
|
|
|
+ TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
|
|
|
+ TangentMode.OutStep);
|
|
|
|
|
|
- isDragInProgress = true;
|
|
|
+ newMode &= ~outFlags;
|
|
|
+ newMode |= (mode & outFlags);
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (isDragInProgress)
|
|
|
- {
|
|
|
- if (isMousePressedOverKey && !disableCurveEdit)
|
|
|
- {
|
|
|
- Vector2 diff = Vector2.Zero;
|
|
|
+ curve.SetTangentMode(keyframeIdx, newMode);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Vector2 dragStartCurve;
|
|
|
- if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve, true))
|
|
|
- {
|
|
|
- Vector2 currentPosCurve;
|
|
|
- if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve, true))
|
|
|
- diff = currentPosCurve - dragStartCurve;
|
|
|
- }
|
|
|
+ curve.Apply();
|
|
|
+ }
|
|
|
|
|
|
- foreach (var draggedEntry in draggedKeyframes)
|
|
|
- {
|
|
|
- EdAnimationCurve curve = curveInfos[draggedEntry.curveIdx].curve;
|
|
|
+ OnCurveModified?.Invoke();
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ }
|
|
|
|
|
|
- for (int i = 0; i < draggedEntry.keys.Count; i++)
|
|
|
- {
|
|
|
- DraggedKeyframe draggedKey = draggedEntry.keys[i];
|
|
|
+ /// <summary>
|
|
|
+ /// Adds a new keyframe at the specified time on the provided curve.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="curveIdx">Index of the curve to add the keyframe to.</param>
|
|
|
+ /// <param name="time">Time at which to add the keyframe.</param>
|
|
|
+ private void AddKeyframe(int curveIdx, float time)
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
|
|
|
- float newTime = Math.Max(0.0f, draggedKey.original.time + diff.x);
|
|
|
- float newValue = draggedKey.original.value + diff.y;
|
|
|
+ if (!disableCurveEdit)
|
|
|
+ {
|
|
|
+ if (curveIdx < curveInfos.Length)
|
|
|
+ {
|
|
|
+ EdAnimationCurve curve = curveInfos[curveIdx].curve;
|
|
|
|
|
|
- int newIndex = curve.UpdateKeyframe(draggedKey.index, newTime, newValue);
|
|
|
+ float value = curve.Evaluate(time, false);
|
|
|
|
|
|
- // It's possible key changed position due to time change, but since we're moving all
|
|
|
- // keys at once they cannot change position relative to one another, otherwise we would
|
|
|
- // need to update indices for other keys as well.
|
|
|
- draggedKey.index = newIndex;
|
|
|
- draggedEntry.keys[i] = draggedKey;
|
|
|
- }
|
|
|
+ curve.AddOrUpdateKeyframe(time, value);
|
|
|
+ curve.Apply();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ShowReadOnlyMessage();
|
|
|
|
|
|
- curve.Apply();
|
|
|
- }
|
|
|
+ OnCurveModified?.Invoke();
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
|
|
|
- // Rebuild selected keys from dragged keys (after potential sorting)
|
|
|
- ClearSelection();
|
|
|
- foreach (var draggedEntry in draggedKeyframes)
|
|
|
- {
|
|
|
- foreach (var keyframe in draggedEntry.keys)
|
|
|
- SelectKeyframe(new KeyframeRef(draggedEntry.curveIdx, keyframe.index));
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Adds a new keyframe at the position the context menu was opened at.
|
|
|
+ /// </summary>
|
|
|
+ private void AddKeyframeAtPosition()
|
|
|
+ {
|
|
|
+ Vector2 curveCoord;
|
|
|
+ if (guiCurveDrawing.PixelToCurveSpace(contextClickPosition, out curveCoord))
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
|
|
|
- isModifiedDuringDrag = true;
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
+ if (!disableCurveEdit)
|
|
|
+ {
|
|
|
+ foreach (var curveInfo in curveInfos)
|
|
|
+ {
|
|
|
+ float t = curveCoord.x;
|
|
|
+ float value = curveCoord.y;
|
|
|
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
- else if (isMousePressedOverTangent && !disableCurveEdit)
|
|
|
- {
|
|
|
- EdAnimationCurve curve = curveInfos[draggedTangent.keyframeRef.curveIdx].curve;
|
|
|
- KeyFrame keyframe = curve.KeyFrames[draggedTangent.keyframeRef.keyIdx];
|
|
|
+ curveInfo.curve.AddOrUpdateKeyframe(t, value);
|
|
|
+ curveInfo.curve.Apply();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ShowReadOnlyMessage();
|
|
|
|
|
|
- Vector2 keyframeCurveCoords = new Vector2(keyframe.time, keyframe.value);
|
|
|
+ OnCurveModified?.Invoke();
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Vector2 currentPosCurve;
|
|
|
- if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve, true))
|
|
|
- {
|
|
|
- Vector2 normal = currentPosCurve - keyframeCurveCoords;
|
|
|
- normal = normal.Normalized;
|
|
|
+ /// <summary>
|
|
|
+ /// Adds a new event at the position the context menu was opened at.
|
|
|
+ /// </summary>
|
|
|
+ private void AddEventAtPosition()
|
|
|
+ {
|
|
|
+ if (!showEvents)
|
|
|
+ return;
|
|
|
|
|
|
- float tangent = EdAnimationCurve.NormalToTangent(normal);
|
|
|
+ int frame = guiEvents.GetFrame(contextClickPosition);
|
|
|
+ if (frame != -1)
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
+ float time = guiEvents.GetTime(contextClickPosition.x);
|
|
|
|
|
|
- if (draggedTangent.type == TangentType.In)
|
|
|
- {
|
|
|
- if (normal.x > 0.0f)
|
|
|
- tangent = float.PositiveInfinity;
|
|
|
+ EventInfo eventInfo = new EventInfo();
|
|
|
+ eventInfo.animEvent = new AnimationEvent("", time);
|
|
|
|
|
|
- keyframe.inTangent = -tangent;
|
|
|
- if(curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
|
|
|
- keyframe.outTangent = -tangent;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- if (normal.x < 0.0f)
|
|
|
- tangent = float.PositiveInfinity;
|
|
|
+ events.Add(eventInfo);
|
|
|
|
|
|
- keyframe.outTangent = tangent;
|
|
|
- if (curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
|
|
|
- keyframe.inTangent = tangent;
|
|
|
- }
|
|
|
+ OnEventAdded?.Invoke();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
|
|
|
- curve.KeyFrames[draggedTangent.keyframeRef.keyIdx] = keyframe;
|
|
|
- curve.Apply();
|
|
|
+ StartEventEdit(events.Count - 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- isModifiedDuringDrag = true;
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- else // Move frame marker
|
|
|
+ /// <summary>
|
|
|
+ /// Removes all currently selected keyframes from the curves.
|
|
|
+ /// </summary>
|
|
|
+ private void DeleteSelectedKeyframes()
|
|
|
+ {
|
|
|
+ if (!disableCurveEdit)
|
|
|
+ {
|
|
|
+ foreach (var selectedEntry in selectedKeyframes)
|
|
|
{
|
|
|
- int frameIdx = guiTimeline.GetFrame(pointerPos);
|
|
|
+ EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
|
|
|
|
|
|
- if (frameIdx != -1)
|
|
|
- SetMarkedFrame(frameIdx);
|
|
|
+ // Sort keys from highest to lowest so the indices don't change
|
|
|
+ selectedEntry.keyIndices.Sort((x, y) =>
|
|
|
+ {
|
|
|
+ return y.CompareTo(x);
|
|
|
+ });
|
|
|
|
|
|
- OnFrameSelected?.Invoke(frameIdx);
|
|
|
+ foreach (var keyframeIdx in selectedEntry.keyIndices)
|
|
|
+ curve.RemoveKeyframe(keyframeIdx);
|
|
|
+
|
|
|
+ curve.Apply();
|
|
|
}
|
|
|
}
|
|
|
+ else
|
|
|
+ ShowReadOnlyMessage();
|
|
|
+
|
|
|
+ ClearSelection();
|
|
|
+
|
|
|
+ OnCurveModified?.Invoke();
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Handles input. Should be called by the owning window whenever a pointer is released.
|
|
|
+ /// Deletes all currently selected events.
|
|
|
/// </summary>
|
|
|
- /// <param name="ev">Object containing pointer release event information.</param>
|
|
|
- internal void OnPointerReleased(PointerEvent ev)
|
|
|
+ private void DeleteSelectedEvents()
|
|
|
{
|
|
|
- if (isModifiedDuringDrag)
|
|
|
- OnCurveModified?.Invoke();
|
|
|
+ List<EventInfo> newEvents = new List<EventInfo>();
|
|
|
+ foreach (var entry in events)
|
|
|
+ {
|
|
|
+ if(!entry.selected)
|
|
|
+ newEvents.Add(entry);
|
|
|
+ }
|
|
|
|
|
|
- isPointerHeld = false;
|
|
|
- isDragInProgress = false;
|
|
|
- isMousePressedOverKey = false;
|
|
|
- isMousePressedOverTangent = false;
|
|
|
- isModifiedDuringDrag = false;
|
|
|
+ events = newEvents;
|
|
|
+
|
|
|
+ OnEventDeleted?.Invoke();
|
|
|
+ ClearSelection();
|
|
|
+
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Handles input. Should be called by the owning window whenever a button is released.
|
|
|
+ /// Unselects any selected keyframes and events.
|
|
|
/// </summary>
|
|
|
- /// <param name="ev">Object containing button release event information.</param>
|
|
|
- internal void OnButtonUp(ButtonEvent ev)
|
|
|
+ private void ClearSelection()
|
|
|
{
|
|
|
- if(ev.Button == ButtonCode.Delete)
|
|
|
- DeleteSelectedKeyframes();
|
|
|
+ guiCurveDrawing.ClearSelectedKeyframes();
|
|
|
+ selectedKeyframes.Clear();
|
|
|
+
|
|
|
+ foreach (var entry in events)
|
|
|
+ entry.selected = false;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Change the set of curves to display.
|
|
|
+ /// Adds the provided keyframe to the selection list (doesn't clear existing ones).
|
|
|
/// </summary>
|
|
|
- /// <param name="curveInfos">New set of curves to draw on the GUI element.</param>
|
|
|
- public void SetCurves(CurveDrawInfo[] curveInfos)
|
|
|
+ /// <param name="keyframeRef">Keyframe to select.</param>
|
|
|
+ private void SelectKeyframe(KeyframeRef keyframeRef)
|
|
|
{
|
|
|
- this.curveInfos = curveInfos;
|
|
|
- guiCurveDrawing.SetCurves(curveInfos);
|
|
|
+ guiCurveDrawing.SelectKeyframe(keyframeRef, true);
|
|
|
|
|
|
- Redraw();
|
|
|
+ if (!IsSelected(keyframeRef))
|
|
|
+ {
|
|
|
+ int curveIdx = selectedKeyframes.FindIndex(x =>
|
|
|
+ {
|
|
|
+ return x.curveIdx == keyframeRef.curveIdx;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (curveIdx == -1)
|
|
|
+ {
|
|
|
+ curveIdx = selectedKeyframes.Count;
|
|
|
+
|
|
|
+ SelectedKeyframes newKeyframes = new SelectedKeyframes();
|
|
|
+ newKeyframes.curveIdx = keyframeRef.curveIdx;
|
|
|
+
|
|
|
+ selectedKeyframes.Add(newKeyframes);
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedKeyframes[curveIdx].keyIndices.Add(keyframeRef.keyIdx);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Changes curve rendering mode. Normally the curves are drawn individually, but when range rendering is enabled
|
|
|
- /// the area between the first two curves is drawn instead. This setting is ignored if less than two curves are
|
|
|
- /// present. More than two curves are also ignored.
|
|
|
+ /// Checks is the provided keyframe currently selected.
|
|
|
/// </summary>
|
|
|
- /// <param name="drawRange">True to enable range rendering mode, false to enable individual curve rendering.</param>
|
|
|
- public void SetDrawRange(bool drawRange)
|
|
|
+ /// <param name="keyframeRef">Keyframe to check.</param>
|
|
|
+ /// <returns>True if selected, false otherwise.</returns>
|
|
|
+ private bool IsSelected(KeyframeRef keyframeRef)
|
|
|
{
|
|
|
- drawCurveRange = drawRange;
|
|
|
- guiCurveDrawing.SetDrawRange(drawRange);
|
|
|
+ int curveIdx = selectedKeyframes.FindIndex(x =>
|
|
|
+ {
|
|
|
+ return x.curveIdx == keyframeRef.curveIdx;
|
|
|
+ });
|
|
|
|
|
|
- Redraw();
|
|
|
+ if (curveIdx == -1)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ int keyIdx = selectedKeyframes[curveIdx].keyIndices.FindIndex(x =>
|
|
|
+ {
|
|
|
+ return x == keyframeRef.keyIdx;
|
|
|
+ });
|
|
|
+
|
|
|
+ return keyIdx != -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Opens the edit window for the currently selected keyframe.
|
|
|
+ /// </summary>
|
|
|
+ private void EditSelectedKeyframe()
|
|
|
+ {
|
|
|
+ if (disableCurveEdit)
|
|
|
+ {
|
|
|
+ ShowReadOnlyMessage();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedKeyframes.Count == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ EdAnimationCurve curve = curveInfos[selectedKeyframes[0].curveIdx].curve;
|
|
|
+ KeyFrame[] keyFrames = curve.KeyFrames;
|
|
|
+
|
|
|
+ int keyIndex = selectedKeyframes[0].keyIndices[0];
|
|
|
+ KeyFrame keyFrame = keyFrames[keyIndex];
|
|
|
+ Vector2I position = guiCurveDrawing.CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
|
|
|
+
|
|
|
+ Rect2I drawingBounds = GUIUtility.CalculateBounds(drawingPanel, window.GUI);
|
|
|
+ position.x = MathEx.Clamp(position.x, 0, drawingBounds.width);
|
|
|
+ position.y = MathEx.Clamp(position.y, 0, drawingBounds.height);
|
|
|
+
|
|
|
+ Vector2I windowPos = position + new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
+
|
|
|
+ KeyframeEditWindow editWindow = DropDownWindow.Open<KeyframeEditWindow>(window, windowPos);
|
|
|
+ editWindow.Initialize(keyFrame, x =>
|
|
|
+ {
|
|
|
+ curve.UpdateKeyframe(keyIndex, x.time, x.value);
|
|
|
+ curve.Apply();
|
|
|
+
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ },
|
|
|
+ x =>
|
|
|
+ {
|
|
|
+ if (x)
|
|
|
+ OnCurveModified?.Invoke();
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Change the physical size of the GUI element.
|
|
|
+ /// Opens the edit window for the currently selected event.
|
|
|
/// </summary>
|
|
|
- /// <param name="width">Width of the element in pixels.</param>
|
|
|
- /// <param name="height">Height of the element in pixels.</param>
|
|
|
- public void SetSize(int width, int height)
|
|
|
+ private void EditSelectedEvent()
|
|
|
{
|
|
|
- this.width = width;
|
|
|
- this.height = height;
|
|
|
+ if (!showEvents)
|
|
|
+ return;
|
|
|
|
|
|
- int eventsHeaderHeight = 0;
|
|
|
- if (showEvents)
|
|
|
+ for (int i = 0; i < events.Count; i++)
|
|
|
{
|
|
|
- eventsHeaderHeight = EVENTS_HEIGHT;
|
|
|
- guiEvents.SetSize(width, EVENTS_HEIGHT);
|
|
|
- eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
|
|
|
+ if (events[i].selected)
|
|
|
+ {
|
|
|
+ StartEventEdit(i);
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- guiTimeline.SetSize(width, TIMELINE_HEIGHT);
|
|
|
- guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT - eventsHeaderHeight);
|
|
|
- guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - eventsHeaderHeight);
|
|
|
-
|
|
|
- timelineBackground.Bounds = new Rect2I(0, 0, width, TIMELINE_HEIGHT + VERT_PADDING);
|
|
|
-
|
|
|
- Redraw();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Number of frames per second, used for frame selection and marking.
|
|
|
+ /// Opens the event edit window for the specified event.
|
|
|
/// </summary>
|
|
|
- /// <param name="fps">Number of prames per second.</param>
|
|
|
- public void SetFPS(int fps)
|
|
|
+ /// <param name="eventIdx">Event index to open the edit window for.</param>
|
|
|
+ private void StartEventEdit(int eventIdx)
|
|
|
{
|
|
|
- guiTimeline.SetFPS(fps);
|
|
|
- guiCurveDrawing.SetFPS(fps);
|
|
|
+ AnimationEvent animEvent = events[eventIdx].animEvent;
|
|
|
|
|
|
- if(showEvents)
|
|
|
- guiEvents.SetFPS(fps);
|
|
|
+ Vector2I position = new Vector2I();
|
|
|
+ position.x = guiEvents.GetOffset(animEvent.time);
|
|
|
+ position.y = EVENTS_HEIGHT/2;
|
|
|
|
|
|
- Redraw();
|
|
|
- }
|
|
|
+ Rect2I eventBounds = GUIUtility.CalculateBounds(eventsPanel, window.GUI);
|
|
|
+ Vector2I windowPos = position + new Vector2I(eventBounds.x, eventBounds.y);
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Sets a scene object that will be used for enumerating components/methods used for adding events.
|
|
|
- /// </summary>
|
|
|
- /// <param name="so">Scene object containing the animation component.</param>
|
|
|
- public void SetEventSceneObject(SceneObject so)
|
|
|
- {
|
|
|
- eventsSO = so;
|
|
|
+ if (eventsSO == null)
|
|
|
+ return;
|
|
|
+
|
|
|
+ Component[] components = eventsSO.GetComponents();
|
|
|
+ string[] componentNames = new string[components.Length];
|
|
|
+ for (int i = 0; i < components.Length; i++)
|
|
|
+ componentNames[i] = components[i].GetType().Name;
|
|
|
+
|
|
|
+ EventEditWindow editWindow = DropDownWindow.Open<EventEditWindow>(window, windowPos);
|
|
|
+ editWindow.Initialize(animEvent, componentNames, () =>
|
|
|
+ {
|
|
|
+ UpdateEventsGUI();
|
|
|
+ },
|
|
|
+ x =>
|
|
|
+ {
|
|
|
+ if(x)
|
|
|
+ OnEventModified?.Invoke();
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Returns time for a frame with the specified index. Depends on set range and FPS.
|
|
|
+ /// Shows a dialog box that notifies the user that the animation clip is read only.
|
|
|
/// </summary>
|
|
|
- /// <param name="frameIdx">Index of the frame (not a key-frame) to get the time for.</param>
|
|
|
- /// <returns>Time of the frame with the provided index. </returns>
|
|
|
- public float GetTimeForFrame(int frameIdx)
|
|
|
+ private void ShowReadOnlyMessage()
|
|
|
{
|
|
|
- return guiCurveDrawing.GetTimeForFrame(frameIdx);
|
|
|
+ LocEdString title = new LocEdString("Warning");
|
|
|
+ LocEdString message =
|
|
|
+ new LocEdString("You cannot edit keyframes on animation clips that" +
|
|
|
+ " are imported from an external file.");
|
|
|
+
|
|
|
+ DialogBox.Open(title, message, DialogBox.Type.OK);
|
|
|
}
|
|
|
|
|
|
+ #region Input
|
|
|
+
|
|
|
/// <summary>
|
|
|
- /// Sets the frame at which to display the frame marker.
|
|
|
+ /// Handles input. Should be called by the owning window whenever a pointer is pressed.
|
|
|
/// </summary>
|
|
|
- /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
|
|
|
- public void SetMarkedFrame(int frameIdx)
|
|
|
+ /// <param name="ev">Object containing pointer press event information.</param>
|
|
|
+ internal void OnPointerPressed(PointerEvent ev)
|
|
|
{
|
|
|
- markedFrameIdx = frameIdx;
|
|
|
+ if (ev.IsUsed)
|
|
|
+ return;
|
|
|
|
|
|
- guiTimeline.SetMarkedFrame(frameIdx);
|
|
|
- guiCurveDrawing.SetMarkedFrame(frameIdx);
|
|
|
+ Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
|
|
|
|
|
|
- if(showEvents)
|
|
|
- guiEvents.SetMarkedFrame(frameIdx);
|
|
|
+ Rect2I elementBounds = GUIUtility.CalculateBounds(mainPanel, window.GUI);
|
|
|
+ Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
|
|
|
- Redraw();
|
|
|
- }
|
|
|
+ bool isOverEditor = pointerPos.x >= 0 && pointerPos.x < width && pointerPos.y >= 0 && pointerPos.y < height;
|
|
|
+ if (!isOverEditor)
|
|
|
+ return;
|
|
|
+ else
|
|
|
+ OnClicked?.Invoke();
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Adds a new keyframe at the currently selected frame.
|
|
|
- /// </summary>
|
|
|
- public void AddKeyFrameAtMarker()
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
+ Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
+ Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
|
|
|
- if (!disableCurveEdit)
|
|
|
+ Rect2I eventBounds = eventsPanel.Bounds;
|
|
|
+ Vector2I eventPos = pointerPos - new Vector2I(eventBounds.x, eventBounds.y);
|
|
|
+
|
|
|
+ if (ev.Button == PointerButton.Left)
|
|
|
{
|
|
|
- foreach (var curveInfo in curveInfos)
|
|
|
+ Vector2 curveCoord;
|
|
|
+ if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord, true))
|
|
|
{
|
|
|
- float t = guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
|
|
|
- float value = curveInfo.curve.Evaluate(t);
|
|
|
+ KeyframeRef keyframeRef;
|
|
|
+ if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
|
|
|
+ {
|
|
|
+ TangentRef tangentRef;
|
|
|
+ if (guiCurveDrawing.FindTangent(drawingPos, out tangentRef))
|
|
|
+ {
|
|
|
+ isMousePressedOverTangent = true;
|
|
|
+ dragStart = drawingPos;
|
|
|
+ draggedTangent = tangentRef;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ClearSelection();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (!IsSelected(keyframeRef))
|
|
|
+ {
|
|
|
+ if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
|
|
|
+ ClearSelection();
|
|
|
|
|
|
- curveInfo.curve.AddOrUpdateKeyframe(t, value);
|
|
|
- curveInfo.curve.Apply();
|
|
|
+ SelectKeyframe(keyframeRef);
|
|
|
+ }
|
|
|
+
|
|
|
+ isMousePressedOverKey = true;
|
|
|
+ dragStart = drawingPos;
|
|
|
+ }
|
|
|
+
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ int frameIdx = guiTimeline.GetFrame(pointerPos);
|
|
|
+
|
|
|
+ if (frameIdx != -1)
|
|
|
+ SetMarkedFrame(frameIdx);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ int eventIdx;
|
|
|
+ if (showEvents && guiEvents.FindEvent(eventPos, out eventIdx))
|
|
|
+ {
|
|
|
+ if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
|
|
|
+ ClearSelection();
|
|
|
+
|
|
|
+ events[eventIdx].selected = true;
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
+
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ OnFrameSelected?.Invoke(frameIdx);
|
|
|
}
|
|
|
+
|
|
|
+ isPointerHeld = true;
|
|
|
}
|
|
|
- else
|
|
|
- ShowReadOnlyMessage();
|
|
|
+ else if (ev.Button == PointerButton.Right)
|
|
|
+ {
|
|
|
+ Vector2 curveCoord;
|
|
|
+ if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord, true))
|
|
|
+ {
|
|
|
+ contextClickPosition = drawingPos;
|
|
|
|
|
|
- OnCurveModified?.Invoke();
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
- }
|
|
|
+ KeyframeRef keyframeRef;
|
|
|
+ if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Adds a new event at the currently selected event.
|
|
|
- /// </summary>
|
|
|
- public void AddEventAtMarker()
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
+ blankContextMenu.Open(pointerPos, mainPanel);
|
|
|
|
|
|
- if (!showEvents)
|
|
|
- return;
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // If clicked outside of current selection, just select the one keyframe
|
|
|
+ if (!IsSelected(keyframeRef))
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
+ SelectKeyframe(keyframeRef);
|
|
|
|
|
|
- float eventTime = guiEvents.GetTimeForFrame(markedFrameIdx);
|
|
|
- EventInfo eventInfo = new EventInfo();
|
|
|
- eventInfo.animEvent = new AnimationEvent("", eventTime);
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
|
|
|
- events.Add(eventInfo);
|
|
|
- OnEventAdded?.Invoke();
|
|
|
+ keyframeContextMenu.Open(pointerPos, mainPanel);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (showEvents && guiEvents.GetFrame(eventPos) != -1) // Clicked over events bar
|
|
|
+ {
|
|
|
+ contextClickPosition = eventPos;
|
|
|
|
|
|
- UpdateEventsGUI();
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
+ int eventIdx;
|
|
|
+ if (!guiEvents.FindEvent(eventPos, out eventIdx))
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
|
|
|
- StartEventEdit(events.Count - 1);
|
|
|
+ blankEventContextMenu.Open(pointerPos, mainPanel);
|
|
|
+
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // If clicked outside of current selection, just select the one event
|
|
|
+ if (!events[eventIdx].selected)
|
|
|
+ {
|
|
|
+ ClearSelection();
|
|
|
+ events[eventIdx].selected = true;
|
|
|
+
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ UpdateEventsGUI();
|
|
|
+ }
|
|
|
+
|
|
|
+ eventContextMenu.Open(pointerPos, mainPanel);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (ev.button == PointerButton.Middle)
|
|
|
+ {
|
|
|
+ Vector2 curvePos;
|
|
|
+ if (WindowToCurveSpace(windowPos, out curvePos))
|
|
|
+ {
|
|
|
+ dragStartPos = windowPos;
|
|
|
+ isMiddlePointerHeld = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Rebuilds GUI displaying the animation events list.
|
|
|
+ /// Handles input. Should be called by the owning window whenever a pointer is double-clicked.
|
|
|
/// </summary>
|
|
|
- private void UpdateEventsGUI()
|
|
|
+ /// <param name="ev">Object containing pointer press event information.</param>
|
|
|
+ internal void OnPointerDoubleClicked(PointerEvent ev)
|
|
|
{
|
|
|
- if (!showEvents)
|
|
|
+ if (ev.IsUsed)
|
|
|
return;
|
|
|
|
|
|
- AnimationEvent[] animEvents = new AnimationEvent[events.Count];
|
|
|
- bool[] selected = new bool[events.Count];
|
|
|
+ Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
|
|
|
|
|
|
- for (int i = 0; i < events.Count; i++)
|
|
|
- {
|
|
|
- animEvents[i] = events[i].animEvent;
|
|
|
- selected[i] = events[i].selected;
|
|
|
- }
|
|
|
+ Rect2I elementBounds = GUIUtility.CalculateBounds(mainPanel, window.GUI);
|
|
|
+ Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
|
|
|
- guiEvents.SetEvents(animEvents, selected);
|
|
|
- guiEvents.Rebuild();
|
|
|
- }
|
|
|
+ bool isOverEditor = pointerPos.x >= 0 && pointerPos.x < width && pointerPos.y >= 0 && pointerPos.y < height;
|
|
|
+ if (!isOverEditor)
|
|
|
+ return;
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Rebuilds the entire curve editor GUI.
|
|
|
- /// </summary>
|
|
|
- public void Redraw()
|
|
|
- {
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- guiTimeline.Rebuild();
|
|
|
- guiSidebar.Rebuild();
|
|
|
+ Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
+ Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
|
|
|
- if(showEvents)
|
|
|
- guiEvents.Rebuild();
|
|
|
+ if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out var curveCoord, true))
|
|
|
+ {
|
|
|
+ int curveIdx = guiCurveDrawing.FindCurve(drawingPos);
|
|
|
+ if (curveIdx == -1)
|
|
|
+ return;
|
|
|
+
|
|
|
+ AddKeyframe(curveIdx, curveCoord.x);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Changes the tangent mode for all currently selected keyframes.
|
|
|
+ /// Handles input. Should be called by the owning window whenever a pointer is moved.
|
|
|
/// </summary>
|
|
|
- /// <param name="mode">Tangent mode to set. If only in or out tangent mode is provided, the mode for the opposite
|
|
|
- /// tangent will be kept as is.</param>
|
|
|
- private void ChangeSelectionTangentMode(TangentMode mode)
|
|
|
+ /// <param name="ev">Object containing pointer move event information.</param>
|
|
|
+ internal void OnPointerMoved(PointerEvent ev)
|
|
|
{
|
|
|
- if (disableCurveEdit)
|
|
|
- {
|
|
|
- ShowReadOnlyMessage();
|
|
|
+ if (ev.Button != PointerButton.Left)
|
|
|
return;
|
|
|
- }
|
|
|
|
|
|
- foreach (var selectedEntry in selectedKeyframes)
|
|
|
+ if (isPointerHeld)
|
|
|
{
|
|
|
- EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
|
|
|
+ Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
|
|
|
|
|
|
- foreach (var keyframeIdx in selectedEntry.keyIndices)
|
|
|
+ Rect2I elementBounds = GUIUtility.CalculateBounds(mainPanel, window.GUI);
|
|
|
+ Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
+
|
|
|
+ if (isMousePressedOverKey || isMousePressedOverTangent)
|
|
|
{
|
|
|
- if (mode == TangentMode.Auto || mode == TangentMode.Free)
|
|
|
- curve.SetTangentMode(keyframeIdx, mode);
|
|
|
- else
|
|
|
- {
|
|
|
- TangentMode newMode = curve.TangentModes[keyframeIdx];
|
|
|
+ Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
+ Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
|
|
|
- if (mode.HasFlag((TangentMode) TangentType.In))
|
|
|
+ if (!isDragInProgress)
|
|
|
+ {
|
|
|
+ int distance = Vector2I.Distance(drawingPos, dragStart);
|
|
|
+ if (distance >= DRAG_START_DISTANCE)
|
|
|
{
|
|
|
- // Replace only the in tangent mode, keeping the out tangent as is
|
|
|
- TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
|
|
|
- TangentMode.InStep);
|
|
|
+ if (isMousePressedOverKey && !disableCurveEdit)
|
|
|
+ {
|
|
|
+ draggedKeyframes.Clear();
|
|
|
+ foreach (var selectedEntry in selectedKeyframes)
|
|
|
+ {
|
|
|
+ EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
|
|
|
+ KeyFrame[] keyFrames = curve.KeyFrames;
|
|
|
|
|
|
- newMode &= ~inFlags;
|
|
|
- newMode |= (mode & inFlags);
|
|
|
+ DraggedKeyframes newEntry = new DraggedKeyframes();
|
|
|
+ newEntry.curveIdx = selectedEntry.curveIdx;
|
|
|
+ draggedKeyframes.Add(newEntry);
|
|
|
+
|
|
|
+ foreach (var keyframeIdx in selectedEntry.keyIndices)
|
|
|
+ newEntry.keys.Add(new DraggedKeyframe(keyframeIdx, keyFrames[keyframeIdx]));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ isDragInProgress = true;
|
|
|
}
|
|
|
- else
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isDragInProgress)
|
|
|
+ {
|
|
|
+ if (isMousePressedOverKey && !disableCurveEdit)
|
|
|
{
|
|
|
- // Replace only the out tangent mode, keeping the in tangent as is
|
|
|
- TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
|
|
|
- TangentMode.OutStep);
|
|
|
+ Vector2 diff = Vector2.Zero;
|
|
|
|
|
|
- newMode &= ~outFlags;
|
|
|
- newMode |= (mode & outFlags);
|
|
|
+ Vector2 dragStartCurve;
|
|
|
+ if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve, true))
|
|
|
+ {
|
|
|
+ Vector2 currentPosCurve;
|
|
|
+ if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve, true))
|
|
|
+ diff = currentPosCurve - dragStartCurve;
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var draggedEntry in draggedKeyframes)
|
|
|
+ {
|
|
|
+ EdAnimationCurve curve = curveInfos[draggedEntry.curveIdx].curve;
|
|
|
+
|
|
|
+ for (int i = 0; i < draggedEntry.keys.Count; i++)
|
|
|
+ {
|
|
|
+ DraggedKeyframe draggedKey = draggedEntry.keys[i];
|
|
|
+
|
|
|
+ float newTime = Math.Max(0.0f, draggedKey.original.time + diff.x);
|
|
|
+ float newValue = draggedKey.original.value + diff.y;
|
|
|
+
|
|
|
+ int newIndex = curve.UpdateKeyframe(draggedKey.index, newTime, newValue);
|
|
|
+
|
|
|
+ // It's possible key changed position due to time change, but since we're moving all
|
|
|
+ // keys at once they cannot change position relative to one another, otherwise we would
|
|
|
+ // need to update indices for other keys as well.
|
|
|
+ draggedKey.index = newIndex;
|
|
|
+ draggedEntry.keys[i] = draggedKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ curve.Apply();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Rebuild selected keys from dragged keys (after potential sorting)
|
|
|
+ ClearSelection();
|
|
|
+ foreach (var draggedEntry in draggedKeyframes)
|
|
|
+ {
|
|
|
+ foreach (var keyframe in draggedEntry.keys)
|
|
|
+ SelectKeyframe(new KeyframeRef(draggedEntry.curveIdx, keyframe.index));
|
|
|
+ }
|
|
|
+
|
|
|
+ isModifiedDuringDrag = true;
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+
|
|
|
+ UpdateEventsGUI();
|
|
|
}
|
|
|
+ else if (isMousePressedOverTangent && !disableCurveEdit)
|
|
|
+ {
|
|
|
+ EdAnimationCurve curve = curveInfos[draggedTangent.keyframeRef.curveIdx].curve;
|
|
|
+ KeyFrame keyframe = curve.KeyFrames[draggedTangent.keyframeRef.keyIdx];
|
|
|
|
|
|
- curve.SetTangentMode(keyframeIdx, newMode);
|
|
|
+ Vector2 keyframeCurveCoords = new Vector2(keyframe.time, keyframe.value);
|
|
|
+
|
|
|
+ Vector2 currentPosCurve;
|
|
|
+ if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve, true))
|
|
|
+ {
|
|
|
+ Vector2 normal = currentPosCurve - keyframeCurveCoords;
|
|
|
+ normal = normal.Normalized;
|
|
|
+
|
|
|
+ float tangent = EdAnimationCurve.NormalToTangent(normal);
|
|
|
+
|
|
|
+ if (draggedTangent.type == TangentType.In)
|
|
|
+ {
|
|
|
+ if (normal.x > 0.0f)
|
|
|
+ tangent = float.PositiveInfinity;
|
|
|
+
|
|
|
+ keyframe.inTangent = -tangent;
|
|
|
+ if(curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
|
|
|
+ keyframe.outTangent = -tangent;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (normal.x < 0.0f)
|
|
|
+ tangent = float.PositiveInfinity;
|
|
|
+
|
|
|
+ keyframe.outTangent = tangent;
|
|
|
+ if (curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
|
|
|
+ keyframe.inTangent = tangent;
|
|
|
+ }
|
|
|
+
|
|
|
+ curve.KeyFrames[draggedTangent.keyframeRef.keyIdx] = keyframe;
|
|
|
+ curve.Apply();
|
|
|
+
|
|
|
+ isModifiedDuringDrag = true;
|
|
|
+ guiCurveDrawing.Rebuild();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ else // Move frame marker
|
|
|
+ {
|
|
|
+ int frameIdx = guiTimeline.GetFrame(pointerPos);
|
|
|
|
|
|
- curve.Apply();
|
|
|
+ if (frameIdx != -1)
|
|
|
+ SetMarkedFrame(frameIdx);
|
|
|
+
|
|
|
+ OnFrameSelected?.Invoke(frameIdx);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- OnCurveModified?.Invoke();
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
+ if (isMiddlePointerHeld)
|
|
|
+ {
|
|
|
+ Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
|
|
|
+
|
|
|
+ int distance = Vector2I.Distance(dragStartPos, windowPos);
|
|
|
+ if (distance >= DRAG_START_DISTANCE)
|
|
|
+ {
|
|
|
+ isZoomDragInProgress = true;
|
|
|
+
|
|
|
+ Cursor.Hide();
|
|
|
+
|
|
|
+ Rect2I clipRect;
|
|
|
+ clipRect.x = ev.ScreenPos.x - 2;
|
|
|
+ clipRect.y = ev.ScreenPos.y - 2;
|
|
|
+ clipRect.width = 4;
|
|
|
+ clipRect.height = 4;
|
|
|
+
|
|
|
+ Cursor.ClipToRect(clipRect);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Adds a new keyframe at the specified time on the provided curve.
|
|
|
+ /// Handles input. Should be called by the owning window whenever a pointer is released.
|
|
|
/// </summary>
|
|
|
- /// <param name="curveIdx">Index of the curve to add the keyframe to.</param>
|
|
|
- /// <param name="time">Time at which to add the keyframe.</param>
|
|
|
- private void AddKeyframe(int curveIdx, float time)
|
|
|
+ /// <param name="ev">Object containing pointer release event information.</param>
|
|
|
+ internal void OnPointerReleased(PointerEvent ev)
|
|
|
{
|
|
|
- ClearSelection();
|
|
|
-
|
|
|
- if (!disableCurveEdit)
|
|
|
+ if (isZoomDragInProgress)
|
|
|
{
|
|
|
- if (curveIdx < curveInfos.Length)
|
|
|
- {
|
|
|
- EdAnimationCurve curve = curveInfos[curveIdx].curve;
|
|
|
+ Cursor.Show();
|
|
|
+ Cursor.ClipDisable();
|
|
|
+ }
|
|
|
|
|
|
- float value = curve.Evaluate(time, false);
|
|
|
+ if (isModifiedDuringDrag)
|
|
|
+ OnCurveModified?.Invoke();
|
|
|
|
|
|
- curve.AddOrUpdateKeyframe(time, value);
|
|
|
- curve.Apply();
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- ShowReadOnlyMessage();
|
|
|
+ isMiddlePointerHeld = false;
|
|
|
+ isZoomDragInProgress = false;
|
|
|
+ isPointerHeld = false;
|
|
|
+ isDragInProgress = false;
|
|
|
+ isMousePressedOverKey = false;
|
|
|
+ isMousePressedOverTangent = false;
|
|
|
+ isModifiedDuringDrag = false;
|
|
|
+ }
|
|
|
|
|
|
- OnCurveModified?.Invoke();
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
+ /// <summary>
|
|
|
+ /// Handles input. Should be called by the owning window whenever a button is released.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="ev">Object containing button release event information.</param>
|
|
|
+ internal void OnButtonUp(ButtonEvent ev)
|
|
|
+ {
|
|
|
+ if(ev.Button == ButtonCode.Delete)
|
|
|
+ DeleteSelectedKeyframes();
|
|
|
}
|
|
|
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Scroll, drag, zoom
|
|
|
+ private Vector2I dragStartPos;
|
|
|
+ private bool isMiddlePointerHeld;
|
|
|
+ private bool isZoomDragInProgress;
|
|
|
+
|
|
|
+ private float zoomAmount;
|
|
|
+
|
|
|
/// <summary>
|
|
|
- /// Adds a new keyframe at the position the context menu was opened at.
|
|
|
+ /// Handles mouse scroll wheel and dragging events in order to zoom or drag the displayed curve editor contents.
|
|
|
+ /// Should be called every frame.
|
|
|
/// </summary>
|
|
|
- private void AddKeyframeAtPosition()
|
|
|
+ internal void HandleDragAndZoomInput()
|
|
|
{
|
|
|
- Vector2 curveCoord;
|
|
|
- if (guiCurveDrawing.PixelToCurveSpace(contextClickPosition, out curveCoord))
|
|
|
+ // Handle middle mouse dragging
|
|
|
+ if (isZoomDragInProgress)
|
|
|
{
|
|
|
- ClearSelection();
|
|
|
+ float lengthPerPixel = Range.x / Width;
|
|
|
+ float heightPerPixel = Range.y / Height;
|
|
|
|
|
|
- if (!disableCurveEdit)
|
|
|
- {
|
|
|
- foreach (var curveInfo in curveInfos)
|
|
|
- {
|
|
|
- float t = curveCoord.x;
|
|
|
- float value = curveCoord.y;
|
|
|
+ float dragX = Input.GetAxisValue(InputAxis.MouseX) * DRAG_SCALE * lengthPerPixel;
|
|
|
+ float dragY = Input.GetAxisValue(InputAxis.MouseY) * DRAG_SCALE * heightPerPixel;
|
|
|
|
|
|
- curveInfo.curve.AddOrUpdateKeyframe(t, value);
|
|
|
- curveInfo.curve.Apply();
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- ShowReadOnlyMessage();
|
|
|
+ Vector2 offset = Offset;
|
|
|
+ offset.x = Math.Max(0.0f, offset.x + dragX);
|
|
|
+ offset.y -= dragY;
|
|
|
|
|
|
- OnCurveModified?.Invoke();
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
+ Offset = offset;
|
|
|
+ UpdateScrollBarSize();
|
|
|
+ UpdateScrollBarPosition();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle zoom in/out
|
|
|
+ float scroll = Input.GetAxisValue(InputAxis.MouseZ);
|
|
|
+ if (scroll != 0.0f)
|
|
|
+ {
|
|
|
+ Vector2I windowPos = window.ScreenToWindowPos(Input.PointerPosition);
|
|
|
+ Vector2 curvePos;
|
|
|
+ if (WindowToCurveSpace(windowPos, out curvePos))
|
|
|
+ {
|
|
|
+ float zoom = scroll * ZOOM_SCALE;
|
|
|
+ Zoom(curvePos, zoom);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Adds a new event at the position the context menu was opened at.
|
|
|
+ /// Moves or resizes the vertical scroll bar under the curve editor.
|
|
|
/// </summary>
|
|
|
- private void AddEventAtPosition()
|
|
|
+ /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
|
|
|
+ /// <param name="size">New size of the scrollbar handle, in range [0, 1].</param>
|
|
|
+ private void SetVertScrollbarProperties(float position, float size)
|
|
|
{
|
|
|
- if (!showEvents)
|
|
|
- return;
|
|
|
-
|
|
|
- int frame = guiEvents.GetFrame(contextClickPosition);
|
|
|
- if (frame != -1)
|
|
|
- {
|
|
|
- ClearSelection();
|
|
|
- float time = guiEvents.GetTime(contextClickPosition.x);
|
|
|
+ Vector2 visibleRange = Range;
|
|
|
+ Vector2 totalRange = GetTotalRange();
|
|
|
|
|
|
- EventInfo eventInfo = new EventInfo();
|
|
|
- eventInfo.animEvent = new AnimationEvent("", time);
|
|
|
+ visibleRange.y = totalRange.y*size;
|
|
|
+ Range = visibleRange;
|
|
|
|
|
|
- events.Add(eventInfo);
|
|
|
+ float scrollableRange = totalRange.y - visibleRange.y;
|
|
|
|
|
|
- OnEventAdded?.Invoke();
|
|
|
- UpdateEventsGUI();
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
+ Vector2 offset = Offset;
|
|
|
+ offset.y = -scrollableRange * (position * 2.0f - 1.0f);
|
|
|
|
|
|
- StartEventEdit(events.Count - 1);
|
|
|
- }
|
|
|
+ Offset = offset;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Removes all currently selected keyframes from the curves.
|
|
|
+ /// Moves or resizes the horizontal scroll bar under the curve editor.
|
|
|
/// </summary>
|
|
|
- private void DeleteSelectedKeyframes()
|
|
|
+ /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
|
|
|
+ /// <param name="size">New size of the scrollbar handle, in range [0, 1].</param>
|
|
|
+ private void SetHorzScrollbarProperties(float position, float size)
|
|
|
{
|
|
|
- if (!disableCurveEdit)
|
|
|
- {
|
|
|
- foreach (var selectedEntry in selectedKeyframes)
|
|
|
- {
|
|
|
- EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
|
|
|
+ Vector2 visibleRange = Range;
|
|
|
+ Vector2 totalRange = GetTotalRange();
|
|
|
|
|
|
- // Sort keys from highest to lowest so the indices don't change
|
|
|
- selectedEntry.keyIndices.Sort((x, y) =>
|
|
|
- {
|
|
|
- return y.CompareTo(x);
|
|
|
- });
|
|
|
+ visibleRange.x = totalRange.x * size;
|
|
|
+ Range = visibleRange;
|
|
|
|
|
|
- foreach (var keyframeIdx in selectedEntry.keyIndices)
|
|
|
- curve.RemoveKeyframe(keyframeIdx);
|
|
|
+ float scrollableRange = totalRange.x - visibleRange.x;
|
|
|
|
|
|
- curve.Apply();
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- ShowReadOnlyMessage();
|
|
|
+ Vector2 offset = Offset;
|
|
|
+ offset.x = scrollableRange * position;
|
|
|
|
|
|
- ClearSelection();
|
|
|
+ Offset = offset;
|
|
|
+ }
|
|
|
|
|
|
- OnCurveModified?.Invoke();
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
+ /// <summary>
|
|
|
+ /// Updates the size of both scrollbars depending on the currently visible curve area vs. the total curve area.
|
|
|
+ /// </summary>
|
|
|
+ private void UpdateScrollBarSize()
|
|
|
+ {
|
|
|
+ Vector2 visibleRange = Range;
|
|
|
+ Vector2 totalRange = GetTotalRange();
|
|
|
+
|
|
|
+ horzScrollBar.HandleSize = visibleRange.x / totalRange.x;
|
|
|
+ vertScrollBar.HandleSize = visibleRange.y / totalRange.y;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Deletes all currently selected events.
|
|
|
+ /// Updates the position of both scrollbars depending on the offset currently applied to the visible curve area.
|
|
|
/// </summary>
|
|
|
- private void DeleteSelectedEvents()
|
|
|
+ private void UpdateScrollBarPosition()
|
|
|
{
|
|
|
- List<EventInfo> newEvents = new List<EventInfo>();
|
|
|
- foreach (var entry in events)
|
|
|
+ Vector2 visibleRange = Range;
|
|
|
+ Vector2 totalRange = GetTotalRange();
|
|
|
+ Vector2 scrollableRange = totalRange - visibleRange;
|
|
|
+
|
|
|
+ Vector2 offset = Offset;
|
|
|
+ if (scrollableRange.x > 0.0f)
|
|
|
+ horzScrollBar.Position = offset.x / scrollableRange.x;
|
|
|
+ else
|
|
|
+ horzScrollBar.Position = 0.0f;
|
|
|
+
|
|
|
+ if (scrollableRange.y > 0.0f)
|
|
|
{
|
|
|
- if(!entry.selected)
|
|
|
- newEvents.Add(entry);
|
|
|
- }
|
|
|
+ float pos = offset.y/scrollableRange.y;
|
|
|
+ float sign = MathEx.Sign(pos);
|
|
|
+ pos = sign*MathEx.Clamp01(MathEx.Abs(pos));
|
|
|
+ pos = (1.0f - pos) /2.0f;
|
|
|
|
|
|
- events = newEvents;
|
|
|
+ vertScrollBar.Position = pos;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ vertScrollBar.Position = 0.0f;
|
|
|
+ }
|
|
|
|
|
|
- OnEventDeleted?.Invoke();
|
|
|
- ClearSelection();
|
|
|
+ /// <summary>
|
|
|
+ /// Calculates the width/height of the curve area depending on the current zoom level.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>Width/height of the curve area, in curve space (value, time).</returns>
|
|
|
+ private Vector2 GetZoomedRange()
|
|
|
+ {
|
|
|
+ float zoomLevel = MathEx.Pow(2, zoomAmount);
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- UpdateEventsGUI();
|
|
|
+ Vector2 optimalRange = GetOptimalRange();
|
|
|
+ return optimalRange / zoomLevel;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Unselects any selected keyframes and events.
|
|
|
+ /// Returns the total width/height of the contents of the curve area.
|
|
|
/// </summary>
|
|
|
- private void ClearSelection()
|
|
|
+ /// <returns>Width/height of the curve area, in curve space (value, time).</returns>
|
|
|
+ private Vector2 GetTotalRange()
|
|
|
{
|
|
|
- guiCurveDrawing.ClearSelectedKeyframes();
|
|
|
- selectedKeyframes.Clear();
|
|
|
+ // Return optimal range (that covers the visible curve)
|
|
|
+ Vector2 optimalRange = GetOptimalRange();
|
|
|
|
|
|
- foreach (var entry in events)
|
|
|
- entry.selected = false;
|
|
|
+ // Increase range in case user zoomed out
|
|
|
+ Vector2 zoomedRange = GetZoomedRange();
|
|
|
+ return Vector2.Max(optimalRange, zoomedRange);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Adds the provided keyframe to the selection list (doesn't clear existing ones).
|
|
|
+ /// Zooms in or out at the provided position in the curve display.
|
|
|
/// </summary>
|
|
|
- /// <param name="keyframeRef">Keyframe to select.</param>
|
|
|
- private void SelectKeyframe(KeyframeRef keyframeRef)
|
|
|
+ /// <param name="curvePos">Position to zoom towards, relative to the curve display area, in curve space
|
|
|
+ /// (value, time)</param>
|
|
|
+ /// <param name="amount">Amount to zoom in (positive), or out (negative).</param>
|
|
|
+ private void Zoom(Vector2 curvePos, float amount)
|
|
|
{
|
|
|
- guiCurveDrawing.SelectKeyframe(keyframeRef, true);
|
|
|
+ // Increase or decrease the visible range depending on zoom level
|
|
|
+ Vector2 oldZoomedRange = GetZoomedRange();
|
|
|
+ zoomAmount = MathEx.Clamp(zoomAmount + amount, -10.0f, 10.0f);
|
|
|
+ Vector2 zoomedRange = GetZoomedRange();
|
|
|
|
|
|
- if (!IsSelected(keyframeRef))
|
|
|
- {
|
|
|
- int curveIdx = selectedKeyframes.FindIndex(x =>
|
|
|
- {
|
|
|
- return x.curveIdx == keyframeRef.curveIdx;
|
|
|
- });
|
|
|
+ Vector2 zoomedDiff = zoomedRange - oldZoomedRange;
|
|
|
|
|
|
- if (curveIdx == -1)
|
|
|
- {
|
|
|
- curveIdx = selectedKeyframes.Count;
|
|
|
+ Vector2 currentRange = Range;
|
|
|
+ Vector2 newRange = currentRange + zoomedDiff;
|
|
|
+ Range = newRange;
|
|
|
|
|
|
- SelectedKeyframes newKeyframes = new SelectedKeyframes();
|
|
|
- newKeyframes.curveIdx = keyframeRef.curveIdx;
|
|
|
+ // When zooming, make sure to focus on the point provided, so adjust the offset
|
|
|
+ Vector2 rangeScale = newRange;
|
|
|
+ rangeScale.x /= currentRange.x;
|
|
|
+ rangeScale.y /= currentRange.y;
|
|
|
|
|
|
- selectedKeyframes.Add(newKeyframes);
|
|
|
- }
|
|
|
+ Vector2 relativeCurvePos = curvePos - Offset;
|
|
|
+ Vector2 newCurvePos = relativeCurvePos * rangeScale;
|
|
|
+ Vector2 diff = newCurvePos - relativeCurvePos;
|
|
|
|
|
|
- selectedKeyframes[curveIdx].keyIndices.Add(keyframeRef.keyIdx);
|
|
|
- }
|
|
|
+ Offset -= diff;
|
|
|
+
|
|
|
+ UpdateScrollBarSize();
|
|
|
+ UpdateScrollBarPosition();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Checks is the provided keyframe currently selected.
|
|
|
+ /// Updates the offset and range of the curve display to fully fit the currently selected set of curves.
|
|
|
/// </summary>
|
|
|
- /// <param name="keyframeRef">Keyframe to check.</param>
|
|
|
- /// <returns>True if selected, false otherwise.</returns>
|
|
|
- private bool IsSelected(KeyframeRef keyframeRef)
|
|
|
+ /// <param name="resetTime">If true the time offset/range will be recalculated, otherwise current time offset will
|
|
|
+ /// be kept as is.</param>
|
|
|
+ internal void CenterAndResize(bool resetTime)
|
|
|
{
|
|
|
- int curveIdx = selectedKeyframes.FindIndex(x =>
|
|
|
+ Vector2 offset, range;
|
|
|
+ GetOptimalRangeAndOffset(out offset, out range);
|
|
|
+
|
|
|
+ if (!resetTime)
|
|
|
{
|
|
|
- return x.curveIdx == keyframeRef.curveIdx;
|
|
|
- });
|
|
|
+ offset.x = Offset.x;
|
|
|
+ range.x = Range.x;
|
|
|
+ }
|
|
|
|
|
|
- if (curveIdx == -1)
|
|
|
- return false;
|
|
|
+ Range = range;
|
|
|
+ Offset = offset;
|
|
|
|
|
|
- int keyIdx = selectedKeyframes[curveIdx].keyIndices.FindIndex(x =>
|
|
|
- {
|
|
|
- return x == keyframeRef.keyIdx;
|
|
|
- });
|
|
|
+ UpdateScrollBarPosition();
|
|
|
+ UpdateScrollBarSize();
|
|
|
+ }
|
|
|
|
|
|
- return keyIdx != -1;
|
|
|
+ /// <summary>
|
|
|
+ /// Triggered when the user moves or resizes the horizontal scrollbar.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
|
|
|
+ /// <param name="size">New size of the scrollbar, in range [0, 1].</param>
|
|
|
+ private void OnHorzScrollOrResize(float position, float size)
|
|
|
+ {
|
|
|
+ SetHorzScrollbarProperties(position, size);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Opens the edit window for the currently selected keyframe.
|
|
|
+ /// Triggered when the user moves or resizes the vertical scrollbar.
|
|
|
/// </summary>
|
|
|
- private void EditSelectedKeyframe()
|
|
|
+ /// <param name="position">New position of the scrollbar, in range [0, 1].</param>
|
|
|
+ /// <param name="size">New size of the scrollbar, in range [0, 1].</param>
|
|
|
+ private void OnVertScrollOrResize(float position, float size)
|
|
|
{
|
|
|
- if (disableCurveEdit)
|
|
|
- {
|
|
|
- ShowReadOnlyMessage();
|
|
|
- return;
|
|
|
- }
|
|
|
+ SetVertScrollbarProperties(position, size);
|
|
|
+ }
|
|
|
|
|
|
- if (selectedKeyframes.Count == 0)
|
|
|
- return;
|
|
|
+ #endregion
|
|
|
|
|
|
- EdAnimationCurve curve = curveInfos[selectedKeyframes[0].curveIdx].curve;
|
|
|
- KeyFrame[] keyFrames = curve.KeyFrames;
|
|
|
+ #region Helpers
|
|
|
|
|
|
- int keyIndex = selectedKeyframes[0].keyIndices[0];
|
|
|
- KeyFrame keyFrame = keyFrames[keyIndex];
|
|
|
- Vector2I position = guiCurveDrawing.CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
|
|
|
+ /// <summary>
|
|
|
+ /// Returns width/height required to show the entire contents of the currently displayed curves.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>Width/height of the curve area, in curve space (time, value).</returns>
|
|
|
+ private Vector2 GetOptimalRange()
|
|
|
+ {
|
|
|
+ float xMin, xMax;
|
|
|
+ float yMin, yMax;
|
|
|
+ CalculateRange(curveInfos, out xMin, out xMax, out yMin, out yMax);
|
|
|
|
|
|
- Rect2I drawingBounds = GUIUtility.CalculateBounds(drawingPanel, window.GUI);
|
|
|
- position.x = MathEx.Clamp(position.x, 0, drawingBounds.width);
|
|
|
- position.y = MathEx.Clamp(position.y, 0, drawingBounds.height);
|
|
|
+ float xRange = xMax;
|
|
|
+ float yRange = Math.Max(Math.Abs(yMin), Math.Abs(yMax));
|
|
|
|
|
|
- Vector2I windowPos = position + new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
-
|
|
|
- KeyframeEditWindow editWindow = DropDownWindow.Open<KeyframeEditWindow>(window, windowPos);
|
|
|
- editWindow.Initialize(keyFrame, x =>
|
|
|
- {
|
|
|
- curve.UpdateKeyframe(keyIndex, x.time, x.value);
|
|
|
- curve.Apply();
|
|
|
+ // Add padding to y range
|
|
|
+ yRange *= 1.05f;
|
|
|
|
|
|
- guiCurveDrawing.Rebuild();
|
|
|
- },
|
|
|
- x =>
|
|
|
- {
|
|
|
- if (x)
|
|
|
- OnCurveModified?.Invoke();
|
|
|
- });
|
|
|
+ // Don't allow zero range
|
|
|
+ if (xRange == 0.0f)
|
|
|
+ xRange = 60.0f;
|
|
|
+
|
|
|
+ if (yRange == 0.0f)
|
|
|
+ yRange = 10.0f;
|
|
|
+
|
|
|
+ return new Vector2(xRange, yRange);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Opens the edit window for the currently selected event.
|
|
|
+ /// Returns the offset and range required for fully displaying the currently selected set of curves.
|
|
|
/// </summary>
|
|
|
- private void EditSelectedEvent()
|
|
|
+ /// <param name="offset">Offset used for centering the curves.</param>
|
|
|
+ /// <param name="range">Range representing the width/height in curve space (time, value). </param>
|
|
|
+ private void GetOptimalRangeAndOffset(out Vector2 offset, out Vector2 range)
|
|
|
{
|
|
|
- if (!showEvents)
|
|
|
- return;
|
|
|
+ float xMin, xMax;
|
|
|
+ float yMin, yMax;
|
|
|
+ CalculateRange(curveInfos, out xMin, out xMax, out yMin, out yMax);
|
|
|
|
|
|
- for (int i = 0; i < events.Count; i++)
|
|
|
- {
|
|
|
- if (events[i].selected)
|
|
|
- {
|
|
|
- StartEventEdit(i);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
+ float xRange = xMax - xMin;
|
|
|
+
|
|
|
+ float yRange = (yMax - yMin) * 0.5f;
|
|
|
+ float yOffset = yMin + yRange;
|
|
|
+
|
|
|
+ // Add padding to y range
|
|
|
+ yRange *= 1.05f;
|
|
|
+
|
|
|
+ // Don't allow zero range
|
|
|
+ if (xRange == 0.0f)
|
|
|
+ xRange = 60.0f;
|
|
|
+
|
|
|
+ if (yRange == 0.0f)
|
|
|
+ yRange = 10.0f;
|
|
|
+
|
|
|
+ offset = new Vector2(xMin, yOffset);
|
|
|
+ range = new Vector2(xRange, yRange);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/// <summary>
|
|
|
- /// Opens the event edit window for the specified event.
|
|
|
+ /// Calculates the total range covered by a set of curves.
|
|
|
/// </summary>
|
|
|
- /// <param name="eventIdx">Event index to open the edit window for.</param>
|
|
|
- private void StartEventEdit(int eventIdx)
|
|
|
+ /// <param name="curveInfos">Curves to calculate range for.</param>
|
|
|
+ /// <param name="xMin">Minimum time value present in the curves.</param>
|
|
|
+ /// <param name="xMax">Maximum time value present in the curves.</param>
|
|
|
+ /// <param name="yMin">Minimum curve value present in the curves.</param>
|
|
|
+ /// <param name="yMax">Maximum curve value present in the curves.</param>
|
|
|
+ private static void CalculateRange(CurveDrawInfo[] curveInfos, out float xMin, out float xMax, out float yMin,
|
|
|
+ out float yMax)
|
|
|
{
|
|
|
- AnimationEvent animEvent = events[eventIdx].animEvent;
|
|
|
+ // Note: This only evaluates at keyframes, we should also evaluate in-between in order to account for steep
|
|
|
+ // tangents
|
|
|
+ xMin = float.PositiveInfinity;
|
|
|
+ xMax = float.NegativeInfinity;
|
|
|
+ yMin = float.PositiveInfinity;
|
|
|
+ yMax = float.NegativeInfinity;
|
|
|
+
|
|
|
+ foreach (var curveInfo in curveInfos)
|
|
|
+ {
|
|
|
+ KeyFrame[] keyframes = curveInfo.curve.KeyFrames;
|
|
|
|
|
|
- Vector2I position = new Vector2I();
|
|
|
- position.x = guiEvents.GetOffset(animEvent.time);
|
|
|
- position.y = EVENTS_HEIGHT/2;
|
|
|
+ foreach (var key in keyframes)
|
|
|
+ {
|
|
|
+ xMin = Math.Min(xMin, key.time);
|
|
|
+ xMax = Math.Max(xMax, key.time);
|
|
|
+ yMin = Math.Min(yMin, key.value);
|
|
|
+ yMax = Math.Max(yMax, key.value);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Rect2I eventBounds = GUIUtility.CalculateBounds(eventsPanel, window.GUI);
|
|
|
- Vector2I windowPos = position + new Vector2I(eventBounds.x, eventBounds.y);
|
|
|
+ if (xMin == float.PositiveInfinity)
|
|
|
+ xMin = 0.0f;
|
|
|
|
|
|
- if (eventsSO == null)
|
|
|
- return;
|
|
|
+ if (xMax == float.NegativeInfinity)
|
|
|
+ xMax = 0.0f;
|
|
|
|
|
|
- Component[] components = eventsSO.GetComponents();
|
|
|
- string[] componentNames = new string[components.Length];
|
|
|
- for (int i = 0; i < components.Length; i++)
|
|
|
- componentNames[i] = components[i].GetType().Name;
|
|
|
+ if (yMin == float.PositiveInfinity)
|
|
|
+ yMin = 0.0f;
|
|
|
|
|
|
- EventEditWindow editWindow = DropDownWindow.Open<EventEditWindow>(window, windowPos);
|
|
|
- editWindow.Initialize(animEvent, componentNames, () =>
|
|
|
- {
|
|
|
- UpdateEventsGUI();
|
|
|
- },
|
|
|
- x =>
|
|
|
- {
|
|
|
- if(x)
|
|
|
- OnEventModified?.Invoke();
|
|
|
- });
|
|
|
+ if (yMax == float.NegativeInfinity)
|
|
|
+ yMax = 0.0f;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Shows a dialog box that notifies the user that the animation clip is read only.
|
|
|
+ /// Converts coordinate in curve space (time, value) into pixel coordinates relative to the curve drawing area
|
|
|
+ /// origin.
|
|
|
/// </summary>
|
|
|
- private void ShowReadOnlyMessage()
|
|
|
+ /// <param name="curveCoords">Time and value of the location to convert.</param>
|
|
|
+ /// <returns>Coordinates relative to curve drawing area's origin, in pixels.</returns>
|
|
|
+ public Vector2I CurveToPixelSpace(Vector2 curveCoords)
|
|
|
{
|
|
|
- LocEdString title = new LocEdString("Warning");
|
|
|
- LocEdString message =
|
|
|
- new LocEdString("You cannot edit keyframes on animation clips that" +
|
|
|
- " are imported from an external file.");
|
|
|
+ return guiCurveDrawing.CurveToPixelSpace(curveCoords);
|
|
|
+ }
|
|
|
|
|
|
- DialogBox.Open(title, message, DialogBox.Type.OK);
|
|
|
+ /// <summary>
|
|
|
+ /// Converts coordinates in window space (relative to the parent window origin) into coordinates in curve space.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="windowPos">Coordinates relative to parent editor window, in pixels.</param>
|
|
|
+ /// <param name="curveCoord">Curve coordinates within the range as specified by <see cref="Range"/>. Only
|
|
|
+ /// valid when function returns true.</param>
|
|
|
+ /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
|
|
|
+ public bool WindowToCurveSpace(Vector2I windowPos, out Vector2 curveCoord)
|
|
|
+ {
|
|
|
+ Rect2I elementBounds = GUIUtility.CalculateBounds(mainPanel, window.GUI);
|
|
|
+ Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
|
|
|
+
|
|
|
+ Rect2I drawingBounds = drawingPanel.Bounds;
|
|
|
+ Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
|
|
|
+
|
|
|
+ return guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord);
|
|
|
}
|
|
|
+
|
|
|
+ #endregion
|
|
|
}
|
|
|
|
|
|
/// <summary>
|