159 lines
5.5 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|