diff --git a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs index c6aa20d63f..793643d91f 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseManiaPlayfield.cs @@ -46,7 +46,7 @@ namespace osu.Desktop.VisualTests.Tests const double start_time = 500; const double duration = 500; - Func createTimingChange = (time, gravity) => new DrawableManiaTimingSection(new TimingSection + Func createTimingChange = (time, gravity) => new ManiaSpeedAdjustmentContainer(new SpeedAdjustment { BeatLength = 1000, Time = time diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs index 3caa6cb3ab..1a0b97a625 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModGravity.cs @@ -32,11 +32,11 @@ namespace osu.Game.Rulesets.Mania.Mods { var maniaHitRenderer = (ManiaHitRenderer)hitRenderer; - maniaHitRenderer.HitObjectTimingChanges = new List[maniaHitRenderer.PreferredColumns]; - maniaHitRenderer.BarlineTimingChanges = new List(); + maniaHitRenderer.HitObjectTimingChanges = new List[maniaHitRenderer.PreferredColumns]; + maniaHitRenderer.BarlineTimingChanges = new List(); for (int i = 0; i < maniaHitRenderer.PreferredColumns; i++) - maniaHitRenderer.HitObjectTimingChanges[i] = new List(); + maniaHitRenderer.HitObjectTimingChanges[i] = new List(); foreach (HitObject obj in maniaHitRenderer.Objects) { @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Mods if (maniaObject == null) continue; - maniaHitRenderer.HitObjectTimingChanges[maniaObject.Column].Add(new DrawableManiaTimingSection(new TimingSection + maniaHitRenderer.HitObjectTimingChanges[maniaObject.Column].Add(new ManiaSpeedAdjustmentContainer(new SpeedAdjustment { Time = obj.StartTime, BeatLength = 1000 @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Mods for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength) { - maniaHitRenderer.BarlineTimingChanges.Add(new DrawableManiaTimingSection(new TimingSection + maniaHitRenderer.BarlineTimingChanges.Add(new ManiaSpeedAdjustmentContainer(new SpeedAdjustment { Time = t, BeatLength = 1000 diff --git a/osu.Game.Rulesets.Mania/Timing/Drawables/BasicScrollingHitObjectCollection.cs b/osu.Game.Rulesets.Mania/Timing/Drawables/BasicScrollingDrawableTimingSection.cs similarity index 70% rename from osu.Game.Rulesets.Mania/Timing/Drawables/BasicScrollingHitObjectCollection.cs rename to osu.Game.Rulesets.Mania/Timing/Drawables/BasicScrollingDrawableTimingSection.cs index 27eec6288b..581887f574 100644 --- a/osu.Game.Rulesets.Mania/Timing/Drawables/BasicScrollingHitObjectCollection.cs +++ b/osu.Game.Rulesets.Mania/Timing/Drawables/BasicScrollingDrawableTimingSection.cs @@ -7,11 +7,11 @@ using osu.Game.Rulesets.Timing.Drawables; namespace osu.Game.Rulesets.Mania.Timing.Drawables { - internal class BasicScrollingHitObjectCollection : HitObjectCollection + internal class BasicScrollingDrawableTimingSection : DrawableTimingSection { - private readonly TimingSection timingSection; + private readonly SpeedAdjustment timingSection; - public BasicScrollingHitObjectCollection(TimingSection timingSection) + public BasicScrollingDrawableTimingSection(SpeedAdjustment timingSection) : base(Axes.Y) { this.timingSection = timingSection; diff --git a/osu.Game.Rulesets.Mania/Timing/Drawables/GravityScrollingHitObjectCollection.cs b/osu.Game.Rulesets.Mania/Timing/Drawables/GravityScrollingDrawableTimingSection.cs similarity index 78% rename from osu.Game.Rulesets.Mania/Timing/Drawables/GravityScrollingHitObjectCollection.cs rename to osu.Game.Rulesets.Mania/Timing/Drawables/GravityScrollingDrawableTimingSection.cs index 919a1f0703..9e90acde96 100644 --- a/osu.Game.Rulesets.Mania/Timing/Drawables/GravityScrollingHitObjectCollection.cs +++ b/osu.Game.Rulesets.Mania/Timing/Drawables/GravityScrollingDrawableTimingSection.cs @@ -8,16 +8,14 @@ using osu.Game.Rulesets.Timing.Drawables; namespace osu.Game.Rulesets.Mania.Timing.Drawables { - internal class GravityScrollingHitObjectCollection : HitObjectCollection + internal class GravityScrollingDrawableTimingSection : DrawableTimingSection { - private readonly TimingSection timingSection; - private readonly Func timeSpan; + private readonly SpeedAdjustment timingSection; - public GravityScrollingHitObjectCollection(TimingSection timingSection, Func timeSpan) + public GravityScrollingDrawableTimingSection(SpeedAdjustment timingSection) : base(Axes.Y) { this.timingSection = timingSection; - this.timeSpan = timeSpan; } protected override void UpdateAfterChildren() @@ -45,19 +43,19 @@ namespace osu.Game.Rulesets.Mania.Timing.Drawables // The sign of the relative time, this is used to apply backwards acceleration leading into startTime double sign = relativeTime < 0 ? -1 : 1; - return timeSpan() - acceleration * relativeTime * relativeTime * sign; + return VisibleTimeRange - acceleration * relativeTime * relativeTime * sign; } /// /// The acceleration due to "gravity" of the content of this container. /// - private double acceleration => 1 / timeSpan(); + private double acceleration => 1 / VisibleTimeRange; /// /// Computes the current time relative to , accounting for . /// /// The non-offset time. /// The current time relative to - . - private double relativeTimeAt(double time) => Time.Current - time + timeSpan(); + private double relativeTimeAt(double time) => Time.Current - time + VisibleTimeRange; } } diff --git a/osu.Game.Rulesets.Mania/Timing/Drawables/DrawableManiaTimingSection.cs b/osu.Game.Rulesets.Mania/Timing/Drawables/ManiaSpeedAdjustmentContainer.cs similarity index 62% rename from osu.Game.Rulesets.Mania/Timing/Drawables/DrawableManiaTimingSection.cs rename to osu.Game.Rulesets.Mania/Timing/Drawables/ManiaSpeedAdjustmentContainer.cs index 772c8acca1..fb15ff99ed 100644 --- a/osu.Game.Rulesets.Mania/Timing/Drawables/DrawableManiaTimingSection.cs +++ b/osu.Game.Rulesets.Mania/Timing/Drawables/ManiaSpeedAdjustmentContainer.cs @@ -7,11 +7,11 @@ using osu.Game.Rulesets.Timing.Drawables; namespace osu.Game.Rulesets.Mania.Timing.Drawables { - public class DrawableManiaTimingSection : DrawableTimingSection + public class ManiaSpeedAdjustmentContainer : SpeedAdjustmentContainer { private readonly ScrollingAlgorithm scrollingAlgorithm; - public DrawableManiaTimingSection(TimingSection timingSection, ScrollingAlgorithm scrollingAlgorithm) + public ManiaSpeedAdjustmentContainer(SpeedAdjustment timingSection, ScrollingAlgorithm scrollingAlgorithm) : base(timingSection, Axes.Y) { this.scrollingAlgorithm = scrollingAlgorithm; @@ -21,25 +21,25 @@ namespace osu.Game.Rulesets.Mania.Timing.Drawables { base.UpdateAfterChildren(); - var parent = Parent as TimingSectionCollection; + var parent = Parent as SpeedAdjustmentCollection; if (parent == null) return; // This is very naive and can be improved, but is adequate for now - LifetimeStart = TimingSection.Time - parent.TimeSpan; + LifetimeStart = TimingSection.Time - VisibleTimeRange; LifetimeEnd = TimingSection.Time + Content.Height * 2; } - protected override HitObjectCollection CreateHitObjectCollection() + protected override DrawableTimingSection CreateTimingSection() { switch (scrollingAlgorithm) { default: case ScrollingAlgorithm.Basic: - return new BasicScrollingHitObjectCollection(TimingSection); + return new BasicScrollingDrawableTimingSection(TimingSection); case ScrollingAlgorithm.Gravity: - return new GravityScrollingHitObjectCollection(TimingSection, () => RelativeChildSize.Y); + return new GravityScrollingDrawableTimingSection(TimingSection); } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index c3d69fadcd..4737cd3db1 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -31,6 +31,13 @@ namespace osu.Game.Rulesets.Mania.UI private const float column_width = 45; private const float special_column_width = 70; + private readonly BindableDouble visibleTimeRange = new BindableDouble(); + public BindableDouble VisibleTimeRange + { + get { return visibleTimeRange; } + set { visibleTimeRange.BindTo(value); } + } + /// /// The key that will trigger input actions for this column and hit objects contained inside it. /// @@ -40,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Container hitTargetBar; private readonly Container keyIcon; - private readonly TimingSectionCollection timingChanges; + private readonly SpeedAdjustmentCollection speedAdjustments; public Column() { @@ -91,10 +98,11 @@ namespace osu.Game.Rulesets.Mania.UI } } }, - timingChanges = new TimingSectionCollection + speedAdjustments = new SpeedAdjustmentCollection { Name = "Hit objects", RelativeSizeAxes = Axes.Both, + VisibleTimeRange = VisibleTimeRange }, // For column lighting, we need to capture input events before the notes new InputTarget @@ -185,17 +193,11 @@ namespace osu.Game.Rulesets.Mania.UI } } - public double TimeSpan - { - get { return timingChanges.TimeSpan; } - set { timingChanges.TimeSpan = value; } - } - - public void Add(DrawableTimingSection timingChange) => timingChanges.Add(timingChange); + public void Add(SpeedAdjustmentContainer speedAdjustment) => speedAdjustments.Add(speedAdjustment); public void Add(DrawableHitObject hitObject) { hitObject.AccentColour = AccentColour; - timingChanges.Add(hitObject); + speedAdjustments.Add(hitObject); } private bool onKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs index cdc5563fc9..33b2faf3ba 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs @@ -41,12 +41,12 @@ namespace osu.Game.Rulesets.Mania.UI /// /// Per-column timing changes. /// - public List[] HitObjectTimingChanges; + public List[] HitObjectTimingChanges; /// /// Bar line timing changes. /// - public List BarlineTimingChanges; + public List BarlineTimingChanges; /// /// Number of columns in the playfield of this hit renderer. Null if the play field hasn't been generated yet. @@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Mania.UI if (HitObjectTimingChanges != null || BarlineTimingChanges != null) return; - HitObjectTimingChanges = new List[PreferredColumns]; - BarlineTimingChanges = new List(); + HitObjectTimingChanges = new List[PreferredColumns]; + BarlineTimingChanges = new List(); for (int i = 0; i < PreferredColumns; i++) - HitObjectTimingChanges[i] = new List(); + HitObjectTimingChanges[i] = new List(); double lastSpeedMultiplier = 1; double lastBeatLength = 500; @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.UI if (difficultyPoint != null) lastSpeedMultiplier = difficultyPoint.SpeedMultiplier; - return new TimingSection + return new SpeedAdjustment { Time = c.Time, BeatLength = lastBeatLength, @@ -115,9 +115,9 @@ namespace osu.Game.Rulesets.Mania.UI timingChanges.ForEach(t => { for (int i = 0; i < PreferredColumns; i++) - HitObjectTimingChanges[i].Add(new DrawableManiaTimingSection(t, ScrollingAlgorithm.Basic)); + HitObjectTimingChanges[i].Add(new ManiaSpeedAdjustmentContainer(t, ScrollingAlgorithm.Basic)); - BarlineTimingChanges.Add(new DrawableManiaTimingSection(t, ScrollingAlgorithm.Basic)); + BarlineTimingChanges.Add(new ManiaSpeedAdjustmentContainer(t, ScrollingAlgorithm.Basic)); }); } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index a88a543d96..dd3e3fee1e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -23,6 +23,7 @@ using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.Timing.Drawables; +using osu.Framework.Configuration; namespace osu.Game.Rulesets.Mania.UI { @@ -59,7 +60,13 @@ namespace osu.Game.Rulesets.Mania.UI private readonly FlowContainer columns; public IEnumerable Columns => columns.Children; - private readonly TimingSectionCollection barLineContainer; + private readonly BindableDouble visibleTimeRange = new BindableDouble(time_span_default) + { + MinValue = TIME_SPAN_MIN, + MaxValue = TIME_SPAN_MAX + }; + + private readonly SpeedAdjustmentCollection barLineContainer; private List normalColumnColours = new List(); private Color4 specialColumnColour; @@ -117,7 +124,7 @@ namespace osu.Game.Rulesets.Mania.UI Padding = new MarginPadding { Top = HIT_TARGET_POSITION }, Children = new[] { - barLineContainer = new TimingSectionCollection + barLineContainer = new SpeedAdjustmentCollection { Name = "Bar lines", Anchor = Anchor.TopCentre, @@ -132,9 +139,7 @@ namespace osu.Game.Rulesets.Mania.UI }; for (int i = 0; i < columnCount; i++) - columns.Add(new Column()); - - TimeSpan = time_span_default; + columns.Add(new Column { VisibleTimeRange = visibleTimeRange }); } [BackgroundDependencyLoader] @@ -209,7 +214,7 @@ namespace osu.Game.Rulesets.Mania.UI public override void Add(DrawableHitObject h) => Columns.ElementAt(h.HitObject.Column).Add(h); - public void Add(DrawableTimingSection timingChange) => barLineContainer.Add(timingChange); + public void Add(SpeedAdjustmentContainer timingChange) => barLineContainer.Add(timingChange); public void Add(DrawableBarLine barline) => barLineContainer.Add(barline); protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) @@ -219,10 +224,10 @@ namespace osu.Game.Rulesets.Mania.UI switch (args.Key) { case Key.Minus: - transformTimeSpanTo(TimeSpan + time_span_step, 200, EasingTypes.OutQuint); + transformVisibleTimeRangeTo(visibleTimeRange + time_span_step, 200, EasingTypes.OutQuint); break; case Key.Plus: - transformTimeSpanTo(TimeSpan - time_span_step, 200, EasingTypes.OutQuint); + transformVisibleTimeRangeTo(visibleTimeRange - time_span_step, 200, EasingTypes.OutQuint); break; } } @@ -230,29 +235,9 @@ namespace osu.Game.Rulesets.Mania.UI return false; } - private double timeSpan; - /// - /// The amount of time which the length of the playfield spans. - /// - public double TimeSpan + private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, EasingTypes easing = EasingTypes.None) { - get { return timeSpan; } - set - { - if (timeSpan == value) - return; - timeSpan = value; - - timeSpan = MathHelper.Clamp(timeSpan, TIME_SPAN_MIN, TIME_SPAN_MAX); - - barLineContainer.TimeSpan = value; - Columns.ForEach(c => c.TimeSpan = value); - } - } - - private void transformTimeSpanTo(double newTimeSpan, double duration = 0, EasingTypes easing = EasingTypes.None) - { - TransformTo(() => TimeSpan, newTimeSpan, duration, easing, new TransformTimeSpan()); + TransformTo(() => visibleTimeRange.Value, newTimeRange, duration, easing, new TransformTimeSpan()); } protected override void Update() @@ -281,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.UI base.Apply(d); var p = (ManiaPlayfield)d; - p.TimeSpan = (float)CurrentValue; + p.visibleTimeRange.Value = (float)CurrentValue; } } } diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 0300061d57..eda50dd627 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -77,8 +77,8 @@ - - + + @@ -87,7 +87,7 @@ - + diff --git a/osu.Game/Rulesets/Timing/Drawables/DrawableTimingSection.cs b/osu.Game/Rulesets/Timing/Drawables/DrawableTimingSection.cs index d07aff50e3..06c7a201ab 100644 --- a/osu.Game/Rulesets/Timing/Drawables/DrawableTimingSection.cs +++ b/osu.Game/Rulesets/Timing/Drawables/DrawableTimingSection.cs @@ -1,46 +1,67 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Allocation; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; +using osu.Framework.Configuration; namespace osu.Game.Rulesets.Timing.Drawables { /// - /// A container for hit objects which applies applies the speed changes defined by the and - /// properties to its to affect the scroll speed. + /// A collection of hit objects which scrolls within a . + /// + /// + /// This container handles the conversion between time and position through and + /// such that hit objects added to this container should have time values set as their + /// positions/sizes to make proper use of this container. + /// + /// + /// + /// This container will auto-size to the total size of its children along the desired auto-sizing axes such that the reasulting size + /// of this container will also be a time value. + /// + /// + /// + /// This container will always be relatively-sized and positioned to its parent through the use of + /// and such that the parent can utilise and + /// to apply further time offsets to this collection of hit objects. + /// /// public abstract class DrawableTimingSection : Container { - public readonly TimingSection TimingSection; + private readonly BindableDouble visibleTimeRange = new BindableDouble(); + public BindableDouble VisibleTimeRange + { + get { return visibleTimeRange; } + set { visibleTimeRange.BindTo(value); } + } - protected override Container Content => content; - private Container content; + protected override IComparer DepthComparer => new HitObjectReverseStartTimeComparer(); - private readonly Axes scrollingAxes; + private readonly Axes autoSizingAxes; + + private Cached layout = new Cached(); /// /// Creates a new . /// - /// The encapsulated timing section that provides the speed changes. - /// The axes through which this drawable timing section scrolls through. - protected DrawableTimingSection(TimingSection timingSection, Axes scrollingAxes) + /// The axes on which to auto-size to the total size of items in the container. + protected DrawableTimingSection(Axes autoSizingAxes) { - this.scrollingAxes = scrollingAxes; + this.autoSizingAxes = autoSizingAxes; - TimingSection = timingSection; + // We need a default size since RelativeSizeAxes is overridden + Size = Vector2.One; } - [BackgroundDependencyLoader] - private void load() - { - AddInternal(content = CreateHitObjectCollection()); - content.RelativeChildOffset = new Vector2((scrollingAxes & Axes.X) > 0 ? (float)TimingSection.Time : 0, (scrollingAxes & Axes.Y) > 0 ? (float)TimingSection.Time : 0); - } + public override Axes AutoSizeAxes { set { throw new InvalidOperationException($"{nameof(DrawableTimingSection)} must always be relatively-sized."); } } public override Axes RelativeSizeAxes { @@ -48,29 +69,55 @@ namespace osu.Game.Rulesets.Timing.Drawables set { throw new InvalidOperationException($"{nameof(DrawableTimingSection)} must always be relatively-sized."); } } - protected override void Update() + public override Axes RelativePositionAxes { - var parent = Parent as TimingSectionCollection; - - if (parent == null) - return; - - float speedAdjustedSize = (float)(1000 / TimingSection.BeatLength / TimingSection.SpeedMultiplier); - - // The application of speed changes happens by modifying our size while maintaining the parent's time span as our relative child size - Size = new Vector2((scrollingAxes & Axes.X) > 0 ? speedAdjustedSize : 1, (scrollingAxes & Axes.Y) > 0 ? speedAdjustedSize : 1); - RelativeChildSize = new Vector2((scrollingAxes & Axes.X) > 0 ? (float)parent.TimeSpan : 1, (scrollingAxes & Axes.Y) > 0 ? (float)parent.TimeSpan : 1); + get { return Axes.Both; } + set { throw new InvalidOperationException($"{nameof(DrawableTimingSection)} must always be relatively-positioned."); } } - /// - /// Whether this timing section can contain a hit object. This is true if the hit object occurs after this timing section with respect to time. - /// - public bool CanContain(DrawableHitObject hitObject) => TimingSection.Time <= hitObject.HitObject.StartTime; + public override void InvalidateFromChild(Invalidation invalidation) + { + // We only want to re-compute our size when a child's size or position has changed + if ((invalidation & Invalidation.RequiredParentSizeToFit) == 0) + { + base.InvalidateFromChild(invalidation); + return; + } - /// - /// Creates the container which handles the movement of a collection of hit objects. - /// - /// The hit object collection. - protected abstract HitObjectCollection CreateHitObjectCollection(); + layout.Invalidate(); + + base.InvalidateFromChild(invalidation); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (!layout.EnsureValid()) + { + layout.Refresh(() => + { + if (!Children.Any()) + return; + + //double maxDuration = Children.Select(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime).Max(); + //float width = (float)maxDuration - RelativeChildOffset.X; + //float height = (float)maxDuration - RelativeChildOffset.Y; + + + // Auto-size to the total size of our children + // This ends up being the total duration of our children, however for now this is a more sure-fire way to calculate this + // than the above due to some undesired masking optimisations causing some hit objects to be culled... + // Todo: When this is investigated more we should use the above method as it is a little more exact + float width = Children.Select(child => child.X + child.Width).Max() - RelativeChildOffset.X; + float height = Children.Select(child => child.Y + child.Height).Max() - RelativeChildOffset.Y; + + // Consider that width/height are time values. To have ourselves span these time values 1:1, we first need to set our size + Size = new Vector2((autoSizingAxes & Axes.X) > 0 ? width : Size.X, (autoSizingAxes & Axes.Y) > 0 ? height : Size.Y); + // Then to make our position-space be time values again, we need our relative child size to follow our size + RelativeChildSize = Size; + }); + } + } } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Timing/Drawables/HitObjectCollection.cs b/osu.Game/Rulesets/Timing/Drawables/HitObjectCollection.cs deleted file mode 100644 index 792ccf377f..0000000000 --- a/osu.Game/Rulesets/Timing/Drawables/HitObjectCollection.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Caching; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.Timing.Drawables -{ - /// - /// A collection of hit objects which scrolls within a . - /// - /// - /// This container handles the conversion between time and position through and - /// such that hit objects added to this container should have time values set as their - /// positions/sizes to make proper use of this container. - /// - /// - /// - /// This container will auto-size to the total size of its children along the desired auto-sizing axes such that the reasulting size - /// of this container will also be a time value. - /// - /// - /// - /// This container will always be relatively-sized and positioned to its parent through the use of - /// and such that the parent can utilise and - /// to apply further time offsets to this collection of hit objects. - /// - /// - public abstract class HitObjectCollection : Container - { - protected override IComparer DepthComparer => new HitObjectReverseStartTimeComparer(); - - private readonly Axes autoSizingAxes; - - private Cached layout = new Cached(); - - /// - /// Creates a new . - /// - /// The axes on which to auto-size to the total size of items in the container. - protected HitObjectCollection(Axes autoSizingAxes) - { - this.autoSizingAxes = autoSizingAxes; - - // We need a default size since RelativeSizeAxes is overridden - Size = Vector2.One; - } - - public override Axes AutoSizeAxes { set { throw new InvalidOperationException($"{nameof(HitObjectCollection)} must always be relatively-sized."); } } - - public override Axes RelativeSizeAxes - { - get { return Axes.Both; } - set { throw new InvalidOperationException($"{nameof(HitObjectCollection)} must always be relatively-sized."); } - } - - public override Axes RelativePositionAxes - { - get { return Axes.Both; } - set { throw new InvalidOperationException($"{nameof(HitObjectCollection)} must always be relatively-positioned."); } - } - - public override void InvalidateFromChild(Invalidation invalidation) - { - // We only want to re-compute our size when a child's size or position has changed - if ((invalidation & Invalidation.RequiredParentSizeToFit) == 0) - { - base.InvalidateFromChild(invalidation); - return; - } - - layout.Invalidate(); - - base.InvalidateFromChild(invalidation); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (!layout.EnsureValid()) - { - layout.Refresh(() => - { - if (!Children.Any()) - return; - - //double maxDuration = Children.Select(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime).Max(); - //float width = (float)maxDuration - RelativeChildOffset.X; - //float height = (float)maxDuration - RelativeChildOffset.Y; - - - // Auto-size to the total size of our children - // This ends up being the total duration of our children, however for now this is a more sure-fire way to calculate this - // than the above due to some undesired masking optimisations causing some hit objects to be culled... - // Todo: When this is investigated more we should use the above method as it is a little more exact - float width = Children.Select(child => child.X + child.Width).Max() - RelativeChildOffset.X; - float height = Children.Select(child => child.Y + child.Height).Max() - RelativeChildOffset.Y; - - // Consider that width/height are time values. To have ourselves span these time values 1:1, we first need to set our size - Size = new Vector2((autoSizingAxes & Axes.X) > 0 ? width : Size.X, (autoSizingAxes & Axes.Y) > 0 ? height : Size.Y); - // Then to make our position-space be time values again, we need our relative child size to follow our size - RelativeChildSize = Size; - }); - } - } - } -} diff --git a/osu.Game/Rulesets/Timing/Drawables/SpeedAdjustmentContainer.cs b/osu.Game/Rulesets/Timing/Drawables/SpeedAdjustmentContainer.cs new file mode 100644 index 0000000000..bb2c6eec28 --- /dev/null +++ b/osu.Game/Rulesets/Timing/Drawables/SpeedAdjustmentContainer.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; +using osu.Framework.Configuration; + +namespace osu.Game.Rulesets.Timing.Drawables +{ + /// + /// A container for hit objects which applies applies the speed changes defined by the and + /// properties to its to affect the scroll speed. + /// + public abstract class SpeedAdjustmentContainer : Container + { + private readonly Bindable visibleTimeRange = new Bindable(); + public Bindable VisibleTimeRange + { + get { return visibleTimeRange; } + set { visibleTimeRange.BindTo(value); } + } + + public readonly SpeedAdjustment TimingSection; + + protected override Container Content => content; + private Container content; + + private readonly Axes scrollingAxes; + + /// + /// Creates a new . + /// + /// The encapsulated timing section that provides the speed changes. + /// The axes through which this drawable timing section scrolls through. + protected SpeedAdjustmentContainer(SpeedAdjustment timingSection, Axes scrollingAxes) + { + this.scrollingAxes = scrollingAxes; + + TimingSection = timingSection; + } + + [BackgroundDependencyLoader] + private void load() + { + DrawableTimingSection timingSection = CreateTimingSection(); + + timingSection.VisibleTimeRange.BindTo(VisibleTimeRange); + timingSection.RelativeChildOffset = new Vector2((scrollingAxes & Axes.X) > 0 ? (float)TimingSection.Time : 0, (scrollingAxes & Axes.Y) > 0 ? (float)TimingSection.Time : 0); + + AddInternal(content = timingSection); + } + + public override Axes RelativeSizeAxes + { + get { return Axes.Both; } + set { throw new InvalidOperationException($"{nameof(SpeedAdjustmentContainer)} must always be relatively-sized."); } + } + + protected override void Update() + { + float speedAdjustedSize = (float)(1000 / TimingSection.BeatLength / TimingSection.SpeedMultiplier); + + // The speed adjustment happens by modifying our size while maintaining the visible time range as the relatve size for our children + Size = new Vector2((scrollingAxes & Axes.X) > 0 ? speedAdjustedSize : 1, (scrollingAxes & Axes.Y) > 0 ? speedAdjustedSize : 1); + RelativeChildSize = new Vector2((scrollingAxes & Axes.X) > 0 ? (float)VisibleTimeRange : 1, (scrollingAxes & Axes.Y) > 0 ? (float)VisibleTimeRange : 1); + } + + /// + /// Whether this speed adjustment can contain a hit object. This is true if the hit object occurs after this speed adjustment with respect to time. + /// + public bool CanContain(DrawableHitObject hitObject) => TimingSection.Time <= hitObject.HitObject.StartTime; + + /// + /// Creates the container which handles the movement of a collection of hit objects. + /// + /// The drawable timing section. + protected abstract DrawableTimingSection CreateTimingSection(); + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Timing/TimingSection.cs b/osu.Game/Rulesets/Timing/SpeedAdjustment.cs similarity index 93% rename from osu.Game/Rulesets/Timing/TimingSection.cs rename to osu.Game/Rulesets/Timing/SpeedAdjustment.cs index 9958dd1022..ee30be8e6a 100644 --- a/osu.Game/Rulesets/Timing/TimingSection.cs +++ b/osu.Game/Rulesets/Timing/SpeedAdjustment.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Timing { - public class TimingSection + public class SpeedAdjustment { /// /// The time in milliseconds at which this timing section starts. diff --git a/osu.Game/Rulesets/Timing/TimingSectionCollection.cs b/osu.Game/Rulesets/Timing/SpeedAdjustmentCollection.cs similarity index 65% rename from osu.Game/Rulesets/Timing/TimingSectionCollection.cs rename to osu.Game/Rulesets/Timing/SpeedAdjustmentCollection.cs index a8774e1a4f..2ebda1ffb5 100644 --- a/osu.Game/Rulesets/Timing/TimingSectionCollection.cs +++ b/osu.Game/Rulesets/Timing/SpeedAdjustmentCollection.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -12,20 +13,24 @@ using osu.Game.Rulesets.Timing.Drawables; namespace osu.Game.Rulesets.Timing { /// - /// A collection of timing sections which contain hit objects. - /// + /// A collection of s. + /// /// - /// This container provides for the timing sections. This is a value that indicates the amount of time - /// that is visible throughout the span of this container. - /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. + /// This container provides for the s. /// /// - public class TimingSectionCollection : Container + public class SpeedAdjustmentCollection : Container { + private readonly BindableDouble visibleTimeRange = new BindableDouble(); /// - /// The length of time visible throughout the span of this container. + /// The amount of time visible by span of this container. + /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. /// - public double TimeSpan; + public Bindable VisibleTimeRange + { + get { return visibleTimeRange; } + set { visibleTimeRange.BindTo(value); } + } /// /// Adds a hit object to the most applicable timing section in this container. @@ -41,6 +46,12 @@ namespace osu.Game.Rulesets.Timing target.Add(hitObject); } + public override void Add(SpeedAdjustmentContainer speedAdjustment) + { + speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange); + base.Add(speedAdjustment); + } + protected override IComparer DepthComparer => new TimingSectionReverseStartTimeComparer(); /// @@ -49,7 +60,7 @@ namespace osu.Game.Rulesets.Timing /// /// The hit object to contain. /// The last (time-wise) timing section which can contain . Null if no timing section exists. - private DrawableTimingSection timingSectionFor(DrawableHitObject hitObject) => Children.FirstOrDefault(c => c.CanContain(hitObject)) ?? Children.LastOrDefault(); + private SpeedAdjustmentContainer timingSectionFor(DrawableHitObject hitObject) => Children.FirstOrDefault(c => c.CanContain(hitObject)) ?? Children.LastOrDefault(); /// /// Compares two timing sections by their start time, falling back to creation order if their start time is equal. @@ -59,8 +70,8 @@ namespace osu.Game.Rulesets.Timing { public override int Compare(Drawable x, Drawable y) { - var timingChangeX = x as DrawableTimingSection; - var timingChangeY = y as DrawableTimingSection; + var timingChangeX = x as SpeedAdjustmentContainer; + var timingChangeY = y as SpeedAdjustmentContainer; // If either of the two drawables are not hit objects, fall back to the base comparer if (timingChangeX?.TimingSection == null || timingChangeY?.TimingSection == null) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4c870190bb..fcb890fe28 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -195,10 +195,10 @@ + - - - + +