Files
FlaxEngine/Source/Editor/GUI/Timeline/GUI/Background.cs
2022-06-13 00:40:32 +02:00

356 lines
14 KiB
C#

// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
using System.Globalization;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Timeline.GUI
{
/// <summary>
/// The timeline background control.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
class Background : ContainerControl
{
private readonly Timeline _timeline;
private float[] _tickSteps;
private float[] _tickStrengths;
private bool _isSelecting;
private Float2 _selectingStartPos = Float2.Minimum;
private Float2 _mousePos = Float2.Minimum;
/// <summary>
/// Initializes a new instance of the <see cref="Background"/> class.
/// </summary>
/// <param name="timeline">The timeline.</param>
public Background(Timeline timeline)
{
_timeline = timeline;
_tickSteps = Utilities.Utils.CurveTickSteps;
_tickStrengths = new float[_tickSteps.Length];
}
private void UpdateSelectionRectangle()
{
var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos);
_timeline.OnKeyframesSelection(null, this, selectionRect);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
_mousePos = location;
if (button == MouseButton.Left)
{
// Start selecting
_isSelecting = true;
_selectingStartPos = location;
_timeline.OnKeyframesDeselect(null);
Focus();
StartMouseCapture();
return true;
}
return false;
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
_mousePos = location;
if (_isSelecting && button == MouseButton.Left)
{
// End selecting
_isSelecting = false;
EndMouseCapture();
return true;
}
if (base.OnMouseUp(location, button))
return true;
return true;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
_mousePos = location;
// Selecting
if (_isSelecting)
{
UpdateSelectionRectangle();
return;
}
base.OnMouseMove(location);
}
/// <inheritdoc />
public override void OnLostFocus()
{
if (_isSelecting)
{
_isSelecting = false;
EndMouseCapture();
}
base.OnLostFocus();
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
_isSelecting = false;
EndMouseCapture();
base.OnEndMouseCapture();
}
/// <inheritdoc />
public override bool IntersectsContent(ref Float2 locationParent, out Float2 location)
{
// Pass all events
location = PointFromParent(ref locationParent);
return true;
}
/// <inheritdoc />
public override void Draw()
{
var style = Style.Current;
var mediaBackground = _timeline.MediaBackground;
var tracks = _timeline.Tracks;
var linesColor = style.BackgroundNormal;
var areaLeft = -X;
var areaRight = Parent.Width + mediaBackground.ControlsBounds.BottomRight.X;
var height = Height;
// Calculate the timeline range in the view to optimize background drawing
Render2D.PeekClip(out var globalClipping);
Render2D.PeekTransform(out var globalTransform);
var globalRect = new Rectangle(globalTransform.M31 + areaLeft, globalTransform.M32, areaRight * globalTransform.M11, height * globalTransform.M22);
var globalMask = Rectangle.Shared(globalClipping, globalRect);
var globalTransformInv = Matrix3x3.Invert(globalTransform);
var localRect = Rectangle.FromPoints(Matrix3x3.Transform2D(globalMask.UpperLeft, globalTransformInv), Matrix3x3.Transform2D(globalMask.BottomRight, globalTransformInv));
var localRectMin = localRect.UpperLeft;
var localRectMax = localRect.BottomRight;
// Draw lines between tracks
Render2D.DrawLine(new Float2(areaLeft, 0.5f), new Float2(areaRight, 0.5f), linesColor);
for (int i = 0; i < tracks.Count; i++)
{
var track = tracks[i];
if (track.Visible)
{
var top = track.Bottom + 0.5f;
Render2D.DrawLine(new Float2(areaLeft, top), new Float2(areaRight, top), linesColor);
}
}
// Highlight selected tracks
for (int i = 0; i < tracks.Count; i++)
{
var track = tracks[i];
if (track.Visible && _timeline.SelectedTracks.Contains(track) && _timeline.ContainsFocus)
{
Render2D.FillRectangle(new Rectangle(areaLeft, track.Top, areaRight, track.Height), style.BackgroundSelected.RGBMultiplied(0.4f));
}
}
// Setup time axis ticks
var minDistanceBetweenTicks = 50.0f;
var maxDistanceBetweenTicks = 100.0f;
var zoom = Timeline.UnitsPerSecond * _timeline.Zoom;
var left = Float2.Min(localRectMin, localRectMax).X;
var right = Float2.Max(localRectMin, localRectMax).X;
var leftFrame = Mathf.Floor((left - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond;
var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond;
var min = leftFrame;
var max = rightFrame;
int smallestTick = 0;
int biggestTick = _tickSteps.Length - 1;
for (int i = _tickSteps.Length - 1; i >= 0; i--)
{
// Calculate how far apart these modulo tick steps are spaced
float tickSpacing = _tickSteps[i] * _timeline.Zoom;
// Calculate the strength of the tick markers based on the spacing
_tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks));
// Beyond threshold the ticks don't get any bigger or fatter
if (_tickStrengths[i] >= 1)
biggestTick = i;
// Do not show small tick markers
if (tickSpacing <= minDistanceBetweenTicks)
{
smallestTick = i;
break;
}
}
int tickLevels = biggestTick - smallestTick + 1;
// Draw vertical lines for time axis
for (int level = 0; level < tickLevels; level++)
{
float strength = _tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon)
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1);
var lStep = _tickSteps[l];
var lNextStep = _tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep);
Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = i * lStep;
var time = tick / _timeline.FramesPerSecond;
var x = time * zoom + Timeline.StartOffset;
// Draw line
Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor);
}
}
// Draw selection rectangle
if (_isSelecting)
{
var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos);
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
Render2D.DrawRectangle(selectionRect, Color.Orange);
}
DrawChildren();
// Disabled overlay
for (int i = 0; i < tracks.Count; i++)
{
var track = tracks[i];
if (track.DrawDisabled && track.IsExpandedAll)
{
Render2D.FillRectangle(new Rectangle(areaLeft, track.Top, areaRight, track.Height), new Color(0, 0, 0, 100));
}
}
// Darken area outside the duration
{
var outsideDurationAreaColor = new Color(0, 0, 0, 100);
var leftSideMin = PointFromParent(Float2.Zero);
var leftSideMax = BottomLeft;
var rightSideMin = UpperRight;
var rightSideMax = PointFromParent(Parent.BottomRight) + mediaBackground.ControlsBounds.BottomRight;
Render2D.FillRectangle(new Rectangle(leftSideMin, leftSideMax.X - leftSideMin.X, height), outsideDurationAreaColor);
Render2D.FillRectangle(new Rectangle(rightSideMin, rightSideMax.X - rightSideMin.X, height), outsideDurationAreaColor);
}
// Draw time axis header
var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y;
var verticalLinesHeaderExtend = Timeline.HeaderTopAreaHeight * 0.5f;
var timeShowMode = _timeline.TimeShowMode;
Render2D.FillRectangle(new Rectangle(areaLeft, timeAxisHeaderOffset - Timeline.HeaderTopAreaHeight, areaRight - areaLeft, Timeline.HeaderTopAreaHeight), style.Background.RGBMultiplied(0.7f));
for (int level = 0; level < tickLevels; level++)
{
float strength = _tickStrengths[smallestTick + level];
if (strength <= Mathf.Epsilon)
continue;
// Draw all ticks
int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1);
var lStep = _tickSteps[l];
var lNextStep = _tickSteps[l + 1];
int startTick = Mathf.FloorToInt(min / lStep);
int endTick = Mathf.CeilToInt(max / lStep);
Color lineColor = style.Foreground.RGBMultiplied(0.8f).AlphaMultiplied(strength);
Color labelColor = style.ForegroundDisabled.AlphaMultiplied(strength);
for (int i = startTick; i <= endTick; i++)
{
if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0))
continue;
var tick = i * lStep;
var time = tick / _timeline.FramesPerSecond;
var x = time * zoom + Timeline.StartOffset;
// Header line
var lineRect = new Rectangle(x - 0.5f, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 1.0f, verticalLinesHeaderExtend);
Render2D.FillRectangle(lineRect, lineColor);
// Time label
string labelText;
switch (timeShowMode)
{
case Timeline.TimeShowModes.Frames:
labelText = tick.ToString("###0", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Seconds:
labelText = time.ToString("###0.##'s'", CultureInfo.InvariantCulture);
break;
case Timeline.TimeShowModes.Time:
labelText = TimeSpan.FromSeconds(time).ToString("g");
break;
default: throw new ArgumentOutOfRangeException();
}
var labelRect = new Rectangle(x + 2, -verticalLinesHeaderExtend + timeAxisHeaderOffset, 50, verticalLinesHeaderExtend);
Render2D.DrawText(style.FontSmall, labelText, labelRect, labelColor, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.8f);
}
}
}
/// <inheritdoc />
public override bool OnMouseWheel(Float2 location, float delta)
{
if (base.OnMouseWheel(location, delta))
return true;
// Zoom in/out
if (IsMouseOver && Root.GetKey(KeyboardKeys.Control))
{
var locationTimeOld = _timeline.MediaBackground.PointFromParent(_timeline, _timeline.Size * 0.5f).X;
var frame = (locationTimeOld - Timeline.StartOffset * 2.0f) / _timeline.Zoom / Timeline.UnitsPerSecond * _timeline.FramesPerSecond;
_timeline.Zoom += delta * 0.1f;
var locationTimeNew = frame / _timeline.FramesPerSecond * Timeline.UnitsPerSecond * _timeline.Zoom + Timeline.StartOffset * 2.0f;
var locationTimeDelta = locationTimeNew - locationTimeOld;
var scroll = _timeline.MediaBackground.HScrollBar;
if (scroll.Visible && scroll.Enabled)
scroll.TargetValue += locationTimeDelta;
return true;
}
// Scroll view horizontally
if (IsMouseOver && Root.GetKey(KeyboardKeys.Shift))
{
var scroll = _timeline.MediaBackground.HScrollBar;
if (scroll.Visible && scroll.Enabled)
{
scroll.TargetValue -= delta * Timeline.UnitsPerSecond / _timeline.Zoom;
return true;
}
}
return false;
}
/// <inheritdoc />
public override void OnDestroy()
{
// Cleanup
_tickSteps = null;
_tickStrengths = null;
base.OnDestroy();
}
}
}