Add shared rectangle selection for all timeline tracks to select keyframes

#519
This commit is contained in:
Wojtek Figat
2021-08-24 17:14:41 +02:00
parent 603c9fac07
commit 0063ec3527
11 changed files with 493 additions and 94 deletions

View File

@@ -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. /// The base class for <see cref="CurveBase{T}"/> editors. Allows to use generic curve editor without type information at compile-time.
/// </summary> /// </summary>
[HideInEditor] [HideInEditor]
public abstract class CurveEditorBase : ContainerControl public abstract class CurveEditorBase : ContainerControl, IKeyframesEditor
{ {
/// <summary> /// <summary>
/// The UI use mode flags. /// The UI use mode flags.
@@ -124,6 +124,11 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
public abstract int KeyframesCount { get; } public abstract int KeyframesCount { get; }
/// <summary>
/// Clears the selection.
/// </summary>
public abstract void ClearSelection();
/// <summary> /// <summary>
/// Called when curve gets edited. /// Called when curve gets edited.
/// </summary> /// </summary>
@@ -256,5 +261,20 @@ namespace FlaxEditor.GUI
(mode & UseMode.Vertical) == UseMode.Vertical ? value.Y : defaultValue.Y (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);
} }
} }

View File

@@ -40,7 +40,14 @@ namespace FlaxEditor.GUI
private void UpdateSelectionRectangle() private void UpdateSelectionRectangle()
{ {
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos); 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 // Find controls to select
for (int i = 0; i < Children.Count; i++) for (int i = 0; i < Children.Count; i++)
{ {
@@ -49,7 +56,6 @@ namespace FlaxEditor.GUI
p.IsSelected = p.Bounds.Intersects(ref selectionRect); p.IsSelected = p.Bounds.Intersects(ref selectionRect);
} }
} }
_editor.UpdateTangents(); _editor.UpdateTangents();
} }
@@ -74,6 +80,21 @@ namespace FlaxEditor.GUI
{ {
_mousePos = 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 // Moving view
if (_rightMouseDown) if (_rightMouseDown)
{ {
@@ -256,30 +277,32 @@ namespace FlaxEditor.GUI
// Check if user is pressing control // Check if user is pressing control
if (Root.GetKey(KeyboardKeys.Control)) if (Root.GetKey(KeyboardKeys.Control))
{ {
// Add to selection // Toggle selection
keyframe.IsSelected = true; keyframe.IsSelected = !keyframe.IsSelected;
_editor.UpdateTangents(); _editor.UpdateTangents();
} }
// Check if node isn't selected // Check if node isn't selected
else if (!keyframe.IsSelected) else if (!keyframe.IsSelected)
{ {
// Select node // Select node
_editor.ClearSelection(); if (_editor.KeyframesEditorContext != null)
_editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
else
_editor.ClearSelection();
keyframe.IsSelected = true; keyframe.IsSelected = true;
_editor.UpdateTangents(); _editor.UpdateTangents();
} }
if (_editor.ShowCollapsed)
// Start moving selected nodes {
// 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(); 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(); Focus();
Tooltip?.Hide(); Tooltip?.Hide();
return true; return true;
@@ -306,7 +329,10 @@ namespace FlaxEditor.GUI
{ {
// Start selecting // Start selecting
StartMouseCapture(); StartMouseCapture();
_editor.ClearSelection(); if (_editor.KeyframesEditorContext != null)
_editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
else
_editor.ClearSelection();
_editor.UpdateTangents(); _editor.UpdateTangents();
Focus(); Focus();
return true; return true;
@@ -354,11 +380,6 @@ namespace FlaxEditor.GUI
_editor.OnEditingEnd(); _editor.OnEditingEnd();
} }
} }
// Selecting
else
{
UpdateSelectionRectangle();
}
_isMovingSelection = false; _isMovingSelection = false;
_isMovingTangent = false; _isMovingTangent = false;
@@ -388,18 +409,15 @@ namespace FlaxEditor.GUI
var cm = new ContextMenu.ContextMenu(); var cm = new ContextMenu.ContextMenu();
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.KeyframesCount < _editor.MaxKeyframes; cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.KeyframesCount < _editor.MaxKeyframes;
if (selectionCount == 0) if (selectionCount > 0)
{ {
cm.AddButton(selectionCount == 1 ? "Edit keyframe" : "Edit keyframes", () => _editor.EditKeyframes(this, location));
} }
else if (selectionCount == 1) var totalSelectionCount = _editor.KeyframesEditorContext?.OnKeyframesSelectionCount() ?? selectionCount;
Debug.Log(totalSelectionCount);
if (totalSelectionCount > 0)
{ {
cm.AddButton("Edit keyframe", () => _editor.EditKeyframes(this, location)); cm.AddButton(totalSelectionCount == 1 ? "Remove keyframe" : "Remove keyframes", _editor.RemoveKeyframes);
cm.AddButton("Remove keyframe", _editor.RemoveKeyframes);
}
else
{
cm.AddButton("Edit keyframes", () => _editor.EditKeyframes(this, location));
cm.AddButton("Remove keyframes", _editor.RemoveKeyframes);
} }
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
if (_editor.EnableZoom != UseMode.Off || _editor.EnablePanning != UseMode.Off) if (_editor.EnableZoom != UseMode.Off || _editor.EnablePanning != UseMode.Off)
@@ -464,5 +482,32 @@ namespace FlaxEditor.GUI
); );
} }
} }
/// <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();
}
} }
} }

View File

@@ -394,7 +394,24 @@ namespace FlaxEditor.GUI
UpdateKeyframes(); UpdateKeyframes();
UpdateTangents(); UpdateTangents();
if (value) 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(); ShowWholeCurve();
}
} }
} }
@@ -590,6 +607,16 @@ namespace FlaxEditor.GUI
private void RemoveKeyframes() private void RemoveKeyframes()
{ {
if (KeyframesEditorContext != null)
KeyframesEditorContext.OnKeyframesDelete(this);
else
RemoveKeyframesInner();
}
private void RemoveKeyframesInner()
{
if (SelectionCount == 0)
return;
var indicesToRemove = new HashSet<int>(); var indicesToRemove = new HashSet<int>();
for (int i = 0; i < _points.Count; i++) for (int i = 0; i < _points.Count; i++)
{ {
@@ -600,8 +627,6 @@ namespace FlaxEditor.GUI
indicesToRemove.Add(p.Index); indicesToRemove.Add(p.Index);
} }
} }
if (indicesToRemove.Count == 0)
return;
OnEditingStart(); OnEditingStart();
RemoveKeyframesInternal(indicesToRemove); RemoveKeyframesInternal(indicesToRemove);
@@ -630,14 +655,24 @@ namespace FlaxEditor.GUI
get get
{ {
int result = 0; int result = 0;
for (int i = 0; i < _points.Count; i++) if (ShowCollapsed)
if (_points[i].IsSelected) {
result++; 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; return result;
} }
} }
private void ClearSelection() /// <inheritdoc />
public override void ClearSelection()
{ {
for (int i = 0; i < _points.Count; i++) for (int i = 0; i < _points.Count; i++)
{ {
@@ -645,7 +680,10 @@ namespace FlaxEditor.GUI
} }
} }
private void SelectAll() /// <summary>
/// Selects all keyframes.
/// </summary>
public void SelectAll()
{ {
for (int i = 0; i < _points.Count; i++) for (int i = 0; i < _points.Count; i++)
{ {
@@ -888,6 +926,7 @@ namespace FlaxEditor.GUI
TickSteps = null; TickSteps = null;
_tickStrengths = null; _tickStrengths = null;
KeyframesChanged = null; KeyframesChanged = null;
KeyframesEditorContext = null;
base.OnDestroy(); base.OnDestroy();
} }

View File

@@ -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);
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI
{
/// <summary>
/// Interface for context for collection of <see cref="IKeyframesEditor"/>.
/// </summary>
public interface IKeyframesEditorContext
{
/// <summary>
/// Called when keyframes selection should be cleared for all editors.
/// </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);
}
}

View File

@@ -15,7 +15,7 @@ namespace FlaxEditor.GUI
/// </summary> /// </summary>
/// <seealso cref="FlaxEngine.GUI.ContainerControl" /> /// <seealso cref="FlaxEngine.GUI.ContainerControl" />
[HideInEditor] [HideInEditor]
public class KeyframesEditor : ContainerControl public class KeyframesEditor : ContainerControl, IKeyframesEditor
{ {
/// <summary> /// <summary>
/// A single keyframe. /// A single keyframe.
@@ -91,7 +91,14 @@ namespace FlaxEditor.GUI
private void UpdateSelectionRectangle() private void UpdateSelectionRectangle()
{ {
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos); 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 // Find controls to select
for (int i = 0; i < Children.Count; i++) for (int i = 0; i < Children.Count; i++)
{ {
@@ -123,6 +130,21 @@ namespace FlaxEditor.GUI
{ {
_mousePos = 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).X;
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._keyframes.Count)
_movingSelectionOffsets = new float[_editor._keyframes.Count];
for (int i = 0; i < _movingSelectionOffsets.Length; i++)
_movingSelectionOffsets[i] = _editor._keyframes[i].Time - _movingSelectionStart;
_editor.OnEditingStart();
}
// Moving view // Moving view
if (_rightMouseDown) if (_rightMouseDown)
{ {
@@ -248,28 +270,20 @@ namespace FlaxEditor.GUI
// Check if user is pressing control // Check if user is pressing control
if (Root.GetKey(KeyboardKeys.Control)) if (Root.GetKey(KeyboardKeys.Control))
{ {
// Add to selection // Toggle selection
keyframe.Select(); keyframe.IsSelected = !keyframe.IsSelected;
} }
// Check if node isn't selected // Check if node isn't selected
else if (!keyframe.IsSelected) else if (!keyframe.IsSelected)
{ {
// Select node // Select node
_editor.ClearSelection(); if (_editor.KeyframesEditorContext != null)
keyframe.Select(); _editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
else
_editor.ClearSelection();
keyframe.IsSelected = true;
} }
// Start moving selected nodes
StartMouseCapture(); StartMouseCapture();
_isMovingSelection = true;
_movedKeyframes = false;
var viewRect = _editor._mainPanel.GetClientArea();
_movingSelectionStart = PointToKeyframes(location, ref viewRect).X;
if (_movingSelectionOffsets == null || _movingSelectionOffsets.Length != _editor._keyframes.Count)
_movingSelectionOffsets = new float[_editor._keyframes.Count];
for (int i = 0; i < _movingSelectionOffsets.Length; i++)
_movingSelectionOffsets[i] = _editor._keyframes[i].Time - _movingSelectionStart;
_editor.OnEditingStart();
Focus(); Focus();
Tooltip?.Hide(); Tooltip?.Hide();
return true; return true;
@@ -281,7 +295,10 @@ namespace FlaxEditor.GUI
{ {
// Start selecting // Start selecting
StartMouseCapture(); StartMouseCapture();
_editor.ClearSelection(); if (_editor.KeyframesEditorContext != null)
_editor.KeyframesEditorContext.OnKeyframesDeselect(_editor);
else
_editor.ClearSelection();
Focus(); Focus();
return true; return true;
} }
@@ -319,11 +336,6 @@ namespace FlaxEditor.GUI
_editor.UpdateKeyframes(); _editor.UpdateKeyframes();
} }
} }
// Selecting
else
{
UpdateSelectionRectangle();
}
_isMovingSelection = false; _isMovingSelection = false;
_movedKeyframes = false; _movedKeyframes = false;
@@ -343,7 +355,7 @@ namespace FlaxEditor.GUI
{ {
// Select node // Select node
selectionCount = 1; selectionCount = 1;
point.Select(); point.IsSelected = true;
} }
var viewRect = _editor._mainPanel.GetClientArea(); var viewRect = _editor._mainPanel.GetClientArea();
@@ -351,18 +363,15 @@ namespace FlaxEditor.GUI
var cm = new ContextMenu.ContextMenu(); var cm = new ContextMenu.ContextMenu();
cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.Keyframes.Count < _editor.MaxKeyframes; cm.AddButton("Add keyframe", () => _editor.AddKeyframe(_cmShowPos)).Enabled = _editor.Keyframes.Count < _editor.MaxKeyframes;
if (selectionCount == 0) if (selectionCount > 0)
{ {
cm.AddButton(selectionCount == 1 ? "Edit keyframe" : "Edit keyframes", () => _editor.EditKeyframes(this, location));
} }
else if (selectionCount == 1) var totalSelectionCount = _editor.KeyframesEditorContext?.OnKeyframesSelectionCount() ?? selectionCount;
Debug.Log(totalSelectionCount);
if (totalSelectionCount > 0)
{ {
cm.AddButton("Edit keyframe", () => _editor.EditKeyframes(this, location)); cm.AddButton(totalSelectionCount == 1 ? "Remove keyframe" : "Remove keyframes", _editor.RemoveKeyframes);
cm.AddButton("Remove keyframe", _editor.RemoveKeyframes);
}
else
{
cm.AddButton("Edit keyframes", () => _editor.EditKeyframes(this, location));
cm.AddButton("Remove keyframes", _editor.RemoveKeyframes);
} }
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
if (_editor.EnableZoom && _editor.EnablePanning) if (_editor.EnableZoom && _editor.EnablePanning)
@@ -482,16 +491,6 @@ namespace FlaxEditor.GUI
return false; return false;
} }
public void Select()
{
IsSelected = true;
}
public void Deselect()
{
IsSelected = false;
}
/// <summary> /// <summary>
/// Updates the tooltip. /// Updates the tooltip.
/// </summary> /// </summary>
@@ -980,7 +979,16 @@ namespace FlaxEditor.GUI
private void RemoveKeyframes() private void RemoveKeyframes()
{ {
bool edited = false; if (KeyframesEditorContext != null)
KeyframesEditorContext.OnKeyframesDelete(this);
else
RemoveKeyframesInner();
}
private void RemoveKeyframesInner()
{
if (SelectionCount == 0)
return;
var keyframes = new Dictionary<int, Keyframe>(_keyframes.Count); var keyframes = new Dictionary<int, Keyframe>(_keyframes.Count);
for (int i = 0; i < _points.Count; i++) for (int i = 0; i < _points.Count; i++)
{ {
@@ -991,12 +999,9 @@ namespace FlaxEditor.GUI
} }
else else
{ {
p.Deselect(); p.IsSelected = false;
edited = true;
} }
} }
if (!edited)
return;
OnEditingStart(); OnEditingStart();
_keyframes.Clear(); _keyframes.Clear();
@@ -1088,19 +1093,25 @@ namespace FlaxEditor.GUI
} }
} }
private void ClearSelection() /// <summary>
/// Clears the selection.
/// </summary>
public void ClearSelection()
{ {
for (int i = 0; i < _points.Count; i++) for (int i = 0; i < _points.Count; i++)
{ {
_points[i].Deselect(); _points[i].IsSelected = false;
} }
} }
private void SelectAll() /// <summary>
/// Selects all keyframes.
/// </summary>
public void SelectAll()
{ {
for (int i = 0; i < _points.Count; i++) for (int i = 0; i < _points.Count; i++)
{ {
_points[i].Select(); _points[i].IsSelected = true;
} }
} }
@@ -1182,8 +1193,39 @@ namespace FlaxEditor.GUI
// Cleanup // Cleanup
_points.Clear(); _points.Clear();
_keyframes.Clear(); _keyframes.Clear();
KeyframesEditorContext = null;
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
public IKeyframesEditorContext KeyframesEditorContext { get; set; }
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
{
ClearSelection();
}
/// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (_keyframes.Count == 0)
return;
var selectionRect = Rectangle.FromPoints(_contents.PointFromParent(control, selection.UpperLeft), _contents.PointFromParent(control, selection.BottomRight));
_contents.UpdateSelection(ref selectionRect);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
{
return SelectionCount;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
{
RemoveKeyframesInner();
}
} }
} }

View File

@@ -21,7 +21,7 @@ namespace FlaxEditor.GUI.Timeline
/// </summary> /// </summary>
/// <seealso cref="FlaxEngine.GUI.ContainerControl" /> /// <seealso cref="FlaxEngine.GUI.ContainerControl" />
[HideInEditor] [HideInEditor]
public class Timeline : ContainerControl public class Timeline : ContainerControl, IKeyframesEditorContext
{ {
private static readonly KeyValuePair<float, string>[] FPSValues = private static readonly KeyValuePair<float, string>[] FPSValues =
{ {
@@ -2393,5 +2393,49 @@ namespace FlaxEditor.GUI.Timeline
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
{
for (int i = 0; i < _tracks.Count; i++)
{
if (_tracks[i] is IKeyframesEditorContext trackContext)
trackContext.OnKeyframesDeselect(editor);
}
}
/// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
var globalControl = _backgroundArea;
var globalRect = Rectangle.FromPoints(control.PointToParent(globalControl, selection.UpperLeft), control.PointToParent(globalControl, selection.BottomRight));
for (int i = 0; i < _tracks.Count; i++)
{
if (_tracks[i] is IKeyframesEditorContext trackContext)
trackContext.OnKeyframesSelection(editor, globalControl, globalRect);
}
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
{
int result = 0;
for (int i = 0; i < _tracks.Count; i++)
{
if (_tracks[i] is IKeyframesEditorContext trackContext)
result += trackContext.OnKeyframesSelectionCount();
}
return result;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
{
for (int i = 0; i < _tracks.Count; i++)
{
if (_tracks[i] is IKeyframesEditorContext trackContext)
trackContext.OnKeyframesDelete(editor);
}
}
} }
} }

View File

@@ -225,7 +225,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The child volume track for audio track. Used to animate audio volume over time. /// The child volume track for audio track. Used to animate audio volume over time.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.GUI.Timeline.Track" /> /// <seealso cref="FlaxEditor.GUI.Timeline.Track" />
class AudioVolumeTrack : Track class AudioVolumeTrack : Track, IKeyframesEditorContext
{ {
/// <summary> /// <summary>
/// Gets the archetype. /// Gets the archetype.
@@ -478,7 +478,11 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return; return;
Curve.Visible = Visible; Curve.Visible = Visible;
if (!Visible) if (!Visible)
{
Curve.ClearSelection();
return; return;
}
Curve.KeyframesEditorContext = Timeline;
Curve.CustomViewPanning = Timeline.OnKeyframesViewPanning; Curve.CustomViewPanning = Timeline.OnKeyframesViewPanning;
Curve.Bounds = new Rectangle(_audioMedia.X, Y + 1.0f, _audioMedia.Width, Height - 2.0f); Curve.Bounds = new Rectangle(_audioMedia.X, Y + 1.0f, _audioMedia.Width, Height - 2.0f);
@@ -639,5 +643,32 @@ namespace FlaxEditor.GUI.Timeline.Tracks
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesDeselect(editor);
}
/// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
{
return Curve != null && Curve.Visible ? Curve.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesDelete(editor);
}
} }
} }

View File

@@ -15,7 +15,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline track for animating object property via Curve. /// The timeline track for animating object property via Curve.
/// </summary> /// </summary>
/// <seealso cref="MemberTrack" /> /// <seealso cref="MemberTrack" />
public abstract class CurvePropertyTrackBase : MemberTrack public abstract class CurvePropertyTrackBase : MemberTrack, IKeyframesEditorContext
{ {
private sealed class Splitter : Control private sealed class Splitter : Control
{ {
@@ -226,8 +226,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return; return;
Curve.Visible = Visible; Curve.Visible = Visible;
if (!Visible) if (!Visible)
{
Curve.ClearSelection();
return; return;
}
var expanded = IsExpanded; var expanded = IsExpanded;
Curve.KeyframesEditorContext = Timeline;
Curve.CustomViewPanning = Timeline.OnKeyframesViewPanning; Curve.CustomViewPanning = Timeline.OnKeyframesViewPanning;
Curve.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f); Curve.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f);
Curve.ViewScale = new Vector2(Timeline.Zoom, Curve.ViewScale.Y); Curve.ViewScale = new Vector2(Timeline.Zoom, Curve.ViewScale.Y);
@@ -240,11 +244,13 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (expanded) if (expanded)
{ {
if (_splitter == null) if (_splitter == null)
{
_splitter = new Splitter _splitter = new Splitter
{ {
_track = this, _track = this,
Parent = Curve, Parent = Curve,
}; };
}
var splitterHeight = 4.0f; var splitterHeight = 4.0f;
_splitter.Bounds = new Rectangle(0, Curve.Height - splitterHeight, Curve.Width, splitterHeight); _splitter.Bounds = new Rectangle(0, Curve.Height - splitterHeight, Curve.Width, splitterHeight);
} }
@@ -424,6 +430,33 @@ namespace FlaxEditor.GUI.Timeline.Tracks
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesDeselect(editor);
}
/// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
{
return Curve != null && Curve.Visible ? Curve.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Curve != null && Curve.Visible)
Curve.OnKeyframesDelete(editor);
}
} }
/// <summary> /// <summary>

View File

@@ -16,7 +16,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline track for invoking events on a certain points in the time. /// The timeline track for invoking events on a certain points in the time.
/// </summary> /// </summary>
/// <seealso cref="MemberTrack" /> /// <seealso cref="MemberTrack" />
public class EventTrack : MemberTrack public class EventTrack : MemberTrack, IKeyframesEditorContext
{ {
/// <summary> /// <summary>
/// Gets the archetype. /// Gets the archetype.
@@ -285,7 +285,11 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return; return;
Events.Visible = Visible; Events.Visible = Visible;
if (!Visible) if (!Visible)
{
Events.ClearSelection();
return; return;
}
Events.KeyframesEditorContext = Timeline;
Events.CustomViewPanning = Timeline.OnKeyframesViewPanning; Events.CustomViewPanning = Timeline.OnKeyframesViewPanning;
Events.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f); Events.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f);
Events.ViewScale = new Vector2(Timeline.Zoom, 1.0f); Events.ViewScale = new Vector2(Timeline.Zoom, 1.0f);
@@ -407,5 +411,32 @@ namespace FlaxEditor.GUI.Timeline.Tracks
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Events != null && Events.Visible)
Events.OnKeyframesDeselect(editor);
}
/// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (Events != null && Events.Visible)
Events.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
{
return Events != null && Events.Visible ? Events.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Events != null && Events.Visible)
Events.OnKeyframesDelete(editor);
}
} }
} }

View File

@@ -15,7 +15,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// The timeline track for animating object property via keyframes collection. /// The timeline track for animating object property via keyframes collection.
/// </summary> /// </summary>
/// <seealso cref="MemberTrack" /> /// <seealso cref="MemberTrack" />
public class KeyframesPropertyTrack : MemberTrack public class KeyframesPropertyTrack : MemberTrack, IKeyframesEditorContext
{ {
/// <summary> /// <summary>
/// Gets the archetype. /// Gets the archetype.
@@ -252,7 +252,11 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return; return;
Keyframes.Visible = Visible; Keyframes.Visible = Visible;
if (!Visible) if (!Visible)
{
Keyframes.ClearSelection();
return; return;
}
Keyframes.KeyframesEditorContext = Timeline;
Keyframes.CustomViewPanning = Timeline.OnKeyframesViewPanning; Keyframes.CustomViewPanning = Timeline.OnKeyframesViewPanning;
Keyframes.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f); Keyframes.Bounds = new Rectangle(Timeline.StartOffset, Y + 1.0f, Timeline.Duration * Timeline.UnitsPerSecond * Timeline.Zoom, Height - 2.0f);
Keyframes.ViewScale = new Vector2(Timeline.Zoom, 1.0f); Keyframes.ViewScale = new Vector2(Timeline.Zoom, 1.0f);
@@ -387,5 +391,32 @@ namespace FlaxEditor.GUI.Timeline.Tracks
base.OnDestroy(); base.OnDestroy();
} }
/// <inheritdoc />
public void OnKeyframesDeselect(IKeyframesEditor editor)
{
if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesDeselect(editor);
}
/// <inheritdoc />
public void OnKeyframesSelection(IKeyframesEditor editor, ContainerControl control, Rectangle selection)
{
if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesSelection(editor, control, selection);
}
/// <inheritdoc />
public int OnKeyframesSelectionCount()
{
return Keyframes != null && Keyframes.Visible ? Keyframes.OnKeyframesSelectionCount() : 0;
}
/// <inheritdoc />
public void OnKeyframesDelete(IKeyframesEditor editor)
{
if (Keyframes != null && Keyframes.Visible)
Keyframes.OnKeyframesDelete(editor);
}
} }
} }