1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 17:27:24 +08:00

Implement song select v2 length and bpm statistic pill

This commit is contained in:
Joseph Madamba 2024-08-15 21:14:23 -07:00
parent e01e630fd7
commit 4ba629773b
3 changed files with 423 additions and 0 deletions

View File

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

View 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; }
}
}
}

View File

@ -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();
}
}
}