mirror of
https://github.com/ppy/osu.git
synced 2026-05-20 19:00:15 +08:00
Merge branch 'master' into stable-hitwindow-tests
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// 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.Game.Beatmaps;
|
||||
@@ -16,26 +17,30 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
int fruits = HitObjects.Count(s => s is Fruit);
|
||||
int juiceStreams = HitObjects.Count(s => s is JuiceStream);
|
||||
int bananaShowers = HitObjects.Count(s => s is BananaShower);
|
||||
int sum = Math.Max(1, fruits + juiceStreams);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Fruit Count",
|
||||
Name = @"Fruits",
|
||||
Content = fruits.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
BarDisplayLength = fruits / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Juice Stream Count",
|
||||
Name = @"Juice Streams",
|
||||
Content = juiceStreams.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
BarDisplayLength = juiceStreams / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Banana Shower Count",
|
||||
Name = @"Banana Showers",
|
||||
Content = bananaShowers.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
BarDisplayLength = Math.Min(bananaShowers / 10f, 1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -19d, HitResult.Perfect },
|
||||
new object[] { 5f, -19.2d, HitResult.Perfect },
|
||||
new object[] { 5f, -19.38d, HitResult.Perfect },
|
||||
// new object[] { 5f, -19.4d, HitResult.Perfect }, <- in theory this should work, in practice it does not (fails even before encode & rounding due to floating point precision issues)
|
||||
new object[] { 5f, -19.44d, HitResult.Great },
|
||||
new object[] { 5f, -19.7d, HitResult.Great },
|
||||
new object[] { 5f, -20d, HitResult.Great },
|
||||
@@ -73,6 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 9.3f, 14d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 14.2d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 14.6d, HitResult.Perfect },
|
||||
// new object[] { 9.3f, 14.67d, HitResult.Perfect }, <- in theory this should work, in practice it does not (fails even before encode & rounding due to floating point precision issues)
|
||||
new object[] { 9.3f, 14.7d, HitResult.Great },
|
||||
new object[] { 9.3f, 15d, HitResult.Great },
|
||||
new object[] { 9.3f, 35d, HitResult.Great },
|
||||
|
||||
@@ -36,20 +36,23 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
{
|
||||
int notes = HitObjects.Count(s => s is Note);
|
||||
int holdNotes = HitObjects.Count(s => s is HoldNote);
|
||||
int sum = Math.Max(1, notes + holdNotes);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Note Count",
|
||||
Name = @"Notes",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = notes.ToString(),
|
||||
BarDisplayLength = notes / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hold Note Count",
|
||||
Name = @"Hold Notes",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = holdNotes.ToString(),
|
||||
BarDisplayLength = holdNotes / (float)sum,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
@@ -16,26 +16,30 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
int circles = HitObjects.Count(c => c is HitCircle);
|
||||
int sliders = HitObjects.Count(s => s is Slider);
|
||||
int spinners = HitObjects.Count(s => s is Spinner);
|
||||
int sum = Math.Max(1, circles + sliders);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = BeatmapsetsStrings.ShowStatsCountCircles,
|
||||
Name = "Circles",
|
||||
Content = circles.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
BarDisplayLength = circles / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = BeatmapsetsStrings.ShowStatsCountSliders,
|
||||
Name = "Sliders",
|
||||
Content = sliders.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
BarDisplayLength = sliders / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Spinner Count",
|
||||
Name = @"Spinners",
|
||||
Content = spinners.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
BarDisplayLength = Math.Min(spinners / 10f, 1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
var spinner = (DrawableSpinner)drawable;
|
||||
|
||||
spinner.RotationTracker.Tracking = true;
|
||||
spinner.RotationTracker.Tracking = spinner.RotationTracker.IsSpinnableTime;
|
||||
|
||||
// early-return if we were paused to avoid division-by-zero in the subsequent calculations.
|
||||
if (Precision.AlmostEquals(spinner.Clock.Rate, 0))
|
||||
|
||||
@@ -277,13 +277,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
base.Update();
|
||||
|
||||
if (HandleUserInput)
|
||||
{
|
||||
bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime;
|
||||
|
||||
RotationTracker.Tracking = !Result.HasResult
|
||||
&& correctButtonPressed()
|
||||
&& isValidSpinningTime;
|
||||
}
|
||||
RotationTracker.Tracking = RotationTracker.IsSpinnableTime && !Result.HasResult && correctButtonPressed();
|
||||
|
||||
if (spinningSample != null && spinnerFrequencyModulate)
|
||||
spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
/// <summary>
|
||||
/// Whether currently in the correct time range to allow spinning.
|
||||
/// </summary>
|
||||
private bool isSpinnableTime => drawableSpinner.HitObject.StartTime <= Time.Current && drawableSpinner.HitObject.EndTime > Time.Current;
|
||||
public bool IsSpinnableTime => drawableSpinner.HitObject.StartTime <= Time.Current && drawableSpinner.HitObject.EndTime > Time.Current;
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
lastAngle = thisAngle;
|
||||
}
|
||||
|
||||
IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation - Rotation) > 10f;
|
||||
IsSpinning.Value = IsSpinnableTime && Math.Abs(currentRotation - Rotation) > 10f;
|
||||
Rotation = (float)Interpolation.Damp(Rotation, currentRotation, 0.99, Math.Abs(Time.Elapsed));
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
/// <param name="delta">The delta angle.</param>
|
||||
public void AddRotation(float delta)
|
||||
{
|
||||
if (!isSpinnableTime)
|
||||
if (!IsSpinnableTime)
|
||||
return;
|
||||
|
||||
if (!rotationTransferred)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Game.Beatmaps;
|
||||
@@ -15,26 +16,30 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
int hits = HitObjects.Count(s => s is Hit);
|
||||
int drumRolls = HitObjects.Count(s => s is DrumRoll);
|
||||
int swells = HitObjects.Count(s => s is Swell);
|
||||
int sum = Math.Max(1, hits + drumRolls);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hit Count",
|
||||
Name = @"Hits",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = hits.ToString(),
|
||||
BarDisplayLength = hits / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Drumroll Count",
|
||||
Name = @"Drumrolls",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = drumRolls.ToString(),
|
||||
BarDisplayLength = drumRolls / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Swell Count",
|
||||
Name = @"Swells",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
Content = swells.ToString(),
|
||||
BarDisplayLength = Math.Min(swells / 10f, 1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@@ -112,5 +113,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableHitObject.IsNotNull())
|
||||
drawableHitObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
@@ -202,5 +203,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
.Then()
|
||||
.FadeEdgeEffectTo(edge_alpha_kiai, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableHitObject.IsNotNull())
|
||||
drawableHitObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
internal partial class LegacyKiaiGlow : BeatSyncedContainer
|
||||
{
|
||||
private bool isKiaiActive;
|
||||
[Resolved]
|
||||
private HealthProcessor? healthProcessor { get; set; }
|
||||
|
||||
private bool isKiaiActive;
|
||||
private Sprite sprite = null!;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(ISkinSource skin, HealthProcessor? healthProcessor)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
Child = sprite = new Sprite
|
||||
{
|
||||
@@ -33,6 +35,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
Scale = new Vector2(TaikoLegacyHitTarget.SCALE),
|
||||
Colour = new Colour4(255, 228, 0, 255),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (healthProcessor != null)
|
||||
healthProcessor.NewJudgement += onNewJudgement;
|
||||
@@ -61,5 +68,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
sprite.ScaleTo(TaikoLegacyHitTarget.SCALE + 0.15f).Then()
|
||||
.ScaleTo(TaikoLegacyHitTarget.SCALE, 80, Easing.OutQuad);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (healthProcessor != null)
|
||||
healthProcessor.NewJudgement -= onNewJudgement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public partial class TestSceneBeatmapSetOnlineStatusPill : ThemeComparisonTestScene
|
||||
{
|
||||
private bool showUnknownStatus;
|
||||
|
||||
protected override Drawable CreateContent() => new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@@ -26,12 +28,20 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
Origin = Anchor.Centre,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
ChildrenEnumerable = Enum.GetValues(typeof(BeatmapOnlineStatus)).Cast<BeatmapOnlineStatus>().Select(status => new BeatmapSetOnlineStatusPill
|
||||
ChildrenEnumerable = Enum.GetValues(typeof(BeatmapOnlineStatus)).Cast<BeatmapOnlineStatus>().Select(status => new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Status = status
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 20,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
ShowUnknownStatus = showUnknownStatus,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Status = status
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -48,6 +58,12 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
pill.Width = 90;
|
||||
}));
|
||||
|
||||
AddStep("toggle show unknown", () =>
|
||||
{
|
||||
showUnknownStatus = !showUnknownStatus;
|
||||
CreateThemedContent(OverlayColourScheme.Red);
|
||||
});
|
||||
|
||||
AddStep("unset fixed width", () => statusPills.ForEach(pill => pill.AutoSizeAxes = Axes.Both));
|
||||
}
|
||||
|
||||
@@ -65,11 +81,6 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
pill.Status = BeatmapOnlineStatus.LocallyModified;
|
||||
break;
|
||||
|
||||
// skip none
|
||||
case BeatmapOnlineStatus.LocallyModified:
|
||||
pill.Status = BeatmapOnlineStatus.Graveyard;
|
||||
break;
|
||||
|
||||
default:
|
||||
pill.Status = (pill.Status + 1);
|
||||
break;
|
||||
|
||||
@@ -15,7 +15,7 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.SelectV2.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.Metadata;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
@@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
AddStep("force transforms to finish", () => FinishTransforms(true));
|
||||
AddStep("right click second score", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<LeaderboardScoreV2>().ElementAt(1));
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<BeatmapLeaderboardScore>().ElementAt(1));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("use these mods not present",
|
||||
|
||||
@@ -105,6 +105,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMarkCompleted()
|
||||
{
|
||||
createPlaylist();
|
||||
AddStep("mark some items as complete", () =>
|
||||
{
|
||||
playlist.Items[0].MarkCompleted();
|
||||
playlist.Items[2].MarkCompleted();
|
||||
playlist.Items[3].MarkCompleted();
|
||||
playlist.Items[5].MarkCompleted();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectable()
|
||||
{
|
||||
|
||||
@@ -215,6 +215,32 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChannelCloseViaMiddleClick()
|
||||
{
|
||||
var testPMChannel = new Channel(testUser);
|
||||
|
||||
AddStep("Show overlay", () => chatOverlay.Show());
|
||||
joinTestChannel(0);
|
||||
joinChannel(testPMChannel);
|
||||
AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
|
||||
AddStep("Middle click", () =>
|
||||
{
|
||||
var item = getChannelListItem(testPMChannel);
|
||||
InputManager.MoveMouseTo(item);
|
||||
InputManager.Click(MouseButton.Middle);
|
||||
});
|
||||
AddAssert("PM channel closed", () => !channelManager.JoinedChannels.Contains(testPMChannel));
|
||||
AddStep("Select normal channel", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||
AddStep("Click close button", () =>
|
||||
{
|
||||
var item = getChannelListItem(testChannel1);
|
||||
InputManager.MoveMouseTo(item);
|
||||
InputManager.Click(MouseButton.Middle);
|
||||
});
|
||||
AddAssert("Normal channel closed", () => !channelManager.JoinedChannels.Contains(testChannel1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChannelCloseButton()
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
foreach (var rulesetInfo in rulesets.AvailableRulesets)
|
||||
{
|
||||
var instance = rulesetInfo.CreateInstance();
|
||||
var testBeatmap = createTestBeatmap(rulesetInfo);
|
||||
var testBeatmap = CreateTestBeatmap(rulesetInfo);
|
||||
|
||||
beatmaps.Add(testBeatmap);
|
||||
|
||||
@@ -124,6 +124,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTruncation()
|
||||
{
|
||||
selectBeatmap(CreateLongMetadata());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullBeatmap()
|
||||
{
|
||||
@@ -135,17 +141,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTruncation()
|
||||
{
|
||||
selectBeatmap(createLongMetadata());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBPMUpdates()
|
||||
{
|
||||
const double bpm = 120;
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
IBeatmap beatmap = CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm });
|
||||
|
||||
OsuModDoubleTime doubleTime = null!;
|
||||
@@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[TestCase(120, 120.4, "DT", "180")]
|
||||
public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay)
|
||||
{
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
IBeatmap beatmap = CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
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 });
|
||||
@@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[TestCase]
|
||||
public void TestLengthUpdates()
|
||||
{
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
IBeatmap beatmap = CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
double drain = beatmap.CalculateDrainLength();
|
||||
beatmap.BeatmapInfo.Length = drain;
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||
}
|
||||
|
||||
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
||||
public static IBeatmap CreateTestBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
List<HitObject> objects = new List<HitObject>();
|
||||
for (double i = 0; i < 50000; i += 1000)
|
||||
@@ -274,7 +274,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
};
|
||||
}
|
||||
|
||||
private IBeatmap createLongMetadata()
|
||||
public static IBeatmap CreateLongMetadata()
|
||||
{
|
||||
return new Beatmap
|
||||
{
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private PlaySongSelect songSelect = null!;
|
||||
|
||||
private LeaderboardManager leaderboardManager = null!;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
@@ -51,6 +53,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
|
||||
dependencies.CacheAs<Screens.Select.SongSelect>(songSelect = new PlaySongSelect());
|
||||
dependencies.Cache(leaderboardManager = new LeaderboardManager());
|
||||
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
return dependencies;
|
||||
@@ -60,6 +64,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(songSelect);
|
||||
LoadComponent(leaderboardManager);
|
||||
}
|
||||
|
||||
public TestSceneBeatmapLeaderboard()
|
||||
@@ -112,6 +117,27 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
checkDisplayedCount(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalScoresDisplayWorksWhenStartingOffline()
|
||||
{
|
||||
BeatmapInfo beatmapInfo = null!;
|
||||
|
||||
AddStep("Log out", () => API.Logout());
|
||||
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||
|
||||
AddStep(@"Set beatmap", () =>
|
||||
{
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||
|
||||
leaderboard.BeatmapInfo = beatmapInfo;
|
||||
});
|
||||
|
||||
clearScores();
|
||||
importMoreScores(() => beatmapInfo);
|
||||
checkDisplayedCount(10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalScoresDisplayOnBeatmapEdit()
|
||||
{
|
||||
@@ -180,8 +206,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
public void TestGlobalScoresDisplay()
|
||||
{
|
||||
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global);
|
||||
AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo())));
|
||||
AddStep(@"New Scores with teams", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo()).Select(s =>
|
||||
AddStep(@"New Scores", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo())));
|
||||
AddStep(@"New Scores with teams", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()).Select(s =>
|
||||
{
|
||||
s.User.Team = new APITeam();
|
||||
return s;
|
||||
@@ -286,7 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
AddStep(@"Import new scores", () =>
|
||||
{
|
||||
foreach (var score in generateSampleScores(beatmapInfo()))
|
||||
foreach (var score in GenerateSampleScores(beatmapInfo()))
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
}
|
||||
@@ -302,7 +328,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void checkStoredCount(int expected) =>
|
||||
AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All<ScoreInfo>().Count(s => !s.DeletePending)), () => Is.EqualTo(expected));
|
||||
|
||||
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
||||
public static ScoreInfo[] GenerateSampleScores(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
|
||||
+7
-4
@@ -16,6 +16,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Select;
|
||||
@@ -27,9 +28,9 @@ using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public abstract partial class BeatmapCarouselV2TestScene : OsuManualInputManagerTestScene
|
||||
public abstract partial class BeatmapCarouselTestScene : OsuManualInputManagerTestScene
|
||||
{
|
||||
protected readonly BindableList<BeatmapSetInfo> BeatmapSets = new BindableList<BeatmapSetInfo>();
|
||||
|
||||
@@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private int beatmapCount;
|
||||
|
||||
protected BeatmapCarouselV2TestScene()
|
||||
protected BeatmapCarouselTestScene()
|
||||
{
|
||||
store = new TestBeatmapStore
|
||||
{
|
||||
@@ -96,6 +97,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
Carousel = new BeatmapCarousel
|
||||
{
|
||||
BleedTop = 50,
|
||||
BleedBottom = 50,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 800,
|
||||
@@ -189,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||
.OrderBy(p => p.Y)
|
||||
.ElementAt(index)
|
||||
.ChildrenOfType<PanelBase>().Single()
|
||||
.ChildrenOfType<Panel>().Single()
|
||||
.TriggerClick();
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Overlays;
|
||||
@@ -20,7 +19,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
};
|
||||
|
||||
private Container? resizeContainer;
|
||||
@@ -33,15 +31,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
Width = relativeWidth,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background5,
|
||||
},
|
||||
Content
|
||||
}
|
||||
};
|
||||
@@ -55,6 +47,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
ChangeBackgroundColour(ColourProvider.Background6);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public virtual void SetUpSteps()
|
||||
{
|
||||
|
||||
+2
-2
@@ -10,13 +10,13 @@ using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
/// <summary>
|
||||
/// Covers common steps which can be used for manual testing.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2 : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarousel : BeatmapCarouselTestScene
|
||||
{
|
||||
[Test]
|
||||
[Explicit]
|
||||
+2
-2
@@ -9,10 +9,10 @@ using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2ArtistGrouping : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselArtistGrouping : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
+3
-2
@@ -5,15 +5,16 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2DifficultyGrouping : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselDifficultyGrouping : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
+3
-2
@@ -5,16 +5,17 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2NoGrouping : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselNoGrouping : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
+2
-2
@@ -8,10 +8,10 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2Scrolling : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselScrolling : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
@@ -1,83 +0,0 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2GroupPanel : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneBeatmapCarouselV2GroupPanel()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A"))
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(1, "1"))
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(3, "3")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(5, "5")),
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(7, "7")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(8, "8")),
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(9, "9")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapInfoWedge : SongSelectComponentsTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private TestBeatmapInfoWedgeV2 infoWedge = null!;
|
||||
private readonly List<IBeatmap> beatmaps = new List<IBeatmap>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
// This exists only to make the wedge more visible in the test scene
|
||||
new Box
|
||||
{
|
||||
Y = -20,
|
||||
Colour = Colour4.Cornsilk.Darken(0.2f),
|
||||
Height = BeatmapInfoWedgeV2.WEDGE_HEIGHT + 40,
|
||||
Width = 0.65f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Top = 20, Left = -10 }
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 20 },
|
||||
Child = infoWedge = new TestBeatmapInfoWedgeV2
|
||||
{
|
||||
Width = 0.6f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
AddSliderStep("change star difficulty", 0, 11.9, 5.55, v =>
|
||||
{
|
||||
foreach (var hasCurrentValue in infoWedge.ChildrenOfType<IHasCurrentValue<StarDifficulty>>())
|
||||
hasCurrentValue.Current.Value = new StarDifficulty(v, 0);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesetChange()
|
||||
{
|
||||
selectBeatmap(Beatmap.Value.Beatmap);
|
||||
|
||||
AddWaitStep("wait for select", 3);
|
||||
|
||||
foreach (var rulesetInfo in rulesets.AvailableRulesets)
|
||||
{
|
||||
var instance = rulesetInfo.CreateInstance();
|
||||
var testBeatmap = createTestBeatmap(rulesetInfo);
|
||||
|
||||
beatmaps.Add(testBeatmap);
|
||||
|
||||
setRuleset(rulesetInfo);
|
||||
|
||||
selectBeatmap(testBeatmap);
|
||||
|
||||
testBeatmapLabels(instance);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWedgeVisibility()
|
||||
{
|
||||
AddStep("hide", () => { infoWedge.Hide(); });
|
||||
AddWaitStep("wait for hide", 3);
|
||||
AddAssert("check visibility", () => infoWedge.Alpha == 0);
|
||||
AddStep("show", () => { infoWedge.Show(); });
|
||||
AddWaitStep("wait for show", 1);
|
||||
AddAssert("check visibility", () => infoWedge.Alpha > 0);
|
||||
}
|
||||
|
||||
private void testBeatmapLabels(Ruleset ruleset)
|
||||
{
|
||||
AddAssert("check title", () => infoWedge.Info!.TitleLabel.Current.Value == $"{ruleset.ShortName}Title");
|
||||
AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTruncation()
|
||||
{
|
||||
selectBeatmap(createLongMetadata());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullBeatmapWithBackground()
|
||||
{
|
||||
selectBeatmap(null);
|
||||
AddAssert("check default title", () => infoWedge.Info!.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title);
|
||||
AddAssert("check default artist", () => infoWedge.Info!.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
|
||||
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Any());
|
||||
}
|
||||
|
||||
private void setRuleset(RulesetInfo rulesetInfo)
|
||||
{
|
||||
Container? containerBefore = null;
|
||||
|
||||
AddStep("set ruleset", () =>
|
||||
{
|
||||
// wedge content is only refreshed if the ruleset changes, so only wait for load in that case.
|
||||
if (!rulesetInfo.Equals(Ruleset.Value))
|
||||
containerBefore = infoWedge.DisplayedContent;
|
||||
|
||||
Ruleset.Value = rulesetInfo;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||
}
|
||||
|
||||
private void selectBeatmap(IBeatmap? b)
|
||||
{
|
||||
Container? containerBefore = null;
|
||||
|
||||
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
|
||||
{
|
||||
containerBefore = infoWedge.DisplayedContent;
|
||||
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b);
|
||||
infoWedge.Show();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||
}
|
||||
|
||||
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
List<HitObject> objects = new List<HitObject>();
|
||||
for (double i = 0; i < 50000; i += 1000)
|
||||
objects.Add(new TestHitObject { StartTime = i });
|
||||
|
||||
return new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Author = { Username = $"{ruleset.ShortName}Author" },
|
||||
Artist = $"{ruleset.ShortName}Artist",
|
||||
Source = $"{ruleset.ShortName}Source",
|
||||
Title = $"{ruleset.ShortName}Title"
|
||||
},
|
||||
Ruleset = ruleset,
|
||||
StarRating = 6,
|
||||
DifficultyName = $"{ruleset.ShortName}Version",
|
||||
Difficulty = new BeatmapDifficulty()
|
||||
},
|
||||
HitObjects = objects
|
||||
};
|
||||
}
|
||||
|
||||
private IBeatmap createLongMetadata()
|
||||
{
|
||||
return new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Author = { Username = "WWWWWWWWWWWWWWW" },
|
||||
Artist = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist",
|
||||
Source = "Verrrrry long Source",
|
||||
Title = "Verrrrry long Title"
|
||||
},
|
||||
DifficultyName = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version",
|
||||
Status = BeatmapOnlineStatus.Graveyard,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2
|
||||
{
|
||||
public new Container? DisplayedContent => base.DisplayedContent;
|
||||
public new WedgeInfoText? Info => base.Info;
|
||||
}
|
||||
|
||||
private class TestHitObject : ConvertHitObject;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Screens.SelectV2.Wedge;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene
|
||||
{
|
||||
private DifficultyNameContent? difficultyNameContent;
|
||||
|
||||
[Test]
|
||||
public void TestLocalBeatmap()
|
||||
{
|
||||
AddStep("set component", () => Child = difficultyNameContent = new LocalDifficultyNameContent());
|
||||
|
||||
AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||
AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||
|
||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
DifficultyName = "really long difficulty name that gets truncated",
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Author = { Username = "really long username that is autosized" },
|
||||
},
|
||||
OnlineID = 1,
|
||||
}
|
||||
}));
|
||||
|
||||
AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||
AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -13,19 +13,19 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneScreenFooterButtonMods : OsuTestScene
|
||||
public partial class TestSceneFooterButtonMods : OsuTestScene
|
||||
{
|
||||
private readonly TestScreenFooterButtonMods footerButtonMods;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
public TestSceneScreenFooterButtonMods()
|
||||
public TestSceneFooterButtonMods()
|
||||
{
|
||||
Add(footerButtonMods = new TestScreenFooterButtonMods(new TestModSelectOverlay())
|
||||
{
|
||||
@@ -98,9 +98,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public void TestUnrankedBadge()
|
||||
{
|
||||
AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() }));
|
||||
AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType<ScreenFooterButtonMods.UnrankedBadge>().Single().Alpha == 1);
|
||||
AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType<FooterButtonMods.UnrankedBadge>().Single().Alpha == 1);
|
||||
AddStep(@"Clear selected mod", () => changeMods(Array.Empty<Mod>()));
|
||||
AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType<ScreenFooterButtonMods.UnrankedBadge>().Single().Alpha == 0);
|
||||
AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType<FooterButtonMods.UnrankedBadge>().Single().Alpha == 0);
|
||||
}
|
||||
|
||||
private void changeMods(IReadOnlyList<Mod> mods) => footerButtonMods.Current.Value = mods;
|
||||
@@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestScreenFooterButtonMods : ScreenFooterButtonMods
|
||||
private partial class TestScreenFooterButtonMods : FooterButtonMods
|
||||
{
|
||||
public new OsuSpriteText MultiplierText => base.MultiplierText;
|
||||
|
||||
@@ -20,7 +20,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.SelectV2.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
@@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, 2f),
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0)
|
||||
Shear = OsuGame.SHEAR,
|
||||
},
|
||||
drawWidthText = new OsuSpriteText(),
|
||||
};
|
||||
|
||||
foreach (var scoreInfo in getTestScores())
|
||||
{
|
||||
fillFlow.Add(new LeaderboardScoreV2(scoreInfo)
|
||||
fillFlow.Add(new BeatmapLeaderboardScore(scoreInfo)
|
||||
{
|
||||
Rank = scoreInfo.Position,
|
||||
IsPersonalBest = scoreInfo.User.Id == 2,
|
||||
@@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
foreach (var scoreInfo in getTestScores())
|
||||
{
|
||||
fillFlow.Add(new LeaderboardScoreV2(scoreInfo)
|
||||
fillFlow.Add(new BeatmapLeaderboardScore(scoreInfo)
|
||||
{
|
||||
Rank = scoreInfo.Position,
|
||||
IsPersonalBest = scoreInfo.User.Id == 2,
|
||||
@@ -108,7 +108,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test]
|
||||
public void TestUseTheseModsDoesNotCopySystemMods()
|
||||
{
|
||||
LeaderboardScoreV2 score = null!;
|
||||
BeatmapLeaderboardScore score = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, 2f),
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0)
|
||||
Shear = OsuGame.SHEAR,
|
||||
},
|
||||
drawWidthText = new OsuSpriteText(),
|
||||
};
|
||||
@@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Date = DateTimeOffset.Now.AddYears(-2),
|
||||
};
|
||||
|
||||
fillFlow.Add(score = new LeaderboardScoreV2(scoreInfo)
|
||||
fillFlow.Add(score = new BeatmapLeaderboardScore(scoreInfo)
|
||||
{
|
||||
Rank = scoreInfo.Position,
|
||||
Shear = Vector2.Zero,
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -18,14 +19,14 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2DifficultyPanel : ThemeComparisonTestScene
|
||||
public partial class TestScenePanelBeatmap : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapInfo beatmap = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2DifficultyPanel()
|
||||
public TestScenePanelBeatmap()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
+3
-2
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -18,14 +19,14 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2StandalonePanel : ThemeComparisonTestScene
|
||||
public partial class TestScenePanelBeatmapStandalone : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapInfo beatmap = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2StandalonePanel()
|
||||
public TestScenePanelBeatmapStandalone()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestScenePanelGroup : ThemeComparisonTestScene
|
||||
{
|
||||
public TestScenePanelGroup()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGeneral()
|
||||
{
|
||||
AddStep("general", () => CreateThemedContent(OverlayColourScheme.Aquamarine));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStars()
|
||||
{
|
||||
for (int i = 0; i <= 10; i++)
|
||||
{
|
||||
int star = i;
|
||||
|
||||
AddStep($"display {i} star(s)", () =>
|
||||
{
|
||||
ContentContainer.Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Aquamarine))
|
||||
},
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new[]
|
||||
{
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString()))
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString())),
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString())),
|
||||
Expanded = { Value = true },
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString())),
|
||||
Expanded = { Value = true },
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A"))
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
@@ -16,14 +17,14 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2SetPanel : ThemeComparisonTestScene
|
||||
public partial class TestScenePanelSet : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2SetPanel()
|
||||
public TestScenePanelSet()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
+3
-3
@@ -9,14 +9,14 @@ using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneUpdateBeatmapSetButtonV2 : OsuTestScene
|
||||
public partial class TestScenePanelUpdateBeatmapButton : OsuTestScene
|
||||
{
|
||||
private UpdateBeatmapSetButton button = null!;
|
||||
private PanelUpdateBeatmapButton button = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = button = new UpdateBeatmapSetButton
|
||||
Child = button = new PanelUpdateBeatmapButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
+5
-5
@@ -15,9 +15,9 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||
{
|
||||
@@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
screenFooter.SetButtons(new ScreenFooterButton[]
|
||||
{
|
||||
new ScreenFooterButtonMods(modOverlay) { Current = SelectedMods },
|
||||
new ScreenFooterButtonRandom(),
|
||||
new ScreenFooterButtonOptions(),
|
||||
new FooterButtonMods(modOverlay) { Current = SelectedMods },
|
||||
new FooterButtonRandom(),
|
||||
new FooterButtonOptions(),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
@@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SoloSongSelect()));
|
||||
AddStep("load screen", () => Stack.Push(new SoloSongSelect()));
|
||||
AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelect songSelect && songSelect.IsLoaded);
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
AddStep("Press F1", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ScreenFooterButtonMods>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonMods>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("Overlay visible", () => this.ChildrenOfType<ModSelectOverlay>().Single().State.Value == Visibility.Visible);
|
||||
|
||||
@@ -53,6 +53,14 @@ namespace osu.Game.Tournament
|
||||
return new ProductionEndpointConfiguration();
|
||||
}
|
||||
|
||||
public override void SetHost(GameHost host)
|
||||
{
|
||||
base.SetHost(host);
|
||||
|
||||
if (host.Window != null)
|
||||
host.Window.Title = $"{Name} [tournament client]";
|
||||
}
|
||||
|
||||
private TournamentSpriteText initialisationText = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -14,10 +14,10 @@ namespace osu.Game.Beatmaps
|
||||
/// This is a special status given when local changes are made via the editor.
|
||||
/// Once in this state, online status changes should be ignored unless the beatmap is reverted or submitted.
|
||||
/// </summary>
|
||||
[Description("Local")]
|
||||
[LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.LocallyModified))]
|
||||
LocallyModified = -4,
|
||||
|
||||
[Description("Unknown")]
|
||||
None = -3,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusGraveyard))]
|
||||
|
||||
@@ -16,7 +16,19 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public Func<Drawable> CreateIcon;
|
||||
|
||||
public string Content;
|
||||
/// <summary>
|
||||
/// The name of this statistic.
|
||||
/// </summary>
|
||||
public LocalisableString Name;
|
||||
|
||||
/// <summary>
|
||||
/// The text representing the value of this statistic.
|
||||
/// </summary>
|
||||
public string Content;
|
||||
|
||||
/// <summary>
|
||||
/// The length of a bar which visually represents this statistic's relevance in the beatmap.
|
||||
/// </summary>
|
||||
public float? BarDisplayLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,10 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public partial class BeatmapSetOnlineStatusPill : CircularContainer, IHasTooltip
|
||||
{
|
||||
private const double animation_duration = 400;
|
||||
|
||||
private BeatmapOnlineStatus status;
|
||||
/// <summary>
|
||||
/// Whether to show <see cref="BeatmapOnlineStatus.None"/> as "unknown" instead of fading out.
|
||||
/// </summary>
|
||||
public bool ShowUnknownStatus { get; init; }
|
||||
|
||||
public BeatmapOnlineStatus Status
|
||||
{
|
||||
@@ -34,30 +35,27 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
status = value;
|
||||
|
||||
if (IsLoaded)
|
||||
{
|
||||
AutoSizeDuration = (float)animation_duration;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapOnlineStatus status;
|
||||
|
||||
public float TextSize
|
||||
{
|
||||
get => statusText.Font.Size;
|
||||
set => statusText.Font = statusText.Font.With(size: value);
|
||||
init => statusText.Font = statusText.Font.With(size: value);
|
||||
}
|
||||
|
||||
public MarginPadding TextPadding
|
||||
{
|
||||
get => statusText.Padding;
|
||||
set => statusText.Padding = value;
|
||||
init => statusText.Padding = value;
|
||||
}
|
||||
|
||||
private readonly OsuSpriteText statusText;
|
||||
private readonly Box background;
|
||||
|
||||
private const double animation_duration = 400;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
@@ -66,6 +64,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
public BeatmapSetOnlineStatusPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Masking = true;
|
||||
|
||||
Alpha = 0;
|
||||
@@ -99,14 +98,27 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (Status == BeatmapOnlineStatus.None)
|
||||
if (Status == BeatmapOnlineStatus.None && !ShowUnknownStatus)
|
||||
{
|
||||
Hide();
|
||||
this.FadeOut(animation_duration, Easing.OutQuint);
|
||||
return;
|
||||
}
|
||||
|
||||
// The autosize animation on this component is intended to animate horizontal sizing only.
|
||||
// To avoid vertical autosize animating from zero to non-zero, only apply the duration
|
||||
// after we have a valid size.
|
||||
if (Height > 0)
|
||||
{
|
||||
AutoSizeDuration = (float)animation_duration;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
}
|
||||
|
||||
this.FadeIn(animation_duration, Easing.OutQuint);
|
||||
|
||||
// Handle the case where transition from hidden to non-hidden may cause
|
||||
// a fade from a colour that doesn't make sense (due to not being able to see the previous colour).
|
||||
double duration = Alpha > 0 ? animation_duration : 0;
|
||||
|
||||
Color4 statusTextColour;
|
||||
|
||||
if (colourProvider != null)
|
||||
@@ -114,8 +126,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
else
|
||||
statusTextColour = status == BeatmapOnlineStatus.Graveyard ? colours.GreySeaFoamLight : Color4.Black;
|
||||
|
||||
statusText.FadeColour(statusTextColour, animation_duration, Easing.OutQuint);
|
||||
background.FadeColour(OsuColour.ForBeatmapSetOnlineStatus(Status) ?? colourProvider?.Light1 ?? colours.GreySeaFoamLighter, animation_duration, Easing.OutQuint);
|
||||
statusText.FadeColour(statusTextColour, duration, Easing.OutQuint);
|
||||
background.FadeColour(OsuColour.ForBeatmapSetOnlineStatus(Status) ?? colourProvider?.Light1 ?? colours.GreySeaFoamLighter, duration, Easing.OutQuint);
|
||||
|
||||
statusText.Text = Status.GetLocalisableDescription().ToUpper();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Status = beatmapSet.Status,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@@ -39,8 +38,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private readonly Bindable<double> displayedStars = new BindableDouble();
|
||||
|
||||
private readonly Container textContainer;
|
||||
|
||||
/// <summary>
|
||||
/// The currently displayed stars of this display wrapped in a bindable.
|
||||
/// This bindable gets transformed on change rather than instantaneous, if animation is enabled.
|
||||
@@ -119,19 +116,14 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Size = new Vector2(8f),
|
||||
},
|
||||
Empty(),
|
||||
textContainer = new Container
|
||||
starsText = new OsuSpriteText
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = starsText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 1.5f },
|
||||
// todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f
|
||||
// see https://github.com/ppy/osu-framework/issues/3271.
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
|
||||
Shadow = false,
|
||||
},
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 1.5f },
|
||||
Spacing = new Vector2(-1.4f),
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold, fixedWidth: true),
|
||||
Shadow = false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -162,11 +154,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
starIcon.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
|
||||
starsText.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
|
||||
|
||||
// In order to avoid autosize throwing the width of these displays all over the place,
|
||||
// let's lock in some sane defaults for the text width based on how many digits we're
|
||||
// displaying.
|
||||
textContainer.Width = 24 + Math.Max(starsText.Text.ToString().Length - 4, 0) * 6;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace osu.Game.Database
|
||||
public override IBindableList<BeatmapSetInfo> GetBeatmapSets(CancellationToken? cancellationToken)
|
||||
{
|
||||
loaded.Wait(cancellationToken ?? CancellationToken.None);
|
||||
return detachedBeatmapSets.GetBoundCopy();
|
||||
lock (detachedBeatmapSets)
|
||||
return detachedBeatmapSets.GetBoundCopy();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -65,8 +66,11 @@ namespace osu.Game.Database
|
||||
{
|
||||
var detached = frozenSets.Detach();
|
||||
|
||||
detachedBeatmapSets.Clear();
|
||||
detachedBeatmapSets.AddRange(detached);
|
||||
lock (detachedBeatmapSets)
|
||||
{
|
||||
detachedBeatmapSets.Clear();
|
||||
detachedBeatmapSets.AddRange(detached);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
@@ -116,22 +120,28 @@ namespace osu.Game.Database
|
||||
if (!loaded.IsSet)
|
||||
return;
|
||||
|
||||
// If this ever leads to performance issues, we could dequeue a limited number of operations per update frame.
|
||||
while (pendingOperations.TryDequeue(out var op))
|
||||
if (pendingOperations.Count == 0)
|
||||
return;
|
||||
|
||||
lock (detachedBeatmapSets)
|
||||
{
|
||||
switch (op.Type)
|
||||
// If this ever leads to performance issues, we could dequeue a limited number of operations per update frame.
|
||||
while (pendingOperations.TryDequeue(out var op))
|
||||
{
|
||||
case OperationType.Insert:
|
||||
detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!);
|
||||
break;
|
||||
switch (op.Type)
|
||||
{
|
||||
case OperationType.Insert:
|
||||
detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!);
|
||||
break;
|
||||
|
||||
case OperationType.Update:
|
||||
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
|
||||
break;
|
||||
case OperationType.Update:
|
||||
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
|
||||
break;
|
||||
|
||||
case OperationType.Remove:
|
||||
detachedBeatmapSets.RemoveAt(op.Index);
|
||||
break;
|
||||
case OperationType.Remove:
|
||||
detachedBeatmapSets.RemoveAt(op.Index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ using osu.Game.Input.Bindings;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// A highly efficient vertical list display that is used primarily for the song select screen,
|
||||
@@ -38,12 +38,12 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// <summary>
|
||||
/// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||
/// </summary>
|
||||
public float BleedTop { get; set; } = 0;
|
||||
public float BleedTop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Height of the area below the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||
/// </summary>
|
||||
public float BleedBottom { get; set; } = 0;
|
||||
public float BleedBottom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of pixels outside the carousel's vertical bounds to manifest drawables.
|
||||
@@ -228,6 +228,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
InternalChild = Scroll = new CarouselScrollContainer
|
||||
{
|
||||
Masking = false,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
@@ -505,7 +506,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
private void scrollToSelection()
|
||||
{
|
||||
if (currentKeyboardSelection.CarouselItem != null)
|
||||
Scroll.ScrollTo(currentKeyboardSelection.CarouselItem.CarouselYPosition - visibleHalfHeight);
|
||||
Scroll.ScrollTo(currentKeyboardSelection.CarouselItem.CarouselYPosition - visibleHalfHeight + BleedTop);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -519,17 +520,17 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// <summary>
|
||||
/// The position of the lower visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleBottomBound => (float)(Scroll.Current + DrawHeight + BleedBottom);
|
||||
private float visibleBottomBound;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the upper visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleUpperBound => (float)(Scroll.Current - BleedTop);
|
||||
private float visibleUpperBound;
|
||||
|
||||
/// <summary>
|
||||
/// Half the height of the visible content.
|
||||
/// </summary>
|
||||
private float visibleHalfHeight => (DrawHeight + BleedBottom + BleedTop) / 2;
|
||||
private float visibleHalfHeight;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@@ -538,6 +539,10 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (carouselItems == null)
|
||||
return;
|
||||
|
||||
visibleBottomBound = (float)(Scroll.Current + DrawHeight + BleedBottom);
|
||||
visibleUpperBound = (float)(Scroll.Current - BleedTop);
|
||||
visibleHalfHeight = (DrawHeight + BleedBottom + BleedTop) / 2;
|
||||
|
||||
if (!selectionValid.IsValid)
|
||||
{
|
||||
refreshAfterSelection();
|
||||
@@ -582,7 +587,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
protected virtual float GetPanelXOffset(Drawable panel)
|
||||
{
|
||||
Vector2 posInScroll = Scroll.ToLocalSpace(panel.ScreenSpaceDrawQuad.Centre);
|
||||
float dist = Math.Abs(1f - posInScroll.Y / visibleHalfHeight);
|
||||
float dist = Math.Abs(1f - (posInScroll.Y + BleedTop) / visibleHalfHeight);
|
||||
|
||||
return offsetX(dist, visibleHalfHeight);
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single display item for display in a <see cref="Carousel{T}"/>.
|
||||
+1
-1
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface representing a filter operation which can be run on a <see cref="Carousel{T}"/>.
|
||||
+1
-1
@@ -5,7 +5,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface to be attached to any <see cref="Drawable"/>s which are used for display inside a <see cref="Carousel{T}"/>.
|
||||
@@ -20,10 +20,7 @@ namespace osu.Game.Graphics
|
||||
public static Color4 Gray(float amt) => new Color4(amt, amt, amt, 1f);
|
||||
public static Color4 Gray(byte amt) => new Color4(amt, amt, amt, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a given point in the star range.
|
||||
/// </summary>
|
||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[]
|
||||
public static readonly (float, Color4)[] STAR_DIFFICULTY_SPECTRUM =
|
||||
{
|
||||
(0.1f, Color4Extensions.FromHex("aaaaaa")),
|
||||
(0.1f, Color4Extensions.FromHex("4290fb")),
|
||||
@@ -37,7 +34,13 @@ namespace osu.Game.Graphics
|
||||
(6.7f, Color4Extensions.FromHex("6563de")),
|
||||
(7.7f, Color4Extensions.FromHex("18158e")),
|
||||
(9.0f, Color4.Black),
|
||||
}, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||
(10.0f, Color4.Black),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a given point in the star range.
|
||||
/// </summary>
|
||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(STAR_DIFFICULTY_SPECTRUM, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a <see cref="ScoreRank"/>.
|
||||
@@ -120,6 +123,9 @@ namespace osu.Game.Graphics
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case BeatmapOnlineStatus.None:
|
||||
return Color4.RosyBrown;
|
||||
|
||||
case BeatmapOnlineStatus.LocallyModified:
|
||||
return Color4.OrangeRed;
|
||||
|
||||
@@ -403,6 +409,12 @@ namespace osu.Game.Graphics
|
||||
public readonly Color4 Orange3 = Color4Extensions.FromHex(@"cca633");
|
||||
public readonly Color4 Orange4 = Color4Extensions.FromHex(@"6b5c2e");
|
||||
|
||||
public readonly Color4 DarkOrange0 = Color4Extensions.FromHex(@"ffbb99");
|
||||
public readonly Color4 DarkOrange1 = Color4Extensions.FromHex(@"ff9966");
|
||||
public readonly Color4 DarkOrange2 = Color4Extensions.FromHex(@"eb7e47");
|
||||
public readonly Color4 DarkOrange3 = Color4Extensions.FromHex(@"cc6633");
|
||||
public readonly Color4 DarkOrange4 = Color4Extensions.FromHex(@"6b422e");
|
||||
|
||||
public readonly Color4 Red0 = Color4Extensions.FromHex(@"ff9b9b");
|
||||
public readonly Color4 Red1 = Color4Extensions.FromHex(@"ff6666");
|
||||
public readonly Color4 Red2 = Color4Extensions.FromHex(@"eb4747");
|
||||
|
||||
@@ -15,15 +15,65 @@ namespace osu.Game.Graphics
|
||||
/// </summary>
|
||||
public const float DEFAULT_FONT_SIZE = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Template font styles which should be preferred whenever possible for UI elements.
|
||||
/// </summary>
|
||||
public static class Style
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to Torus with 32px size and semi-bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Title => GetFont(Typeface.TorusAlternate, size: 32, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 28px size and semi-bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Subtitle => GetFont(size: 28, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 22px size and bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Heading1 => GetFont(size: 22, weight: FontWeight.Bold);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 18px size and semi-bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Heading2 => GetFont(size: 18, weight: FontWeight.SemiBold);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 16px size and regular weight.
|
||||
/// </summary>
|
||||
public static FontUsage Body => GetFont(size: DEFAULT_FONT_SIZE, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 14px size and regular weight.
|
||||
/// </summary>
|
||||
public static FontUsage Caption1 => GetFont(size: 14, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 12px size and regular weight.
|
||||
/// </summary>
|
||||
public static FontUsage Caption2 => GetFont(size: 12, weight: FontWeight.Regular);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default font.
|
||||
/// </summary>
|
||||
public static FontUsage Default => GetFont();
|
||||
public static FontUsage Default => GetFont(weight: FontWeight.Medium);
|
||||
|
||||
/// <summary>
|
||||
/// Font face for numeric display.
|
||||
/// </summary>
|
||||
public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold);
|
||||
|
||||
/// <summary>
|
||||
/// Default font face for UI and game elements.
|
||||
/// </summary>
|
||||
public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Default font face with alternate character set for headings and flair text.
|
||||
/// </summary>
|
||||
public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular);
|
||||
|
||||
public static FontUsage Inter => GetFont(Typeface.Inter, weight: FontWeight.Regular);
|
||||
|
||||
@@ -115,6 +115,7 @@ namespace osu.Game.Graphics
|
||||
public static IconUsage ChangelogB => get(OsuIconMapping.ChangelogB);
|
||||
public static IconUsage Chat => get(OsuIconMapping.Chat);
|
||||
public static IconUsage CheckCircle => get(OsuIconMapping.CheckCircle);
|
||||
public static IconUsage Clock => get(OsuIconMapping.Clock);
|
||||
public static IconUsage CollapseA => get(OsuIconMapping.CollapseA);
|
||||
public static IconUsage Collections => get(OsuIconMapping.Collections);
|
||||
public static IconUsage Cross => get(OsuIconMapping.Cross);
|
||||
@@ -141,6 +142,7 @@ namespace osu.Game.Graphics
|
||||
public static IconUsage Input => get(OsuIconMapping.Input);
|
||||
public static IconUsage Maintenance => get(OsuIconMapping.Maintenance);
|
||||
public static IconUsage Megaphone => get(OsuIconMapping.Megaphone);
|
||||
public static IconUsage Metronome => get(OsuIconMapping.Metronome);
|
||||
public static IconUsage Music => get(OsuIconMapping.Music);
|
||||
public static IconUsage News => get(OsuIconMapping.News);
|
||||
public static IconUsage Next => get(OsuIconMapping.Next);
|
||||
@@ -204,6 +206,9 @@ namespace osu.Game.Graphics
|
||||
[Description(@"check-circle")]
|
||||
CheckCircle,
|
||||
|
||||
[Description(@"clock")]
|
||||
Clock,
|
||||
|
||||
[Description(@"collapse-a")]
|
||||
CollapseA,
|
||||
|
||||
@@ -282,6 +287,9 @@ namespace osu.Game.Graphics
|
||||
[Description(@"megaphone")]
|
||||
Megaphone,
|
||||
|
||||
[Description(@"metronome")]
|
||||
Metronome,
|
||||
|
||||
[Description(@"music")]
|
||||
Music,
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Radius = 5,
|
||||
},
|
||||
Colour = ButtonColour,
|
||||
Shear = new Vector2(0.2f, 0),
|
||||
Shear = OsuGame.SHEAR,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@@ -149,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
TriangleScale = 4,
|
||||
ColourDark = OsuColour.Gray(0.88f),
|
||||
Shear = new Vector2(-0.2f, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
ClampAxes = Axes.Y
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,7 +11,6 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@@ -66,8 +65,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private readonly Box background;
|
||||
private readonly OsuSpriteText text;
|
||||
|
||||
private const float shear = OsuGame.SHEAR;
|
||||
|
||||
private Colour4? darkerColour;
|
||||
private Colour4? lighterColour;
|
||||
private Colour4? textColour;
|
||||
@@ -91,10 +88,10 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public ShearedButton(float? width = null, float height = DEFAULT_HEIGHT)
|
||||
{
|
||||
Height = height;
|
||||
Padding = new MarginPadding { Horizontal = shear * height };
|
||||
Padding = new MarginPadding { Horizontal = OsuGame.SHEAR.X * height };
|
||||
|
||||
Content.CornerRadius = CORNER_RADIUS;
|
||||
Content.Shear = new Vector2(shear, 0);
|
||||
Content.Shear = OsuGame.SHEAR;
|
||||
Content.Masking = true;
|
||||
Content.Anchor = Content.Origin = Anchor.Centre;
|
||||
|
||||
@@ -117,7 +114,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(-shear, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Child = text = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.TorusAlternate.With(size: 17),
|
||||
|
||||
@@ -26,8 +26,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public const int HEIGHT = 30;
|
||||
public const float EXPANDED_SIZE = 50;
|
||||
|
||||
public static readonly Vector2 SHEAR = new Vector2(0.15f, 0);
|
||||
|
||||
private readonly Box fill;
|
||||
private readonly Container main;
|
||||
|
||||
@@ -40,7 +38,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Size = new Vector2(EXPANDED_SIZE, HEIGHT);
|
||||
InternalChild = main = new Container
|
||||
{
|
||||
Shear = SHEAR,
|
||||
Shear = OsuGame.SHEAR,
|
||||
BorderColour = Colour4.White,
|
||||
BorderThickness = BORDER_WIDTH,
|
||||
Masking = true,
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public ShearedSearchTextBox()
|
||||
{
|
||||
Height = 42;
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
Shear = OsuGame.SHEAR;
|
||||
Masking = true;
|
||||
CornerRadius = corner_radius;
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
PlaceholderText = CommonStrings.InputSearch;
|
||||
|
||||
CornerRadius = corner_radius;
|
||||
TextContainer.Shear = new Vector2(-OsuGame.SHEAR, 0);
|
||||
TextContainer.Shear = -OsuGame.SHEAR;
|
||||
}
|
||||
|
||||
protected override SpriteText CreatePlaceholder() => new SearchPlaceholder();
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public ShearedSliderBar()
|
||||
{
|
||||
Shear = SHEAR;
|
||||
Shear = OsuGame.SHEAR;
|
||||
Height = HEIGHT;
|
||||
RangePadding = EXPANDED_SIZE / 2;
|
||||
Children = new Drawable[]
|
||||
@@ -98,11 +98,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
},
|
||||
nubContainer = new Container
|
||||
{
|
||||
Shear = -SHEAR,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Nub = new ShearedNub
|
||||
{
|
||||
X = -SHEAR.X * HEIGHT / 2f,
|
||||
X = -OsuGame.SHEAR.X * HEIGHT / 2f,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true },
|
||||
|
||||
@@ -62,8 +62,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
protected partial class ShearedDropdownMenu : OsuDropdown<T>.OsuDropdownMenu
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => base.Padding;
|
||||
@@ -72,7 +70,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public ShearedDropdownMenu()
|
||||
{
|
||||
Shear = shear;
|
||||
Shear = OsuGame.SHEAR;
|
||||
Margin = new MarginPadding { Top = 5f };
|
||||
}
|
||||
|
||||
@@ -84,12 +82,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public partial class ShearedMenuItem : DrawableOsuDropdownMenuItem
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
public ShearedMenuItem(MenuItem item)
|
||||
: base(item)
|
||||
{
|
||||
Foreground.Shear = -shear;
|
||||
Foreground.Shear = -OsuGame.SHEAR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,14 +121,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
public ShearedDropdown<T> Dropdown = null!;
|
||||
private ShearedDropdownSearchBar searchBar = null!;
|
||||
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public ShearedDropdownHeader()
|
||||
{
|
||||
Shear = shear;
|
||||
Shear = OsuGame.SHEAR;
|
||||
CornerRadius = corner_radius;
|
||||
Masking = true;
|
||||
|
||||
@@ -167,7 +161,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 8f },
|
||||
Font = OsuFont.Torus.With(size: 16.8f, weight: FontWeight.SemiBold),
|
||||
Shear = -shear,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -178,7 +172,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 10f },
|
||||
Shear = -shear,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
valueText = new TruncatingSpriteText
|
||||
@@ -286,12 +280,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private partial class DropdownSearchTextBox : OsuTextBox
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider? colourProvider)
|
||||
{
|
||||
TextContainer.Shear = -shear;
|
||||
TextContainer.Shear = -OsuGame.SHEAR;
|
||||
BackgroundUnfocused = colourProvider?.Background5 ?? new Color4(10, 10, 10, 255);
|
||||
BackgroundFocused = colourProvider?.Background5 ?? new Color4(10, 10, 10, 255);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class DrawableRoomPlaylistItemStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.DrawableRoomPlaylistItem";
|
||||
|
||||
/// <summary>
|
||||
/// "You have completed this beatmap"
|
||||
/// </summary>
|
||||
public static LocalisableString CompletedTooltip => new TranslatableString(getKey(@"completed_tooltip"), @"You have completed this beatmap");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace osu.Game.Online.API.Requests
|
||||
this.mods = mods ?? Array.Empty<IMod>();
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/solo-scores{createQueryParameters()}";
|
||||
protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}";
|
||||
|
||||
private string createQueryParameters()
|
||||
{
|
||||
|
||||
@@ -32,6 +32,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"playcount")]
|
||||
public int PlayCount { get; set; }
|
||||
|
||||
[JsonProperty(@"current_user_playcount")]
|
||||
public int UserPlayCount { get; set; }
|
||||
|
||||
[JsonProperty(@"passcount")]
|
||||
public int PassCount { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public partial class LeaderboardManager : Component
|
||||
{
|
||||
public IBindable<LeaderboardScores?> Scores => scores;
|
||||
private readonly Bindable<LeaderboardScores?> scores = new Bindable<LeaderboardScores?>();
|
||||
|
||||
public LeaderboardCriteria? CurrentCriteria { get; private set; }
|
||||
|
||||
private IDisposable? localScoreSubscription;
|
||||
private TaskCompletionSource<LeaderboardScores?>? localFetchCompletionSource;
|
||||
private TaskCompletionSource<LeaderboardScores?>? lastFetchCompletionSource;
|
||||
private GetScoresRequest? inFlightOnlineRequest;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
public Task<LeaderboardScores?> FetchWithCriteriaAsync(LeaderboardCriteria newCriteria)
|
||||
{
|
||||
if (CurrentCriteria?.Equals(newCriteria) == true && lastFetchCompletionSource?.Task.IsFaulted == false)
|
||||
return lastFetchCompletionSource?.Task ?? Task.FromResult(Scores.Value);
|
||||
|
||||
CurrentCriteria = newCriteria;
|
||||
localScoreSubscription?.Dispose();
|
||||
inFlightOnlineRequest?.Cancel();
|
||||
lastFetchCompletionSource?.TrySetCanceled();
|
||||
scores.Value = null;
|
||||
|
||||
switch (newCriteria.Scope)
|
||||
{
|
||||
case BeatmapLeaderboardScope.Local:
|
||||
{
|
||||
// this task completion source will be marked completed in the `localScoresChanged()` below.
|
||||
// yes it's twisty, but such are the costs of trying to reconcile data-push / subscription and data-pull / explicit fetch flows.
|
||||
lastFetchCompletionSource = localFetchCompletionSource = new TaskCompletionSource<LeaderboardScores?>();
|
||||
localScoreSubscription = realm.RegisterForNotifications(r =>
|
||||
r.All<ScoreInfo>().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0"
|
||||
+ $" AND {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
|
||||
+ $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1"
|
||||
+ $" AND {nameof(ScoreInfo.DeletePending)} == false"
|
||||
, newCriteria.Beatmap.ID, newCriteria.Ruleset.ShortName), localScoresChanged);
|
||||
return localFetchCompletionSource.Task;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
var onlineFetchCompletionSource = new TaskCompletionSource<LeaderboardScores?>();
|
||||
lastFetchCompletionSource = onlineFetchCompletionSource;
|
||||
|
||||
IReadOnlyList<Mod>? requestMods = null;
|
||||
|
||||
if (newCriteria.ExactMods != null)
|
||||
{
|
||||
if (!newCriteria.ExactMods.Any())
|
||||
// add nomod for the request
|
||||
requestMods = new Mod[] { new ModNoMod() };
|
||||
else
|
||||
requestMods = newCriteria.ExactMods;
|
||||
}
|
||||
|
||||
var newRequest = new GetScoresRequest(newCriteria.Beatmap, newCriteria.Ruleset, newCriteria.Scope, requestMods);
|
||||
newRequest.Success += response =>
|
||||
{
|
||||
if (inFlightOnlineRequest != null && !newRequest.Equals(inFlightOnlineRequest))
|
||||
return;
|
||||
|
||||
var result = new LeaderboardScores
|
||||
(
|
||||
response.Scores.Select(s => s.ToScoreInfo(rulesets, newCriteria.Beatmap)).OrderByTotalScore(),
|
||||
response.UserScore?.CreateScoreInfo(rulesets, newCriteria.Beatmap)
|
||||
);
|
||||
inFlightOnlineRequest = null;
|
||||
if (onlineFetchCompletionSource.TrySetResult(result))
|
||||
scores.Value = result;
|
||||
};
|
||||
newRequest.Failure += ex => onlineFetchCompletionSource.TrySetException(ex);
|
||||
api.Queue(inFlightOnlineRequest = newRequest);
|
||||
return onlineFetchCompletionSource.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void localScoresChanged(IRealmCollection<ScoreInfo> sender, ChangeSet? changes)
|
||||
{
|
||||
Debug.Assert(CurrentCriteria != null);
|
||||
|
||||
// This subscription may fire from changes to linked beatmaps, which we don't care about.
|
||||
// It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications.
|
||||
if (changes?.HasCollectionChanges() == false)
|
||||
return;
|
||||
|
||||
var newScores = sender.AsEnumerable();
|
||||
|
||||
if (CurrentCriteria.ExactMods != null)
|
||||
{
|
||||
if (!CurrentCriteria.ExactMods.Any())
|
||||
{
|
||||
// we need to filter out all scores that have any mods to get all local nomod scores
|
||||
newScores = newScores.Where(s => !s.Mods.Any());
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise find all the scores that have all of the currently selected mods (similar to how web applies mod filters)
|
||||
// we're creating and using a string HashSet representation of selected mods so that it can be translated into the DB query itself
|
||||
var selectedMods = CurrentCriteria.ExactMods.Select(m => m.Acronym).ToHashSet();
|
||||
|
||||
newScores = newScores.Where(s => selectedMods.SetEquals(s.Mods.Select(m => m.Acronym)));
|
||||
}
|
||||
}
|
||||
|
||||
newScores = newScores.Detach().OrderByTotalScore();
|
||||
|
||||
scores.Value = new LeaderboardScores(newScores, null);
|
||||
|
||||
if (localFetchCompletionSource != null && localFetchCompletionSource == lastFetchCompletionSource)
|
||||
{
|
||||
localFetchCompletionSource.SetResult(scores.Value);
|
||||
localFetchCompletionSource = lastFetchCompletionSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record LeaderboardCriteria(
|
||||
BeatmapInfo Beatmap,
|
||||
RulesetInfo Ruleset,
|
||||
BeatmapLeaderboardScope Scope,
|
||||
Mod[]? ExactMods
|
||||
);
|
||||
|
||||
public record LeaderboardScores(IEnumerable<ScoreInfo> TopScores, ScoreInfo? UserScore)
|
||||
{
|
||||
public IEnumerable<ScoreInfo> AllScores
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var score in TopScores)
|
||||
yield return score;
|
||||
|
||||
if (UserScore != null && TopScores.All(topScore => !topScore.Equals(UserScore) && !topScore.MatchesOnlineID(UserScore)))
|
||||
yield return UserScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,22 @@ namespace osu.Game.Online.Rooms
|
||||
/// </summary>
|
||||
public class ItemAttemptsCount
|
||||
{
|
||||
/// <summary>
|
||||
/// The playlist item this object describes.
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int PlaylistItemID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of times the user attempted the playlist item.
|
||||
/// </summary>
|
||||
[JsonProperty("attempts")]
|
||||
public int Attempts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user has a passing score on the playlist item.
|
||||
/// </summary>
|
||||
[JsonProperty("passed")]
|
||||
public bool Passed { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,11 @@ namespace osu.Game.Online.Rooms
|
||||
|
||||
private readonly Bindable<bool> valid = new BindableBool(true);
|
||||
|
||||
[JsonIgnore]
|
||||
public IBindable<bool> Completed => completed;
|
||||
|
||||
private readonly Bindable<bool> completed = new BindableBool(false);
|
||||
|
||||
[JsonConstructor]
|
||||
private PlaylistItem()
|
||||
: this(new APIBeatmap())
|
||||
@@ -118,6 +123,8 @@ namespace osu.Game.Online.Rooms
|
||||
|
||||
public void MarkInvalid() => valid.Value = false;
|
||||
|
||||
public void MarkCompleted() => completed.Value = true;
|
||||
|
||||
#region Newtonsoft.Json implicit ShouldSerialize() methods
|
||||
|
||||
// The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.
|
||||
|
||||
+26
-1
@@ -47,6 +47,7 @@ using osu.Game.IO;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
@@ -67,6 +68,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Seasonal;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Updater;
|
||||
@@ -100,7 +102,12 @@ namespace osu.Game
|
||||
/// <summary>
|
||||
/// A common shear factor applied to most components of the game.
|
||||
/// </summary>
|
||||
public const float SHEAR = 0.2f;
|
||||
public static readonly Vector2 SHEAR = new Vector2(0.2f, 0);
|
||||
|
||||
/// <summary>
|
||||
/// For elements placed close to the screen edge, this is the margin to leave to the edge.
|
||||
/// </summary>
|
||||
public const float SCREEN_EDGE_MARGIN = 12f;
|
||||
|
||||
public Toolbar Toolbar { get; private set; }
|
||||
|
||||
@@ -784,6 +791,24 @@ namespace osu.Game
|
||||
if (!Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap))
|
||||
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
|
||||
|
||||
var currentLeaderboard = LeaderboardManager.CurrentCriteria;
|
||||
|
||||
bool leaderboardBeatmapMatches = currentLeaderboard != null && databasedBeatmap.Equals(currentLeaderboard.Beatmap);
|
||||
bool leaderboardRulesetMatches = currentLeaderboard != null && databasedScore.ScoreInfo.Ruleset.Equals(currentLeaderboard.Ruleset);
|
||||
|
||||
if (!leaderboardBeatmapMatches || !leaderboardRulesetMatches)
|
||||
{
|
||||
var newLeaderboard = currentLeaderboard != null
|
||||
? currentLeaderboard with { Beatmap = databasedBeatmap, Ruleset = databasedScore.ScoreInfo.Ruleset }
|
||||
: new LeaderboardCriteria(databasedBeatmap, databasedScore.ScoreInfo.Ruleset, BeatmapLeaderboardScope.Global, null);
|
||||
LeaderboardManager.FetchWithCriteriaAsync(newLeaderboard)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.Exception != null)
|
||||
Logger.Log($@"Failed to fetch leaderboards when displaying results: {t.Exception}", LoggingTarget.Network);
|
||||
});
|
||||
}
|
||||
|
||||
switch (presentType)
|
||||
{
|
||||
case ScorePresentType.Gameplay:
|
||||
|
||||
@@ -49,6 +49,7 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Spectator;
|
||||
@@ -203,6 +204,7 @@ namespace osu.Game
|
||||
|
||||
private UserLookupCache userCache;
|
||||
private BeatmapLookupCache beatmapCache;
|
||||
protected LeaderboardManager LeaderboardManager { get; private set; }
|
||||
|
||||
private RulesetConfigCache rulesetConfigCache;
|
||||
|
||||
@@ -365,6 +367,9 @@ namespace osu.Game
|
||||
dependencies.CacheAs<IBindable<WorkingBeatmap>>(Beatmap);
|
||||
dependencies.CacheAs(Beatmap);
|
||||
|
||||
dependencies.Cache(LeaderboardManager = new LeaderboardManager());
|
||||
base.Content.Add(LeaderboardManager);
|
||||
|
||||
// add api components to hierarchy.
|
||||
if (API is APIAccess apiAccess)
|
||||
base.Content.Add(apiAccess);
|
||||
|
||||
@@ -177,7 +177,6 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
onlineStatusPill = new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
TextSize = 14,
|
||||
|
||||
@@ -18,6 +18,7 @@ using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Chat.Listing;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Chat.ChannelList
|
||||
{
|
||||
@@ -160,6 +161,17 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Middle)
|
||||
{
|
||||
close?.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
private ChannelListItemCloseButton? createCloseButton()
|
||||
{
|
||||
if (isSelector || !CanLeave)
|
||||
|
||||
@@ -20,7 +20,6 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
@@ -66,21 +65,19 @@ namespace osu.Game.Overlays.Mods
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const float shear = OsuGame.SHEAR;
|
||||
|
||||
LeftContent.AddRange(new Drawable[]
|
||||
{
|
||||
starRatingDisplay = new StarRatingDisplay(default, animated: true)
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Shear = new Vector2(-shear, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
},
|
||||
bpmDisplay = new BPMDisplay
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Shear = new Vector2(-shear, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 75,
|
||||
}
|
||||
@@ -89,10 +86,10 @@ namespace osu.Game.Overlays.Mods
|
||||
RightContent.Alpha = 0;
|
||||
RightContent.AddRange(new Drawable[]
|
||||
{
|
||||
circleSizeDisplay = new VerticalAttributeDisplay("CS") { Shear = new Vector2(-shear, 0), },
|
||||
drainRateDisplay = new VerticalAttributeDisplay("HP") { Shear = new Vector2(-shear, 0), },
|
||||
overallDifficultyDisplay = new VerticalAttributeDisplay("OD") { Shear = new Vector2(-shear, 0), },
|
||||
approachRateDisplay = new VerticalAttributeDisplay("AR") { Shear = new Vector2(-shear, 0), },
|
||||
circleSizeDisplay = new VerticalAttributeDisplay("CS") { Shear = -OsuGame.SHEAR, },
|
||||
drainRateDisplay = new VerticalAttributeDisplay("HP") { Shear = -OsuGame.SHEAR, },
|
||||
overallDifficultyDisplay = new VerticalAttributeDisplay("OD") { Shear = -OsuGame.SHEAR, },
|
||||
approachRateDisplay = new VerticalAttributeDisplay("AR") { Shear = -OsuGame.SHEAR, },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.8f),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0)
|
||||
Shear = -OsuGame.SHEAR
|
||||
});
|
||||
ItemsFlow.Padding = new MarginPadding
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Anchor = Anchor.BottomRight,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Height = ShearedButton.DEFAULT_HEIGHT,
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||
Shear = OsuGame.SHEAR,
|
||||
CornerRadius = ShearedButton.CORNER_RADIUS,
|
||||
BorderThickness = ShearedButton.BORDER_THICKNESS,
|
||||
Masking = true,
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Active = { BindTarget = Active },
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
Width = WIDTH;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
Shear = OsuGame.SHEAR;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Velocity = 0.7f,
|
||||
ClampAxes = Axes.Y
|
||||
},
|
||||
@@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Mods
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 17,
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||
Shear = OsuGame.SHEAR,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Horizontal = 70 },
|
||||
@@ -726,7 +726,7 @@ namespace osu.Game.Overlays.Mods
|
||||
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
|
||||
// so we have to manually compensate.
|
||||
var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent);
|
||||
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * OsuGame.SHEAR, 0), ScrollContent);
|
||||
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * OsuGame.SHEAR.X, 0), ScrollContent);
|
||||
|
||||
bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound)
|
||||
&& Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X);
|
||||
|
||||
@@ -20,7 +20,6 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -87,7 +86,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Content.CornerRadius = CORNER_RADIUS;
|
||||
Content.BorderThickness = 2;
|
||||
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
Shear = OsuGame.SHEAR;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -128,10 +127,10 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Left = -18 * OsuGame.SHEAR
|
||||
Left = -18 * OsuGame.SHEAR.X
|
||||
},
|
||||
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||
},
|
||||
@@ -139,7 +138,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
@@ -52,7 +51,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||
Shear = OsuGame.SHEAR,
|
||||
CornerRadius = ShearedButton.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
@@ -79,7 +78,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
||||
}
|
||||
}
|
||||
@@ -94,7 +93,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Origin = Anchor.Centre,
|
||||
Child = counter = new EffectCounter
|
||||
{
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = { BindTarget = ModMultiplier }
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||
Keywords = new[] { "converts", "converted" }
|
||||
},
|
||||
new SettingsEnumDropdown<RandomSelectAlgorithm>
|
||||
{
|
||||
|
||||
@@ -54,6 +54,23 @@ namespace osu.Game.Rulesets.Scoring
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the average hit offset/error for a sequence of <see cref="HitEvent"/>s, where negative numbers mean the user hit too early on average.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A non-null <see langword="double"/> value if unstable rate could be calculated,
|
||||
/// and <see langword="null"/> if unstable rate cannot be calculated due to <paramref name="hitEvents"/> being empty.
|
||||
/// </returns>
|
||||
public static double? CalculateAverageHitError(this IEnumerable<HitEvent> hitEvents)
|
||||
{
|
||||
double[] timeOffsets = hitEvents.Where(AffectsUnstableRate).Select(ev => ev.TimeOffset).ToArray();
|
||||
|
||||
if (timeOffsets.Length == 0)
|
||||
return null;
|
||||
|
||||
return timeOffsets.Average();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the median hit offset/error for a sequence of <see cref="HitEvent"/>s, where negative numbers mean the user hit too early on average.
|
||||
/// </summary>
|
||||
|
||||
@@ -104,14 +104,14 @@ namespace osu.Game.Screens.Footer
|
||||
},
|
||||
BackButton = new ScreenBackButton
|
||||
{
|
||||
Margin = new MarginPadding { Bottom = 15f, Left = 12f },
|
||||
Margin = new MarginPadding { Bottom = OsuGame.SCREEN_EDGE_MARGIN, Left = OsuGame.SCREEN_EDGE_MARGIN },
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = onBackPressed,
|
||||
},
|
||||
hiddenButtonsContainer = new Container<ScreenFooterButton>
|
||||
{
|
||||
Margin = new MarginPadding { Left = 12f + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
Margin = new MarginPadding { Left = OsuGame.SCREEN_EDGE_MARGIN + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
Y = 10f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
|
||||
@@ -25,16 +25,12 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private const float shear = OsuGame.SHEAR;
|
||||
|
||||
protected const int CORNER_RADIUS = 10;
|
||||
protected const int BUTTON_HEIGHT = 75;
|
||||
protected const int BUTTON_WIDTH = 116;
|
||||
|
||||
public Bindable<Visibility> OverlayState = new Bindable<Visibility>();
|
||||
|
||||
protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear, 0);
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
@@ -89,7 +85,7 @@ namespace osu.Game.Screens.Footer
|
||||
Colour = Colour4.Black.Opacity(0.25f),
|
||||
Offset = new Vector2(0, 2),
|
||||
},
|
||||
Shear = BUTTON_SHEAR,
|
||||
Shear = OsuGame.SHEAR,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -108,7 +104,7 @@ namespace osu.Game.Screens.Footer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -135,7 +131,7 @@ namespace osu.Game.Screens.Footer
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = -CORNER_RADIUS,
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0f),
|
||||
Shear = OsuGame.SHEAR,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
titleContainer = new Container
|
||||
@@ -147,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Today's Challenge",
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate),
|
||||
},
|
||||
}
|
||||
@@ -173,7 +173,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
Origin = Anchor.Centre,
|
||||
Text = room.Name.Split(':', StringSplitOptions.TrimEntries).Last(),
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate),
|
||||
},
|
||||
}
|
||||
@@ -246,7 +246,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
MaxWidth = horizontal_info_size,
|
||||
Text = beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false),
|
||||
Padding = new MarginPadding { Horizontal = 5f },
|
||||
@@ -257,7 +257,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
Text = $"Difficulty: {beatmap.DifficultyName}",
|
||||
Font = OsuFont.GetFont(size: 20, italics: true),
|
||||
MaxWidth = horizontal_info_size,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
@@ -266,13 +266,13 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
Text = $"by {beatmap.Metadata.Author.Username}",
|
||||
Font = OsuFont.GetFont(size: 16, italics: true),
|
||||
MaxWidth = horizontal_info_size,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
starRatingDisplay = new StarRatingDisplay(default)
|
||||
{
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Margin = new MarginPadding(5),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
@@ -301,7 +301,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Current =
|
||||
{
|
||||
Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray()
|
||||
@@ -329,7 +329,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fit,
|
||||
Scale = new Vector2(1.2f),
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
}, c =>
|
||||
{
|
||||
beatmapBackground.Add(c);
|
||||
|
||||
@@ -17,7 +17,7 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.SelectV2.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
private readonly Room room;
|
||||
private readonly PlaylistItem playlistItem;
|
||||
|
||||
private FillFlowContainer<LeaderboardScoreV2> scoreFlow = null!;
|
||||
private FillFlowContainer<BeatmapLeaderboardScore> scoreFlow = null!;
|
||||
private Container userBestContainer = null!;
|
||||
private SectionHeader userBestHeader = null!;
|
||||
private LoadingLayer loadingLayer = null!;
|
||||
@@ -91,7 +91,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = scoreFlow = new FillFlowContainer<LeaderboardScoreV2>
|
||||
Child = scoreFlow = new FillFlowContainer<BeatmapLeaderboardScore>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@@ -158,7 +158,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadComponentsAsync(best.Select((s, index) => new LeaderboardScoreV2(s, sheared: false)
|
||||
LoadComponentsAsync(best.Select((s, index) => new BeatmapLeaderboardScore(s, sheared: false)
|
||||
{
|
||||
Rank = index + 1,
|
||||
IsPersonalBest = s.UserID == api.LocalUser.Value.Id,
|
||||
@@ -178,7 +178,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
|
||||
if (userBest != null)
|
||||
{
|
||||
userBestContainer.Add(new LeaderboardScoreV2(userBest, sheared: false)
|
||||
userBestContainer.Add(new BeatmapLeaderboardScore(userBest, sheared: false)
|
||||
{
|
||||
Rank = userBest.Position,
|
||||
IsPersonalBest = true,
|
||||
|
||||
@@ -31,13 +31,13 @@ using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapSet;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
@@ -76,6 +76,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
private readonly DelayedLoadWrapper onScreenLoader;
|
||||
private readonly IBindable<bool> valid = new Bindable<bool>();
|
||||
private readonly IBindable<bool> completed = new Bindable<bool>();
|
||||
|
||||
private IBeatmapInfo? beatmap;
|
||||
private IRulesetInfo? ruleset;
|
||||
@@ -128,6 +129,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
Item = item;
|
||||
|
||||
valid.BindTo(item.Valid);
|
||||
completed.BindTo(item.Completed);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -525,9 +527,27 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
private IEnumerable<Drawable> createButtons() => new[]
|
||||
{
|
||||
beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap),
|
||||
new CompletionIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Visible = { BindTarget = completed }
|
||||
},
|
||||
beatmap == null
|
||||
? Empty().With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.Origin = Anchor.Centre;
|
||||
})
|
||||
: new PlaylistDownloadButton(beatmap)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(30, 30),
|
||||
Action = () => RequestResults?.Invoke(Item),
|
||||
Alpha = AllowShowingResults ? 1 : 0,
|
||||
@@ -535,13 +555,17 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
},
|
||||
editButton = new PlaylistEditButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(30, 30),
|
||||
Alpha = AllowEditing ? 1 : 0,
|
||||
Action = () => RequestEdit?.Invoke(Item),
|
||||
TooltipText = CommonStrings.ButtonsEdit
|
||||
TooltipText = Resources.Localisation.Web.CommonStrings.ButtonsEdit
|
||||
},
|
||||
removeButton = new PlaylistRemoveButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(30, 30),
|
||||
Alpha = AllowDeletion ? 1 : 0,
|
||||
Action = () => RequestDeletion?.Invoke(Item),
|
||||
@@ -768,5 +792,64 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
this.allowInteraction = allowInteraction;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class CompletionIcon : CompositeDrawable, IHasTooltip
|
||||
{
|
||||
public readonly BindableBool Visible = new BindableBool();
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(16),
|
||||
Masking = true,
|
||||
Colour = colours.Lime0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(0.5f),
|
||||
Colour = OsuColour.Gray(0.5f),
|
||||
Icon = FontAwesome.Solid.Check
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Visible.BindValueChanged(onVisibleChanged, true);
|
||||
}
|
||||
|
||||
private void onVisibleChanged(ValueChangedEvent<bool> visible)
|
||||
{
|
||||
if (visible.NewValue)
|
||||
{
|
||||
Size = new Vector2(16);
|
||||
Alpha = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Size = Vector2.Zero;
|
||||
Alpha = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public LocalisableString TooltipText => DrawableRoomPlaylistItemStrings.CompletedTooltip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,6 +465,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
});
|
||||
|
||||
updateSetupState();
|
||||
updateUserScore();
|
||||
updateGameplayState();
|
||||
}
|
||||
|
||||
@@ -480,6 +481,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
case nameof(Room.RoomID):
|
||||
updateSetupState();
|
||||
break;
|
||||
|
||||
case nameof(Room.UserScore):
|
||||
updateUserScore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,12 +512,32 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
progressSection.Alpha = room.MaxAttempts != null ? 1 : 0;
|
||||
drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, room.Playlist);
|
||||
|
||||
updateUserScore();
|
||||
|
||||
// Select an initial item for the user to help them get into a playable state quicker.
|
||||
SelectedItem.Value = room.Playlist.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responds to changes in <see cref="Room.UserScore"/> to mark playlist items as completed.
|
||||
/// </summary>
|
||||
private void updateUserScore()
|
||||
{
|
||||
if (room.UserScore == null)
|
||||
return;
|
||||
|
||||
if (drawablePlaylist.Items.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var item in room.UserScore.PlaylistItemAttempts)
|
||||
{
|
||||
if (item.Passed)
|
||||
drawablePlaylist.Items.Single(i => i.ID == item.PlaylistItemID).MarkCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the rate at which the <see cref="Room"/> is updated.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
@@ -59,6 +60,12 @@ namespace osu.Game.Screens.Play
|
||||
this.createScore = createScore;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private LeaderboardManager leaderboardManager { get; set; } = null!;
|
||||
|
||||
private readonly IBindable<LeaderboardScores> globalScores = new Bindable<LeaderboardScores>();
|
||||
private readonly BindableList<ScoreInfo> localScores = new BindableList<ScoreInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Add a settings group to the HUD overlay. Intended to be used by rulesets to add replay-specific settings.
|
||||
/// </summary>
|
||||
@@ -87,6 +94,20 @@ namespace osu.Game.Screens.Play
|
||||
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
globalScores.BindTo(leaderboardManager.Scores);
|
||||
globalScores.BindValueChanged(_ =>
|
||||
{
|
||||
localScores.Clear();
|
||||
|
||||
if (globalScores.Value is LeaderboardScores g)
|
||||
localScores.AddRange(g.AllScores.OrderByTotalScore());
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void PrepareReplay()
|
||||
{
|
||||
DrawableRuleset?.SetReplayScore(Score);
|
||||
@@ -97,13 +118,11 @@ namespace osu.Game.Screens.Play
|
||||
// Don't re-import replay scores as they're already present in the database.
|
||||
protected override Task ImportScore(Score score) => Task.CompletedTask;
|
||||
|
||||
public readonly BindableList<ScoreInfo> LeaderboardScores = new BindableList<ScoreInfo>();
|
||||
|
||||
protected override GameplayLeaderboard CreateGameplayLeaderboard() =>
|
||||
new SoloGameplayLeaderboard(Score.ScoreInfo.User)
|
||||
{
|
||||
AlwaysVisible = { Value = true },
|
||||
Scores = { BindTarget = LeaderboardScores }
|
||||
Scores = { BindTarget = localScores }
|
||||
};
|
||||
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score)
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Scoring;
|
||||
@@ -29,6 +31,26 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private LeaderboardManager leaderboardManager { get; set; } = null!;
|
||||
|
||||
private readonly IBindable<LeaderboardScores> globalScores = new Bindable<LeaderboardScores>();
|
||||
private readonly BindableList<ScoreInfo> localScores = new BindableList<ScoreInfo>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
globalScores.BindTo(leaderboardManager.Scores);
|
||||
globalScores.BindValueChanged(_ =>
|
||||
{
|
||||
localScores.Clear();
|
||||
|
||||
if (globalScores.Value is LeaderboardScores g)
|
||||
localScores.AddRange(g.AllScores.OrderByTotalScore());
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
||||
{
|
||||
int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID;
|
||||
@@ -43,13 +65,11 @@ namespace osu.Game.Screens.Play
|
||||
return new CreateSoloScoreRequest(Beatmap.Value.BeatmapInfo, rulesetId, Game.VersionHash);
|
||||
}
|
||||
|
||||
public readonly BindableList<ScoreInfo> LeaderboardScores = new BindableList<ScoreInfo>();
|
||||
|
||||
protected override GameplayLeaderboard CreateGameplayLeaderboard() =>
|
||||
new SoloGameplayLeaderboard(Score.ScoreInfo.User)
|
||||
{
|
||||
AlwaysVisible = { Value = false },
|
||||
Scores = { BindTarget = LeaderboardScores }
|
||||
Scores = { BindTarget = localScores }
|
||||
};
|
||||
|
||||
protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
|
||||
@@ -59,7 +79,7 @@ namespace osu.Game.Screens.Play
|
||||
// Before importing a score, stop binding the leaderboard with its score source.
|
||||
// This avoids a case where the imported score may cause a leaderboard refresh
|
||||
// (if the leaderboard's source is local).
|
||||
LeaderboardScores.UnbindBindings();
|
||||
globalScores.UnbindBindings();
|
||||
|
||||
return base.ImportScore(score);
|
||||
}
|
||||
|
||||
@@ -8,18 +8,18 @@ using osu.Game.Rulesets.Scoring;
|
||||
namespace osu.Game.Screens.Ranking.Statistics
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays the average hit error statistic for a given play.
|
||||
/// Displays the unstable rate statistic for a given play.
|
||||
/// </summary>
|
||||
public partial class AverageHitError : SimpleStatisticItem<double?>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates and computes an <see cref="AverageHitError"/> statistic.
|
||||
/// </summary>
|
||||
/// <param name="hitEvents">Sequence of <see cref="HitEvent"/>s to calculate the average hit error based on.</param>
|
||||
/// <param name="hitEvents">Sequence of <see cref="HitEvent"/>s to calculate the unstable rate based on.</param>
|
||||
public AverageHitError(IEnumerable<HitEvent> hitEvents)
|
||||
: base("Average Hit Error")
|
||||
{
|
||||
Value = hitEvents.CalculateMedianHitError();
|
||||
Value = hitEvents.CalculateAverageHitError();
|
||||
}
|
||||
|
||||
protected override string DisplayValue(double? value) => value == null ? "(not available)" : $"{Math.Abs(value.Value):N2} ms {(value.Value < 0 ? "early" : "late")}";
|
||||
|
||||
@@ -263,7 +263,6 @@ namespace osu.Game.Screens.Select
|
||||
},
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Shear = -wedged_container_shear,
|
||||
|
||||
@@ -77,7 +77,6 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
},
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
TextSize = 11,
|
||||
|
||||
@@ -3,21 +3,17 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Screens.Select.Leaderboards
|
||||
{
|
||||
@@ -67,6 +63,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Bindable<LeaderboardScores?> fetchedScores = new Bindable<LeaderboardScores?>();
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
@@ -77,14 +75,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
private IDisposable? scoreSubscription;
|
||||
|
||||
private GetScoresRequest? scoreRetrievalRequest;
|
||||
private LeaderboardManager leaderboardManager { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -95,15 +86,13 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
if (filterMods)
|
||||
RefetchScores();
|
||||
};
|
||||
((IBindable<LeaderboardScores?>)fetchedScores).BindTo(leaderboardManager.Scores);
|
||||
}
|
||||
|
||||
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
|
||||
|
||||
protected override APIRequest? FetchScores(CancellationToken cancellationToken)
|
||||
{
|
||||
scoreRetrievalRequest?.Cancel();
|
||||
scoreRetrievalRequest = null;
|
||||
|
||||
var fetchBeatmapInfo = BeatmapInfo;
|
||||
|
||||
if (fetchBeatmapInfo == null)
|
||||
@@ -114,13 +103,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset;
|
||||
|
||||
if (Scope == BeatmapLeaderboardScope.Local)
|
||||
{
|
||||
subscribeToLocalScores(fetchBeatmapInfo, cancellationToken);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!api.IsLoggedIn)
|
||||
if (!api.IsLoggedIn && IsOnlineScope)
|
||||
{
|
||||
SetErrorState(LeaderboardState.NotLoggedIn);
|
||||
return null;
|
||||
@@ -132,7 +115,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
return null;
|
||||
}
|
||||
|
||||
if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
|
||||
if ((fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) && IsOnlineScope)
|
||||
{
|
||||
SetErrorState(LeaderboardState.BeatmapUnavailable);
|
||||
return null;
|
||||
@@ -150,29 +133,24 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
return null;
|
||||
}
|
||||
|
||||
IReadOnlyList<Mod>? requestMods = null;
|
||||
leaderboardManager.FetchWithCriteriaAsync(new LeaderboardCriteria(fetchBeatmapInfo, fetchRuleset, Scope, filterMods ? mods.Value.ToArray() : null))
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.Exception != null && !t.IsCanceled)
|
||||
{
|
||||
Schedule(() => SetErrorState(LeaderboardState.NetworkFailure));
|
||||
return;
|
||||
}
|
||||
|
||||
if (filterMods && !mods.Value.Any())
|
||||
// add nomod for the request
|
||||
requestMods = new Mod[] { new ModNoMod() };
|
||||
else if (filterMods)
|
||||
requestMods = mods.Value;
|
||||
fetchedScores.UnbindEvents();
|
||||
fetchedScores.BindValueChanged(scores =>
|
||||
{
|
||||
if (scores.NewValue != null)
|
||||
Schedule(() => SetScores(scores.NewValue.TopScores, scores.NewValue.UserScore));
|
||||
}, true);
|
||||
}, cancellationToken);
|
||||
|
||||
var newRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods);
|
||||
newRequest.Success += response => Schedule(() =>
|
||||
{
|
||||
// Request may have changed since fetch request.
|
||||
// Can't rely on request cancellation due to Schedule inside SetScores so let's play it safe.
|
||||
if (!newRequest.Equals(scoreRetrievalRequest))
|
||||
return;
|
||||
|
||||
SetScores(
|
||||
response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo)).OrderByTotalScore(),
|
||||
response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo)
|
||||
);
|
||||
});
|
||||
|
||||
return scoreRetrievalRequest = newRequest;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope, Scope != BeatmapLeaderboardScope.Friend)
|
||||
@@ -184,59 +162,5 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
{
|
||||
Action = () => ScoreSelected?.Invoke(model)
|
||||
};
|
||||
|
||||
private void subscribeToLocalScores(BeatmapInfo beatmapInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
Debug.Assert(beatmapInfo != null);
|
||||
|
||||
scoreSubscription?.Dispose();
|
||||
scoreSubscription = null;
|
||||
|
||||
scoreSubscription = realm.RegisterForNotifications(r =>
|
||||
r.All<ScoreInfo>().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0"
|
||||
+ $" AND {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
|
||||
+ $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1"
|
||||
+ $" AND {nameof(ScoreInfo.DeletePending)} == false"
|
||||
, beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged);
|
||||
|
||||
void localScoresChanged(IRealmCollection<ScoreInfo> sender, ChangeSet? changes)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// This subscription may fire from changes to linked beatmaps, which we don't care about.
|
||||
// It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications.
|
||||
if (changes?.HasCollectionChanges() == false)
|
||||
return;
|
||||
|
||||
var scores = sender.AsEnumerable();
|
||||
|
||||
if (filterMods && !mods.Value.Any())
|
||||
{
|
||||
// we need to filter out all scores that have any mods to get all local nomod scores
|
||||
scores = scores.Where(s => !s.Mods.Any());
|
||||
}
|
||||
else if (filterMods)
|
||||
{
|
||||
// otherwise find all the scores that have all of the currently selected mods (similar to how web applies mod filters)
|
||||
// we're creating and using a string HashSet representation of selected mods so that it can be translated into the DB query itself
|
||||
var selectedMods = mods.Value.Select(m => m.Acronym).ToHashSet();
|
||||
|
||||
scores = scores.Where(s => selectedMods.SetEquals(s.Mods.Select(m => m.Acronym)));
|
||||
}
|
||||
|
||||
scores = scores.Detach().OrderByTotalScore();
|
||||
|
||||
SetScores(scores);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
scoreSubscription?.Dispose();
|
||||
scoreRetrievalRequest?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace osu.Game.Screens.Select.Options
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(0.2f, 0f),
|
||||
Shear = OsuGame.SHEAR,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
|
||||
@@ -129,17 +129,11 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
if (replayGeneratingMod != null)
|
||||
{
|
||||
player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods))
|
||||
{
|
||||
LeaderboardScores = { BindTarget = playBeatmapDetailArea.Leaderboard.Scores }
|
||||
};
|
||||
player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods));
|
||||
}
|
||||
else
|
||||
{
|
||||
player = new SoloPlayer
|
||||
{
|
||||
LeaderboardScores = { BindTarget = playBeatmapDetailArea.Leaderboard.Scores }
|
||||
};
|
||||
player = new SoloPlayer();
|
||||
}
|
||||
|
||||
return player;
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Utils;
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
// 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.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class BeatmapInfoWedgeV2 : VisibilityContainer
|
||||
{
|
||||
public const float WEDGE_HEIGHT = 120;
|
||||
private const float shear_width = 21;
|
||||
private const float transition_duration = 250;
|
||||
private const float corner_radius = 10;
|
||||
private const float colour_bar_width = 30;
|
||||
|
||||
/// Todo: move this const out to song select when more new design elements are implemented for the beatmap details area, since it applies to text alignment of various elements
|
||||
private const float text_margin = 62;
|
||||
|
||||
private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / WEDGE_HEIGHT, 0);
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||
|
||||
protected Container? DisplayedContent { get; private set; }
|
||||
|
||||
protected WedgeInfoText? Info { get; private set; }
|
||||
|
||||
private Container difficultyColourBar = null!;
|
||||
private StarCounter starCounter = null!;
|
||||
private StarRatingDisplay starRatingDisplay = null!;
|
||||
private BeatmapSetOnlineStatusPill statusPill = null!;
|
||||
private Container content = null!;
|
||||
|
||||
private IBindable<StarDifficulty?>? starDifficulty;
|
||||
private CancellationTokenSource? cancellationSource;
|
||||
|
||||
public BeatmapInfoWedgeV2()
|
||||
{
|
||||
Height = WEDGE_HEIGHT;
|
||||
Shear = wedged_container_shear;
|
||||
Masking = true;
|
||||
Margin = new MarginPadding { Left = -corner_radius };
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = Colour4.Black.Opacity(0.2f),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 3,
|
||||
};
|
||||
CornerRadius = corner_radius;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// These elements can't be grouped with the rest of the content, due to being present either outside or under the backgrounds area
|
||||
difficultyColourBar = new Container
|
||||
{
|
||||
Colour = Colour4.Transparent,
|
||||
Depth = float.MaxValue,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
|
||||
// By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it.
|
||||
Width = colour_bar_width + corner_radius,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
new Container
|
||||
{
|
||||
// Applying the shear to this container and nesting the starCounter inside avoids
|
||||
// the deformation that occurs if the shear is applied to the starCounter whilst rotated
|
||||
Shear = -wedged_container_shear,
|
||||
X = -colour_bar_width / 2,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = colour_bar_width,
|
||||
Child = starCounter = new StarCounter
|
||||
{
|
||||
Rotation = (float)(Math.Atan(shear_width / WEDGE_HEIGHT) * (180 / Math.PI)),
|
||||
Colour = Colour4.Transparent,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.35f),
|
||||
Direction = FillDirection.Vertical
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Name = "Topright-aligned metadata",
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Top = 3, Right = colour_bar_width + 8 },
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Depth = float.MinValue,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
starRatingDisplay = new StarRatingDisplay(default, animated: true)
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Shear = -wedged_container_shear,
|
||||
Alpha = 0,
|
||||
},
|
||||
statusPill = new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Shear = -wedged_container_shear,
|
||||
TextSize = 11,
|
||||
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
|
||||
Alpha = 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ruleset.BindValueChanged(_ => updateDisplay());
|
||||
|
||||
starRatingDisplay.Current.BindValueChanged(s =>
|
||||
{
|
||||
// use actual stars as star counter has its own animation
|
||||
starCounter.Current = (float)s.NewValue.Stars;
|
||||
}, true);
|
||||
|
||||
starRatingDisplay.DisplayedStars.BindValueChanged(s =>
|
||||
{
|
||||
// sync color with star rating display
|
||||
starCounter.Colour = s.NewValue >= 6.5 ? colours.Orange1 : Colour4.Black.Opacity(0.75f);
|
||||
difficultyColourBar.FadeColour(colours.ForStarDifficulty(s.NewValue));
|
||||
}, true);
|
||||
}
|
||||
|
||||
private const double animation_duration = 600;
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.MoveToX(0, animation_duration, Easing.OutQuint);
|
||||
this.FadeIn(200, Easing.In);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.MoveToX(-150, animation_duration, Easing.OutQuint);
|
||||
this.FadeOut(200, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private WorkingBeatmap beatmap = null!;
|
||||
|
||||
public WorkingBeatmap Beatmap
|
||||
{
|
||||
get => beatmap;
|
||||
set
|
||||
{
|
||||
if (beatmap == value) return;
|
||||
|
||||
beatmap = value;
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private Container? loadingInfo;
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
statusPill.Status = beatmap.BeatmapInfo.Status;
|
||||
|
||||
starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, (cancellationSource = new CancellationTokenSource()).Token);
|
||||
|
||||
starDifficulty.BindValueChanged(s =>
|
||||
{
|
||||
starRatingDisplay.Current.Value = s.NewValue ?? default;
|
||||
|
||||
starRatingDisplay.FadeIn(transition_duration);
|
||||
});
|
||||
|
||||
Scheduler.AddOnce(() =>
|
||||
{
|
||||
LoadComponentAsync(loadingInfo = new Container
|
||||
{
|
||||
Padding = new MarginPadding { Right = colour_bar_width },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = DisplayedContent?.Depth + 1 ?? 0,
|
||||
Child = new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = corner_radius,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// TODO: New wedge design uses a coloured horizontal gradient for its background, however this lacks implementation information in the figma draft.
|
||||
// pending https://www.figma.com/file/DXKwqZhD5yyb1igc3mKo1P?node-id=2980:3361#340801912 being answered.
|
||||
new BeatmapInfoWedgeBackground(beatmap) { Shear = -Shear },
|
||||
Info = new WedgeInfoText(beatmap) { Shear = -Shear }
|
||||
}
|
||||
}
|
||||
}, d =>
|
||||
{
|
||||
// Ensure we are the most recent loaded wedge.
|
||||
if (d != loadingInfo) return;
|
||||
|
||||
removeOldInfo();
|
||||
content.Add(DisplayedContent = d);
|
||||
});
|
||||
});
|
||||
|
||||
void removeOldInfo()
|
||||
{
|
||||
DisplayedContent?.FadeOut(transition_duration);
|
||||
DisplayedContent?.Expire();
|
||||
DisplayedContent = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
cancellationSource?.Cancel();
|
||||
}
|
||||
|
||||
public partial class WedgeInfoText : Container
|
||||
{
|
||||
public OsuSpriteText TitleLabel { get; private set; } = null!;
|
||||
public OsuSpriteText ArtistLabel { get; private set; } = null!;
|
||||
|
||||
private readonly WorkingBeatmap working;
|
||||
|
||||
public WedgeInfoText(WorkingBeatmap working)
|
||||
{
|
||||
this.working = working;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(SongSelect? songSelect, LocalisationManager localisation)
|
||||
{
|
||||
var metadata = working.Metadata;
|
||||
|
||||
var titleText = new RomanisableString(metadata.TitleUnicode, metadata.Title);
|
||||
var artistText = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Name = "Top-left aligned metadata",
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Left = text_margin, Top = 12 },
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuHoverContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Action = () => songSelect?.Search(titleText.GetPreferred(localisation.CurrentParameters.Value.PreferOriginalScript)),
|
||||
Child = TitleLabel = new TruncatingSpriteText
|
||||
{
|
||||
Shadow = true,
|
||||
Text = titleText,
|
||||
Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.SemiBold),
|
||||
},
|
||||
},
|
||||
new OsuHoverContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Action = () => songSelect?.Search(artistText.GetPreferred(localisation.CurrentParameters.Value.PreferOriginalScript)),
|
||||
Child = ArtistLabel = new TruncatingSpriteText
|
||||
{
|
||||
// TODO : figma design has a diffused shadow, instead of the solid one present here, not possible currently as far as i'm aware.
|
||||
Shadow = true,
|
||||
Text = artistText,
|
||||
// Not sure if this should be semi bold or medium
|
||||
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
// best effort to confine the auto-sized text to wedge bounds
|
||||
// the artist label doesn't have an extra text_margin as it doesn't touch the right metadata
|
||||
TitleLabel.MaxWidth = DrawWidth - text_margin * 2 - shear_width;
|
||||
ArtistLabel.MaxWidth = DrawWidth - text_margin - shear_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -41,9 +41,9 @@ using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using CommonStrings = osu.Game.Localisation.CommonStrings;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip<ScoreInfo>
|
||||
public partial class BeatmapLeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip<ScoreInfo>
|
||||
{
|
||||
public Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>();
|
||||
|
||||
@@ -117,12 +117,12 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
public ITooltip<ScoreInfo> GetCustomTooltip() => new LeaderboardScoreTooltip();
|
||||
public virtual ScoreInfo TooltipContent => score;
|
||||
|
||||
public LeaderboardScoreV2(ScoreInfo score, bool sheared = true)
|
||||
public BeatmapLeaderboardScore(ScoreInfo score, bool sheared = true)
|
||||
{
|
||||
this.score = score;
|
||||
this.sheared = sheared;
|
||||
|
||||
Shear = new Vector2(sheared ? OsuGame.SHEAR : 0, 0);
|
||||
Shear = sheared ? OsuGame.SHEAR : Vector2.Zero;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = height;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
User = score.User,
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Colour = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0.5f), Colour4.FromHex(@"222A27").Opacity(1)),
|
||||
@@ -286,7 +286,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(1.1f),
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
})
|
||||
{
|
||||
@@ -326,7 +326,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
{
|
||||
flagBadgeAndDateContainer = new FillFlowContainer
|
||||
{
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@@ -356,7 +356,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
nameLabel = new TruncatingSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
Text = user.Username,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold)
|
||||
}
|
||||
@@ -372,7 +372,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
Name = @"Statistics container",
|
||||
Padding = new MarginPadding { Right = 40 },
|
||||
Spacing = new Vector2(25, 0),
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@@ -430,7 +430,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
},
|
||||
RankContainer = new Container
|
||||
{
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
@@ -488,7 +488,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
UseFullGlyphHeight = false,
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
|
||||
},
|
||||
@@ -496,7 +496,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(2f, 0f),
|
||||
@@ -704,7 +704,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
|
||||
Child = new OsuSpriteText
|
||||
{
|
||||
Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0),
|
||||
Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold, italics: true),
|
||||
@@ -1,195 +0,0 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2.Footer
|
||||
{
|
||||
public partial class BeatmapOptionsPopover : OsuPopover
|
||||
{
|
||||
private FillFlowContainer buttonFlow = null!;
|
||||
private readonly ScreenFooterButtonOptions footerButton;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider;
|
||||
|
||||
private WorkingBeatmap beatmapWhenOpening = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
public BeatmapOptionsPopover(ScreenFooterButtonOptions footerButton, OverlayColourProvider colourProvider)
|
||||
{
|
||||
this.footerButton = footerButton;
|
||||
this.colourProvider = colourProvider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ManageCollectionsDialog? manageCollectionsDialog, OsuColour colours, BeatmapManager? beatmapManager)
|
||||
{
|
||||
Content.Padding = new MarginPadding(5);
|
||||
|
||||
Child = buttonFlow = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(3),
|
||||
};
|
||||
|
||||
beatmapWhenOpening = beatmap.Value;
|
||||
|
||||
addHeader(CommonStrings.General);
|
||||
addButton(SongSelectStrings.ManageCollections, FontAwesome.Solid.Book, () => manageCollectionsDialog?.Show());
|
||||
|
||||
addHeader(SongSelectStrings.ForAllDifficulties, beatmapWhenOpening.BeatmapSetInfo.ToString());
|
||||
addButton(SongSelectStrings.DeleteBeatmap, FontAwesome.Solid.Trash, () => { }, colours.Red1); // songSelect?.DeleteBeatmap(beatmapWhenOpening.BeatmapSetInfo);
|
||||
|
||||
addHeader(SongSelectStrings.ForSelectedDifficulty, beatmapWhenOpening.BeatmapInfo.DifficultyName);
|
||||
// TODO: make work, and make show "unplayed" or "played" based on status.
|
||||
addButton(SongSelectStrings.MarkAsPlayed, FontAwesome.Regular.TimesCircle, null);
|
||||
addButton(SongSelectStrings.ClearAllLocalScores, FontAwesome.Solid.Eraser, () => { }, colours.Red1); // songSelect?.ClearScores(beatmapWhenOpening.BeatmapInfo);
|
||||
|
||||
// if (songSelect != null && songSelect.AllowEditing)
|
||||
addButton(SongSelectStrings.EditBeatmap, FontAwesome.Solid.PencilAlt, () => { }); // songSelect.Edit(beatmapWhenOpening.BeatmapInfo);
|
||||
|
||||
addButton(WebCommonStrings.ButtonsHide.ToSentence(), FontAwesome.Solid.Magic, () => beatmapManager?.Hide(beatmapWhenOpening.BeatmapInfo));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(this));
|
||||
|
||||
beatmap.BindValueChanged(_ => Hide());
|
||||
}
|
||||
|
||||
private void addHeader(LocalisableString text, string? context = null)
|
||||
{
|
||||
var textFlow = new OsuTextFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding(10),
|
||||
};
|
||||
|
||||
textFlow.AddText(text, t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold));
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
textFlow.NewLine();
|
||||
textFlow.AddText(context, t =>
|
||||
{
|
||||
t.Colour = colourProvider.Content2;
|
||||
t.Font = t.Font.With(size: 13);
|
||||
});
|
||||
}
|
||||
|
||||
buttonFlow.Add(textFlow);
|
||||
}
|
||||
|
||||
private void addButton(LocalisableString text, IconUsage icon, Action? action, Color4? colour = null)
|
||||
{
|
||||
var button = new OptionButton
|
||||
{
|
||||
Text = text,
|
||||
Icon = icon,
|
||||
TextColour = colour,
|
||||
Action = () =>
|
||||
{
|
||||
Scheduler.AddDelayed(Hide, 50);
|
||||
action?.Invoke();
|
||||
},
|
||||
};
|
||||
|
||||
buttonFlow.Add(button);
|
||||
}
|
||||
|
||||
private partial class OptionButton : OsuButton
|
||||
{
|
||||
public IconUsage Icon { get; init; }
|
||||
public Color4? TextColour { get; init; }
|
||||
|
||||
public OptionButton()
|
||||
{
|
||||
Size = new Vector2(265, 50);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
BackgroundColour = colourProvider.Background3;
|
||||
|
||||
SpriteText.Colour = TextColour ?? Color4.White;
|
||||
Content.CornerRadius = 10;
|
||||
|
||||
Add(new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(17),
|
||||
X = 15,
|
||||
Icon = Icon,
|
||||
Colour = TextColour ?? Color4.White,
|
||||
});
|
||||
}
|
||||
|
||||
protected override SpriteText CreateText() => new OsuSpriteText
|
||||
{
|
||||
Depth = -1,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
X = 40
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
// don't absorb control as ToolbarRulesetSelector uses control + number to navigate
|
||||
if (e.ControlPressed) return false;
|
||||
|
||||
if (!e.Repeat && e.Key >= Key.Number1 && e.Key <= Key.Number9)
|
||||
{
|
||||
int requested = e.Key - Key.Number1;
|
||||
|
||||
OptionButton? found = buttonFlow.Children.OfType<OptionButton>().ElementAtOrDefault(requested);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
found.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void UpdateState(ValueChangedEvent<Visibility> state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
footerButton.OverlayState.Value = state.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -28,9 +28,9 @@ using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2.Footer
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class ScreenFooterButtonMods : ScreenFooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
|
||||
public partial class FooterButtonMods : ScreenFooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
|
||||
{
|
||||
private const float bar_height = 30f;
|
||||
private const float mod_display_portion = 0.65f;
|
||||
@@ -58,7 +58,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public ScreenFooterButtonMods(ModSelectOverlay overlay)
|
||||
public FooterButtonMods(ModSelectOverlay overlay)
|
||||
: base(overlay)
|
||||
{
|
||||
}
|
||||
@@ -78,7 +78,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Y = -5f,
|
||||
Depth = float.MaxValue,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Shear = BUTTON_SHEAR,
|
||||
Shear = OsuGame.SHEAR,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Size = new Vector2(BUTTON_WIDTH, bar_height),
|
||||
Masking = true,
|
||||
@@ -108,7 +108,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
UseFullGlyphHeight = false,
|
||||
Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold)
|
||||
}
|
||||
@@ -130,7 +130,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Scale = new Vector2(0.5f),
|
||||
Current = { BindTarget = Current },
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
@@ -139,7 +139,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold),
|
||||
Mods = { BindTarget = Current },
|
||||
}
|
||||
@@ -305,7 +305,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Y = -5f;
|
||||
Depth = float.MaxValue;
|
||||
Origin = Anchor.BottomLeft;
|
||||
Shear = BUTTON_SHEAR;
|
||||
Shear = OsuGame.SHEAR;
|
||||
CornerRadius = CORNER_RADIUS;
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = bar_height;
|
||||
@@ -329,7 +329,7 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = -BUTTON_SHEAR,
|
||||
Shear = -OsuGame.SHEAR,
|
||||
Text = ModSelectOverlayStrings.Unranked.ToUpper(),
|
||||
Margin = new MarginPadding { Horizontal = 15 },
|
||||
UseFullGlyphHeight = false,
|
||||
+3
-4
@@ -5,15 +5,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2.Footer
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class ScreenFooterButtonOptions : ScreenFooterButton, IHasPopover
|
||||
public partial class FooterButtonOptions : ScreenFooterButton, IHasPopover
|
||||
{
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
@@ -29,6 +28,6 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Action = this.ShowPopover;
|
||||
}
|
||||
|
||||
public Popover GetPopover() => new BeatmapOptionsPopover(this, colourProvider);
|
||||
public Framework.Graphics.UserInterface.Popover GetPopover() => new Popover(this, colourProvider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class FooterButtonOptions
|
||||
{
|
||||
public partial class Popover : OsuPopover
|
||||
{
|
||||
private FillFlowContainer buttonFlow = null!;
|
||||
private readonly FooterButtonOptions footerButton;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider;
|
||||
|
||||
private WorkingBeatmap beatmapWhenOpening = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
public Popover(FooterButtonOptions footerButton, OverlayColourProvider colourProvider)
|
||||
{
|
||||
this.footerButton = footerButton;
|
||||
this.colourProvider = colourProvider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ManageCollectionsDialog? manageCollectionsDialog, OsuColour colours, BeatmapManager? beatmapManager)
|
||||
{
|
||||
Content.Padding = new MarginPadding(5);
|
||||
|
||||
Child = buttonFlow = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(3),
|
||||
};
|
||||
|
||||
beatmapWhenOpening = beatmap.Value;
|
||||
|
||||
addHeader(CommonStrings.General);
|
||||
addButton(SongSelectStrings.ManageCollections, FontAwesome.Solid.Book, () => manageCollectionsDialog?.Show());
|
||||
|
||||
addHeader(SongSelectStrings.ForAllDifficulties, beatmapWhenOpening.BeatmapSetInfo.ToString());
|
||||
addButton(SongSelectStrings.DeleteBeatmap, FontAwesome.Solid.Trash, () => { }, colours.Red1); // songSelect?.DeleteBeatmap(beatmapWhenOpening.BeatmapSetInfo);
|
||||
|
||||
addHeader(SongSelectStrings.ForSelectedDifficulty, beatmapWhenOpening.BeatmapInfo.DifficultyName);
|
||||
// TODO: make work, and make show "unplayed" or "played" based on status.
|
||||
addButton(SongSelectStrings.MarkAsPlayed, FontAwesome.Regular.TimesCircle, null);
|
||||
addButton(SongSelectStrings.ClearAllLocalScores, FontAwesome.Solid.Eraser, () => { }, colours.Red1); // songSelect?.ClearScores(beatmapWhenOpening.BeatmapInfo);
|
||||
|
||||
// if (songSelect != null && songSelect.AllowEditing)
|
||||
addButton(SongSelectStrings.EditBeatmap, FontAwesome.Solid.PencilAlt, () => { }); // songSelect.Edit(beatmapWhenOpening.BeatmapInfo);
|
||||
|
||||
addButton(WebCommonStrings.ButtonsHide.ToSentence(), FontAwesome.Solid.Magic, () => beatmapManager?.Hide(beatmapWhenOpening.BeatmapInfo));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(this));
|
||||
|
||||
beatmap.BindValueChanged(_ => Hide());
|
||||
}
|
||||
|
||||
private void addHeader(LocalisableString text, string? context = null)
|
||||
{
|
||||
var textFlow = new OsuTextFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding(10),
|
||||
};
|
||||
|
||||
textFlow.AddText(text, t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold));
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
textFlow.NewLine();
|
||||
textFlow.AddText(context, t =>
|
||||
{
|
||||
t.Colour = colourProvider.Content2;
|
||||
t.Font = t.Font.With(size: 13);
|
||||
});
|
||||
}
|
||||
|
||||
buttonFlow.Add(textFlow);
|
||||
}
|
||||
|
||||
private void addButton(LocalisableString text, IconUsage icon, Action? action, Color4? colour = null)
|
||||
{
|
||||
var button = new OptionButton
|
||||
{
|
||||
Text = text,
|
||||
Icon = icon,
|
||||
TextColour = colour,
|
||||
Action = () =>
|
||||
{
|
||||
Scheduler.AddDelayed(Hide, 50);
|
||||
action?.Invoke();
|
||||
},
|
||||
};
|
||||
|
||||
buttonFlow.Add(button);
|
||||
}
|
||||
|
||||
private partial class OptionButton : OsuButton
|
||||
{
|
||||
public IconUsage Icon { get; init; }
|
||||
public Color4? TextColour { get; init; }
|
||||
|
||||
public OptionButton()
|
||||
{
|
||||
Size = new Vector2(265, 50);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
BackgroundColour = colourProvider.Background3;
|
||||
|
||||
SpriteText.Colour = TextColour ?? Color4.White;
|
||||
Content.CornerRadius = 10;
|
||||
|
||||
Add(new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(17),
|
||||
X = 15,
|
||||
Icon = Icon,
|
||||
Colour = TextColour ?? Color4.White,
|
||||
});
|
||||
}
|
||||
|
||||
protected override SpriteText CreateText() => new OsuSpriteText
|
||||
{
|
||||
Depth = -1,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
X = 40
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
// don't absorb control as ToolbarRulesetSelector uses control + number to navigate
|
||||
if (e.ControlPressed) return false;
|
||||
|
||||
if (!e.Repeat && e.Key >= Key.Number1 && e.Key <= Key.Number9)
|
||||
{
|
||||
int requested = e.Key - Key.Number1;
|
||||
|
||||
OptionButton? found = buttonFlow.Children.OfType<OptionButton>().ElementAtOrDefault(requested);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
found.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void UpdateState(ValueChangedEvent<Visibility> state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
footerButton.OverlayState.Value = state.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -14,9 +14,9 @@ using osu.Game.Screens.Footer;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2.Footer
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class ScreenFooterButtonRandom : ScreenFooterButton
|
||||
public partial class FooterButtonRandom : ScreenFooterButton
|
||||
{
|
||||
public Action? NextRandom { get; set; }
|
||||
public Action? PreviousRandom { get; set; }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user