Files
FlaxEngine/Source/Editor/GUI/Timeline/GUI/Background.cs
2021-08-10 17:35:27 +02:00

233 lines
9.8 KiB
C#

// Copyright (c) 2012-2021 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;
/// <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];
}
/// <inheritdoc />
public override bool IntersectsContent(ref Vector2 locationParent, out Vector2 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;
var leftSideMin = PointFromParent(Vector2.Zero);
var leftSideMax = BottomLeft;
var rightSideMin = UpperRight;
var rightSideMax = PointFromParent(Parent.BottomRight) + mediaBackground.ControlsBounds.BottomRight;
// Draw lines between tracks
Render2D.DrawLine(new Vector2(areaLeft, 0.5f), new Vector2(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 Vector2(areaLeft, top), new Vector2(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);
}
}
// Setup time axis ticks
var minDistanceBetweenTicks = 50.0f;
var maxDistanceBetweenTicks = 100.0f;
var zoom = Timeline.UnitsPerSecond * _timeline.Zoom;
var left = Vector2.Min(leftSideMin, rightSideMax).X;
var right = Vector2.Max(leftSideMin, rightSideMax).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);
}
}
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);
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(Vector2 location, float delta)
{
if (base.OnMouseWheel(location, delta))
return true;
// Zoom in/out
if (IsMouseOver && Root.GetKey(KeyboardKeys.Control))
{
// TODO: preserve the view center point for easier zooming
_timeline.Zoom += delta * 0.1f;
return true;
}
return false;
}
/// <inheritdoc />
public override void OnDestroy()
{
// Cleanup
_tickSteps = null;
_tickStrengths = null;
base.OnDestroy();
}
}
}