// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Caching; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Lists; using osu.Game.Configuration; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI.Scrolling.Visualisers; namespace osu.Game.Rulesets.UI.Scrolling { public class ScrollingHitObjectContainer : HitObjectContainer { /// /// The duration required to scroll through one length of the before any control point adjustments. /// public readonly BindableDouble TimeRange = new BindableDouble { MinValue = 0, MaxValue = double.MaxValue }; /// /// The control points that adjust the scrolling speed. /// protected readonly SortedList ControlPoints = new SortedList(); public readonly Bindable Direction = new Bindable(); private Cached initialStateCache = new Cached(); private readonly ISpeedChangeVisualiser speedChangeVisualiser; public ScrollingHitObjectContainer(SpeedChangeVisualisationMethod visualisationMethod) { RelativeSizeAxes = Axes.Both; TimeRange.ValueChanged += _ => initialStateCache.Invalidate(); Direction.ValueChanged += _ => initialStateCache.Invalidate(); switch (visualisationMethod) { case SpeedChangeVisualisationMethod.Sequential: speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints); break; case SpeedChangeVisualisationMethod.Overlapping: speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); break; case SpeedChangeVisualisationMethod.Constant: speedChangeVisualiser = new ConstantSpeedChangeVisualiser(); break; } } public override void Add(DrawableHitObject hitObject) { initialStateCache.Invalidate(); base.Add(hitObject); } public override bool Remove(DrawableHitObject hitObject) { var result = base.Remove(hitObject); if (result) initialStateCache.Invalidate(); return result; } public void AddControlPoint(MultiplierControlPoint controlPoint) { ControlPoints.Add(controlPoint); initialStateCache.Invalidate(); } public bool RemoveControlPoint(MultiplierControlPoint controlPoint) { var result = ControlPoints.Remove(controlPoint); if (result) initialStateCache.Invalidate(); return result; } public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) initialStateCache.Invalidate(); return base.Invalidate(invalidation, source, shallPropagate); } protected override void Update() { base.Update(); speedChangeVisualiser.TimeRange = TimeRange.Value; switch (Direction.Value) { case ScrollingDirection.Up: case ScrollingDirection.Down: speedChangeVisualiser.ScrollLength = DrawSize.Y; break; default: speedChangeVisualiser.ScrollLength = DrawSize.X; break; } if (!initialStateCache.IsValid) { speedChangeVisualiser.ComputeInitialStates(Objects, Direction); initialStateCache.Validate(); } } protected override void UpdateAfterChildrenLife() { base.UpdateAfterChildrenLife(); // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions speedChangeVisualiser.UpdatePositions(AliveObjects, Direction, Time.Current); } } }