//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System.Collections.Generic;
using BansheeEngine;
namespace BansheeEditor
{
/** @addtogroup Windows
* @{
*/
///
/// Displays animation curve editor window.
///
internal class AnimationWindow : EditorWindow
{
public struct KeyframeRef
{
public KeyframeRef(int curveIdx, int keyIdx)
{
this.curveIdx = curveIdx;
this.keyIdx = keyIdx;
}
public int curveIdx;
public int keyIdx;
}
private GUIGraphTime timeline;
private GUICurveDrawing curveDrawing;
private GUIGraphValues sidebar;
private GUIFloatField lengthField;
private GUIIntField fpsField;
private GUIFloatField yRangeField;
private GUIButton addKeyframeBtn;
private GUILayout buttonLayout;
private EdAnimationCurve[] curves = new EdAnimationCurve[0];
private int markedFrameIdx;
private List selectedKeyframes = new List();
// Keyframe drag
private bool isMousePressedOverKey;
private KeyFrame[] draggedKeyframes;
private Vector2 dragStart;
///
/// Opens the animation window.
///
[MenuItem("Windows/Animation", ButtonModifier.CtrlAlt, ButtonCode.A, 6000)]
private static void OpenGameWindow()
{
OpenWindow();
}
///
protected override LocString GetDisplayName()
{
return new LocEdString("Animation");
}
private void OnInitialize()
{
lengthField = new GUIFloatField(new LocEdString("Length"), 50);
fpsField = new GUIIntField(new LocEdString("FPS"), 50);
yRangeField = new GUIFloatField(new LocEdString("Y range"), 50);
addKeyframeBtn = new GUIButton(new LocEdString("Add keyframe"));
lengthField.Value = 60.0f;
fpsField.Value = 1;
yRangeField.Value = 20.0f;
lengthField.OnChanged += x =>
{
timeline.SetRange(lengthField.Value);
curveDrawing.SetRange(lengthField.Value, yRangeField.Value);
};
fpsField.OnChanged += x =>
{
timeline.SetFPS(x);
curveDrawing.SetFPS(x);
};
yRangeField.OnChanged += x =>
{
curveDrawing.SetRange(lengthField.Value, x);
sidebar.SetRange(x * -0.5f, x * 0.5f);
};
addKeyframeBtn.OnClick += () =>
{
AddKeyframeAtMarker();
};
GUILayout mainLayout = GUI.AddLayoutY();
buttonLayout = mainLayout.AddLayoutX();
buttonLayout.AddSpace(5);
buttonLayout.AddElement(lengthField);
buttonLayout.AddSpace(5);
buttonLayout.AddElement(yRangeField);
buttonLayout.AddSpace(5);
buttonLayout.AddElement(fpsField);
buttonLayout.AddSpace(5);
buttonLayout.AddElement(addKeyframeBtn);
buttonLayout.AddSpace(5);
timeline = new GUIGraphTime(mainLayout, Width, 20);
curves = CreateDummyCurves();
curveDrawing = new GUICurveDrawing(mainLayout, Width, Height - 20, curves);
curveDrawing.SetRange(60.0f, 20.0f);
GUIPanel sidebarPanel = GUI.AddPanel(-10);
sidebarPanel.SetPosition(0, 20 + buttonLayout.Bounds.height);
sidebar = new GUIGraphValues(sidebarPanel, 30, Height - 20 - buttonLayout.Bounds.height);
sidebar.SetRange(-10.0f, 10.0f);
curveDrawing.SetSize(Width, Height - 20 - buttonLayout.Bounds.height);
curveDrawing.Rebuild();
// TODO - Calculate min/max Y and range to set as default
// - Also recalculate whenever curves change and increase as needed
}
private void AddKeyframeAtMarker()
{
ClearSelection();
foreach (var curve in curves)
{
float t = curveDrawing.GetTimeForFrame(markedFrameIdx);
float value = curve.Native.Evaluate(t);
curve.AddKeyframe(t, value);
}
curveDrawing.Rebuild();
}
private void DeleteSelectedKeyframes()
{
// Sort keys from highest to lowest so they can be removed without changing the indices of the keys
// after them
selectedKeyframes.Sort((x, y) =>
{
if (x.curveIdx.Equals(y.curveIdx))
return y.keyIdx.CompareTo(x.keyIdx);
return x.curveIdx.CompareTo(y.curveIdx);
});
foreach (var keyframe in selectedKeyframes)
curves[keyframe.curveIdx].RemoveKeyframe(keyframe.keyIdx);
ClearSelection();
curveDrawing.Rebuild();
}
private EdAnimationCurve[] CreateDummyCurves()
{
EdAnimationCurve[] curves = new EdAnimationCurve[1];
curves[0] = new EdAnimationCurve();
curves[0].AddKeyframe(0.0f, 1.0f);
curves[0].AddKeyframe(10.0f, 5.0f);
curves[0].AddKeyframe(15.0f, -2.0f);
curves[0].AddKeyframe(20.0f, 3.0f, TangentMode.InStep);
return curves;
}
protected override void WindowResized(int width, int height)
{
timeline.SetSize(width, 20);
curveDrawing.SetSize(width, height - 20 - buttonLayout.Bounds.height);
sidebar.SetSize(30, height - 20 - buttonLayout.Bounds.height);
curveDrawing.Rebuild();
}
private void ClearSelection()
{
curveDrawing.ClearSelectedKeyframes();
selectedKeyframes.Clear();
isMousePressedOverKey = false;
}
private void OnEditorUpdate()
{
if (Input.IsPointerButtonDown(PointerButton.Left))
{
Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
Vector2 curveCoord;
int curveIdx;
int keyIdx;
if (curveDrawing.GetCoordInfo(windowPos, out curveCoord, out curveIdx, out keyIdx))
{
if (keyIdx == -1)
ClearSelection();
else
{
if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
ClearSelection();
curveDrawing.SelectKeyframe(curveIdx, keyIdx, true);
int existingIdx = selectedKeyframes.FindIndex(x =>
{
return x.curveIdx == curveIdx && x.keyIdx == keyIdx;
});
if (existingIdx == -1)
selectedKeyframes.Add(new KeyframeRef(curveIdx, keyIdx));
isMousePressedOverKey = true;
dragStart = curveCoord;
}
curveDrawing.Rebuild();
}
}
else if (Input.IsPointerButtonHeld(PointerButton.Left))
{
Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
if (isMousePressedOverKey)
{
// TODO - Check if pointer moves some minimal amount
// - If so, start drag. Record all current positions
// - Calculate offset in curve space and apply to all keyframes
}
else
{
int frameIdx = timeline.GetFrame(windowPos);
if (frameIdx != -1)
{
timeline.SetMarkedFrame(frameIdx);
curveDrawing.SetMarkedFrame(frameIdx);
markedFrameIdx = frameIdx;
curveDrawing.Rebuild();
}
}
}
else if (Input.IsPointerButtonUp(PointerButton.Left))
{
isMousePressedOverKey = false;
}
if(Input.IsButtonUp(ButtonCode.Delete))
DeleteSelectedKeyframes();
}
}
/** @} */
}