/// <summary> /// Opens the event edit window for the specified event. /// </summary> /// <param name="eventIdx">Event index to open the edit window for.</param> private void StartEventEdit(int eventIdx) { AnimationEvent animEvent = events[eventIdx].animEvent; Vector2I position = new Vector2I(); position.x = guiEvents.GetOffset(animEvent.Time); position.y = EVENTS_HEIGHT / 2; Rect2I eventBounds = GUIUtility.CalculateBounds(eventsPanel, window.GUI); Vector2I windowPos = position + new Vector2I(eventBounds.x, eventBounds.y); SceneObject so = window.SelectedSO; Component[] components = so.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(); OnEventModified?.Invoke(); }); }
/// <summary> /// Converts screen coordinates in coordinates relative to the panel containing the key editor control. /// </summary> /// <param name="screenPos">Coordinates in screen space.</param> /// <returns>Coordinates relative to the key editor control.</returns> private Vector2I ScreenToKeyEditorPos(Vector2I screenPos) { Vector2I windowPos = ScreenToWindowPos(screenPos); Rect2I elementBounds = GUIUtility.CalculateBounds(editorPanel, GUI); return(windowPos - new Vector2I(elementBounds.x, elementBounds.y)); }
/// <summary> /// Creates the scene camera and updates the render texture. Should be called at least once before using the /// scene view. Should be called whenever the window is resized. /// </summary> /// <param name="width">Width of the scene render target, in pixels.</param> /// <param name="height">Height of the scene render target, in pixels.</param> private void UpdateRenderTexture(int width, int height) { width = MathEx.Max(20, width); height = MathEx.Max(20, height); // Note: Depth buffer is required because ScenePicking uses it renderTexture = new RenderTexture2D(PixelFormat.R8G8B8A8, width, height, 1, false, true); renderTexture.Priority = 1; if (camera == null) { SceneObject sceneCameraSO = new SceneObject("SceneCamera", true); camera = sceneCameraSO.AddComponent <Camera>(); camera.Target = renderTexture; camera.ViewportRect = new Rect2(0.0f, 0.0f, 1.0f, 1.0f); sceneCameraSO.Position = new Vector3(0, 0.5f, 1); sceneCameraSO.LookAt(new Vector3(0, 0.5f, 0)); camera.Priority = 2; camera.NearClipPlane = 0.05f; camera.FarClipPlane = 2500.0f; camera.ClearColor = ClearColor; camera.Layers = UInt64.MaxValue & ~SceneAxesHandle.LAYER; // Don't draw scene axes in this camera cameraController = sceneCameraSO.AddComponent <SceneCamera>(); renderTextureGUI = new GUIRenderTexture(renderTexture); rtPanel.AddElement(renderTextureGUI); sceneGrid = new SceneGrid(camera); sceneSelection = new SceneSelection(camera); sceneGizmos = new SceneGizmos(camera); sceneHandles = new SceneHandles(this, camera); } else { camera.Target = renderTexture; renderTextureGUI.RenderTexture = renderTexture; } Rect2I rtBounds = new Rect2I(0, 0, width, height); renderTextureGUI.Bounds = rtBounds; focusCatcher.Bounds = GUIUtility.CalculateBounds(rtPanel, GUI); sceneAxesGUI.SetPosition(width - HandleAxesGUISize - HandleAxesGUIPaddingX, HandleAxesGUIPaddingY); // TODO - Consider only doing the resize once user stops resizing the widget in order to reduce constant // render target destroy/create cycle for every single pixel. camera.AspectRatio = width / (float)height; if (profilerCamera != null) { profilerCamera.Target = renderTexture; } }
/// <summary> /// Creates the scene camera and updates the render texture. Should be called at least once before using the /// scene view. Should be called whenever the window is resized. /// </summary> /// <param name="width">Width of the scene render target, in pixels.</param> /// <param name="height">Height of the scene render target, in pixels.</param> private void UpdateRenderTexture(int width, int height) { width = MathEx.Max(20, width); height = MathEx.Max(20, height); // Note: Depth buffer and readable flags are required because ScenePicking uses it Texture colorTex = Texture.Create2D((uint)width, (uint)height, PixelFormat.RGBA8, TextureUsage.Render | TextureUsage.CPUReadable); Texture depthTex = Texture.Create2D((uint)width, (uint)height, PixelFormat.D32_S8X24, TextureUsage.DepthStencil | TextureUsage.CPUReadable); renderTexture = new RenderTexture(colorTex, depthTex); renderTexture.Priority = 1; if (camera == null) { SceneObject sceneCameraSO = new SceneObject("SceneCamera", true); camera = sceneCameraSO.AddComponent <Camera>(); camera.Viewport.Target = renderTexture; camera.Viewport.Area = new Rect2(0.0f, 0.0f, 1.0f, 1.0f); sceneCameraSO.Position = new Vector3(0, 0.5f, 1); sceneCameraSO.LookAt(new Vector3(0, 0.5f, 0)); camera.Priority = 2; camera.Viewport.ClearColor = ClearColor; camera.Layers = UInt64.MaxValue & ~SceneAxesHandle.LAYER; // Don't draw scene axes in this camera cameraController = sceneCameraSO.AddComponent <SceneCamera>(); cameraController.Initialize(); renderTextureGUI = new GUIRenderTexture(renderTexture); rtPanel.AddElement(renderTextureGUI); sceneGrid = new SceneGrid(camera); sceneSelection = new SceneSelection(camera); sceneGizmos = new SceneGizmos(camera); sceneHandles = new SceneHandles(this, camera); } else { camera.Viewport.Target = renderTexture; renderTextureGUI.RenderTexture = renderTexture; } Rect2I rtBounds = new Rect2I(0, 0, width, height); renderTextureGUI.Bounds = rtBounds; focusCatcher.Bounds = GUIUtility.CalculateBounds(rtPanel, GUI); sceneAxesGUI.SetPosition(width - HandleAxesGUISize - HandleAxesGUIPaddingX, HandleAxesGUIPaddingY); // TODO - Consider only doing the resize once user stops resizing the widget in order to reduce constant // render target destroy/create cycle for every single pixel. camera.AspectRatio = width / (float)height; }
/// <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(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); return(guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord)); }
private void OnCameraOptionsClicked() { Vector2I openPosition; Rect2I buttonBounds = GUIUtility.CalculateBounds(cameraOptionsButton, GUI); openPosition.x = buttonBounds.x + buttonBounds.width / 2; openPosition.y = buttonBounds.y + buttonBounds.height / 2; SceneCameraOptionsDropdown cameraOptionsDropdown = DropDownWindow.Open <SceneCameraOptionsDropdown>(GUI, openPosition); cameraOptionsDropdown.Initialize(this); }
/// <summary> /// Triggered when the user clicks on the create button on the title bar. Creates a brand new object with default /// values in the place of the current array. /// </summary> private void OnCreateButtonClicked() { if (createContextMenu == null) { return; } Rect2I bounds = GUIUtility.CalculateBounds(guiCreateBtn, guiInternalTitleLayout); Vector2I position = new Vector2I(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); createContextMenu.Open(position, guiInternalTitleLayout); }
/// <summary> /// Triggered when the user clicks on the create button on the title bar. Creates a brand new object with default /// values in the place of the current array. /// </summary> private void OnCreateButtonClicked() { if (createContextMenu == null) { if (instantiableTypes.Length > 0) { property.SetValue(Activator.CreateInstance(instantiableTypes[0])); } } else { Rect2I bounds = GUIUtility.CalculateBounds(guiCreateBtn, guiInternalTitleLayout); Vector2I position = new Vector2I(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); createContextMenu.Open(position, guiInternalTitleLayout); } }
/// <summary> /// Converts screen coordinates into coordinates relative to the scene view render texture. /// </summary> /// <param name="screenPos">Coordinates relative to the screen.</param> /// <param name="scenePos">Output coordinates relative to the scene view texture.</param> /// <returns>True if the coordinates are within the scene view texture, false otherwise.</returns> private bool ScreenToScenePos(Vector2I screenPos, out Vector2I scenePos) { scenePos = screenPos; Vector2I windowPos = ScreenToWindowPos(screenPos); Rect2I bounds = GUIUtility.CalculateBounds(renderTextureGUI, GUI); if (bounds.Contains(windowPos)) { scenePos.x = windowPos.x - bounds.x; scenePos.y = windowPos.y - bounds.y; return(true); } return(false); }
/// <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(); // TODO UNDOREDO guiCurveDrawing.Rebuild(); OnCurveModified?.Invoke(); }); }
/// <summary> /// Returns the bounds of the GUI element relative to the provided panel. /// </summary> public Rect2I GetBounds(GUIPanel relativeTo) { return(GUIUtility.CalculateBounds(canvas, relativeTo)); }
/// <summary> /// Handles input. Should be called by the owning window whenever a pointer is moved. /// </summary> /// <param name="ev">Object containing pointer move event information.</param> internal void OnPointerMoved(PointerEvent ev) { if (ev.Button != PointerButton.Left) { return; } if (isPointerHeld) { Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos); Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI); Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y); if (isMousePressedOverKey || isMousePressedOverTangent) { Rect2I drawingBounds = drawingPanel.Bounds; Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y); if (!isDragInProgress) { 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; 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])); } } } // TODO - UNDOREDO record keyframe or tangent isDragInProgress = true; } } if (isDragInProgress) { if (isMousePressedOverKey && !disableCurveEdit) { Vector2 diff = Vector2.Zero; Vector2 dragStartCurve; if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve)) { Vector2 currentPosCurve; if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve)) { 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 = 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)); } } OnCurveModified?.Invoke(); guiCurveDrawing.Rebuild(); UpdateEventsGUI(); } else if (isMousePressedOverTangent && !disableCurveEdit) { EdAnimationCurve curve = curveInfos[draggedTangent.keyframeRef.curveIdx].curve; KeyFrame keyframe = curve.KeyFrames[draggedTangent.keyframeRef.keyIdx]; Vector2 keyframeCurveCoords = new Vector2(keyframe.time, keyframe.value); Vector2 currentPosCurve; if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve)) { 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(); OnCurveModified?.Invoke(); guiCurveDrawing.Rebuild(); } } } } else // Move frame marker { int frameIdx = guiTimeline.GetFrame(pointerPos); if (frameIdx != -1) { SetMarkedFrame(frameIdx); } OnFrameSelected?.Invoke(frameIdx); } } }
/// <summary> /// Handles input. Should be called by the owning window whenever a pointer is pressed. /// </summary> /// <param name="ev">Object containing pointer press event information.</param> internal void OnPointerPressed(PointerEvent ev) { 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); 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); if (ev.Button == PointerButton.Left) { Vector2 curveCoord; if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord)) { 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; } guiCurveDrawing.Rebuild(); UpdateEventsGUI(); } else { int frameIdx = guiTimeline.GetFrame(pointerPos); if (frameIdx != -1) { SetMarkedFrame(frameIdx); } else { int eventIdx; if (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 if (ev.Button == PointerButton.Right) { Vector2 curveCoord; if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord)) { contextClickPosition = drawingPos; KeyframeRef keyframeRef; if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef)) { ClearSelection(); blankContextMenu.Open(pointerPos, gui); guiCurveDrawing.Rebuild(); UpdateEventsGUI(); } else { // If clicked outside of current selection, just select the one keyframe if (!IsSelected(keyframeRef)) { ClearSelection(); SelectKeyframe(keyframeRef); guiCurveDrawing.Rebuild(); UpdateEventsGUI(); } keyframeContextMenu.Open(pointerPos, gui); } } else if (guiEvents.GetFrame(eventPos) != -1) // Clicked over events bar { contextClickPosition = eventPos; int eventIdx; if (!guiEvents.FindEvent(eventPos, out eventIdx)) { ClearSelection(); blankEventContextMenu.Open(pointerPos, gui); 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, gui); } } } }