1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 18:52:55 +08:00

Merge branch 'master' into osu-ruleset-multi-touch-basic

This commit is contained in:
Dean Herbert 2023-01-18 13:13:22 +09:00
commit 92dc626734
72 changed files with 1437 additions and 488 deletions

View File

@ -0,0 +1,30 @@
// 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.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Tests.Visual;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Tests.Editor
{
public partial class TestScenePlacementBeforeTrackStart : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
[Test]
public void TestPlacement()
{
AddStep("Seek to 0", () => EditorClock.Seek(0));
AddStep("Select note", () => InputManager.Key(Key.Number2));
AddStep("Hover negative span", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
});
AddStep("Click", () => InputManager.Click(MouseButton.Left));
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
}
}
}

View File

@ -31,10 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test] [Test]
public void TestMaximumDistanceTrackingWithoutMovement( public void TestMaximumDistanceTrackingWithoutMovement(
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] [Values(0, 5, 10)] float circleSize,
float circleSize, [Values(0, 5, 10)] double velocity)
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
double velocity)
{ {
const double time_slider_start = 1000; const double time_slider_start = 1000;

View File

@ -0,0 +1,79 @@
// 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 NUnit.Framework;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Tests.Editing
{
[TestFixture]
public class TestSceneSnappingNearZero
{
private readonly ControlPointInfo cpi = new ControlPointInfo();
[Test]
public void TestOnZero()
{
test(0, 500, 0, 0);
test(0, 500, 100, 0);
test(0, 500, 250, 500);
test(0, 500, 600, 500);
test(0, 500, -600, 0);
}
[Test]
public void TestAlmostOnZero()
{
test(50, 500, 0, 50);
test(50, 500, 50, 50);
test(50, 500, 100, 50);
test(50, 500, 299, 50);
test(50, 500, 300, 550);
test(50, 500, -500, 50);
}
[Test]
public void TestAlmostOnOne()
{
test(499, 500, -1, 499);
test(499, 500, 0, 499);
test(499, 500, 1, 499);
test(499, 500, 499, 499);
test(499, 500, 600, 499);
test(499, 500, 800, 999);
}
[Test]
public void TestOnOne()
{
test(500, 500, -500, 0);
test(500, 500, 0, 0);
test(500, 500, 200, 0);
test(500, 500, 400, 500);
test(500, 500, 500, 500);
test(500, 500, 600, 500);
test(500, 500, 900, 1000);
}
[Test]
public void TestNegative()
{
test(-600, 500, -600, 400);
test(-600, 500, -100, 400);
test(-600, 500, 0, 400);
test(-600, 500, 200, 400);
test(-600, 500, 400, 400);
test(-600, 500, 600, 400);
test(-600, 500, 1000, 900);
}
private void test(double pointTime, double beatLength, double from, double expected)
{
cpi.Clear();
cpi.Add(pointTime, new TimingControlPoint { BeatLength = beatLength });
Assert.That(cpi.GetClosestSnappedTime(from, 1), Is.EqualTo(expected), $"From: {from}");
}
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Tests.NonVisual
public void TestExactDivisors() public void TestExactDivisors()
{ {
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 }); cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 }; double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
@ -47,7 +47,7 @@ namespace osu.Game.Tests.NonVisual
public void TestExactDivisorsHighBPMStream() public void TestExactDivisorsHighBPMStream()
{ {
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.Add(-50, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing) cpi.Add(0, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing)
// A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors. // A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors.
double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 }; double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 };
@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
public void TestApproximateDivisors() public void TestApproximateDivisors()
{ {
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 }); cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 }; double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 };
double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 }; double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
@ -68,7 +68,7 @@ namespace osu.Game.Tests.NonVisual
assertClosestDivisors(divisors, closestDivisors, cpi); assertClosestDivisors(divisors, closestDivisors, cpi);
} }
private void assertClosestDivisors(IReadOnlyList<double> divisors, IReadOnlyList<double> closestDivisors, ControlPointInfo cpi, double step = 1) private static void assertClosestDivisors(IReadOnlyList<double> divisors, IReadOnlyList<double> closestDivisors, ControlPointInfo cpi, double step = 1)
{ {
List<HitObject> hitobjects = new List<HitObject>(); List<HitObject> hitobjects = new List<HitObject>();
double offset = cpi.TimingPoints[0].Time; double offset = cpi.TimingPoints[0].Time;

View File

@ -20,6 +20,8 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Setup;
using osu.Game.Storyboards; using osu.Game.Storyboards;
@ -395,5 +397,52 @@ namespace osu.Game.Tests.Visual.Editing
return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2); return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
}); });
} }
[Test]
public void TestCreateNewDifficultyForInconvertibleRuleset()
{
Guid setId = Guid.Empty;
AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
AddStep("save beatmap", () => Editor.Save());
AddStep("try to create new taiko difficulty", () => Editor.CreateNewDifficulty(new TaikoRuleset().RulesetInfo));
AddUntilStep("wait for created", () =>
{
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName == "New Difficulty";
});
AddAssert("new difficulty persisted", () =>
{
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
});
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
{
new Hit
{
StartTime = 0
},
new Hit
{
StartTime = 1000
}
}));
AddStep("save beatmap", () => Editor.Save());
AddStep("try to create new catch difficulty", () => Editor.CreateNewDifficulty(new CatchRuleset().RulesetInfo));
AddUntilStep("wait for created", () =>
{
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName == "New Difficulty (1)";
});
AddAssert("new difficulty persisted", () =>
{
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3);
});
}
} }
} }

View File

@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Editing
double lastStarRating = 0; double lastStarRating = 0;
double lastLength = 0; double lastLength = 0;
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint())); AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 600 }));
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2)); AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));

View File

@ -5,6 +5,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -34,6 +35,12 @@ namespace osu.Game.Tests.Visual.Gameplay
base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }); base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
} }
[BackgroundDependencyLoader]
private void load()
{
LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0);
}
[SetUpSteps] [SetUpSteps]
public override void SetUpSteps() public override void SetUpSteps()
{ {
@ -43,6 +50,22 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(true); confirmClockRunning(true);
} }
[Test]
public void TestTogglePauseViaBackAction()
{
pauseViaBackAction();
pauseViaBackAction();
confirmPausedWithNoOverlay();
}
[Test]
public void TestTogglePauseViaPauseGameplayAction()
{
pauseViaPauseGameplayAction();
pauseViaPauseGameplayAction();
confirmPausedWithNoOverlay();
}
[Test] [Test]
public void TestPauseWithLargeOffset() public void TestPauseWithLargeOffset()
{ {
@ -144,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("disable pause support", () => Player.Configuration.AllowPause = false); AddStep("disable pause support", () => Player.Configuration.AllowPause = false);
pauseFromUserExitKey(); pauseViaBackAction();
confirmExited(); confirmExited();
} }
@ -156,7 +179,7 @@ namespace osu.Game.Tests.Visual.Gameplay
pauseAndConfirm(); pauseAndConfirm();
resume(); resume();
pauseFromUserExitKey(); pauseViaBackAction();
confirmResumed(); confirmResumed();
confirmNotExited(); confirmNotExited();
@ -170,7 +193,7 @@ namespace osu.Game.Tests.Visual.Gameplay
pauseAndConfirm(); pauseAndConfirm();
resume(); resume();
AddStep("pause via exit key", () => Player.ExitViaQuickExit()); exitViaQuickExitAction();
confirmResumed(); confirmResumed();
AddAssert("exited", () => !Player.IsCurrentScreen()); AddAssert("exited", () => !Player.IsCurrentScreen());
@ -214,7 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(false); confirmClockRunning(false);
AddStep("exit via user pause", () => Player.ExitViaPause()); pauseViaBackAction();
confirmExited(); confirmExited();
} }
@ -224,11 +247,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
// will finish the fail animation and show the fail/pause screen. // will finish the fail animation and show the fail/pause screen.
AddStep("attempt exit via pause key", () => Player.ExitViaPause()); pauseViaBackAction();
AddAssert("fail overlay shown", () => Player.FailOverlayVisible); AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
// will actually exit. // will actually exit.
AddStep("exit via pause key", () => Player.ExitViaPause()); pauseViaBackAction();
confirmExited(); confirmExited();
} }
@ -245,7 +268,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickExitFromFailedGameplay() public void TestQuickExitFromFailedGameplay()
{ {
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke()); exitViaQuickExitAction();
confirmExited(); confirmExited();
} }
@ -261,7 +284,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestQuickExitFromGameplay() public void TestQuickExitFromGameplay()
{ {
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke()); exitViaQuickExitAction();
confirmExited(); confirmExited();
} }
@ -327,7 +350,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void pauseAndConfirm() private void pauseAndConfirm()
{ {
pauseFromUserExitKey(); pauseViaBackAction();
confirmPaused(); confirmPaused();
} }
@ -374,7 +397,17 @@ namespace osu.Game.Tests.Visual.Gameplay
} }
private void restart() => AddStep("restart", () => Player.Restart()); private void restart() => AddStep("restart", () => Player.Restart());
private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause()); private void pauseViaBackAction() => AddStep("press escape", () => InputManager.Key(Key.Escape));
private void pauseViaPauseGameplayAction() => AddStep("press middle mouse", () => InputManager.Click(MouseButton.Middle));
private void exitViaQuickExitAction() => AddStep("press ctrl-tilde", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(Key.Tilde);
InputManager.ReleaseKey(Key.Tilde);
InputManager.ReleaseKey(Key.ControlLeft);
});
private void resume() => AddStep("resume", () => Player.Resume()); private void resume() => AddStep("resume", () => Player.Resume());
private void confirmPauseOverlayShown(bool isShown) => private void confirmPauseOverlayShown(bool isShown) =>
@ -405,10 +438,6 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible; public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
public void ExitViaPause() => PerformExit(true);
public void ExitViaQuickExit() => PerformExit(false);
public override void OnEntering(ScreenTransitionEvent e) public override void OnEntering(ScreenTransitionEvent e)
{ {
base.OnEntering(e); base.OnEntering(e);

View File

@ -34,6 +34,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
public void TestSingleItemExpiredAfterGameplay()
{
RunGameplay();
AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1);
AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test]
[FlakyTest]
/*
* TearDown : System.TimeoutException : "wait for ongoing operation to complete" timed out
* --TearDown
* at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0()
* at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered)
* at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition)
*/
public void TestItemAddedToTheEndOfQueue() public void TestItemAddedToTheEndOfQueue()
{ {
addItem(() => OtherBeatmap); addItem(() => OtherBeatmap);
@ -46,16 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
public void TestSingleItemExpiredAfterGameplay() [FlakyTest] // See above
{
RunGameplay();
AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1);
AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test]
public void TestNextItemSelectedAfterGameplayFinish() public void TestNextItemSelectedAfterGameplayFinish()
{ {
addItem(() => OtherBeatmap); addItem(() => OtherBeatmap);
@ -73,6 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestItemsNotClearedWhenSwitchToHostOnlyMode() public void TestItemsNotClearedWhenSwitchToHostOnlyMode()
{ {
addItem(() => OtherBeatmap); addItem(() => OtherBeatmap);
@ -88,6 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestCorrectItemSelectedAfterNewItemAdded() public void TestCorrectItemSelectedAfterNewItemAdded()
{ {
addItem(() => OtherBeatmap); addItem(() => OtherBeatmap);
@ -95,6 +106,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestCorrectRulesetSelectedAfterNewItemAdded() public void TestCorrectRulesetSelectedAfterNewItemAdded()
{ {
addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo); addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo);
@ -112,6 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestCorrectModsSelectedAfterNewItemAdded() public void TestCorrectModsSelectedAfterNewItemAdded()
{ {
addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() }); addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() });

View File

@ -27,22 +27,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
} }
[Test]
public void TestItemStillSelectedAfterChangeToSameBeatmap()
{
selectNewItem(() => InitialBeatmap);
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test]
public void TestItemStillSelectedAfterChangeToOtherBeatmap()
{
selectNewItem(() => OtherBeatmap);
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test] [Test]
public void TestNewItemCreatedAfterGameplayFinished() public void TestNewItemCreatedAfterGameplayFinished()
{ {
@ -54,20 +38,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID);
} }
[Test]
public void TestOnlyLastItemChangedAfterGameplayFinished()
{
RunGameplay();
IBeatmapInfo firstBeatmap = null;
AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap);
selectNewItem(() => OtherBeatmap);
AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap == firstBeatmap);
AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap);
}
[Test] [Test]
public void TestSettingsUpdatedWhenChangingQueueMode() public void TestSettingsUpdatedWhenChangingQueueMode()
{ {
@ -80,6 +50,47 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest]
/*
* TearDown : System.TimeoutException : "wait for ongoing operation to complete" timed out
* --TearDown
* at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0()
* at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered)
* at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition)
*/
public void TestItemStillSelectedAfterChangeToSameBeatmap()
{
selectNewItem(() => InitialBeatmap);
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test]
[FlakyTest] // See above
public void TestItemStillSelectedAfterChangeToOtherBeatmap()
{
selectNewItem(() => OtherBeatmap);
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test]
[FlakyTest] // See above
public void TestOnlyLastItemChangedAfterGameplayFinished()
{
RunGameplay();
IBeatmapInfo firstBeatmap = null;
AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap);
selectNewItem(() => OtherBeatmap);
AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap == firstBeatmap);
AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap);
}
[Test]
[FlakyTest] // See above
public void TestAddItemsAsHost() public void TestAddItemsAsHost()
{ {
addItem(() => OtherBeatmap); addItem(() => OtherBeatmap);

View File

@ -377,6 +377,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest]
/*
* On a slight investigation, this is occurring due to the ready button
* not receiving the click input generated by the manual input manager.
*
* TearDown : System.TimeoutException : "wait for ready button to be enabled" timed out
* --TearDown
* at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0()
* at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered)
* at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition)
*/
public void TestUserSetToIdleWhenBeatmapDeleted() public void TestUserSetToIdleWhenBeatmapDeleted()
{ {
createRoom(() => new Room createRoom(() => new Room
@ -398,6 +409,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect() public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect()
{ {
PlaylistItem? item = null; PlaylistItem? item = null;
@ -438,6 +450,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect() public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect()
{ {
PlaylistItem? item = null; PlaylistItem? item = null;
@ -478,6 +491,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestPlayStartsWithCorrectModsWhileAtSongSelect() public void TestPlayStartsWithCorrectModsWhileAtSongSelect()
{ {
PlaylistItem? item = null; PlaylistItem? item = null;
@ -651,6 +665,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestGameplayFlow() public void TestGameplayFlow()
{ {
createRoom(() => new Room createRoom(() => new Room
@ -678,6 +693,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestGameplayExitFlow() public void TestGameplayExitFlow()
{ {
Bindable<double>? holdDelay = null; Bindable<double>? holdDelay = null;
@ -715,6 +731,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestGameplayDoesntStartWithNonLoadedUser() public void TestGameplayDoesntStartWithNonLoadedUser()
{ {
createRoom(() => new Room createRoom(() => new Room
@ -796,6 +813,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestSpectatingStateResetOnBackButtonDuringGameplay() public void TestSpectatingStateResetOnBackButtonDuringGameplay()
{ {
createRoom(() => new Room createRoom(() => new Room
@ -831,6 +849,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestSpectatingStateNotResetOnBackButtonOutsideOfGameplay() public void TestSpectatingStateNotResetOnBackButtonOutsideOfGameplay()
{ {
createRoom(() => new Room createRoom(() => new Room
@ -869,6 +888,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestItemAddedByOtherUserDuringGameplay() public void TestItemAddedByOtherUserDuringGameplay()
{ {
createRoom(() => new Room createRoom(() => new Room
@ -899,6 +919,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
[FlakyTest] // See above
public void TestItemAddedAndDeletedByOtherUserDuringGameplay() public void TestItemAddedAndDeletedByOtherUserDuringGameplay()
{ {
createRoom(() => new Room createRoom(() => new Room

View File

@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("Click gameplay scene button", () => AddStep("Click gameplay scene button", () =>
{ {
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text == "Gameplay")); InputManager.MoveMouseTo(skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text.ToString() == "Gameplay"));
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Navigation
public void TestBeatmapLink() public void TestBeatmapLink()
{ {
AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType<BeatmapSetOverlay>().FirstOrDefault()?.State.Value == Visibility.Visible); AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType<BeatmapSetOverlay>().FirstOrDefault()?.State.Value == Visibility.Visible);
AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType<BeatmapPicker>().FirstOrDefault()?.Beatmap.Value.OnlineID == requested_beatmap_id); AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType<BeatmapPicker>().FirstOrDefault()?.Beatmap.Value?.OnlineID == requested_beatmap_id);
} }
} }
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -11,7 +9,10 @@ using osu.Game.Overlays;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -27,15 +28,20 @@ namespace osu.Game.Tests.Visual.Online
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private CommentsContainer commentsContainer; private CommentsContainer commentsContainer = null!;
private TextBox editorTextBox = null!;
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{
Child = new BasicScrollContainer Child = new BasicScrollContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = commentsContainer = new CommentsContainer() Child = commentsContainer = new CommentsContainer()
}); };
editorTextBox = commentsContainer.ChildrenOfType<TextBox>().First();
});
[Test] [Test]
public void TestIdleState() public void TestIdleState()
@ -126,6 +132,44 @@ namespace osu.Game.Tests.Visual.Online
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1); commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
} }
[Test]
public void TestPost()
{
setUpCommentsResponse(new CommentBundle { Comments = new List<Comment>() });
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
AddAssert("no comments placeholder shown", () => commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
setUpPostResponse();
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
AddStep("submit", () => commentsContainer.ChildrenOfType<RoundedButton>().First().TriggerClick());
AddUntilStep("comment sent", () =>
{
string writtenText = editorTextBox.Current.Value;
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
});
AddAssert("no comments placeholder removed", () => !commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
}
[Test]
public void TestPostWithExistingComments()
{
setUpCommentsResponse(getExampleComments());
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
setUpPostResponse();
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
AddStep("submit", () => commentsContainer.ChildrenOfType<CommentEditor>().Single().ChildrenOfType<RoundedButton>().First().TriggerClick());
AddUntilStep("comment sent", () =>
{
string writtenText = editorTextBox.Current.Value;
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
});
}
private void setUpCommentsResponse(CommentBundle commentBundle) private void setUpCommentsResponse(CommentBundle commentBundle)
=> AddStep("set up response", () => => AddStep("set up response", () =>
{ {
@ -139,7 +183,33 @@ namespace osu.Game.Tests.Visual.Online
}; };
}); });
private CommentBundle getExampleComments(bool withPinned = false) private void setUpPostResponse()
=> AddStep("set up response", () =>
{
dummyAPI.HandleRequest = request =>
{
if (!(request is CommentPostRequest req))
return false;
req.TriggerSuccess(new CommentBundle
{
Comments = new List<Comment>
{
new Comment
{
Id = 98,
Message = req.Message,
LegacyName = "FirstUser",
CreatedAt = DateTimeOffset.Now,
VotesCount = 98,
}
}
});
return true;
};
});
private static CommentBundle getExampleComments(bool withPinned = false)
{ {
var bundle = new CommentBundle var bundle = new CommentBundle
{ {

View File

@ -113,7 +113,7 @@ namespace osu.Game.Tournament.Screens.Setup
new LabelledDropdown<RulesetInfo> new LabelledDropdown<RulesetInfo>
{ {
Label = "Ruleset", Label = "Ruleset",
Description = "Decides what stats are displayed and which ranks are retrieved for players.", Description = "Decides what stats are displayed and which ranks are retrieved for players. This requires a restart to reload data for an existing bracket.",
Items = rulesets.AvailableRulesets, Items = rulesets.AvailableRulesets,
Current = LadderInfo.Ruleset, Current = LadderInfo.Ruleset,
}, },

View File

@ -16,6 +16,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tournament.IO; using osu.Game.Tournament.IO;
@ -44,6 +45,14 @@ namespace osu.Game.Tournament
return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); return dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
} }
public override EndpointConfiguration CreateEndpoints()
{
if (UseDevelopmentServer)
return base.CreateEndpoints();
return new ProductionEndpointConfiguration();
}
private TournamentSpriteText initialisationText; private TournamentSpriteText initialisationText;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -156,9 +165,21 @@ namespace osu.Game.Tournament
addedInfo |= addSeedingBeatmaps(); addedInfo |= addSeedingBeatmaps();
if (addedInfo) if (addedInfo)
SaveChanges(); saveChanges();
ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value);
ladder.Ruleset.BindValueChanged(r =>
{
// Refetch player rank data on next startup as the ruleset has changed.
foreach (var team in ladder.Teams)
{
foreach (var player in team.Players)
player.Rank = null;
}
SaveChanges();
});
} }
catch (Exception e) catch (Exception e)
{ {
@ -306,6 +327,11 @@ namespace osu.Game.Tournament
return; return;
} }
saveChanges();
}
private void saveChanges()
{
foreach (var r in ladder.Rounds) foreach (var r in ladder.Rounds)
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList(); r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();

View File

@ -136,14 +136,12 @@ namespace osu.Game.Beatmaps
/// <param name="rulesetInfo">The ruleset with which the new difficulty should be created.</param> /// <param name="rulesetInfo">The ruleset with which the new difficulty should be created.</param>
public virtual WorkingBeatmap CreateNewDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap, RulesetInfo rulesetInfo) public virtual WorkingBeatmap CreateNewDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap, RulesetInfo rulesetInfo)
{ {
var playableBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(rulesetInfo); var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceWorkingBeatmap.Metadata.DeepClone())
var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone())
{ {
DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty") DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty")
}; };
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
foreach (var timingPoint in playableBeatmap.ControlPointInfo.TimingPoints) foreach (var timingPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.TimingPoints)
newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin);

View File

@ -183,9 +183,15 @@ namespace osu.Game.Beatmaps.ControlPoints
private static double getClosestSnappedTime(TimingControlPoint timingPoint, double time, int beatDivisor) private static double getClosestSnappedTime(TimingControlPoint timingPoint, double time, int beatDivisor)
{ {
double beatLength = timingPoint.BeatLength / beatDivisor; double beatLength = timingPoint.BeatLength / beatDivisor;
int beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); double beats = (Math.Max(time, 0) - timingPoint.Time) / beatLength;
return timingPoint.Time + beatLengths * beatLength; int roundedBeats = (int)Math.Round(beats, MidpointRounding.AwayFromZero);
double snappedTime = timingPoint.Time + roundedBeats * beatLength;
if (snappedTime >= 0)
return snappedTime;
return snappedTime + beatLength;
} }
/// <summary> /// <summary>

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation; using osu.Framework.Localisation;
@ -13,12 +11,12 @@ namespace osu.Game.Graphics.UserInterface
{ {
public readonly MenuItemType Type; public readonly MenuItemType Type;
public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard) public OsuMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard)
: this(text, type, null) : this(text, type, null)
{ {
} }
public OsuMenuItem(LocalisableString text, MenuItemType type, Action action) public OsuMenuItem(LocalisableString text, MenuItemType type, Action? action)
: base(text, action) : base(text, action)
{ {
Type = type; Type = type;

View File

@ -1,11 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -25,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface
/// <param name="text">The text to display.</param> /// <param name="text">The text to display.</param>
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param> /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param> /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
protected StatefulMenuItem(string text, Func<object, object> changeStateFunc, MenuItemType type = MenuItemType.Standard) protected StatefulMenuItem(LocalisableString text, Func<object, object> changeStateFunc, MenuItemType type = MenuItemType.Standard)
: this(text, changeStateFunc, type, null) : this(text, changeStateFunc, type, null)
{ {
} }
@ -37,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param> /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param> /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param> /// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
protected StatefulMenuItem(string text, Func<object, object> changeStateFunc, MenuItemType type, Action<object> action) protected StatefulMenuItem(LocalisableString text, Func<object, object>? changeStateFunc, MenuItemType type, Action<object>? action)
: base(text, type) : base(text, type)
{ {
Action.Value = () => Action.Value = () =>
@ -69,7 +68,7 @@ namespace osu.Game.Graphics.UserInterface
/// <param name="text">The text to display.</param> /// <param name="text">The text to display.</param>
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param> /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param> /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
protected StatefulMenuItem(string text, Func<T, T> changeStateFunc, MenuItemType type = MenuItemType.Standard) protected StatefulMenuItem(LocalisableString text, Func<T, T>? changeStateFunc, MenuItemType type = MenuItemType.Standard)
: this(text, changeStateFunc, type, null) : this(text, changeStateFunc, type, null)
{ {
} }
@ -81,7 +80,7 @@ namespace osu.Game.Graphics.UserInterface
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param> /// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param> /// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param> /// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
protected StatefulMenuItem(string text, Func<T, T> changeStateFunc, MenuItemType type, Action<T> action) protected StatefulMenuItem(LocalisableString text, Func<T, T>? changeStateFunc, MenuItemType type, Action<T>? action)
: base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o)) : base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o))
{ {
base.State.BindValueChanged(state => base.State.BindValueChanged(state =>

View File

@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -18,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface
/// </summary> /// </summary>
/// <param name="text">The text to display.</param> /// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param> /// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param>
public ToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard) public ToggleMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard)
: this(text, type, null) : this(text, type, null)
{ {
} }
@ -29,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface
/// <param name="text">The text to display.</param> /// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param> /// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="ToggleMenuItem"/> is pressed.</param> /// <param name="action">A delegate to be invoked when this <see cref="ToggleMenuItem"/> is pressed.</param>
public ToggleMenuItem(string text, MenuItemType type, Action<bool> action) public ToggleMenuItem(LocalisableString text, MenuItemType type, Action<bool>? action)
: base(text, value => !value, type, action) : base(text, value => !value, type, action)
{ {
} }

View File

@ -5,6 +5,7 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
@ -14,6 +15,7 @@ using osu.Framework.Input.Events;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Overlays; using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
@ -58,6 +60,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
this.FadeOut(fade_duration, Easing.OutQuint); this.FadeOut(fade_duration, Easing.OutQuint);
} }
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Key == Key.Escape)
return false; // disable the framework-level handling of escape key for conformity (we use GlobalAction.Back).
return base.OnKeyDown(e);
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.Repeat) if (e.Repeat)
@ -68,7 +78,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
if (e.Action == GlobalAction.Back) if (e.Action == GlobalAction.Back)
{ {
Hide(); this.HidePopover();
return true; return true;
} }

View File

@ -104,6 +104,61 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"Description"); public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"Description");
/// <summary>
/// "File"
/// </summary>
public static LocalisableString MenuBarFile => new TranslatableString(getKey(@"menu_bar_file"), @"File");
/// <summary>
/// "Edit"
/// </summary>
public static LocalisableString MenuBarEdit => new TranslatableString(getKey(@"menu_bar_edit"), @"Edit");
/// <summary>
/// "View"
/// </summary>
public static LocalisableString MenuBarView => new TranslatableString(getKey(@"menu_bar_view"), @"View");
/// <summary>
/// "Undo"
/// </summary>
public static LocalisableString Undo => new TranslatableString(getKey(@"undo"), @"Undo");
/// <summary>
/// "Redo"
/// </summary>
public static LocalisableString Redo => new TranslatableString(getKey(@"redo"), @"Redo");
/// <summary>
/// "Cut"
/// </summary>
public static LocalisableString Cut => new TranslatableString(getKey(@"cut"), @"Cut");
/// <summary>
/// "Copy"
/// </summary>
public static LocalisableString Copy => new TranslatableString(getKey(@"copy"), @"Copy");
/// <summary>
/// "Paste"
/// </summary>
public static LocalisableString Paste => new TranslatableString(getKey(@"paste"), @"Paste");
/// <summary>
/// "Clone"
/// </summary>
public static LocalisableString Clone => new TranslatableString(getKey(@"clone"), @"Clone");
/// <summary>
/// "Exit"
/// </summary>
public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"Exit");
/// <summary>
/// "Revert to default"
/// </summary>
public static LocalisableString RevertToDefault => new TranslatableString(getKey(@"revert_to_default"), @"Revert to default");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -0,0 +1,54 @@
// 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 EditorDialogsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.EditorDialogs";
/// <summary>
/// "Would you like to create a blank difficulty?"
/// </summary>
public static LocalisableString NewDifficultyDialogHeader => new TranslatableString(getKey(@"new_difficulty_dialog_header"), @"Would you like to create a blank difficulty?");
/// <summary>
/// "Yeah, let&#39;s start from scratch!"
/// </summary>
public static LocalisableString CreateNew => new TranslatableString(getKey(@"create_new"), @"Yeah, let's start from scratch!");
/// <summary>
/// "No, create an exact copy of this difficulty"
/// </summary>
public static LocalisableString CreateCopy => new TranslatableString(getKey(@"create_copy"), @"No, create an exact copy of this difficulty");
/// <summary>
/// "I changed my mind, I want to keep editing this difficulty"
/// </summary>
public static LocalisableString KeepEditing => new TranslatableString(getKey(@"keep_editing"), @"I changed my mind, I want to keep editing this difficulty");
/// <summary>
/// "Did you want to save your changes?"
/// </summary>
public static LocalisableString SaveDialogHeader => new TranslatableString(getKey(@"save_dialog_header"), @"Did you want to save your changes?");
/// <summary>
/// "Save my masterpiece!"
/// </summary>
public static LocalisableString Save => new TranslatableString(getKey(@"save"), @"Save my masterpiece!");
/// <summary>
/// "Forget all changes"
/// </summary>
public static LocalisableString ForgetAllChanges => new TranslatableString(getKey(@"forget_all_changes"), @"Forget all changes");
/// <summary>
/// "Oops, continue editing"
/// </summary>
public static LocalisableString ContinueEditing => new TranslatableString(getKey(@"continue_editing"), @"Oops, continue editing");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -42,7 +42,8 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "If enabled, an &quot;Are you ready? 3, 2, 1, GO!&quot; countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so." /// "If enabled, an &quot;Are you ready? 3, 2, 1, GO!&quot; countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so."
/// </summary> /// </summary>
public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"), @"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so."); public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"),
@"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so.");
/// <summary> /// <summary>
/// "Countdown speed" /// "Countdown speed"
@ -52,7 +53,8 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "If the countdown sounds off-time, use this to make it appear one or more beats early." /// "If the countdown sounds off-time, use this to make it appear one or more beats early."
/// </summary> /// </summary>
public static LocalisableString CountdownOffsetDescription => new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early."); public static LocalisableString CountdownOffsetDescription =>
new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early.");
/// <summary> /// <summary>
/// "Countdown offset" /// "Countdown offset"
@ -67,7 +69,8 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "Allows storyboards to use the full screen space, rather than be confined to a 4:3 area." /// "Allows storyboards to use the full screen space, rather than be confined to a 4:3 area."
/// </summary> /// </summary>
public static LocalisableString WidescreenSupportDescription => new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area."); public static LocalisableString WidescreenSupportDescription =>
new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area.");
/// <summary> /// <summary>
/// "Epilepsy warning" /// "Epilepsy warning"
@ -77,7 +80,8 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "Recommended if the storyboard or video contain scenes with rapidly flashing colours." /// "Recommended if the storyboard or video contain scenes with rapidly flashing colours."
/// </summary> /// </summary>
public static LocalisableString EpilepsyWarningDescription => new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours."); public static LocalisableString EpilepsyWarningDescription =>
new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours.");
/// <summary> /// <summary>
/// "Letterbox during breaks" /// "Letterbox during breaks"
@ -87,7 +91,8 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "Adds horizontal letterboxing to give a cinematic look during breaks." /// "Adds horizontal letterboxing to give a cinematic look during breaks."
/// </summary> /// </summary>
public static LocalisableString LetterboxDuringBreaksDescription => new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks."); public static LocalisableString LetterboxDuringBreaksDescription =>
new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks.");
/// <summary> /// <summary>
/// "Samples match playback rate" /// "Samples match playback rate"
@ -97,7 +102,8 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "When enabled, all samples will speed up or slow down when rate-changing mods are enabled." /// "When enabled, all samples will speed up or slow down when rate-changing mods are enabled."
/// </summary> /// </summary>
public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"), @"When enabled, all samples will speed up or slow down when rate-changing mods are enabled."); public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"),
@"When enabled, all samples will speed up or slow down when rate-changing mods are enabled.");
/// <summary> /// <summary>
/// "The size of all hit objects" /// "The size of all hit objects"
@ -117,7 +123,8 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "The harshness of hit windows and difficulty of special objects (ie. spinners)" /// "The harshness of hit windows and difficulty of special objects (ie. spinners)"
/// </summary> /// </summary>
public static LocalisableString OverallDifficultyDescription => new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)"); public static LocalisableString OverallDifficultyDescription =>
new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)");
/// <summary> /// <summary>
/// "Metadata" /// "Metadata"
@ -199,6 +206,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString DifficultyHeader => new TranslatableString(getKey(@"difficulty_header"), @"Difficulty"); public static LocalisableString DifficultyHeader => new TranslatableString(getKey(@"difficulty_header"), @"Difficulty");
/// <summary>
/// "Drag image here to set beatmap background!"
/// </summary>
public static LocalisableString DragToSetBackground => new TranslatableString(getKey(@"drag_to_set_background"), @"Drag image here to set beatmap background!");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -0,0 +1,99 @@
// 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 EditorStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.Editor";
/// <summary>
/// "Waveform opacity"
/// </summary>
public static LocalisableString WaveformOpacity => new TranslatableString(getKey(@"waveform_opacity"), @"Waveform opacity");
/// <summary>
/// "Show hit markers"
/// </summary>
public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers");
/// <summary>
/// "Timing"
/// </summary>
public static LocalisableString Timing => new TranslatableString(getKey(@"timing"), @"Timing");
/// <summary>
/// "Set preview point to current time"
/// </summary>
public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time");
/// <summary>
/// "Export package"
/// </summary>
public static LocalisableString ExportPackage => new TranslatableString(getKey(@"export_package"), @"Export package");
/// <summary>
/// "Create new difficulty"
/// </summary>
public static LocalisableString CreateNewDifficulty => new TranslatableString(getKey(@"create_new_difficulty"), @"Create new difficulty");
/// <summary>
/// "Change difficulty"
/// </summary>
public static LocalisableString ChangeDifficulty => new TranslatableString(getKey(@"change_difficulty"), @"Change difficulty");
/// <summary>
/// "Delete difficulty"
/// </summary>
public static LocalisableString DeleteDifficulty => new TranslatableString(getKey(@"delete_difficulty"), @"Delete difficulty");
/// <summary>
/// "setup"
/// </summary>
public static LocalisableString SetupScreen => new TranslatableString(getKey(@"setup_screen"), @"setup");
/// <summary>
/// "compose"
/// </summary>
public static LocalisableString ComposeScreen => new TranslatableString(getKey(@"compose_screen"), @"compose");
/// <summary>
/// "design"
/// </summary>
public static LocalisableString DesignScreen => new TranslatableString(getKey(@"design_screen"), @"design");
/// <summary>
/// "timing"
/// </summary>
public static LocalisableString TimingScreen => new TranslatableString(getKey(@"timing_screen"), @"timing");
/// <summary>
/// "verify"
/// </summary>
public static LocalisableString VerifyScreen => new TranslatableString(getKey(@"verify_screen"), @"verify");
/// <summary>
/// "Playback speed"
/// </summary>
public static LocalisableString PlaybackSpeed => new TranslatableString(getKey(@"playback_speed"), @"Playback speed");
/// <summary>
/// "Test!"
/// </summary>
public static LocalisableString TestBeatmap => new TranslatableString(getKey(@"test_beatmap"), @"Test!");
/// <summary>
/// "Waveform"
/// </summary>
public static LocalisableString TimelineWaveform => new TranslatableString(getKey(@"timeline_waveform"), @"Waveform");
/// <summary>
/// "Ticks"
/// </summary>
public static LocalisableString TimelineTicks => new TranslatableString(getKey(@"timeline_ticks"), @"Ticks");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -155,9 +155,9 @@ namespace osu.Game.Localisation
public static LocalisableString ToggleProfile => new TranslatableString(getKey(@"toggle_profile"), @"Toggle profile"); public static LocalisableString ToggleProfile => new TranslatableString(getKey(@"toggle_profile"), @"Toggle profile");
/// <summary> /// <summary>
/// "Pause gameplay" /// "Pause / resume gameplay"
/// </summary> /// </summary>
public static LocalisableString PauseGameplay => new TranslatableString(getKey(@"pause_gameplay"), @"Pause gameplay"); public static LocalisableString PauseGameplay => new TranslatableString(getKey(@"pause_gameplay"), @"Pause / resume gameplay");
/// <summary> /// <summary>
/// "Setup mode" /// "Setup mode"

View File

@ -0,0 +1,44 @@
// 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 SkinEditorStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.SkinEditor";
/// <summary>
/// "Skin editor"
/// </summary>
public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Skin editor");
/// <summary>
/// "Components"
/// </summary>
public static LocalisableString Components => new TranslatableString(getKey(@"components"), @"Components");
/// <summary>
/// "Scene library"
/// </summary>
public static LocalisableString SceneLibrary => new TranslatableString(getKey(@"scene_library"), @"Scene library");
/// <summary>
/// "Song Select"
/// </summary>
public static LocalisableString SongSelect => new TranslatableString(getKey(@"song_select"), @"Song Select");
/// <summary>
/// "Gameplay"
/// </summary>
public static LocalisableString Gameplay => new TranslatableString(getKey(@"gameplay"), @"Gameplay");
/// <summary>
/// "Settings ({0})"
/// </summary>
public static LocalisableString Settings(string arg0) => new TranslatableString(getKey(@"settings"), @"Settings ({0})", arg0);
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -19,6 +19,21 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Connecting => new TranslatableString(getKey(@"connecting"), @"Connecting..."); public static LocalisableString Connecting => new TranslatableString(getKey(@"connecting"), @"Connecting...");
/// <summary>
/// "home"
/// </summary>
public static LocalisableString HomeHeaderTitle => new TranslatableString(getKey(@"home_header_title"), @"home");
/// <summary>
/// "return to the main menu"
/// </summary>
public static LocalisableString HomeHeaderDescription => new TranslatableString(getKey(@"home_header_description"), @"return to the main menu");
/// <summary>
/// "play some {0}"
/// </summary>
public static LocalisableString PlaySomeRuleset(string arg0) => new TranslatableString(getKey(@"play_some_ruleset"), @"play some {0}", arg0);
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -0,0 +1,41 @@
// 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.Net.Http;
using osu.Framework.IO.Network;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class CommentPostRequest : APIRequest<CommentBundle>
{
public readonly CommentableType Commentable;
public readonly long CommentableId;
public readonly string Message;
public readonly long? ParentCommentId;
public CommentPostRequest(CommentableType commentable, long commentableId, string message, long? parentCommentId = null)
{
Commentable = commentable;
CommentableId = commentableId;
Message = message;
ParentCommentId = parentCommentId;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
req.AddParameter(@"comment[commentable_type]", Commentable.ToString().ToLowerInvariant());
req.AddParameter(@"comment[commentable_id]", $"{CommentableId}");
req.AddParameter(@"comment[message]", Message);
if (ParentCommentId.HasValue)
req.AddParameter(@"comment[parent_id]", $"{ParentCommentId}");
return req;
}
protected override string Target => "comments";
}
}

View File

@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses
set => Length = TimeSpan.FromSeconds(value).TotalMilliseconds; set => Length = TimeSpan.FromSeconds(value).TotalMilliseconds;
} }
[JsonProperty(@"convert")]
public bool Convert { get; set; }
[JsonProperty(@"count_circles")] [JsonProperty(@"count_circles")]
public int CircleCount { get; set; } public int CircleCount { get; set; }

View File

@ -125,6 +125,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"beatmaps")] [JsonProperty(@"beatmaps")]
public APIBeatmap[] Beatmaps { get; set; } = Array.Empty<APIBeatmap>(); public APIBeatmap[] Beatmaps { get; set; } = Array.Empty<APIBeatmap>();
[JsonProperty(@"converts")]
public APIBeatmap[]? Converts { get; set; }
private BeatmapMetadata metadata => new BeatmapMetadata private BeatmapMetadata metadata => new BeatmapMetadata
{ {
Title = Title, Title = Title,

View File

@ -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.
namespace osu.Game.Online
{
public class ExperimentalEndpointConfiguration : EndpointConfiguration
{
public ExperimentalEndpointConfiguration()
{
WebsiteRootUrl = @"https://osu.ppy.sh";
APIEndpointUrl = @"https://lazer.ppy.sh";
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
APIClientID = "5";
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";
MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer";
MetadataEndpointUrl = "https://spectator.ppy.sh/metadata";
}
}
}

View File

@ -1,16 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Online namespace osu.Game.Online
{ {
public class ProductionEndpointConfiguration : EndpointConfiguration public class ProductionEndpointConfiguration : EndpointConfiguration
{ {
public ProductionEndpointConfiguration() public ProductionEndpointConfiguration()
{ {
WebsiteRootUrl = @"https://osu.ppy.sh"; WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh";
APIEndpointUrl = @"https://lazer.ppy.sh";
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
APIClientID = "5"; APIClientID = "5";
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";

View File

@ -98,8 +98,8 @@ namespace osu.Game
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
internal EndpointConfiguration CreateEndpoints() => public virtual EndpointConfiguration CreateEndpoints() =>
UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ExperimentalEndpointConfiguration();
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework; using osu.Framework;
@ -38,10 +36,10 @@ namespace osu.Game.Overlays.BeatmapSet
public readonly DifficultiesContainer Difficulties; public readonly DifficultiesContainer Difficulties;
public readonly Bindable<APIBeatmap> Beatmap = new Bindable<APIBeatmap>(); public readonly Bindable<APIBeatmap?> Beatmap = new Bindable<APIBeatmap?>();
private APIBeatmapSet beatmapSet; private APIBeatmapSet? beatmapSet;
public APIBeatmapSet BeatmapSet public APIBeatmapSet? BeatmapSet
{ {
get => beatmapSet; get => beatmapSet;
set set
@ -142,7 +140,7 @@ namespace osu.Game.Overlays.BeatmapSet
} }
[Resolved] [Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } private IBindable<RulesetInfo> ruleset { get; set; } = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
@ -168,10 +166,11 @@ namespace osu.Game.Overlays.BeatmapSet
if (BeatmapSet != null) if (BeatmapSet != null)
{ {
Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Concat(BeatmapSet.Converts ?? Array.Empty<APIBeatmap>())
.Where(b => b.Ruleset.MatchesOnlineID(ruleset.Value)) .Where(b => b.Ruleset.MatchesOnlineID(ruleset.Value))
.OrderBy(b => b.StarRating) .OrderBy(b => !b.Convert)
.Select(b => new DifficultySelectorButton(b) .ThenBy(b => b.StarRating)
.Select(b => new DifficultySelectorButton(b, b.Convert ? new RulesetInfo { OnlineID = 0 } : null)
{ {
State = DifficultySelectorState.NotSelected, State = DifficultySelectorState.NotSelected,
OnHovered = beatmap => OnHovered = beatmap =>
@ -199,9 +198,9 @@ namespace osu.Game.Overlays.BeatmapSet
updateDifficultyButtons(); updateDifficultyButtons();
} }
private void showBeatmap(IBeatmapInfo beatmapInfo) private void showBeatmap(IBeatmapInfo? beatmapInfo)
{ {
version.Text = beatmapInfo?.DifficultyName; version.Text = beatmapInfo?.DifficultyName ?? string.Empty;
} }
private void updateDifficultyButtons() private void updateDifficultyButtons()
@ -211,7 +210,7 @@ namespace osu.Game.Overlays.BeatmapSet
public partial class DifficultiesContainer : FillFlowContainer<DifficultySelectorButton> public partial class DifficultiesContainer : FillFlowContainer<DifficultySelectorButton>
{ {
public Action OnLostHover; public Action? OnLostHover;
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
@ -232,9 +231,9 @@ namespace osu.Game.Overlays.BeatmapSet
public readonly APIBeatmap Beatmap; public readonly APIBeatmap Beatmap;
public Action<APIBeatmap> OnHovered; public Action<APIBeatmap>? OnHovered;
public Action<APIBeatmap> OnClicked; public Action<APIBeatmap>? OnClicked;
public event Action<DifficultySelectorState> StateChanged; public event Action<DifficultySelectorState>? StateChanged;
private DifficultySelectorState state; private DifficultySelectorState state;
@ -255,7 +254,7 @@ namespace osu.Game.Overlays.BeatmapSet
} }
} }
public DifficultySelectorButton(APIBeatmap beatmapInfo) public DifficultySelectorButton(APIBeatmap beatmapInfo, IRulesetInfo? ruleset)
{ {
Beatmap = beatmapInfo; Beatmap = beatmapInfo;
Size = new Vector2(size); Size = new Vector2(size);
@ -274,7 +273,7 @@ namespace osu.Game.Overlays.BeatmapSet
Alpha = 0.5f Alpha = 0.5f
} }
}, },
icon = new DifficultyIcon(beatmapInfo) icon = new DifficultyIcon(beatmapInfo, ruleset)
{ {
ShowTooltip = false, ShowTooltip = false,
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) }, Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },

View File

@ -68,11 +68,12 @@ namespace osu.Game.Overlays.BeatmapSet
BeatmapSet.BindValueChanged(setInfo => BeatmapSet.BindValueChanged(setInfo =>
{ {
int beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.MatchesOnlineID(Value)) ?? 0; int beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.MatchesOnlineID(Value)) ?? 0;
int osuBeatmaps = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.OnlineID == 0) ?? 0;
count.Text = beatmapsCount.ToString(); count.Text = beatmapsCount.ToString();
countContainer.FadeTo(beatmapsCount > 0 ? 1 : 0); countContainer.FadeTo(beatmapsCount > 0 ? 1 : 0);
Enabled.Value = beatmapsCount > 0; Enabled.Value = beatmapsCount > 0 || osuBeatmaps > 0;
}, true); }, true);
} }
} }

View File

@ -3,6 +3,7 @@
#nullable disable #nullable disable
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -17,16 +18,22 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Threading; using osu.Framework.Threading;
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Overlays.Comments namespace osu.Game.Overlays.Comments
{ {
[Cached]
public partial class CommentsContainer : CompositeDrawable public partial class CommentsContainer : CompositeDrawable
{ {
private readonly Bindable<CommentableType> type = new Bindable<CommentableType>(); private readonly Bindable<CommentableType> type = new Bindable<CommentableType>();
private readonly BindableLong id = new BindableLong(); private readonly BindableLong id = new BindableLong();
public IBindable<CommentableType> Type => type;
public IBindable<long> Id => id;
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>(); public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
public readonly BindableBool ShowDeleted = new BindableBool(); public readonly BindableBool ShowDeleted = new BindableBool();
@ -46,12 +53,14 @@ namespace osu.Game.Overlays.Comments
private DeletedCommentsCounter deletedCommentsCounter; private DeletedCommentsCounter deletedCommentsCounter;
private CommentsShowMoreButton moreButton; private CommentsShowMoreButton moreButton;
private TotalCommentsCounter commentCounter; private TotalCommentsCounter commentCounter;
private UpdateableAvatar avatar;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new Box new Box
@ -86,6 +95,32 @@ namespace osu.Game.Overlays.Comments
}, },
}, },
}, },
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 50, Vertical = 20 },
Children = new Drawable[]
{
avatar = new UpdateableAvatar(api.LocalUser.Value)
{
Size = new Vector2(50),
CornerExponent = 2,
CornerRadius = 25,
Masking = true,
},
new Container
{
Padding = new MarginPadding { Left = 60 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new NewCommentEditor
{
OnPost = prependPostedComments
}
}
}
},
new CommentsHeader new CommentsHeader
{ {
Sort = { BindTarget = Sort }, Sort = { BindTarget = Sort },
@ -151,6 +186,7 @@ namespace osu.Game.Overlays.Comments
protected override void LoadComplete() protected override void LoadComplete()
{ {
User.BindValueChanged(_ => refetchComments()); User.BindValueChanged(_ => refetchComments());
User.BindValueChanged(e => avatar.User = e.NewValue);
Sort.BindValueChanged(_ => refetchComments(), true); Sort.BindValueChanged(_ => refetchComments(), true);
base.LoadComplete(); base.LoadComplete();
} }
@ -245,7 +281,6 @@ namespace osu.Game.Overlays.Comments
{ {
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned)); pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
content.AddRange(loaded.Where(d => !d.Comment.Pinned)); content.AddRange(loaded.Where(d => !d.Comment.Pinned));
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel); deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
if (bundle.HasMore) if (bundle.HasMore)
@ -288,6 +323,34 @@ namespace osu.Game.Overlays.Comments
} }
} }
private void prependPostedComments(CommentBundle bundle)
{
var topLevelComments = new List<DrawableComment>();
foreach (var comment in bundle.Comments)
{
// Exclude possible duplicated comments.
if (CommentDictionary.ContainsKey(comment.Id))
continue;
topLevelComments.Add(getDrawableComment(comment));
}
if (topLevelComments.Any())
{
LoadComponentsAsync(topLevelComments, loaded =>
{
if (content.Count > 0 && content[0] is NoCommentsPlaceholder placeholder)
content.Remove(placeholder, true);
foreach (var comment in loaded)
{
content.Insert((int)-Clock.CurrentTime, comment);
}
}, (loadCancellation = new CancellationTokenSource()).Token);
}
}
private DrawableComment getDrawableComment(Comment comment) private DrawableComment getDrawableComment(Comment comment)
{ {
if (CommentDictionary.TryGetValue(comment.Id, out var existing)) if (CommentDictionary.TryGetValue(comment.Id, out var existing))
@ -317,7 +380,7 @@ namespace osu.Game.Overlays.Comments
base.Dispose(isDisposing); base.Dispose(isDisposing);
} }
private partial class NoCommentsPlaceholder : CompositeDrawable internal partial class NoCommentsPlaceholder : CompositeDrawable
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@ -336,5 +399,41 @@ namespace osu.Game.Overlays.Comments
}); });
} }
} }
private partial class NewCommentEditor : CommentEditor
{
[Resolved]
private CommentsContainer commentsContainer { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
public Action<CommentBundle> OnPost;
//TODO should match web, left empty due to no multiline support
protected override LocalisableString FooterText => default;
protected override LocalisableString CommitButtonText => CommonStrings.ButtonsPost;
protected override LocalisableString TextBoxPlaceholder => CommentsStrings.PlaceholderNew;
protected override void OnCommit(string text)
{
ShowLoadingSpinner = true;
CommentPostRequest req = new CommentPostRequest(commentsContainer.Type.Value, commentsContainer.Id.Value, text);
req.Failure += e => Schedule(() =>
{
ShowLoadingSpinner = false;
Logger.Error(e, "Posting comment failed.");
});
req.Success += cb => Schedule(() =>
{
ShowLoadingSpinner = false;
Current.Value = string.Empty;
OnPost?.Invoke(cb);
});
api.Queue(req);
}
}
} }
} }

View File

@ -57,6 +57,11 @@ namespace osu.Game.Overlays.Comments
/// </summary> /// </summary>
public bool WasDeleted { get; protected set; } public bool WasDeleted { get; protected set; }
/// <summary>
/// Tracks this comment's level of nesting. 0 means that this comment has no parents.
/// </summary>
public int Level { get; private set; }
private FillFlowContainer childCommentsVisibilityContainer = null!; private FillFlowContainer childCommentsVisibilityContainer = null!;
private FillFlowContainer childCommentsContainer = null!; private FillFlowContainer childCommentsContainer = null!;
private LoadRepliesButton loadRepliesButton = null!; private LoadRepliesButton loadRepliesButton = null!;
@ -70,7 +75,7 @@ namespace osu.Game.Overlays.Comments
private GridContainer content = null!; private GridContainer content = null!;
private VotePill votePill = null!; private VotePill votePill = null!;
[Resolved(canBeNull: true)] [Resolved]
private IDialogOverlay? dialogOverlay { get; set; } private IDialogOverlay? dialogOverlay { get; set; }
[Resolved] [Resolved]
@ -79,7 +84,7 @@ namespace osu.Game.Overlays.Comments
[Resolved] [Resolved]
private GameHost host { get; set; } = null!; private GameHost host { get; set; } = null!;
[Resolved(canBeNull: true)] [Resolved]
private OnScreenDisplay? onScreenDisplay { get; set; } private OnScreenDisplay? onScreenDisplay { get; set; }
public DrawableComment(Comment comment) public DrawableComment(Comment comment)
@ -88,12 +93,16 @@ namespace osu.Game.Overlays.Comments
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider, DrawableComment? parentComment)
{ {
LinkFlowContainer username; LinkFlowContainer username;
FillFlowContainer info; FillFlowContainer info;
CommentMarkdownContainer message; CommentMarkdownContainer message;
Level = parentComment?.Level + 1 ?? 0;
float childrenPadding = Level < 6 ? 20 : 5;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
@ -248,7 +257,7 @@ namespace osu.Game.Overlays.Comments
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Padding = new MarginPadding { Left = 20 }, Padding = new MarginPadding { Left = childrenPadding },
Children = new Drawable[] Children = new Drawable[]
{ {
childCommentsContainer = new FillFlowContainer childCommentsContainer = new FillFlowContainer

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osuTK; using osuTK;
@ -15,6 +16,8 @@ namespace osu.Game.Overlays.Profile.Header
{ {
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(); public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
private LevelBadge levelBadge = null!;
public CentreHeaderContainer() public CentreHeaderContainer()
{ {
Height = 60; Height = 60;
@ -62,12 +65,11 @@ namespace osu.Game.Overlays.Profile.Header
Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN },
Children = new Drawable[] Children = new Drawable[]
{ {
new LevelBadge levelBadge = new LevelBadge
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Size = new Vector2(40), Size = new Vector2(40)
User = { BindTarget = User }
}, },
new Container new Container
{ {
@ -86,5 +88,17 @@ namespace osu.Game.Overlays.Profile.Header
} }
}; };
} }
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(user => updateDisplay(user.NewValue?.User), true);
}
private void updateDisplay(APIUser? user)
{
levelBadge.LevelInfo.Value = user?.Statistics?.Level;
}
} }
} }

View File

@ -0,0 +1,110 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class ExtendedDetails : CompositeDrawable
{
public Bindable<UserProfileData?> User { get; } = new Bindable<UserProfileData?>();
private SpriteText rankedScore = null!;
private SpriteText hitAccuracy = null!;
private SpriteText playCount = null!;
private SpriteText totalScore = null!;
private SpriteText totalHits = null!;
private SpriteText maximumCombo = null!;
private SpriteText replaysWatched = null!;
[BackgroundDependencyLoader]
private void load()
{
var font = OsuFont.Default.With(size: 12);
const float vertical_spacing = 4;
AutoSizeAxes = Axes.Both;
// this should really be a grid, but trying to avoid one to avoid the performance hit.
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20, 0),
Children = new[]
{
new FillFlowContainer
{
Name = @"Labels",
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, vertical_spacing),
Children = new Drawable[]
{
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsRankedScore },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsHitAccuracy },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsPlayCount },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsTotalScore },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsTotalHits },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsMaximumCombo },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsReplaysWatchedByOthers },
}
},
new FillFlowContainer
{
Name = @"Values",
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, vertical_spacing),
Children = new Drawable[]
{
rankedScore = new OsuSpriteText { Font = font },
hitAccuracy = new OsuSpriteText { Font = font },
playCount = new OsuSpriteText { Font = font },
totalScore = new OsuSpriteText { Font = font },
totalHits = new OsuSpriteText { Font = font },
maximumCombo = new OsuSpriteText { Font = font },
replaysWatched = new OsuSpriteText { Font = font },
}
},
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(user => updateStatistics(user.NewValue?.User.Statistics), true);
}
private void updateStatistics(UserStatistics? statistics)
{
if (statistics == null)
{
Alpha = 0;
return;
}
Alpha = 1;
rankedScore.Text = statistics.RankedScore.ToLocalisableString(@"N0");
hitAccuracy.Text = statistics.DisplayAccuracy;
playCount.Text = statistics.PlayCount.ToLocalisableString(@"N0");
totalScore.Text = statistics.TotalScore.ToLocalisableString(@"N0");
totalHits.Text = statistics.TotalHits.ToLocalisableString(@"N0");
maximumCombo.Text = statistics.MaxCombo.ToLocalisableString(@"N0");
replaysWatched.Text = statistics.ReplaysWatched.ToLocalisableString(@"N0");
}
}
}

View File

@ -11,14 +11,14 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
{ {
public partial class LevelBadge : CompositeDrawable, IHasTooltip public partial class LevelBadge : CompositeDrawable, IHasTooltip
{ {
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(); public readonly Bindable<UserStatistics.LevelInfo?> LevelInfo = new Bindable<UserStatistics.LevelInfo?>();
public LocalisableString TooltipText { get; private set; } public LocalisableString TooltipText { get; private set; }
@ -47,13 +47,18 @@ namespace osu.Game.Overlays.Profile.Header.Components
Font = OsuFont.GetFont(size: 20) Font = OsuFont.GetFont(size: 20)
} }
}; };
User.BindValueChanged(user => updateLevel(user.NewValue?.User));
} }
private void updateLevel(APIUser? user) protected override void LoadComplete()
{ {
string level = user?.Statistics?.Level.Current.ToString() ?? "0"; base.LoadComplete();
LevelInfo.BindValueChanged(level => updateLevel(level.NewValue), true);
}
private void updateLevel(UserStatistics.LevelInfo? levelInfo)
{
string level = levelInfo?.Current.ToString() ?? "0";
levelText.Text = level; levelText.Text = level;
TooltipText = UsersStrings.ShowStatsLevel(level); TooltipText = UsersStrings.ShowStatsLevel(level);
} }

View File

@ -0,0 +1,186 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class MainDetails : CompositeDrawable
{
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
private ProfileValueDisplay medalInfo = null!;
private ProfileValueDisplay ppInfo = null!;
private ProfileValueDisplay detailGlobalRank = null!;
private ProfileValueDisplay detailCountryRank = null!;
private RankGraph rankGraph = null!;
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
AutoSizeDuration = 200,
AutoSizeEasing = Easing.OutQuint,
Masking = true,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 15),
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new Drawable[]
{
detailGlobalRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankGlobalSimple,
},
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
}
},
new Container
{
RelativeSizeAxes = Axes.X,
Height = 60,
Children = new Drawable[]
{
rankGraph = new RankGraph
{
RelativeSizeAxes = Axes.Both,
},
}
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
medalInfo = new ProfileValueDisplay
{
Title = UsersStrings.ShowStatsMedals,
},
ppInfo = new ProfileValueDisplay
{
Title = "pp",
},
new TotalPlayTime
{
User = { BindTarget = User }
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new[]
{
scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH),
scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X),
scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH),
scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S),
scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A),
}
}
}
},
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(e => updateDisplay(e.NewValue), true);
}
private void updateDisplay(UserProfileData? data)
{
var user = data?.User;
medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0";
ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
foreach (var scoreRankInfo in scoreRankInfos)
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
rankGraph.Statistics.Value = user?.Statistics;
}
private partial class ScoreRankInfo : CompositeDrawable
{
private readonly OsuSpriteText rankCount;
public int RankCount
{
set => rankCount.Text = value.ToLocalisableString("#,##0");
}
public ScoreRankInfo(ScoreRank rank)
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
Width = 44,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DrawableRank(rank)
{
RelativeSizeAxes = Axes.X,
Height = 22,
},
rankCount = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
}
};
}
}
}
}

View File

@ -1,33 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Overlays.Profile.Header namespace osu.Game.Overlays.Profile.Header
{ {
public partial class DetailHeaderContainer : CompositeDrawable public partial class DetailHeaderContainer : CompositeDrawable
{ {
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
private ProfileValueDisplay medalInfo = null!;
private ProfileValueDisplay ppInfo = null!;
private ProfileValueDisplay detailGlobalRank = null!;
private ProfileValueDisplay detailCountryRank = null!;
private RankGraph rankGraph = null!;
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(); public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -35,8 +19,6 @@ namespace osu.Game.Overlays.Profile.Header
{ {
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
User.ValueChanged += e => updateDisplay(e.NewValue);
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box new Box
@ -44,149 +26,52 @@ namespace osu.Game.Overlays.Profile.Header
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5, Colour = colourProvider.Background5,
}, },
new FillFlowContainer new Container
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
AutoSizeDuration = 200,
AutoSizeEasing = Easing.OutQuint,
Masking = true,
Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 },
Direction = FillDirection.Vertical, Child = new GridContainer
Spacing = new Vector2(0, 15),
Children = new Drawable[]
{ {
new FillFlowContainer RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
RowDimensions = new[]
{ {
RelativeSizeAxes = Axes.X, new Dimension(GridSizeMode.AutoSize),
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new Drawable[]
{
detailGlobalRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankGlobalSimple,
},
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
}
}, },
new Container ColumnDimensions = new[]
{ {
RelativeSizeAxes = Axes.X, new Dimension(),
Height = 60, new Dimension(GridSizeMode.AutoSize),
Children = new Drawable[] new Dimension(GridSizeMode.AutoSize),
{
rankGraph = new RankGraph
{
RelativeSizeAxes = Axes.Both,
},
}
}, },
new Container Content = new[]
{ {
RelativeSizeAxes = Axes.X, new Drawable[]
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{ {
new FillFlowContainer new MainDetails
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User }
},
new Box
{
RelativeSizeAxes = Axes.Y,
Width = 2,
Colour = colourProvider.Background6,
Margin = new MarginPadding { Horizontal = 15 }
},
new ExtendedDetails
{ {
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Direction = FillDirection.Horizontal, User = { BindTarget = User }
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
medalInfo = new ProfileValueDisplay
{
Title = UsersStrings.ShowStatsMedals,
},
ppInfo = new ProfileValueDisplay
{
Title = "pp",
},
new TotalPlayTime
{
User = { BindTarget = User }
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new[]
{
scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH),
scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X),
scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH),
scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S),
scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A),
}
} }
} }
},
}
},
};
}
private void updateDisplay(UserProfileData? data)
{
var user = data?.User;
medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0";
ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
foreach (var scoreRankInfo in scoreRankInfos)
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
rankGraph.Statistics.Value = user?.Statistics;
}
private partial class ScoreRankInfo : CompositeDrawable
{
private readonly OsuSpriteText rankCount;
public int RankCount
{
set => rankCount.Text = value.ToLocalisableString("#,##0");
}
public ScoreRankInfo(ScoreRank rank)
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
Width = 44,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DrawableRank(rank)
{
RelativeSizeAxes = Axes.X,
Height = 22,
},
rankCount = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
} }
} }
}; }
} };
} }
} }
} }

View File

@ -5,18 +5,15 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users.Drawables; using osu.Game.Users.Drawables;
using osuTK; using osuTK;
@ -38,7 +35,6 @@ namespace osu.Game.Overlays.Profile.Header
private OsuSpriteText titleText = null!; private OsuSpriteText titleText = null!;
private UpdateableFlag userFlag = null!; private UpdateableFlag userFlag = null!;
private OsuSpriteText userCountryText = null!; private OsuSpriteText userCountryText = null!;
private FillFlowContainer userStats = null!;
private GroupBadgeFlow groupBadgeFlow = null!; private GroupBadgeFlow groupBadgeFlow = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -164,16 +160,6 @@ namespace osu.Game.Overlays.Profile.Header
} }
} }
}, },
userStats = new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Y,
Width = 300,
Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN },
Padding = new MarginPadding { Vertical = 15 },
Spacing = new Vector2(0, 2)
}
}; };
User.BindValueChanged(user => updateUser(user.NewValue)); User.BindValueChanged(user => updateUser(user.NewValue));
@ -192,43 +178,6 @@ namespace osu.Game.Overlays.Profile.Header
titleText.Text = user?.Title ?? string.Empty; titleText.Text = user?.Title ?? string.Empty;
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");
groupBadgeFlow.User.Value = user; groupBadgeFlow.User.Value = user;
userStats.Clear();
if (user?.Statistics != null)
{
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsRankedScore, user.Statistics.RankedScore.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsHitAccuracy, user.Statistics.DisplayAccuracy));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsPlayCount, user.Statistics.PlayCount.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalScore, user.Statistics.TotalScore.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalHits, user.Statistics.TotalHits.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsMaximumCombo, user.Statistics.MaxCombo.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsReplaysWatchedByOthers, user.Statistics.ReplaysWatched.ToLocalisableString("#,##0")));
}
}
private partial class UserStatsLine : Container
{
public UserStatsLine(LocalisableString left, LocalisableString right)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 15),
Text = left,
},
new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
Text = right,
},
};
}
} }
} }
} }

View File

@ -6,6 +6,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
@ -17,6 +18,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osuTK; using osuTK;
using osu.Game.Localisation;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -96,7 +98,7 @@ namespace osu.Game.Overlays
FinishTransforms(true); FinishTransforms(true);
} }
public override LocalisableString TooltipText => "revert to default"; public override LocalisableString TooltipText => CommonStrings.RevertToDefault.ToLower();
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {

View File

@ -135,12 +135,14 @@ namespace osu.Game.Overlays
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
updateFadeState(); updateFadeState();
updateExpandedState(true);
return false; return false;
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
updateFadeState(); updateFadeState();
updateExpandedState(true);
base.OnHoverLost(e); base.OnHoverLost(e);
} }
@ -168,7 +170,7 @@ namespace osu.Game.Overlays
// potentially continuing to get processed while content has changed to autosize. // potentially continuing to get processed while content has changed to autosize.
content.ClearTransforms(); content.ClearTransforms();
if (Expanded.Value) if (Expanded.Value || IsHovered)
{ {
content.AutoSizeAxes = Axes.Y; content.AutoSizeAxes = Axes.Y;
content.AutoSizeDuration = animate ? transition_duration : 0; content.AutoSizeDuration = animate ? transition_duration : 0;

View File

@ -5,6 +5,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
@ -19,8 +20,8 @@ namespace osu.Game.Overlays.Toolbar
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
TooltipMain = "home"; TooltipMain = ToolbarStrings.HomeHeaderTitle;
TooltipSub = "return to the main menu"; TooltipSub = ToolbarStrings.HomeHeaderDescription;
SetIcon("Icons/Hexacons/home"); SetIcon("Icons/Hexacons/home");
} }
} }

View File

@ -3,12 +3,13 @@
#nullable disable #nullable disable
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Localisation;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
@ -29,7 +30,7 @@ namespace osu.Game.Overlays.Toolbar
var rInstance = value.CreateInstance(); var rInstance = value.CreateInstance();
ruleset.TooltipMain = rInstance.Description; ruleset.TooltipMain = rInstance.Description;
ruleset.TooltipSub = $"play some {rInstance.Description}"; ruleset.TooltipSub = ToolbarStrings.PlaySomeRuleset(rInstance.Description);
ruleset.SetIcon(rInstance.CreateIcon()); ruleset.SetIcon(rInstance.CreateIcon());
} }

View File

@ -5,17 +5,18 @@ using System.Collections.Generic;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
internal class BackgroundDimMenuItem : MenuItem internal class BackgroundDimMenuItem : MenuItem
{ {
private readonly Bindable<float> backgroudDim; private readonly Bindable<float> backgroundDim;
private readonly Dictionary<float, TernaryStateRadioMenuItem> menuItemLookup = new Dictionary<float, TernaryStateRadioMenuItem>(); private readonly Dictionary<float, TernaryStateRadioMenuItem> menuItemLookup = new Dictionary<float, TernaryStateRadioMenuItem>();
public BackgroundDimMenuItem(Bindable<float> backgroudDim) public BackgroundDimMenuItem(Bindable<float> backgroundDim)
: base("Background dim") : base(GameplaySettingsStrings.BackgroundDim)
{ {
Items = new[] Items = new[]
{ {
@ -25,8 +26,8 @@ namespace osu.Game.Screens.Edit
createMenuItem(0.75f), createMenuItem(0.75f),
}; };
this.backgroudDim = backgroudDim; this.backgroundDim = backgroundDim;
backgroudDim.BindValueChanged(dim => backgroundDim.BindValueChanged(dim =>
{ {
foreach (var kvp in menuItemLookup) foreach (var kvp in menuItemLookup)
kvp.Value.State.Value = kvp.Key == dim.NewValue ? TernaryState.True : TernaryState.False; kvp.Value.State.Value = kvp.Key == dim.NewValue ? TernaryState.True : TernaryState.False;
@ -40,6 +41,6 @@ namespace osu.Game.Screens.Edit
return item; return item;
} }
private void updateOpacity(float dim) => backgroudDim.Value = dim; private void updateOpacity(float dim) => backgroundDim.Value = dim;
} }
} }

View File

@ -2,21 +2,20 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Edit.Components.Menus namespace osu.Game.Screens.Edit.Components.Menus
{ {
public class EditorMenuItem : OsuMenuItem public class EditorMenuItem : OsuMenuItem
{ {
private const int min_text_length = 40; public EditorMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard)
: base(text, type)
public EditorMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: base(text.PadRight(min_text_length), type)
{ {
} }
public EditorMenuItem(string text, MenuItemType type, Action action) public EditorMenuItem(LocalisableString text, MenuItemType type, Action action)
: base(text.PadRight(min_text_length), type, action) : base(text, type, action)
{ {
} }
} }

View File

@ -56,11 +56,6 @@ namespace osu.Game.Screens.Edit.Components.Menus
Bar.Expire(); Bar.Expire();
} }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
}
protected override void OnActivated() protected override void OnActivated()
{ {
base.OnActivated(); base.OnActivated();

View File

@ -16,6 +16,7 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
using osuTK.Input; using osuTK.Input;
@ -47,7 +48,7 @@ namespace osu.Game.Screens.Edit.Components
new OsuSpriteText new OsuSpriteText
{ {
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Text = "Playback speed", Text = EditorStrings.PlaybackSpeed,
RelativePositionAxes = Axes.Y, RelativePositionAxes = Axes.Y,
Y = 0.5f, Y = 0.5f,
Padding = new MarginPadding { Left = 45 } Padding = new MarginPadding { Left = 45 }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
namespace osu.Game.Screens.Edit.Components.Timelines.Summary namespace osu.Game.Screens.Edit.Components.Timelines.Summary
@ -30,7 +31,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
Content.CornerRadius = 0; Content.CornerRadius = 0;
Text = "Test!"; Text = EditorStrings.TestBeatmap;
} }
} }
} }

View File

@ -9,7 +9,9 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Resources.Localisation.Web;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
@ -75,17 +77,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
waveformCheckbox = new OsuCheckbox waveformCheckbox = new OsuCheckbox
{ {
LabelText = "Waveform", LabelText = EditorStrings.TimelineWaveform,
Current = { Value = true }, Current = { Value = true },
}, },
ticksCheckbox = new OsuCheckbox ticksCheckbox = new OsuCheckbox
{ {
LabelText = "Ticks", LabelText = EditorStrings.TimelineTicks,
Current = { Value = true }, Current = { Value = true },
}, },
controlPointsCheckbox = new OsuCheckbox controlPointsCheckbox = new OsuCheckbox
{ {
LabelText = "BPM", LabelText = BeatmapsetsStrings.ShowStatsBpm,
Current = { Value = true }, Current = { Value = true },
}, },
} }

View File

@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Localisation;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -20,7 +19,7 @@ namespace osu.Game.Screens.Edit
public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty) public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty)
{ {
HeaderText = "Would you like to create a blank difficulty?"; HeaderText = EditorDialogsStrings.NewDifficultyDialogHeader;
Icon = FontAwesome.Regular.Clone; Icon = FontAwesome.Regular.Clone;
@ -28,17 +27,17 @@ namespace osu.Game.Screens.Edit
{ {
new PopupDialogOkButton new PopupDialogOkButton
{ {
Text = "Yeah, let's start from scratch!", Text = EditorDialogsStrings.CreateNew,
Action = () => createNewDifficulty.Invoke(false) Action = () => createNewDifficulty.Invoke(false)
}, },
new PopupDialogCancelButton new PopupDialogCancelButton
{ {
Text = "No, create an exact copy of this difficulty", Text = EditorDialogsStrings.CreateCopy,
Action = () => createNewDifficulty.Invoke(true) Action = () => createNewDifficulty.Invoke(true)
}, },
new PopupDialogCancelButton new PopupDialogCancelButton
{ {
Text = "I changed my mind, I want to keep editing this difficulty", Text = EditorDialogsStrings.KeepEditing,
Action = () => { } Action = () => { }
} }
}; };

View File

@ -51,7 +51,7 @@ using osu.Game.Screens.Edit.Verify;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Users; using osu.Game.Users;
using osuTK.Input; using osuTK.Input;
using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -294,40 +294,40 @@ namespace osu.Game.Screens.Edit
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Items = new[] Items = new[]
{ {
new MenuItem("File") new MenuItem(CommonStrings.MenuBarFile)
{ {
Items = createFileMenuItems() Items = createFileMenuItems()
}, },
new MenuItem(CommonStrings.ButtonsEdit) new MenuItem(CommonStrings.MenuBarEdit)
{ {
Items = new[] Items = new[]
{ {
undoMenuItem = new EditorMenuItem("Undo", MenuItemType.Standard, Undo), undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
redoMenuItem = new EditorMenuItem("Redo", MenuItemType.Standard, Redo), redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo),
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut), cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut),
copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy), copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy),
pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste), pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste),
cloneMenuItem = new EditorMenuItem("Clone", MenuItemType.Standard, Clone), cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone),
} }
}, },
new MenuItem("View") new MenuItem(CommonStrings.MenuBarView)
{ {
Items = new MenuItem[] Items = new MenuItem[]
{ {
new WaveformOpacityMenuItem(config.GetBindable<float>(OsuSetting.EditorWaveformOpacity)), new WaveformOpacityMenuItem(config.GetBindable<float>(OsuSetting.EditorWaveformOpacity)),
new BackgroundDimMenuItem(editorBackgroundDim), new BackgroundDimMenuItem(editorBackgroundDim),
new ToggleMenuItem("Show hit markers") new ToggleMenuItem(EditorStrings.ShowHitMarkers)
{ {
State = { BindTarget = editorHitMarkers }, State = { BindTarget = editorHitMarkers },
} }
} }
}, },
new MenuItem("Timing") new MenuItem(EditorStrings.Timing)
{ {
Items = new MenuItem[] Items = new MenuItem[]
{ {
new EditorMenuItem("Set preview point to current time", MenuItemType.Standard, SetPreviewPointToCurrentTime) new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime)
} }
} }
} }
@ -344,7 +344,6 @@ namespace osu.Game.Screens.Edit
bottomBar = new BottomBar(), bottomBar = new BottomBar(),
} }
}); });
changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
@ -716,7 +715,7 @@ namespace osu.Game.Screens.Edit
if (!(refetchedBeatmap is DummyWorkingBeatmap)) if (!(refetchedBeatmap is DummyWorkingBeatmap))
{ {
Logger.Log("Editor providing re-fetched beatmap post edit session"); Logger.Log(@"Editor providing re-fetched beatmap post edit session");
Beatmap.Value = refetchedBeatmap; Beatmap.Value = refetchedBeatmap;
} }
} }
@ -952,15 +951,15 @@ namespace osu.Game.Screens.Edit
private List<MenuItem> createFileMenuItems() => new List<MenuItem> private List<MenuItem> createFileMenuItems() => new List<MenuItem>
{ {
new EditorMenuItem("Save", MenuItemType.Standard, () => Save()), new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, new EditorMenuItem(EditorStrings.ExportPackage, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
createDifficultyCreationMenu(), createDifficultyCreationMenu(),
createDifficultySwitchMenu(), createDifficultySwitchMenu(),
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem("Delete difficulty", MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } }, new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } },
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit) new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit)
}; };
private void exportBeatmap() private void exportBeatmap()
@ -1009,7 +1008,7 @@ namespace osu.Game.Screens.Edit
foreach (var ruleset in rulesets.AvailableRulesets) foreach (var ruleset in rulesets.AvailableRulesets)
rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => CreateNewDifficulty(ruleset))); rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => CreateNewDifficulty(ruleset)));
return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; return new EditorMenuItem(EditorStrings.CreateNewDifficulty) { Items = rulesetItems };
} }
protected void CreateNewDifficulty(RulesetInfo rulesetInfo) protected void CreateNewDifficulty(RulesetInfo rulesetInfo)
@ -1045,7 +1044,7 @@ namespace osu.Game.Screens.Edit
} }
} }
return new EditorMenuItem("Change difficulty") { Items = difficultyItems }; return new EditorMenuItem(EditorStrings.ChangeDifficulty) { Items = difficultyItems };
} }
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset)); protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset));

View File

@ -1,27 +1,26 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable using osu.Framework.Localisation;
using osu.Game.Localisation;
using System.ComponentModel;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
public enum EditorScreenMode public enum EditorScreenMode
{ {
[Description("setup")] [LocalisableDescription(typeof(EditorStrings), nameof(EditorStrings.SetupScreen))]
SongSetup, SongSetup,
[Description("compose")] [LocalisableDescription(typeof(EditorStrings), nameof(EditorStrings.ComposeScreen))]
Compose, Compose,
[Description("design")] [LocalisableDescription(typeof(EditorStrings), nameof(EditorStrings.DesignScreen))]
Design, Design,
[Description("timing")] [LocalisableDescription(typeof(EditorStrings), nameof(EditorStrings.TimingScreen))]
Timing, Timing,
[Description("verify")] [LocalisableDescription(typeof(EditorStrings), nameof(EditorStrings.VerifyScreen))]
Verify, Verify,
} }
} }

View File

@ -1,11 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Localisation;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -13,7 +12,7 @@ namespace osu.Game.Screens.Edit
{ {
public PromptForSaveDialog(Action exit, Action saveAndExit, Action cancel) public PromptForSaveDialog(Action exit, Action saveAndExit, Action cancel)
{ {
HeaderText = "Did you want to save your changes?"; HeaderText = EditorDialogsStrings.SaveDialogHeader;
Icon = FontAwesome.Regular.Save; Icon = FontAwesome.Regular.Save;
@ -21,17 +20,17 @@ namespace osu.Game.Screens.Edit
{ {
new PopupDialogOkButton new PopupDialogOkButton
{ {
Text = @"Save my masterpiece!", Text = EditorDialogsStrings.Save,
Action = saveAndExit Action = saveAndExit
}, },
new PopupDialogDangerousButton new PopupDialogDangerousButton
{ {
Text = @"Forget all changes", Text = EditorDialogsStrings.ForgetAllChanges,
Action = exit Action = exit
}, },
new PopupDialogCancelButton new PopupDialogCancelButton
{ {
Text = @"Oops, continue editing", Text = EditorDialogsStrings.ContinueEditing,
Action = cancel Action = cancel
}, },
}; };

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
namespace osu.Game.Screens.Edit.Setup namespace osu.Game.Screens.Edit.Setup
{ {
@ -61,7 +62,7 @@ namespace osu.Game.Screens.Edit.Setup
}, },
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24))
{ {
Text = "Drag image here to set beatmap background!", Text = EditorSetupStrings.DragToSetBackground,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both AutoSizeAxes = Axes.Both

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -17,7 +18,7 @@ namespace osu.Game.Screens.Edit
private readonly Dictionary<float, TernaryStateRadioMenuItem> menuItemLookup = new Dictionary<float, TernaryStateRadioMenuItem>(); private readonly Dictionary<float, TernaryStateRadioMenuItem> menuItemLookup = new Dictionary<float, TernaryStateRadioMenuItem>();
public WaveformOpacityMenuItem(Bindable<float> waveformOpacity) public WaveformOpacityMenuItem(Bindable<float> waveformOpacity)
: base("Waveform opacity") : base(EditorStrings.WaveformOpacity)
{ {
Items = new[] Items = new[]
{ {

View File

@ -173,7 +173,7 @@ namespace osu.Game.Screens.OnlinePlay
IsValidMod = IsValidMod IsValidMod = IsValidMod
}; };
protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateFooterButtons()
{ {
var buttons = base.CreateFooterButtons().ToList(); var buttons = base.CreateFooterButtons().ToList();
buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay)); buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay));

View File

@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play
/// <summary> /// <summary>
/// Action that is invoked when <see cref="GlobalAction.Back"/> is triggered. /// Action that is invoked when <see cref="GlobalAction.Back"/> is triggered.
/// </summary> /// </summary>
protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.TriggerClick(); protected virtual Action BackAction => () => InternalButtons.LastOrDefault()?.TriggerClick();
/// <summary> /// <summary>
/// Action that is invoked when <see cref="GlobalAction.Select"/> is triggered. /// Action that is invoked when <see cref="GlobalAction.Select"/> is triggered.
@ -189,7 +189,7 @@ namespace osu.Game.Screens.Play
InternalButtons.Add(button); InternalButtons.Add(button);
} }
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
switch (e.Action) switch (e.Action)
{ {

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD
{ {
//CollectionSettings = new CollectionSettings(), //CollectionSettings = new CollectionSettings(),
//DiscussionSettings = new DiscussionSettings(), //DiscussionSettings = new DiscussionSettings(),
PlaybackSettings = new PlaybackSettings(), PlaybackSettings = new PlaybackSettings { Expanded = { Value = false } },
VisualSettings = new VisualSettings { Expanded = { Value = false } } VisualSettings = new VisualSettings { Expanded = { Value = false } }
} }
}; };

View File

@ -8,8 +8,10 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Input.Bindings;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK.Graphics; using osuTK.Graphics;
@ -26,7 +28,7 @@ namespace osu.Game.Screens.Play
private SkinnableSound pauseLoop; private SkinnableSound pauseLoop;
protected override Action BackAction => () => InternalButtons.Children.First().TriggerClick(); protected override Action BackAction => () => InternalButtons.First().TriggerClick();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
@ -56,5 +58,17 @@ namespace osu.Game.Screens.Play
pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
} }
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
{
case GlobalAction.PauseGameplay:
InternalButtons.First().TriggerClick();
return true;
}
return base.OnPressed(e);
}
} }
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Overlays; using osu.Game.Overlays;

View File

@ -596,6 +596,9 @@ namespace osu.Game.Screens.Select
public void FlushPendingFilterOperations() public void FlushPendingFilterOperations()
{ {
if (!IsLoaded)
return;
if (PendingFilter?.Completed == false) if (PendingFilter?.Completed == false)
{ {
applyActiveCriteria(false); applyActiveCriteria(false);

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -36,7 +34,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Collections; using osu.Game.Collections;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using System.Diagnostics; using System.Diagnostics;
using JetBrains.Annotations; using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -49,7 +47,7 @@ namespace osu.Game.Screens.Select
protected const float BACKGROUND_BLUR = 20; protected const float BACKGROUND_BLUR = 20;
private const float left_area_padding = 20; private const float left_area_padding = 20;
public FilterControl FilterControl { get; private set; } public FilterControl FilterControl { get; private set; } = null!;
/// <summary> /// <summary>
/// Whether this song select instance should take control of the global track, /// Whether this song select instance should take control of the global track,
@ -64,75 +62,71 @@ namespace osu.Game.Screens.Select
/// <summary> /// <summary>
/// Can be null if <see cref="ShowFooter"/> is false. /// Can be null if <see cref="ShowFooter"/> is false.
/// </summary> /// </summary>
protected BeatmapOptionsOverlay BeatmapOptions { get; private set; } protected BeatmapOptionsOverlay BeatmapOptions { get; private set; } = null!;
/// <summary> /// <summary>
/// Can be null if <see cref="ShowFooter"/> is false. /// Can be null if <see cref="ShowFooter"/> is false.
/// </summary> /// </summary>
protected Footer Footer { get; private set; } protected Footer? Footer { get; private set; }
/// <summary> /// <summary>
/// Contains any panel which is triggered by a footer button. /// Contains any panel which is triggered by a footer button.
/// Helps keep them located beneath the footer itself. /// Helps keep them located beneath the footer itself.
/// </summary> /// </summary>
protected Container FooterPanels { get; private set; } protected Container FooterPanels { get; private set; } = null!;
/// <summary> /// <summary>
/// Whether entering editor mode should be allowed. /// Whether entering editor mode should be allowed.
/// </summary> /// </summary>
public virtual bool AllowEditing => true; public virtual bool AllowEditing => true;
public bool BeatmapSetsLoaded => IsLoaded && Carousel?.BeatmapSetsLoaded == true; public bool BeatmapSetsLoaded => IsLoaded && Carousel.BeatmapSetsLoaded;
[Resolved] [Resolved]
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;
protected BeatmapCarousel Carousel { get; private set; } protected BeatmapCarousel Carousel { get; private set; } = null!;
private ParallaxContainer wedgeBackground; private ParallaxContainer wedgeBackground = null!;
protected Container LeftArea { get; private set; } protected Container LeftArea { get; private set; } = null!;
private BeatmapInfoWedge beatmapInfoWedge; private BeatmapInfoWedge beatmapInfoWedge = null!;
[Resolved(canBeNull: true)]
private IDialogOverlay dialogOverlay { get; set; }
[Resolved] [Resolved]
private BeatmapManager beatmaps { get; set; } private IDialogOverlay? dialogOverlay { get; set; }
protected ModSelectOverlay ModSelect { get; private set; } [Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
protected Sample SampleConfirm { get; private set; } protected ModSelectOverlay ModSelect { get; private set; } = null!;
private Sample sampleChangeDifficulty; protected Sample? SampleConfirm { get; private set; }
private Sample sampleChangeBeatmap;
private Container carouselContainer; private Sample sampleChangeDifficulty = null!;
private Sample sampleChangeBeatmap = null!;
protected BeatmapDetailArea BeatmapDetails { get; private set; } private Container carouselContainer = null!;
private FooterButtonOptions beatmapOptionsButton; protected BeatmapDetailArea BeatmapDetails { get; private set; } = null!;
private FooterButtonOptions beatmapOptionsButton = null!;
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>(); private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
private double audioFeedbackLastPlaybackTime; private double audioFeedbackLastPlaybackTime;
[CanBeNull] private IDisposable? modSelectOverlayRegistration;
private IDisposable modSelectOverlayRegistration;
[Resolved] [Resolved]
private MusicController music { get; set; } private MusicController music { get; set; } = null!;
[Resolved(CanBeNull = true)] [Resolved]
internal IOverlayManager OverlayManager { get; private set; } internal IOverlayManager? OverlayManager { get; private set; }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender) private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender)
{ {
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
transferRulesetValue();
LoadComponentAsync(Carousel = new BeatmapCarousel LoadComponentAsync(Carousel = new BeatmapCarousel
{ {
AllowSelection = false, // delay any selection until our bindables are ready to make a good choice. AllowSelection = false, // delay any selection until our bindables are ready to make a good choice.
@ -146,6 +140,9 @@ namespace osu.Game.Screens.Select
GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
}, c => carouselContainer.Child = c); }, c => carouselContainer.Child = c);
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
transferRulesetValue();
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new ResetScrollContainer(() => Carousel.ScrollToSelected()) new ResetScrollContainer(() => Carousel.ScrollToSelected())
@ -273,7 +270,7 @@ namespace osu.Game.Screens.Select
BeatmapOptions = new BeatmapOptionsOverlay(), BeatmapOptions = new BeatmapOptionsOverlay(),
} }
}, },
Footer = new Footer(), Footer = new Footer()
}); });
} }
@ -318,7 +315,7 @@ namespace osu.Game.Screens.Select
/// Creates the buttons to be displayed in the footer. /// Creates the buttons to be displayed in the footer.
/// </summary> /// </summary>
/// <returns>A set of <see cref="FooterButton"/> and an optional <see cref="OverlayContainer"/> which the button opens when pressed.</returns> /// <returns>A set of <see cref="FooterButton"/> and an optional <see cref="OverlayContainer"/> which the button opens when pressed.</returns>
protected virtual IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() => new (FooterButton, OverlayContainer)[] protected virtual IEnumerable<(FooterButton, OverlayContainer?)> CreateFooterButtons() => new (FooterButton, OverlayContainer?)[]
{ {
(new FooterButtonMods { Current = Mods }, ModSelect), (new FooterButtonMods { Current = Mods }, ModSelect),
(new FooterButtonRandom (new FooterButtonRandom
@ -339,7 +336,7 @@ namespace osu.Game.Screens.Select
Carousel.Filter(criteria, shouldDebounce); Carousel.Filter(criteria, shouldDebounce);
} }
private DependencyContainer dependencies; private DependencyContainer dependencies = null!;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
@ -357,7 +354,7 @@ namespace osu.Game.Screens.Select
/// </summary> /// </summary>
protected abstract BeatmapDetailArea CreateBeatmapDetailArea(); protected abstract BeatmapDetailArea CreateBeatmapDetailArea();
public void Edit(BeatmapInfo beatmapInfo = null) public void Edit(BeatmapInfo? beatmapInfo = null)
{ {
if (!AllowEditing) if (!AllowEditing)
throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled"); throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled");
@ -372,7 +369,7 @@ namespace osu.Game.Screens.Select
/// <param name="beatmapInfo">An optional beatmap to override the current carousel selection.</param> /// <param name="beatmapInfo">An optional beatmap to override the current carousel selection.</param>
/// <param name="ruleset">An optional ruleset to override the current carousel selection.</param> /// <param name="ruleset">An optional ruleset to override the current carousel selection.</param>
/// <param name="customStartAction">An optional custom action to perform instead of <see cref="OnStart"/>.</param> /// <param name="customStartAction">An optional custom action to perform instead of <see cref="OnStart"/>.</param>
public void FinaliseSelection(BeatmapInfo beatmapInfo = null, RulesetInfo ruleset = null, Action customStartAction = null) public void FinaliseSelection(BeatmapInfo? beatmapInfo = null, RulesetInfo? ruleset = null, Action? customStartAction = null)
{ {
// This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed.
if (!Carousel.BeatmapSetsLoaded) if (!Carousel.BeatmapSetsLoaded)
@ -419,9 +416,9 @@ namespace osu.Game.Screens.Select
/// <returns>If a resultant action occurred that takes the user away from SongSelect.</returns> /// <returns>If a resultant action occurred that takes the user away from SongSelect.</returns>
protected abstract bool OnStart(); protected abstract bool OnStart();
private ScheduledDelegate selectionChangedDebounce; private ScheduledDelegate? selectionChangedDebounce;
private void updateCarouselSelection(ValueChangedEvent<WorkingBeatmap> e = null) private void updateCarouselSelection(ValueChangedEvent<WorkingBeatmap>? e = null)
{ {
var beatmap = e?.NewValue ?? Beatmap.Value; var beatmap = e?.NewValue ?? Beatmap.Value;
if (beatmap is DummyWorkingBeatmap || !this.IsCurrentScreen()) return; if (beatmap is DummyWorkingBeatmap || !this.IsCurrentScreen()) return;
@ -451,11 +448,11 @@ namespace osu.Game.Screens.Select
} }
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
private BeatmapInfo beatmapInfoPrevious; private BeatmapInfo? beatmapInfoPrevious;
private BeatmapInfo beatmapInfoNoDebounce; private BeatmapInfo? beatmapInfoNoDebounce;
private RulesetInfo rulesetNoDebounce; private RulesetInfo? rulesetNoDebounce;
private void updateSelectedBeatmap(BeatmapInfo beatmapInfo) private void updateSelectedBeatmap(BeatmapInfo? beatmapInfo)
{ {
if (beatmapInfo == null && beatmapInfoNoDebounce == null) if (beatmapInfo == null && beatmapInfoNoDebounce == null)
return; return;
@ -467,7 +464,7 @@ namespace osu.Game.Screens.Select
performUpdateSelected(); performUpdateSelected();
} }
private void updateSelectedRuleset(RulesetInfo ruleset) private void updateSelectedRuleset(RulesetInfo? ruleset)
{ {
if (ruleset == null && rulesetNoDebounce == null) if (ruleset == null && rulesetNoDebounce == null)
return; return;
@ -485,7 +482,7 @@ namespace osu.Game.Screens.Select
private void performUpdateSelected() private void performUpdateSelected()
{ {
var beatmap = beatmapInfoNoDebounce; var beatmap = beatmapInfoNoDebounce;
var ruleset = rulesetNoDebounce; RulesetInfo? ruleset = rulesetNoDebounce;
selectionChangedDebounce?.Cancel(); selectionChangedDebounce?.Cancel();
@ -694,6 +691,7 @@ namespace osu.Game.Screens.Select
isHandlingLooping = true; isHandlingLooping = true;
ensureTrackLooping(Beatmap.Value, TrackChangeDirection.None); ensureTrackLooping(Beatmap.Value, TrackChangeDirection.None);
music.TrackChanged += ensureTrackLooping; music.TrackChanged += ensureTrackLooping;
} }
@ -728,7 +726,7 @@ namespace osu.Game.Screens.Select
decoupledRuleset.UnbindAll(); decoupledRuleset.UnbindAll();
if (music != null) if (music.IsNotNull())
music.TrackChanged -= ensureTrackLooping; music.TrackChanged -= ensureTrackLooping;
modSelectOverlayRegistration?.Dispose(); modSelectOverlayRegistration?.Dispose();
@ -763,7 +761,7 @@ namespace osu.Game.Screens.Select
} }
} }
private readonly WeakReference<ITrack> lastTrack = new WeakReference<ITrack>(null); private readonly WeakReference<ITrack?> lastTrack = new WeakReference<ITrack?>(null);
/// <summary> /// <summary>
/// Ensures some music is playing for the current track. /// Ensures some music is playing for the current track.
@ -864,18 +862,19 @@ namespace osu.Game.Screens.Select
// if we have a pending filter operation, we want to run it now. // if we have a pending filter operation, we want to run it now.
// it could change selection (ie. if the ruleset has been changed). // it could change selection (ie. if the ruleset has been changed).
Carousel?.FlushPendingFilterOperations(); Carousel.FlushPendingFilterOperations();
return true; return true;
} }
private void delete(BeatmapSetInfo beatmap) private void delete(BeatmapSetInfo? beatmap)
{ {
if (beatmap == null) return; if (beatmap == null) return;
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap));
} }
private void clearScores(BeatmapInfo beatmapInfo) private void clearScores(BeatmapInfo? beatmapInfo)
{ {
if (beatmapInfo == null) return; if (beatmapInfo == null) return;
@ -950,7 +949,7 @@ namespace osu.Game.Screens.Select
private partial class ResetScrollContainer : Container private partial class ResetScrollContainer : Container
{ {
private readonly Action onHoverAction; private readonly Action? onHoverAction;
public ResetScrollContainer(Action onHoverAction) public ResetScrollContainer(Action onHoverAction)
{ {

View File

@ -10,6 +10,7 @@ using osu.Framework.Logging;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
@ -26,7 +27,7 @@ namespace osu.Game.Skinning.Editor
private FillFlowContainer fill = null!; private FillFlowContainer fill = null!;
public SkinComponentToolbox(CompositeDrawable? target = null) public SkinComponentToolbox(CompositeDrawable? target = null)
: base("Components") : base(SkinEditorStrings.Components)
{ {
this.target = target; this.target = target;
} }

View File

@ -109,7 +109,7 @@ namespace osu.Game.Skinning.Editor
{ {
new Container new Container
{ {
Name = "Menu container", Name = @"Menu container",
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Depth = float.MinValue, Depth = float.MinValue,
Height = MENU_HEIGHT, Height = MENU_HEIGHT,
@ -122,14 +122,14 @@ namespace osu.Game.Skinning.Editor
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Items = new[] Items = new[]
{ {
new MenuItem("File") new MenuItem(CommonStrings.MenuBarFile)
{ {
Items = new[] Items = new[]
{ {
new EditorMenuItem("Save", MenuItemType.Standard, Save), new EditorMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsSave, MenuItemType.Standard, Save),
new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, revert),
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem("Exit", MenuItemType.Standard, () => skinEditorOverlay?.Hide()), new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
}, },
}, },
} }
@ -234,7 +234,6 @@ namespace osu.Game.Skinning.Editor
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target. // Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
content?.Clear(); content?.Clear();
Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(loadBlueprintContainer);
Scheduler.AddOnce(populateSettings); Scheduler.AddOnce(populateSettings);
@ -253,7 +252,7 @@ namespace osu.Game.Skinning.Editor
{ {
headerText.Clear(); headerText.Clear();
headerText.AddParagraph("Skin editor", cp => cp.Font = OsuFont.Default.With(size: 16)); headerText.AddParagraph(SkinEditorStrings.SkinEditor, cp => cp.Font = OsuFont.Default.With(size: 16));
headerText.NewParagraph(); headerText.NewParagraph();
headerText.AddText("Currently editing ", cp => headerText.AddText("Currently editing ", cp =>
{ {

View File

@ -16,6 +16,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -66,7 +67,7 @@ namespace osu.Game.Skinning.Editor
{ {
new FillFlowContainer new FillFlowContainer
{ {
Name = "Scene library", Name = @"Scene library",
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Spacing = new Vector2(padding), Spacing = new Vector2(padding),
@ -76,14 +77,14 @@ namespace osu.Game.Skinning.Editor
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = "Scene library", Text = SkinEditorStrings.SceneLibrary,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Margin = new MarginPadding(10), Margin = new MarginPadding(10),
}, },
new SceneButton new SceneButton
{ {
Text = "Song Select", Text = SkinEditorStrings.SongSelect,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Action = () => performer?.PerformFromScreen(screen => Action = () => performer?.PerformFromScreen(screen =>
@ -96,7 +97,7 @@ namespace osu.Game.Skinning.Editor
}, },
new SceneButton new SceneButton
{ {
Text = "Gameplay", Text = SkinEditorStrings.Gameplay,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Action = () => performer?.PerformFromScreen(screen => Action = () => performer?.PerformFromScreen(screen =>

View File

@ -1,12 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osuTK; using osuTK;
@ -17,7 +16,7 @@ namespace osu.Game.Skinning.Editor
protected override Container<Drawable> Content { get; } protected override Container<Drawable> Content { get; }
public SkinSettingsToolbox(Drawable component) public SkinSettingsToolbox(Drawable component)
: base($"Settings ({component.GetType().Name})") : base(SkinEditorStrings.Settings(component.GetType().Name))
{ {
base.Content.Add(Content = new FillFlowContainer base.Content.Add(Content = new FillFlowContainer
{ {

View File

@ -50,17 +50,16 @@ namespace osu.Game.Tests.Visual
{ {
var cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }; var cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both };
cursorDisplay.Add(new OsuTooltipContainer(cursorDisplay.MenuCursor) cursorDisplay.Add(content = new OsuTooltipContainer(cursorDisplay.MenuCursor)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = mainContent
}); });
mainContent = cursorDisplay; mainContent.Add(cursorDisplay);
} }
if (CreateNestedActionContainer) if (CreateNestedActionContainer)
mainContent = new GlobalActionContainer(null).WithChild(mainContent); mainContent.Add(new GlobalActionContainer(null));
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
{ {