using ImGuiNET; using T3.Core.Animation; using T3.Core.DataTypes; using T3.Core.DataTypes.Vector; using T3.Core.Operator; using T3.Editor.Gui.Interaction.Snapping; using T3.Editor.Gui.UiHelpers; using T3.Editor.UiModel.Commands; using T3.Editor.UiModel.Commands.Animation; namespace T3.Editor.Gui.Interaction.WithCurves; /// /// Note: This component is currently not used because most interactions can be completed by dragging keyframes with /// snapping or using the TimeRangeSelection's scale functionality. /// internal class CurveEditBox : IValueSnapAttractor { public CurveEditBox(ScalableCanvas canvas, ValueSnapHandler snapHandler) { _canvas = canvas; _snapHandler = snapHandler; //_selectionHandler = selectionHandler; } private HashSet _selectedKeyframes; public void Draw(Guid compositionSymbolId, HashSet selectedKeyframes, Instance compositionOp) { if (selectedKeyframes.Count <= 1) return; if (!ImGui.GetIO().KeyAlt) { if (_changeKeyframesCommand != null) { CompleteDragCommand(); } return; } _compositionOp = compositionOp; _selectedKeyframes = selectedKeyframes; var drawlist = ImGui.GetWindowDrawList(); _bounds = GetBoundingBox(); var minU = _bounds.Min.X; var maxU = _bounds.Max.X; var minV = _bounds.Min.Y; var maxV = _bounds.Max.Y; var boundsOnScreen = _canvas.TransformRect(_bounds); drawlist.AddRect(boundsOnScreen.Min, boundsOnScreen.Max, SelectBoxBorderColor); drawlist.AddRectFilled(boundsOnScreen.Min, boundsOnScreen.Max, SelectBoxBorderFill); var deltaOnCanvas = _canvas.InverseTransformDirection(ImGui.GetIO().MouseDelta); /* ScaleHandle( id: "##top", screenPos: boundsOnScreen.Min - VerticalHandleOffset, size: new Vector2(boundsOnScreen.GetWidth(), DragHandleSize), Direction.Vertical, scale: (maxV + deltaOnCanvas.Y - minV) / (maxV - minV), (scale, ep) => { ep.ManipulateV(minV + (ep.PosOnCanvas.Y - minV) * scale); } //(scale, ep) => { ManipulateKeyframeValue(ep, minV + (ep.PosOnCanvas.Y - minV) * scale); } ); ScaleHandle( id: "##bottom", screenPos: new Vector2(boundsOnScreen.Min.X, boundsOnScreen.Max.Y), size: new Vector2(boundsOnScreen.GetWidth(), DragHandleSize), Direction.Vertical, scale: (maxV - deltaOnCanvas.Y - minV) / (maxV - minV), (scale, ep) => { ep.ManipulateV(maxV - (maxV - ep.PosOnCanvas.Y) * scale); } ); ScaleHandle( id: "##right", screenPos: new Vector2(boundsOnScreen.Max.X, boundsOnScreen.Min.Y), size: new Vector2(DragHandleSize, boundsOnScreen.GetHeight()), Direction.Horizontal, scale: (maxU + deltaOnCanvas.X - minU) / (maxU - minU), (scale, ep) => { ep.ManipulateU(minU + (ep.PosOnCanvas.X - minU) * scale); } ); ScaleHandle( id: "##left", screenPos: boundsOnScreen.Min - HorizontalHandleOffset, size: new Vector2(DragHandleSize, boundsOnScreen.GetHeight()), Direction.Horizontal, scale: (maxU - deltaOnCanvas.X - minU) / (maxU - minU), (scale, ep) => { ep.ManipulateU(maxU - (maxU - ep.PosOnCanvas.X) * scale); } ); */ var combined = (MoveRingInnerRadius + MoveRingOuterRadius); var center = _canvas.TransformPosition(_bounds.GetCenter()); MoveHandle(compositionSymbolId, "<##moveLeft", Direction.Horizontal, screenPos: center + new Vector2(-MoveRingOuterRadius, -0.5f * combined), size: new Vector2(MoveRingOuterRadius - MoveRingInnerRadius, combined)); MoveHandle(compositionSymbolId, "^##moveUp", Direction.Vertical, screenPos: center + new Vector2(-0.5f * combined, -MoveRingOuterRadius), size: new Vector2(combined, MoveRingOuterRadius - MoveRingInnerRadius)); MoveHandle(compositionSymbolId, ">##moveRight", Direction.Horizontal, screenPos: center + new Vector2(MoveRingInnerRadius, -0.5f * combined), size: new Vector2(MoveRingOuterRadius - MoveRingInnerRadius, combined)); MoveHandle(compositionSymbolId, "v##moveDown", Direction.Vertical, screenPos: center + new Vector2(-0.5f * combined, MoveRingInnerRadius), size: new Vector2(combined, MoveRingOuterRadius - MoveRingInnerRadius)); MoveHandle(compositionSymbolId, "+##both", Direction.Both, screenPos: center - new Vector2(1, 1) * MoveRingInnerRadius, size: Vector2.One * MoveRingInnerRadius * 2f); } #region implement snapping interface ----------------------------------- void IValueSnapAttractor.CheckForSnap(ref SnapResult snapResult) { } #endregion private const float MoveRingOuterRadius = 25; private const float MoveRingInnerRadius = 3; private enum Direction { Horizontal, Vertical, Both, } private void ScaleHandle(string id, Vector2 screenPos, Vector2 size, Direction direction, float scale, Action scaleFunction) { if ((direction == Direction.Vertical && _bounds.GetHeight() <= 0.001f) || (direction == Direction.Horizontal && _bounds.GetWidth() <= 0.001f)) return; ImGui.SetCursorScreenPos(screenPos); ImGui.Button(id, size); if (ImGui.IsItemActive() || ImGui.IsItemHovered()) ImGui.SetMouseCursor( direction == Direction.Horizontal ? ImGuiMouseCursor.ResizeEW : ImGuiMouseCursor.ResizeNS); if (!ImGui.IsItemActive() || !ImGui.IsMouseDragging(ImGuiMouseButton.Left)) return; if (Double.IsNaN(scale) || Math.Abs(scale) > 10000) return; if (scale < 0) scale = 0; } private double _uDragStarted; private double _boundUMaxDragStarted; private double _boundUMinDragStarted; private double _lastU; private void MoveHandle(in Guid compositionSymbolId, string labelAndId, Direction direction, Vector2 screenPos, Vector2 size) { ImGui.SetCursorScreenPos(screenPos); ImGui.PushStyleColor(ImGuiCol.Button, new Color(0.2f).Rgba); ImGui.Button(labelAndId, size); ImGui.PopStyleColor(); var isMouseDragging = ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left); if (isMouseDragging) { var u = _canvas.InverseTransformX(ImGui.GetIO().MousePos.X); if (_changeKeyframesCommand == null) { StartDragCommand(compositionSymbolId); _uDragStarted = u; _lastU = u; _boundUMaxDragStarted = _bounds.Max.X; _boundUMinDragStarted = _bounds.Min.X; //_lastBoundsMin = _boundUMinDragStarted; } var totalDeltaU = u - _uDragStarted; if (!ImGui.GetIO().KeyShift) { var newBoundsMax = totalDeltaU + _boundUMaxDragStarted; var newBoundsMin = totalDeltaU + _boundUMinDragStarted; if (_snapHandler.TryCheckForSnapping(newBoundsMin, out var snappedMinValue, _canvas.Scale.X, new List { this })) { totalDeltaU = snappedMinValue - _boundUMinDragStarted; } else if (_snapHandler.TryCheckForSnapping(newBoundsMax, out var snappedMaxValue, _canvas.Scale.X, new List { this })) { totalDeltaU = snappedMaxValue - _boundUMaxDragStarted; } } var newU = totalDeltaU + _uDragStarted; var deltaU = newU - _lastU; _lastU = newU; foreach (var ep in _selectedKeyframes) { ep.U += deltaU; } } else if (ImGui.IsItemDeactivated()) { CompleteDragCommand(); } } public void StartDragCommand(Guid compositionSymbolId) { // FIXME: getting current curves is complicated here. var mockNotWorking = new List(); _changeKeyframesCommand = new ChangeKeyframesCommand(_selectedKeyframes, mockNotWorking); } public void CompleteDragCommand() { if (_changeKeyframesCommand == null) return; // Update reference in macro command _changeKeyframesCommand.StoreCurrentValues(); UndoRedoStack.Add(_changeKeyframesCommand); _changeKeyframesCommand = null; } private static ChangeKeyframesCommand _changeKeyframesCommand; private ImRect GetBoundingBox() { var minU = float.PositiveInfinity; var minV = float.PositiveInfinity; var maxU = float.NegativeInfinity; var maxV = float.NegativeInfinity; if (_selectedKeyframes.Count > 1) { foreach (var selected in _selectedKeyframes) { minU = Math.Min(minU, (float)selected.U); maxU = Math.Max(maxU, (float)selected.U); minV = Math.Min(minV, (float)selected.Value); maxV = Math.Max(maxV, (float)selected.Value); } return new ImRect(minU, minV, maxU, maxV); } if (_selectedKeyframes.Count == 1) { var vDef = _selectedKeyframes.First(); minU = maxU = (float)vDef.U; minV = maxV = (float)vDef.Value; return new ImRect(minU, minV, maxU, maxV); } return new ImRect(100, 100, 200, 200); } private ImRect _bounds; private readonly ScalableCanvas _canvas; // Styling private const float DragHandleSize = 10; private static readonly Vector2 VerticalHandleOffset = new(0, DragHandleSize); private static readonly Vector2 HorizontalHandleOffset = new(DragHandleSize, 0); private static readonly Color SelectBoxBorderColor = new(1, 1, 1, 0.2f); private static readonly Color SelectBoxBorderFill = new(1, 1, 1, 0.05f); private Instance _compositionOp; private readonly ValueSnapHandler _snapHandler; }