1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 00:53:22 +08:00

Use new naming structure + VisibleTimeRange bindable.

This commit is contained in:
smoogipooo 2017-06-09 16:11:31 +09:00
parent 3e3e64eb39
commit d11002e499
15 changed files with 257 additions and 246 deletions

View File

@ -46,7 +46,7 @@ namespace osu.Desktop.VisualTests.Tests
const double start_time = 500; const double start_time = 500;
const double duration = 500; const double duration = 500;
Func<double, bool, DrawableTimingSection> createTimingChange = (time, gravity) => new DrawableManiaTimingSection(new TimingSection Func<double, bool, SpeedAdjustmentContainer> createTimingChange = (time, gravity) => new ManiaSpeedAdjustmentContainer(new SpeedAdjustment
{ {
BeatLength = 1000, BeatLength = 1000,
Time = time Time = time

View File

@ -32,11 +32,11 @@ namespace osu.Game.Rulesets.Mania.Mods
{ {
var maniaHitRenderer = (ManiaHitRenderer)hitRenderer; var maniaHitRenderer = (ManiaHitRenderer)hitRenderer;
maniaHitRenderer.HitObjectTimingChanges = new List<DrawableTimingSection>[maniaHitRenderer.PreferredColumns]; maniaHitRenderer.HitObjectTimingChanges = new List<SpeedAdjustmentContainer>[maniaHitRenderer.PreferredColumns];
maniaHitRenderer.BarlineTimingChanges = new List<DrawableTimingSection>(); maniaHitRenderer.BarlineTimingChanges = new List<SpeedAdjustmentContainer>();
for (int i = 0; i < maniaHitRenderer.PreferredColumns; i++) for (int i = 0; i < maniaHitRenderer.PreferredColumns; i++)
maniaHitRenderer.HitObjectTimingChanges[i] = new List<DrawableTimingSection>(); maniaHitRenderer.HitObjectTimingChanges[i] = new List<SpeedAdjustmentContainer>();
foreach (HitObject obj in maniaHitRenderer.Objects) foreach (HitObject obj in maniaHitRenderer.Objects)
{ {
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Mods
if (maniaObject == null) if (maniaObject == null)
continue; continue;
maniaHitRenderer.HitObjectTimingChanges[maniaObject.Column].Add(new DrawableManiaTimingSection(new TimingSection maniaHitRenderer.HitObjectTimingChanges[maniaObject.Column].Add(new ManiaSpeedAdjustmentContainer(new SpeedAdjustment
{ {
Time = obj.StartTime, Time = obj.StartTime,
BeatLength = 1000 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) 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, Time = t,
BeatLength = 1000 BeatLength = 1000

View File

@ -7,11 +7,11 @@ using osu.Game.Rulesets.Timing.Drawables;
namespace osu.Game.Rulesets.Mania.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) : base(Axes.Y)
{ {
this.timingSection = timingSection; this.timingSection = timingSection;

View File

@ -8,16 +8,14 @@ using osu.Game.Rulesets.Timing.Drawables;
namespace osu.Game.Rulesets.Mania.Timing.Drawables namespace osu.Game.Rulesets.Mania.Timing.Drawables
{ {
internal class GravityScrollingHitObjectCollection : HitObjectCollection internal class GravityScrollingDrawableTimingSection : DrawableTimingSection
{ {
private readonly TimingSection timingSection; private readonly SpeedAdjustment timingSection;
private readonly Func<double> timeSpan;
public GravityScrollingHitObjectCollection(TimingSection timingSection, Func<double> timeSpan) public GravityScrollingDrawableTimingSection(SpeedAdjustment timingSection)
: base(Axes.Y) : base(Axes.Y)
{ {
this.timingSection = timingSection; this.timingSection = timingSection;
this.timeSpan = timeSpan;
} }
protected override void UpdateAfterChildren() 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 // The sign of the relative time, this is used to apply backwards acceleration leading into startTime
double sign = relativeTime < 0 ? -1 : 1; double sign = relativeTime < 0 ? -1 : 1;
return timeSpan() - acceleration * relativeTime * relativeTime * sign; return VisibleTimeRange - acceleration * relativeTime * relativeTime * sign;
} }
/// <summary> /// <summary>
/// The acceleration due to "gravity" of the content of this container. /// The acceleration due to "gravity" of the content of this container.
/// </summary> /// </summary>
private double acceleration => 1 / timeSpan(); private double acceleration => 1 / VisibleTimeRange;
/// <summary> /// <summary>
/// Computes the current time relative to <paramref name="time"/>, accounting for <see cref="timeSpan"/>. /// Computes the current time relative to <paramref name="time"/>, accounting for <see cref="timeSpan"/>.
/// </summary> /// </summary>
/// <param name="time">The non-offset time.</param> /// <param name="time">The non-offset time.</param>
/// <returns>The current time relative to <paramref name="time"/> - <see cref="timeSpan"/>. </returns> /// <returns>The current time relative to <paramref name="time"/> - <see cref="timeSpan"/>. </returns>
private double relativeTimeAt(double time) => Time.Current - time + timeSpan(); private double relativeTimeAt(double time) => Time.Current - time + VisibleTimeRange;
} }
} }

View File

@ -7,11 +7,11 @@ using osu.Game.Rulesets.Timing.Drawables;
namespace osu.Game.Rulesets.Mania.Timing.Drawables namespace osu.Game.Rulesets.Mania.Timing.Drawables
{ {
public class DrawableManiaTimingSection : DrawableTimingSection public class ManiaSpeedAdjustmentContainer : SpeedAdjustmentContainer
{ {
private readonly ScrollingAlgorithm scrollingAlgorithm; private readonly ScrollingAlgorithm scrollingAlgorithm;
public DrawableManiaTimingSection(TimingSection timingSection, ScrollingAlgorithm scrollingAlgorithm) public ManiaSpeedAdjustmentContainer(SpeedAdjustment timingSection, ScrollingAlgorithm scrollingAlgorithm)
: base(timingSection, Axes.Y) : base(timingSection, Axes.Y)
{ {
this.scrollingAlgorithm = scrollingAlgorithm; this.scrollingAlgorithm = scrollingAlgorithm;
@ -21,25 +21,25 @@ namespace osu.Game.Rulesets.Mania.Timing.Drawables
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
var parent = Parent as TimingSectionCollection; var parent = Parent as SpeedAdjustmentCollection;
if (parent == null) if (parent == null)
return; return;
// This is very naive and can be improved, but is adequate for now // 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; LifetimeEnd = TimingSection.Time + Content.Height * 2;
} }
protected override HitObjectCollection CreateHitObjectCollection() protected override DrawableTimingSection CreateTimingSection()
{ {
switch (scrollingAlgorithm) switch (scrollingAlgorithm)
{ {
default: default:
case ScrollingAlgorithm.Basic: case ScrollingAlgorithm.Basic:
return new BasicScrollingHitObjectCollection(TimingSection); return new BasicScrollingDrawableTimingSection(TimingSection);
case ScrollingAlgorithm.Gravity: case ScrollingAlgorithm.Gravity:
return new GravityScrollingHitObjectCollection(TimingSection, () => RelativeChildSize.Y); return new GravityScrollingDrawableTimingSection(TimingSection);
} }
} }
} }

View File

@ -31,6 +31,13 @@ namespace osu.Game.Rulesets.Mania.UI
private const float column_width = 45; private const float column_width = 45;
private const float special_column_width = 70; private const float special_column_width = 70;
private readonly BindableDouble visibleTimeRange = new BindableDouble();
public BindableDouble VisibleTimeRange
{
get { return visibleTimeRange; }
set { visibleTimeRange.BindTo(value); }
}
/// <summary> /// <summary>
/// The key that will trigger input actions for this column and hit objects contained inside it. /// The key that will trigger input actions for this column and hit objects contained inside it.
/// </summary> /// </summary>
@ -40,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Container hitTargetBar; private readonly Container hitTargetBar;
private readonly Container keyIcon; private readonly Container keyIcon;
private readonly TimingSectionCollection timingChanges; private readonly SpeedAdjustmentCollection speedAdjustments;
public Column() public Column()
{ {
@ -91,10 +98,11 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
}, },
timingChanges = new TimingSectionCollection speedAdjustments = new SpeedAdjustmentCollection
{ {
Name = "Hit objects", Name = "Hit objects",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
VisibleTimeRange = VisibleTimeRange
}, },
// For column lighting, we need to capture input events before the notes // For column lighting, we need to capture input events before the notes
new InputTarget new InputTarget
@ -185,17 +193,11 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
public double TimeSpan public void Add(SpeedAdjustmentContainer speedAdjustment) => speedAdjustments.Add(speedAdjustment);
{
get { return timingChanges.TimeSpan; }
set { timingChanges.TimeSpan = value; }
}
public void Add(DrawableTimingSection timingChange) => timingChanges.Add(timingChange);
public void Add(DrawableHitObject hitObject) public void Add(DrawableHitObject hitObject)
{ {
hitObject.AccentColour = AccentColour; hitObject.AccentColour = AccentColour;
timingChanges.Add(hitObject); speedAdjustments.Add(hitObject);
} }
private bool onKeyDown(InputState state, KeyDownEventArgs args) private bool onKeyDown(InputState state, KeyDownEventArgs args)

View File

@ -41,12 +41,12 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary> /// <summary>
/// Per-column timing changes. /// Per-column timing changes.
/// </summary> /// </summary>
public List<DrawableTimingSection>[] HitObjectTimingChanges; public List<SpeedAdjustmentContainer>[] HitObjectTimingChanges;
/// <summary> /// <summary>
/// Bar line timing changes. /// Bar line timing changes.
/// </summary> /// </summary>
public List<DrawableTimingSection> BarlineTimingChanges; public List<SpeedAdjustmentContainer> BarlineTimingChanges;
/// <summary> /// <summary>
/// Number of columns in the playfield of this hit renderer. Null if the play field hasn't been generated yet. /// 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) if (HitObjectTimingChanges != null || BarlineTimingChanges != null)
return; return;
HitObjectTimingChanges = new List<DrawableTimingSection>[PreferredColumns]; HitObjectTimingChanges = new List<SpeedAdjustmentContainer>[PreferredColumns];
BarlineTimingChanges = new List<DrawableTimingSection>(); BarlineTimingChanges = new List<SpeedAdjustmentContainer>();
for (int i = 0; i < PreferredColumns; i++) for (int i = 0; i < PreferredColumns; i++)
HitObjectTimingChanges[i] = new List<DrawableTimingSection>(); HitObjectTimingChanges[i] = new List<SpeedAdjustmentContainer>();
double lastSpeedMultiplier = 1; double lastSpeedMultiplier = 1;
double lastBeatLength = 500; double lastBeatLength = 500;
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.UI
if (difficultyPoint != null) if (difficultyPoint != null)
lastSpeedMultiplier = difficultyPoint.SpeedMultiplier; lastSpeedMultiplier = difficultyPoint.SpeedMultiplier;
return new TimingSection return new SpeedAdjustment
{ {
Time = c.Time, Time = c.Time,
BeatLength = lastBeatLength, BeatLength = lastBeatLength,
@ -115,9 +115,9 @@ namespace osu.Game.Rulesets.Mania.UI
timingChanges.ForEach(t => timingChanges.ForEach(t =>
{ {
for (int i = 0; i < PreferredColumns; i++) 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));
}); });
} }

View File

@ -23,6 +23,7 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.Timing.Drawables; using osu.Game.Rulesets.Timing.Drawables;
using osu.Framework.Configuration;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -59,7 +60,13 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly FlowContainer<Column> columns; private readonly FlowContainer<Column> columns;
public IEnumerable<Column> Columns => columns.Children; public IEnumerable<Column> 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<Color4> normalColumnColours = new List<Color4>(); private List<Color4> normalColumnColours = new List<Color4>();
private Color4 specialColumnColour; private Color4 specialColumnColour;
@ -117,7 +124,7 @@ namespace osu.Game.Rulesets.Mania.UI
Padding = new MarginPadding { Top = HIT_TARGET_POSITION }, Padding = new MarginPadding { Top = HIT_TARGET_POSITION },
Children = new[] Children = new[]
{ {
barLineContainer = new TimingSectionCollection barLineContainer = new SpeedAdjustmentCollection
{ {
Name = "Bar lines", Name = "Bar lines",
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
@ -132,9 +139,7 @@ namespace osu.Game.Rulesets.Mania.UI
}; };
for (int i = 0; i < columnCount; i++) for (int i = 0; i < columnCount; i++)
columns.Add(new Column()); columns.Add(new Column { VisibleTimeRange = visibleTimeRange });
TimeSpan = time_span_default;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -209,7 +214,7 @@ namespace osu.Game.Rulesets.Mania.UI
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.ElementAt(h.HitObject.Column).Add(h); public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> 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); public void Add(DrawableBarLine barline) => barLineContainer.Add(barline);
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@ -219,10 +224,10 @@ namespace osu.Game.Rulesets.Mania.UI
switch (args.Key) switch (args.Key)
{ {
case Key.Minus: case Key.Minus:
transformTimeSpanTo(TimeSpan + time_span_step, 200, EasingTypes.OutQuint); transformVisibleTimeRangeTo(visibleTimeRange + time_span_step, 200, EasingTypes.OutQuint);
break; break;
case Key.Plus: case Key.Plus:
transformTimeSpanTo(TimeSpan - time_span_step, 200, EasingTypes.OutQuint); transformVisibleTimeRangeTo(visibleTimeRange - time_span_step, 200, EasingTypes.OutQuint);
break; break;
} }
} }
@ -230,29 +235,9 @@ namespace osu.Game.Rulesets.Mania.UI
return false; return false;
} }
private double timeSpan; private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, EasingTypes easing = EasingTypes.None)
/// <summary>
/// The amount of time which the length of the playfield spans.
/// </summary>
public double TimeSpan
{ {
get { return timeSpan; } TransformTo(() => visibleTimeRange.Value, newTimeRange, duration, easing, new TransformTimeSpan());
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());
} }
protected override void Update() protected override void Update()
@ -281,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.UI
base.Apply(d); base.Apply(d);
var p = (ManiaPlayfield)d; var p = (ManiaPlayfield)d;
p.TimeSpan = (float)CurrentValue; p.visibleTimeRange.Value = (float)CurrentValue;
} }
} }
} }

View File

@ -77,8 +77,8 @@
<Compile Include="Objects\ManiaHitObject.cs" /> <Compile Include="Objects\ManiaHitObject.cs" />
<Compile Include="Objects\Note.cs" /> <Compile Include="Objects\Note.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Timing\Drawables\BasicScrollingHitObjectCollection.cs" /> <Compile Include="Timing\Drawables\BasicScrollingDrawableTimingSection.cs" />
<Compile Include="Timing\Drawables\GravityScrollingHitObjectCollection.cs" /> <Compile Include="Timing\Drawables\GravityScrollingDrawableTimingSection.cs" />
<Compile Include="Timing\ScrollingAlgorithm.cs" /> <Compile Include="Timing\ScrollingAlgorithm.cs" />
<Compile Include="UI\Column.cs" /> <Compile Include="UI\Column.cs" />
<Compile Include="UI\ManiaHitRenderer.cs" /> <Compile Include="UI\ManiaHitRenderer.cs" />
@ -87,7 +87,7 @@
<Compile Include="Mods\ManiaMod.cs" /> <Compile Include="Mods\ManiaMod.cs" />
<Compile Include="Mods\ManiaModGravity.cs" /> <Compile Include="Mods\ManiaModGravity.cs" />
<Compile Include="UI\SpecialColumnPosition.cs" /> <Compile Include="UI\SpecialColumnPosition.cs" />
<Compile Include="Timing\Drawables\DrawableManiaTimingSection.cs" /> <Compile Include="Timing\Drawables\ManiaSpeedAdjustmentContainer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj"> <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

View File

@ -1,46 +1,67 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; 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;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using osu.Framework.Configuration;
namespace osu.Game.Rulesets.Timing.Drawables namespace osu.Game.Rulesets.Timing.Drawables
{ {
/// <summary> /// <summary>
/// A container for hit objects which applies applies the speed changes defined by the <see cref="Timing.TimingSection.BeatLength"/> and <see cref="Timing.TimingSection.SpeedMultiplier"/> /// A collection of hit objects which scrolls within a <see cref="SpeedAdjustmentContainer"/>.
/// properties to its <see cref="Container{T}.Content"/> to affect the <see cref="HitObjectCollection"/> scroll speed. ///
/// <para>
/// This container handles the conversion between time and position through <see cref="Container{T}.RelativeChildSize"/> and
/// <see cref="Container{T}.RelativeChildOffset"/> such that hit objects added to this container should have time values set as their
/// positions/sizes to make proper use of this container.
/// </para>
///
/// <para>
/// 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.
/// </para>
///
/// <para>
/// This container will always be relatively-sized and positioned to its parent through the use of <see cref="Drawable.RelativeSizeAxes"/>
/// and <see cref="Drawable.RelativePositionAxes"/> such that the parent can utilise <see cref="Container{T}.RelativeChildSize"/> and
/// <see cref="Container{T}.RelativeChildOffset"/> to apply further time offsets to this collection of hit objects.
/// </para>
/// </summary> /// </summary>
public abstract class DrawableTimingSection : Container<DrawableHitObject> public abstract class DrawableTimingSection : Container<DrawableHitObject>
{ {
public readonly TimingSection TimingSection; private readonly BindableDouble visibleTimeRange = new BindableDouble();
public BindableDouble VisibleTimeRange
{
get { return visibleTimeRange; }
set { visibleTimeRange.BindTo(value); }
}
protected override Container<DrawableHitObject> Content => content; protected override IComparer<Drawable> DepthComparer => new HitObjectReverseStartTimeComparer();
private Container<DrawableHitObject> content;
private readonly Axes scrollingAxes; private readonly Axes autoSizingAxes;
private Cached layout = new Cached();
/// <summary> /// <summary>
/// Creates a new <see cref="DrawableTimingSection"/>. /// Creates a new <see cref="DrawableTimingSection"/>.
/// </summary> /// </summary>
/// <param name="timingSection">The encapsulated timing section that provides the speed changes.</param> /// <param name="autoSizingAxes">The axes on which to auto-size to the total size of items in the container.</param>
/// <param name="scrollingAxes">The axes through which this drawable timing section scrolls through.</param> protected DrawableTimingSection(Axes autoSizingAxes)
protected DrawableTimingSection(TimingSection timingSection, Axes scrollingAxes)
{ {
this.scrollingAxes = scrollingAxes; this.autoSizingAxes = autoSizingAxes;
TimingSection = timingSection; // We need a default size since RelativeSizeAxes is overridden
Size = Vector2.One;
} }
[BackgroundDependencyLoader] public override Axes AutoSizeAxes { set { throw new InvalidOperationException($"{nameof(DrawableTimingSection)} must always be relatively-sized."); } }
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 RelativeSizeAxes 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."); } set { throw new InvalidOperationException($"{nameof(DrawableTimingSection)} must always be relatively-sized."); }
} }
protected override void Update() public override Axes RelativePositionAxes
{ {
var parent = Parent as TimingSectionCollection; get { return Axes.Both; }
set { throw new InvalidOperationException($"{nameof(DrawableTimingSection)} must always be relatively-positioned."); }
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);
} }
/// <summary> public override void InvalidateFromChild(Invalidation invalidation)
/// 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. {
/// </summary> // We only want to re-compute our size when a child's size or position has changed
public bool CanContain(DrawableHitObject hitObject) => TimingSection.Time <= hitObject.HitObject.StartTime; if ((invalidation & Invalidation.RequiredParentSizeToFit) == 0)
{
base.InvalidateFromChild(invalidation);
return;
}
/// <summary> layout.Invalidate();
/// Creates the container which handles the movement of a collection of hit objects.
/// </summary> base.InvalidateFromChild(invalidation);
/// <returns>The hit object collection.</returns> }
protected abstract HitObjectCollection CreateHitObjectCollection();
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;
});
}
}
} }
} }

View File

@ -1,115 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// A collection of hit objects which scrolls within a <see cref="DrawableTimingSection"/>.
///
/// <para>
/// This container handles the conversion between time and position through <see cref="Container{T}.RelativeChildSize"/> and
/// <see cref="Container{T}.RelativeChildOffset"/> such that hit objects added to this container should have time values set as their
/// positions/sizes to make proper use of this container.
/// </para>
///
/// <para>
/// 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.
/// </para>
///
/// <para>
/// This container will always be relatively-sized and positioned to its parent through the use of <see cref="Drawable.RelativeSizeAxes"/>
/// and <see cref="Drawable.RelativePositionAxes"/> such that the parent can utilise <see cref="Container{T}.RelativeChildSize"/> and
/// <see cref="Container{T}.RelativeChildOffset"/> to apply further time offsets to this collection of hit objects.
/// </para>
/// </summary>
public abstract class HitObjectCollection : Container<DrawableHitObject>
{
protected override IComparer<Drawable> DepthComparer => new HitObjectReverseStartTimeComparer();
private readonly Axes autoSizingAxes;
private Cached layout = new Cached();
/// <summary>
/// Creates a new <see cref="HitObjectCollection"/>.
/// </summary>
/// <param name="autoSizingAxes">The axes on which to auto-size to the total size of items in the container.</param>
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;
});
}
}
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// A container for hit objects which applies applies the speed changes defined by the <see cref="Timing.SpeedAdjustment.BeatLength"/> and <see cref="Timing.SpeedAdjustment.SpeedMultiplier"/>
/// properties to its <see cref="Container{T}.Content"/> to affect the <see cref="Drawables.DrawableTimingSection"/> scroll speed.
/// </summary>
public abstract class SpeedAdjustmentContainer : Container<DrawableHitObject>
{
private readonly Bindable<double> visibleTimeRange = new Bindable<double>();
public Bindable<double> VisibleTimeRange
{
get { return visibleTimeRange; }
set { visibleTimeRange.BindTo(value); }
}
public readonly SpeedAdjustment TimingSection;
protected override Container<DrawableHitObject> Content => content;
private Container<DrawableHitObject> content;
private readonly Axes scrollingAxes;
/// <summary>
/// Creates a new <see cref="SpeedAdjustmentContainer"/>.
/// </summary>
/// <param name="timingSection">The encapsulated timing section that provides the speed changes.</param>
/// <param name="scrollingAxes">The axes through which this drawable timing section scrolls through.</param>
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);
}
/// <summary>
/// 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.
/// </summary>
public bool CanContain(DrawableHitObject hitObject) => TimingSection.Time <= hitObject.HitObject.StartTime;
/// <summary>
/// Creates the container which handles the movement of a collection of hit objects.
/// </summary>
/// <returns>The drawable timing section.</returns>
protected abstract DrawableTimingSection CreateTimingSection();
}
}

View File

@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Timing namespace osu.Game.Rulesets.Timing
{ {
public class TimingSection public class SpeedAdjustment
{ {
/// <summary> /// <summary>
/// The time in milliseconds at which this timing section starts. /// The time in milliseconds at which this timing section starts.

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -12,20 +13,24 @@ using osu.Game.Rulesets.Timing.Drawables;
namespace osu.Game.Rulesets.Timing namespace osu.Game.Rulesets.Timing
{ {
/// <summary> /// <summary>
/// A collection of timing sections which contain hit objects. /// A collection of <see cref="SpeedAdjustmentContainer"/>s.
/// ///
/// <para> /// <para>
/// This container provides <see cref="TimeSpan"/> for the timing sections. This is a value that indicates the amount of time /// This container provides <see cref="VisibleTimeRange"/> for the <see cref="SpeedAdjustmentContainer"/>s.
/// 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 <see cref="TimeSpan"/> = 1000.
/// </para> /// </para>
/// </summary> /// </summary>
public class TimingSectionCollection : Container<DrawableTimingSection> public class SpeedAdjustmentCollection : Container<SpeedAdjustmentContainer>
{ {
private readonly BindableDouble visibleTimeRange = new BindableDouble();
/// <summary> /// <summary>
/// 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 <see cref="VisibleTimeRange"/> = 1000.
/// </summary> /// </summary>
public double TimeSpan; public Bindable<double> VisibleTimeRange
{
get { return visibleTimeRange; }
set { visibleTimeRange.BindTo(value); }
}
/// <summary> /// <summary>
/// Adds a hit object to the most applicable timing section in this container. /// 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); target.Add(hitObject);
} }
public override void Add(SpeedAdjustmentContainer speedAdjustment)
{
speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange);
base.Add(speedAdjustment);
}
protected override IComparer<Drawable> DepthComparer => new TimingSectionReverseStartTimeComparer(); protected override IComparer<Drawable> DepthComparer => new TimingSectionReverseStartTimeComparer();
/// <summary> /// <summary>
@ -49,7 +60,7 @@ namespace osu.Game.Rulesets.Timing
/// </summary> /// </summary>
/// <param name="hitObject">The hit object to contain.</param> /// <param name="hitObject">The hit object to contain.</param>
/// <returns>The last (time-wise) timing section which can contain <paramref name="hitObject"/>. Null if no timing section exists.</returns> /// <returns>The last (time-wise) timing section which can contain <paramref name="hitObject"/>. Null if no timing section exists.</returns>
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();
/// <summary> /// <summary>
/// Compares two timing sections by their start time, falling back to creation order if their start time is equal. /// 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) public override int Compare(Drawable x, Drawable y)
{ {
var timingChangeX = x as DrawableTimingSection; var timingChangeX = x as SpeedAdjustmentContainer;
var timingChangeY = y as DrawableTimingSection; var timingChangeY = y as SpeedAdjustmentContainer;
// If either of the two drawables are not hit objects, fall back to the base comparer // If either of the two drawables are not hit objects, fall back to the base comparer
if (timingChangeX?.TimingSection == null || timingChangeY?.TimingSection == null) if (timingChangeX?.TimingSection == null || timingChangeY?.TimingSection == null)

View File

@ -195,10 +195,10 @@
<Compile Include="Database\RulesetDatabase.cs" /> <Compile Include="Database\RulesetDatabase.cs" />
<Compile Include="Rulesets\Scoring\Score.cs" /> <Compile Include="Rulesets\Scoring\Score.cs" />
<Compile Include="Rulesets\Scoring\ScoreProcessor.cs" /> <Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
<Compile Include="Rulesets\Timing\Drawables\SpeedAdjustmentContainer.cs" />
<Compile Include="Rulesets\Timing\Drawables\DrawableTimingSection.cs" /> <Compile Include="Rulesets\Timing\Drawables\DrawableTimingSection.cs" />
<Compile Include="Rulesets\Timing\Drawables\HitObjectCollection.cs" /> <Compile Include="Rulesets\Timing\SpeedAdjustment.cs" />
<Compile Include="Rulesets\Timing\TimingSection.cs" /> <Compile Include="Rulesets\Timing\SpeedAdjustmentCollection.cs" />
<Compile Include="Rulesets\Timing\TimingSectionCollection.cs" />
<Compile Include="Screens\Menu\MenuSideFlashes.cs" /> <Compile Include="Screens\Menu\MenuSideFlashes.cs" />
<Compile Include="Screens\Play\HUD\HealthDisplay.cs" /> <Compile Include="Screens\Play\HUD\HealthDisplay.cs" />
<Compile Include="Screens\Play\HUDOverlay.cs" /> <Compile Include="Screens\Play\HUDOverlay.cs" />