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/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
index 576f901c1a..f5752aa606 100644
--- a/osu.Game.Tests/Database/RealmTest.cs
+++ b/osu.Game.Tests/Database/RealmTest.cs
@@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
-using Nito.AsyncEx;
using NUnit.Framework;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -28,42 +27,69 @@ namespace osu.Game.Tests.Database
protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
{
- AsyncContext.Run(() =>
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
{
- var testStorage = storage.GetStorageForDirectory(caller);
-
- using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ host.Run(new RealmTestGame(() =>
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- testAction(realmFactory, testStorage);
+ var testStorage = storage.GetStorageForDirectory(caller);
- realmFactory.Dispose();
+ using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ {
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
+ testAction(realmFactory, testStorage);
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
- Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
- }
- });
+ realmFactory.Dispose();
+
+ Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
+ realmFactory.Compact();
+ Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
+ }
+ }));
+ }
}
protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
{
- AsyncContext.Run(async () =>
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
{
- var testStorage = storage.GetStorageForDirectory(caller);
-
- using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ host.Run(new RealmTestGame(async () =>
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- await testAction(realmFactory, testStorage);
+ var testStorage = storage.GetStorageForDirectory(caller);
- realmFactory.Dispose();
+ using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ {
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
+ await testAction(realmFactory, testStorage);
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
- Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
- }
- });
+ realmFactory.Dispose();
+
+ Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
+ realmFactory.Compact();
+ }
+ }));
+ }
+ }
+
+ private class RealmTestGame : Framework.Game
+ {
+ public RealmTestGame(Func work)
+ {
+ // ReSharper disable once AsyncVoidLambda
+ Scheduler.Add(async () =>
+ {
+ await work().ConfigureAwait(true);
+ Exit();
+ });
+ }
+
+ public RealmTestGame(Action work)
+ {
+ Scheduler.Add(() =>
+ {
+ work();
+ Exit();
+ });
+ }
}
private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
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/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/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/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs
index 0ff902a8bc..c3810eb441 100644
--- a/osu.Game/Database/RealmContextFactory.cs
+++ b/osu.Game/Database/RealmContextFactory.cs
@@ -135,9 +135,8 @@ namespace osu.Game.Database
if (IsDisposed)
throw new ObjectDisposedException(nameof(RealmContextFactory));
- // TODO: this can be added for safety once we figure how to bypass in test
- // if (!ThreadSafety.IsUpdateThread)
- // throw new InvalidOperationException($"{nameof(BlockAllOperations)} must be called from the update thread.");
+ if (!ThreadSafety.IsUpdateThread)
+ throw new InvalidOperationException($"{nameof(BlockAllOperations)} must be called from the update thread.");
Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
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/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..7895715045 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;
@@ -336,6 +338,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 +414,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 +794,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);
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/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/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/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 @@
-
+