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 { /// /// Visual representation of the model curve. /// 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 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())); } } /// /// Fills the model with the new view values. /// 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; } } } /// /// Fills the view with the current model values. /// public void UpdateView() { using (_isUpdatingView.Begin()) using (Points.BeginScopedUpdate()) { // Keep the existing points so the selection is persisted. Dictionary existingPoints = Points.OfType().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); } } } /// /// 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. /// 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 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; } } }