// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; using OpenTK.Input; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Framework.Lists; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; namespace osu.Game.Rulesets.UI { /// /// A type of specialized towards scrolling s. /// public class ScrollingPlayfield : Playfield { /// /// The default span of time visible by the length of the scrolling axes. /// This is clamped between and . /// private const double time_span_default = 1500; /// /// The minimum span of time that may be visible by the length of the scrolling axes. /// private const double time_span_min = 50; /// /// The maximum span of time that may be visible by the length of the scrolling axes. /// private const double time_span_max = 10000; /// /// The step increase/decrease of the span of time visible by the length of the scrolling axes. /// private const double time_span_step = 50; /// /// The span of time that is visible by the length of the scrolling axes. /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. /// public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default) { Default = time_span_default, MinValue = time_span_min, MaxValue = time_span_max }; /// /// The container that contains the s and s. /// public new readonly ScrollingHitObjectContainer HitObjects; /// /// Creates a new . /// /// The axes on which s in this container should scroll. /// Whether we want our internal coordinate system to be scaled to a specified width protected ScrollingPlayfield(Direction scrollingDirection, float? customWidth = null) : base(customWidth) { base.HitObjects = HitObjects = new ScrollingHitObjectContainer(scrollingDirection) { RelativeSizeAxes = Axes.Both }; HitObjects.TimeRange.BindTo(VisibleTimeRange); } private List nestedPlayfields; /// /// All the s nested inside this playfield. /// public IEnumerable NestedPlayfields => nestedPlayfields; /// /// Adds a to this playfield. The nested /// will be given all of the same speed adjustments as this playfield. /// /// The to add. protected void AddNested(ScrollingPlayfield otherPlayfield) { if (nestedPlayfields == null) nestedPlayfields = new List(); nestedPlayfields.Add(otherPlayfield); } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { if (state.Keyboard.ControlPressed) { switch (args.Key) { case Key.Minus: transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint); break; case Key.Plus: transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint); break; } } return false; } private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None) { this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing)); } private class TransformVisibleTimeRange : Transform { private double valueAt(double time) { if (time < StartTime) return StartValue; if (time >= EndTime) return EndValue; return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); } public override string TargetMember => "VisibleTimeRange.Value"; protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time); protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value; } public class ScrollingHitObjectContainer : HitObjectContainer { public readonly BindableDouble TimeRange = new BindableDouble { MinValue = 0, MaxValue = double.MaxValue }; public readonly SortedList ControlPoints = new SortedList(); private readonly Direction scrollingDirection; public ScrollingHitObjectContainer(Direction scrollingDirection) { this.scrollingDirection = scrollingDirection; RelativeSizeAxes = Axes.Both; } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); var currentMultiplier = controlPointAt(Time.Current); foreach (var obj in AliveObjects) { var relativePosition = (Time.Current - obj.HitObject.StartTime) / (TimeRange / currentMultiplier.Multiplier); // Todo: We may need to consider scale here var finalPosition = (float)relativePosition * DrawSize; switch (scrollingDirection) { case Direction.Horizontal: obj.X = finalPosition.X; break; case Direction.Vertical: obj.Y = finalPosition.Y; break; } } } private readonly MultiplierControlPoint searchingPoint = new MultiplierControlPoint(); private MultiplierControlPoint controlPointAt(double time) { if (ControlPoints.Count == 0) return new MultiplierControlPoint(double.MinValue); if (time < ControlPoints[0].StartTime) return ControlPoints[0]; searchingPoint.StartTime = time; int index = ControlPoints.BinarySearch(searchingPoint); if (index < 0) index = ~index - 1; return ControlPoints[index]; } } } }