diff --git a/osu.Android.props b/osu.Android.props
index 5a0e7479fa..956093b2ac 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs
index 2c2c4dc24e..af87fc17ad 100644
--- a/osu.Game.Tests/Chat/MessageFormatterTests.cs
+++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs
@@ -509,5 +509,17 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual(LinkAction.External, result.Action);
Assert.AreEqual("/relative", result.Argument);
}
+
+ [TestCase("https://dev.ppy.sh/home/changelog", "")]
+ [TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")]
+ public void TestChangelogLinks(string link, string expectedArg)
+ {
+ MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
+
+ LinkDetails result = MessageFormatter.GetLinkDetails(link);
+
+ Assert.AreEqual(LinkAction.OpenChangelog, result.Action);
+ Assert.AreEqual(expectedArg, result.Argument);
+ }
}
}
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 79767bc671..558b874234 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -168,14 +168,14 @@ namespace osu.Game.Tests.Online
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
}
- protected override BeatmapModelDownloader CreateBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider api, GameHost host)
+ protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager manager, IAPIProvider api, GameHost host)
{
- return new TestBeatmapModelDownloader(modelManager, api, host);
+ return new TestBeatmapModelDownloader(manager, api, host);
}
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
{
- public TestBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider apiProvider, GameHost gameHost)
+ public TestBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider apiProvider, GameHost gameHost)
: base(modelManager, apiProvider, gameHost)
{
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
index ab47067411..ffb3d41d18 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinProvidingContainer.cs
@@ -6,7 +6,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
@@ -65,10 +64,9 @@ namespace osu.Game.Tests.Skins
public new void TriggerSourceChanged() => base.TriggerSourceChanged();
- protected override void OnSourceChanged()
+ protected override void RefreshSources()
{
- ResetSources();
- sources.ForEach(AddSource);
+ SetSources(sources);
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
index 2258a209e2..f0aa3e2350 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
@@ -32,6 +32,8 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("wait for editor load", () => editor != null);
+ AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
+
AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
@@ -41,11 +43,11 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
- AddStep("Save and exit", () =>
- {
- InputManager.Keys(PlatformAction.Save);
- InputManager.Key(Key.Escape);
- });
+ AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
+
+ AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
+
+ AddStep("Exit", () => InputManager.Key(Key.Escape));
AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
@@ -57,6 +59,7 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("Wait for editor load", () => editor != null);
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
+ AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
index 5eb71e92c2..ae0decaee1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs
@@ -103,6 +103,30 @@ namespace osu.Game.Tests.Visual.Gameplay
checkFrameCount(0);
}
+ [Test]
+ public void TestRatePreservedWhenTimeNotProgressing()
+ {
+ AddStep("set manual clock rate", () => manualClock.Rate = 1);
+ seekManualTo(5000);
+ createStabilityContainer();
+ checkRate(1);
+
+ seekManualTo(10000);
+ checkRate(1);
+
+ AddWaitStep("wait some", 3);
+ checkRate(1);
+
+ seekManualTo(5000);
+ checkRate(-1);
+
+ AddWaitStep("wait some", 3);
+ checkRate(-1);
+
+ seekManualTo(10000);
+ checkRate(1);
+ }
+
private const int max_frames_catchup = 50;
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
@@ -116,6 +140,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private void checkFrameCount(int frames) =>
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
+ private void checkRate(double rate) =>
+ AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate);
+
public class ClockConsumingChild : CompositeDrawable
{
private readonly OsuSpriteText text;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index 3ed274690e..48a97d54f7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -90,8 +90,12 @@ namespace osu.Game.Tests.Visual.Gameplay
CreateTest(() =>
{
AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true);
- AddStep("set storyboard duration to 1.3s", () => currentStoryboardDuration = 1300);
+
+ // Fail occurs at 164ms with the provided beatmap.
+ // Fail animation runs for 2.5s realtime but the gameplay time change is *variable* due to the frequency transform being applied, so we need a bit of lenience.
+ AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600);
});
+
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs b/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs
new file mode 100644
index 0000000000..cb7c334656
--- /dev/null
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneStartupImport.cs
@@ -0,0 +1,31 @@
+// Copyright (c) ppy Pty Ltd . 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.Testing;
+using osu.Game.Database;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Visual.Navigation
+{
+ public class TestSceneStartupImport : OsuGameTestScene
+ {
+ private string importFilename;
+
+ protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { importFilename });
+
+ public override void SetUpSteps()
+ {
+ AddStep("Prepare import beatmap", () => importFilename = TestResources.GetTestBeatmapForImport());
+
+ base.SetUpSteps();
+ }
+
+ [Test]
+ public void TestImportCreatedNotification()
+ {
+ AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType().Count() == 1);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
index 997eac709d..dc5b0e0d77 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Handlers.Tablet;
@@ -21,6 +22,9 @@ namespace osu.Game.Tests.Visual.Settings
private TestTabletHandler tabletHandler;
private TabletSettings settings;
+ [Cached]
+ private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+
[SetUpSteps]
public void SetUpSteps()
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 067f1cabb4..4811fc979e 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -142,6 +142,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("store selected beatmap", () => selected = Beatmap.Value);
+ AddUntilStep("wait for beatmaps to load", () => songSelect.Carousel.ChildrenOfType().Any());
+
AddStep("select next and enter", () =>
{
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType()
@@ -599,10 +601,10 @@ namespace osu.Game.Tests.Visual.SongSelect
});
FilterableDifficultyIcon difficultyIcon = null;
- AddStep("Find an icon", () =>
+ AddUntilStep("Find an icon", () =>
{
- difficultyIcon = set.ChildrenOfType()
- .First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex());
+ return (difficultyIcon = set.ChildrenOfType()
+ .FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex())) != null;
});
AddStep("Click on a difficulty", () =>
@@ -765,10 +767,10 @@ namespace osu.Game.Tests.Visual.SongSelect
});
FilterableGroupedDifficultyIcon groupIcon = null;
- AddStep("Find group icon for different ruleset", () =>
+ AddUntilStep("Find group icon for different ruleset", () =>
{
- groupIcon = set.ChildrenOfType()
- .First(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3);
+ return (groupIcon = set.ChildrenOfType()
+ .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3)) != null;
});
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs
new file mode 100644
index 0000000000..9ccfba7c74
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.UserInterfaceV2;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneRoundedButton : OsuTestScene
+ {
+ [Test]
+ public void TestBasic()
+ {
+ RoundedButton button = null;
+
+ AddStep("create button", () => Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Colour4.DarkGray
+ },
+ button = new RoundedButton
+ {
+ Width = 400,
+ Text = "Test button",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Action = () => { }
+ }
+ }
+ });
+
+ AddToggleStep("toggle disabled", disabled => button.Action = disabled ? (Action)null : () => { });
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index f3434c5153..627e54c803 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -40,7 +40,13 @@ namespace osu.Game.Beatmaps
public IBeatmap Convert(CancellationToken cancellationToken = default)
{
// We always operate on a clone of the original beatmap, to not modify it game-wide
- return ConvertBeatmap(Beatmap.Clone(), cancellationToken);
+ var original = Beatmap.Clone();
+
+ // Shallow clone isn't enough to ensure we don't mutate beatmap info unexpectedly.
+ // Can potentially be removed after `Beatmap.Difficulty` doesn't save back to `Beatmap.BeatmapInfo`.
+ original.BeatmapInfo = original.BeatmapInfo.Clone();
+
+ return ConvertBeatmap(original, cancellationToken);
}
///
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 240db22c00..14175f251b 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
}
}
- protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(BeatmapModelManager modelManager, IAPIProvider api, GameHost host)
+ protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider api, GameHost host)
{
return new BeatmapModelDownloader(modelManager, api, host);
}
diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs
index ae482eeafd..30dc95a966 100644
--- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs
+++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps
protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
- public BeatmapModelDownloader(BeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null)
+ public BeatmapModelDownloader(IBeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null)
: base(beatmapModelManager, api, host)
{
}
diff --git a/osu.Game/Configuration/RandomSelectAlgorithm.cs b/osu.Game/Configuration/RandomSelectAlgorithm.cs
index 8d0c87374f..b22f2ae485 100644
--- a/osu.Game/Configuration/RandomSelectAlgorithm.cs
+++ b/osu.Game/Configuration/RandomSelectAlgorithm.cs
@@ -10,7 +10,7 @@ namespace osu.Game.Configuration
[Description("Never repeat")]
RandomPermutation,
- [Description("Random")]
+ [Description("True Random")]
Random
}
}
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index ee1a7e2900..c235fc7728 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Database
/// One or more archive locations on disk.
public Task Import(params string[] paths)
{
- var notification = new ProgressNotification { State = ProgressNotificationState.Active };
+ var notification = new ImportProgressNotification();
PostNotification?.Invoke(notification);
@@ -125,7 +125,7 @@ namespace osu.Game.Database
public Task Import(params ImportTask[] tasks)
{
- var notification = new ProgressNotification { State = ProgressNotificationState.Active };
+ var notification = new ImportProgressNotification();
PostNotification?.Invoke(notification);
diff --git a/osu.Game/Database/IHasRealmFiles.cs b/osu.Game/Database/IHasRealmFiles.cs
index 2adfe73d1e..024d9f2a89 100644
--- a/osu.Game/Database/IHasRealmFiles.cs
+++ b/osu.Game/Database/IHasRealmFiles.cs
@@ -4,6 +4,8 @@
using System.Collections.Generic;
using osu.Game.Models;
+#nullable enable
+
namespace osu.Game.Database
{
///
diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs
index e94af01772..8e658cb0f5 100644
--- a/osu.Game/Database/IModelImporter.cs
+++ b/osu.Game/Database/IModelImporter.cs
@@ -10,7 +10,7 @@ using osu.Game.Overlays.Notifications;
namespace osu.Game.Database
{
///
- /// A class which handles importing of asociated models to the game store.
+ /// A class which handles importing of associated models to the game store.
///
/// The model type.
public interface IModelImporter : IPostNotifications
diff --git a/osu.Game/Database/INamedFile.cs b/osu.Game/Database/INamedFile.cs
index 9c94aed38c..2bd45d4e42 100644
--- a/osu.Game/Database/INamedFile.cs
+++ b/osu.Game/Database/INamedFile.cs
@@ -3,15 +3,17 @@
using osu.Game.Models;
+#nullable enable
+
namespace osu.Game.Database
{
///
- /// Represent a join model which gives a filename and scope to a .
+ /// Represents a join model which gives a filename and scope to a .
///
public interface INamedFile
{
- public string Filename { get; set; }
+ string Filename { get; set; }
- public RealmFile File { get; set; }
+ RealmFile File { get; set; }
}
}
diff --git a/osu.Game/Database/ImportProgressNotification.cs b/osu.Game/Database/ImportProgressNotification.cs
new file mode 100644
index 0000000000..aaee3e117f
--- /dev/null
+++ b/osu.Game/Database/ImportProgressNotification.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Overlays.Notifications;
+
+namespace osu.Game.Database
+{
+ public class ImportProgressNotification : ProgressNotification
+ {
+ public ImportProgressNotification()
+ {
+ State = ProgressNotificationState.Active;
+ }
+ }
+}
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index d7cfc4094c..af2bb26871 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -225,6 +225,16 @@ namespace osu.Game.Graphics
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
+ ///
+ /// Equivalent to 's .
+ ///
+ public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378");
+
+ ///
+ /// Equivalent to 's .
+ ///
+ public readonly Color4 Blue3 = Color4Extensions.FromHex(@"3399cc");
+
///
/// Equivalent to 's .
///
diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
new file mode 100644
index 0000000000..27e28f1e03
--- /dev/null
+++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Graphics.UserInterfaceV2
+{
+ public class RoundedButton : OsuButton, IFilterable
+ {
+ public override float Height
+ {
+ get => base.Height;
+ set
+ {
+ base.Height = value;
+
+ if (IsLoaded)
+ updateCornerRadius();
+ }
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ BackgroundColour = colours.Blue3;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateCornerRadius();
+ }
+
+ private void updateCornerRadius() => Content.CornerRadius = DrawHeight / 2;
+
+ public virtual IEnumerable FilterTerms => new[] { Text.ToString() };
+
+ public bool MatchingFilter
+ {
+ set => this.FadeTo(value ? 1 : 0);
+ }
+
+ public bool FilteringActive { get; set; }
+ }
+}
diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs
index aa6eabd7d1..008781c2e5 100644
--- a/osu.Game/Localisation/AudioSettingsStrings.cs
+++ b/osu.Game/Localisation/AudioSettingsStrings.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
+ ///
+ /// "Output device"
+ ///
+ public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device");
+
///
/// "Master"
///
diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs
index 6d6381b429..fa92187650 100644
--- a/osu.Game/Localisation/GameplaySettingsStrings.cs
+++ b/osu.Game/Localisation/GameplaySettingsStrings.cs
@@ -14,11 +14,36 @@ namespace osu.Game.Localisation
///
public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
+ ///
+ /// "Beatmap"
+ ///
+ public static LocalisableString BeatmapHeader => new TranslatableString(getKey(@"beatmap_header"), @"Beatmap");
+
///
/// "General"
///
public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+ ///
+ /// "Audio"
+ ///
+ public static LocalisableString AudioHeader => new TranslatableString(getKey(@"audio"), @"Audio");
+
+ ///
+ /// "HUD"
+ ///
+ public static LocalisableString HUDHeader => new TranslatableString(getKey(@"h_u_d"), @"HUD");
+
+ ///
+ /// "Input"
+ ///
+ public static LocalisableString InputHeader => new TranslatableString(getKey(@"input"), @"Input");
+
+ ///
+ /// "Background"
+ ///
+ public static LocalisableString BackgroundHeader => new TranslatableString(getKey(@"background"), @"Background");
+
///
/// "Background dim"
///
diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs
index 0e384f983f..f85cc0f2ae 100644
--- a/osu.Game/Localisation/GraphicsSettingsStrings.cs
+++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs
@@ -104,6 +104,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
+ ///
+ /// "Screenshots"
+ ///
+ public static LocalisableString Screenshots => new TranslatableString(getKey(@"screenshots"), @"Screenshots");
+
///
/// "Screenshot format"
///
diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs
new file mode 100644
index 0000000000..a356c9e20b
--- /dev/null
+++ b/osu.Game/Localisation/RulesetSettingsStrings.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . 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 RulesetSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.RulesetSettings";
+
+ ///
+ /// "Rulesets"
+ ///
+ public static LocalisableString Rulesets => new TranslatableString(getKey(@"rulesets"), @"Rulesets");
+
+ private static string getKey(string key) => $@"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs
index f22b4d6bf5..8b74b94d59 100644
--- a/osu.Game/Localisation/SkinSettingsStrings.cs
+++ b/osu.Game/Localisation/SkinSettingsStrings.cs
@@ -14,6 +14,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
+ ///
+ /// "Current skin"
+ ///
+ public static LocalisableString CurrentSkin => new TranslatableString(getKey(@"current_skin"), @"Current skin");
+
///
/// "Skin layout editor"
///
diff --git a/osu.Game/Models/RealmBeatmap.cs b/osu.Game/Models/RealmBeatmap.cs
index 09f8dafeb6..5049c1384d 100644
--- a/osu.Game/Models/RealmBeatmap.cs
+++ b/osu.Game/Models/RealmBeatmap.cs
@@ -97,11 +97,6 @@ namespace osu.Game.Models
#endregion
- ///
- /// Returns a shallow-clone of this .
- ///
- public RealmBeatmap Clone() => (RealmBeatmap)MemberwiseClone();
-
public bool AudioEquals(RealmBeatmap? other) => other != null
&& BeatmapSet != null
&& other.BeatmapSet != null
diff --git a/osu.Game/Models/RealmBeatmapDifficulty.cs b/osu.Game/Models/RealmBeatmapDifficulty.cs
index 44bfdda491..3c1dad69e4 100644
--- a/osu.Game/Models/RealmBeatmapDifficulty.cs
+++ b/osu.Game/Models/RealmBeatmapDifficulty.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
using Realms;
@@ -8,6 +9,7 @@ using Realms;
namespace osu.Game.Models
{
+ [ExcludeFromDynamicCompile]
[MapTo("BeatmapDifficulty")]
public class RealmBeatmapDifficulty : EmbeddedObject, IBeatmapDifficultyInfo
{
diff --git a/osu.Game/Models/RealmBeatmapMetadata.cs b/osu.Game/Models/RealmBeatmapMetadata.cs
index 00dd120791..6ea7170d0f 100644
--- a/osu.Game/Models/RealmBeatmapMetadata.cs
+++ b/osu.Game/Models/RealmBeatmapMetadata.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Models
[JsonProperty("artist_unicode")]
public string ArtistUnicode { get; set; } = string.Empty;
- public string Author { get; set; } = string.Empty; // eventually should be linked to a persisted User. = string.Empty;
+ public string Author { get; set; } = string.Empty; // eventually should be linked to a persisted User.
public string Source { get; set; } = string.Empty;
diff --git a/osu.Game/Models/RealmFile.cs b/osu.Game/Models/RealmFile.cs
index 6836d79d2d..2715f4be45 100644
--- a/osu.Game/Models/RealmFile.cs
+++ b/osu.Game/Models/RealmFile.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.IO;
+using osu.Framework.Testing;
using osu.Game.IO;
using Realms;
@@ -9,6 +10,7 @@ using Realms;
namespace osu.Game.Models
{
+ [ExcludeFromDynamicCompile]
[MapTo("File")]
public class RealmFile : RealmObject, IFileInfo
{
diff --git a/osu.Game/Models/RealmNamedFileUsage.cs b/osu.Game/Models/RealmNamedFileUsage.cs
index 59b446112d..ba12d51d0b 100644
--- a/osu.Game/Models/RealmNamedFileUsage.cs
+++ b/osu.Game/Models/RealmNamedFileUsage.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
+using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.IO;
using Realms;
@@ -10,6 +11,7 @@ using Realms;
namespace osu.Game.Models
{
+ [ExcludeFromDynamicCompile]
public class RealmNamedFileUsage : EmbeddedObject, INamedFile, INamedFileUsage
{
public RealmFile File { get; set; } = null!;
diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs
index 0e4ea694aa..201ba6239b 100644
--- a/osu.Game/Online/Chat/MessageFormatter.cs
+++ b/osu.Game/Online/Chat/MessageFormatter.cs
@@ -177,6 +177,24 @@ namespace osu.Game.Online.Chat
case "wiki":
return new LinkDetails(LinkAction.OpenWiki, string.Join('/', args.Skip(3)));
+
+ case "home":
+ if (mainArg != "changelog")
+ // handle link other than changelog as external for now
+ return new LinkDetails(LinkAction.External, url);
+
+ switch (args.Length)
+ {
+ case 4:
+ // https://osu.ppy.sh/home/changelog
+ return new LinkDetails(LinkAction.OpenChangelog, string.Empty);
+
+ case 6:
+ // https://osu.ppy.sh/home/changelog/lazer/2021.1006
+ return new LinkDetails(LinkAction.OpenChangelog, $"{args[4]}/{args[5]}");
+ }
+
+ break;
}
}
@@ -324,6 +342,7 @@ namespace osu.Game.Online.Chat
SearchBeatmapSet,
OpenWiki,
Custom,
+ OpenChangelog,
}
public class Link : IComparable
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 8a018f17d9..020cdebab6 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -90,6 +90,8 @@ namespace osu.Game
private WikiOverlay wikiOverlay;
+ private ChangelogOverlay changelogOverlay;
+
private SkinEditorOverlay skinEditor;
private Container overlayContent;
@@ -209,13 +211,6 @@ namespace osu.Game
[BackgroundDependencyLoader]
private void load()
{
- if (args?.Length > 0)
- {
- var paths = args.Where(a => !a.StartsWith('-')).ToArray();
- if (paths.Length > 0)
- Task.Run(() => Import(paths));
- }
-
dependencies.CacheAs(this);
dependencies.Cache(SentryLogger);
@@ -336,6 +331,17 @@ namespace osu.Game
ShowWiki(link.Argument);
break;
+ case LinkAction.OpenChangelog:
+ if (string.IsNullOrEmpty(link.Argument))
+ ShowChangelogListing();
+ else
+ {
+ var changelogArgs = link.Argument.Split("/");
+ ShowChangelogBuild(changelogArgs[0], changelogArgs[1]);
+ }
+
+ break;
+
default:
throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action.");
}
@@ -401,6 +407,18 @@ namespace osu.Game
/// The wiki page to show
public void ShowWiki(string path) => waitForReady(() => wikiOverlay, _ => wikiOverlay.ShowPage(path));
+ ///
+ /// Show changelog listing overlay
+ ///
+ public void ShowChangelogListing() => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowListing());
+
+ ///
+ /// Show changelog's build as an overlay
+ ///
+ /// The update stream name
+ /// The build version of the update stream
+ public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version));
+
///
/// Present a beatmap at song select immediately.
/// The user should have already requested this interactively.
@@ -769,7 +787,7 @@ namespace osu.Game
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true);
- var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
+ loadComponentSingleFile(changelogOverlay = new ChangelogOverlay(), overlayContent.Add, true);
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true);
@@ -842,6 +860,19 @@ namespace osu.Game
{
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
};
+
+ // Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup.
+ handleStartupImport();
+ }
+
+ private void handleStartupImport()
+ {
+ if (args?.Length > 0)
+ {
+ var paths = args.Where(a => !a.StartsWith('-')).ToArray();
+ if (paths.Length > 0)
+ Task.Run(() => Import(paths));
+ }
}
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 7f4fe8a943..09eb482d16 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
@@ -410,11 +411,28 @@ namespace osu.Game
{
Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
- using (realmFactory.BlockAllOperations())
+ IDisposable realmBlocker = null;
+
+ try
{
- contextFactory.FlushConnections();
+ ManualResetEventSlim readyToRun = new ManualResetEventSlim();
+
+ Scheduler.Add(() =>
+ {
+ realmBlocker = realmFactory.BlockAllOperations();
+ contextFactory.FlushConnections();
+
+ readyToRun.Set();
+ }, false);
+
+ readyToRun.Wait();
+
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
}
+ finally
+ {
+ realmBlocker?.Dispose();
+ }
Logger.Log(@"Migration complete!");
}
diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs
index 51214fe460..198aa1438a 100644
--- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs
+++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Overlays.OSD
private Sample sampleChange;
public TrackedSettingToast(SettingDescription description)
- : base(description.Name, description.Value, description.Shortcut)
+ : base(description.Name.ToString(), description.Value.ToString(), description.Shortcut.ToString())
{
FillFlowContainer optionLights;
diff --git a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs
index c02db40eca..4ca3ace8a1 100644
--- a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs
+++ b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs
@@ -14,10 +14,7 @@ namespace osu.Game.Overlays.Settings
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- BackgroundColour = colours.Pink;
-
- Triangles.ColourDark = colours.PinkDark;
- Triangles.ColourLight = colours.PinkLight;
+ BackgroundColour = colours.Pink3;
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
index d697b45424..0c54ae2763 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
@@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
dropdown = new AudioDeviceSettingsDropdown
{
+ LabelText = AudioSettingsStrings.OutputDevice,
Keywords = new[] { "speaker", "headphone", "output" }
}
};
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs
new file mode 100644
index 0000000000..dba64d695a
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/AudioSettings.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Configuration;
+using osu.Game.Localisation;
+
+namespace osu.Game.Overlays.Settings.Sections.Gameplay
+{
+ public class AudioSettings : SettingsSubsection
+ {
+ protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.PositionalHitsounds,
+ Current = config.GetBindable(OsuSetting.PositionalHitSounds)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
+ Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak)
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/BackgroundSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/BackgroundSettings.cs
new file mode 100644
index 0000000000..94e0c5e494
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/BackgroundSettings.cs
@@ -0,0 +1,48 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Configuration;
+using osu.Game.Localisation;
+
+namespace osu.Game.Overlays.Settings.Sections.Gameplay
+{
+ public class BackgroundSettings : SettingsSubsection
+ {
+ protected override LocalisableString Header => GameplaySettingsStrings.BackgroundHeader;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsSlider
+ {
+ LabelText = GameplaySettingsStrings.BackgroundDim,
+ Current = config.GetBindable(OsuSetting.DimLevel),
+ KeyboardStep = 0.01f,
+ DisplayAsPercentage = true
+ },
+ new SettingsSlider
+ {
+ LabelText = GameplaySettingsStrings.BackgroundBlur,
+ Current = config.GetBindable(OsuSetting.BlurLevel),
+ KeyboardStep = 0.01f,
+ DisplayAsPercentage = true
+ },
+ new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.LightenDuringBreaks,
+ Current = config.GetBindable(OsuSetting.LightenDuringBreaks)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
+ Current = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow),
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs
new file mode 100644
index 0000000000..aaa60ce81b
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Configuration;
+using osu.Game.Localisation;
+
+namespace osu.Game.Overlays.Settings.Sections.Gameplay
+{
+ public class BeatmapSettings : SettingsSubsection
+ {
+ protected override LocalisableString Header => GameplaySettingsStrings.BeatmapHeader;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = SkinSettingsStrings.BeatmapSkins,
+ Current = config.GetBindable(OsuSetting.BeatmapSkins)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = SkinSettingsStrings.BeatmapColours,
+ Current = config.GetBindable(OsuSetting.BeatmapColours)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = SkinSettingsStrings.BeatmapHitsounds,
+ Current = config.GetBindable(OsuSetting.BeatmapHitsounds)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = GraphicsSettingsStrings.StoryboardVideo,
+ Current = config.GetBindable(OsuSetting.ShowStoryboard)
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
index 3a0265e453..d4e4fd571d 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
@@ -1,7 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
@@ -20,77 +19,18 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
Children = new Drawable[]
{
- new SettingsSlider
- {
- LabelText = GameplaySettingsStrings.BackgroundDim,
- Current = config.GetBindable(OsuSetting.DimLevel),
- KeyboardStep = 0.01f,
- DisplayAsPercentage = true
- },
- new SettingsSlider
- {
- LabelText = GameplaySettingsStrings.BackgroundBlur,
- Current = config.GetBindable(OsuSetting.BlurLevel),
- KeyboardStep = 0.01f,
- DisplayAsPercentage = true
- },
- new SettingsCheckbox
- {
- LabelText = GameplaySettingsStrings.LightenDuringBreaks,
- Current = config.GetBindable(OsuSetting.LightenDuringBreaks)
- },
- new SettingsEnumDropdown
- {
- LabelText = GameplaySettingsStrings.HUDVisibilityMode,
- Current = config.GetBindable(OsuSetting.HUDVisibilityMode)
- },
- new SettingsCheckbox
- {
- LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
- Current = config.GetBindable(OsuSetting.ShowProgressGraph)
- },
- new SettingsCheckbox
- {
- LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
- Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail),
- Keywords = new[] { "hp", "bar" }
- },
- new SettingsCheckbox
- {
- LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
- Current = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow),
- },
- new SettingsCheckbox
- {
- LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
- Current = config.GetBindable(OsuSetting.KeyOverlay)
- },
- new SettingsCheckbox
- {
- LabelText = GameplaySettingsStrings.PositionalHitsounds,
- Current = config.GetBindable(OsuSetting.PositionalHitSounds)
- },
- new SettingsCheckbox
- {
- LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
- Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak)
- },
new SettingsEnumDropdown
{
LabelText = GameplaySettingsStrings.ScoreDisplayMode,
Current = config.GetBindable(OsuSetting.ScoreDisplayMode),
Keywords = new[] { "scoring" }
},
- };
-
- if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
- {
- Add(new SettingsCheckbox
+ new SettingsCheckbox
{
- LabelText = GameplaySettingsStrings.DisableWinKey,
- Current = config.GetBindable(OsuSetting.GameplayDisableWinKey)
- });
- }
+ LabelText = GraphicsSettingsStrings.HitLighting,
+ Current = config.GetBindable(OsuSetting.HitLighting)
+ },
+ };
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs
new file mode 100644
index 0000000000..e1b452e322
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Configuration;
+using osu.Game.Localisation;
+
+namespace osu.Game.Overlays.Settings.Sections.Gameplay
+{
+ public class HUDSettings : SettingsSubsection
+ {
+ protected override LocalisableString Header => GameplaySettingsStrings.HUDHeader;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsEnumDropdown
+ {
+ LabelText = GameplaySettingsStrings.HUDVisibilityMode,
+ Current = config.GetBindable(OsuSetting.HUDVisibilityMode)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
+ Current = config.GetBindable(OsuSetting.ShowProgressGraph)
+ },
+ new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
+ Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail),
+ Keywords = new[] { "hp", "bar" }
+ },
+ new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
+ Current = config.GetBindable(OsuSetting.KeyOverlay)
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs
new file mode 100644
index 0000000000..962572ca6e
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Configuration;
+using osu.Game.Localisation;
+
+namespace osu.Game.Overlays.Settings.Sections.Gameplay
+{
+ public class InputSettings : SettingsSubsection
+ {
+ protected override LocalisableString Header => GameplaySettingsStrings.InputHeader;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsSlider
+ {
+ LabelText = SkinSettingsStrings.GameplayCursorSize,
+ Current = config.GetBindable(OsuSetting.GameplayCursorSize),
+ KeyboardStep = 0.01f
+ },
+ new SettingsCheckbox
+ {
+ LabelText = SkinSettingsStrings.AutoCursorSize,
+ Current = config.GetBindable(OsuSetting.AutoCursorSize)
+ },
+ };
+
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
+ {
+ Add(new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.DisableWinKey,
+ Current = config.GetBindable(OsuSetting.GameplayDisableWinKey)
+ });
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
index 42d9d48d73..120e2d908c 100644
--- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
@@ -1,16 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Overlays.Settings.Sections.Gameplay;
-using osu.Game.Rulesets;
-using System.Linq;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Logging;
using osu.Framework.Localisation;
using osu.Game.Localisation;
+using osu.Game.Overlays.Settings.Sections.Gameplay;
namespace osu.Game.Overlays.Settings.Sections
{
@@ -20,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override Drawable CreateIcon() => new SpriteIcon
{
- Icon = FontAwesome.Regular.Circle
+ Icon = FontAwesome.Regular.DotCircle
};
public GameplaySection()
@@ -28,27 +23,13 @@ namespace osu.Game.Overlays.Settings.Sections
Children = new Drawable[]
{
new GeneralSettings(),
+ new AudioSettings(),
+ new BeatmapSettings(),
+ new BackgroundSettings(),
+ new HUDSettings(),
+ new InputSettings(),
new ModsSettings(),
};
}
-
- [BackgroundDependencyLoader]
- private void load(RulesetStore rulesets)
- {
- foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
- {
- try
- {
- SettingsSubsection section = ruleset.CreateSettings();
-
- if (section != null)
- Add(section);
- }
- catch (Exception e)
- {
- Logger.Error(e, "Failed to load ruleset settings");
- }
- }
- }
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/ScreenshotSettings.cs
similarity index 67%
rename from osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
rename to osu.Game/Overlays/Settings/Sections/Graphics/ScreenshotSettings.cs
index 20b1d8d801..dbb9ddc1c1 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/ScreenshotSettings.cs
@@ -9,25 +9,15 @@ using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
- public class DetailSettings : SettingsSubsection
+ public class ScreenshotSettings : SettingsSubsection
{
- protected override LocalisableString Header => GraphicsSettingsStrings.DetailSettingsHeader;
+ protected override LocalisableString Header => GraphicsSettingsStrings.Screenshots;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
- new SettingsCheckbox
- {
- LabelText = GraphicsSettingsStrings.StoryboardVideo,
- Current = config.GetBindable(OsuSetting.ShowStoryboard)
- },
- new SettingsCheckbox
- {
- LabelText = GraphicsSettingsStrings.HitLighting,
- Current = config.GetBindable(OsuSetting.HitLighting)
- },
new SettingsEnumDropdown
{
LabelText = GraphicsSettingsStrings.ScreenshotFormat,
diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
index fd0718f9f2..591848506a 100644
--- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
@@ -22,9 +22,9 @@ namespace osu.Game.Overlays.Settings.Sections
{
Children = new Drawable[]
{
- new RendererSettings(),
new LayoutSettings(),
- new DetailSettings(),
+ new RendererSettings(),
+ new ScreenshotSettings(),
};
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
index 39dddbe1e6..2051af6f3c 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
@@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Game.Database;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Localisation;
@@ -59,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}
}
- public class ResetButton : DangerousTriangleButton
+ public class ResetButton : DangerousSettingsButton
{
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs
index 26610628d5..3ef5ce8941 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -8,16 +9,24 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Handlers.Tablet;
using osu.Game.Graphics;
-using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Overlays.Settings.Sections.Input
{
- internal class RotationPresetButtons : FillFlowContainer
+ internal class RotationPresetButtons : CompositeDrawable
{
+ public new MarginPadding Padding
+ {
+ get => base.Padding;
+ set => base.Padding = value;
+ }
+
private readonly ITabletHandler tabletHandler;
private Bindable rotation;
+ private readonly RotationButton[] rotationPresets = new RotationButton[preset_count];
+ private const int preset_count = 4;
private const int height = 50;
public RotationPresetButtons(ITabletHandler tabletHandler)
@@ -27,18 +36,39 @@ namespace osu.Game.Overlays.Settings.Sections.Input
RelativeSizeAxes = Axes.X;
Height = height;
- for (int i = 0; i < 360; i += 90)
+ IEnumerable createColumns(int count)
{
- var presetRotation = i;
-
- Add(new RotationButton(i)
+ for (int i = 0; i < count; ++i)
{
- RelativeSizeAxes = Axes.X,
- Height = height,
- Width = 0.25f,
- Text = $@"{presetRotation}º",
- Action = () => tabletHandler.Rotation.Value = presetRotation,
- });
+ if (i > 0)
+ yield return new Dimension(GridSizeMode.Absolute, 10);
+
+ yield return new Dimension();
+ }
+ }
+
+ GridContainer grid;
+
+ InternalChild = grid = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ColumnDimensions = createColumns(preset_count).ToArray()
+ };
+
+ grid.Content = new[] { new Drawable[preset_count * 2 - 1] };
+
+ for (int i = 0; i < preset_count; i++)
+ {
+ var rotationValue = i * 90;
+
+ var rotationPreset = new RotationButton(rotationValue)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Height = 1,
+ Text = $@"{rotationValue}º",
+ Action = () => tabletHandler.Rotation.Value = rotationValue,
+ };
+ grid.Content[0][2 * i] = rotationPresets[i] = rotationPreset;
}
}
@@ -49,16 +79,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input
rotation = tabletHandler.Rotation.GetBoundCopy();
rotation.BindValueChanged(val =>
{
- foreach (var b in Children.OfType())
+ foreach (var b in rotationPresets)
b.IsSelected = b.Preset == val.NewValue;
}, true);
}
- public class RotationButton : TriangleButton
+ public class RotationButton : RoundedButton
{
[Resolved]
private OsuColour colours { get; set; }
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; }
+
public readonly int Preset;
public RotationButton(int preset)
@@ -91,18 +124,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private void updateColour()
{
- if (isSelected)
- {
- BackgroundColour = colours.BlueDark;
- Triangles.ColourDark = colours.BlueDarker;
- Triangles.ColourLight = colours.Blue;
- }
- else
- {
- BackgroundColour = colours.Gray4;
- Triangles.ColourDark = colours.Gray5;
- Triangles.ColourLight = colours.Gray6;
- }
+ BackgroundColour = isSelected ? colours.Blue3 : colourProvider.Background3;
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
index 803c8332c1..43df58a8b1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -10,7 +10,6 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Database;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Scoring;
using osu.Game.Skinning;
@@ -21,15 +20,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
protected override LocalisableString Header => "General";
- private TriangleButton importBeatmapsButton;
- private TriangleButton importScoresButton;
- private TriangleButton importSkinsButton;
- private TriangleButton importCollectionsButton;
- private TriangleButton deleteBeatmapsButton;
- private TriangleButton deleteScoresButton;
- private TriangleButton deleteSkinsButton;
- private TriangleButton restoreButton;
- private TriangleButton undeleteButton;
+ private SettingsButton importBeatmapsButton;
+ private SettingsButton importScoresButton;
+ private SettingsButton importSkinsButton;
+ private SettingsButton importCollectionsButton;
+ private SettingsButton deleteBeatmapsButton;
+ private SettingsButton deleteScoresButton;
+ private SettingsButton deleteSkinsButton;
+ private SettingsButton restoreButton;
+ private SettingsButton undeleteButton;
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay)
diff --git a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs
new file mode 100644
index 0000000000..b9339d5299
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Framework.Logging;
+using osu.Game.Rulesets;
+using osu.Game.Localisation;
+
+namespace osu.Game.Overlays.Settings.Sections
+{
+ public class RulesetSection : SettingsSection
+ {
+ public override LocalisableString Header => RulesetSettingsStrings.Rulesets;
+
+ public override Drawable CreateIcon() => new SpriteIcon
+ {
+ Icon = FontAwesome.Solid.Chess
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
+ {
+ try
+ {
+ SettingsSubsection section = ruleset.CreateSettings();
+
+ if (section != null)
+ Add(section);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Failed to load ruleset settings");
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index d18099eb0a..00198235c5 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -64,39 +64,16 @@ namespace osu.Game.Overlays.Settings.Sections
{
Children = new Drawable[]
{
- skinDropdown = new SkinSettingsDropdown(),
+ skinDropdown = new SkinSettingsDropdown
+ {
+ LabelText = SkinSettingsStrings.CurrentSkin
+ },
new SettingsButton
{
Text = SkinSettingsStrings.SkinLayoutEditor,
Action = () => skinEditor?.Toggle(),
},
new ExportSkinButton(),
- new SettingsSlider
- {
- LabelText = SkinSettingsStrings.GameplayCursorSize,
- Current = config.GetBindable(OsuSetting.GameplayCursorSize),
- KeyboardStep = 0.01f
- },
- new SettingsCheckbox
- {
- LabelText = SkinSettingsStrings.AutoCursorSize,
- Current = config.GetBindable(OsuSetting.AutoCursorSize)
- },
- new SettingsCheckbox
- {
- LabelText = SkinSettingsStrings.BeatmapSkins,
- Current = config.GetBindable(OsuSetting.BeatmapSkins)
- },
- new SettingsCheckbox
- {
- LabelText = SkinSettingsStrings.BeatmapColours,
- Current = config.GetBindable(OsuSetting.BeatmapColours)
- },
- new SettingsCheckbox
- {
- LabelText = SkinSettingsStrings.BeatmapHitsounds,
- Current = config.GetBindable(OsuSetting.BeatmapHitsounds)
- },
};
managerUpdated = skins.ItemUpdated.GetBoundCopy();
diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs
index 87b1aa0e46..be7f2de480 100644
--- a/osu.Game/Overlays/Settings/SettingsButton.cs
+++ b/osu.Game/Overlays/Settings/SettingsButton.cs
@@ -6,11 +6,11 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
-using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Overlays.Settings
{
- public class SettingsButton : TriangleButton, IHasTooltip
+ public class SettingsButton : RoundedButton, IHasTooltip
{
public SettingsButton()
{
diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs
index 55e8aee266..c84cba8189 100644
--- a/osu.Game/Overlays/SettingsOverlay.cs
+++ b/osu.Game/Overlays/SettingsOverlay.cs
@@ -24,12 +24,13 @@ namespace osu.Game.Overlays
protected override IEnumerable CreateSections() => new SettingsSection[]
{
new GeneralSection(),
- new GraphicsSection(),
- new AudioSection(),
+ new SkinSection(),
new InputSection(createSubPanel(new KeyBindingPanel())),
new UserInterfaceSection(),
new GameplaySection(),
- new SkinSection(),
+ new RulesetSection(),
+ new AudioSection(),
+ new GraphicsSection(),
new OnlineSection(),
new MaintenanceSection(),
new DebugSection(),
diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
index e9865f6c8b..c0b339a231 100644
--- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
+++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
@@ -55,7 +55,10 @@ namespace osu.Game.Rulesets.UI
///
/// The current direction of playback to be exposed to frame stable children.
///
- private int direction;
+ ///
+ /// Initially it is presumed that playback will proceed in the forward direction.
+ ///
+ private int direction = 1;
[BackgroundDependencyLoader(true)]
private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler)
@@ -139,7 +142,9 @@ namespace osu.Game.Rulesets.UI
state = PlaybackState.NotValid;
}
- if (state == PlaybackState.Valid)
+ // if the proposed time is the same as the current time, assume that the clock will continue progressing in the same direction as previously.
+ // this avoids spurious flips in direction from -1 to 1 during rewinds.
+ if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime)
direction = proposedTime >= manualClock.CurrentTime ? 1 : -1;
double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime);
diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs
index e250791b72..ea158c5789 100644
--- a/osu.Game/Screens/Play/FailAnimation.cs
+++ b/osu.Game/Screens/Play/FailAnimation.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play
{
///
/// Manage the animation to be applied when a player fails.
- /// Single file; automatically disposed after use.
+ /// Single use and automatically disposed after use.
///
public class FailAnimation : CompositeDrawable
{
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 090210e611..444bea049b 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -947,7 +947,7 @@ namespace osu.Game.Screens.Play
public override void OnSuspending(IScreen next)
{
- screenSuspension?.Expire();
+ screenSuspension?.RemoveAndDisposeImmediately();
fadeOut();
base.OnSuspending(next);
@@ -955,7 +955,8 @@ namespace osu.Game.Screens.Play
public override bool OnExiting(IScreen next)
{
- screenSuspension?.Expire();
+ screenSuspension?.RemoveAndDisposeImmediately();
+ failAnimation?.RemoveAndDisposeImmediately();
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
if (prepareScoreForDisplayTask == null)
diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs
index ba3e2bf6ad..a5ed0fc990 100644
--- a/osu.Game/Skinning/ISkinSource.cs
+++ b/osu.Game/Skinning/ISkinSource.cs
@@ -12,6 +12,9 @@ namespace osu.Game.Skinning
///
public interface ISkinSource : ISkin
{
+ ///
+ /// Fired whenever a source change occurs, signalling that consumers should re-query as required.
+ ///
event Action SourceChanged;
///
diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs
index f5a7788359..b884794739 100644
--- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs
+++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs
@@ -58,10 +58,8 @@ namespace osu.Game.Skinning
return base.CreateChildDependencies(parent);
}
- protected override void OnSourceChanged()
+ protected override void RefreshSources()
{
- ResetSources();
-
// Populate a local list first so we can adjust the returned order as we go.
var sources = new List();
@@ -91,8 +89,7 @@ namespace osu.Game.Skinning
else
sources.Add(rulesetResourcesSkin);
- foreach (var skin in sources)
- AddSource(skin);
+ SetSources(sources);
}
protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin)
diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs
index ada6e4b788..c8e4c2c7b6 100644
--- a/osu.Game/Skinning/SkinProvidingContainer.cs
+++ b/osu.Game/Skinning/SkinProvidingContainer.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@@ -40,10 +41,12 @@ namespace osu.Game.Skinning
protected virtual bool AllowColourLookup => true;
+ private readonly object sourceSetLock = new object();
+
///
/// A dictionary mapping each source to a wrapper which handles lookup allowances.
///
- private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>();
+ private (ISkin skin, DisableableSkinSource wrapped)[] skinSources = Array.Empty<(ISkin skin, DisableableSkinSource wrapped)>();
///
/// Constructs a new initialised with a single skin source.
@@ -52,7 +55,7 @@ namespace osu.Game.Skinning
: this()
{
if (skin != null)
- AddSource(skin);
+ SetSources(new[] { skin });
}
///
@@ -168,49 +171,42 @@ namespace osu.Game.Skinning
}
///
- /// Add a new skin to this provider. Will be added to the end of the lookup order precedence.
+ /// Replace the sources used for lookups in this container.
///
- /// The skin to add.
- protected void AddSource(ISkin skin)
+ ///
+ /// This does not implicitly fire a event. Consider calling if required.
+ ///
+ /// The new sources.
+ protected void SetSources(IEnumerable sources)
{
- skinSources.Add((skin, new DisableableSkinSource(skin, this)));
+ lock (sourceSetLock)
+ {
+ foreach (var skin in skinSources)
+ {
+ if (skin.skin is ISkinSource source)
+ source.SourceChanged -= TriggerSourceChanged;
+ }
- if (skin is ISkinSource source)
- source.SourceChanged += TriggerSourceChanged;
+ skinSources = sources.Select(skin => (skin, new DisableableSkinSource(skin, this))).ToArray();
+
+ foreach (var skin in skinSources)
+ {
+ if (skin.skin is ISkinSource source)
+ source.SourceChanged += TriggerSourceChanged;
+ }
+ }
}
///
- /// Remove a skin from this provider.
- ///
- /// The skin to remove.
- protected void RemoveSource(ISkin skin)
- {
- if (skinSources.RemoveAll(s => s.skin == skin) == 0)
- return;
-
- if (skin is ISkinSource source)
- source.SourceChanged -= TriggerSourceChanged;
- }
-
- ///
- /// Clears all skin sources.
- ///
- protected void ResetSources()
- {
- foreach (var i in skinSources.ToArray())
- RemoveSource(i.skin);
- }
-
- ///
- /// Invoked when any source has changed (either or a source registered via ).
+ /// Invoked after any consumed source change, before the external event is fired.
/// This is also invoked once initially during to ensure sources are ready for children consumption.
///
- protected virtual void OnSourceChanged() { }
+ protected virtual void RefreshSources() { }
protected void TriggerSourceChanged()
{
// Expose to implementations, giving them a chance to react before notifying external consumers.
- OnSourceChanged();
+ RefreshSources();
SourceChanged?.Invoke();
}
diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs
index e7125bb034..20c2fcc075 100644
--- a/osu.Game/Skinning/SkinnableTargetContainer.cs
+++ b/osu.Game/Skinning/SkinnableTargetContainer.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Threading;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -22,6 +23,8 @@ namespace osu.Game.Skinning
public bool ComponentsLoaded { get; private set; }
+ private CancellationTokenSource cancellationSource;
+
public SkinnableTargetContainer(SkinnableTarget target)
{
Target = target;
@@ -38,6 +41,9 @@ namespace osu.Game.Skinning
content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer;
+ cancellationSource?.Cancel();
+ cancellationSource = null;
+
if (content != null)
{
LoadComponentAsync(content, wrapper =>
@@ -45,7 +51,7 @@ namespace osu.Game.Skinning
AddInternal(wrapper);
components.AddRange(wrapper.Children.OfType());
ComponentsLoaded = true;
- });
+ }, (cancellationSource = new CancellationTokenSource()).Token);
}
else
ComponentsLoaded = true;
diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs
index aac52b193c..f7b7471634 100644
--- a/osu.Game/Stores/RealmFileStore.cs
+++ b/osu.Game/Stores/RealmFileStore.cs
@@ -8,6 +8,7 @@ using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Models;
using Realms;
@@ -17,14 +18,16 @@ using Realms;
namespace osu.Game.Stores
{
///
- /// Handles the Store and retrieval of Files/FileSets to the database backing
+ /// Handles the storing of files to the file system (and database) backing.
///
+ [ExcludeFromDynamicCompile]
public class RealmFileStore
{
private readonly RealmContextFactory realmFactory;
+
public readonly IResourceStore Store;
- public Storage Storage;
+ public readonly Storage Storage;
public RealmFileStore(RealmContextFactory realmFactory, Storage storage)
{
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 64f1ee4a7a..6d63525011 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
+using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -109,6 +110,8 @@ namespace osu.Game.Tests.Beatmaps
{
var beatmap = GetBeatmap(name);
+ string beforeConversion = beatmap.Serialize();
+
var converterResult = new Dictionary>();
var working = new ConversionWorkingBeatmap(beatmap)
@@ -122,6 +125,10 @@ namespace osu.Game.Tests.Beatmaps
working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods);
+ string afterConversion = beatmap.Serialize();
+
+ Assert.AreEqual(beforeConversion, afterConversion, "Conversion altered original beatmap");
+
return new ConvertResult
{
Mappings = converterResult.Select(r =>
diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs
index 77db697cb6..6a11bd3fea 100644
--- a/osu.Game/Tests/Visual/OsuGameTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs
@@ -78,9 +78,11 @@ namespace osu.Game.Tests.Visual
protected void CreateGame()
{
- AddGame(Game = new TestOsuGame(LocalStorage, API));
+ AddGame(Game = CreateTestGame());
}
+ protected virtual TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API);
+
protected void PushAndConfirm(Func newScreen)
{
Screen screen = null;
@@ -135,7 +137,8 @@ namespace osu.Game.Tests.Visual
public new void PerformFromScreen(Action action, IEnumerable validScreens = null) => base.PerformFromScreen(action, validScreens);
- public TestOsuGame(Storage storage, IAPIProvider api)
+ public TestOsuGame(Storage storage, IAPIProvider api, string[] args = null)
+ : base(args)
{
Storage = storage;
API = api;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 4877ddf725..184c9d3f63 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,7 +36,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index edce9d27fe..38b920420b 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+