Skip to content

Commit

Permalink
Add shared rectangle selection for all timeline tracks to select keyf…
Browse files Browse the repository at this point in the history
…rames

#519
  • Loading branch information
mafiesto4 committed Aug 24, 2021
1 parent 603c9fa commit 0063ec3
Show file tree
Hide file tree
Showing 11 changed files with 493 additions and 94 deletions.
22 changes: 21 additions & 1 deletion Source/Editor/GUI/CurveEditor.Base.cs
Expand Up @@ -10,7 +10,7 @@ namespace FlaxEditor.GUI
/// The base class for <see cref="CurveBase{T}"/> editors. Allows to use generic curve editor without type information at compile-time.
/// </summary>
[HideInEditor]
public abstract class CurveEditorBase : ContainerControl
public abstract class CurveEditorBase : ContainerControl, IKeyframesEditor
{
/// <summary>
/// The UI use mode flags.
Expand Down Expand Up @@ -124,6 +124,11 @@ public enum UseMode
/// </summary>
public abstract int KeyframesCount { get; }

/// <summary>
/// Clears the selection.
/// </summary>
public abstract void ClearSelection();

/// <summary>
/// Called when curve gets edited.
/// </summary>
Expand Down Expand Up @@ -256,5 +261,20 @@ protected static Vector2 ApplyUseModeMask(UseMode mode, Vector2 value, Vector2 d
(mode & UseMode.Vertical) == UseMode.Vertical ? value.Y : defaultValue.Y
);
}

/// <inheritdoc />
public IKeyframesEditorContext KeyframesEditorContext { get; set; }

/// <inheritdoc />
public abstract void OnKeyframesDeselect(IKeyframesEditor editor);

/// <inheritdoc />
public abstract void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection);

/// <inheritdoc />
public abstract int OnKeyframesSelectionCount();

/// <inheritdoc />
public abstract void OnKeyframesDelete(IKeyframesEditor editor);
}
}
105 changes: 75 additions & 30 deletions Source/Editor/GUI/CurveEditor.Contents.cs
Expand Up @@ -40,7 +40,14 @@ public ContentsBase(CurveEditor<T> editor)
private void UpdateSelectionRectangle()
{
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
if (_editor.KeyframesEditorContext != null)
_editor.KeyframesEditorContext.OnKeyframesSelection(_editor, this, selectionRect);
else
UpdateSelection(ref selectionRect);
}

internal void UpdateSelection(ref Rectangle selectionRect)
{
// Find controls to select
for (int i = 0; i < Children.Count; i++)
{
Expand All @@ -49,7 +56,6 @@ private void UpdateSelectionRectangle()
p.IsSelected = p.Bounds.Intersects(ref selectionRect);
}
}

_editor.UpdateTangents();
}

Expand All @@ -74,6 +80,21 @@ public override void OnMouseMove(Vector2 location)
{
_mousePos = location;

// Start moving selection if movement started from the keyframe
if (_leftMouseDown && !_isMovingSelection && GetChildAt(_leftMouseDownPos) is KeyframePoint)
{
// Start moving selected nodes
_isMovingSelection = true;
_movedKeyframes = false;
var viewRect = _editor._mainPanel.GetClientArea();
_movingSelectionStart = PointToKeyframes(location, ref viewRect);
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._points.Count)
_movingSelectionOffsets = new Vector2[_editor._points.Count];
for (int i = 0; i < _movingSelectionOffsets.Length; i++)
_movingSelectionOffsets[i] = _editor._points[i].Point - _movingSelectionStart;
_editor.OnEditingStart();
}

// Moving view
if (_rightMouseDown)
{
Expand Down Expand Up @@ -256,30 +277,32 @@ public override bool OnMouseDown(Vector2 location, MouseButton button)
// Check if user is pressing control
if (Root.GetKey(KeyboardKeys.Control))
{
// Add to selection
keyframe.IsSelected = true;
// Toggle selection
keyframe.IsSelected = !keyframe.IsSelected;
_editor.UpdateTangents();
}
// Check if node isn't selected
else if (!keyframe.IsSelected)
{
// Select node
_editor.ClearSelection();
if (_editor.KeyframesEditorContext != null)
_editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
else
_editor.ClearSelection();
keyframe.IsSelected = true;
_editor.UpdateTangents();
}

// Start moving selected nodes
if (_editor.ShowCollapsed)
{
// Synchronize selection for curve points when collapsed so all points fo the keyframe are selected
for (var i = 0; i < _editor._points.Count; i++)
{
var p = _editor._points[i];
if (p.Index == keyframe.Index)
p.IsSelected = keyframe.IsSelected;
}
}
StartMouseCapture();
_isMovingSelection = true;
_movedKeyframes = false;
var viewRect = _editor._mainPanel.GetClientArea();
_movingSelectionStart = PointToKeyframes(location, ref viewRect);
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._points.Count)
_movingSelectionOffsets = new Vector2[_editor._points.Count];
for (int i = 0; i < _movingSelectionOffsets.Length; i++)
_movingSelectionOffsets[i] = _editor._points[i].Point - _movingSelectionStart;
_editor.OnEditingStart();
Focus();
Tooltip?.Hide();
return true;
Expand All @@ -306,7 +329,10 @@ public override bool OnMouseDown(Vector2 location, MouseButton button)
{
// Start selecting
StartMouseCapture();
_editor.ClearSelection();
if (_editor.KeyframesEditorContext != null)
_editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
else
_editor.ClearSelection();
_editor.UpdateTangents();
Focus();
return true;
Expand Down Expand Up @@ -354,11 +380,6 @@ public override bool OnMouseUp(Vector2 location, MouseButton button)
_editor.OnEditingEnd();
}
}
// Selecting
else
{
UpdateSelectionRectangle();
}

_isMovingSelection = false;
_isMovingTangent = false;
Expand Down Expand Up @@ -388,18 +409,15 @@ public override bool OnMouseUp(Vector2 location, MouseButton button)

var cm = new ContextMenu.ContextMenu();
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.KeyframesCount < _editor.MaxKeyframes;
if (selectionCount == 0)
{
}
else if (selectionCount == 1)
if (selectionCount > 0)
{
cm.AddButton("Edit keyframe", () => _editor.EditKeyframes(this, location));
cm.AddButton("Remove keyframe", _editor.RemoveKeyframes);
cm.AddButton(selectionCount == 1 ? "Edit keyframe" : "Edit keyframes", () => _editor.EditKeyframes(this, location));
}
else
var totalSelectionCount = _editor.KeyframesEditorContext?.OnKeyframesSelectionCount() ?? selectionCount;
Debug.Log(totalSelectionCount);
if (totalSelectionCount > 0)
{
cm.AddButton("Edit keyframes", () => _editor.EditKeyframes(this, location));
cm.AddButton("Remove keyframes", _editor.RemoveKeyframes);
cm.AddButton(totalSelectionCount == 1 ? "Remove keyframe" : "Remove keyframes", _editor.RemoveKeyframes);
}
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
if (_editor.EnableZoom != UseMode.Off || _editor.EnablePanning != UseMode.Off)
Expand Down Expand Up @@ -464,5 +482,32 @@ private Vector2 PointToKeyframes(Vector2 point, ref Rectangle curveContentAreaBo
);
}
}

/// <inheritdoc />
public override void OnKeyframesDeselect(IKeyframesEditor editor)
{
ClearSelection();
}

/// <inheritdoc />
public override void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (_points.Count == 0)
return;
var selectionRect = Rectangle.FromPoints(_contents.PointFromParent(control, selection.UpperLeft), _contents.PointFromParent(control, selection.BottomRight));
_contents.UpdateSelection(ref selectionRect);
}

/// <inheritdoc />
public override int OnKeyframesSelectionCount()
{
return SelectionCount;
}

/// <inheritdoc />
public override void OnKeyframesDelete(IKeyframesEditor editor)
{
RemoveKeyframesInner();
}
}
}
53 changes: 46 additions & 7 deletions Source/Editor/GUI/CurveEditor.cs
Expand Up @@ -394,7 +394,24 @@ public override bool ShowCollapsed
UpdateKeyframes();
UpdateTangents();
if (value)
{
// Synchronize selection for curve points when collapsed so all points fo the keyframe are selected
for (var i = 0; i < _points.Count; i++)
{
var p = _points[i];
if (p.IsSelected)
{
for (var j = 0; j < _points.Count; j++)
{
var q = _points[j];
if (q.Index == p.Index)
q.IsSelected = true;
}
}
}

ShowWholeCurve();
}
}
}

Expand Down Expand Up @@ -590,6 +607,16 @@ private void EditKeyframes(Control control, Vector2 pos, List<int> keyframeIndic

private void RemoveKeyframes()
{
if (KeyframesEditorContext != null)
KeyframesEditorContext.OnKeyframesDelete(this);
else
RemoveKeyframesInner();
}

private void RemoveKeyframesInner()
{
if (SelectionCount == 0)
return;
var indicesToRemove = new HashSet<int>();
for (int i = 0; i < _points.Count; i++)
{
Expand All @@ -600,8 +627,6 @@ private void RemoveKeyframes()
indicesToRemove.Add(p.Index);
}
}
if (indicesToRemove.Count == 0)
return;

OnEditingStart();
RemoveKeyframesInternal(indicesToRemove);
Expand Down Expand Up @@ -630,22 +655,35 @@ private int SelectionCount
get
{
int result = 0;
for (int i = 0; i < _points.Count; i++)
if (_points[i].IsSelected)
result++;
if (ShowCollapsed)
{
for (int i = 0; i < _points.Count; i++)
if (_points[i].Component == 0 && _points[i].IsSelected)
result++;
}
else
{
for (int i = 0; i < _points.Count; i++)
if (_points[i].IsSelected)
result++;
}
return result;
}
}

private void ClearSelection()
/// <inheritdoc />
public override void ClearSelection()
{
for (int i = 0; i < _points.Count; i++)
{
_points[i].IsSelected = false;
}
}

private void SelectAll()
/// <summary>
/// Selects all keyframes.
/// </summary>
public void SelectAll()
{
for (int i = 0; i < _points.Count; i++)
{
Expand Down Expand Up @@ -888,6 +926,7 @@ public override void OnDestroy()
TickSteps = null;
_tickStrengths = null;
KeyframesChanged = null;
KeyframesEditorContext = null;

base.OnDestroy();
}
Expand Down
44 changes: 44 additions & 0 deletions Source/Editor/GUI/IKeyframesEditor.cs
@@ -0,0 +1,44 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.

using FlaxEngine;
using FlaxEngine.GUI;

namespace FlaxEditor.GUI
{
/// <summary>
/// Interface for keyframes/curves editors.
/// </summary>
public interface IKeyframesEditor
{
/// <summary>
/// Gets or sets the keyframes editor collection used by this editor.
/// </summary>
IKeyframesEditorContext KeyframesEditorContext { get; set; }

/// <summary>
/// Called when keyframes selection should be cleared for editor.
/// </summary>
/// <param name="editor">The source editor.</param>
void OnKeyframesDeselect(IKeyframesEditor editor);

/// <summary>
/// Called when keyframes selection rectangle gets updated.
/// </summary>
/// <param name="editor">The source editor.</param>
/// <param name="control">The source selection control.</param>
/// <param name="selection">The source selection rectangle (in source control local space).</param>
void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection);

/// <summary>
/// Called to calculate the total amount of selected keyframes in the editor.
/// </summary>
/// <returns>The selected keyframes amount.</returns>
int OnKeyframesSelectionCount();

/// <summary>
/// Called when keyframes selection should be deleted for all editors.
/// </summary>
/// <param name="editor">The source editor.</param>
void OnKeyframesDelete(IKeyframesEditor editor);
}
}

0 comments on commit 0063ec3

Please sign in to comment.