1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 10:42:54 +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 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,
Time = time

View File

@ -32,11 +32,11 @@ namespace osu.Game.Rulesets.Mania.Mods
{
var maniaHitRenderer = (ManiaHitRenderer)hitRenderer;
maniaHitRenderer.HitObjectTimingChanges = new List<DrawableTimingSection>[maniaHitRenderer.PreferredColumns];
maniaHitRenderer.BarlineTimingChanges = new List<DrawableTimingSection>();
maniaHitRenderer.HitObjectTimingChanges = new List<SpeedAdjustmentContainer>[maniaHitRenderer.PreferredColumns];
maniaHitRenderer.BarlineTimingChanges = new List<SpeedAdjustmentContainer>();
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)
{
@ -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

View File

@ -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;

View File

@ -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<double> timeSpan;
private readonly SpeedAdjustment timingSection;
public GravityScrollingHitObjectCollection(TimingSection timingSection, Func<double> 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;
}
/// <summary>
/// The acceleration due to "gravity" of the content of this container.
/// </summary>
private double acceleration => 1 / timeSpan();
private double acceleration => 1 / VisibleTimeRange;
/// <summary>
/// Computes the current time relative to <paramref name="time"/>, accounting for <see cref="timeSpan"/>.
/// </summary>
/// <param name="time">The non-offset time.</param>
/// <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
{
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);
}
}
}

View File

@ -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); }
}
/// <summary>
/// The key that will trigger input actions for this column and hit objects contained inside it.
/// </summary>
@ -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)

View File

@ -41,12 +41,12 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary>
/// Per-column timing changes.
/// </summary>
public List<DrawableTimingSection>[] HitObjectTimingChanges;
public List<SpeedAdjustmentContainer>[] HitObjectTimingChanges;
/// <summary>
/// Bar line timing changes.
/// </summary>
public List<DrawableTimingSection> BarlineTimingChanges;
public List<SpeedAdjustmentContainer> BarlineTimingChanges;
/// <summary>
/// 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<DrawableTimingSection>[PreferredColumns];
BarlineTimingChanges = new List<DrawableTimingSection>();
HitObjectTimingChanges = new List<SpeedAdjustmentContainer>[PreferredColumns];
BarlineTimingChanges = new List<SpeedAdjustmentContainer>();
for (int i = 0; i < PreferredColumns; i++)
HitObjectTimingChanges[i] = new List<DrawableTimingSection>();
HitObjectTimingChanges[i] = new List<SpeedAdjustmentContainer>();
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));
});
}

View File

@ -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<Column> columns;
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 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<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);
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;
/// <summary>
/// The amount of time which the length of the playfield spans.
/// </summary>
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;
}
}
}

View File

@ -77,8 +77,8 @@
<Compile Include="Objects\ManiaHitObject.cs" />
<Compile Include="Objects\Note.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Timing\Drawables\BasicScrollingHitObjectCollection.cs" />
<Compile Include="Timing\Drawables\GravityScrollingHitObjectCollection.cs" />
<Compile Include="Timing\Drawables\BasicScrollingDrawableTimingSection.cs" />
<Compile Include="Timing\Drawables\GravityScrollingDrawableTimingSection.cs" />
<Compile Include="Timing\ScrollingAlgorithm.cs" />
<Compile Include="UI\Column.cs" />
<Compile Include="UI\ManiaHitRenderer.cs" />
@ -87,7 +87,7 @@
<Compile Include="Mods\ManiaMod.cs" />
<Compile Include="Mods\ManiaModGravity.cs" />
<Compile Include="UI\SpecialColumnPosition.cs" />
<Compile Include="Timing\Drawables\DrawableManiaTimingSection.cs" />
<Compile Include="Timing\Drawables\ManiaSpeedAdjustmentContainer.cs" />
</ItemGroup>
<ItemGroup>
<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
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
{
/// <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"/>
/// properties to its <see cref="Container{T}.Content"/> to affect the <see cref="HitObjectCollection"/> scroll speed.
/// A collection of hit objects which scrolls within a <see cref="SpeedAdjustmentContainer"/>.
///
/// <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 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;
private Container<DrawableHitObject> content;
protected override IComparer<Drawable> DepthComparer => new HitObjectReverseStartTimeComparer();
private readonly Axes scrollingAxes;
private readonly Axes autoSizingAxes;
private Cached layout = new Cached();
/// <summary>
/// Creates a new <see cref="DrawableTimingSection"/>.
/// </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 DrawableTimingSection(TimingSection timingSection, Axes scrollingAxes)
/// <param name="autoSizingAxes">The axes on which to auto-size to the total size of items in the container.</param>
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;
get { return Axes.Both; }
set { throw new InvalidOperationException($"{nameof(DrawableTimingSection)} must always be relatively-positioned."); }
}
if (parent == null)
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;
float speedAdjustedSize = (float)(1000 / TimingSection.BeatLength / TimingSection.SpeedMultiplier);
//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;
// 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>
/// 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>
public bool CanContain(DrawableHitObject hitObject) => TimingSection.Time <= hitObject.HitObject.StartTime;
// 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;
/// <summary>
/// Creates the container which handles the movement of a collection of hit objects.
/// </summary>
/// <returns>The hit object collection.</returns>
protected abstract HitObjectCollection CreateHitObjectCollection();
// 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
{
public class TimingSection
public class SpeedAdjustment
{
/// <summary>
/// The time in milliseconds at which this timing section starts.

View File

@ -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
{
/// <summary>
/// A collection of timing sections which contain hit objects.
/// A collection of <see cref="SpeedAdjustmentContainer"/>s.
///
/// <para>
/// This container provides <see cref="TimeSpan"/> 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 <see cref="TimeSpan"/> = 1000.
/// This container provides <see cref="VisibleTimeRange"/> for the <see cref="SpeedAdjustmentContainer"/>s.
/// </para>
/// </summary>
public class TimingSectionCollection : Container<DrawableTimingSection>
public class SpeedAdjustmentCollection : Container<SpeedAdjustmentContainer>
{
private readonly BindableDouble visibleTimeRange = new BindableDouble();
/// <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>
public double TimeSpan;
public Bindable<double> VisibleTimeRange
{
get { return visibleTimeRange; }
set { visibleTimeRange.BindTo(value); }
}
/// <summary>
/// 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<Drawable> DepthComparer => new TimingSectionReverseStartTimeComparer();
/// <summary>
@ -49,7 +60,7 @@ namespace osu.Game.Rulesets.Timing
/// </summary>
/// <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>
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>
/// 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)

View File

@ -195,10 +195,10 @@
<Compile Include="Database\RulesetDatabase.cs" />
<Compile Include="Rulesets\Scoring\Score.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\HitObjectCollection.cs" />
<Compile Include="Rulesets\Timing\TimingSection.cs" />
<Compile Include="Rulesets\Timing\TimingSectionCollection.cs" />
<Compile Include="Rulesets\Timing\SpeedAdjustment.cs" />
<Compile Include="Rulesets\Timing\SpeedAdjustmentCollection.cs" />
<Compile Include="Screens\Menu\MenuSideFlashes.cs" />
<Compile Include="Screens\Play\HUD\HealthDisplay.cs" />
<Compile Include="Screens\Play\HUDOverlay.cs" />