mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 09:42:54 +08:00
Implement song select v2 length and bpm statistic pill
This commit is contained in:
parent
e01e630fd7
commit
4ba629773b
@ -0,0 +1,176 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.SelectV2.Wedge;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneLengthAndBPMStatisticPill : SongSelectComponentsTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Test]
|
||||
public void TestValueColour()
|
||||
{
|
||||
AddStep("set pill", () => Child = new LocalLengthAndBPMStatisticPill
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
});
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
List<HitObject> objects = new List<HitObject>();
|
||||
for (double i = 0; i < 50000; i += 1000)
|
||||
objects.Add(new HitCircle { StartTime = i });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Length = 83000,
|
||||
OnlineID = 1,
|
||||
},
|
||||
HitObjects = objects
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("set double time", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
|
||||
|
||||
AddAssert("length value is red", () => this.ChildrenOfType<LengthAndBPMStatisticPill.PillStatistic>().ElementAt(0).ValueColour,
|
||||
() => Is.EqualTo(colours.ForModType(ModType.DifficultyIncrease)));
|
||||
AddAssert("bpm value is red", () => this.ChildrenOfType<LengthAndBPMStatisticPill.PillStatistic>().ElementAt(1).ValueColour,
|
||||
() => Is.EqualTo(colours.ForModType(ModType.DifficultyIncrease)));
|
||||
|
||||
AddStep("set half time", () => SelectedMods.Value = new[] { new OsuModHalfTime() });
|
||||
|
||||
AddAssert("length value is green", () => this.ChildrenOfType<LengthAndBPMStatisticPill.PillStatistic>().ElementAt(0).ValueColour,
|
||||
() => Is.EqualTo(colours.ForModType(ModType.DifficultyReduction)));
|
||||
AddAssert("bpm value is green", () => this.ChildrenOfType<LengthAndBPMStatisticPill.PillStatistic>().ElementAt(1).ValueColour,
|
||||
() => Is.EqualTo(colours.ForModType(ModType.DifficultyReduction)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLengthUpdates()
|
||||
{
|
||||
OsuModDoubleTime? doubleTime = null;
|
||||
|
||||
List<HitObject> objects = new List<HitObject>();
|
||||
for (double i = 0; i < 50000; i += 1000)
|
||||
objects.Add(new HitCircle { StartTime = i });
|
||||
|
||||
Beatmap beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = objects,
|
||||
};
|
||||
|
||||
double drain = beatmap.CalculateDrainLength();
|
||||
beatmap.BeatmapInfo.Length = drain;
|
||||
|
||||
AddStep("set pill", () => Child = new LocalLengthAndBPMStatisticPill
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
});
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
});
|
||||
|
||||
checkDisplayedLength(drain);
|
||||
|
||||
AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() });
|
||||
checkDisplayedLength(Math.Round(drain / 1.5f));
|
||||
|
||||
AddStep("change DT rate", () => doubleTime!.SpeedChange.Value = 2);
|
||||
checkDisplayedLength(Math.Round(drain / 2));
|
||||
}
|
||||
|
||||
private void checkDisplayedLength(double drain)
|
||||
{
|
||||
var displayedLength = drain.ToFormattedDuration();
|
||||
|
||||
AddAssert($"check map drain ({displayedLength})", () => this.ChildrenOfType<LengthAndBPMStatisticPill.PillStatistic>().ElementAt(0).Value, () => Is.EqualTo(displayedLength));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBPMUpdates()
|
||||
{
|
||||
const double bpm = 120;
|
||||
OsuModDoubleTime? doubleTime = null;
|
||||
|
||||
AddStep("set pill", () => Child = new LocalLengthAndBPMStatisticPill
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
});
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
Beatmap beatmap = new Beatmap();
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
});
|
||||
|
||||
checkDisplayedBPM($"{bpm}");
|
||||
|
||||
AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() });
|
||||
checkDisplayedBPM($"{bpm * 1.5f}");
|
||||
|
||||
AddStep("change DT rate", () => doubleTime!.SpeedChange.Value = 2);
|
||||
checkDisplayedBPM($"{bpm * 2}");
|
||||
}
|
||||
|
||||
[TestCase(120, 125, null, "120-125 (120)")]
|
||||
[TestCase(120, 120.6, null, "120-121 (120)")]
|
||||
[TestCase(120, 120.4, null, "120")]
|
||||
[TestCase(120, 120.6, "DT", "180-182 (180)")]
|
||||
[TestCase(120, 120.4, "DT", "180")]
|
||||
public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay)
|
||||
{
|
||||
AddStep("set pill", () => Child = new LocalLengthAndBPMStatisticPill
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
});
|
||||
|
||||
if (mod != null)
|
||||
AddStep($"select {mod}", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateModFromAcronym(mod) });
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
Beatmap beatmap = new Beatmap();
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm });
|
||||
beatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 60 * 1000 / otherBpm });
|
||||
beatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
});
|
||||
|
||||
checkDisplayedBPM(expectedDisplay);
|
||||
}
|
||||
|
||||
private void checkDisplayedBPM(string target)
|
||||
{
|
||||
AddAssert($"displayed bpm is {target}", () => this.ChildrenOfType<LengthAndBPMStatisticPill.PillStatistic>().ElementAt(1).Value.ToString(), () => Is.EqualTo(target));
|
||||
}
|
||||
}
|
||||
}
|
143
osu.Game/Screens/SelectV2/Wedge/LengthAndBPMStatisticPill.cs
Normal file
143
osu.Game/Screens/SelectV2/Wedge/LengthAndBPMStatisticPill.cs
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2.Wedge
|
||||
{
|
||||
public abstract partial class LengthAndBPMStatisticPill : CompositeDrawable
|
||||
{
|
||||
protected PillStatistic LengthStatistic = null!;
|
||||
protected PillStatistic BPMStatistic = null!;
|
||||
|
||||
protected LengthAndBPMStatisticPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = 20;
|
||||
Masking = true;
|
||||
CornerRadius = 10;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10),
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
LengthStatistic = new PillStatistic(new BeatmapStatistic { Name = "Length" }),
|
||||
BPMStatistic = new PillStatistic(new BeatmapStatistic { Name = BeatmapsetsStrings.ShowStatsBpm }),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public partial class PillStatistic : CompositeDrawable, IHasTooltip
|
||||
{
|
||||
private readonly BeatmapStatistic statistic;
|
||||
private OsuSpriteText valueSpriteText = null!;
|
||||
|
||||
private LocalisableString valueText;
|
||||
|
||||
public LocalisableString Value
|
||||
{
|
||||
get => valueText;
|
||||
set
|
||||
{
|
||||
valueText = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateValueText();
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 valueColour;
|
||||
|
||||
public Color4 ValueColour
|
||||
{
|
||||
get => valueColour;
|
||||
set
|
||||
{
|
||||
valueColour = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateValueText();
|
||||
}
|
||||
}
|
||||
|
||||
public PillStatistic(BeatmapStatistic statistic)
|
||||
{
|
||||
this.statistic = statistic;
|
||||
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.CentreLeft;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
valueColour = colourProvider.Content2;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = statistic.Name,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14),
|
||||
},
|
||||
valueSpriteText = new OsuSpriteText
|
||||
{
|
||||
Text = statistic.Content,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateValueText();
|
||||
}
|
||||
|
||||
private void updateValueText()
|
||||
{
|
||||
valueSpriteText.Text = LocalisableString.IsNullOrEmpty(valueText) ? "-" : valueText;
|
||||
valueSpriteText.Colour = valueColour;
|
||||
}
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2.Wedge
|
||||
{
|
||||
public partial class LocalLengthAndBPMStatisticPill : LengthAndBPMStatisticPill
|
||||
{
|
||||
private ModSettingChangeTracker? modSettingChangeTracker;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> workingBeatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
workingBeatmap.BindValueChanged(_ => updateStatistics());
|
||||
|
||||
mods.BindValueChanged(m =>
|
||||
{
|
||||
modSettingChangeTracker?.Dispose();
|
||||
|
||||
updateStatistics();
|
||||
|
||||
modSettingChangeTracker = new ModSettingChangeTracker(m.NewValue);
|
||||
modSettingChangeTracker.SettingChanged += _ => updateStatistics();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updateStatistics()
|
||||
{
|
||||
// TODO: consider mods which apply variable rates.
|
||||
double rate = 1;
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
|
||||
var beatmap = workingBeatmap.Value.Beatmap;
|
||||
|
||||
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
|
||||
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
|
||||
int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.GetMostCommonBeatLength(), rate);
|
||||
|
||||
string labelText = bpmMin == bpmMax
|
||||
? $"{bpmMin}"
|
||||
: $"{bpmMin}-{bpmMax} ({mostCommonBPM})";
|
||||
|
||||
BPMStatistic.Value = labelText;
|
||||
|
||||
double drainLength = Math.Round(beatmap.CalculateDrainLength() / rate);
|
||||
double hitLength = Math.Round(beatmap.BeatmapInfo.Length / rate);
|
||||
|
||||
LengthStatistic.Value = hitLength.ToFormattedDuration();
|
||||
LengthStatistic.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(drainLength.ToFormattedDuration());
|
||||
|
||||
BPMStatistic.ValueColour = getColourByRate(rate);
|
||||
LengthStatistic.ValueColour = getColourByRate(rate);
|
||||
}
|
||||
|
||||
private Colour4 getColourByRate(double rate)
|
||||
{
|
||||
switch (rate)
|
||||
{
|
||||
case < 1:
|
||||
return colours.ForModType(ModType.DifficultyReduction);
|
||||
|
||||
case > 1:
|
||||
return colours.ForModType(ModType.DifficultyIncrease);
|
||||
|
||||
default:
|
||||
return colourProvider.Content2;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
modSettingChangeTracker?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user