JD2022-TU1/main/tools/framework/JD.Timeline/View/CurveEditor/ClipCurve.cs

159 lines
5.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using JD.Collections;
using JD.Collections.Interfaces;
using JD.Kernel.Classes;
using JD.Kernel.Structs;
using JD.Timeline.Model.Curves;
using TeaBox;
using TeaBox.Editors.Curves;
using JD.Serialization;
using BezierCurve = TeaBox.Editors.Curves.BezierCurve;
using FrameConverter = JD.Core.FrameConverter;
namespace JD.Timeline.View.CurveEditor
{
/// <summary>
/// Visual representation of the model curve.
/// </summary>
internal class ClipCurve : BezierCurve
{
private readonly BezierCurveFloat _model;
private readonly Scope _isUpdatingView = new Scope();
private readonly Scope _isUpdatingModel;
private float _minBoundsWidth;
public ClipCurve(BezierCurveFloat model, Scope isUpdatingModel)
{
_isUpdatingModel = isUpdatingModel;
_model = model;
ColorChannel = model.Type.Infos().ColorChannel;
}
public BezierCurveFloat Model
{
get { return _model; }
}
protected override void Points_CollectionChanged(object sender, CollectionChangedEventArgs<CurvePoint> e)
{
base.Points_CollectionChanged(sender, e);
// When updating the view, a lot of events are trigger
// but those values should not affect the model.
if (!_isUpdatingView.InScope && !Points.IsUpdating)
{
UpdateModel();
// Restore the end bound so that when a point outside the curve bound is moved, user cannot move it back outside the bounds.
_minBoundsWidth = Math.Min(_minBoundsWidth, CurveTimeFromFrame(_model.Keys.Select(point => point.Frame).DefaultIfEmpty().Max()));
}
}
/// <summary>
/// Fills the model with the new view values.
/// </summary>
private void UpdateModel()
{
using (_isUpdatingModel.Begin())
using (_model.BeginScopedUpdate())
{
_model.Keys.Clear();
foreach (var point in BezierPoints)
{
var pointModel = _model.AddBezierPoint(ToModelTime(point.Position), ToModelTime(point.In.Position), ToModelTime(point.Out.Position));
point.Tag = pointModel;
}
}
}
/// <summary>
/// Fills the view with the current model values.
/// </summary>
public void UpdateView()
{
using (_isUpdatingView.Begin())
using (Points.BeginScopedUpdate())
{
// Keep the existing points so the selection is persisted.
Dictionary<KeyFloat, BezierCurvePoint> existingPoints = Points.OfType<BezierCurvePoint>().ToDictionary(point => (KeyFloat)point.Tag);
Points.Clear();
UpdateBounds();
foreach (var point in _model.Keys.OrderBy(model => model.Frame))
{
BezierCurvePoint bezierPoint;
if (!existingPoints.TryGetValue(point, out bezierPoint))
{
bezierPoint = new BezierCurvePoint();
}
bezierPoint.Tag = point;
bezierPoint.Position = ToViewTime(point.Position);
bezierPoint.In.Position = ToViewTime(point.In);
bezierPoint.Out.Position = ToViewTime(point.Out);
Points.Add(bezierPoint);
}
}
}
/// <summary>
/// When a clip has keys outside the bounds of the clip, we allow the user to manipulate those key outside the bounds
/// but when resized with valid bounds, we restore the clip limit.
/// </summary>
protected override void Clamp(CurvePoint point, RectangleF region)
{
region.Width = Math.Max(region.Width, _minBoundsWidth);
base.Clamp(point, region);
}
private void UpdateBounds()
{
Bounds = ToCurveBounds(_model.Type.Infos().ValueRange);
_minBoundsWidth = CurveTimeFromFrame(_model.Keys.Select(point => point.Frame).DefaultIfEmpty().Max());
}
private RectangleF ToCurveBounds(Range<float> valueRange)
{
var bounds = Bounds;
bounds.Y = valueRange.Min;
bounds.Height = valueRange.Max - valueRange.Min;
bounds.X = CurveTimeFromFrame(_model.Clip.StartFrame);
bounds.Width = CurveTimeFromFrame(_model.Clip.Duration);
return bounds;
}
public PointF ToModelTime(PointF point)
{
var x = FrameFromCurveTime(point.X) - _model.Clip.StartFrame;
var y = point.Y / _model.Type.Infos().EditionUnitConversion;
return new PointF(x, y);
}
private PointF ToViewTime(PointF point)
{
var x = CurveTimeFromFrame(point.X + _model.Clip.StartFrame);
var y = point.Y * _model.Type.Infos().EditionUnitConversion;
return new PointF(x, y);
}
private static float CurveTimeFromFrame(float frame)
{
return FrameConverter.TimeFromFrame(frame);
}
public static float FrameFromCurveTime(float frame)
{
return FrameConverter.FrameFromTime(frame);
}
public override string ToString()
{
return "View of " + Model;
}
}
}