Ver Fonte

Refactor: GUICurveEditor made more generic so it may be used outside of the animation window

BearishSun há 7 anos atrás
pai
commit
79223dd0c9

+ 1085 - 668
Source/Scripting/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -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>

+ 6 - 417
Source/Scripting/MBansheeEditor/Windows/AnimationWindow.cs

@@ -20,9 +20,6 @@ namespace BansheeEditor
     internal class AnimationWindow : EditorWindow
     {
         private const int FIELD_DISPLAY_WIDTH = 300;
-        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 SceneObject selectedSO;
 
@@ -55,7 +52,7 @@ namespace BansheeEditor
             if (selectedSO == null)
                 return;
 
-            HandleDragAndZoomInput();
+            guiCurveEditor.HandleDragAndZoomInput();
 
             if (state == State.Playback)
             {
@@ -131,13 +128,7 @@ namespace BansheeEditor
         private GUILayout buttonLayout;
 
         private int buttonLayoutHeight;
-        private int scrollBarWidth;
-        private int scrollBarHeight;
 
-        private GUIResizeableScrollBarH horzScrollBar;
-        private GUIResizeableScrollBarV vertScrollBar;
-
-        private GUIPanel editorPanel;
         private GUIAnimFieldDisplay guiFieldDisplay;
         private GUICurveEditor guiCurveEditor;
 
@@ -416,30 +407,13 @@ namespace BansheeEditor
             bottomButtonLayout.AddElement(addPropertyBtn);
             bottomButtonLayout.AddElement(delPropertyBtn);
 
-            horzScrollBar = new GUIResizeableScrollBarH();
-            horzScrollBar.OnScrollOrResize += OnHorzScrollOrResize;
-
-            vertScrollBar = new GUIResizeableScrollBarV();
-            vertScrollBar.OnScrollOrResize += OnVertScrollOrResize;
-
             GUITexture separator = new GUITexture(null, EditorStyles.Separator, GUIOption.FixedWidth(3));
             contentLayout.AddElement(separator);
 
             GUILayout curveLayout = contentLayout.AddLayoutY();
-            GUILayout curveLayoutHorz = curveLayout.AddLayoutX();
-            GUILayout horzScrollBarLayout = curveLayout.AddLayoutX();
-            horzScrollBarLayout.AddElement(horzScrollBar);
-            horzScrollBarLayout.AddFlexibleSpace();
-
-            editorPanel = curveLayoutHorz.AddPanel();
-            curveLayoutHorz.AddElement(vertScrollBar);
-            curveLayoutHorz.AddFlexibleSpace();
-
-            scrollBarHeight = horzScrollBar.Bounds.height;
-            scrollBarWidth = vertScrollBar.Bounds.width;
 
             Vector2I curveEditorSize = GetCurveEditorSize();
-            guiCurveEditor = new GUICurveEditor(this, editorPanel, curveEditorSize.x, curveEditorSize.y, true);
+            guiCurveEditor = new GUICurveEditor(this, curveLayout, curveEditorSize.x, curveEditorSize.y, true);
             guiCurveEditor.SetEventSceneObject(selectedSO);
 
             guiCurveEditor.OnFrameSelected += OnFrameSelected;
@@ -473,12 +447,8 @@ namespace BansheeEditor
                 if(state != State.Recording)
                     SwitchState(State.Normal);
             };
-            guiCurveEditor.Redraw();
 
-            horzScrollBar.SetWidth(curveEditorSize.x);
-            vertScrollBar.SetHeight(curveEditorSize.y);
-
-            UpdateScrollBarSize();
+            guiCurveEditor.Redraw();
         }
 
         /// <summary>
@@ -493,199 +463,6 @@ namespace BansheeEditor
             Vector2I curveEditorSize = GetCurveEditorSize();
             guiCurveEditor.SetSize(curveEditorSize.x, curveEditorSize.y);
             guiCurveEditor.Redraw();
-
-            horzScrollBar.SetWidth(curveEditorSize.x);
-            vertScrollBar.SetHeight(curveEditorSize.y);
-
-            UpdateScrollBarSize();
-            UpdateScrollBarPosition();
-        }
-        #endregion
-
-        #region Scroll, drag, zoom
-        private Vector2I dragStartPos;
-        private bool isButtonHeld;
-        private bool isDragInProgress;
-
-        private float zoomAmount;
-
-        /// <summary>
-        /// Handles mouse scroll wheel and dragging events in order to zoom or drag the displayed curve editor contents.
-        /// </summary>
-        private void HandleDragAndZoomInput()
-        {
-            // Handle middle mouse dragging
-            if (isDragInProgress)
-            {
-                float lengthPerPixel = guiCurveEditor.Range.x / guiCurveEditor.Width;
-                float heightPerPixel = guiCurveEditor.Range.y / guiCurveEditor.Height;
-
-                float dragX = Input.GetAxisValue(InputAxis.MouseX) * DRAG_SCALE * lengthPerPixel;
-                float dragY = Input.GetAxisValue(InputAxis.MouseY) * DRAG_SCALE * heightPerPixel;
-
-                Vector2 offset = guiCurveEditor.Offset;
-                offset.x = Math.Max(0.0f, offset.x + dragX);
-                offset.y -= dragY;
-
-                guiCurveEditor.Offset = offset;
-                UpdateScrollBarSize();
-                UpdateScrollBarPosition();
-            }
-
-            // Handle zoom in/out
-            float scroll = Input.GetAxisValue(InputAxis.MouseZ);
-            if (scroll != 0.0f)
-            {
-                Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
-                Vector2 curvePos;
-                if (guiCurveEditor.WindowToCurveSpace(windowPos, out curvePos))
-                {
-                    float zoom = scroll * ZOOM_SCALE;
-                    Zoom(curvePos, zoom);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Moves or resizes the vertical scroll bar under the curve editor.
-        /// </summary>
-        /// <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)
-        {
-            Vector2 visibleRange = guiCurveEditor.Range;
-            Vector2 totalRange = GetTotalRange();
-
-            visibleRange.y = totalRange.y*size;
-            guiCurveEditor.Range = visibleRange;
-
-            float scrollableRange = totalRange.y - visibleRange.y;
-
-            Vector2 offset = guiCurveEditor.Offset;
-            offset.y = -scrollableRange * (position * 2.0f - 1.0f);
-
-            guiCurveEditor.Offset = offset;
-        }
-
-        /// <summary>
-        /// Moves or resizes the horizontal scroll bar under the curve editor.
-        /// </summary>
-        /// <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)
-        {
-            Vector2 visibleRange = guiCurveEditor.Range;
-            Vector2 totalRange = GetTotalRange();
-
-            visibleRange.x = totalRange.x * size;
-            guiCurveEditor.Range = visibleRange;
-
-            float scrollableRange = totalRange.x - visibleRange.x;
-
-            Vector2 offset = guiCurveEditor.Offset;
-            offset.x = scrollableRange * position;
-
-            guiCurveEditor.Offset = offset;
-        }
-
-        /// <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 = guiCurveEditor.Range;
-            Vector2 totalRange = GetTotalRange();
-
-            horzScrollBar.HandleSize = visibleRange.x / totalRange.x;
-            vertScrollBar.HandleSize = visibleRange.y / totalRange.y;
-        }
-
-        /// <summary>
-        /// Updates the position of both scrollbars depending on the offset currently applied to the visible curve area.
-        /// </summary>
-        private void UpdateScrollBarPosition()
-        {
-            Vector2 visibleRange = guiCurveEditor.Range;
-            Vector2 totalRange = GetTotalRange();
-            Vector2 scrollableRange = totalRange - visibleRange;
-
-            Vector2 offset = guiCurveEditor.Offset;
-            if (scrollableRange.x > 0.0f)
-                horzScrollBar.Position = offset.x / scrollableRange.x;
-            else
-                horzScrollBar.Position = 0.0f;
-
-            if (scrollableRange.y > 0.0f)
-            {
-                float pos = offset.y/scrollableRange.y;
-                float sign = MathEx.Sign(pos);
-                pos = sign*MathEx.Clamp01(MathEx.Abs(pos));
-                pos = (1.0f - pos) /2.0f;
-
-                vertScrollBar.Position = pos;
-            }
-            else
-                vertScrollBar.Position = 0.0f;
-        }
-
-        /// <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);
-
-            Vector2 optimalRange = GetOptimalRange();
-            return optimalRange / zoomLevel;
-        }
-
-        /// <summary>
-        /// Returns the total width/height of the contents of the curve area.
-        /// </summary>
-        /// <returns>Width/height of the curve area, in curve space (value, time).</returns>
-        private Vector2 GetTotalRange()
-        {
-            // Return optimal range (that covers the visible curve)
-            Vector2 optimalRange = GetOptimalRange();
-
-            // Increase range in case user zoomed out
-            Vector2 zoomedRange = GetZoomedRange();
-            return Vector2.Max(optimalRange, zoomedRange);
-        }
-
-        /// <summary>
-        /// Zooms in or out at the provided position in the curve display.
-        /// </summary>
-        /// <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)
-        {
-            // 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();
-
-            Vector2 zoomedDiff = zoomedRange - oldZoomedRange;
-
-            Vector2 currentRange = guiCurveEditor.Range;
-            Vector2 newRange = currentRange + zoomedDiff;
-            guiCurveEditor.Range = newRange;
-
-            // 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;
-
-            Vector2 relativeCurvePos = curvePos - guiCurveEditor.Offset;
-            Vector2 newCurvePos = relativeCurvePos * rangeScale;
-            Vector2 diff = newCurvePos - relativeCurvePos;
-
-            guiCurveEditor.Offset -= diff;
-
-            UpdateScrollBarSize();
-            UpdateScrollBarPosition();
         }
         #endregion
 
@@ -774,7 +551,6 @@ namespace BansheeEditor
                 SwitchState(State.Empty);
 
                 selectedSO = so;
-                zoomAmount = 0.0f;
                 selectedFields.Clear();
                 clipInfo = null;
                 UndoRedo.Clear();
@@ -1316,66 +1092,6 @@ namespace BansheeEditor
             return curvesToDisplay.ToArray();
         }
 
-        /// <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()
-        {
-            CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
-
-            float xMin, xMax;
-            float yMin, yMax;
-            CalculateRange(curvesToDisplay, out xMin, out xMax, out yMin, out yMax);
-
-            float xRange = xMax;
-            float yRange = Math.Max(Math.Abs(yMin), Math.Abs(yMax));
-
-            // 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;
-
-            return new Vector2(xRange, yRange);
-        }
-
-        /// <summary>
-        /// Returns the offset and range required for fully displaying the currently selected set of curves. 
-        /// </summary>
-        /// <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)
-        {
-            CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
-
-            float xMin, xMax;
-            float yMin, yMax;
-            CalculateRange(curvesToDisplay, out xMin, out xMax, out yMin, out yMax);
-
-            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>
         /// Calculates an unique color for each animation curve.
         /// </summary>
@@ -1389,29 +1105,6 @@ namespace BansheeEditor
             }
         }
 
-        /// <summary>
-        /// Updates the offset and range of the curve display to fully fit the currently selected set of curves.
-        /// </summary>
-        /// <param name="resetTime">If true the time offset/range will be recalculated, otherwise current time offset will
-        ///                         be kept as is.</param>
-        private void CenterAndResizeCurveDisplay(bool resetTime)
-        {
-            Vector2 offset, range;
-            GetOptimalRangeAndOffset(out offset, out range);
-
-            if (!resetTime)
-            {
-                offset.x = guiCurveEditor.Offset.x;
-                range.x = guiCurveEditor.Range.x;
-            }
-
-            guiCurveEditor.Range = range;
-            guiCurveEditor.Offset = offset;
-
-            UpdateScrollBarPosition();
-            UpdateScrollBarSize();
-        }
-
         /// <summary>
         /// Updates the curve display with currently selected curves.
         /// </summary>
@@ -1421,8 +1114,7 @@ namespace BansheeEditor
         {
             CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
             guiCurveEditor.SetCurves(curvesToDisplay);
-
-            CenterAndResizeCurveDisplay(resetTime);
+            guiCurveEditor.CenterAndResize(resetTime);
         }
 
         #endregion 
@@ -1593,56 +1285,12 @@ namespace BansheeEditor
         private Vector2I GetCurveEditorSize()
         {
             Vector2I output = new Vector2I();
-            output.x = Math.Max(0, Width - FIELD_DISPLAY_WIDTH - scrollBarWidth);
-            output.y = Math.Max(0, Height - buttonLayoutHeight - scrollBarHeight);
+            output.x = Math.Max(0, Width - FIELD_DISPLAY_WIDTH);
+            output.y = Math.Max(0, Height - buttonLayoutHeight);
 
             return output;
         }
 
-        /// <summary>
-        /// Calculates the total range covered by a set of curves.
-        /// </summary>
-        /// <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)
-        {
-            // 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;
-
-                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);
-                }
-            }
-
-            if (xMin == float.PositiveInfinity)
-                xMin = 0.0f;
-
-            if (xMax == float.NegativeInfinity)
-                xMax = 0.0f;
-
-            if (yMin == float.PositiveInfinity)
-                yMin = 0.0f;
-
-            if (yMax == float.NegativeInfinity)
-                yMax = 0.0f;
-        }
-
         /// <summary>
         /// Attempts to find a curve field at the specified path.
         /// </summary>
@@ -1918,16 +1566,6 @@ namespace BansheeEditor
         {
             guiCurveEditor.OnPointerPressed(ev);
 
-            if (ev.button == PointerButton.Middle)
-            {
-                Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
-                Vector2 curvePos;
-                if (guiCurveEditor.WindowToCurveSpace(windowPos, out curvePos))
-                {
-                    dragStartPos = windowPos;
-                    isButtonHeld = true;
-                }
-            }
         }
 
         /// <summary>
@@ -1947,26 +1585,6 @@ namespace BansheeEditor
         {
             guiCurveEditor.OnPointerMoved(ev);
 
-            if (isButtonHeld)
-            {
-                Vector2I windowPos = ScreenToWindowPos(ev.ScreenPos);
-
-                int distance = Vector2I.Distance(dragStartPos, windowPos);
-                if (distance >= DRAG_START_DISTANCE)
-                {
-                    isDragInProgress = 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>
@@ -1975,15 +1593,6 @@ namespace BansheeEditor
         /// <param name="ev">Information about the mouse release event.</param>
         private void OnPointerReleased(PointerEvent ev)
         {
-            if (isDragInProgress)
-            {
-                Cursor.Show();
-                Cursor.ClipDisable();
-            }
-
-            isButtonHeld = false;
-            isDragInProgress = false;
-
             guiCurveEditor.OnPointerReleased(ev);
         }
 
@@ -2018,26 +1627,6 @@ namespace BansheeEditor
             ApplyClipChanges();
         }
 
-        /// <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>
-        /// Triggered when the user moves or resizes the vertical 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 OnVertScrollOrResize(float position, float size)
-        {
-            SetVertScrollbarProperties(position, size);
-        }
-
         /// <summary>
         /// Triggered when the user selects a new curve field.
         /// </summary>