diff --git a/osu.Android.props b/osu.Android.props
index ec223f98c2..8fd761691c 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
index efc3f21149..dbfd170ea1 100644
--- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
+++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
@@ -5,23 +5,23 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Platform;
-using osu.Game;
using osu.Game.Configuration;
+using osu.Game.Screens.Play;
namespace osu.Desktop.Windows
{
public class GameplayWinKeyBlocker : Component
{
private Bindable disableWinKey;
- private Bindable localUserPlaying;
+ private IBindable localUserPlaying;
[Resolved]
private GameHost host { get; set; }
[BackgroundDependencyLoader]
- private void load(OsuGame game, OsuConfigManager config)
+ private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
{
- localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
+ localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
localUserPlaying.BindValueChanged(_ => updateBlocking());
disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey);
diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs
new file mode 100644
index 0000000000..050ddf36d5
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkMod.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 System;
+using BenchmarkDotNet.Attributes;
+using osu.Game.Rulesets.Osu.Mods;
+
+namespace osu.Game.Benchmarks
+{
+ public class BenchmarkMod : BenchmarkTest
+ {
+ private OsuModDoubleTime mod;
+
+ [Params(1, 10, 100)]
+ public int Times { get; set; }
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ mod = new OsuModDoubleTime();
+ }
+
+ [Benchmark]
+ public int ModHashCode()
+ {
+ var hashCode = new HashCode();
+
+ for (int i = 0; i < Times; i++)
+ hashCode.Add(mod);
+
+ return hashCode.ToHashCode();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
index 4372ed938c..cfb3fe40be 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{
- public class Movement : StrainSkill
+ public class Movement : StrainDecaySkill
{
private const float absolute_player_positioning_error = 16f;
private const float normalized_hitobject_radius = 41.0f;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
index 2ba2ee6b4a..01d930d585 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
- public class Strain : StrainSkill
+ public class Strain : StrainDecaySkill
{
private const double individual_decay_base = 0.125;
private const double overall_decay_base = 0.30;
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
return individualStrain + overallStrain - CurrentStrain;
}
- protected override double GetPeakStrain(double offset)
+ protected override double CalculateInitialStrain(double offset)
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index e47edc37cc..7bcd867a9c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -10,7 +10,7 @@ using osu.Framework.Utils;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
- public abstract class OsuStrainSkill : StrainSkill
+ public abstract class OsuStrainSkill : StrainDecaySkill
{
///
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
index d4f1602a46..c89527d8bd 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
@@ -64,11 +64,14 @@ namespace osu.Game.Rulesets.Osu.Edit
if (hitObject is DrawableHitCircle circle)
{
- circle.ApproachCircle
- .FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
- .Expire();
+ using (circle.BeginAbsoluteSequence(circle.HitStateUpdateTime))
+ {
+ circle.ApproachCircle
+ .FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
+ .Expire();
- circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
+ circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
+ }
}
if (hitObject is IHasMainCirclePiece mainPieceContainer)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
index 587ff4b573..75d847d54d 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
@@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override bool InterpolateMovements => !disjointTrail;
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
+ protected override bool AvoidDrawingNearCursor => !disjointTrail;
protected override void Update()
{
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 7a95111c91..62cab4d6d7 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -138,6 +138,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected virtual bool InterpolateMovements => true;
protected virtual float IntervalMultiplier => 1.0f;
+ protected virtual bool AvoidDrawingNearCursor => false;
private Vector2? lastPosition;
private readonly InputResampler resampler = new InputResampler();
@@ -171,8 +172,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Vector2 direction = diff / distance;
float interval = partSize.X / 2.5f * IntervalMultiplier;
+ float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
- for (float d = interval; d < distance; d += interval)
+ for (float d = interval; d < stopAt; d += interval)
{
lastPosition = pos1 + direction * d;
addPart(lastPosition.Value);
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
index 769d021362..0c17ca66b9 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
/// Calculates the colour coefficient of taiko difficulty.
///
- public class Colour : StrainSkill
+ public class Colour : StrainDecaySkill
{
protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.4;
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
index a32f6ebe0d..973e55f4b4 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
/// Calculates the rhythm coefficient of taiko difficulty.
///
- public class Rhythm : StrainSkill
+ public class Rhythm : StrainDecaySkill
{
protected override double SkillMultiplier => 10;
protected override double StrainDecayBase => 0;
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
index 4cceadb23f..54cf233d69 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
///
- public class Stamina : StrainSkill
+ public class Stamina : StrainDecaySkill
{
protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.4;
diff --git a/osu.Game.Tests/Beatmaps/BeatmapDifficultyCacheTest.cs b/osu.Game.Tests/Beatmaps/BeatmapDifficultyCacheTest.cs
deleted file mode 100644
index d407c0663f..0000000000
--- a/osu.Game.Tests/Beatmaps/BeatmapDifficultyCacheTest.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using NUnit.Framework;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu.Mods;
-
-namespace osu.Game.Tests.Beatmaps
-{
- [TestFixture]
- public class BeatmapDifficultyCacheTest
- {
- [Test]
- public void TestKeyEqualsWithDifferentModInstances()
- {
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
-
- Assert.That(key1, Is.EqualTo(key2));
- }
-
- [Test]
- public void TestKeyEqualsWithDifferentModOrder()
- {
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
-
- Assert.That(key1, Is.EqualTo(key2));
- }
-
- [TestCase(1.3, DifficultyRating.Easy)]
- [TestCase(1.993, DifficultyRating.Easy)]
- [TestCase(1.998, DifficultyRating.Normal)]
- [TestCase(2.4, DifficultyRating.Normal)]
- [TestCase(2.693, DifficultyRating.Normal)]
- [TestCase(2.698, DifficultyRating.Hard)]
- [TestCase(3.5, DifficultyRating.Hard)]
- [TestCase(3.993, DifficultyRating.Hard)]
- [TestCase(3.997, DifficultyRating.Insane)]
- [TestCase(5.0, DifficultyRating.Insane)]
- [TestCase(5.292, DifficultyRating.Insane)]
- [TestCase(5.297, DifficultyRating.Expert)]
- [TestCase(6.2, DifficultyRating.Expert)]
- [TestCase(6.493, DifficultyRating.Expert)]
- [TestCase(6.498, DifficultyRating.ExpertPlus)]
- [TestCase(8.3, DifficultyRating.ExpertPlus)]
- public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
- {
- var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
-
- Assert.AreEqual(expectedBracket, actualBracket);
- }
- }
-}
diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
new file mode 100644
index 0000000000..da457c9e8f
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
@@ -0,0 +1,146 @@
+// 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 System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Tests.Beatmaps.IO;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ [HeadlessTest]
+ public class TestSceneBeatmapDifficultyCache : OsuTestScene
+ {
+ public const double BASE_STARS = 5.55;
+
+ private BeatmapSetInfo importedSet;
+
+ [Resolved]
+ private BeatmapManager beatmaps { get; set; }
+
+ private TestBeatmapDifficultyCache difficultyCache;
+
+ private IBindable starDifficultyBindable;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osu)
+ {
+ importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result;
+ }
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("setup difficulty cache", () =>
+ {
+ SelectedMods.Value = Array.Empty();
+
+ Child = difficultyCache = new TestBeatmapDifficultyCache();
+
+ starDifficultyBindable = difficultyCache.GetBindableDifficulty(importedSet.Beatmaps.First());
+ });
+
+ AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value?.Stars == BASE_STARS);
+ }
+
+ [Test]
+ public void TestStarDifficultyChangesOnModSettings()
+ {
+ OsuModDoubleTime dt = null;
+
+ AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
+ AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.5);
+
+ AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 1.25);
+ AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.25);
+
+ AddStep("change selected mod to NC", () => SelectedMods.Value = new[] { new OsuModNightcore { SpeedChange = { Value = 1.75 } } });
+ AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.75);
+ }
+
+ [Test]
+ public void TestKeyEqualsWithDifferentModInstances()
+ {
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+
+ Assert.That(key1, Is.EqualTo(key2));
+ Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
+ }
+
+ [Test]
+ public void TestKeyEqualsWithDifferentModOrder()
+ {
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
+
+ Assert.That(key1, Is.EqualTo(key2));
+ Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
+ }
+
+ [Test]
+ public void TestKeyDoesntEqualWithDifferentModSettings()
+ {
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
+
+ Assert.That(key1, Is.Not.EqualTo(key2));
+ Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
+ }
+
+ [Test]
+ public void TestKeyEqualWithMatchingModSettings()
+ {
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
+
+ Assert.That(key1, Is.EqualTo(key2));
+ Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
+ }
+
+ [TestCase(1.3, DifficultyRating.Easy)]
+ [TestCase(1.993, DifficultyRating.Easy)]
+ [TestCase(1.998, DifficultyRating.Normal)]
+ [TestCase(2.4, DifficultyRating.Normal)]
+ [TestCase(2.693, DifficultyRating.Normal)]
+ [TestCase(2.698, DifficultyRating.Hard)]
+ [TestCase(3.5, DifficultyRating.Hard)]
+ [TestCase(3.993, DifficultyRating.Hard)]
+ [TestCase(3.997, DifficultyRating.Insane)]
+ [TestCase(5.0, DifficultyRating.Insane)]
+ [TestCase(5.292, DifficultyRating.Insane)]
+ [TestCase(5.297, DifficultyRating.Expert)]
+ [TestCase(6.2, DifficultyRating.Expert)]
+ [TestCase(6.493, DifficultyRating.Expert)]
+ [TestCase(6.498, DifficultyRating.ExpertPlus)]
+ [TestCase(8.3, DifficultyRating.ExpertPlus)]
+ public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
+ {
+ var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
+
+ Assert.AreEqual(expectedBracket, actualBracket);
+ }
+
+ private class TestBeatmapDifficultyCache : BeatmapDifficultyCache
+ {
+ protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default)
+ {
+ var rateAdjust = lookup.OrderedMods.OfType().SingleOrDefault();
+ if (rateAdjust != null)
+ return Task.FromResult(new StarDifficulty(BASE_STARS + rateAdjust.SpeedChange.Value, 0));
+
+ return Task.FromResult(new StarDifficulty(BASE_STARS, 0));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index 8f5ebf53bd..d87ac29d75 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -7,6 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Collections.IO
@@ -127,7 +128,7 @@ namespace osu.Game.Tests.Collections.IO
[Test]
public async Task TestSaveAndReload()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload", bypassCleanup: true))
{
try
{
@@ -148,7 +149,7 @@ namespace osu.Game.Tests.Collections.IO
}
}
- using (HeadlessGameHost host = new HeadlessGameHost("TestSaveAndReload"))
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload"))
{
try
{
diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
index 27cece42e8..b612899d79 100644
--- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
+++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
@@ -8,7 +8,7 @@ using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Input;
-using osu.Game.Tests.Visual.Navigation;
+using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Input
{
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 4c44e2ec72..5e14af5c27 100644
--- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
+++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
@@ -6,10 +6,10 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using NUnit.Framework;
-using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.IO;
@@ -278,7 +278,7 @@ namespace osu.Game.Tests.NonVisual
private static string getDefaultLocationFor(string testTypeName)
{
- string path = Path.Combine(RuntimeInfo.StartupDirectory, "headless", testTypeName);
+ string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, testTypeName);
if (Directory.Exists(path))
Directory.Delete(path, true);
@@ -288,7 +288,7 @@ namespace osu.Game.Tests.NonVisual
private string prepareCustomPath(string suffix = "")
{
- string path = Path.Combine(RuntimeInfo.StartupDirectory, $"custom-path{suffix}");
+ string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path{suffix}");
if (Directory.Exists(path))
Directory.Delete(path, true);
@@ -308,6 +308,19 @@ namespace osu.Game.Tests.NonVisual
InitialStorage = new DesktopStorage(defaultStorageLocation, this);
InitialStorage.DeleteDirectory(string.Empty);
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ try
+ {
+ // the storage may have changed from the initial location.
+ // this handles cleanup of the initial location.
+ InitialStorage.DeleteDirectory(string.Empty);
+ }
+ catch { }
+ }
}
}
}
diff --git a/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs
new file mode 100644
index 0000000000..5491774e26
--- /dev/null
+++ b/osu.Game.Tests/Online/TestMultiplayerMessagePackSerialization.cs
@@ -0,0 +1,72 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using MessagePack;
+using NUnit.Framework;
+using osu.Game.Online;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
+
+namespace osu.Game.Tests.Online
+{
+ [TestFixture]
+ public class TestMultiplayerMessagePackSerialization
+ {
+ [Test]
+ public void TestSerialiseRoom()
+ {
+ var room = new MultiplayerRoom(1)
+ {
+ MatchState = new TeamVersusRoomState()
+ };
+
+ var serialized = MessagePackSerializer.Serialize(room);
+
+ var deserialized = MessagePackSerializer.Deserialize(serialized);
+
+ Assert.IsTrue(deserialized.MatchState is TeamVersusRoomState);
+ }
+
+ [Test]
+ public void TestSerialiseUserStateExpected()
+ {
+ var state = new TeamVersusUserState();
+
+ var serialized = MessagePackSerializer.Serialize(typeof(MatchUserState), state);
+ var deserialized = MessagePackSerializer.Deserialize(serialized);
+
+ Assert.IsTrue(deserialized is TeamVersusUserState);
+ }
+
+ [Test]
+ public void TestSerialiseUnionFailsWithSingalR()
+ {
+ var state = new TeamVersusUserState();
+
+ // SignalR serialises using the actual type, rather than a base specification.
+ var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state);
+
+ // works with explicit type specified.
+ MessagePackSerializer.Deserialize(serialized);
+
+ // fails with base (union) type.
+ Assert.Throws(() => MessagePackSerializer.Deserialize(serialized));
+ }
+
+ [Test]
+ public void TestSerialiseUnionSucceedsWithWorkaround()
+ {
+ var state = new TeamVersusUserState();
+
+ // SignalR serialises using the actual type, rather than a base specification.
+ var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state, SignalRUnionWorkaroundResolver.OPTIONS);
+
+ // works with explicit type specified.
+ MessagePackSerializer.Deserialize(serialized);
+
+ // works with custom resolver.
+ var deserialized = MessagePackSerializer.Deserialize(serialized, SignalRUnionWorkaroundResolver.OPTIONS);
+ Assert.IsTrue(deserialized is TeamVersusUserState);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index 8124bd4199..bab8dfc983 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -50,10 +50,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(1));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Count, Is.EqualTo(1));
// the first should be overwritten by the second import.
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
}
finally
{
@@ -76,10 +76,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Count, Is.EqualTo(2));
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
}
finally
{
@@ -101,10 +101,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Count, Is.EqualTo(2));
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
- Assert.That(osu.Dependencies.Get().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
+ Assert.That(osu.Dependencies.Get().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
}
finally
{
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
index 0b1617b6a6..0abf0c47f8 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
@@ -78,6 +78,24 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
}
+ [Test]
+ public void TestClampWhenSeekOutsideBeatmapBounds()
+ {
+ AddStep("stop clock", Clock.Stop);
+
+ AddStep("seek before start time", () => Clock.Seek(-1000));
+ AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
+
+ AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000));
+ AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
+
+ AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000));
+ AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
+
+ AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000));
+ AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
+ }
+
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index 3017428039..290ba3317b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
+ private OsuConfigManager localConfig;
+
private HUDOverlay hudOverlay;
[Cached]
@@ -31,8 +33,14 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
- [Resolved]
- private OsuConfigManager config { get; set; }
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
+ }
+
+ [SetUp]
+ public void SetUp() => Schedule(() => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
[Test]
public void TestComboCounterIncrementing()
@@ -85,11 +93,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
- HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
-
- AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
-
- AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
+ AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
@@ -98,37 +102,28 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
-
- AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
[Test]
public void TestExternalHideDoesntAffectConfig()
{
- HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
-
createNew();
- AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
-
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
- AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode));
+ AddAssert("config unchanged", () => localConfig.GetBindable(OsuSetting.HUDVisibilityMode).IsDefault);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
- AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode));
+ AddAssert("config unchanged", () => localConfig.GetBindable(OsuSetting.HUDVisibilityMode).IsDefault);
}
[Test]
public void TestChangeHUDVisibilityOnHiddenKeyCounter()
{
- bool keyCounterVisibleValue = false;
-
createNew();
- AddStep("save keycounter visible value", () => keyCounterVisibleValue = config.Get(OsuSetting.KeyOverlay));
- AddStep("set keycounter visible false", () =>
+ AddStep("hide key overlay", () =>
{
- config.SetValue(OsuSetting.KeyOverlay, false);
+ localConfig.SetValue(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
});
@@ -139,24 +134,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
-
- AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
[Test]
public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad()
{
- HUDVisibilityMode originalConfigValue = default;
-
- AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
-
- AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
+ AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded);
-
- AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
private void createNew(Action action = null)
@@ -175,5 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay
Child = hudOverlay;
});
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ localConfig?.Dispose();
+ base.Dispose(isDisposing);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
index 4fa4c00981..b308f3d7d8 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
@@ -3,7 +3,6 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Bindables;
using osu.Game.Overlays;
using osu.Game.Rulesets;
@@ -66,7 +65,6 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class OverlayTestPlayer : TestPlayer
{
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
- public new Bindable LocalUserPlaying => base.LocalUserPlaying;
}
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
index aaf3323432..9037338e23 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
@@ -10,7 +10,6 @@ using osu.Game.Database;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osu.Game.Tests.Resources;
-using osu.Game.Tests.Visual.Navigation;
namespace osu.Game.Tests.Visual.Menus
{
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
index e58f85b0b3..e34ec6c46a 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
@@ -1,10 +1,13 @@
// 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 NUnit.Framework;
using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Configuration;
using osu.Game.Overlays;
-using osu.Game.Tests.Visual.Navigation;
namespace osu.Game.Tests.Visual.Menus
{
@@ -22,21 +25,48 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestScreenOffsettingOnSettingsOverlay()
{
- AddStep("open settings", () => Game.Settings.Show());
- AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
+ foreach (var scalingMode in Enum.GetValues(typeof(ScalingMode)).Cast())
+ {
+ AddStep($"set scaling mode to {scalingMode}", () =>
+ {
+ Game.LocalConfig.SetValue(OsuSetting.Scaling, scalingMode);
- AddStep("hide settings", () => Game.Settings.Hide());
- AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ if (scalingMode != ScalingMode.Off)
+ {
+ Game.LocalConfig.SetValue(OsuSetting.ScalingSizeX, 0.5f);
+ Game.LocalConfig.SetValue(OsuSetting.ScalingSizeY, 0.5f);
+ }
+ });
+
+ AddStep("open settings", () => Game.Settings.Show());
+ AddUntilStep("right screen offset applied", () => Precision.AlmostEquals(Game.ScreenOffsetContainer.X, SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO));
+
+ AddStep("hide settings", () => Game.Settings.Hide());
+ AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ }
}
[Test]
public void TestScreenOffsettingOnNotificationOverlay()
{
- AddStep("open notifications", () => Game.Notifications.Show());
- AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
+ foreach (var scalingMode in Enum.GetValues(typeof(ScalingMode)).Cast())
+ {
+ if (scalingMode != ScalingMode.Off)
+ {
+ AddStep($"set scaling mode to {scalingMode}", () =>
+ {
+ Game.LocalConfig.SetValue(OsuSetting.Scaling, scalingMode);
+ Game.LocalConfig.SetValue(OsuSetting.ScalingSizeX, 0.5f);
+ Game.LocalConfig.SetValue(OsuSetting.ScalingSizeY, 0.5f);
+ });
+ }
- AddStep("hide notifications", () => Game.Notifications.Hide());
- AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ AddStep("open notifications", () => Game.Notifications.Show());
+ AddUntilStep("right screen offset applied", () => Precision.AlmostEquals(Game.ScreenOffsetContainer.X, -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO));
+
+ AddStep("hide notifications", () => Game.Notifications.Hide());
+ AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
index 299bbacf08..8ca578b592 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
@@ -10,11 +10,11 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Overlays;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
@@ -108,12 +108,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
EndDate = { Value = DateTimeOffset.Now },
}),
createDrawableRoom(new Room
- {
- Name = { Value = "Room 4 (realtime)" },
- Status = { Value = new RoomStatusOpen() },
- Category = { Value = RoomCategory.Realtime },
- }),
- createDrawableRoom(new Room
{
Name = { Value = "Room 4 (spotlight)" },
Status = { Value = new RoomStatusOpen() },
@@ -134,7 +128,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Name = { Value = "Room with password" },
Status = { Value = new RoomStatusOpen() },
- Category = { Value = RoomCategory.Realtime },
+ Type = { Value = MatchType.HeadToHead },
}));
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha));
@@ -159,10 +153,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}));
}
- var drawableRoom = new DrawableRoom(room) { MatchingFilter = true };
- drawableRoom.Action = () => drawableRoom.State = drawableRoom.State == SelectionState.Selected ? SelectionState.NotSelected : SelectionState.Selected;
-
- return drawableRoom;
+ return new DrawableLoungeRoom(room) { MatchingFilter = true };
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
new file mode 100644
index 0000000000..eadfc9b279
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs
@@ -0,0 +1,131 @@
+// 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 Moq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Testing;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Screens.Play;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneGameplayChatDisplay : MultiplayerTestScene
+ {
+ private GameplayChatDisplay chatDisplay;
+
+ [Cached(typeof(ILocalUserPlayInfo))]
+ private ILocalUserPlayInfo localUserInfo;
+
+ private readonly Bindable localUserPlaying = new Bindable();
+
+ private TextBox textBox => chatDisplay.ChildrenOfType().First();
+
+ public TestSceneGameplayChatDisplay()
+ {
+ var mockLocalUserInfo = new Mock();
+ mockLocalUserInfo.SetupGet(i => i.IsPlaying).Returns(localUserPlaying);
+
+ localUserInfo = mockLocalUserInfo.Object;
+ }
+
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("load chat display", () => Child = chatDisplay = new GameplayChatDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 0.5f,
+ });
+
+ AddStep("expand", () => chatDisplay.Expanded.Value = true);
+ }
+
+ [Test]
+ public void TestCantClickWhenPlaying()
+ {
+ setLocalUserPlaying(true);
+
+ AddStep("attempt focus chat", () =>
+ {
+ InputManager.MoveMouseTo(textBox);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ assertChatFocused(false);
+ }
+
+ [Test]
+ public void TestFocusDroppedWhenPlaying()
+ {
+ assertChatFocused(false);
+
+ AddStep("focus chat", () =>
+ {
+ InputManager.MoveMouseTo(textBox);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ setLocalUserPlaying(true);
+ assertChatFocused(false);
+
+ // should still stay non-focused even after entering a new break section.
+ setLocalUserPlaying(false);
+ assertChatFocused(false);
+ }
+
+ [Test]
+ public void TestFocusOnTabKeyWhenExpanded()
+ {
+ setLocalUserPlaying(true);
+
+ assertChatFocused(false);
+ AddStep("press tab", () => InputManager.Key(Key.Tab));
+ assertChatFocused(true);
+ }
+
+ [Test]
+ public void TestFocusOnTabKeyWhenNotExpanded()
+ {
+ AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
+ AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
+
+ AddStep("press tab", () => InputManager.Key(Key.Tab));
+ assertChatFocused(true);
+ AddUntilStep("is visible", () => chatDisplay.IsPresent);
+
+ AddStep("press enter", () => InputManager.Key(Key.Enter));
+ assertChatFocused(false);
+ AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
+ }
+
+ [Test]
+ public void TestFocusToggleViaAction()
+ {
+ AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
+ AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
+
+ AddStep("press tab", () => InputManager.Key(Key.Tab));
+ assertChatFocused(true);
+ AddUntilStep("is visible", () => chatDisplay.IsPresent);
+
+ AddStep("press tab", () => InputManager.Key(Key.Tab));
+ assertChatFocused(false);
+ AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
+ }
+
+ private void assertChatFocused(bool isFocused) =>
+ AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused);
+
+ private void setLocalUserPlaying(bool playing) =>
+ AddStep($"local user {(playing ? "playing" : "not playing")}", () => localUserPlaying.Value = playing);
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index bcbdcd2a4f..2f895d2157 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
@@ -9,6 +9,7 @@ using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
@@ -17,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
{
- protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
+ protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private RoomsContainer container;
@@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
- AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault()));
+ AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault()));
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
@@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
var room = RoomManager.Rooms[1];
RoomManager.RemoveRoom(room);
- RoomManager.AddRoom(room);
+ RoomManager.AddOrUpdateRoom(room);
});
AddAssert("no selection", () => checkRoomSelected(null));
@@ -115,11 +116,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
- AddStep("filter one room", () => container.Filter(new FilterCriteria { SearchString = "1" }));
+ AddStep("filter one room", () => container.Filter.Value = new FilterCriteria { SearchString = "1" });
AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
- AddStep("remove filter", () => container.Filter(null));
+ AddStep("remove filter", () => container.Filter.Value = null);
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
}
@@ -131,13 +132,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo));
// Todo: What even is this case...?
- AddStep("set empty filter criteria", () => container.Filter(null));
+ AddStep("set empty filter criteria", () => container.Filter.Value = null);
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
- AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }));
+ AddStep("filter osu! rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo });
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
- AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo }));
+ AddStep("filter catch rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo });
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
}
@@ -150,6 +151,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private bool checkRoomSelected(Room room) => SelectedRoom.Value == room;
private Room getRoomInFlow(int index) =>
- (container.ChildrenOfType>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
+ (container.ChildrenOfType>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
deleted file mode 100644
index 71ba5db481..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using NUnit.Framework;
-using osu.Game.Beatmaps;
-using osu.Game.Online.Rooms;
-using osu.Game.Rulesets.Osu;
-using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Screens.OnlinePlay.Match.Components;
-using osu.Game.Tests.Visual.OnlinePlay;
-using osu.Game.Users;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public class TestSceneMatchHeader : OnlinePlayTestScene
- {
- [SetUp]
- public new void Setup() => Schedule(() =>
- {
- SelectedRoom.Value = new Room
- {
- Name = { Value = "A very awesome room" },
- Host = { Value = new User { Id = 2, Username = "peppy" } },
- Playlist =
- {
- new PlaylistItem
- {
- Beatmap =
- {
- Value = new BeatmapInfo
- {
- Metadata = new BeatmapMetadata
- {
- Title = "Title",
- Artist = "Artist",
- AuthorString = "Author",
- },
- Version = "Version",
- Ruleset = new OsuRuleset().RulesetInfo
- }
- },
- RequiredMods =
- {
- new OsuModDoubleTime(),
- new OsuModNoFail(),
- new OsuModRelax(),
- }
- }
- }
- };
-
- Child = new Header();
- });
- }
-}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index 65b1d6d53a..18e4a6c575 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -6,13 +6,17 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Users;
@@ -23,6 +27,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Resolved]
private OsuGameBase game { get; set; }
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@@ -80,6 +87,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 20);
}
+ [Test]
+ public void TestSpectatorPlayerInteractiveElementsHidden()
+ {
+ HUDVisibilityMode originalConfigValue = default;
+
+ AddStep("get original config hud visibility", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
+ AddStep("set config hud visibility to always", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
+
+ start(new[] { PLAYER_1_ID, PLAYER_2_ID });
+ loadSpectateScreen(false);
+
+ AddUntilStep("wait for player loaders", () => this.ChildrenOfType().Count() == 2);
+ AddAssert("all player loader settings hidden", () => this.ChildrenOfType().All(l => !l.ChildrenOfType>().Any()));
+
+ AddUntilStep("wait for players to load", () => spectatorScreen.AllPlayersLoaded);
+
+ // components wrapped in skinnable target containers load asynchronously, potentially taking more than one frame to load.
+ // therefore use until step rather than direct assert to account for that.
+ AddUntilStep("all interactive elements removed", () => this.ChildrenOfType().All(p =>
+ !p.ChildrenOfType().Any() &&
+ !p.ChildrenOfType().Any() &&
+ p.ChildrenOfType().SingleOrDefault()?.ShowHandle == false));
+
+ AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
+ }
+
[Test]
public void TestTeamDisplay()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 08b3fb98a8..6c1aed71e6 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -44,6 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayer multiplayerScreen;
private TestMultiplayerClient client;
+ private TestRequestHandlingMultiplayerRoomManager roomManager => multiplayerScreen.RoomManager;
+
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache();
@@ -68,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("load dependencies", () =>
{
- client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
+ client = new TestMultiplayerClient(roomManager);
// The screen gets suspended so it stops receiving updates.
Child = client;
@@ -132,39 +134,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestExitMidJoin()
{
- Room room = null;
-
AddStep("create room", () =>
{
- room = new Room
- {
- Name = { Value = "Test Room" },
- Playlist =
- {
- new PlaylistItem
- {
- Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
- Ruleset = { Value = new OsuRuleset().RulesetInfo },
- }
- }
- };
- });
-
- AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
- AddStep("select room", () => InputManager.Key(Key.Down));
- AddStep("join room and immediately exit", () =>
- {
- multiplayerScreen.ChildrenOfType().Single().Open(room);
- Schedule(() => Stack.CurrentScreen.Exit());
- });
- }
-
- [Test]
- public void TestJoinRoomWithoutPassword()
- {
- AddStep("create room", () =>
- {
- multiplayerScreen.RoomManager.AddRoom(new Room
+ roomManager.AddServerSideRoom(new Room
{
Name = { Value = "Test Room" },
Playlist =
@@ -178,7 +150,39 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
});
- AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
+ AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter());
+ AddUntilStep("wait for room", () => this.ChildrenOfType().Any());
+
+ AddStep("select room", () => InputManager.Key(Key.Down));
+ AddStep("join room and immediately exit select", () =>
+ {
+ InputManager.Key(Key.Enter);
+ Schedule(() => Stack.CurrentScreen.Exit());
+ });
+ }
+
+ [Test]
+ public void TestJoinRoomWithoutPassword()
+ {
+ AddStep("create room", () =>
+ {
+ roomManager.AddServerSideRoom(new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ }
+ }
+ });
+ });
+
+ AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter());
+ AddUntilStep("wait for room", () => this.ChildrenOfType().Any());
+
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room", () => InputManager.Key(Key.Enter));
@@ -211,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
- multiplayerScreen.RoomManager.AddRoom(new Room
+ roomManager.AddServerSideRoom(new Room
{
Name = { Value = "Test Room" },
Password = { Value = "password" },
@@ -226,12 +230,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
});
- AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
+ AddStep("refresh rooms", () => this.ChildrenOfType().Single().UpdateFilter());
+ AddUntilStep("wait for room", () => this.ChildrenOfType().Any());
+
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room", () => InputManager.Key(Key.Enter));
- DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
- AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
+ DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
+ AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick());
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
index c66d5429d6..99f6ab1ae1 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
@@ -9,7 +9,6 @@ using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
@@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
{
- protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
+ protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen;
@@ -59,20 +58,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
- AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any());
+ AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any());
AddStep("exit screen", () => Stack.Exit());
- AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any());
+ AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any());
}
[Test]
public void TestJoinRoomWithPassword()
{
- DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
+ DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
- AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
+ AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick());
@@ -83,12 +82,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestJoinRoomWithPasswordViaKeyboardOnly()
{
- DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
+ DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
- AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
+ AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password");
AddStep("press enter", () => InputManager.Key(Key.Enter));
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index ea10fc1b8b..21364fe154 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -59,23 +59,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for load", () => screen.IsCurrentScreen());
}
- [Test]
- public void TestSettingValidity()
- {
- AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value);
-
- AddStep("set playlist", () =>
- {
- SelectedRoom.Value.Playlist.Add(new PlaylistItem
- {
- Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
- Ruleset = { Value = new OsuRuleset().RulesetInfo },
- });
- });
-
- AddAssert("create button enabled", () => this.ChildrenOfType().Single().Enabled.Value);
- }
-
[Test]
public void TestCreatedRoom()
{
@@ -97,6 +80,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => Client.Room != null);
}
+ [Test]
+ public void TestSettingValidity()
+ {
+ AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("set playlist", () =>
+ {
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
+ {
+ Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ });
+ });
+
+ AddAssert("create button enabled", () => this.ChildrenOfType().Single().Enabled.Value);
+ }
+
[Test]
public void TestStartMatchWhileSpectating()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs
new file mode 100644
index 0000000000..80da7a7e5e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs
@@ -0,0 +1,38 @@
+// 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.Rulesets.Osu;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerPlayer : MultiplayerTestScene
+ {
+ private MultiplayerPlayer player;
+
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("set beatmap", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ });
+
+ AddStep("initialise gameplay", () =>
+ {
+ Stack.Push(player = new MultiplayerPlayer(Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
+ });
+ }
+
+ [Test]
+ public void TestGameplay()
+ {
+ AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
deleted file mode 100644
index b17427a30b..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-// 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.Testing;
-using osu.Game.Online.Rooms;
-using osu.Game.Screens.OnlinePlay.Components;
-using osu.Game.Tests.Beatmaps;
-using osu.Game.Tests.Visual.OnlinePlay;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- [HeadlessTest]
- public class TestSceneMultiplayerRoomManager : MultiplayerTestScene
- {
- protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
-
- public TestSceneMultiplayerRoomManager()
- : base(false)
- {
- }
-
- [Test]
- public void TestPollsInitially()
- {
- AddStep("create room manager with a few rooms", () =>
- {
- RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
- RoomManager.PartRoom();
- RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
- RoomManager.PartRoom();
- RoomManager.ClearRooms();
- });
-
- AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
- AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
- }
-
- [Test]
- public void TestRoomsClearedOnDisconnection()
- {
- AddStep("create room manager with a few rooms", () =>
- {
- RoomManager.CreateRoom(createRoom());
- RoomManager.PartRoom();
- RoomManager.CreateRoom(createRoom());
- RoomManager.PartRoom();
- });
-
- AddStep("disconnect", () => Client.Disconnect());
-
- AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0);
- AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
- }
-
- [Test]
- public void TestRoomsPolledOnReconnect()
- {
- AddStep("create room manager with a few rooms", () =>
- {
- RoomManager.CreateRoom(createRoom());
- RoomManager.PartRoom();
- RoomManager.CreateRoom(createRoom());
- RoomManager.PartRoom();
- });
-
- AddStep("disconnect", () => Client.Disconnect());
- AddStep("connect", () => Client.Connect());
-
- AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
- AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
- }
-
- [Test]
- public void TestRoomsNotPolledWhenJoined()
- {
- AddStep("create room manager with a room", () =>
- {
- RoomManager.CreateRoom(createRoom());
- RoomManager.ClearRooms();
- });
-
- AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0);
- AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
- }
-
- [Test]
- public void TestMultiplayerRoomJoinedWhenCreated()
- {
- AddStep("create room manager with a room", () =>
- {
- RoomManager.CreateRoom(createRoom());
- });
-
- AddUntilStep("multiplayer room joined", () => Client.Room != null);
- }
-
- [Test]
- public void TestMultiplayerRoomPartedWhenAPIRoomParted()
- {
- AddStep("create room manager with a room", () =>
- {
- RoomManager.CreateRoom(createRoom());
- RoomManager.PartRoom();
- });
-
- AddAssert("multiplayer room parted", () => Client.Room == null);
- }
-
- [Test]
- public void TestMultiplayerRoomJoinedWhenAPIRoomJoined()
- {
- AddStep("create room manager with a room", () =>
- {
- var r = createRoom();
- RoomManager.CreateRoom(r);
- RoomManager.PartRoom();
- RoomManager.JoinRoom(r);
- });
-
- AddUntilStep("multiplayer room joined", () => Client.Room != null);
- }
-
- private Room createRoom(Action initFunc = null)
- {
- var room = new Room
- {
- Name =
- {
- Value = "test room"
- },
- Playlist =
- {
- new PlaylistItem
- {
- Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
- Ruleset = { Value = Ruleset.Value }
- }
- }
- };
-
- initFunc?.Invoke(room);
- return room;
- }
-
- private class TestDependencies : MultiplayerTestSceneDependencies
- {
- public TestDependencies()
- {
- // Need to set these values as early as possible.
- RoomManager.TimeBetweenListingPolls.Value = 1;
- RoomManager.TimeBetweenSelectionPolls.Value = 1;
- }
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
index 26641214b1..b8232837b5 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -88,21 +89,27 @@ namespace osu.Game.Tests.Visual.Navigation
[Resolved]
private OsuGameBase gameBase { get; set; }
- [BackgroundDependencyLoader]
- private void load(GameHost host)
- {
- game = new OsuGame();
- game.SetHost(host);
+ [Resolved]
+ private GameHost host { get; set; }
- Children = new Drawable[]
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create game", () =>
{
- new Box
+ game = new OsuGame();
+ game.SetHost(host);
+
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- game
- };
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ },
+ game
+ };
+ });
AddUntilStep("wait for load", () => game.IsLoaded);
}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
index c1c968e862..7e3d8290be 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
@@ -9,9 +9,12 @@ namespace osu.Game.Tests.Visual.Navigation
{
public class TestSettingsMigration : OsuGameTestScene
{
- public override void RecycleLocalStorage()
+ public override void RecycleLocalStorage(bool isDisposing)
{
- base.RecycleLocalStorage();
+ base.RecycleLocalStorage(isDisposing);
+
+ if (isDisposing)
+ return;
using (var config = new OsuConfigManager(LocalStorage))
{
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
index aff0e7ba4b..a4bcb4baae 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
{
- protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
+ protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen;
@@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddStep("add rooms", () => RoomManager.AddRooms(30));
+ AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
@@ -53,6 +54,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void TestScrollSelectedIntoView()
{
AddStep("add rooms", () => RoomManager.AddRooms(30));
+ AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
index cdc655500d..04b44efd32 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
@@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent);
}
- private class TestRoomSettings : PlaylistsMatchSettingsOverlay
+ private class TestRoomSettings : PlaylistsRoomSettingsOverlay
{
public TriangleButton ApplyButton => ((MatchSettings)Settings).ApplyButton;
@@ -141,6 +141,12 @@ namespace osu.Game.Tests.Visual.Playlists
public IBindableList Rooms => null;
+ public void AddOrUpdateRoom(Room room) => throw new NotImplementedException();
+
+ public void RemoveRoom(Room room) => throw new NotImplementedException();
+
+ public void ClearRooms() => throw new NotImplementedException();
+
public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
{
if (CreateRequested == null)
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
index 9fc29049ef..9051c71fc6 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
@@ -11,7 +11,6 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
-using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
@@ -19,7 +18,6 @@ using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual.OnlinePlay;
-using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Playlists
@@ -36,18 +34,6 @@ namespace osu.Game.Tests.Visual.Playlists
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
-
- ((DummyAPIAccess)API).HandleRequest = req =>
- {
- switch (req)
- {
- case CreateRoomScoreRequest createRoomScoreRequest:
- createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
- return true;
- }
-
- return false;
- };
}
[SetUpSteps]
@@ -66,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
SelectedRoom.Value.RoomID.Value = 1;
SelectedRoom.Value.Name.Value = "my awesome room";
- SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value);
SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
SelectedRoom.Value.Playlist.Add(new PlaylistItem
@@ -86,7 +72,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("set room properties", () =>
{
SelectedRoom.Value.Name.Value = "my awesome room";
- SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
@@ -96,7 +82,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("move mouse to create button", () =>
{
- InputManager.MoveMouseTo(this.ChildrenOfType().Single());
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
@@ -137,7 +123,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("load room", () =>
{
SelectedRoom.Value.Name.Value = "my awesome room";
- SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = importedSet.Beatmaps[0] },
@@ -147,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("create room", () =>
{
- InputManager.MoveMouseTo(match.ChildrenOfType().Single());
+ InputManager.MoveMouseTo(match.ChildrenOfType().Single());
InputManager.Click(MouseButton.Left);
});
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs
deleted file mode 100644
index 566452249f..0000000000
--- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-// 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.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Utils;
-using osu.Game.Beatmaps;
-using osu.Game.Screens.Ranking.Expanded;
-using osuTK;
-
-namespace osu.Game.Tests.Visual.Ranking
-{
- public class TestSceneStarRatingDisplay : OsuTestScene
- {
- [Test]
- public void TestDisplay()
- {
- AddStep("load displays", () => Child = new FillFlowContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- ChildrenEnumerable = new[]
- {
- 1.23,
- 2.34,
- 3.45,
- 4.56,
- 5.67,
- 6.78,
- 10.11,
- }.Select(starRating => new StarRatingDisplay(new StarDifficulty(starRating, 0))
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre
- })
- });
- }
-
- [Test]
- public void TestChangingStarRatingDisplay()
- {
- StarRatingDisplay starRating = null;
-
- AddStep("load display", () => Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1))
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(3f),
- });
-
- AddRepeatStep("set random value", () =>
- {
- starRating.Current.Value = new StarDifficulty(RNG.NextDouble(0.0, 11.0), 1);
- }, 10);
-
- AddSliderStep("set exact stars", 0.0, 11.0, 5.55, d =>
- {
- if (starRating != null)
- starRating.Current.Value = new StarDifficulty(d, 1);
- });
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index fa2c9ecdea..57ba051214 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Settings
[SetUpSteps]
public void SetUpSteps()
{
+ AddUntilStep("wait for load", () => panel.ChildrenOfType().Any());
AddStep("Scroll to top", () => panel.ChildrenOfType().First().ScrollToTop());
AddWaitStep("wait for scroll", 5);
}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
index df59b9284b..d9cce69ee3 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
@@ -76,5 +76,23 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("restore default", () => sliderBar.Current.SetDefault());
AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0);
}
+
+ [Test]
+ public void TestWarningTextVisibility()
+ {
+ SettingsNumberBox numberBox = null;
+
+ AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox());
+ AddAssert("warning text not created", () => !numberBox.ChildrenOfType().Any());
+
+ AddStep("set warning text", () => numberBox.WarningText = "this is a warning!");
+ AddAssert("warning text created", () => numberBox.ChildrenOfType().Single().Alpha == 1);
+
+ AddStep("unset warning text", () => numberBox.WarningText = default);
+ AddAssert("warning text hidden", () => numberBox.ChildrenOfType().Single().Alpha == 0);
+
+ AddStep("set warning text again", () => numberBox.WarningText = "another warning!");
+ AddAssert("warning text shown again", () => numberBox.ChildrenOfType().Single().Alpha == 1);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
index 115d2fec7d..0af77b3b5a 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Settings
@@ -11,27 +12,39 @@ namespace osu.Game.Tests.Visual.Settings
[TestFixture]
public class TestSceneSettingsPanel : OsuTestScene
{
- private readonly SettingsPanel settings;
- private readonly DialogOverlay dialogOverlay;
+ private SettingsPanel settings;
+ private DialogOverlay dialogOverlay;
- public TestSceneSettingsPanel()
+ [SetUpSteps]
+ public void SetUpSteps()
{
- settings = new SettingsOverlay
+ AddStep("create settings", () =>
{
- State = { Value = Visibility.Visible }
- };
- Add(dialogOverlay = new DialogOverlay
- {
- Depth = -1
+ settings?.Expire();
+
+ Add(settings = new SettingsOverlay
+ {
+ State = { Value = Visibility.Visible }
+ });
});
}
+ [Test]
+ public void ToggleVisibility()
+ {
+ AddWaitStep("wait some", 5);
+ AddToggleStep("toggle editor visibility", visible => settings.ToggleVisibility());
+ }
+
[BackgroundDependencyLoader]
private void load()
{
- Dependencies.Cache(dialogOverlay);
+ Add(dialogOverlay = new DialogOverlay
+ {
+ Depth = -1
+ });
- Add(settings);
+ Dependencies.Cache(dialogOverlay);
}
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
index a416fd4daf..2b38c4f936 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
@@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
@@ -65,6 +66,12 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("show", () => { infoWedge.Show(); });
+ AddSliderStep("change star difficulty", 0, 11.9, 5.55, v =>
+ {
+ foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType>())
+ hasCurrentValue.Current.Value = new StarDifficulty(v, 0);
+ });
+
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
var instance = rulesetInfo.CreateInstance();
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
index 5e2d5eba5d..53cb628bb3 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
@@ -14,7 +14,6 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
-using osu.Game.Tests.Visual.Navigation;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.SongSelect
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
index e0d76b3e4a..f8652573f4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
@@ -1,8 +1,10 @@
// 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 NUnit.Framework;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -174,6 +176,60 @@ namespace osu.Game.Tests.Visual.UserInterface
checkBindableAtValue("Circle Size", null);
}
+ [Test]
+ public void TestModSettingChangeTracker()
+ {
+ ModSettingChangeTracker tracker = null;
+ Queue settingsChangedQueue = null;
+
+ setBeatmapWithDifficultyParameters(5);
+
+ AddStep("add mod settings change tracker", () =>
+ {
+ settingsChangedQueue = new Queue();
+
+ tracker = new ModSettingChangeTracker(modDifficultyAdjust.Yield())
+ {
+ SettingChanged = settingsChangedQueue.Enqueue
+ };
+ });
+
+ AddAssert("no settings changed", () => settingsChangedQueue.Count == 0);
+
+ setSliderValue("Circle Size", 3);
+
+ settingsChangedFired();
+
+ setSliderValue("Circle Size", 5);
+ checkBindableAtValue("Circle Size", 5);
+
+ settingsChangedFired();
+
+ AddStep("reset mod settings", () => modDifficultyAdjust.CircleSize.SetDefault());
+ checkBindableAtValue("Circle Size", null);
+
+ settingsChangedFired();
+
+ setExtendedLimits(true);
+
+ settingsChangedFired();
+
+ AddStep("dispose tracker", () =>
+ {
+ tracker.Dispose();
+ tracker = null;
+ });
+
+ void settingsChangedFired()
+ {
+ AddAssert("setting changed event fired", () =>
+ {
+ settingsChangedQueue.Dequeue();
+ return settingsChangedQueue.Count == 0;
+ });
+ }
+ }
+
private void resetToDefault(string name)
{
AddStep($"Reset {name} to default", () =>
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs
index 5c2e6e457d..2312c57af2 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSectionsContainer.cs
@@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
using osuTK.Graphics;
@@ -61,10 +62,12 @@ namespace osu.Game.Tests.Visual.UserInterface
));
AddStep("scroll up", () => triggerUserScroll(1));
AddStep("scroll down", () => triggerUserScroll(-1));
+ AddStep("scroll up a bit", () => triggerUserScroll(0.1f));
+ AddStep("scroll down a bit", () => triggerUserScroll(-0.1f));
}
[Test]
- public void TestCorrectSectionSelected()
+ public void TestCorrectSelectionAndVisibleTop()
{
const int sections_count = 11;
float[] alternating = { 0.07f, 0.33f, 0.16f, 0.33f };
@@ -79,6 +82,12 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddStep($"scroll to section {scrollIndex + 1}", () => container.ScrollTo(container.Children[scrollIndex]));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children[scrollIndex]);
+ AddUntilStep("section top is visible", () =>
+ {
+ float scrollPosition = container.ChildrenOfType().First().Current;
+ float sectionTop = container.Children[scrollIndex].BoundingBox.Top;
+ return scrollPosition < sectionTop;
+ });
}
for (int i = 1; i < sections_count; i++)
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs
new file mode 100644
index 0000000000..2806e6d347
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs
@@ -0,0 +1,71 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneStarRatingDisplay : OsuTestScene
+ {
+ [TestCase(StarRatingDisplaySize.Regular)]
+ [TestCase(StarRatingDisplaySize.Small)]
+ public void TestDisplay(StarRatingDisplaySize size)
+ {
+ AddStep("load displays", () =>
+ {
+ Child = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(2f),
+ Direction = FillDirection.Horizontal,
+ ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(2f),
+ Direction = FillDirection.Vertical,
+ ChildrenEnumerable = Enumerable.Range(0, 10).Select(j => new StarRatingDisplay(new StarDifficulty(i * (i >= 11 ? 25f : 1f) + j * 0.1f, 0), size)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }),
+ })
+ };
+ });
+ }
+
+ [Test]
+ public void TestSpectrum()
+ {
+ StarRatingDisplay starRating = null;
+
+ AddStep("load display", () => Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1), animated: true)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(3f),
+ });
+
+ AddRepeatStep("set random value", () =>
+ {
+ starRating.Current.Value = new StarDifficulty(RNG.NextDouble(0.0, 11.0), 1);
+ }, 10);
+
+ AddSliderStep("set exact stars", 0.0, 11.0, 5.55, d =>
+ {
+ if (starRating != null)
+ starRating.Current.Value = new StarDifficulty(d, 1);
+ });
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
index 6ed623d0c0..5025e4ad4b 100644
--- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
@@ -14,6 +14,7 @@ using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Framework.Utils;
+using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -56,12 +57,28 @@ namespace osu.Game.Beatmaps
[Resolved]
private Bindable> currentMods { get; set; }
+ private ModSettingChangeTracker modSettingChangeTracker;
+ private ScheduledDelegate debouncedModSettingsChange;
+
protected override void LoadComplete()
{
base.LoadComplete();
currentRuleset.BindValueChanged(_ => updateTrackedBindables());
- currentMods.BindValueChanged(_ => updateTrackedBindables(), true);
+
+ currentMods.BindValueChanged(mods =>
+ {
+ modSettingChangeTracker?.Dispose();
+
+ updateTrackedBindables();
+
+ modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
+ modSettingChangeTracker.SettingChanged += _ =>
+ {
+ debouncedModSettingsChange?.Cancel();
+ debouncedModSettingsChange = Scheduler.AddDelayed(updateTrackedBindables, 100);
+ };
+ }, true);
}
///
@@ -84,7 +101,7 @@ namespace osu.Game.Beatmaps
/// Retrieves a bindable containing the star difficulty of a with a given and combination.
///
///
- /// The bindable will not update to follow the currently-selected ruleset and mods.
+ /// The bindable will not update to follow the currently-selected ruleset and mods or its settings.
///
/// The to get the difficulty of.
/// The to get the difficulty with. If null, the 's ruleset is used.
@@ -275,6 +292,8 @@ namespace osu.Game.Beatmaps
{
base.Dispose(isDisposing);
+ modSettingChangeTracker?.Dispose();
+
cancelTrackedBindableUpdate();
updateScheduler?.Dispose();
}
@@ -297,7 +316,7 @@ namespace osu.Game.Beatmaps
public bool Equals(DifficultyCacheLookup other)
=> Beatmap.ID == other.Beatmap.ID
&& Ruleset.ID == other.Ruleset.ID
- && OrderedMods.Select(m => m.Acronym).SequenceEqual(other.OrderedMods.Select(m => m.Acronym));
+ && OrderedMods.SequenceEqual(other.OrderedMods);
public override int GetHashCode()
{
@@ -307,7 +326,7 @@ namespace osu.Game.Beatmaps
hashCode.Add(Ruleset.ID);
foreach (var mod in OrderedMods)
- hashCode.Add(mod.Acronym);
+ hashCode.Add(mod);
return hashCode.ToHashCode();
}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 4a78ceb299..241649062e 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -365,6 +365,10 @@ namespace osu.Game.Beatmaps
queryable = beatmaps.BeatmapSetsOverview;
break;
+ case IncludedDetails.AllButRuleset:
+ queryable = beatmaps.BeatmapSetsWithoutRuleset;
+ break;
+
case IncludedDetails.AllButFiles:
queryable = beatmaps.BeatmapSetsWithoutFiles;
break;
@@ -384,8 +388,33 @@ namespace osu.Game.Beatmaps
/// Perform a lookup query on available s.
///
/// The query.
+ /// The level of detail to include in the returned objects.
/// Results from the provided query.
- public IEnumerable QueryBeatmapSets(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().Where(query);
+ public IEnumerable QueryBeatmapSets(Expression> query, IncludedDetails includes = IncludedDetails.All)
+ {
+ IQueryable queryable;
+
+ switch (includes)
+ {
+ case IncludedDetails.Minimal:
+ queryable = beatmaps.BeatmapSetsOverview;
+ break;
+
+ case IncludedDetails.AllButRuleset:
+ queryable = beatmaps.BeatmapSetsWithoutRuleset;
+ break;
+
+ case IncludedDetails.AllButFiles:
+ queryable = beatmaps.BeatmapSetsWithoutFiles;
+ break;
+
+ default:
+ queryable = beatmaps.ConsumableItems;
+ break;
+ }
+
+ return queryable.AsNoTracking().Where(query);
+ }
///
/// Perform a lookup query on available s.
@@ -554,6 +583,11 @@ namespace osu.Game.Beatmaps
///
AllButFiles,
+ ///
+ /// Include everything except ruleset. Used for cases where we aren't sure the ruleset is present but still want to consume the beatmap.
+ ///
+ AllButRuleset,
+
///
/// Include everything.
///
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index 642bafd2ac..e3214b7c03 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -92,6 +92,13 @@ namespace osu.Game.Beatmaps
.Include(s => s.Beatmaps)
.AsNoTracking();
+ public IQueryable BeatmapSetsWithoutRuleset => ContextFactory.Get().BeatmapSetInfo
+ .Include(s => s.Metadata)
+ .Include(s => s.Files).ThenInclude(f => f.FileInfo)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .AsNoTracking();
+
public IQueryable BeatmapSetsWithoutFiles => ContextFactory.Get().BeatmapSetInfo
.Include(s => s.Metadata)
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
diff --git a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs
new file mode 100644
index 0000000000..c239fda455
--- /dev/null
+++ b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs
@@ -0,0 +1,168 @@
+// 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.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Localisation;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Beatmaps.Drawables
+{
+ ///
+ /// A pill that displays the star rating of a beatmap.
+ ///
+ public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue
+ {
+ private readonly bool animated;
+ private readonly Box background;
+ private readonly SpriteIcon starIcon;
+ private readonly OsuSpriteText starsText;
+
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
+
+ private readonly Bindable displayedStars = new BindableDouble();
+
+ ///
+ /// The currently displayed stars of this display wrapped in a bindable.
+ /// This bindable gets transformed on change rather than instantaneous, if animation is enabled.
+ ///
+ public IBindable DisplayedStars => displayedStars;
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [Resolved(canBeNull: true)]
+ private OverlayColourProvider colourProvider { get; set; }
+
+ ///
+ /// Creates a new using an already computed .
+ ///
+ /// The already computed to display.
+ /// The size of the star rating display.
+ /// Whether the star rating display will perform transforms on change rather than updating instantaneously.
+ public StarRatingDisplay(StarDifficulty starDifficulty, StarRatingDisplaySize size = StarRatingDisplaySize.Regular, bool animated = false)
+ {
+ this.animated = animated;
+
+ Current.Value = starDifficulty;
+
+ AutoSizeAxes = Axes.Both;
+
+ MarginPadding margin = default;
+
+ switch (size)
+ {
+ case StarRatingDisplaySize.Small:
+ margin = new MarginPadding { Horizontal = 7f };
+ break;
+
+ case StarRatingDisplaySize.Range:
+ margin = new MarginPadding { Horizontal = 8f };
+ break;
+
+ case StarRatingDisplaySize.Regular:
+ margin = new MarginPadding { Horizontal = 8f, Vertical = 2f };
+ break;
+ }
+
+ InternalChild = new CircularContainer
+ {
+ Masking = true,
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new GridContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Margin = margin,
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Absolute, 3f),
+ new Dimension(GridSizeMode.AutoSize, minSize: 25f),
+ },
+ RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
+ Content = new[]
+ {
+ new[]
+ {
+ starIcon = new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Icon = FontAwesome.Solid.Star,
+ Size = new Vector2(8f),
+ },
+ Empty(),
+ starsText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Margin = new MarginPadding { Bottom = 1.5f },
+ // todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f
+ // see https://github.com/ppy/osu-framework/issues/3271.
+ Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
+ Shadow = false,
+ },
+ }
+ }
+ },
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Current.BindValueChanged(c =>
+ {
+ if (animated)
+ this.TransformBindableTo(displayedStars, c.NewValue.Stars, 750, Easing.OutQuint);
+ else
+ displayedStars.Value = c.NewValue.Stars;
+ });
+
+ displayedStars.Value = Current.Value.Stars;
+
+ displayedStars.BindValueChanged(s =>
+ {
+ starsText.Text = s.NewValue.ToLocalisableString("0.00");
+
+ background.Colour = colours.ForStarDifficulty(s.NewValue);
+
+ starIcon.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
+ starsText.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
+ }, true);
+ }
+ }
+
+ public enum StarRatingDisplaySize
+ {
+ Small,
+ Range,
+ Regular,
+ }
+}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 6c7adcc806..9b0d7f51da 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -101,7 +101,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.HitLighting, true);
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
- SetDefault(OsuSetting.ShowDifficultyGraph, true);
+ SetDefault(OsuSetting.ShowProgressGraph, true);
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false);
@@ -201,6 +201,8 @@ namespace osu.Game.Configuration
public Func LookupKeyBindings { get; set; }
}
+ // IMPORTANT: These are used in user configuration files.
+ // The naming of these keys should not be changed once they are deployed in a release, unless migration logic is also added.
public enum OsuSetting
{
Ruleset,
@@ -217,7 +219,7 @@ namespace osu.Game.Configuration
AlwaysPlayFirstComboBreak,
FloatingComments,
HUDVisibilityMode,
- ShowDifficultyGraph,
+ ShowProgressGraph,
ShowHealthDisplayWhenCantFail,
FadePlayfieldWhenHealthLow,
MouseDisableButtons,
diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs
index c9d0c4bc41..b0feb7bb78 100644
--- a/osu.Game/Database/MutableDatabaseBackedStore.cs
+++ b/osu.Game/Database/MutableDatabaseBackedStore.cs
@@ -36,6 +36,11 @@ namespace osu.Game.Database
///
public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set());
+ ///
+ /// Access barebones items with no includes.
+ ///
+ public IQueryable Items => ContextFactory.Get().Set();
+
///
/// Add a to the database.
///
diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs
index 8ab146efe7..76492cab55 100644
--- a/osu.Game/Graphics/Containers/SectionsContainer.cs
+++ b/osu.Game/Graphics/Containers/SectionsContainer.cs
@@ -22,7 +22,8 @@ namespace osu.Game.Graphics.Containers
where T : Drawable
{
public Bindable SelectedSection { get; } = new Bindable();
- private Drawable lastClickedSection;
+
+ private T lastClickedSection;
public Drawable ExpandableHeader
{
@@ -144,10 +145,25 @@ namespace osu.Game.Graphics.Containers
footerHeight = null;
}
- public void ScrollTo(Drawable section)
+ public void ScrollTo(Drawable target)
{
- lastClickedSection = section;
- scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - scrollContainer.DisplayableContent * scroll_y_centre - (FixedHeader?.BoundingBox.Height ?? 0));
+ lastKnownScroll = null;
+
+ float fixedHeaderSize = FixedHeader?.BoundingBox.Height ?? 0;
+
+ // implementation similar to ScrollIntoView but a bit more nuanced.
+ float top = scrollContainer.GetChildPosInContent(target);
+
+ float bottomScrollExtent = scrollContainer.ScrollableExtent - fixedHeaderSize;
+ float scrollTarget = top - fixedHeaderSize - scrollContainer.DisplayableContent * scroll_y_centre;
+
+ if (scrollTarget > bottomScrollExtent)
+ scrollContainer.ScrollToEnd();
+ else
+ scrollContainer.ScrollTo(scrollTarget);
+
+ if (target is T section)
+ lastClickedSection = section;
}
public void ScrollToTop() => scrollContainer.ScrollTo(0);
@@ -170,13 +186,22 @@ namespace osu.Game.Graphics.Containers
if (source == InvalidationSource.Child && (invalidation & Invalidation.DrawSize) != 0)
{
- lastKnownScroll = null;
+ InvalidateScrollPosition();
result = true;
}
return result;
}
+ protected void InvalidateScrollPosition()
+ {
+ Schedule(() =>
+ {
+ lastKnownScroll = null;
+ lastClickedSection = null;
+ });
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@@ -224,15 +249,19 @@ namespace osu.Game.Graphics.Containers
float scrollCentre = fixedHeaderSize + scrollContainer.DisplayableContent * scroll_y_centre + selectionLenienceAboveSection;
- if (Precision.AlmostBigger(0, scrollContainer.Current))
- SelectedSection.Value = lastClickedSection as T ?? Children.FirstOrDefault();
+ var presentChildren = Children.Where(c => c.IsPresent);
+
+ if (lastClickedSection != null)
+ SelectedSection.Value = lastClickedSection;
+ else if (Precision.AlmostBigger(0, scrollContainer.Current))
+ SelectedSection.Value = presentChildren.FirstOrDefault();
else if (Precision.AlmostBigger(scrollContainer.Current, scrollContainer.ScrollableExtent))
- SelectedSection.Value = lastClickedSection as T ?? Children.LastOrDefault();
+ SelectedSection.Value = presentChildren.LastOrDefault();
else
{
- SelectedSection.Value = Children
+ SelectedSection.Value = presentChildren
.TakeWhile(section => scrollContainer.GetChildPosInContent(section) - currentScroll - scrollCentre <= 0)
- .LastOrDefault() ?? Children.FirstOrDefault();
+ .LastOrDefault() ?? presentChildren.FirstOrDefault();
}
}
}
diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs
index 17506ce0f5..0561051e35 100644
--- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs
+++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs
@@ -42,6 +42,12 @@ namespace osu.Game.Graphics.Containers
base.OnUserScroll(value, animated, distanceDecay);
}
+ public new void ScrollIntoView(Drawable target, bool animated = true)
+ {
+ UserScrolling = false;
+ base.ScrollIntoView(target, animated);
+ }
+
public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null)
{
UserScrolling = false;
diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
index f77a3109c9..ed9f0710b0 100644
--- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
@@ -22,9 +22,14 @@ namespace osu.Game.Graphics.UserInterface
public void TakeFocus()
{
- if (allowImmediateFocus) GetContainingInputManager().ChangeFocus(this);
+ if (!allowImmediateFocus)
+ return;
+
+ Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this), false);
}
+ public new void KillFocus() => base.KillFocus();
+
public bool HoldFocus
{
get => allowImmediateFocus && focus;
diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs
index 8d686e8c2f..664f32b083 100644
--- a/osu.Game/Graphics/UserInterface/Nub.cs
+++ b/osu.Game/Graphics/UserInterface/Nub.cs
@@ -6,6 +6,7 @@ using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
@@ -57,18 +58,13 @@ namespace osu.Game.Graphics.UserInterface
EdgeEffect = new EdgeEffectParameters
{
- Colour = GlowColour,
+ Colour = GlowColour.Opacity(0),
Type = EdgeEffectType.Glow,
Radius = 10,
Roundness = 8,
};
}
- protected override void LoadComplete()
- {
- FadeEdgeEffectTo(0);
- }
-
private bool glowing;
public bool Glowing
@@ -153,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface
glowColour = value;
var effect = EdgeEffect;
- effect.Colour = value;
+ effect.Colour = Glowing ? value : value.Opacity(0);
EdgeEffect = effect;
}
}
diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs
index cd9ca9f87f..82a3e73b84 100644
--- a/osu.Game/Graphics/UserInterface/OsuButton.cs
+++ b/osu.Game/Graphics/UserInterface/OsuButton.cs
@@ -36,6 +36,7 @@ namespace osu.Game.Graphics.UserInterface
public Color4 BackgroundColour
{
+ get => backgroundColour ?? Color4.White;
set
{
backgroundColour = value;
diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index 61dd5fb2d9..42f628a75a 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -69,6 +69,7 @@ namespace osu.Game.Graphics.UserInterface
BackgroundColour = Color4.Black.Opacity(0.5f);
MaskingContainer.CornerRadius = corner_radius;
+ Alpha = 0;
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
ItemsContainer.Padding = new MarginPadding(5);
@@ -94,9 +95,11 @@ namespace osu.Game.Graphics.UserInterface
protected override void AnimateClose()
{
- this.FadeOut(300, Easing.OutQuint);
if (wasOpened)
+ {
+ this.FadeOut(300, Easing.OutQuint);
sampleClose?.Play();
+ }
}
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index d3cc90ef99..0176a00e9d 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -90,6 +90,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
+ new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus),
};
public IEnumerable SongSelectKeyBindings => new[]
@@ -280,5 +281,8 @@ namespace osu.Game.Input.Bindings
[Description("Seek replay backward")]
SeekReplayBackward,
+
+ [Description("Toggle chat focus")]
+ ToggleChatFocus
}
}
diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs
index 75d9c8debb..d2bf953dbc 100644
--- a/osu.Game/Input/ConfineMouseTracker.cs
+++ b/osu.Game/Input/ConfineMouseTracker.cs
@@ -7,6 +7,7 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Configuration;
+using osu.Game.Screens.Play;
namespace osu.Game.Input
{
@@ -24,14 +25,14 @@ namespace osu.Game.Input
private IBindable localUserPlaying;
[BackgroundDependencyLoader]
- private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
+ private void load(ILocalUserPlayInfo localUserInfo, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
{
frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode);
frameworkWindowMode = frameworkConfigManager.GetBindable(FrameworkSetting.WindowMode);
frameworkWindowMode.BindValueChanged(_ => updateConfineMode());
osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode);
- localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
+ localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
osuConfineMode.ValueChanged += _ => updateConfineMode();
localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);
diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs
new file mode 100644
index 0000000000..aa6eabd7d1
--- /dev/null
+++ b/osu.Game/Localisation/AudioSettingsStrings.cs
@@ -0,0 +1,64 @@
+// 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 AudioSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.AudioSettings";
+
+ ///
+ /// "Audio"
+ ///
+ public static LocalisableString AudioSectionHeader => new TranslatableString(getKey(@"audio_section_header"), @"Audio");
+
+ ///
+ /// "Devices"
+ ///
+ public static LocalisableString AudioDevicesHeader => new TranslatableString(getKey(@"audio_devices_header"), @"Devices");
+
+ ///
+ /// "Volume"
+ ///
+ public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
+
+ ///
+ /// "Master"
+ ///
+ public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
+
+ ///
+ /// "Master (window inactive)"
+ ///
+ public static LocalisableString MasterVolumeInactive => new TranslatableString(getKey(@"master_volume_inactive"), @"Master (window inactive)");
+
+ ///
+ /// "Effect"
+ ///
+ public static LocalisableString EffectVolume => new TranslatableString(getKey(@"effect_volume"), @"Effect");
+
+ ///
+ /// "Music"
+ ///
+ public static LocalisableString MusicVolume => new TranslatableString(getKey(@"music_volume"), @"Music");
+
+ ///
+ /// "Offset Adjustment"
+ ///
+ public static LocalisableString OffsetHeader => new TranslatableString(getKey(@"offset_header"), @"Offset Adjustment");
+
+ ///
+ /// "Audio offset"
+ ///
+ public static LocalisableString AudioOffset => new TranslatableString(getKey(@"audio_offset"), @"Audio offset");
+
+ ///
+ /// "Offset wizard"
+ ///
+ public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs
index b4381fc442..33a6eb5d58 100644
--- a/osu.Game/Localisation/CommonStrings.cs
+++ b/osu.Game/Localisation/CommonStrings.cs
@@ -14,11 +14,21 @@ namespace osu.Game.Localisation
///
public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel");
+ ///
+ /// "Clear"
+ ///
+ public static LocalisableString Clear => new TranslatableString(getKey(@"clear"), @"Clear");
+
///
/// "Enabled"
///
public static LocalisableString Enabled => new TranslatableString(getKey(@"enabled"), @"Enabled");
+ ///
+ /// "Default"
+ ///
+ public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default");
+
///
/// "Width"
///
diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs
new file mode 100644
index 0000000000..dd21739096
--- /dev/null
+++ b/osu.Game/Localisation/DebugSettingsStrings.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 osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class DebugSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings";
+
+ ///
+ /// "Debug"
+ ///
+ public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Show log overlay"
+ ///
+ public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay");
+
+ ///
+ /// "Bypass front-to-back render pass"
+ ///
+ public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass");
+
+ ///
+ /// "Import files"
+ ///
+ public static LocalisableString ImportFiles => new TranslatableString(getKey(@"import_files"), @"Import files");
+
+ ///
+ /// "Memory"
+ ///
+ public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory");
+
+ ///
+ /// "Clear all caches"
+ ///
+ public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs
new file mode 100644
index 0000000000..6d6381b429
--- /dev/null
+++ b/osu.Game/Localisation/GameplaySettingsStrings.cs
@@ -0,0 +1,94 @@
+// 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 GameplaySettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GameplaySettings";
+
+ ///
+ /// "Gameplay"
+ ///
+ public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Background dim"
+ ///
+ public static LocalisableString BackgroundDim => new TranslatableString(getKey(@"dim"), @"Background dim");
+
+ ///
+ /// "Background blur"
+ ///
+ public static LocalisableString BackgroundBlur => new TranslatableString(getKey(@"blur"), @"Background blur");
+
+ ///
+ /// "Lighten playfield during breaks"
+ ///
+ public static LocalisableString LightenDuringBreaks => new TranslatableString(getKey(@"lighten_during_breaks"), @"Lighten playfield during breaks");
+
+ ///
+ /// "HUD overlay visibility mode"
+ ///
+ public static LocalisableString HUDVisibilityMode => new TranslatableString(getKey(@"hud_visibility_mode"), @"HUD overlay visibility mode");
+
+ ///
+ /// "Show difficulty graph on progress bar"
+ ///
+ public static LocalisableString ShowDifficultyGraph => new TranslatableString(getKey(@"show_difficulty_graph"), @"Show difficulty graph on progress bar");
+
+ ///
+ /// "Show health display even when you can't fail"
+ ///
+ public static LocalisableString ShowHealthDisplayWhenCantFail => new TranslatableString(getKey(@"show_health_display_when_cant_fail"), @"Show health display even when you can't fail");
+
+ ///
+ /// "Fade playfield to red when health is low"
+ ///
+ public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low");
+
+ ///
+ /// "Always show key overlay"
+ ///
+ public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay");
+
+ ///
+ /// "Positional hitsounds"
+ ///
+ public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds");
+
+ ///
+ /// "Always play first combo break sound"
+ ///
+ public static LocalisableString AlwaysPlayFirstComboBreak => new TranslatableString(getKey(@"always_play_first_combo_break"), @"Always play first combo break sound");
+
+ ///
+ /// "Score display mode"
+ ///
+ public static LocalisableString ScoreDisplayMode => new TranslatableString(getKey(@"score_display_mode"), @"Score display mode");
+
+ ///
+ /// "Disable Windows key during gameplay"
+ ///
+ public static LocalisableString DisableWinKey => new TranslatableString(getKey(@"disable_win_key"), @"Disable Windows key during gameplay");
+
+ ///
+ /// "Mods"
+ ///
+ public static LocalisableString ModsHeader => new TranslatableString(getKey(@"mods_header"), @"Mods");
+
+ ///
+ /// "Increase visibility of first object when visual impairment mods are enabled"
+ ///
+ public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs
new file mode 100644
index 0000000000..a60e4891f4
--- /dev/null
+++ b/osu.Game/Localisation/GeneralSettingsStrings.cs
@@ -0,0 +1,59 @@
+// 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 GeneralSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GeneralSettings";
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralSectionHeader => new TranslatableString(getKey(@"general_section_header"), @"General");
+
+ ///
+ /// "Language"
+ ///
+ public static LocalisableString LanguageHeader => new TranslatableString(getKey(@"language_header"), @"Language");
+
+ ///
+ /// "Language"
+ ///
+ public static LocalisableString LanguageDropdown => new TranslatableString(getKey(@"language_dropdown"), @"Language");
+
+ ///
+ /// "Prefer metadata in original language"
+ ///
+ public static LocalisableString PreferOriginalMetadataLanguage => new TranslatableString(getKey(@"prefer_original"), @"Prefer metadata in original language");
+
+ ///
+ /// "Updates"
+ ///
+ public static LocalisableString UpdateHeader => new TranslatableString(getKey(@"update_header"), @"Updates");
+
+ ///
+ /// "Release stream"
+ ///
+ public static LocalisableString ReleaseStream => new TranslatableString(getKey(@"release_stream"), @"Release stream");
+
+ ///
+ /// "Check for updates"
+ ///
+ public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates");
+
+ ///
+ /// "Open osu! folder"
+ ///
+ public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open osu! folder");
+
+ ///
+ /// "Change folder location..."
+ ///
+ public static LocalisableString ChangeFolderLocation => new TranslatableString(getKey(@"change_folder_location"), @"Change folder location...");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs
new file mode 100644
index 0000000000..0e384f983f
--- /dev/null
+++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs
@@ -0,0 +1,119 @@
+// 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 GraphicsSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GraphicsSettings";
+
+ ///
+ /// "Graphics"
+ ///
+ public static LocalisableString GraphicsSectionHeader => new TranslatableString(getKey(@"graphics_section_header"), @"Graphics");
+
+ ///
+ /// "Renderer"
+ ///
+ public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
+
+ ///
+ /// "Frame limiter"
+ ///
+ public static LocalisableString FrameLimiter => new TranslatableString(getKey(@"frame_limiter"), @"Frame limiter");
+
+ ///
+ /// "Threading mode"
+ ///
+ public static LocalisableString ThreadingMode => new TranslatableString(getKey(@"threading_mode"), @"Threading mode");
+
+ ///
+ /// "Show FPS"
+ ///
+ public static LocalisableString ShowFPS => new TranslatableString(getKey(@"show_fps"), @"Show FPS");
+
+ ///
+ /// "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. "2x refresh rate" is recommended."
+ ///
+ public static LocalisableString UnlimitedFramesNote => new TranslatableString(getKey(@"unlimited_frames_note"), @"Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. ""2x refresh rate"" is recommended.");
+
+ ///
+ /// "Layout"
+ ///
+ public static LocalisableString LayoutHeader => new TranslatableString(getKey(@"layout_header"), @"Layout");
+
+ ///
+ /// "Screen mode"
+ ///
+ public static LocalisableString ScreenMode => new TranslatableString(getKey(@"screen_mode"), @"Screen mode");
+
+ ///
+ /// "Resolution"
+ ///
+ public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution");
+
+ ///
+ /// "UI scaling"
+ ///
+ public static LocalisableString UIScaling => new TranslatableString(getKey(@"ui_scaling"), @"UI scaling");
+
+ ///
+ /// "Screen scaling"
+ ///
+ public static LocalisableString ScreenScaling => new TranslatableString(getKey(@"screen_scaling"), @"Screen scaling");
+
+ ///
+ /// "Horizontal position"
+ ///
+ public static LocalisableString HorizontalPosition => new TranslatableString(getKey(@"horizontal_position"), @"Horizontal position");
+
+ ///
+ /// "Vertical position"
+ ///
+ public static LocalisableString VerticalPosition => new TranslatableString(getKey(@"vertical_position"), @"Vertical position");
+
+ ///
+ /// "Horizontal scale"
+ ///
+ public static LocalisableString HorizontalScale => new TranslatableString(getKey(@"horizontal_scale"), @"Horizontal scale");
+
+ ///
+ /// "Vertical scale"
+ ///
+ public static LocalisableString VerticalScale => new TranslatableString(getKey(@"vertical_scale"), @"Vertical scale");
+
+ ///
+ /// "Running without fullscreen mode will increase your input latency!"
+ ///
+ public static LocalisableString NotFullscreenNote => new TranslatableString(getKey(@"not_fullscreen_note"), @"Running without fullscreen mode will increase your input latency!");
+
+ ///
+ /// "Detail Settings"
+ ///
+ public static LocalisableString DetailSettingsHeader => new TranslatableString(getKey(@"detail_settings_header"), @"Detail Settings");
+
+ ///
+ /// "Storyboard / video"
+ ///
+ public static LocalisableString StoryboardVideo => new TranslatableString(getKey(@"storyboard_video"), @"Storyboard / video");
+
+ ///
+ /// "Hit lighting"
+ ///
+ public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
+
+ ///
+ /// "Screenshot format"
+ ///
+ public static LocalisableString ScreenshotFormat => new TranslatableString(getKey(@"screenshot_format"), @"Screenshot format");
+
+ ///
+ /// "Show menu cursor in screenshots"
+ ///
+ public static LocalisableString ShowCursorInScreenshots => new TranslatableString(getKey(@"show_cursor_in_screenshots"), @"Show menu cursor in screenshots");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/InputSettingsStrings.cs b/osu.Game/Localisation/InputSettingsStrings.cs
new file mode 100644
index 0000000000..e46b4cecf3
--- /dev/null
+++ b/osu.Game/Localisation/InputSettingsStrings.cs
@@ -0,0 +1,59 @@
+// 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 InputSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.InputSettings";
+
+ ///
+ /// "Input"
+ ///
+ public static LocalisableString InputSectionHeader => new TranslatableString(getKey(@"input_section_header"), @"Input");
+
+ ///
+ /// "Global"
+ ///
+ public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
+
+ ///
+ /// "Song Select"
+ ///
+ public static LocalisableString SongSelectSection => new TranslatableString(getKey(@"song_select_section"), @"Song Select");
+
+ ///
+ /// "In Game"
+ ///
+ public static LocalisableString InGameSection => new TranslatableString(getKey(@"in_game_section"), @"In Game");
+
+ ///
+ /// "Audio"
+ ///
+ public static LocalisableString AudioSection => new TranslatableString(getKey(@"audio_section"), @"Audio");
+
+ ///
+ /// "Editor"
+ ///
+ public static LocalisableString EditorSection => new TranslatableString(getKey(@"editor_section"), @"Editor");
+
+ ///
+ /// "Reset all bindings in section"
+ ///
+ public static LocalisableString ResetSectionButton => new TranslatableString(getKey(@"reset_section_button"), @"Reset all bindings in section");
+
+ ///
+ /// "key configuration"
+ ///
+ public static LocalisableString KeyBindingPanelHeader => new TranslatableString(getKey(@"key_binding_panel_header"), @"key configuration");
+
+ ///
+ /// "Customise your keys!"
+ ///
+ public static LocalisableString KeyBindingPanelDescription => new TranslatableString(getKey(@"key_binding_panel_description"), @"Customise your keys!");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
new file mode 100644
index 0000000000..a0e1a9ddab
--- /dev/null
+++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
@@ -0,0 +1,74 @@
+// 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 MaintenanceSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.MaintenanceSettings";
+
+ ///
+ /// "Maintenance"
+ ///
+ public static LocalisableString MaintenanceSectionHeader => new TranslatableString(getKey(@"maintenance_section_header"), @"Maintenance");
+
+ ///
+ /// "Select directory"
+ ///
+ public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory");
+
+ ///
+ /// "Import beatmaps from stable"
+ ///
+ public static LocalisableString ImportBeatmapsFromStable => new TranslatableString(getKey(@"import_beatmaps_from_stable"), @"Import beatmaps from stable");
+
+ ///
+ /// "Delete ALL beatmaps"
+ ///
+ public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
+
+ ///
+ /// "Import scores from stable"
+ ///
+ public static LocalisableString ImportScoresFromStable => new TranslatableString(getKey(@"import_scores_from_stable"), @"Import scores from stable");
+
+ ///
+ /// "Delete ALL scores"
+ ///
+ public static LocalisableString DeleteAllScores => new TranslatableString(getKey(@"delete_all_scores"), @"Delete ALL scores");
+
+ ///
+ /// "Import skins from stable"
+ ///
+ public static LocalisableString ImportSkinsFromStable => new TranslatableString(getKey(@"import_skins_from_stable"), @"Import skins from stable");
+
+ ///
+ /// "Delete ALL skins"
+ ///
+ public static LocalisableString DeleteAllSkins => new TranslatableString(getKey(@"delete_all_skins"), @"Delete ALL skins");
+
+ ///
+ /// "Import collections from stable"
+ ///
+ public static LocalisableString ImportCollectionsFromStable => new TranslatableString(getKey(@"import_collections_from_stable"), @"Import collections from stable");
+
+ ///
+ /// "Delete ALL collections"
+ ///
+ public static LocalisableString DeleteAllCollections => new TranslatableString(getKey(@"delete_all_collections"), @"Delete ALL collections");
+
+ ///
+ /// "Restore all hidden difficulties"
+ ///
+ public static LocalisableString RestoreAllHiddenDifficulties => new TranslatableString(getKey(@"restore_all_hidden_difficulties"), @"Restore all hidden difficulties");
+
+ ///
+ /// "Restore all recently deleted beatmaps"
+ ///
+ public static LocalisableString RestoreAllRecentlyDeletedBeatmaps => new TranslatableString(getKey(@"restore_all_recently_deleted_beatmaps"), @"Restore all recently deleted beatmaps");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/OnlineSettingsStrings.cs b/osu.Game/Localisation/OnlineSettingsStrings.cs
new file mode 100644
index 0000000000..6862f4ac2c
--- /dev/null
+++ b/osu.Game/Localisation/OnlineSettingsStrings.cs
@@ -0,0 +1,69 @@
+// 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 OnlineSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.OnlineSettings";
+
+ ///
+ /// "Online"
+ ///
+ public static LocalisableString OnlineSectionHeader => new TranslatableString(getKey(@"online_section_header"), @"Online");
+
+ ///
+ /// "Alerts and Privacy"
+ ///
+ public static LocalisableString AlertsAndPrivacyHeader => new TranslatableString(getKey(@"alerts_and_privacy_header"), @"Alerts and Privacy");
+
+ ///
+ /// "Show a notification when someone mentions your name"
+ ///
+ public static LocalisableString NotifyOnMentioned => new TranslatableString(getKey(@"notify_on_mentioned"), @"Show a notification when someone mentions your name");
+
+ ///
+ /// "Show a notification when you receive a private message"
+ ///
+ public static LocalisableString NotifyOnPrivateMessage => new TranslatableString(getKey(@"notify_on_private_message"), @"Show a notification when you receive a private message");
+
+ ///
+ /// "Integrations"
+ ///
+ public static LocalisableString IntegrationsHeader => new TranslatableString(getKey(@"integrations_header"), @"Integrations");
+
+ ///
+ /// "Discord Rich Presence"
+ ///
+ public static LocalisableString DiscordRichPresence => new TranslatableString(getKey(@"discord_rich_presence"), @"Discord Rich Presence");
+
+ ///
+ /// "Web"
+ ///
+ public static LocalisableString WebHeader => new TranslatableString(getKey(@"web_header"), @"Web");
+
+ ///
+ /// "Warn about opening external links"
+ ///
+ public static LocalisableString ExternalLinkWarning => new TranslatableString(getKey(@"external_link_warning"), @"Warn about opening external links");
+
+ ///
+ /// "Prefer downloads without video"
+ ///
+ public static LocalisableString PreferNoVideo => new TranslatableString(getKey(@"prefer_no_video"), @"Prefer downloads without video");
+
+ ///
+ /// "Automatically download beatmaps when spectating"
+ ///
+ public static LocalisableString AutomaticallyDownloadWhenSpectating => new TranslatableString(getKey(@"automatically_download_when_spectating"), @"Automatically download beatmaps when spectating");
+
+ ///
+ /// "Show explicit content in search results"
+ ///
+ public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs
new file mode 100644
index 0000000000..f22b4d6bf5
--- /dev/null
+++ b/osu.Game/Localisation/SkinSettingsStrings.cs
@@ -0,0 +1,54 @@
+// 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 SkinSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.SkinSettings";
+
+ ///
+ /// "Skin"
+ ///
+ public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
+
+ ///
+ /// "Skin layout editor"
+ ///
+ public static LocalisableString SkinLayoutEditor => new TranslatableString(getKey(@"skin_layout_editor"), @"Skin layout editor");
+
+ ///
+ /// "Gameplay cursor size"
+ ///
+ public static LocalisableString GameplayCursorSize => new TranslatableString(getKey(@"gameplay_cursor_size"), @"Gameplay cursor size");
+
+ ///
+ /// "Adjust gameplay cursor size based on current beatmap"
+ ///
+ public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap");
+
+ ///
+ /// "Beatmap skins"
+ ///
+ public static LocalisableString BeatmapSkins => new TranslatableString(getKey(@"beatmap_skins"), @"Beatmap skins");
+
+ ///
+ /// "Beatmap colours"
+ ///
+ public static LocalisableString BeatmapColours => new TranslatableString(getKey(@"beatmap_colours"), @"Beatmap colours");
+
+ ///
+ /// "Beatmap hitsounds"
+ ///
+ public static LocalisableString BeatmapHitsounds => new TranslatableString(getKey(@"beatmap_hitsounds"), @"Beatmap hitsounds");
+
+ ///
+ /// "Export selected skin"
+ ///
+ public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs
new file mode 100644
index 0000000000..4be403edb4
--- /dev/null
+++ b/osu.Game/Localisation/UserInterfaceStrings.cs
@@ -0,0 +1,114 @@
+// 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 UserInterfaceStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.UserInterface";
+
+ ///
+ /// "User Interface"
+ ///
+ public static LocalisableString UserInterfaceSectionHeader => new TranslatableString(getKey(@"user_interface_section_header"), @"User Interface");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Rotate cursor when dragging"
+ ///
+ public static LocalisableString CursorRotation => new TranslatableString(getKey(@"cursor_rotation"), @"Rotate cursor when dragging");
+
+ ///
+ /// "Menu cursor size"
+ ///
+ public static LocalisableString MenuCursorSize => new TranslatableString(getKey(@"menu_cursor_size"), @"Menu cursor size");
+
+ ///
+ /// "Parallax"
+ ///
+ public static LocalisableString Parallax => new TranslatableString(getKey(@"parallax"), @"Parallax");
+
+ ///
+ /// "Hold-to-confirm activation time"
+ ///
+ public static LocalisableString HoldToConfirmActivationTime => new TranslatableString(getKey(@"hold_to_confirm_activation_time"), @"Hold-to-confirm activation time");
+
+ ///
+ /// "Main Menu"
+ ///
+ public static LocalisableString MainMenuHeader => new TranslatableString(getKey(@"main_menu_header"), @"Main Menu");
+
+ ///
+ /// "Interface voices"
+ ///
+ public static LocalisableString InterfaceVoices => new TranslatableString(getKey(@"interface_voices"), @"Interface voices");
+
+ ///
+ /// "osu! music theme"
+ ///
+ public static LocalisableString OsuMusicTheme => new TranslatableString(getKey(@"osu_music_theme"), @"osu! music theme");
+
+ ///
+ /// "Intro sequence"
+ ///
+ public static LocalisableString IntroSequence => new TranslatableString(getKey(@"intro_sequence"), @"Intro sequence");
+
+ ///
+ /// "Background source"
+ ///
+ public static LocalisableString BackgroundSource => new TranslatableString(getKey(@"background_source"), @"Background source");
+
+ ///
+ /// "Seasonal backgrounds"
+ ///
+ public static LocalisableString SeasonalBackgrounds => new TranslatableString(getKey(@"seasonal_backgrounds"), @"Seasonal backgrounds");
+
+ ///
+ /// "Changes to this setting will only apply with an active osu!supporter tag."
+ ///
+ public static LocalisableString NotSupporterNote => new TranslatableString(getKey(@"not_supporter_note"), @"Changes to this setting will only apply with an active osu!supporter tag.");
+
+ ///
+ /// "Song Select"
+ ///
+ public static LocalisableString SongSelectHeader => new TranslatableString(getKey(@"song_select_header"), @"Song Select");
+
+ ///
+ /// "Right mouse drag to absolute scroll"
+ ///
+ public static LocalisableString RightMouseScroll => new TranslatableString(getKey(@"right_mouse_scroll"), @"Right mouse drag to absolute scroll");
+
+ ///
+ /// "Show converted beatmaps"
+ ///
+ public static LocalisableString ShowConvertedBeatmaps => new TranslatableString(getKey(@"show_converted_beatmaps"), @"Show converted beatmaps");
+
+ ///
+ /// "Display beatmaps from"
+ ///
+ public static LocalisableString StarsMinimum => new TranslatableString(getKey(@"stars_minimum"), @"Display beatmaps from");
+
+ ///
+ /// "up to"
+ ///
+ public static LocalisableString StarsMaximum => new TranslatableString(getKey(@"stars_maximum"), @"up to");
+
+ ///
+ /// "Random selection algorithm"
+ ///
+ public static LocalisableString RandomSelectionAlgorithm => new TranslatableString(getKey(@"random_selection_algorithm"), @"Random selection algorithm");
+
+ ///
+ /// "no limit"
+ ///
+ public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index f7a3f4602f..af14cdc7b3 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -305,9 +305,11 @@ namespace osu.Game.Online.API
{
req.Perform(this);
+ if (req.CompletionState != APIRequestCompletionState.Completed)
+ return false;
+
// we could still be in initialisation, at which point we don't want to say we're Online yet.
if (IsLoggedIn) state.Value = APIState.Online;
-
failureCount = 0;
return true;
}
@@ -381,7 +383,7 @@ namespace osu.Game.Online.API
}
}
- public bool IsLoggedIn => localUser.Value.Id > 1;
+ public bool IsLoggedIn => localUser.Value.Id > 1; // TODO: should this also be true if attempting to connect?
public void Queue(APIRequest request)
{
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index e117293ce6..cf17ed4b5d 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Online.API
/// The state of this request, from an outside perspective.
/// This is used to ensure correct notification events are fired.
///
- private APIRequestCompletionState completionState;
+ public APIRequestCompletionState CompletionState { get; private set; }
public void Perform(IAPIProvider api)
{
@@ -127,10 +127,10 @@ namespace osu.Game.Online.API
{
lock (completionStateLock)
{
- if (completionState != APIRequestCompletionState.Waiting)
+ if (CompletionState != APIRequestCompletionState.Waiting)
return;
- completionState = APIRequestCompletionState.Completed;
+ CompletionState = APIRequestCompletionState.Completed;
}
if (API == null)
@@ -143,10 +143,10 @@ namespace osu.Game.Online.API
{
lock (completionStateLock)
{
- if (completionState != APIRequestCompletionState.Waiting)
+ if (CompletionState != APIRequestCompletionState.Waiting)
return;
- completionState = APIRequestCompletionState.Failed;
+ CompletionState = APIRequestCompletionState.Failed;
}
if (API == null)
@@ -161,7 +161,7 @@ namespace osu.Game.Online.API
{
lock (completionStateLock)
{
- if (completionState != APIRequestCompletionState.Waiting)
+ if (CompletionState != APIRequestCompletionState.Waiting)
return;
WebRequest?.Abort();
@@ -200,7 +200,7 @@ namespace osu.Game.Online.API
get
{
lock (completionStateLock)
- return completionState == APIRequestCompletionState.Failed;
+ return CompletionState == APIRequestCompletionState.Failed;
}
}
diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs
index ae9199c428..0e4ea694aa 100644
--- a/osu.Game/Online/Chat/MessageFormatter.cs
+++ b/osu.Game/Online/Chat/MessageFormatter.cs
@@ -43,7 +43,8 @@ namespace osu.Game.Online.Chat
RegexOptions.IgnoreCase);
// 00:00:000 (1,2,3) - test
- private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*");
+ // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10
+ private static readonly Regex time_regex = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)");
// #osu
private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)");
diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
index 8b0caddbc6..6ed2055e65 100644
--- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs
+++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Chat;
@@ -22,7 +23,7 @@ namespace osu.Game.Online.Chat
{
public readonly Bindable Channel = new Bindable();
- private readonly FocusedTextBox textbox;
+ protected readonly ChatTextBox Textbox;
protected ChannelManager ChannelManager;
@@ -30,6 +31,8 @@ namespace osu.Game.Online.Chat
private readonly bool postingTextbox;
+ protected readonly Box Background;
+
private const float textbox_height = 30;
///
@@ -44,7 +47,7 @@ namespace osu.Game.Online.Chat
InternalChildren = new Drawable[]
{
- new Box
+ Background = new Box
{
Colour = Color4.Black,
Alpha = 0.8f,
@@ -54,7 +57,7 @@ namespace osu.Game.Online.Chat
if (postingTextbox)
{
- AddInternal(textbox = new FocusedTextBox
+ AddInternal(Textbox = new ChatTextBox
{
RelativeSizeAxes = Axes.X,
Height = textbox_height,
@@ -65,7 +68,7 @@ namespace osu.Game.Online.Chat
Origin = Anchor.BottomLeft,
});
- textbox.OnCommit += postMessage;
+ Textbox.OnCommit += postMessage;
}
Channel.BindValueChanged(channelChanged);
@@ -82,7 +85,7 @@ namespace osu.Game.Online.Chat
private void postMessage(TextBox sender, bool newtext)
{
- var text = textbox.Text.Trim();
+ var text = Textbox.Text.Trim();
if (string.IsNullOrWhiteSpace(text))
return;
@@ -92,7 +95,7 @@ namespace osu.Game.Online.Chat
else
ChannelManager?.PostMessage(text, target: Channel.Value);
- textbox.Text = string.Empty;
+ Textbox.Text = string.Empty;
}
protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message);
@@ -110,6 +113,25 @@ namespace osu.Game.Online.Chat
AddInternal(drawableChannel);
}
+ public class ChatTextBox : FocusedTextBox
+ {
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ BackgroundUnfocused = new Color4(10, 10, 10, 10);
+ BackgroundFocused = new Color4(10, 10, 10, 255);
+ }
+
+ protected override void OnFocusLost(FocusLostEvent e)
+ {
+ base.OnFocusLost(e);
+ FocusLost?.Invoke();
+ }
+
+ public Action FocusLost;
+ }
+
public class StandAloneDrawableChannel : DrawableChannel
{
public Func CreateChatLineAction;
diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs
index d2dba8a402..e9d6960c71 100644
--- a/osu.Game/Online/HubClientConnector.cs
+++ b/osu.Game/Online/HubClientConnector.cs
@@ -148,7 +148,12 @@ namespace osu.Game.Online
});
if (RuntimeInfo.SupportsJIT && preferMessagePack)
- builder.AddMessagePackProtocol();
+ {
+ builder.AddMessagePackProtocol(options =>
+ {
+ options.SerializerOptions = SignalRUnionWorkaroundResolver.OPTIONS;
+ });
+ }
else
{
// eventually we will precompile resolvers for messagepack, but this isn't working currently
diff --git a/osu.Game/Online/Multiplayer/MatchRoomState.cs b/osu.Game/Online/Multiplayer/MatchRoomState.cs
index 5b662af100..edd34fb5a3 100644
--- a/osu.Game/Online/Multiplayer/MatchRoomState.cs
+++ b/osu.Game/Online/Multiplayer/MatchRoomState.cs
@@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
///
[Serializable]
[MessagePackObject]
- [Union(0, typeof(TeamVersusRoomState))]
- // TODO: this will need to be abstract or interface when/if we get messagepack working. for now it isn't as it breaks json serialisation.
- public class MatchRoomState
+ [Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
+ // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
+ public abstract class MatchRoomState
{
}
}
diff --git a/osu.Game/Online/Multiplayer/MatchUserRequest.cs b/osu.Game/Online/Multiplayer/MatchUserRequest.cs
index 15c3ad0776..8c6809e7f3 100644
--- a/osu.Game/Online/Multiplayer/MatchUserRequest.cs
+++ b/osu.Game/Online/Multiplayer/MatchUserRequest.cs
@@ -3,6 +3,7 @@
using System;
using MessagePack;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
namespace osu.Game.Online.Multiplayer
{
@@ -11,6 +12,7 @@ namespace osu.Game.Online.Multiplayer
///
[Serializable]
[MessagePackObject]
+ [Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
public abstract class MatchUserRequest
{
}
diff --git a/osu.Game/Online/Multiplayer/MatchUserState.cs b/osu.Game/Online/Multiplayer/MatchUserState.cs
index f457191bb5..69245deba0 100644
--- a/osu.Game/Online/Multiplayer/MatchUserState.cs
+++ b/osu.Game/Online/Multiplayer/MatchUserState.cs
@@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
///
[Serializable]
[MessagePackObject]
- [Union(0, typeof(TeamVersusUserState))]
- // TODO: this will need to be abstract or interface when/if we get messagepack working. for now it isn't as it breaks json serialisation.
- public class MatchUserState
+ [Union(0, typeof(TeamVersusUserState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
+ // TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
+ public abstract class MatchUserState
{
}
}
diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
index c38a648a6a..965674c2f2 100644
--- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Online.Multiplayer
{
// Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization.
// More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code.
- connector = api.GetHubConnector(nameof(OnlineMultiplayerClient), endpoint, false);
+ connector = api.GetHubConnector(nameof(OnlineMultiplayerClient), endpoint);
if (connector != null)
{
diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs
index 806c0047e7..243be8da44 100644
--- a/osu.Game/Online/PollingComponent.cs
+++ b/osu.Game/Online/PollingComponent.cs
@@ -47,39 +47,13 @@ namespace osu.Game.Online
pollIfNecessary();
}
- private bool pollIfNecessary()
+ ///
+ /// Immediately performs a .
+ ///
+ public void PollImmediately()
{
- // we must be loaded so we have access to clock.
- if (!IsLoaded) return false;
-
- // there's already a poll process running.
- if (pollingActive) return false;
-
- // don't try polling if the time between polls hasn't been set.
- if (TimeBetweenPolls.Value == 0) return false;
-
- if (!lastTimePolled.HasValue)
- {
- doPoll();
- return true;
- }
-
- if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
- {
- doPoll();
- return true;
- }
-
- // not enough time has passed since the last poll. we do want to schedule a poll to happen, though.
+ lastTimePolled = Time.Current - TimeBetweenPolls.Value;
scheduleNextPoll();
- return false;
- }
-
- private void doPoll()
- {
- scheduledPoll = null;
- pollingActive = true;
- Poll().ContinueWith(_ => pollComplete());
}
///
@@ -90,13 +64,11 @@ namespace osu.Game.Online
return Task.CompletedTask;
}
- ///
- /// Immediately performs a .
- ///
- public void PollImmediately()
+ private void doPoll()
{
- lastTimePolled = Time.Current - TimeBetweenPolls.Value;
- scheduleNextPoll();
+ scheduledPoll = null;
+ pollingActive = true;
+ Poll().ContinueWith(_ => pollComplete());
}
///
@@ -111,6 +83,33 @@ namespace osu.Game.Online
pollIfNecessary();
}
+ private void pollIfNecessary()
+ {
+ // we must be loaded so we have access to clock.
+ if (!IsLoaded) return;
+
+ // there's already a poll process running.
+ if (pollingActive) return;
+
+ // don't try polling if the time between polls hasn't been set.
+ if (TimeBetweenPolls.Value == 0) return;
+
+ if (!lastTimePolled.HasValue)
+ {
+ doPoll();
+ return;
+ }
+
+ if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
+ {
+ doPoll();
+ return;
+ }
+
+ // not enough time has passed since the last poll. we do want to schedule a poll to happen, though.
+ scheduleNextPoll();
+ }
+
private void scheduleNextPoll()
{
scheduledPoll?.Cancel();
diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs
index 4bd5b1a788..d964060f10 100644
--- a/osu.Game/Online/Rooms/Room.cs
+++ b/osu.Game/Online/Rooms/Room.cs
@@ -134,7 +134,7 @@ namespace osu.Game.Online.Rooms
/// The position of this in the list. This is not read from or written to the API.
///
[JsonIgnore]
- public readonly Bindable Position = new Bindable(-1);
+ public readonly Bindable Position = new Bindable(-1); // Todo: This does not need to exist.
public Room()
{
diff --git a/osu.Game/Online/Rooms/RoomCategory.cs b/osu.Game/Online/Rooms/RoomCategory.cs
index bb9f1298d3..a1dcfa5fd9 100644
--- a/osu.Game/Online/Rooms/RoomCategory.cs
+++ b/osu.Game/Online/Rooms/RoomCategory.cs
@@ -8,6 +8,5 @@ namespace osu.Game.Online.Rooms
// used for osu-web deserialization so names shouldn't be changed.
Normal,
Spotlight,
- Realtime,
}
}
diff --git a/osu.Game/Online/SignalRUnionWorkaroundResolver.cs b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs
new file mode 100644
index 0000000000..e44da044cc
--- /dev/null
+++ b/osu.Game/Online/SignalRUnionWorkaroundResolver.cs
@@ -0,0 +1,61 @@
+// 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.Collections.Generic;
+using MessagePack;
+using MessagePack.Formatters;
+using MessagePack.Resolvers;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
+
+namespace osu.Game.Online
+{
+ ///
+ /// Handles SignalR being unable to comprehend [Union] types correctly by redirecting to a known base (union) type.
+ /// See https://github.com/dotnet/aspnetcore/issues/7298.
+ ///
+ public class SignalRUnionWorkaroundResolver : IFormatterResolver
+ {
+ public static readonly MessagePackSerializerOptions OPTIONS =
+ MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver());
+
+ private static readonly Dictionary formatter_map = new Dictionary
+ {
+ { typeof(TeamVersusUserState), new TypeRedirectingFormatter() },
+ { typeof(TeamVersusRoomState), new TypeRedirectingFormatter() },
+ { typeof(ChangeTeamRequest), new TypeRedirectingFormatter() },
+
+ // These should not be required. The fallback should work. But something is weird with the way caching is done.
+ // For future adventurers, I would not advise looking into this further. It's likely not worth the effort.
+ { typeof(MatchUserState), new TypeRedirectingFormatter() },
+ { typeof(MatchRoomState), new TypeRedirectingFormatter() },
+ { typeof(MatchUserRequest), new TypeRedirectingFormatter() },
+ { typeof(MatchServerEvent), new TypeRedirectingFormatter() },
+ };
+
+ public IMessagePackFormatter GetFormatter()
+ {
+ if (formatter_map.TryGetValue(typeof(T), out var formatter))
+ return (IMessagePackFormatter)formatter;
+
+ return StandardResolver.Instance.GetFormatter();
+ }
+
+ public class TypeRedirectingFormatter : IMessagePackFormatter
+ {
+ private readonly IMessagePackFormatter baseFormatter;
+
+ public TypeRedirectingFormatter()
+ {
+ baseFormatter = StandardResolver.Instance.GetFormatter();
+ }
+
+ public void Serialize(ref MessagePackWriter writer, TActual value, MessagePackSerializerOptions options) =>
+ baseFormatter.Serialize(ref writer, (TBase)(object)value, StandardResolver.Options);
+
+ public TActual Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) =>
+ (TActual)(object)baseFormatter.Deserialize(ref reader, StandardResolver.Options);
+ }
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index fb682e0909..4d952c39c6 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -62,7 +62,7 @@ namespace osu.Game
/// The full osu! experience. Builds on top of to add menus and binding logic
/// for initial components that are generally retrieved via DI.
///
- public class OsuGame : OsuGameBase, IKeyBindingHandler
+ public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo
{
///
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
@@ -1018,10 +1018,13 @@ namespace osu.Game
var horizontalOffset = 0f;
+ // Content.ToLocalSpace() is used instead of this.ToLocalSpace() to correctly calculate the offset with scaling modes active.
+ // Content is a child of a scaling container with ScalingMode.Everything set, while the game itself is never scaled.
+ // this avoids a visible jump in the positioning of the screen offset container.
if (Settings.IsLoaded && Settings.IsPresent)
- horizontalOffset += ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO;
+ horizontalOffset += Content.ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO;
if (Notifications.IsLoaded && Notifications.IsPresent)
- horizontalOffset += (ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
+ horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
ScreenOffsetContainer.X = horizontalOffset;
@@ -1085,5 +1088,7 @@ namespace osu.Game
if (newScreen == null)
Exit();
}
+
+ IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
}
}
diff --git a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
index 4bbc3569fe..3aa9aa5ca5 100644
--- a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
@@ -139,19 +139,24 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
LoadComponentAsync(Preview = previewTrackManager.Get(beatmapSet), preview =>
{
- // beatmapset may have changed.
- if (Preview != preview)
- return;
+ // Make sure that we schedule to after the next audio frame to fix crashes in single-threaded execution.
+ // See: https://github.com/ppy/osu-framework/issues/4692
+ Schedule(() =>
+ {
+ // beatmapset may have changed.
+ if (Preview != preview)
+ return;
- AddInternal(preview);
- loading = false;
- // make sure that the update of value of Playing (and the ensuing value change callbacks)
- // are marshaled back to the update thread.
- preview.Stopped += () => Schedule(() => playing.Value = false);
+ AddInternal(preview);
+ loading = false;
+ // make sure that the update of value of Playing (and the ensuing value change callbacks)
+ // are marshaled back to the update thread.
+ preview.Stopped += () => Schedule(() => playing.Value = false);
- // user may have changed their mind.
- if (playing.Value)
- attemptStart();
+ // user may have changed their mind.
+ if (playing.Value)
+ attemptStart();
+ });
});
}
else
diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
index f617b4fc82..508c8399b6 100644
--- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
@@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Changelog
public class ChangelogSupporterPromo : CompositeDrawable
{
private const float image_container_width = 164;
+ private const float heart_size = 75;
private readonly FillFlowContainer textContainer;
private readonly Container imageContainer;
@@ -134,18 +135,30 @@ namespace osu.Game.Overlays.Changelog
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
+ Margin = new MarginPadding { Bottom = 28 },
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Texture = textures.Get(@"Online/supporter-pippi"),
},
- new Sprite
+ new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- Width = 75,
- Height = 75,
+ Size = new Vector2(heart_size),
Margin = new MarginPadding { Top = 70 },
- Texture = textures.Get(@"Online/supporter-heart"),
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = colour.Pink,
+ Radius = 10,
+ Roundness = heart_size / 2,
+ },
+ Child = new Sprite
+ {
+ Size = new Vector2(heart_size),
+ Texture = textures.Get(@"Online/supporter-heart"),
+ },
},
};
}
diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs
index 01cfe9a55b..6f4f568eb2 100644
--- a/osu.Game/Overlays/Chat/ChatLine.cs
+++ b/osu.Game/Overlays/Chat/ChatLine.cs
@@ -110,15 +110,15 @@ namespace osu.Game.Overlays.Chat
EdgeEffect = new EdgeEffectParameters
{
Roundness = 1,
- Offset = new Vector2(0, 3),
- Radius = 3,
+ Radius = 1,
Colour = Color4.Black.Opacity(0.3f),
+ Offset = new Vector2(0, 1),
Type = EdgeEffectType.Shadow,
},
Child = new Container
{
AutoSizeAxes = Axes.Both,
- Y = 3,
+ Y = 0,
Masking = true,
CornerRadius = 4,
Children = new Drawable[]
diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs
index fd3ee16fe6..87a294cc10 100644
--- a/osu.Game/Overlays/RestoreDefaultValueButton.cs
+++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
@@ -38,12 +39,12 @@ namespace osu.Game.Overlays
current.ValueChanged += _ => UpdateState();
current.DefaultChanged += _ => UpdateState();
current.DisabledChanged += _ => UpdateState();
- UpdateState();
+
+ if (IsLoaded)
+ UpdateState();
}
}
- private Color4 buttonColour;
-
private bool hovering;
public RestoreDefaultValueButton()
@@ -58,12 +59,11 @@ namespace osu.Game.Overlays
private void load(OsuColour colour)
{
BackgroundColour = colour.Yellow;
- buttonColour = colour.Yellow;
Content.Width = 0.33f;
Content.CornerRadius = 3;
Content.EdgeEffect = new EdgeEffectParameters
{
- Colour = buttonColour.Opacity(0.1f),
+ Colour = BackgroundColour.Opacity(0.1f),
Type = EdgeEffectType.Glow,
Radius = 2,
};
@@ -81,7 +81,10 @@ namespace osu.Game.Overlays
protected override void LoadComplete()
{
base.LoadComplete();
- UpdateState();
+
+ // avoid unnecessary transforms on first display.
+ Alpha = currentAlpha;
+ Background.Colour = currentColour;
}
public LocalisableString TooltipText => "revert to default";
@@ -101,14 +104,16 @@ namespace osu.Game.Overlays
public void UpdateState() => Scheduler.AddOnce(updateState);
+ private float currentAlpha => current.IsDefault ? 0f : hovering && !current.Disabled ? 1f : 0.65f;
+ private ColourInfo currentColour => current.Disabled ? Color4.Gray : BackgroundColour;
+
private void updateState()
{
if (current == null)
return;
- this.FadeTo(current.IsDefault ? 0f :
- hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint);
- this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
+ this.FadeTo(currentAlpha, 200, Easing.OutQuint);
+ Background.FadeColour(currentColour, 200, Easing.OutQuint);
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
index d64f176468..d697b45424 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
@@ -8,29 +8,39 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class AudioDevicesSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Devices";
+ protected override LocalisableString Header => AudioSettingsStrings.AudioDevicesHeader;
[Resolved]
private AudioManager audio { get; set; }
private SettingsDropdown dropdown;
- protected override void Dispose(bool isDisposing)
+ [BackgroundDependencyLoader]
+ private void load()
{
- base.Dispose(isDisposing);
-
- if (audio != null)
+ Children = new Drawable[]
{
- audio.OnNewDevice -= onDeviceChanged;
- audio.OnLostDevice -= onDeviceChanged;
- }
+ dropdown = new AudioDeviceSettingsDropdown
+ {
+ Keywords = new[] { "speaker", "headphone", "output" }
+ }
+ };
+
+ updateItems();
+
+ audio.OnNewDevice += onDeviceChanged;
+ audio.OnLostDevice += onDeviceChanged;
+ dropdown.Current = audio.AudioDevice;
}
+ private void onDeviceChanged(string name) => updateItems();
+
private void updateItems()
{
var deviceItems = new List { string.Empty };
@@ -49,26 +59,15 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
dropdown.Items = deviceItems.Distinct().ToList();
}
- private void onDeviceChanged(string name) => updateItems();
-
- protected override void LoadComplete()
+ protected override void Dispose(bool isDisposing)
{
- base.LoadComplete();
+ base.Dispose(isDisposing);
- Children = new Drawable[]
+ if (audio != null)
{
- dropdown = new AudioDeviceSettingsDropdown
- {
- Keywords = new[] { "speaker", "headphone", "output" }
- }
- };
-
- updateItems();
-
- dropdown.Current = audio.AudioDevice;
-
- audio.OnNewDevice += onDeviceChanged;
- audio.OnLostDevice += onDeviceChanged;
+ audio.OnNewDevice -= onDeviceChanged;
+ audio.OnLostDevice -= onDeviceChanged;
+ }
}
private class AudioDeviceSettingsDropdown : SettingsDropdown
@@ -78,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
private class AudioDeviceDropdownControl : DropdownControl
{
protected override LocalisableString GenerateItemText(string item)
- => string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item);
+ => string.IsNullOrEmpty(item) ? CommonStrings.Default : base.GenerateItemText(item);
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
index 7f2e377c83..9345d3fcc7 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
@@ -1,17 +1,22 @@
// 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.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class OffsetSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Offset Adjustment";
+ protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader;
+
+ public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "universal", "uo", "timing" });
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -20,13 +25,13 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
new SettingsSlider
{
- LabelText = "Audio offset",
+ LabelText = AudioSettingsStrings.AudioOffset,
Current = config.GetBindable(OsuSetting.AudioOffset),
KeyboardStep = 1f
},
new SettingsButton
{
- Text = "Offset wizard"
+ Text = AudioSettingsStrings.OffsetWizard
}
};
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
index 8f88b03471..00c1cb8f43 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class VolumeSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Volume";
+ protected override LocalisableString Header => AudioSettingsStrings.VolumeHeader;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config)
@@ -20,28 +21,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
new SettingsSlider
{
- LabelText = "Master",
+ LabelText = AudioSettingsStrings.MasterVolume,
Current = audio.Volume,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Master (window inactive)",
+ LabelText = AudioSettingsStrings.MasterVolumeInactive,
Current = config.GetBindable(OsuSetting.VolumeInactive),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Effect",
+ LabelText = AudioSettingsStrings.EffectVolume,
Current = audio.VolumeSample,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Music",
+ LabelText = AudioSettingsStrings.MusicVolume,
Current = audio.VolumeTrack,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs
index 7072d8e63d..694da0529a 100644
--- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs
@@ -3,15 +3,17 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Localisation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Audio;
namespace osu.Game.Overlays.Settings.Sections
{
public class AudioSection : SettingsSection
{
- public override string Header => "Audio";
+ public override LocalisableString Header => AudioSettingsStrings.AudioSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
index 2b868cab85..25e20911b8 100644
--- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
@@ -6,13 +6,14 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Screens;
+using osu.Game.Localisation;
using osu.Game.Screens.Import;
namespace osu.Game.Overlays.Settings.Sections.Debug
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => DebugSettingsStrings.GeneralHeader;
[BackgroundDependencyLoader(true)]
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, OsuGame game)
@@ -21,18 +22,18 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{
new SettingsCheckbox
{
- LabelText = "Show log overlay",
+ LabelText = DebugSettingsStrings.ShowLogOverlay,
Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay)
},
new SettingsCheckbox
{
- LabelText = "Bypass front-to-back render pass",
+ LabelText = DebugSettingsStrings.BypassFrontToBackPass,
Current = config.GetBindable(DebugSetting.BypassFrontToBackPass)
}
};
Add(new SettingsButton
{
- Text = "Import files",
+ Text = DebugSettingsStrings.ImportFiles,
Action = () => game?.PerformFromScreen(menu => menu.Push(new FileImportScreen()))
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
index bf7fb351c0..07fb0aca5a 100644
--- a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Debug
{
public class MemorySettings : SettingsSubsection
{
- protected override LocalisableString Header => "Memory";
+ protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
[BackgroundDependencyLoader]
private void load(FrameworkDebugConfigManager config, GameHost host)
@@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{
new SettingsButton
{
- Text = "Clear all caches",
+ Text = DebugSettingsStrings.ClearAllCaches,
Action = host.Collect
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs
index 44d4088972..aa85ec920c 100644
--- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Debug;
namespace osu.Game.Overlays.Settings.Sections
{
public class DebugSection : SettingsSection
{
- public override string Header => "Debug";
+ public override LocalisableString Header => DebugSettingsStrings.DebugSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
index 69aa57082a..3a0265e453 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
@@ -6,13 +6,14 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => GameplaySettingsStrings.GeneralHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -21,62 +22,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
new SettingsSlider
{
- LabelText = "Background dim",
+ LabelText = GameplaySettingsStrings.BackgroundDim,
Current = config.GetBindable(OsuSetting.DimLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Background blur",
+ LabelText = GameplaySettingsStrings.BackgroundBlur,
Current = config.GetBindable(OsuSetting.BlurLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsCheckbox
{
- LabelText = "Lighten playfield during breaks",
+ LabelText = GameplaySettingsStrings.LightenDuringBreaks,
Current = config.GetBindable(OsuSetting.LightenDuringBreaks)
},
new SettingsEnumDropdown
{
- LabelText = "HUD overlay visibility mode",
+ LabelText = GameplaySettingsStrings.HUDVisibilityMode,
Current = config.GetBindable(OsuSetting.HUDVisibilityMode)
},
new SettingsCheckbox
{
- LabelText = "Show difficulty graph on progress bar",
- Current = config.GetBindable(OsuSetting.ShowDifficultyGraph)
+ LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
+ Current = config.GetBindable(OsuSetting.ShowProgressGraph)
},
new SettingsCheckbox
{
- LabelText = "Show health display even when you can't fail",
+ LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" }
},
new SettingsCheckbox
{
- LabelText = "Fade playfield to red when health is low",
+ LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
Current = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow),
},
new SettingsCheckbox
{
- LabelText = "Always show key overlay",
+ LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
Current = config.GetBindable(OsuSetting.KeyOverlay)
},
new SettingsCheckbox
{
- LabelText = "Positional hitsounds",
+ LabelText = GameplaySettingsStrings.PositionalHitsounds,
Current = config.GetBindable(OsuSetting.PositionalHitSounds)
},
new SettingsCheckbox
{
- LabelText = "Always play first combo break sound",
+ LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak)
},
new SettingsEnumDropdown
{
- LabelText = "Score display mode",
+ LabelText = GameplaySettingsStrings.ScoreDisplayMode,
Current = config.GetBindable(OsuSetting.ScoreDisplayMode),
Keywords = new[] { "scoring" }
},
@@ -86,7 +87,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
Add(new SettingsCheckbox
{
- LabelText = "Disable Windows key during gameplay",
+ LabelText = GameplaySettingsStrings.DisableWinKey,
Current = config.GetBindable(OsuSetting.GameplayDisableWinKey)
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
index ec9ddde2da..dfa060e8d5 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
@@ -6,12 +6,13 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class ModsSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Mods";
+ protected override LocalisableString Header => GameplaySettingsStrings.ModsHeader;
public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "mod" });
@@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
new SettingsCheckbox
{
- LabelText = "Increase visibility of first object when visual impairment mods are enabled",
+ LabelText = GameplaySettingsStrings.IncreaseFirstObjectVisibility,
Current = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility),
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
index acb94a6a01..42d9d48d73 100644
--- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
@@ -9,12 +9,14 @@ using osu.Game.Rulesets;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Logging;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections
{
public class GameplaySection : SettingsSection
{
- public override string Header => "Gameplay";
+ public override LocalisableString Header => GameplaySettingsStrings.GameplaySectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
index c6c752e2fd..200618c469 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
private SettingsDropdown languageSelection;
private Bindable frameworkLocale;
- protected override LocalisableString Header => "Language";
+ protected override LocalisableString Header => GeneralSettingsStrings.LanguageHeader;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig)
@@ -27,11 +27,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
languageSelection = new SettingsEnumDropdown
{
- LabelText = "Language",
+ LabelText = GeneralSettingsStrings.LanguageDropdown,
},
new SettingsCheckbox
{
- LabelText = "Prefer metadata in original language",
+ LabelText = GeneralSettingsStrings.PreferOriginalMetadataLanguage,
Current = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode)
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
index dd20e1d7ef..aa37748653 100644
--- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater;
@@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
[Resolved(CanBeNull = true)]
private UpdateManager updateManager { get; set; }
- protected override LocalisableString Header => "Updates";
+ protected override LocalisableString Header => GeneralSettingsStrings.UpdateHeader;
private SettingsButton checkForUpdatesButton;
@@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(new SettingsEnumDropdown
{
- LabelText = "Release stream",
+ LabelText = GeneralSettingsStrings.ReleaseStream,
Current = config.GetBindable(OsuSetting.ReleaseStream),
});
@@ -40,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(checkForUpdatesButton = new SettingsButton
{
- Text = "Check for updates",
+ Text = GeneralSettingsStrings.CheckUpdate,
Action = () =>
{
checkForUpdatesButton.Enabled.Value = false;
@@ -65,13 +66,13 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(new SettingsButton
{
- Text = "Open osu! folder",
+ Text = GeneralSettingsStrings.OpenOsuFolder,
Action = storage.OpenInNativeExplorer,
});
Add(new SettingsButton
{
- Text = "Change folder location...",
+ Text = GeneralSettingsStrings.ChangeFolderLocation,
Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen()))
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
index fefc3fe6a7..87e9f34833 100644
--- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.General;
namespace osu.Game.Overlays.Settings.Sections
{
public class GeneralSection : SettingsSection
{
- public override string Header => "General";
+ public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
index f889cfca0f..20b1d8d801 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
@@ -5,12 +5,13 @@ 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.Graphics
{
public class DetailSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Detail Settings";
+ protected override LocalisableString Header => GraphicsSettingsStrings.DetailSettingsHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,22 +20,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
new SettingsCheckbox
{
- LabelText = "Storyboard / Video",
+ LabelText = GraphicsSettingsStrings.StoryboardVideo,
Current = config.GetBindable(OsuSetting.ShowStoryboard)
},
new SettingsCheckbox
{
- LabelText = "Hit Lighting",
+ LabelText = GraphicsSettingsStrings.HitLighting,
Current = config.GetBindable(OsuSetting.HitLighting)
},
new SettingsEnumDropdown
{
- LabelText = "Screenshot format",
+ LabelText = GraphicsSettingsStrings.ScreenshotFormat,
Current = config.GetBindable(OsuSetting.ScreenshotFormat)
},
new SettingsCheckbox
{
- LabelText = "Show menu cursor in screenshots",
+ LabelText = GraphicsSettingsStrings.ShowCursorInScreenshots,
Current = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor)
}
};
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 91208cb78a..adf1453d1a 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -16,13 +16,14 @@ using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osuTK.Graphics;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class LayoutSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Layout";
+ protected override LocalisableString Header => GraphicsSettingsStrings.LayoutHeader;
private FillFlowContainer> scalingSettings;
@@ -67,20 +68,20 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
windowModeDropdown = new SettingsDropdown
{
- LabelText = "Screen mode",
+ LabelText = GraphicsSettingsStrings.ScreenMode,
ItemSource = windowModes,
Current = config.GetBindable(FrameworkSetting.WindowMode),
},
resolutionDropdown = new ResolutionSettingsDropdown
{
- LabelText = "Resolution",
+ LabelText = GraphicsSettingsStrings.Resolution,
ShowsDefaultIndicator = false,
ItemSource = resolutions,
Current = sizeFullscreen
},
new SettingsSlider
{
- LabelText = "UI Scaling",
+ LabelText = GraphicsSettingsStrings.UIScaling,
TransferValueOnCommit = true,
Current = osuConfig.GetBindable(OsuSetting.UIScale),
KeyboardStep = 0.01f,
@@ -88,7 +89,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
},
new SettingsEnumDropdown
{
- LabelText = "Screen Scaling",
+ LabelText = GraphicsSettingsStrings.ScreenScaling,
Current = osuConfig.GetBindable(OsuSetting.Scaling),
Keywords = new[] { "scale", "letterbox" },
},
@@ -97,35 +98,33 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- AutoSizeDuration = transition_duration,
- AutoSizeEasing = Easing.OutQuint,
Masking = true,
Children = new[]
{
new SettingsSlider
{
- LabelText = "Horizontal position",
+ LabelText = GraphicsSettingsStrings.HorizontalPosition,
Current = scalingPositionX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Vertical position",
+ LabelText = GraphicsSettingsStrings.VerticalPosition,
Current = scalingPositionY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Horizontal scale",
+ LabelText = GraphicsSettingsStrings.HorizontalScale,
Current = scalingSizeX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Vertical scale",
+ LabelText = GraphicsSettingsStrings.VerticalScale,
Current = scalingSizeY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
@@ -145,9 +144,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
updateResolutionDropdown();
- const string not_fullscreen_note = "Running without fullscreen mode will increase your input latency!";
-
- windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? not_fullscreen_note : string.Empty;
+ windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default;
}, true);
windowModes.BindCollectionChanged((sender, args) =>
@@ -177,13 +174,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
scalingMode.BindValueChanged(mode =>
{
scalingSettings.ClearTransforms();
- scalingSettings.AutoSizeAxes = mode.NewValue != ScalingMode.Off ? Axes.Y : Axes.None;
+ scalingSettings.AutoSizeDuration = transition_duration;
+ scalingSettings.AutoSizeEasing = Easing.OutQuint;
- if (mode.NewValue == ScalingMode.Off)
- scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
+ updateScalingModeVisibility();
+ });
- scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything);
- }, true);
+ // initial update bypasses transforms
+ updateScalingModeVisibility();
void updateResolutionDropdown()
{
@@ -192,6 +190,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
else
resolutionDropdown.Hide();
}
+
+ void updateScalingModeVisibility()
+ {
+ if (scalingMode.Value == ScalingMode.Off)
+ scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
+
+ scalingSettings.AutoSizeAxes = scalingMode.Value != ScalingMode.Off ? Axes.Y : Axes.None;
+ scalingSettings.ForEach(s => s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything);
+ }
}
private void bindPreviewEvent(Bindable bindable)
@@ -245,7 +252,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
protected override LocalisableString GenerateItemText(Size item)
{
if (item == new Size(9999, 9999))
- return "Default";
+ return CommonStrings.Default;
return $"{item.Width}x{item.Height}";
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
index 2210c7911e..653f30a018 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
@@ -7,12 +7,13 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class RendererSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Renderer";
+ protected override LocalisableString Header => GraphicsSettingsStrings.RendererHeader;
private SettingsEnumDropdown frameLimiterDropdown;
@@ -25,17 +26,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
// TODO: this needs to be a custom dropdown at some point
frameLimiterDropdown = new SettingsEnumDropdown
{
- LabelText = "Frame limiter",
+ LabelText = GraphicsSettingsStrings.FrameLimiter,
Current = config.GetBindable(FrameworkSetting.FrameSync)
},
new SettingsEnumDropdown
{
- LabelText = "Threading mode",
+ LabelText = GraphicsSettingsStrings.ThreadingMode,
Current = config.GetBindable(FrameworkSetting.ExecutionMode)
},
new SettingsCheckbox
{
- LabelText = "Show FPS",
+ LabelText = GraphicsSettingsStrings.ShowFPS,
Current = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay)
},
};
@@ -47,9 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
frameLimiterDropdown.Current.BindValueChanged(limit =>
{
- const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. \"2x refresh rate\" is recommended.";
-
- frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty;
+ frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? GraphicsSettingsStrings.UnlimitedFramesNote : default;
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
index 4ade48031f..fd0718f9f2 100644
--- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Graphics;
namespace osu.Game.Overlays.Settings.Sections
{
public class GraphicsSection : SettingsSection
{
- public override string Header => "Graphics";
+ public override LocalisableString Header => GraphicsSettingsStrings.GraphicsSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
index 9898a50320..3350ff4eaa 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Input
{
@@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Icon = FontAwesome.Solid.Globe
};
- public override string Header => "Global";
+ public override LocalisableString Header => InputSettingsStrings.GlobalKeyBindingHeader;
public GlobalKeyBindingsSection(GlobalActionContainer manager)
{
@@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Song Select";
+ protected override LocalisableString Header => InputSettingsStrings.SongSelectSection;
public SongSelectKeyBindingSubsection(GlobalActionContainer manager)
: base(null)
@@ -50,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class InGameKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "In Game";
+ protected override LocalisableString Header => InputSettingsStrings.InGameSection;
public InGameKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
@@ -61,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class AudioControlKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Audio";
+ protected override LocalisableString Header => InputSettingsStrings.AudioSection;
public AudioControlKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
@@ -72,7 +73,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class EditorKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Editor";
+ protected override LocalisableString Header => InputSettingsStrings.EditorSection;
public EditorKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
index 7cdc739b7c..67f1bb8d3e 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
@@ -4,13 +4,14 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Settings.Sections.Input
{
public class KeyBindingPanel : SettingsSubPanel
{
- protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!");
+ protected override Drawable CreateHeader() => new SettingsHeader(InputSettingsStrings.KeyBindingPanelHeader, InputSettingsStrings.KeyBindingPanelDescription);
[BackgroundDependencyLoader(permitNulls: true)]
private void load(RulesetStore rulesets, GlobalActionContainer global)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
index 6e018597be..c38c516f21 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
@@ -20,6 +20,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@@ -385,7 +386,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public CancelButton()
{
- Text = "Cancel";
+ Text = CommonStrings.Cancel;
Size = new Vector2(80, 20);
}
}
@@ -394,7 +395,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public ClearButton()
{
- Text = "Clear";
+ Text = CommonStrings.Clear;
Size = new Vector2(80, 20);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
index d65684fd37..ef5ccae1a0 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
@@ -10,6 +10,7 @@ using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
+using osu.Game.Localisation;
using osuTK;
namespace osu.Game.Overlays.Settings.Sections.Input
@@ -64,7 +65,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
[BackgroundDependencyLoader]
private void load()
{
- Text = "Reset all bindings in section";
+ Text = InputSettingsStrings.ResetSectionButton;
RelativeSizeAxes = Axes.X;
Width = 0.5f;
Anchor = Anchor.TopCentre;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
index 81a4d7eccd..5246051a4a 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets;
@@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Icon = OsuIcon.Hot
};
- public override string Header => ruleset.Name;
+ public override LocalisableString Header => ruleset.Name;
private readonly RulesetInfo ruleset;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
index c561b693d8..b8b86d9069 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
private readonly ITabletHandler tabletHandler;
+ private readonly Bindable enabled = new BindableBool(true);
+
private readonly Bindable areaOffset = new Bindable();
private readonly Bindable areaSize = new Bindable();
private readonly IBindable tablet = new Bindable();
@@ -74,7 +76,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
LabelText = CommonStrings.Enabled,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- Current = tabletHandler.Enabled
+ Current = enabled,
},
noTabletMessage = new FillFlowContainer
{
@@ -194,6 +196,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
base.LoadComplete();
+ enabled.BindTo(tabletHandler.Enabled);
+ enabled.BindValueChanged(_ => Scheduler.AddOnce(updateVisibility));
+
rotation.BindTo(tabletHandler.Rotation);
areaOffset.BindTo(tabletHandler.AreaOffset);
@@ -239,7 +244,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
tablet.BindTo(tabletHandler.Tablet);
tablet.BindValueChanged(val =>
{
- Scheduler.AddOnce(toggleVisibility);
+ Scheduler.AddOnce(updateVisibility);
var tab = val.NewValue;
@@ -259,19 +264,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, true);
}
- private void toggleVisibility()
+ private void updateVisibility()
{
- bool tabletFound = tablet.Value != null;
-
- if (!tabletFound)
- {
- mainSettings.Hide();
- noTabletMessage.Show();
- return;
- }
-
- mainSettings.Show();
+ mainSettings.Hide();
noTabletMessage.Hide();
+
+ if (!tabletHandler.Enabled.Value)
+ return;
+
+ if (tablet.Value != null)
+ mainSettings.Show();
+ else
+ noTabletMessage.Show();
}
private void applyAspectRatio(BindableNumber sizeChanged)
diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs
index 366f39388a..d282ba5318 100644
--- a/osu.Game/Overlays/Settings/Sections/InputSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs
@@ -11,6 +11,7 @@ using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Input;
namespace osu.Game.Overlays.Settings.Sections
@@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
private readonly KeyBindingPanel keyConfig;
- public override string Header => "Input";
+ public override LocalisableString Header => InputSettingsStrings.InputSectionHeader;
[Resolved]
private GameHost host { get; set; }
@@ -95,7 +96,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
new SettingsCheckbox
{
- LabelText = "Enabled",
+ LabelText = CommonStrings.Enabled,
Current = handler.Enabled
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
index 5392ba5d93..e509cac2f1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
@@ -14,6 +14,7 @@ using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
@@ -104,7 +105,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Origin = Anchor.Centre,
Width = 300,
Margin = new MarginPadding(10),
- Text = "Select directory",
+ Text = MaintenanceSettingsStrings.SelectDirectory,
Action = () => OnSelection(directorySelector.CurrentPath.Value)
},
}
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
index b9a408b1f8..803c8332c1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -11,6 +11,7 @@ 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;
@@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importBeatmapsButton = new SettingsButton
{
- Text = "Import beatmaps from stable",
+ Text = MaintenanceSettingsStrings.ImportBeatmapsFromStable,
Action = () =>
{
importBeatmapsButton.Enabled.Value = false;
@@ -48,7 +49,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteBeatmapsButton = new DangerousSettingsButton
{
- Text = "Delete ALL beatmaps",
+ Text = MaintenanceSettingsStrings.DeleteAllBeatmaps,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importScoresButton = new SettingsButton
{
- Text = "Import scores from stable",
+ Text = MaintenanceSettingsStrings.ImportScoresFromStable,
Action = () =>
{
importScoresButton.Enabled.Value = false;
@@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteScoresButton = new DangerousSettingsButton
{
- Text = "Delete ALL scores",
+ Text = MaintenanceSettingsStrings.DeleteAllScores,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -89,7 +90,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importSkinsButton = new SettingsButton
{
- Text = "Import skins from stable",
+ Text = MaintenanceSettingsStrings.ImportSkinsFromStable,
Action = () =>
{
importSkinsButton.Enabled.Value = false;
@@ -100,7 +101,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteSkinsButton = new DangerousSettingsButton
{
- Text = "Delete ALL skins",
+ Text = MaintenanceSettingsStrings.DeleteAllSkins,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -117,7 +118,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importCollectionsButton = new SettingsButton
{
- Text = "Import collections from stable",
+ Text = MaintenanceSettingsStrings.ImportCollectionsFromStable,
Action = () =>
{
importCollectionsButton.Enabled.Value = false;
@@ -128,7 +129,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(new DangerousSettingsButton
{
- Text = "Delete ALL collections",
+ Text = MaintenanceSettingsStrings.DeleteAllCollections,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll));
@@ -140,7 +141,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
restoreButton = new SettingsButton
{
- Text = "Restore all hidden difficulties",
+ Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties,
Action = () =>
{
restoreButton.Enabled.Value = false;
@@ -153,7 +154,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
},
undeleteButton = new SettingsButton
{
- Text = "Restore all recently deleted beatmaps",
+ Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps,
Action = () =>
{
undeleteButton.Enabled.Value = false;
diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
index 73c88b8e71..fa0c06167b 100644
--- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
@@ -3,6 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osuTK;
@@ -10,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
public class MaintenanceSection : SettingsSection
{
- public override string Header => "Maintenance";
+ public override LocalisableString Header => MaintenanceSettingsStrings.MaintenanceSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
index 3a2de2ee36..351a32c72e 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
@@ -5,12 +5,13 @@ 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.Online
{
public class AlertsAndPrivacySettings : SettingsSubsection
{
- protected override LocalisableString Header => "Alerts and Privacy";
+ protected override LocalisableString Header => OnlineSettingsStrings.AlertsAndPrivacyHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,12 +20,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsCheckbox
{
- LabelText = "Show a notification when someone mentions your name",
+ LabelText = OnlineSettingsStrings.NotifyOnMentioned,
Current = config.GetBindable(OsuSetting.NotifyOnUsernameMentioned)
},
new SettingsCheckbox
{
- LabelText = "Show a notification when you receive a private message",
+ LabelText = OnlineSettingsStrings.NotifyOnPrivateMessage,
Current = config.GetBindable(OsuSetting.NotifyOnPrivateMessage)
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
index f2012f0d9c..0207f2fd01 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
@@ -5,12 +5,13 @@ 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.Online
{
public class IntegrationSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Integrations";
+ protected override LocalisableString Header => OnlineSettingsStrings.IntegrationsHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsEnumDropdown
{
- LabelText = "Discord Rich Presence",
+ LabelText = OnlineSettingsStrings.DiscordRichPresence,
Current = config.GetBindable(OsuSetting.DiscordRichPresence)
}
};
diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
index 89e7b096f3..e864260cc6 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
@@ -5,12 +5,13 @@ 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.Online
{
public class WebSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Web";
+ protected override LocalisableString Header => OnlineSettingsStrings.WebHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,24 +20,24 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsCheckbox
{
- LabelText = "Warn about opening external links",
+ LabelText = OnlineSettingsStrings.ExternalLinkWarning,
Current = config.GetBindable(OsuSetting.ExternalLinkWarning)
},
new SettingsCheckbox
{
- LabelText = "Prefer downloads without video",
+ LabelText = OnlineSettingsStrings.PreferNoVideo,
Keywords = new[] { "no-video" },
Current = config.GetBindable(OsuSetting.PreferNoVideo)
},
new SettingsCheckbox
{
- LabelText = "Automatically download beatmaps when spectating",
+ LabelText = OnlineSettingsStrings.AutomaticallyDownloadWhenSpectating,
Keywords = new[] { "spectator" },
Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating),
},
new SettingsCheckbox
{
- LabelText = "Show explicit content in search results",
+ LabelText = OnlineSettingsStrings.ShowExplicitContent,
Keywords = new[] { "nsfw", "18+", "offensive" },
Current = config.GetBindable(OsuSetting.ShowOnlineExplicitContent),
}
diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
index 680d11f7da..8b523b90b9 100644
--- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Online;
namespace osu.Game.Overlays.Settings.Sections
{
public class OnlineSection : SettingsSection
{
- public override string Header => "Online";
+ public override LocalisableString Header => OnlineSettingsStrings.OnlineSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 9f3543d059..e0d8252930 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -13,6 +13,7 @@ using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osuTK;
@@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
private SkinSettingsDropdown skinDropdown;
- public override string Header => "Skin";
+ public override LocalisableString Header => SkinSettingsStrings.SkinSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
@@ -69,34 +70,34 @@ namespace osu.Game.Overlays.Settings.Sections
skinDropdown = new SkinSettingsDropdown(),
new SettingsButton
{
- Text = "Skin layout editor",
+ Text = SkinSettingsStrings.SkinLayoutEditor,
Action = () => skinEditor?.Toggle(),
},
new ExportSkinButton(),
new SettingsSlider
{
- LabelText = "Gameplay cursor size",
+ LabelText = SkinSettingsStrings.GameplayCursorSize,
Current = config.GetBindable(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
- LabelText = "Adjust gameplay cursor size based on current beatmap",
+ LabelText = SkinSettingsStrings.AutoCursorSize,
Current = config.GetBindable(OsuSetting.AutoCursorSize)
},
new SettingsCheckbox
{
- LabelText = "Beatmap skins",
+ LabelText = SkinSettingsStrings.BeatmapSkins,
Current = config.GetBindable(OsuSetting.BeatmapSkins)
},
new SettingsCheckbox
{
- LabelText = "Beatmap colours",
+ LabelText = SkinSettingsStrings.BeatmapColours,
Current = config.GetBindable(OsuSetting.BeatmapColours)
},
new SettingsCheckbox
{
- LabelText = "Beatmap hitsounds",
+ LabelText = SkinSettingsStrings.BeatmapHitsounds,
Current = config.GetBindable(OsuSetting.BeatmapHitsounds)
},
};
@@ -200,7 +201,7 @@ namespace osu.Game.Overlays.Settings.Sections
[BackgroundDependencyLoader]
private void load()
{
- Text = "Export selected skin";
+ Text = SkinSettingsStrings.ExportSkinButton;
Action = export;
currentSkin = skins.CurrentSkin.GetBoundCopy();
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
index 4b26645ef3..0afbed5df5 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => UserInterfaceStrings.GeneralHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -20,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Rotate cursor when dragging",
+ LabelText = UserInterfaceStrings.CursorRotation,
Current = config.GetBindable(OsuSetting.CursorRotation)
},
new SettingsSlider
{
- LabelText = "Menu cursor size",
+ LabelText = UserInterfaceStrings.MenuCursorSize,
Current = config.GetBindable(OsuSetting.MenuCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
- LabelText = "Parallax",
+ LabelText = UserInterfaceStrings.Parallax,
Current = config.GetBindable(OsuSetting.MenuParallax)
},
new SettingsSlider
{
- LabelText = "Hold-to-confirm activation time",
+ LabelText = UserInterfaceStrings.HoldToConfirmActivationTime,
Current = config.GetBindable(OsuSetting.UIHoldActivationDelay),
KeyboardStep = 50
},
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
index 81bbcbb54a..40485a070c 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Users;
@@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
public class MainMenuSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Main Menu";
+ protected override LocalisableString Header => UserInterfaceStrings.MainMenuHeader;
private IBindable user;
@@ -28,27 +29,27 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Interface voices",
+ LabelText = UserInterfaceStrings.InterfaceVoices,
Current = config.GetBindable(OsuSetting.MenuVoice)
},
new SettingsCheckbox
{
- LabelText = "osu! music theme",
+ LabelText = UserInterfaceStrings.OsuMusicTheme,
Current = config.GetBindable(OsuSetting.MenuMusic)
},
new SettingsEnumDropdown
{
- LabelText = "Intro sequence",
+ LabelText = UserInterfaceStrings.IntroSequence,
Current = config.GetBindable(OsuSetting.IntroSequence),
},
backgroundSourceDropdown = new SettingsEnumDropdown
{
- LabelText = "Background source",
+ LabelText = UserInterfaceStrings.BackgroundSource,
Current = config.GetBindable(OsuSetting.MenuBackgroundSource),
},
new SettingsEnumDropdown
{
- LabelText = "Seasonal backgrounds",
+ LabelText = UserInterfaceStrings.SeasonalBackgrounds,
Current = config.GetBindable(OsuSetting.SeasonalBackgroundMode),
}
};
@@ -60,9 +61,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
user.BindValueChanged(u =>
{
- const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag.";
-
- backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? not_supporter_note : string.Empty;
+ backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? UserInterfaceStrings.NotSupporterNote : default;
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
index 587155eb0d..6290046987 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
@@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
private Bindable minStars;
private Bindable maxStars;
- protected override LocalisableString Header => "Song Select";
+ protected override LocalisableString Header => UserInterfaceStrings.SongSelectHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -31,31 +32,31 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Right mouse drag to absolute scroll",
+ LabelText = UserInterfaceStrings.RightMouseScroll,
Current = config.GetBindable(OsuSetting.SongSelectRightMouseScroll),
},
new SettingsCheckbox
{
- LabelText = "Show converted beatmaps",
+ LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,
Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps),
},
new SettingsSlider
{
- LabelText = "Display beatmaps from",
+ LabelText = UserInterfaceStrings.StarsMinimum,
Current = config.GetBindable(OsuSetting.DisplayStarsMinimum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsSlider
{
- LabelText = "up to",
+ LabelText = UserInterfaceStrings.StarsMaximum,
Current = config.GetBindable(OsuSetting.DisplayStarsMaximum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsEnumDropdown
{
- LabelText = "Random selection algorithm",
+ LabelText = UserInterfaceStrings.RandomSelectionAlgorithm,
Current = config.GetBindable(OsuSetting.RandomSelectAlgorithm),
}
};
@@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
private class MaximumStarsSlider : StarsSlider
{
- public override LocalisableString TooltipText => Current.IsDefault ? "no limit" : base.TooltipText;
+ public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText;
}
private class StarsSlider : OsuSliderBar
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
index 718fea5f2b..6228c4c99a 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.UserInterface;
namespace osu.Game.Overlays.Settings.Sections
{
public class UserInterfaceSection : SettingsSection
{
- public override string Header => "User Interface";
+ public override LocalisableString Header => UserInterfaceStrings.UserInterfaceSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index ef2027fdab..5282217013 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Overlays.Settings
{
set
{
- bool hasValue = string.IsNullOrWhiteSpace(value.ToString());
+ bool hasValue = !string.IsNullOrWhiteSpace(value.ToString());
if (warningText == null)
{
@@ -76,7 +76,7 @@ namespace osu.Game.Overlays.Settings
FlowContent.Add(warningText = new SettingsNoticeText(colours) { Margin = new MarginPadding { Bottom = 5 } });
}
- warningText.Alpha = hasValue ? 0 : 1;
+ warningText.Alpha = hasValue ? 1 : 0;
warningText.Text = value.ToString(); // TODO: Remove ToString() call after TextFlowContainer supports localisation (see https://github.com/ppy/osu-framework/issues/4636).
}
}
@@ -93,15 +93,13 @@ namespace osu.Game.Overlays.Settings
public bool MatchingFilter
{
- set => this.FadeTo(value ? 1 : 0);
+ set => Alpha = value ? 1 : 0;
}
public bool FilteringActive { get; set; }
public event Action SettingChanged;
- private readonly RestoreDefaultValueButton restoreDefaultButton;
-
protected SettingsItem()
{
RelativeSizeAxes = Axes.X;
@@ -110,7 +108,6 @@ namespace osu.Game.Overlays.Settings
InternalChildren = new Drawable[]
{
- restoreDefaultButton = new RestoreDefaultValueButton(),
FlowContent = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
@@ -123,7 +120,7 @@ namespace osu.Game.Overlays.Settings
},
};
- // all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is
+ // IMPORTANT: all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is
// never loaded, but requires bindable storage.
if (controlWithCurrent == null)
throw new ArgumentException(@$"Control created via {nameof(CreateControl)} must implement {nameof(IHasCurrentValue)}");
@@ -132,12 +129,17 @@ namespace osu.Game.Overlays.Settings
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
}
- protected override void LoadComplete()
+ [BackgroundDependencyLoader]
+ private void load()
{
- base.LoadComplete();
-
+ // intentionally done before LoadComplete to avoid overhead.
if (ShowsDefaultIndicator)
- restoreDefaultButton.Current = controlWithCurrent.Current;
+ {
+ AddInternal(new RestoreDefaultValueButton
+ {
+ Current = controlWithCurrent.Current,
+ });
+ }
}
private void updateDisabled()
diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs
index 4143605c28..2a6f3f5ed7 100644
--- a/osu.Game/Overlays/Settings/SettingsSection.cs
+++ b/osu.Game/Overlays/Settings/SettingsSection.cs
@@ -4,9 +4,12 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@@ -18,11 +21,15 @@ namespace osu.Game.Overlays.Settings
protected FillFlowContainer FlowContent;
protected override Container Content => FlowContent;
+ private IBindable selectedSection;
+
+ private OsuSpriteText header;
+
public abstract Drawable CreateIcon();
- public abstract string Header { get; }
+ public abstract LocalisableString Header { get; }
public IEnumerable FilterableChildren => Children.OfType();
- public virtual IEnumerable FilterTerms => new[] { Header };
+ public virtual IEnumerable FilterTerms => new[] { Header.ToString() };
private const int header_size = 26;
private const int margin = 20;
@@ -35,9 +42,11 @@ namespace osu.Game.Overlays.Settings
public bool FilteringActive { get; set; }
+ [Resolved]
+ private SettingsPanel settingsPanel { get; set; }
+
protected SettingsSection()
{
- Margin = new MarginPadding { Top = margin };
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
@@ -60,6 +69,7 @@ namespace osu.Game.Overlays.Settings
{
new Box
{
+ Name = "separator",
Colour = new Color4(0, 0, 0, 255),
RelativeSizeAxes = Axes.X,
Height = border_size,
@@ -69,13 +79,13 @@ namespace osu.Game.Overlays.Settings
Padding = new MarginPadding
{
Top = margin + border_size,
- Bottom = 10,
+ Bottom = margin + 10,
},
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
- new OsuSpriteText
+ header = new OsuSpriteText
{
Font = OsuFont.GetFont(size: header_size),
Text = Header,
@@ -90,6 +100,51 @@ namespace osu.Game.Overlays.Settings
}
},
});
+
+ selectedSection = settingsPanel.CurrentSection.GetBoundCopy();
+ selectedSection.BindValueChanged(_ => updateContentFade(), true);
+ }
+
+ private bool isCurrentSection => selectedSection.Value == this;
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateContentFade();
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateContentFade();
+ base.OnHoverLost(e);
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ if (!isCurrentSection)
+ settingsPanel.SectionsContainer.ScrollTo(this);
+
+ return base.OnClick(e);
+ }
+
+ protected override bool ShouldBeConsideredForInput(Drawable child) =>
+ // only the current section should accept input.
+ // this provides the behaviour of the first click scrolling the target section to the centre of the screen.
+ isCurrentSection;
+
+ private void updateContentFade()
+ {
+ float contentFade = 1;
+ float headerFade = 1;
+
+ if (!isCurrentSection)
+ {
+ contentFade = 0.25f;
+ headerFade = IsHovered ? 0.5f : 0.25f;
+ }
+
+ header.FadeTo(headerFade, 500, Easing.OutQuint);
+ FlowContent.FadeTo(contentFade, 500, Easing.OutQuint);
}
}
}
diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs
index df32424b67..4aa9360452 100644
--- a/osu.Game/Overlays/Settings/SettingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Overlays.Settings
protected abstract LocalisableString Header { get; }
public IEnumerable FilterableChildren => Children.OfType();
+
+ // FilterTerms should contains both original string and localised string for user to search.
+ // Since LocalisableString is unable to get original string at this time (2021-08-14),
+ // only call .ToString() to use localised one.
+ // TODO: Update here when FilterTerms accept LocalisableString.
public virtual IEnumerable FilterTerms => new[] { Header.ToString() };
public bool MatchingFilter
diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs
index 30a53b351d..cf6a313a1f 100644
--- a/osu.Game/Overlays/Settings/SidebarButton.cs
+++ b/osu.Game/Overlays/Settings/SidebarButton.cs
@@ -22,6 +22,9 @@ namespace osu.Game.Overlays.Settings
private readonly Box selectionIndicator;
private readonly Container text;
+ // always consider as part of flow, even when not visible (for the sake of the initial animation).
+ public override bool IsPresent => true;
+
private SettingsSection section;
public SettingsSection Section
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index f1c41c4b50..e4e76592d8 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -4,9 +4,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -20,6 +22,7 @@ using osu.Game.Overlays.Settings;
namespace osu.Game.Overlays
{
+ [Cached]
public abstract class SettingsPanel : OsuFocusedOverlayContainer
{
public const float CONTENT_MARGINS = 15;
@@ -45,7 +48,7 @@ namespace osu.Game.Overlays
protected Sidebar Sidebar;
private SidebarButton selectedSidebarButton;
- protected SettingsSectionsContainer SectionsContainer;
+ public SettingsSectionsContainer SectionsContainer { get; private set; }
private SeekLimitedSearchTextBox searchTextBox;
@@ -58,6 +61,14 @@ namespace osu.Game.Overlays
private readonly bool showSidebar;
+ private LoadingLayer loading;
+
+ private readonly List loadableSections = new List();
+
+ private Task sectionsLoadingTask;
+
+ public IBindable CurrentSection = new Bindable();
+
protected SettingsPanel(bool showSidebar)
{
this.showSidebar = showSidebar;
@@ -86,69 +97,49 @@ namespace osu.Game.Overlays
Colour = OsuColour.Gray(0.05f),
Alpha = 1,
},
- SectionsContainer = new SettingsSectionsContainer
+ loading = new LoadingLayer
{
- Masking = true,
- RelativeSizeAxes = Axes.Both,
- ExpandableHeader = CreateHeader(),
- FixedHeader = searchTextBox = new SeekLimitedSearchTextBox
- {
- RelativeSizeAxes = Axes.X,
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- Width = 0.95f,
- Margin = new MarginPadding
- {
- Top = 20,
- Bottom = 20
- },
- },
- Footer = CreateFooter()
- },
+ State = { Value = Visibility.Visible }
+ }
}
};
+ Add(SectionsContainer = new SettingsSectionsContainer
+ {
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ ExpandableHeader = CreateHeader(),
+ SelectedSection = { BindTarget = CurrentSection },
+ FixedHeader = searchTextBox = new SeekLimitedSearchTextBox
+ {
+ RelativeSizeAxes = Axes.X,
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ Width = 0.95f,
+ Margin = new MarginPadding
+ {
+ Top = 20,
+ Bottom = 20
+ },
+ },
+ Footer = CreateFooter().With(f => f.Alpha = 0)
+ });
+
if (showSidebar)
{
AddInternal(Sidebar = new Sidebar { Width = sidebar_width });
-
- SectionsContainer.SelectedSection.ValueChanged += section =>
- {
- selectedSidebarButton.Selected = false;
- selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section.NewValue);
- selectedSidebarButton.Selected = true;
- };
}
- searchTextBox.Current.ValueChanged += term => SectionsContainer.SearchContainer.SearchTerm = term.NewValue;
-
CreateSections()?.ForEach(AddSection);
}
protected void AddSection(SettingsSection section)
{
- SectionsContainer.Add(section);
+ if (IsLoaded)
+ // just to keep things simple. can be accommodated for if we ever need it.
+ throw new InvalidOperationException("All sections must be added before the panel is loaded.");
- if (Sidebar != null)
- {
- var button = new SidebarButton
- {
- Section = section,
- Action = () =>
- {
- SectionsContainer.ScrollTo(section);
- Sidebar.State = ExpandedState.Contracted;
- },
- };
-
- Sidebar.Add(button);
-
- if (selectedSidebarButton == null)
- {
- selectedSidebarButton = Sidebar.Children.First();
- selectedSidebarButton.Selected = true;
- }
- }
+ loadableSections.Add(section);
}
protected virtual Drawable CreateHeader() => new Container();
@@ -161,6 +152,12 @@ namespace osu.Game.Overlays
ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint);
+ // delay load enough to ensure it doesn't overlap with the initial animation.
+ // this is done as there is still a brief stutter during load completion which is more visible if the transition is in progress.
+ // the eventual goal would be to remove the need for this by splitting up load into smaller work pieces, or fixing the remaining
+ // load complete overheads.
+ Scheduler.AddDelayed(loadSections, TRANSITION_LENGTH / 3);
+
Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint);
@@ -199,6 +196,80 @@ namespace osu.Game.Overlays
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
}
+ private const double fade_in_duration = 1000;
+
+ private void loadSections()
+ {
+ if (sectionsLoadingTask != null)
+ return;
+
+ sectionsLoadingTask = LoadComponentsAsync(loadableSections, sections =>
+ {
+ SectionsContainer.AddRange(sections);
+ SectionsContainer.Footer.FadeInFromZero(fade_in_duration, Easing.OutQuint);
+ SectionsContainer.SearchContainer.FadeInFromZero(fade_in_duration, Easing.OutQuint);
+
+ loading.Hide();
+
+ searchTextBox.Current.BindValueChanged(term => SectionsContainer.SearchContainer.SearchTerm = term.NewValue, true);
+ searchTextBox.TakeFocus();
+
+ loadSidebarButtons();
+ });
+ }
+
+ private void loadSidebarButtons()
+ {
+ if (Sidebar == null)
+ return;
+
+ LoadComponentsAsync(createSidebarButtons(), buttons =>
+ {
+ float delay = 0;
+
+ foreach (var button in buttons)
+ {
+ Sidebar.Add(button);
+
+ button.FadeOut()
+ .Delay(delay)
+ .FadeInFromZero(fade_in_duration, Easing.OutQuint);
+
+ delay += 40;
+ }
+
+ SectionsContainer.SelectedSection.BindValueChanged(section =>
+ {
+ if (selectedSidebarButton != null)
+ selectedSidebarButton.Selected = false;
+
+ selectedSidebarButton = Sidebar.Children.FirstOrDefault(b => b.Section == section.NewValue);
+
+ if (selectedSidebarButton != null)
+ selectedSidebarButton.Selected = true;
+ }, true);
+ });
+ }
+
+ private IEnumerable createSidebarButtons()
+ {
+ foreach (var section in SectionsContainer)
+ {
+ yield return new SidebarButton
+ {
+ Section = section,
+ Action = () =>
+ {
+ if (!SectionsContainer.IsLoaded)
+ return;
+
+ SectionsContainer.ScrollTo(section);
+ Sidebar.State = ExpandedState.Contracted;
+ },
+ };
+ }
+ }
+
private class NonMaskedContent : Container
{
// masking breaks the pan-out transform with nested sub-settings panels.
@@ -209,6 +280,16 @@ namespace osu.Game.Overlays
{
public SearchContainer SearchContainer;
+ public string SearchTerm
+ {
+ get => SearchContainer.SearchTerm;
+ set
+ {
+ SearchContainer.SearchTerm = value;
+ InvalidateScrollPosition();
+ }
+ }
+
protected override FlowContainer CreateScrollContentContainer()
=> SearchContainer = new SearchContainer
{
diff --git a/osu.Game/Performance/HighPerformanceSession.cs b/osu.Game/Performance/HighPerformanceSession.cs
index 661c1046f1..3ef0e0bf93 100644
--- a/osu.Game/Performance/HighPerformanceSession.cs
+++ b/osu.Game/Performance/HighPerformanceSession.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Screens.Play;
namespace osu.Game.Performance
{
@@ -12,9 +13,9 @@ namespace osu.Game.Performance
private readonly IBindable localUserPlaying = new Bindable();
[BackgroundDependencyLoader]
- private void load(OsuGame game)
+ private void load(ILocalUserPlayInfo localUserInfo)
{
- localUserPlaying.BindTo(game.LocalUserPlaying);
+ localUserPlaying.BindTo(localUserInfo.IsPlaying);
}
protected override void LoadComplete()
diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainDecaySkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainDecaySkill.cs
new file mode 100644
index 0000000000..73bab31e82
--- /dev/null
+++ b/osu.Game/Rulesets/Difficulty/Skills/StrainDecaySkill.cs
@@ -0,0 +1,54 @@
+// 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.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Difficulty.Skills
+{
+ ///
+ /// Used to processes strain values of s, keep track of strain levels caused by the processed objects
+ /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
+ ///
+ public abstract class StrainDecaySkill : StrainSkill
+ {
+ ///
+ /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
+ ///
+ protected abstract double SkillMultiplier { get; }
+
+ ///
+ /// Determines how quickly strain decays for the given skill.
+ /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
+ ///
+ protected abstract double StrainDecayBase { get; }
+
+ ///
+ /// The current strain level.
+ ///
+ protected double CurrentStrain { get; private set; } = 1;
+
+ protected StrainDecaySkill(Mod[] mods)
+ : base(mods)
+ {
+ }
+
+ protected override double CalculateInitialStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
+
+ protected override double StrainValueAt(DifficultyHitObject current)
+ {
+ CurrentStrain *= strainDecay(current.DeltaTime);
+ CurrentStrain += StrainValueOf(current) * SkillMultiplier;
+
+ return CurrentStrain;
+ }
+
+ ///
+ /// Calculates the strain value of a . This value is affected by previously processed objects.
+ ///
+ protected abstract double StrainValueOf(DifficultyHitObject current);
+
+ private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
+ }
+}
diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
index d4fcefab9b..0880f1b08e 100644
--- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
+++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
@@ -15,27 +15,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills
///
public abstract class StrainSkill : Skill
{
- ///
- /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
- ///
- protected abstract double SkillMultiplier { get; }
-
- ///
- /// Determines how quickly strain decays for the given skill.
- /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
- ///
- protected abstract double StrainDecayBase { get; }
-
///
/// The weight by which each strain value decays.
///
protected virtual double DecayWeight => 0.9;
- ///
- /// The current strain level.
- ///
- protected double CurrentStrain { get; private set; } = 1;
-
///
/// The length of each strain section.
///
@@ -52,6 +36,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills
{
}
+ ///
+ /// Returns the strain value at . This value is calculated with or without respect to previous objects.
+ ///
+ protected abstract double StrainValueAt(DifficultyHitObject current);
+
///
/// Process a and update current strain values accordingly.
///
@@ -68,10 +57,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
currentSectionEnd += SectionLength;
}
- CurrentStrain *= strainDecay(current.DeltaTime);
- CurrentStrain += StrainValueOf(current) * SkillMultiplier;
-
- currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak);
+ currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak);
}
///
@@ -88,9 +74,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// The beginning of the new section in milliseconds.
private void startNewSectionFrom(double time)
{
- // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries.
+ // The maximum strain of the new section is not zero by default
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
- currentSectionPeak = GetPeakStrain(time);
+ currentSectionPeak = CalculateInitialStrain(time);
}
///
@@ -98,7 +84,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
///
/// The time to retrieve the peak strain at.
/// The peak strain.
- protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
+ protected abstract double CalculateInitialStrain(double time);
///
/// Returns a live enumerable of the peak strains for each section of the beatmap,
@@ -124,12 +110,5 @@ namespace osu.Game.Rulesets.Difficulty.Skills
return difficulty;
}
-
- ///
- /// Calculates the strain value of a . This value is affected by previously processed objects.
- ///
- protected abstract double StrainValueOf(DifficultyHitObject current);
-
- private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
}
}
diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
index 067657159b..3978378c3a 100644
--- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
+++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mods
{
// Intercept and extract the internal number bindable from DifficultyBindable.
// This will provide bounds and precision specifications for the slider bar.
- difficultyBindable = ((DifficultyBindable)value).GetBoundCopy();
+ difficultyBindable = (DifficultyBindable)value.GetBoundCopy();
sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber);
base.Current = difficultyBindable;
@@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Mods
{
// This is required as SettingsItem relies heavily on this bindable for internal use.
// The actual update flow is done via the bindable provided in the constructor.
- public Bindable Current { get; set; } = new Bindable();
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
public SliderControl(BindableNumber currentNumber)
{
diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs
index 664b88eef4..e4304795f2 100644
--- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs
+++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs
@@ -128,6 +128,6 @@ namespace osu.Game.Rulesets.Mods
ExtendedLimits.UnbindFrom(otherDifficultyBindable.ExtendedLimits);
}
- public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this };
+ protected override Bindable CreateInstance() => new DifficultyBindable();
}
}
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index f2fd02c652..9f3b5eaf5b 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -129,6 +129,17 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual Type[] IncompatibleMods => Array.Empty();
+ private IReadOnlyList settingsBacking;
+
+ ///
+ /// A list of the all settings within this mod.
+ ///
+ internal IReadOnlyList Settings =>
+ settingsBacking ??= this.GetSettingsSourceProperties()
+ .Select(p => p.Item2.GetValue(this))
+ .Cast()
+ .ToList();
+
///
/// Creates a copy of this initialised to a default state.
///
@@ -191,15 +202,39 @@ namespace osu.Game.Rulesets.Mods
if (ReferenceEquals(this, other)) return true;
return GetType() == other.GetType() &&
- this.GetSettingsSourceProperties().All(pair =>
- EqualityComparer
public class ListingPollingComponent : RoomPollingComponent
{
- [Resolved]
- private Bindable currentFilter { get; set; }
+ public IBindable InitialRoomsReceived => initialRoomsReceived;
+ private readonly Bindable initialRoomsReceived = new Bindable();
+
+ public readonly Bindable Filter = new Bindable();
[Resolved]
private Bindable selectedRoom { get; set; }
@@ -23,9 +26,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
[BackgroundDependencyLoader]
private void load()
{
- currentFilter.BindValueChanged(_ =>
+ Filter.BindValueChanged(_ =>
{
- NotifyRoomsReceived(null);
+ RoomManager.ClearRooms();
+ initialRoomsReceived.Value = false;
+
if (IsLoaded)
PollImmediately();
});
@@ -38,24 +43,26 @@ namespace osu.Game.Screens.OnlinePlay.Components
if (!API.IsLoggedIn)
return base.Poll();
+ if (Filter.Value == null)
+ return base.Poll();
+
var tcs = new TaskCompletionSource();
pollReq?.Cancel();
- pollReq = new GetRoomsRequest(currentFilter.Value.Status, currentFilter.Value.Category);
+ pollReq = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category);
pollReq.Success += result =>
{
- for (int i = 0; i < result.Count; i++)
+ foreach (var existing in RoomManager.Rooms.ToArray())
{
- if (result[i].RoomID.Value == selectedRoom.Value?.RoomID.Value)
- {
- // The listing request always has less information than the opened room, so don't include it.
- result[i] = selectedRoom.Value;
- break;
- }
+ if (result.All(r => r.RoomID.Value != existing.RoomID.Value))
+ RoomManager.RemoveRoom(existing);
}
- NotifyRoomsReceived(result);
+ foreach (var incoming in result)
+ RoomManager.AddOrUpdateRoom(incoming);
+
+ initialRoomsReceived.Value = true;
tcs.SetResult(true);
};
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
index 422576648c..3b6c1c8be0 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
@@ -8,7 +8,6 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
@@ -17,15 +16,12 @@ using osu.Game.Rulesets;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public abstract class RoomManager : CompositeDrawable, IRoomManager
+ public class RoomManager : Component, IRoomManager
{
public event Action RoomsUpdated;
private readonly BindableList rooms = new BindableList();
- public IBindable InitialRoomsReceived => initialRoomsReceived;
- private readonly Bindable initialRoomsReceived = new Bindable();
-
public IBindableList Rooms => rooms;
protected IBindable JoinedRoom => joinedRoom;
@@ -40,15 +36,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
[Resolved]
private IAPIProvider api { get; set; }
- protected RoomManager()
+ public RoomManager()
{
RelativeSizeAxes = Axes.Both;
-
- InternalChildren = CreatePollingComponents().Select(p =>
- {
- p.RoomsReceived = onRoomsReceived;
- return p;
- }).ToList();
}
protected override void Dispose(bool isDisposing)
@@ -67,10 +57,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
joinedRoom.Value = room;
- update(room, result);
- addRoom(room);
+ AddOrUpdateRoom(result);
+ room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere.
- RoomsUpdated?.Invoke();
+ // The server may not contain all properties (such as password), so invoke success with the given room.
onSuccess?.Invoke(room);
};
@@ -118,84 +108,49 @@ namespace osu.Game.Screens.OnlinePlay.Components
private readonly HashSet ignoredRooms = new HashSet();
- private void onRoomsReceived(List received)
+ public void AddOrUpdateRoom(Room room)
{
- if (received == null)
- {
- ClearRooms();
+ Debug.Assert(room.RoomID.Value != null);
+
+ if (ignoredRooms.Contains(room.RoomID.Value.Value))
return;
- }
- // Remove past matches
- foreach (var r in rooms.ToList())
+ room.Position.Value = -room.RoomID.Value.Value;
+
+ try
{
- if (received.All(e => e.RoomID.Value != r.RoomID.Value))
- rooms.Remove(r);
- }
+ foreach (var pi in room.Playlist)
+ pi.MapObjects(beatmaps, rulesets);
- for (int i = 0; i < received.Count; i++)
+ var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
+ if (existing == null)
+ rooms.Add(room);
+ else
+ existing.CopyFrom(room);
+ }
+ catch (Exception ex)
{
- var room = received[i];
+ Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
- Debug.Assert(room.RoomID.Value != null);
-
- if (ignoredRooms.Contains(room.RoomID.Value.Value))
- continue;
-
- room.Position.Value = i;
-
- try
- {
- update(room, room);
- addRoom(room);
- }
- catch (Exception ex)
- {
- Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
-
- ignoredRooms.Add(room.RoomID.Value.Value);
- rooms.Remove(room);
- }
+ ignoredRooms.Add(room.RoomID.Value.Value);
+ rooms.Remove(room);
}
- RoomsUpdated?.Invoke();
- initialRoomsReceived.Value = true;
+ notifyRoomsUpdated();
}
- protected void RemoveRoom(Room room) => rooms.Remove(room);
+ public void RemoveRoom(Room room)
+ {
+ rooms.Remove(room);
+ notifyRoomsUpdated();
+ }
- protected void ClearRooms()
+ public void ClearRooms()
{
rooms.Clear();
- initialRoomsReceived.Value = false;
+ notifyRoomsUpdated();
}
- ///
- /// Updates a local with a remote copy.
- ///
- /// The local to update.
- /// The remote to update with.
- private void update(Room local, Room remote)
- {
- foreach (var pi in remote.Playlist)
- pi.MapObjects(beatmaps, rulesets);
-
- local.CopyFrom(remote);
- }
-
- ///
- /// Adds a to the list of available rooms.
- ///
- /// The to add.
- private void addRoom(Room room)
- {
- var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
- if (existing == null)
- rooms.Add(room);
- else
- existing.CopyFrom(room);
- }
-
- protected abstract IEnumerable CreatePollingComponents();
+ private void notifyRoomsUpdated() => Scheduler.AddOnce(() => RoomsUpdated?.Invoke());
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/RoomPollingComponent.cs
index b2ea3a05d6..cd224a7347 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomPollingComponent.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomPollingComponent.cs
@@ -1,29 +1,18 @@
// 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.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Online;
using osu.Game.Online.API;
-using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
public abstract class RoomPollingComponent : PollingComponent
{
- ///
- /// Invoked when any s have been received from the API.
- ///
- /// Any s present locally but not returned by this event are to be removed from display.
- /// If null, the display of local rooms is reset to an initial state.
- ///
- ///
- public Action> RoomsReceived;
-
[Resolved]
protected IAPIProvider API { get; private set; }
- protected void NotifyRoomsReceived(List rooms) => RoomsReceived?.Invoke(rooms);
+ [Resolved]
+ protected IRoomManager RoomManager { get; private set; }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
index dcf3c94b76..88d9469f8c 100644
--- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
@@ -1,8 +1,6 @@
// 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 System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -48,17 +46,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
pollReq.Success += result =>
{
- // existing rooms need to be ordered by their position because the received of NotifyRoomsReceives expects to be able to sort them based on this order.
- var rooms = new List(roomManager.Rooms.OrderBy(r => r.Position.Value));
-
- int index = rooms.FindIndex(r => r.RoomID.Value == result.RoomID.Value);
-
- if (index < 0)
- return;
-
- rooms[index] = result;
-
- NotifyRoomsReceived(rooms);
+ RoomManager.AddOrUpdateRoom(result);
tcs.SetResult(true);
};
diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
index a27b27b8ad..7b14acf924 100644
--- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
@@ -10,8 +10,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
-using osu.Game.Screens.Ranking.Expanded;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Components
@@ -64,8 +64,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
- minDisplay = new StarRatingDisplay(default),
- maxDisplay = new StarRatingDisplay(default)
+ minDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range),
+ maxDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range)
}
}
};
diff --git a/osu.Game/Screens/OnlinePlay/IRoomManager.cs b/osu.Game/Screens/OnlinePlay/IRoomManager.cs
index 34c1393ff1..6e1ffbda74 100644
--- a/osu.Game/Screens/OnlinePlay/IRoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/IRoomManager.cs
@@ -18,16 +18,29 @@ namespace osu.Game.Screens.OnlinePlay
///
event Action RoomsUpdated;
- ///
- /// Whether an initial listing of rooms has been received.
- ///
- IBindable InitialRoomsReceived { get; }
-
///
/// All the active s.
///
IBindableList Rooms { get; }
+ ///
+ /// Adds a to this .
+ /// If already existing, the local room will be updated with the given one.
+ ///
+ /// The incoming .
+ void AddOrUpdateRoom(Room room);
+
+ ///
+ /// Removes a from this .
+ ///
+ /// The to remove.
+ void RemoveRoom(Room room);
+
+ ///
+ /// Removes all s from this .
+ ///
+ void ClearRooms();
+
///
/// Creates a new .
///
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index c8ecd65574..106211c833 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -1,32 +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 System;
-using System.Collections.Generic;
-using osu.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
-using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.UserInterface;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Graphics.UserInterfaceV2;
-using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components;
@@ -35,105 +22,26 @@ using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
- public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler
+ public class DrawableRoom : CompositeDrawable
{
- public const float SELECTION_BORDER_WIDTH = 4;
- private const float corner_radius = 10;
- private const float transition_duration = 60;
+ protected const float CORNER_RADIUS = 10;
private const float height = 100;
- public event Action StateChanged;
-
- protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
-
- private Drawable selectionBox;
-
- [Resolved(canBeNull: true)]
- private LoungeSubScreen loungeScreen { get; set; }
+ public readonly Room Room;
[Resolved]
private BeatmapManager beatmaps { get; set; }
- [Resolved(canBeNull: true)]
- private Bindable selectedRoom { get; set; }
-
- [Resolved(canBeNull: true)]
- private LoungeSubScreen lounge { get; set; }
-
- public readonly Room Room;
-
- private SelectionState state;
-
- private Sample sampleSelect;
- private Sample sampleJoin;
-
- public SelectionState State
- {
- get => state;
- set
- {
- if (value == state)
- return;
-
- state = value;
-
- if (selectionBox != null)
- {
- if (state == SelectionState.Selected)
- selectionBox.FadeIn(transition_duration);
- else
- selectionBox.FadeOut(transition_duration);
- }
-
- StateChanged?.Invoke(State);
- }
- }
-
- public IEnumerable FilterTerms => new[] { Room.Name.Value };
-
- private bool matchingFilter;
-
- public bool MatchingFilter
- {
- get => matchingFilter;
- set
- {
- matchingFilter = value;
-
- if (!IsLoaded)
- return;
-
- if (matchingFilter)
- this.FadeIn(200);
- else
- Hide();
- }
- }
-
- private int numberOfAvatars = 7;
-
- public int NumberOfAvatars
- {
- get => numberOfAvatars;
- set
- {
- numberOfAvatars = value;
-
- if (recentParticipantsList != null)
- recentParticipantsList.NumberOfCircles = value;
- }
- }
+ protected Container ButtonsContainer { get; private set; }
+ private readonly Bindable roomType = new Bindable();
private readonly Bindable roomCategory = new Bindable();
+ private readonly Bindable hasPassword = new Bindable();
private RecentParticipantsList recentParticipantsList;
private RoomSpecialCategoryPill specialCategoryPill;
-
- public bool FilteringActive { get; set; }
-
private PasswordProtectedIcon passwordIcon;
-
- private readonly Bindable hasPassword = new Bindable();
+ private EndDateInfo endDateInfo;
public DrawableRoom(Room room)
{
@@ -143,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Height = height;
Masking = true;
- CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
+ CornerRadius = CORNER_RADIUS;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
@@ -153,9 +61,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
[BackgroundDependencyLoader]
- private void load(OverlayColourProvider colours, AudioManager audio)
+ private void load(OverlayColourProvider colours)
{
- Children = new Drawable[]
+ InternalChildren = new[]
{
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
new Box
@@ -163,10 +71,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
- new OnlinePlayBackgroundSprite
+ CreateBackground().With(d =>
{
- RelativeSizeAxes = Axes.Both
- },
+ d.RelativeSizeAxes = Axes.Both;
+ }),
new Container
{
Name = @"Room content",
@@ -177,7 +85,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
RelativeSizeAxes = Axes.Both,
Masking = true,
- CornerRadius = corner_radius,
+ CornerRadius = CORNER_RADIUS,
Children = new Drawable[]
{
new GridContainer
@@ -238,10 +146,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
- new EndDateInfo
+ endDateInfo = new EndDateInfo
{
Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft
+ Origin = Anchor.CentreLeft,
},
}
},
@@ -289,13 +197,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
+ Spacing = new Vector2(5),
Padding = new MarginPadding
{
Right = 10,
- Vertical = 5
+ Vertical = 20,
},
Children = new Drawable[]
{
+ ButtonsContainer = new Container
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X
+ },
recentParticipantsList = new RecentParticipantsList
{
Anchor = Anchor.CentreRight,
@@ -308,36 +224,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
},
},
},
- new StatusColouredContainer(transition_duration)
- {
- RelativeSizeAxes = Axes.Both,
- Child = selectionBox = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = state == SelectionState.Selected ? 1 : 0,
- Masking = true,
- CornerRadius = corner_radius,
- BorderThickness = SELECTION_BORDER_WIDTH,
- BorderColour = Color4.White,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
- }
- }
- },
- };
-
- sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
- sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
- }
-
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- return new CachedModelDependencyContainer(base.CreateChildDependencies(parent))
- {
- Model = { Value = Room }
};
}
@@ -345,11 +231,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
- if (matchingFilter)
- this.FadeInFromZero(transition_duration);
- else
- Alpha = 0;
-
roomCategory.BindTo(Room.Category);
roomCategory.BindValueChanged(c =>
{
@@ -359,62 +240,39 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
specialCategoryPill.Hide();
}, true);
+ roomType.BindTo(Room.Type);
+ roomType.BindValueChanged(t =>
+ {
+ endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
+ }, true);
+
hasPassword.BindTo(Room.HasPassword);
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
}
- public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join };
-
- public MenuItem[] ContextMenuItems => new MenuItem[]
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
- new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
+ return new CachedModelDependencyContainer(base.CreateChildDependencies(parent))
{
- lounge?.Open(Room.DeepClone());
- })
- };
-
- public bool OnPressed(GlobalAction action)
- {
- if (selectedRoom.Value != Room)
- return false;
-
- switch (action)
- {
- case GlobalAction.Select:
- TriggerClick();
- return true;
- }
-
- return false;
+ Model = { Value = Room }
+ };
}
- public void OnReleased(GlobalAction action)
+ private int numberOfAvatars = 7;
+
+ public int NumberOfAvatars
{
+ get => numberOfAvatars;
+ set
+ {
+ numberOfAvatars = value;
+
+ if (recentParticipantsList != null)
+ recentParticipantsList.NumberOfCircles = value;
+ }
}
- protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected || child is HoverSounds;
-
- protected override bool OnClick(ClickEvent e)
- {
- if (Room != selectedRoom.Value)
- {
- sampleSelect?.Play();
- selectedRoom.Value = Room;
- return true;
- }
-
- if (Room.HasPassword.Value)
- {
- sampleJoin?.Play();
- this.ShowPopover();
- return true;
- }
-
- sampleJoin?.Play();
- lounge?.Join(Room, null);
-
- return base.OnClick(e);
- }
+ protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite();
private class RoomNameText : OsuSpriteText
{
@@ -500,52 +358,5 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
};
}
}
-
- public class PasswordEntryPopover : OsuPopover
- {
- private readonly Room room;
-
- public Action JoinRequested;
-
- public PasswordEntryPopover(Room room)
- {
- this.room = room;
- }
-
- private OsuPasswordTextBox passwordTextbox;
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Child = new FillFlowContainer
- {
- Margin = new MarginPadding(10),
- Spacing = new Vector2(5),
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Children = new Drawable[]
- {
- passwordTextbox = new OsuPasswordTextBox
- {
- Width = 200,
- },
- new TriangleButton
- {
- Width = 80,
- Text = "Join Room",
- Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text)
- }
- }
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox));
- passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text);
- }
- }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index 46d9850fde..be5558ed0b 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -15,7 +15,6 @@ using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Extensions;
using osu.Game.Graphics.Cursor;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
using osuTK;
@@ -26,12 +25,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
private readonly IBindableList rooms = new BindableList();
- private readonly FillFlowContainer roomFlow;
+ private readonly FillFlowContainer roomFlow;
public IReadOnlyList Rooms => roomFlow.FlowingChildren.Cast().ToArray();
- [Resolved(CanBeNull = true)]
- private Bindable filter { get; set; }
+ public readonly Bindable Filter = new Bindable();
[Resolved]
private Bindable selectedRoom { get; set; }
@@ -57,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Child = roomFlow = new FillFlowContainer
+ Child = roomFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@@ -74,18 +72,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
rooms.BindTo(roomManager.Rooms);
- filter?.BindValueChanged(criteria => Filter(criteria.NewValue));
-
- selectedRoom.BindValueChanged(selection =>
- {
- updateSelection();
- }, true);
+ Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
}
- private void updateSelection() =>
- roomFlow.Children.ForEach(r => r.State = r.Room == selectedRoom.Value ? SelectionState.Selected : SelectionState.NotSelected);
-
- public void Filter(FilterCriteria criteria)
+ private void applyFilterCriteria(FilterCriteria criteria)
{
roomFlow.Children.ForEach(r =>
{
@@ -123,22 +113,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
foreach (var room in rooms)
{
- roomFlow.Add(new DrawableRoom(room));
+ roomFlow.Add(new DrawableLoungeRoom(room));
}
- Filter(filter?.Value);
-
- updateSelection();
+ applyFilterCriteria(Filter?.Value);
}
private void removeRooms(IEnumerable rooms)
{
foreach (var r in rooms)
{
- var toRemove = roomFlow.Single(d => d.Room == r);
- toRemove.Action = null;
-
- roomFlow.Remove(toRemove);
+ roomFlow.RemoveAll(d => d.Room == r);
// selection may have a lease due to being in a sub screen.
if (!selectedRoom.Disabled)
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
new file mode 100644
index 0000000000..70342680e2
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
@@ -0,0 +1,225 @@
+// 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.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Input.Bindings;
+using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge
+{
+ ///
+ /// A with lounge-specific interactions such as selection and hover sounds.
+ ///
+ public class DrawableLoungeRoom : DrawableRoom, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler
+ {
+ private const float transition_duration = 60;
+ private const float selection_border_width = 4;
+
+ [Resolved(canBeNull: true)]
+ private LoungeSubScreen lounge { get; set; }
+
+ [Resolved(canBeNull: true)]
+ private Bindable selectedRoom { get; set; }
+
+ private Sample sampleSelect;
+ private Sample sampleJoin;
+ private Drawable selectionBox;
+
+ public DrawableLoungeRoom(Room room)
+ : base(room)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
+ sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
+
+ AddRangeInternal(new Drawable[]
+ {
+ new StatusColouredContainer(transition_duration)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = selectionBox = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ Masking = true,
+ CornerRadius = CORNER_RADIUS,
+ BorderThickness = selection_border_width,
+ BorderColour = Color4.White,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ },
+ new HoverSounds()
+ });
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (matchingFilter)
+ this.FadeInFromZero(transition_duration);
+ else
+ Alpha = 0;
+
+ selectedRoom.BindValueChanged(updateSelectedRoom, true);
+ }
+
+ private void updateSelectedRoom(ValueChangedEvent selected)
+ {
+ if (selected.NewValue == Room)
+ selectionBox.FadeIn(transition_duration);
+ else
+ selectionBox.FadeOut(transition_duration);
+ }
+
+ public bool FilteringActive { get; set; }
+
+ public IEnumerable FilterTerms => new[] { Room.Name.Value };
+
+ private bool matchingFilter;
+
+ public bool MatchingFilter
+ {
+ get => matchingFilter;
+ set
+ {
+ matchingFilter = value;
+
+ if (!IsLoaded)
+ return;
+
+ if (matchingFilter)
+ this.FadeIn(200);
+ else
+ Hide();
+ }
+ }
+
+ public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join };
+
+ public MenuItem[] ContextMenuItems => new MenuItem[]
+ {
+ new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
+ {
+ lounge?.Open(Room.DeepClone());
+ })
+ };
+
+ public bool OnPressed(GlobalAction action)
+ {
+ if (selectedRoom.Value != Room)
+ return false;
+
+ switch (action)
+ {
+ case GlobalAction.Select:
+ TriggerClick();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ }
+
+ protected override bool ShouldBeConsideredForInput(Drawable child) => selectedRoom.Value == Room || child is HoverSounds;
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ if (Room != selectedRoom.Value)
+ {
+ sampleSelect?.Play();
+ selectedRoom.Value = Room;
+ return true;
+ }
+
+ if (Room.HasPassword.Value)
+ {
+ sampleJoin?.Play();
+ this.ShowPopover();
+ return true;
+ }
+
+ sampleJoin?.Play();
+ lounge?.Join(Room, null);
+ return true;
+ }
+
+ public class PasswordEntryPopover : OsuPopover
+ {
+ private readonly Room room;
+
+ public Action JoinRequested;
+
+ public PasswordEntryPopover(Room room)
+ {
+ this.room = room;
+ }
+
+ private OsuPasswordTextBox passwordTextbox;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ Margin = new MarginPadding(10),
+ Spacing = new Vector2(5),
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
+ {
+ passwordTextbox = new OsuPasswordTextBox
+ {
+ Width = 200,
+ },
+ new TriangleButton
+ {
+ Width = 80,
+ Text = "Join Room",
+ Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text)
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox));
+ passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
index e0e5cc415e..0664e44a73 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
@@ -13,14 +13,17 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Input;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Rulesets;
+using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Users;
@@ -42,10 +45,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
AutoSizeAxes = Axes.Both
};
- private readonly IBindable initialRoomsReceived = new Bindable();
- private readonly IBindable operationInProgress = new Bindable();
-
- private LoadingLayer loadingLayer;
+ protected ListingPollingComponent ListingPollingComponent { get; private set; }
[Resolved]
private Bindable selectedRoom { get; set; }
@@ -56,108 +56,103 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[Resolved(CanBeNull = true)]
private OngoingOperationTracker ongoingOperationTracker { get; set; }
- [Resolved(CanBeNull = true)]
- private Bindable filter { get; set; }
-
[Resolved]
private IBindable ruleset { get; set; }
[CanBeNull]
private IDisposable joiningRoomOperation { get; set; }
+ [CanBeNull]
+ private LeasedBindable selectionLease;
+
+ private readonly Bindable filter = new Bindable(new FilterCriteria());
+ private readonly IBindable operationInProgress = new Bindable();
+ private readonly IBindable isIdle = new BindableBool();
+ private LoadingLayer loadingLayer;
private RoomsContainer roomsContainer;
private SearchTextBox searchTextBox;
private Dropdown statusDropdown;
- [CanBeNull]
- private LeasedBindable selectionLease;
-
- [BackgroundDependencyLoader]
- private void load()
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] IdleTracker idleTracker)
{
- filter ??= new Bindable(new FilterCriteria());
+ const float controls_area_height = 25f;
+
+ if (idleTracker != null)
+ isIdle.BindTo(idleTracker.IsIdle);
OsuScrollContainer scrollContainer;
- InternalChildren = new[]
+ InternalChildren = new Drawable[]
{
- loadingLayer = new LoadingLayer(true),
+ ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter),
new Container
{
+ Name = @"Rooms area",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
- Left = WaveOverlayContainer.WIDTH_PADDING,
- Right = WaveOverlayContainer.WIDTH_PADDING,
+ Horizontal = WaveOverlayContainer.WIDTH_PADDING,
+ Top = Header.HEIGHT + controls_area_height + 20,
},
- Child = new GridContainer
+ Child = scrollContainer = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
- RowDimensions = new[]
+ ScrollbarOverlapsContent = false,
+ Child = roomsContainer = new RoomsContainer
{
- new Dimension(GridSizeMode.Absolute, Header.HEIGHT),
- new Dimension(GridSizeMode.Absolute, 25),
- new Dimension(GridSizeMode.Absolute, 20)
+ Filter = { BindTarget = filter }
+ }
+ },
+ },
+ loadingLayer = new LoadingLayer(true),
+ new FillFlowContainer
+ {
+ Name = @"Header area flow",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = Header.HEIGHT,
+ Child = searchTextBox = new LoungeSearchTextBox
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.X,
+ Width = 0.6f,
+ },
},
- Content = new[]
+ new Container
{
- new Drawable[]
+ RelativeSizeAxes = Axes.X,
+ Height = controls_area_height,
+ Children = new Drawable[]
{
- searchTextBox = new LoungeSearchTextBox
+ Buttons.WithChild(CreateNewRoomButton().With(d =>
{
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- RelativeSizeAxes = Axes.X,
- Width = 0.6f,
- },
- },
- new Drawable[]
- {
- new Container
+ d.Anchor = Anchor.BottomLeft;
+ d.Origin = Anchor.BottomLeft;
+ d.Size = new Vector2(150, 37.5f);
+ d.Action = () => Open();
+ })),
+ new FillFlowContainer
{
- RelativeSizeAxes = Axes.Both,
- Depth = float.MinValue, // Contained filters should appear over the top of rooms.
- Children = new Drawable[]
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10),
+ ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
{
- Buttons.WithChild(CreateNewRoomButton().With(d =>
- {
- d.Anchor = Anchor.BottomLeft;
- d.Origin = Anchor.BottomLeft;
- d.Size = new Vector2(150, 37.5f);
- d.Action = () => Open();
- })),
- new FillFlowContainer
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10),
- ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
- {
- d.Anchor = Anchor.TopRight;
- d.Origin = Anchor.TopRight;
- }))
- }
- }
+ d.Anchor = Anchor.TopRight;
+ d.Origin = Anchor.TopRight;
+ }))
}
- },
- null,
- new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- scrollContainer = new OsuScrollContainer
- {
- RelativeSizeAxes = Axes.Both,
- ScrollbarOverlapsContent = false,
- Child = roomsContainer = new RoomsContainer()
- },
- }
- },
}
}
},
@@ -180,21 +175,22 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced());
ruleset.BindValueChanged(_ => UpdateFilter());
- initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
- initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
+ isIdle.BindValueChanged(_ => updatePollingRate(this.IsCurrentScreen()), true);
if (ongoingOperationTracker != null)
{
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
- operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
+ operationInProgress.BindValueChanged(_ => updateLoadingLayer());
}
+ ListingPollingComponent.InitialRoomsReceived.BindValueChanged(_ => updateLoadingLayer(), true);
+
updateFilter();
}
#region Filtering
- protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
+ public void UpdateFilter() => Scheduler.AddOnce(updateFilter);
private ScheduledDelegate scheduledFilterUpdate;
@@ -235,7 +231,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
-
onReturning();
}
@@ -275,11 +270,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private void onReturning()
{
+ updatePollingRate(true);
searchTextBox.HoldFocus = true;
}
private void onLeaving()
{
+ updatePollingRate(false);
searchTextBox.HoldFocus = false;
// ensure any password prompt is dismissed.
@@ -327,6 +324,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
this.Push(CreateRoomSubScreen(room));
}
+ private void updateLoadingLayer()
+ {
+ if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value)
+ loadingLayer.Show();
+ else
+ loadingLayer.Hide();
+ }
+
+ private void updatePollingRate(bool isCurrentScreen)
+ {
+ if (!isCurrentScreen)
+ ListingPollingComponent.TimeBetweenPolls.Value = 0;
+ else
+ ListingPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 120000 : 15000;
+
+ Logger.Log($"Polling adjusted (listing: {ListingPollingComponent.TimeBetweenPolls.Value})");
+ }
+
protected abstract OsuButton CreateNewRoomButton();
///
@@ -337,13 +352,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
- private void updateLoadingLayer()
- {
- if (operationInProgress.Value || !initialRoomsReceived.Value)
- loadingLayer.Show();
- else
- loadingLayer.Hide();
- }
+ protected abstract ListingPollingComponent CreatePollingComponent();
private class LoungeSearchTextBox : SearchTextBox
{
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs b/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs
deleted file mode 100644
index e91c46beed..0000000000
--- a/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osu.Game.Screens.OnlinePlay.Playlists;
-using osuTK;
-
-namespace osu.Game.Screens.OnlinePlay.Match.Components
-{
- public class Footer : CompositeDrawable
- {
- public const float HEIGHT = 50;
-
- public Action OnStart;
-
- private readonly Drawable background;
-
- public Footer()
- {
- RelativeSizeAxes = Axes.X;
- Height = HEIGHT;
-
- InternalChildren = new[]
- {
- background = new Box { RelativeSizeAxes = Axes.Both },
- new PlaylistsReadyButton
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(600, 50),
- Action = () => OnStart?.Invoke()
- }
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- background.Colour = Color4Extensions.FromHex(@"28242d");
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/Header.cs b/osu.Game/Screens/OnlinePlay/Match/Components/Header.cs
deleted file mode 100644
index a2d11c54c1..0000000000
--- a/osu.Game/Screens/OnlinePlay/Match/Components/Header.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Users.Drawables;
-using osuTK;
-
-namespace osu.Game.Screens.OnlinePlay.Match.Components
-{
- public class Header : OnlinePlayComposite
- {
- public const float HEIGHT = 50;
-
- private UpdateableAvatar avatar;
- private LinkFlowContainer hostText;
-
- public Header()
- {
- RelativeSizeAxes = Axes.X;
- Height = HEIGHT;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- InternalChild = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- Children = new Drawable[]
- {
- avatar = new UpdateableAvatar
- {
- Size = new Vector2(50),
- Masking = true,
- CornerRadius = 10,
- },
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 30),
- Current = { BindTarget = RoomName }
- },
- hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20))
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- }
- }
- }
- }
- };
-
- Host.BindValueChanged(host =>
- {
- avatar.User = host.NewValue;
-
- hostText.Clear();
-
- if (host.NewValue != null)
- {
- hostText.AddText("hosted by ");
- hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
- }
- }, true);
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs
index a96d64cb5d..5f960c1b5c 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs
@@ -19,9 +19,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
[Resolved(CanBeNull = true)]
private ChannelManager channelManager { get; set; }
- public MatchChatDisplay()
+ private readonly bool leaveChannelOnDispose;
+
+ public MatchChatDisplay(bool leaveChannelOnDispose = true)
: base(true)
{
+ this.leaveChannelOnDispose = leaveChannelOnDispose;
}
protected override void LoadComplete()
@@ -42,7 +45,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
- channelManager?.LeaveChannel(Channel.Value);
+
+ if (leaveChannelOnDispose)
+ channelManager?.LeaveChannel(Channel.Value);
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
similarity index 89%
rename from osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs
rename to osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
index 2676453a7e..80a22880d4 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
@@ -14,10 +14,10 @@ using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Match.Components
{
- public abstract class MatchSettingsOverlay : FocusedOverlayContainer, IKeyBindingHandler
+ public abstract class RoomSettingsOverlay : FocusedOverlayContainer, IKeyBindingHandler
{
protected const float TRANSITION_DURATION = 350;
- protected const float FIELD_PADDING = 45;
+ protected const float FIELD_PADDING = 25;
protected OnlinePlayComposite Settings { get; set; }
@@ -27,11 +27,16 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected abstract bool IsLoading { get; }
+ protected RoomSettingsOverlay()
+ {
+ RelativeSizeAxes = Axes.Both;
+ Masking = true;
+ CornerRadius = 10;
+ }
+
[BackgroundDependencyLoader]
private void load()
{
- Masking = true;
-
Add(Settings = CreateSettings());
}
@@ -41,12 +46,16 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override void PopIn()
{
+ base.PopIn();
Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint);
+ Settings.FadeIn(TRANSITION_DURATION / 2);
}
protected override void PopOut()
{
+ base.PopOut();
Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine);
+ Settings.Delay(TRANSITION_DURATION / 2).FadeOut(TRANSITION_DURATION / 2);
}
public bool OnPressed(GlobalAction action)
diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs
new file mode 100644
index 0000000000..0924773338
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs
@@ -0,0 +1,64 @@
+// 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 JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Online.API;
+using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Match
+{
+ public class DrawableMatchRoom : DrawableRoom
+ {
+ public Action OnEdit;
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ private readonly IBindable host = new Bindable();
+ private readonly bool allowEdit;
+
+ [CanBeNull]
+ private Drawable editButton;
+
+ public DrawableMatchRoom(Room room, bool allowEdit = true)
+ : base(room)
+ {
+ this.allowEdit = allowEdit;
+
+ host.BindTo(room.Host);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ if (allowEdit)
+ {
+ ButtonsContainer.Add(editButton = new PurpleTriangleButton
+ {
+ RelativeSizeAxes = Axes.Y,
+ Size = new Vector2(100, 1),
+ Text = "Edit",
+ Action = () => OnEdit?.Invoke()
+ });
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (editButton != null)
+ host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true);
+ }
+
+ protected override Drawable CreateBackground() => new RoomBackgroundSprite();
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Match/RoomBackgroundSprite.cs
new file mode 100644
index 0000000000..97262dd229
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomBackgroundSprite.cs
@@ -0,0 +1,33 @@
+// 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.Game.Beatmaps.Drawables;
+
+namespace osu.Game.Screens.OnlinePlay.Match
+{
+ public class RoomBackgroundSprite : RoomSubScreenComposite
+ {
+ protected readonly BeatmapSetCoverType BeatmapSetCoverType;
+ private UpdateableBeatmapBackgroundSprite sprite;
+
+ public RoomBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
+ {
+ BeatmapSetCoverType = beatmapSetCoverType;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = sprite = new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ SelectedItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap.Value, true);
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index 243d2abf74..10df7cbadc 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -31,8 +31,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
public override bool DisallowExternalBeatmapRulesetChanges => true;
- private readonly ModSelectOverlay userModsSelectOverlay;
-
///
/// A container that provides controls for selection of user mods.
/// This will be shown/hidden automatically when applicable.
@@ -46,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
///
protected readonly Bindable> UserMods = new Bindable>(Array.Empty());
+ protected readonly IBindable RoomId = new Bindable();
+
[Resolved]
private MusicController music { get; set; }
@@ -58,55 +58,184 @@ namespace osu.Game.Screens.OnlinePlay.Match
private IBindable> managerUpdated;
[Cached]
- protected OnlinePlayBeatmapAvailabilityTracker BeatmapAvailabilityTracker { get; }
+ protected OnlinePlayBeatmapAvailabilityTracker BeatmapAvailabilityTracker { get; private set; }
protected IBindable BeatmapAvailability => BeatmapAvailabilityTracker.Availability;
- protected RoomSubScreen()
+ public readonly Room Room;
+ private readonly bool allowEdit;
+
+ private ModSelectOverlay userModsSelectOverlay;
+ private RoomSettingsOverlay settingsOverlay;
+ private Drawable mainContent;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The .
+ /// Whether to allow editing room settings post-creation.
+ protected RoomSubScreen(Room room, bool allowEdit = true)
{
+ Room = room;
+ this.allowEdit = allowEdit;
+
Padding = new MarginPadding { Top = Header.HEIGHT };
- AddRangeInternal(new Drawable[]
+ BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4Extensions.FromHex(@"3e3a44") // This is super temporary.
- },
- BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
- {
- SelectedItem = { BindTarget = SelectedItem }
- },
- new Container
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Depth = float.MinValue,
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
- Child = userModsSelectOverlay = new UserModSelectOverlay
- {
- SelectedMods = { BindTarget = UserMods },
- IsValidMod = _ => false
- }
- },
- });
- }
+ SelectedItem = { BindTarget = SelectedItem }
+ };
- protected override void ClearInternal(bool disposeChildren = true) =>
- throw new InvalidOperationException($"{nameof(RoomSubScreen)}'s children should not be cleared as it will remove required components");
+ RoomId.BindTo(room.RoomID);
+ }
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection");
+
+ InternalChildren = new Drawable[]
+ {
+ BeatmapAvailabilityTracker,
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ RowDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, 50)
+ },
+ Content = new[]
+ {
+ // Padded main content (drawable room + main content)
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding
+ {
+ Horizontal = WaveOverlayContainer.WIDTH_PADDING,
+ Bottom = 30
+ },
+ Children = new[]
+ {
+ mainContent = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Absolute, 10)
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new DrawableMatchRoom(Room, allowEdit)
+ {
+ OnEdit = () => settingsOverlay.Show()
+ }
+ },
+ null,
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = 10,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex(@"3e3a44") // Temporary.
+ },
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(20),
+ Child = CreateMainContent(),
+ },
+ new Container
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Child = userModsSelectOverlay = new UserModSelectOverlay
+ {
+ SelectedMods = { BindTarget = UserMods },
+ IsValidMod = _ => false
+ }
+ },
+ }
+ }
+ }
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ // Resolves 1px masking errors between the settings overlay and the room panel.
+ Padding = new MarginPadding(-1),
+ Child = settingsOverlay = CreateRoomSettingsOverlay()
+ }
+ },
+ },
+ },
+ // Footer
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex(@"28242d") // Temporary.
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(5),
+ Child = CreateFooter()
+ },
+ }
+ }
+ }
+ }
+ }
+ };
}
protected override void LoadComplete()
{
base.LoadComplete();
+ RoomId.BindValueChanged(id =>
+ {
+ if (id.NewValue == null)
+ {
+ // A new room is being created.
+ // The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
+ mainContent.Hide();
+ settingsOverlay.Show();
+ }
+ else
+ {
+ mainContent.Show();
+ settingsOverlay.Hide();
+ }
+ }, true);
+
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
@@ -117,12 +246,25 @@ namespace osu.Game.Screens.OnlinePlay.Match
public override bool OnBackButton()
{
+ if (Room.RoomID.Value == null)
+ {
+ // room has not been created yet; exit immediately.
+ settingsOverlay.Hide();
+ return base.OnBackButton();
+ }
+
if (userModsSelectOverlay.State.Value == Visibility.Visible)
{
userModsSelectOverlay.Hide();
return true;
}
+ if (settingsOverlay.State.Value == Visibility.Visible)
+ {
+ settingsOverlay.Hide();
+ return true;
+ }
+
return base.OnBackButton();
}
@@ -257,6 +399,21 @@ namespace osu.Game.Screens.OnlinePlay.Match
track.Looping = false;
}
+ ///
+ /// Creates the main centred content.
+ ///
+ protected abstract Drawable CreateMainContent();
+
+ ///
+ /// Creates the footer content.
+ ///
+ protected abstract Drawable CreateFooter();
+
+ ///
+ /// Creates the room settings overlay.
+ ///
+ protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay();
+
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
{
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
new file mode 100644
index 0000000000..9fe41842f3
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
@@ -0,0 +1,114 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Input.Bindings;
+using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer
+{
+ public class GameplayChatDisplay : MatchChatDisplay, IKeyBindingHandler
+ {
+ [Resolved]
+ private ILocalUserPlayInfo localUserInfo { get; set; }
+
+ private IBindable localUserPlaying = new Bindable();
+
+ public override bool PropagatePositionalInputSubTree => !localUserPlaying.Value;
+
+ public Bindable Expanded = new Bindable();
+
+ private readonly Bindable expandedFromTextboxFocus = new Bindable();
+
+ private const float height = 100;
+
+ public override bool PropagateNonPositionalInputSubTree => true;
+
+ public GameplayChatDisplay()
+ : base(leaveChannelOnDispose: false)
+ {
+ RelativeSizeAxes = Axes.X;
+
+ Background.Alpha = 0.2f;
+
+ Textbox.FocusLost = () => expandedFromTextboxFocus.Value = false;
+ }
+
+ protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor.
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
+ localUserPlaying.BindValueChanged(playing =>
+ {
+ // for now let's never hold focus. this avoid misdirected gameplay keys entering chat.
+ // note that this is done within this callback as it triggers an un-focus as well.
+ Textbox.HoldFocus = false;
+
+ // only hold focus (after sending a message) during breaks
+ Textbox.ReleaseFocusOnCommit = playing.NewValue;
+ }, true);
+
+ Expanded.BindValueChanged(_ => updateExpandedState(), true);
+ expandedFromTextboxFocus.BindValueChanged(focus =>
+ {
+ if (focus.NewValue)
+ updateExpandedState();
+ else
+ {
+ // on finishing typing a message there should be a brief delay before hiding.
+ using (BeginDelayedSequence(600))
+ updateExpandedState();
+ }
+ }, true);
+ }
+
+ public bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.ToggleChatFocus:
+ if (Textbox.HasFocus)
+ {
+ Schedule(() => Textbox.KillFocus());
+ }
+ else
+ {
+ expandedFromTextboxFocus.Value = true;
+
+ // schedule required to ensure the textbox has become present from above bindable update.
+ Schedule(() => Textbox.TakeFocus());
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ }
+
+ private void updateExpandedState()
+ {
+ if (Expanded.Value || expandedFromTextboxFocus.Value)
+ {
+ this.FadeIn(300, Easing.OutQuint);
+ this.ResizeHeightTo(height, 500, Easing.OutQuint);
+ }
+ else
+ {
+ this.FadeOut(300, Easing.OutQuint);
+ this.ResizeHeightTo(0, 500, Easing.OutQuint);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
index 56b87302c2..2e94e51385 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Screens.OnlinePlay.Match.Components;
+using osuTK;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
@@ -35,6 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
+ Spacing = new Vector2(5),
Children = new Drawable[]
{
beatmapPanelContainer = new Container
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs
index d4f5428bfb..036e37ddfd 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs
@@ -2,18 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public class MultiplayerMatchFooter : CompositeDrawable
{
- public const float HEIGHT = 50;
private const float ready_button_width = 600;
private const float spectate_button_width = 200;
@@ -27,54 +22,42 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
set => spectateButton.OnSpectateClick = value;
}
- private readonly Drawable background;
private readonly MultiplayerReadyButton readyButton;
private readonly MultiplayerSpectateButton spectateButton;
public MultiplayerMatchFooter()
{
- RelativeSizeAxes = Axes.X;
- Height = HEIGHT;
+ RelativeSizeAxes = Axes.Both;
- InternalChildren = new[]
+ InternalChild = new GridContainer
{
- background = new Box { RelativeSizeAxes = Axes.Both },
- new GridContainer
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
{
- RelativeSizeAxes = Axes.Both,
- Content = new[]
+ new Drawable[]
{
- new Drawable[]
+ null,
+ spectateButton = new MultiplayerSpectateButton
{
- null,
- spectateButton = new MultiplayerSpectateButton
- {
- RelativeSizeAxes = Axes.Both,
- },
- null,
- readyButton = new MultiplayerReadyButton
- {
- RelativeSizeAxes = Axes.Both,
- },
- null
- }
- },
- ColumnDimensions = new[]
- {
- new Dimension(),
- new Dimension(maxSize: spectate_button_width),
- new Dimension(GridSizeMode.Absolute, 10),
- new Dimension(maxSize: ready_button_width),
- new Dimension()
+ RelativeSizeAxes = Axes.Both,
+ },
+ null,
+ readyButton = new MultiplayerReadyButton
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ null
}
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(maxSize: spectate_button_width),
+ new Dimension(GridSizeMode.Absolute, 5),
+ new Dimension(maxSize: ready_button_width),
+ new Dimension()
}
};
}
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- background.Colour = Color4Extensions.FromHex(@"28242d");
- }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchHeader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchHeader.cs
deleted file mode 100644
index bb351d06d3..0000000000
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchHeader.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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.Framework.Graphics.Containers;
-using osu.Framework.Graphics.UserInterface;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Online.API;
-using osu.Game.Screens.OnlinePlay.Match.Components;
-using osu.Game.Users.Drawables;
-using osuTK;
-using FontWeight = osu.Game.Graphics.FontWeight;
-using OsuColour = osu.Game.Graphics.OsuColour;
-using OsuFont = osu.Game.Graphics.OsuFont;
-
-namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
-{
- public class MultiplayerMatchHeader : OnlinePlayComposite
- {
- public const float HEIGHT = 50;
-
- public Action OpenSettings;
-
- private UpdateableAvatar avatar;
- private LinkFlowContainer hostText;
- private Button openSettingsButton;
-
- [Resolved]
- private IAPIProvider api { get; set; }
-
- public MultiplayerMatchHeader()
- {
- RelativeSizeAxes = Axes.X;
- Height = HEIGHT;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- InternalChildren = new Drawable[]
- {
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- Children = new Drawable[]
- {
- avatar = new UpdateableAvatar
- {
- Size = new Vector2(50),
- Masking = true,
- CornerRadius = 10,
- },
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 30),
- Current = { BindTarget = RoomName }
- },
- hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20))
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- }
- }
- }
- }
- },
- openSettingsButton = new PurpleTriangleButton
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Size = new Vector2(150, HEIGHT),
- Text = "Open settings",
- Action = () => OpenSettings?.Invoke(),
- Alpha = 0
- }
- };
-
- Host.BindValueChanged(host =>
- {
- avatar.User = host.NewValue;
-
- hostText.Clear();
-
- if (host.NewValue != null)
- {
- hostText.AddText("hosted by ");
- hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
- }
-
- openSettingsButton.Alpha = host.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0;
- }, true);
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
index 5f3921d742..930b92c528 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
@@ -26,7 +26,7 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
- public class MultiplayerMatchSettingsOverlay : MatchSettingsOverlay
+ public class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay
{
private MatchSettings settings;
@@ -150,6 +150,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
+ LengthLimit = 100,
},
},
new Section("Room visibility")
@@ -207,6 +208,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
+ LengthLimit = 255,
},
},
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
index 45928505bb..58b5b7bbeb 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay.Components;
@@ -23,35 +22,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
client.ChangeState(MultiplayerUserState.Idle);
}
- protected override void UpdatePollingRate(bool isIdle)
- {
- var multiplayerRoomManager = (MultiplayerRoomManager)RoomManager;
-
- if (!this.IsCurrentScreen())
- {
- multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
- multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
- }
- else
- {
- switch (CurrentSubScreen)
- {
- case LoungeSubScreen _:
- multiplayerRoomManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000;
- multiplayerRoomManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000;
- break;
-
- // Don't poll inside the match or anywhere else.
- default:
- multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
- multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
- break;
- }
- }
-
- Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})");
- }
-
protected override string ScreenTitle => "Multiplayer";
protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager();
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
index ad7882abc2..6c3dfe7382 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
@@ -1,12 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Threading.Tasks;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Logging;
+using osu.Framework.Screens;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
@@ -21,6 +25,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private MultiplayerClient client { get; set; }
+ public override void OnResuming(IScreen last)
+ {
+ base.OnResuming(last);
+
+ // Upon having left a room, we don't know whether we were the only participant, and whether the room is now closed as a result of leaving it.
+ // To work around this, temporarily remove the room and trigger an immediate listing poll.
+ if (last is MultiplayerMatchSubScreen match)
+ {
+ RoomManager.RemoveRoom(match.Room);
+ ListingPollingComponent.PollImmediately();
+ }
+ }
+
protected override FilterCriteria CreateFilterCriteria()
{
var criteria = base.CreateFilterCriteria();
@@ -33,12 +50,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Room CreateNewRoom() => new Room
{
Name = { Value = $"{api.LocalUser}'s awesome room" },
- Category = { Value = RoomCategory.Realtime },
Type = { Value = MatchType.HeadToHead },
};
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
+ protected override ListingPollingComponent CreatePollingComponent() => new MultiplayerListingPollingComponent();
+
protected override void OpenNewRoom(Room room)
{
if (client?.IsConnected.Value != true)
@@ -49,5 +67,32 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
base.OpenNewRoom(room);
}
+
+ private class MultiplayerListingPollingComponent : ListingPollingComponent
+ {
+ [Resolved]
+ private MultiplayerClient client { get; set; }
+
+ private readonly IBindable isConnected = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ isConnected.BindTo(client.IsConnected);
+ isConnected.BindValueChanged(c => Scheduler.AddOnce(() =>
+ {
+ if (isConnected.Value && IsLoaded)
+ PollImmediately();
+ }), true);
+ }
+
+ protected override Task Poll()
+ {
+ if (!isConnected.Value)
+ return Task.CompletedTask;
+
+ return base.Poll();
+ }
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 1943ff668f..3a012787a4 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -49,215 +49,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private OngoingOperationTracker ongoingOperationTracker { get; set; }
[Resolved]
- private Bindable currentRoom { get; set; }
-
- private MultiplayerMatchSettingsOverlay settingsOverlay;
+ private Bindable currentRoom { get; set; } // Todo: This should not exist.
private readonly IBindable isConnected = new Bindable();
[CanBeNull]
private IDisposable readyClickOperation;
- private GridContainer mainContent;
-
public MultiplayerMatchSubScreen(Room room)
+ : base(room)
{
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
Activity.Value = new UserActivity.InLobby(room);
}
- [BackgroundDependencyLoader]
- private void load()
- {
- AddRangeInternal(new Drawable[]
- {
- mainContent = new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding
- {
- Horizontal = HORIZONTAL_OVERFLOW_PADDING + 55,
- Vertical = 20
- },
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(),
- },
- Content = new[]
- {
- new Drawable[]
- {
- new MultiplayerMatchHeader
- {
- OpenSettings = () => settingsOverlay.Show()
- }
- },
- new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- ColumnDimensions = new[]
- {
- new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
- new Dimension(),
- new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
- },
- Content = new[]
- {
- new Drawable[]
- {
- // Main left column
- new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize)
- },
- Content = new[]
- {
- new Drawable[] { new ParticipantsListHeader() },
- new Drawable[]
- {
- new ParticipantsList
- {
- RelativeSizeAxes = Axes.Both
- },
- }
- }
- },
- // Spacer
- null,
- // Main right column
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new[]
- {
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- new OverlinedHeader("Beatmap"),
- new BeatmapSelectionControl { RelativeSizeAxes = Axes.X }
- }
- },
- UserModsSection = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Margin = new MarginPadding { Top = 10 },
- Children = new Drawable[]
- {
- new OverlinedHeader("Extra mods"),
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- Children = new Drawable[]
- {
- new UserModSelectButton
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Width = 90,
- Text = "Select",
- Action = ShowUserModSelect,
- },
- new ModDisplay
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Current = UserMods,
- Scale = new Vector2(0.8f),
- },
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- },
- new Drawable[]
- {
- new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize)
- },
- Content = new[]
- {
- new Drawable[] { new OverlinedHeader("Chat") },
- new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
- }
- }
- }
- },
- }
- }
- },
- new Drawable[]
- {
- new MultiplayerMatchFooter
- {
- OnReadyClick = onReadyClick,
- OnSpectateClick = onSpectateClick
- }
- }
- },
- RowDimensions = new[]
- {
- new Dimension(),
- new Dimension(GridSizeMode.AutoSize),
- }
- },
- settingsOverlay = new MultiplayerMatchSettingsOverlay
- {
- RelativeSizeAxes = Axes.Both,
- State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden }
- }
- });
-
- if (client.Room == null)
- {
- // A new room is being created.
- // The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
- mainContent.Hide();
-
- settingsOverlay.State.BindValueChanged(visibility =>
- {
- if (visibility.NewValue == Visibility.Hidden)
- mainContent.Show();
- }, true);
- }
- }
-
protected override void LoadComplete()
{
base.LoadComplete();
@@ -289,6 +94,126 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}, true);
}
+ protected override Drawable CreateMainContent() => new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
+ new Dimension(),
+ new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ // Main left column
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize)
+ },
+ Content = new[]
+ {
+ new Drawable[] { new ParticipantsListHeader() },
+ new Drawable[]
+ {
+ new ParticipantsList
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ }
+ }
+ },
+ // Spacer
+ null,
+ // Main right column
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[] { new OverlinedHeader("Beatmap") },
+ new Drawable[] { new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } },
+ new[]
+ {
+ UserModsSection = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Margin = new MarginPadding { Top = 10 },
+ Alpha = 0,
+ Children = new Drawable[]
+ {
+ new OverlinedHeader("Extra mods"),
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10, 0),
+ Children = new Drawable[]
+ {
+ new UserModSelectButton
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Width = 90,
+ Text = "Select",
+ Action = ShowUserModSelect,
+ },
+ new ModDisplay
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Current = UserMods,
+ Scale = new Vector2(0.8f),
+ },
+ }
+ },
+ }
+ },
+ },
+ new Drawable[] { new OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, },
+ new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
+ },
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(),
+ }
+ },
+ }
+ }
+ }
+ }
+ },
+ },
+ };
+
+ protected override Drawable CreateFooter() => new MultiplayerMatchFooter
+ {
+ OnReadyClick = onReadyClick,
+ OnSpectateClick = onSpectateClick
+ };
+
+ protected override RoomSettingsOverlay CreateRoomSettingsOverlay() => new MultiplayerMatchSettingsOverlay();
+
protected override void UpdateMods()
{
if (SelectedItem.Value == null || client.LocalUser == null)
@@ -305,23 +230,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private bool exitConfirmed;
- public override bool OnBackButton()
- {
- if (client.Room == null)
- {
- // room has not been created yet; exit immediately.
- return base.OnBackButton();
- }
-
- if (settingsOverlay.State.Value == Visibility.Visible)
- {
- settingsOverlay.Hide();
- return true;
- }
-
- return base.OnBackButton();
- }
-
public override bool OnExiting(IScreen next)
{
// the room may not be left immediately after a disconnection due to async flow,
@@ -505,19 +413,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
}
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (client != null)
- {
- client.RoomUpdated -= onRoomUpdated;
- client.LoadRequested -= onLoadRequested;
- }
-
- modSettingChangeTracker?.Dispose();
- }
-
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
{
if (!this.IsCurrentScreen())
@@ -533,5 +428,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
this.Push(new MultiplayerMatchSongSelect(beatmap, ruleset));
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (client != null)
+ {
+ client.RoomUpdated -= onRoomUpdated;
+ client.LoadRequested -= onLoadRequested;
+ }
+
+ modSettingChangeTracker?.Dispose();
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
index ca1a3710ab..24657943d7 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
@@ -68,6 +68,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
+ Spacing = new Vector2(5)
});
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
@@ -78,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
((IBindable)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
- leaderboardFlow.Add(l);
+ leaderboardFlow.Insert(0, l);
if (leaderboard.TeamScores.Count >= 2)
{
@@ -87,10 +88,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
Expanded = { BindTarget = HUDOverlay.ShowHud },
- }, leaderboardFlow.Add);
+ }, scoreDisplay => leaderboardFlow.Insert(1, scoreDisplay));
}
});
+ LoadComponentAsync(new GameplayChatDisplay
+ {
+ Expanded = { BindTarget = HUDOverlay.ShowHud },
+ }, chat => leaderboardFlow.Insert(2, chat));
+
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs
index cbba4babe5..2d94b2328d 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs
@@ -2,11 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Threading.Tasks;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Logging;
using osu.Game.Online.Multiplayer;
@@ -21,22 +18,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private MultiplayerClient multiplayerClient { get; set; }
- public readonly Bindable TimeBetweenListingPolls = new Bindable();
- public readonly Bindable TimeBetweenSelectionPolls = new Bindable();
- private readonly IBindable isConnected = new Bindable();
- private readonly Bindable allowPolling = new Bindable();
-
- private ListingPollingComponent listingPollingComponent;
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- isConnected.BindTo(multiplayerClient.IsConnected);
- isConnected.BindValueChanged(_ => Scheduler.AddOnce(updatePolling));
- JoinedRoom.BindValueChanged(_ => Scheduler.AddOnce(updatePolling), true);
- }
-
public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
=> base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError);
@@ -64,19 +45,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (JoinedRoom.Value == null)
return;
- var joinedRoom = JoinedRoom.Value;
-
base.PartRoom();
-
multiplayerClient.LeaveRoom();
-
- // Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case.
- // This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling.
- Schedule(() =>
- {
- RemoveRoom(joinedRoom);
- listingPollingComponent.PollImmediately();
- });
}
private void joinMultiplayerRoom(Room room, string password, Action onSuccess = null, Action onError = null)
@@ -99,70 +69,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
});
}
-
- private void updatePolling()
- {
- if (!isConnected.Value)
- ClearRooms();
-
- // Don't poll when not connected or when a room has been joined.
- allowPolling.Value = isConnected.Value && JoinedRoom.Value == null;
- }
-
- protected override IEnumerable CreatePollingComponents() => new RoomPollingComponent[]
- {
- listingPollingComponent = new MultiplayerListingPollingComponent
- {
- TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls },
- AllowPolling = { BindTarget = allowPolling }
- },
- new MultiplayerSelectionPollingComponent
- {
- TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls },
- AllowPolling = { BindTarget = allowPolling }
- }
- };
-
- private class MultiplayerListingPollingComponent : ListingPollingComponent
- {
- public readonly IBindable AllowPolling = new Bindable();
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- AllowPolling.BindValueChanged(allowPolling =>
- {
- if (!allowPolling.NewValue)
- return;
-
- if (IsLoaded)
- PollImmediately();
- });
- }
-
- protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll();
- }
-
- private class MultiplayerSelectionPollingComponent : SelectionPollingComponent
- {
- public readonly IBindable AllowPolling = new Bindable();
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- AllowPolling.BindValueChanged(allowPolling =>
- {
- if (!allowPolling.NewValue)
- return;
-
- if (IsLoaded)
- PollImmediately();
- });
- }
-
- protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll();
- }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs
index 2c157b0564..ececa1e497 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// The score containing the player's replay.
/// The clock controlling the gameplay running state.
public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock)
- : base(score)
+ : base(score, new PlayerConfiguration { AllowUserInteraction = false })
{
this.spectatorPlayerClock = spectatorPlayerClock;
}
@@ -34,6 +34,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private void load()
{
spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames);
+
+ HUDOverlay.PlayerSettingsOverlay.Expire();
+ HUDOverlay.HoldToQuit.Expire();
}
protected override void UpdateAfterChildren()
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs
index 5a1d28e9c4..14bd8fa6dc 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs
@@ -3,6 +3,7 @@
using System;
using JetBrains.Annotations;
+using osu.Framework.Allocation;
using osu.Game.Scoring;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
@@ -19,6 +20,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ PlayerSettings.Expire();
+ }
+
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index fd265e9978..e5962db608 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -14,14 +14,12 @@ using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
-using osu.Game.Input;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
@@ -44,17 +42,12 @@ namespace osu.Game.Screens.OnlinePlay
private LoungeSubScreen loungeSubScreen;
private ScreenStack screenStack;
- private readonly IBindable isIdle = new BindableBool();
-
[Cached(Type = typeof(IRoomManager))]
protected RoomManager RoomManager { get; private set; }
[Cached]
private readonly Bindable selectedRoom = new Bindable();
- [Cached]
- private readonly Bindable currentFilter = new Bindable(new FilterCriteria());
-
[Cached]
private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
@@ -67,9 +60,6 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved]
protected IAPIProvider API { get; private set; }
- [Resolved(CanBeNull = true)]
- private IdleTracker idleTracker { get; set; }
-
[Resolved(CanBeNull = true)]
private OsuLogo logo { get; set; }
@@ -147,12 +137,6 @@ namespace osu.Game.Screens.OnlinePlay
apiState.BindTo(API.State);
apiState.BindValueChanged(onlineStateChanged, true);
-
- if (idleTracker != null)
- {
- isIdle.BindTo(idleTracker.IsIdle);
- isIdle.BindValueChanged(idle => UpdatePollingRate(idle.NewValue), true);
- }
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@@ -162,8 +146,6 @@ namespace osu.Game.Screens.OnlinePlay
return dependencies;
}
- protected abstract void UpdatePollingRate(bool isIdle);
-
private void forcefullyExit()
{
// This is temporary since we don't currently have a way to force screens to be exited
@@ -199,8 +181,6 @@ namespace osu.Game.Screens.OnlinePlay
screenStack.CurrentScreen.OnResuming(last);
base.OnResuming(last);
-
- UpdatePollingRate(isIdle.Value);
}
public override void OnSuspending(IScreen next)
@@ -210,8 +190,6 @@ namespace osu.Game.Screens.OnlinePlay
Debug.Assert(screenStack.CurrentScreen != null);
screenStack.CurrentScreen.OnSuspending(next);
-
- UpdatePollingRate(isIdle.Value);
}
public override bool OnExiting(IScreen next)
@@ -275,15 +253,13 @@ namespace osu.Game.Screens.OnlinePlay
if (newScreen is IOsuScreen newOsuScreen)
((IBindable)Activity).BindTo(newOsuScreen.Activity);
-
- UpdatePollingRate(isIdle.Value);
}
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
protected abstract string ScreenTitle { get; }
- protected abstract RoomManager CreateRoomManager();
+ protected virtual RoomManager CreateRoomManager() => new RoomManager();
protected abstract LoungeSubScreen CreateLounge();
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs
index e1bd889088..49cc77a5d8 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs
@@ -23,12 +23,6 @@ namespace osu.Game.Screens.OnlinePlay
RelativeSizeAxes = Axes.Both;
}
- public const float X_SHIFT = 200;
-
- public const double X_MOVE_DURATION = 800;
-
- public const double RESUME_TRANSITION_DELAY = DISAPPEAR_DURATION / 2;
-
public const double APPEAR_DURATION = 800;
public const double DISAPPEAR_DURATION = 500;
@@ -36,28 +30,23 @@ namespace osu.Game.Screens.OnlinePlay
public override void OnEntering(IScreen last)
{
this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint);
- this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint);
- this.MoveToX(X_SHIFT).MoveToX(0, X_MOVE_DURATION, Easing.OutQuint);
}
public override bool OnExiting(IScreen next)
{
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
- this.MoveToX(X_SHIFT, X_MOVE_DURATION, Easing.OutQuint);
return false;
}
public override void OnResuming(IScreen last)
{
- this.Delay(RESUME_TRANSITION_DELAY).FadeIn(APPEAR_DURATION, Easing.OutQuint);
- this.MoveToX(0, X_MOVE_DURATION, Easing.OutQuint);
+ this.FadeIn(APPEAR_DURATION, Easing.OutQuint);
}
public override void OnSuspending(IScreen next)
{
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
- this.MoveToX(-X_SHIFT, X_MOVE_DURATION, Easing.OutQuint);
}
public override string ToString() => Title;
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs
index 6a78e24ba1..1edeef77df 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs
@@ -1,53 +1,14 @@
// 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.Logging;
-using osu.Framework.Screens;
-using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
-using osu.Game.Screens.OnlinePlay.Match;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
public class Playlists : OnlinePlayScreen
{
- protected override void UpdatePollingRate(bool isIdle)
- {
- var playlistsManager = (PlaylistsRoomManager)RoomManager;
-
- if (!this.IsCurrentScreen())
- {
- playlistsManager.TimeBetweenListingPolls.Value = 0;
- playlistsManager.TimeBetweenSelectionPolls.Value = 0;
- }
- else
- {
- switch (CurrentSubScreen)
- {
- case LoungeSubScreen _:
- playlistsManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000;
- playlistsManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000;
- break;
-
- case RoomSubScreen _:
- playlistsManager.TimeBetweenListingPolls.Value = 0;
- playlistsManager.TimeBetweenSelectionPolls.Value = isIdle ? 30000 : 5000;
- break;
-
- default:
- playlistsManager.TimeBetweenListingPolls.Value = 0;
- playlistsManager.TimeBetweenSelectionPolls.Value = 0;
- break;
- }
- }
-
- Logger.Log($"Polling adjusted (listing: {playlistsManager.TimeBetweenListingPolls.Value}, selection: {playlistsManager.TimeBetweenSelectionPolls.Value})");
- }
-
protected override string ScreenTitle => "Playlists";
- protected override RoomManager CreateRoomManager() => new PlaylistsRoomManager();
-
protected override LoungeSubScreen CreateLounge() => new PlaylistsLoungeSubScreen();
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs
index eee4d4f407..dced9b8691 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
@@ -66,6 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new PlaylistsRoomSubScreen(room);
+ protected override ListingPollingComponent CreatePollingComponent() => new ListingPollingComponent();
+
private enum PlaylistsCategory
{
Any,
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs
new file mode 100644
index 0000000000..3eb1cde0a4
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs
@@ -0,0 +1,32 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Playlists
+{
+ public class PlaylistsRoomFooter : CompositeDrawable
+ {
+ public Action OnStart;
+
+ public PlaylistsRoomFooter()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new[]
+ {
+ new PlaylistsReadyButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Size = new Vector2(600, 1),
+ Action = () => OnStart?.Invoke()
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomManager.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomManager.cs
deleted file mode 100644
index c55d1c3e94..0000000000
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomManager.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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.Bindables;
-using osu.Game.Screens.OnlinePlay.Components;
-
-namespace osu.Game.Screens.OnlinePlay.Playlists
-{
- public class PlaylistsRoomManager : RoomManager
- {
- public readonly Bindable TimeBetweenListingPolls = new Bindable();
- public readonly Bindable TimeBetweenSelectionPolls = new Bindable();
-
- protected override IEnumerable CreatePollingComponents() => new RoomPollingComponent[]
- {
- new ListingPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls } },
- new SelectionPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls } }
- };
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs
similarity index 99%
rename from osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs
rename to osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs
index 2640f99ea5..3eea59006a 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs
@@ -22,7 +22,7 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
- public class PlaylistsMatchSettingsOverlay : MatchSettingsOverlay
+ public class PlaylistsRoomSettingsOverlay : RoomSettingsOverlay
{
public Action EditPlaylist;
@@ -193,7 +193,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
- Height = 500,
+ Height = 448,
Content = new[]
{
new Drawable[]
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index 953c687087..729ddbef8c 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -3,11 +3,14 @@
using System.Diagnostics;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Logging;
using osu.Framework.Screens;
+using osu.Game.Input;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
@@ -17,7 +20,6 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osuTK;
-using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
@@ -27,254 +29,176 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public override string ShortTitle => "playlist";
- [Resolved(typeof(Room), nameof(Room.RoomID))]
- private Bindable roomId { get; set; }
+ [Resolved]
+ private IAPIProvider api { get; set; }
- [Resolved(typeof(Room), nameof(Room.Playlist))]
- private BindableList playlist { get; set; }
+ private readonly IBindable isIdle = new BindableBool();
- private MatchSettingsOverlay settingsOverlay;
private MatchLeaderboard leaderboard;
-
- private OverlinedHeader participantsHeader;
-
- private GridContainer mainContent;
+ private SelectionPollingComponent selectionPollingComponent;
public PlaylistsRoomSubScreen(Room room)
+ : base(room, false) // Editing is temporarily not allowed.
{
Title = room.RoomID.Value == null ? "New playlist" : room.Name.Value;
Activity.Value = new UserActivity.InLobby(room);
}
- [BackgroundDependencyLoader]
- private void load()
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] IdleTracker idleTracker)
{
- AddRangeInternal(new Drawable[]
- {
- mainContent = new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding
- {
- Horizontal = 105,
- Vertical = 20
- },
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(),
- },
- Content = new[]
- {
- new Drawable[] { new Match.Components.Header() },
- new Drawable[]
- {
- participantsHeader = new OverlinedHeader("Participants")
- {
- ShowLine = false
- }
- },
- new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Margin = new MarginPadding { Top = 5 },
- Child = new ParticipantsDisplay(Direction.Horizontal)
- {
- Details = { BindTarget = participantsHeader.Details }
- }
- }
- },
- new Drawable[]
- {
- new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Right = 5 },
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[] { new OverlinedPlaylistHeader(), },
- new Drawable[]
- {
- new DrawableRoomPlaylistWithResults
- {
- RelativeSizeAxes = Axes.Both,
- Items = { BindTarget = playlist },
- SelectedItem = { BindTarget = SelectedItem },
- RequestShowResults = item =>
- {
- Debug.Assert(roomId.Value != null);
- ParentScreen?.Push(new PlaylistsResultsScreen(null, roomId.Value.Value, item, false));
- }
- }
- },
- },
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(),
- }
- }
- },
- null,
- new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new[]
- {
- UserModsSection = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Margin = new MarginPadding { Bottom = 10 },
- Children = new Drawable[]
- {
- new OverlinedHeader("Extra mods"),
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- Children = new Drawable[]
- {
- new UserModSelectButton
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Width = 90,
- Text = "Select",
- Action = ShowUserModSelect,
- },
- new ModDisplay
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Current = UserMods,
- Scale = new Vector2(0.8f),
- },
- }
- }
- }
- },
- },
- new Drawable[]
- {
- new OverlinedHeader("Leaderboard")
- },
- new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
- new Drawable[] { new OverlinedHeader("Chat"), },
- new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
- },
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(),
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120),
- }
- },
- null
- },
- },
- ColumnDimensions = new[]
- {
- new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
- new Dimension(),
- new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
- new Dimension(),
- }
- }
- }
- },
- }
- }
- },
- new Drawable[]
- {
- new Footer { OnStart = StartPlay }
- }
- },
- RowDimensions = new[]
- {
- new Dimension(),
- new Dimension(GridSizeMode.AutoSize),
- }
- },
- settingsOverlay = new PlaylistsMatchSettingsOverlay
- {
- RelativeSizeAxes = Axes.Both,
- EditPlaylist = () =>
- {
- if (this.IsCurrentScreen())
- this.Push(new PlaylistsSongSelect());
- },
- State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden }
- }
- });
+ if (idleTracker != null)
+ isIdle.BindTo(idleTracker.IsIdle);
- if (roomId.Value == null)
- {
- // A new room is being created.
- // The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
- mainContent.Hide();
-
- settingsOverlay.State.BindValueChanged(visibility =>
- {
- if (visibility.NewValue == Visibility.Hidden)
- mainContent.Show();
- }, true);
- }
+ AddInternal(selectionPollingComponent = new SelectionPollingComponent());
}
- [Resolved]
- private IAPIProvider api { get; set; }
-
protected override void LoadComplete()
{
base.LoadComplete();
- roomId.BindValueChanged(id =>
+ isIdle.BindValueChanged(_ => updatePollingRate(), true);
+ RoomId.BindValueChanged(id =>
{
- if (id.NewValue == null)
- settingsOverlay.Show();
- else
+ if (id.NewValue != null)
{
- settingsOverlay.Hide();
-
// Set the first playlist item.
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
- Schedule(() => SelectedItem.Value = playlist.FirstOrDefault());
+ Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault());
}
}, true);
}
+ protected override Drawable CreateMainContent() => new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Right = 5 },
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[] { new OverlinedPlaylistHeader(), },
+ new Drawable[]
+ {
+ new DrawableRoomPlaylistWithResults
+ {
+ RelativeSizeAxes = Axes.Both,
+ Items = { BindTarget = Room.Playlist },
+ SelectedItem = { BindTarget = SelectedItem },
+ RequestShowResults = item =>
+ {
+ Debug.Assert(RoomId.Value != null);
+ ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
+ }
+ }
+ },
+ },
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(),
+ }
+ }
+ },
+ null,
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new[]
+ {
+ UserModsSection = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Alpha = 0,
+ Margin = new MarginPadding { Bottom = 10 },
+ Children = new Drawable[]
+ {
+ new OverlinedHeader("Extra mods"),
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10, 0),
+ Children = new Drawable[]
+ {
+ new UserModSelectButton
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Width = 90,
+ Text = "Select",
+ Action = ShowUserModSelect,
+ },
+ new ModDisplay
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Current = UserMods,
+ Scale = new Vector2(0.8f),
+ },
+ }
+ }
+ }
+ },
+ },
+ new Drawable[]
+ {
+ new OverlinedHeader("Leaderboard")
+ },
+ new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
+ new Drawable[] { new OverlinedHeader("Chat"), },
+ new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
+ },
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(),
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120),
+ }
+ },
+ },
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
+ new Dimension(),
+ new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
+ }
+ };
+
+ protected override Drawable CreateFooter() => new PlaylistsRoomFooter
+ {
+ OnStart = StartPlay
+ };
+
+ protected override RoomSettingsOverlay CreateRoomSettingsOverlay() => new PlaylistsRoomSettingsOverlay
+ {
+ EditPlaylist = () =>
+ {
+ if (this.IsCurrentScreen())
+ this.Push(new PlaylistsSongSelect());
+ },
+ };
+
+ private void updatePollingRate()
+ {
+ selectionPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 30000 : 5000;
+ Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})");
+ }
+
protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(SelectedItem.Value)
{
Exited = () => leaderboard.RefreshScores()
diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs
index 4265a83ce1..d77673580a 100644
--- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs
+++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs
@@ -10,12 +10,12 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
-using osu.Game.Screens.Ranking.Expanded;
using osuTK;
namespace osu.Game.Screens.Play
diff --git a/osu.Game/Screens/Play/ILocalUserPlayInfo.cs b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
new file mode 100644
index 0000000000..9a2259b12f
--- /dev/null
+++ b/osu.Game/Screens/Play/ILocalUserPlayInfo.cs
@@ -0,0 +1,17 @@
+// 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.Bindables;
+
+namespace osu.Game.Screens.Play
+{
+ [Cached]
+ public interface ILocalUserPlayInfo
+ {
+ ///
+ /// Whether the local user is currently playing.
+ ///
+ IBindable IsPlaying { get; }
+ }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 09eaf1c543..59c7abb299 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play
{
[Cached]
[Cached(typeof(ISamplePlaybackDisabler))]
- public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler
+ public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo
{
///
/// The delay upon completion of the beatmap before displaying the results screen.
@@ -75,7 +75,9 @@ namespace osu.Game.Screens.Play
private readonly Bindable storyboardReplacesBackground = new Bindable();
- protected readonly Bindable LocalUserPlaying = new Bindable();
+ public IBindable LocalUserPlaying => localUserPlaying;
+
+ private readonly Bindable localUserPlaying = new Bindable();
public int RestartCount;
@@ -442,7 +444,7 @@ namespace osu.Game.Screens.Play
{
bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
- LocalUserPlaying.Value = inGameplay;
+ localUserPlaying.Value = inGameplay;
}
private void updateSampleDisabledState()
@@ -1052,5 +1054,7 @@ namespace osu.Game.Screens.Play
#endregion
IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
+
+ IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
}
}
diff --git a/osu.Game/Screens/Play/PlayerConfiguration.cs b/osu.Game/Screens/Play/PlayerConfiguration.cs
index 18ee73374f..3aa424e5d5 100644
--- a/osu.Game/Screens/Play/PlayerConfiguration.cs
+++ b/osu.Game/Screens/Play/PlayerConfiguration.cs
@@ -20,6 +20,11 @@ namespace osu.Game.Screens.Play
///
public bool AllowRestart { get; set; } = true;
+ ///
+ /// Whether the player should be able to interact with this player instance.
+ ///
+ public bool AllowUserInteraction { get; set; } = true;
+
///
/// Whether the player should be allowed to skip intros/outros, advancing to the start of gameplay or the end of a storyboard.
///
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 5f6b4ca2b0..969527a758 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -46,9 +46,14 @@ namespace osu.Game.Screens.Play
protected override bool PlayResumeSound => false;
- protected BeatmapMetadataDisplay MetadataInfo;
+ protected BeatmapMetadataDisplay MetadataInfo { get; private set; }
- protected VisualSettings VisualSettings;
+ ///
+ /// A fill flow containing the player settings groups, exposed for the ability to hide it from inheritors of the player loader.
+ ///
+ protected FillFlowContainer PlayerSettings { get; private set; }
+
+ protected VisualSettings VisualSettings { get; private set; }
protected Task LoadTask { get; private set; }
@@ -140,7 +145,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- new FillFlowContainer
+ PlayerSettings = new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs
index 6aa7e017ce..b27a9c5f5d 100644
--- a/osu.Game/Screens/Play/SongProgress.cs
+++ b/osu.Game/Screens/Play/SongProgress.cs
@@ -119,13 +119,14 @@ namespace osu.Game.Screens.Play
if (drawableRuleset != null)
{
- AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
+ if (player?.Configuration.AllowUserInteraction == true)
+ ((IBindable)AllowSeeking).BindTo(drawableRuleset.HasReplayLoaded);
referenceClock = drawableRuleset.FrameStableClock;
Objects = drawableRuleset.Objects;
}
- config.BindWith(OsuSetting.ShowDifficultyGraph, ShowGraph);
+ config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph);
graph.FillColour = bar.FillColour = colours.BlueLighter;
}
diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs
index f662a479ec..1dae28092a 100644
--- a/osu.Game/Screens/Play/SpectatorPlayer.cs
+++ b/osu.Game/Screens/Play/SpectatorPlayer.cs
@@ -23,7 +23,8 @@ namespace osu.Game.Screens.Play
protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap
- public SpectatorPlayer(Score score)
+ public SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
+ : base(configuration)
{
this.score = score;
}
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index e10fe5726d..bcb5e7999f 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs
deleted file mode 100644
index 2b86100be8..0000000000
--- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Globalization;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.UserInterface;
-using osu.Game.Beatmaps;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Screens.Ranking.Expanded
-{
- ///
- /// A pill that displays the star rating of a .
- ///
- public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue
- {
- private Box background;
- private FillFlowContainer content;
- private OsuTextFlowContainer textFlow;
-
- [Resolved]
- private OsuColour colours { get; set; }
-
- private readonly BindableWithCurrent current = new BindableWithCurrent();
-
- public Bindable Current
- {
- get => current.Current;
- set => current.Current = value;
- }
-
- ///
- /// Creates a new using an already computed .
- ///
- /// The already computed to display the star difficulty of.
- public StarRatingDisplay(StarDifficulty starDifficulty)
- {
- Current.Value = starDifficulty;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache)
- {
- AutoSizeAxes = Axes.Both;
-
- InternalChildren = new Drawable[]
- {
- new CircularContainer
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Children = new Drawable[]
- {
- background = new Box
- {
- RelativeSizeAxes = Axes.Both,
- },
- }
- },
- content = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Padding = new MarginPadding { Horizontal = 8, Vertical = 4 },
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(2, 0),
- Children = new Drawable[]
- {
- new SpriteIcon
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Size = new Vector2(7),
- Icon = FontAwesome.Solid.Star,
- },
- textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black))
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- TextAnchor = Anchor.BottomLeft,
- }
- }
- }
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Current.BindValueChanged(_ => updateDisplay(), true);
- }
-
- private void updateDisplay()
- {
- var starRatingParts = Current.Value.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.');
- string wholePart = starRatingParts[0];
- string fractionPart = starRatingParts[1];
- string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
-
- var stars = Current.Value.Stars;
-
- background.Colour = colours.ForStarDifficulty(stars);
- content.Colour = stars >= 6.5 ? colours.Orange1 : Color4.Black;
-
- textFlow.Clear();
- textFlow.AddText($"{wholePart}", s =>
- {
- s.Font = s.Font.With(size: 14);
- s.UseFullGlyphHeight = false;
- });
-
- textFlow.AddText($"{separator}{fractionPart}", s =>
- {
- s.Font = s.Font.With(size: 7);
- s.UseFullGlyphHeight = false;
- });
- }
- }
-}
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 5b4e077100..297a16dd1d 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -28,7 +28,6 @@ using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
-using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Select
@@ -38,6 +37,8 @@ namespace osu.Game.Screens.Select
public const float BORDER_THICKNESS = 2.5f;
private const float shear_width = 36.75f;
+ private const float transition_duration = 250;
+
private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0);
[Resolved]
@@ -46,11 +47,6 @@ namespace osu.Game.Screens.Select
[Resolved]
private IBindable> mods { get; set; }
- [Resolved]
- private BeatmapDifficultyCache difficultyCache { get; set; }
-
- private IBindable beatmapDifficulty;
-
protected Container DisplayedContent { get; private set; }
protected WedgeInfoText Info { get; private set; }
@@ -77,24 +73,24 @@ namespace osu.Game.Screens.Select
ruleset.BindValueChanged(_ => updateDisplay());
}
+ private const double animation_duration = 800;
+
protected override void PopIn()
{
- this.MoveToX(0, 800, Easing.OutQuint);
- this.RotateTo(0, 800, Easing.OutQuint);
- this.FadeIn(250);
+ this.MoveToX(0, animation_duration, Easing.OutQuint);
+ this.RotateTo(0, animation_duration, Easing.OutQuint);
+ this.FadeIn(transition_duration);
}
protected override void PopOut()
{
- this.MoveToX(-100, 800, Easing.In);
- this.RotateTo(10, 800, Easing.In);
- this.FadeOut(500, Easing.In);
+ this.MoveToX(-100, animation_duration, Easing.In);
+ this.RotateTo(10, animation_duration, Easing.In);
+ this.FadeOut(transition_duration * 2, Easing.In);
}
private WorkingBeatmap beatmap;
- private CancellationTokenSource cancellationSource;
-
public WorkingBeatmap Beatmap
{
get => beatmap;
@@ -103,12 +99,6 @@ namespace osu.Game.Screens.Select
if (beatmap == value) return;
beatmap = value;
- cancellationSource?.Cancel();
- cancellationSource = new CancellationTokenSource();
-
- beatmapDifficulty?.UnbindAll();
- beatmapDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, cancellationSource.Token);
- beatmapDifficulty.BindValueChanged(_ => updateDisplay());
updateDisplay();
}
@@ -128,7 +118,7 @@ namespace osu.Game.Screens.Select
{
State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible;
- DisplayedContent?.FadeOut(250);
+ DisplayedContent?.FadeOut(transition_duration);
DisplayedContent?.Expire();
DisplayedContent = null;
}
@@ -147,7 +137,7 @@ namespace osu.Game.Screens.Select
Children = new Drawable[]
{
new BeatmapInfoWedgeBackground(beatmap),
- Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()),
+ Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value),
}
}, loaded =>
{
@@ -160,12 +150,6 @@ namespace osu.Game.Screens.Select
}
}
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- cancellationSource?.Cancel();
- }
-
public class WedgeInfoText : Container
{
public OsuSpriteText VersionLabel { get; private set; }
@@ -174,6 +158,9 @@ namespace osu.Game.Screens.Select
public BeatmapSetOnlineStatusPill StatusPill { get; private set; }
public FillFlowContainer MapperContainer { get; private set; }
+ private Container difficultyColourBar;
+ private StarRatingDisplay starRatingDisplay;
+
private ILocalisedBindableString titleBinding;
private ILocalisedBindableString artistBinding;
private FillFlowContainer infoLabelContainer;
@@ -182,20 +169,21 @@ namespace osu.Game.Screens.Select
private readonly WorkingBeatmap beatmap;
private readonly RulesetInfo ruleset;
private readonly IReadOnlyList mods;
- private readonly StarDifficulty starDifficulty;
private ModSettingChangeTracker settingChangeTracker;
- public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, StarDifficulty difficulty)
+ public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods)
{
this.beatmap = beatmap;
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
this.mods = mods;
- starDifficulty = difficulty;
}
+ private CancellationTokenSource cancellationSource;
+ private IBindable starDifficulty;
+
[BackgroundDependencyLoader]
- private void load(LocalisationManager localisation)
+ private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache)
{
var beatmapInfo = beatmap.BeatmapInfo;
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
@@ -205,12 +193,30 @@ namespace osu.Game.Screens.Select
titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title));
artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist));
+ const float top_height = 0.7f;
+
Children = new Drawable[]
{
- new DifficultyColourBar(starDifficulty)
+ difficultyColourBar = new Container
{
RelativeSizeAxes = Axes.Y,
- Width = 20,
+ Width = 20f,
+ Children = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Width = top_height,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ RelativePositionAxes = Axes.Both,
+ Alpha = 0.5f,
+ X = top_height,
+ Width = 1 - top_height,
+ }
+ }
},
new FillFlowContainer
{
@@ -241,14 +247,16 @@ namespace osu.Game.Screens.Select
Padding = new MarginPadding { Top = 14, Right = shear_width / 2 },
AutoSizeAxes = Axes.Both,
Shear = wedged_container_shear,
- Children = new[]
+ Spacing = new Vector2(0f, 5f),
+ Children = new Drawable[]
{
- createStarRatingDisplay(starDifficulty).With(display =>
+ starRatingDisplay = new StarRatingDisplay(default, animated: true)
{
- display.Anchor = Anchor.TopRight;
- display.Origin = Anchor.TopRight;
- display.Shear = -wedged_container_shear;
- }),
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Shear = -wedged_container_shear,
+ Alpha = 0f,
+ },
StatusPill = new BeatmapSetOnlineStatusPill
{
Anchor = Anchor.TopRight,
@@ -304,6 +312,18 @@ namespace osu.Game.Screens.Select
titleBinding.BindValueChanged(_ => setMetadata(metadata.Source));
artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true);
+ starRatingDisplay.DisplayedStars.BindValueChanged(s =>
+ {
+ difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue);
+ }, true);
+
+ starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, (cancellationSource = new CancellationTokenSource()).Token);
+ starDifficulty.BindValueChanged(s =>
+ {
+ starRatingDisplay.FadeIn(transition_duration);
+ starRatingDisplay.Current.Value = s.NewValue ?? default;
+ });
+
// no difficulty means it can't have a status to show
if (beatmapInfo.Version == null)
StatusPill.Hide();
@@ -311,13 +331,6 @@ namespace osu.Game.Screens.Select
addInfoLabels();
}
- private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0
- ? new StarRatingDisplay(difficulty)
- {
- Margin = new MarginPadding { Bottom = 5 }
- }
- : Empty();
-
private void setMetadata(string source)
{
ArtistLabel.Text = artistBinding.Value;
@@ -429,6 +442,7 @@ namespace osu.Game.Screens.Select
{
base.Dispose(isDisposing);
settingChangeTracker?.Dispose();
+ cancellationSource?.Cancel();
}
public class InfoLabel : Container, IHasTooltip
@@ -489,43 +503,6 @@ namespace osu.Game.Screens.Select
};
}
}
-
- private class DifficultyColourBar : Container
- {
- private readonly StarDifficulty difficulty;
-
- public DifficultyColourBar(StarDifficulty difficulty)
- {
- this.difficulty = difficulty;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- const float full_opacity_ratio = 0.7f;
-
- var difficultyColour = colours.ForStarDifficulty(difficulty.Stars);
-
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = difficultyColour,
- Width = full_opacity_ratio,
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- RelativePositionAxes = Axes.Both,
- Colour = difficultyColour,
- Alpha = 0.5f,
- X = full_opacity_ratio,
- Width = 1 - full_opacity_ratio,
- }
- };
- }
- }
}
}
}
diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs
index ea55fd28c2..0f805990b9 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -105,12 +105,18 @@ namespace osu.Game.Skinning
/// Returns a list of all usable s that have been loaded by the user.
///
/// A newly allocated list of available .
- public List GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
+ public List GetAllUserSkins(bool includeFiles = false)
+ {
+ if (includeFiles)
+ return ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
+
+ return ModelStore.Items.Where(s => !s.DeletePending).ToList();
+ }
public void SelectRandomSkin()
{
// choose from only user skins, removing the current selection to ensure a new one is chosen.
- var randomChoices = GetAllUsableSkins().Where(s => s.ID != CurrentSkinInfo.Value.ID).ToArray();
+ var randomChoices = ModelStore.Items.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray();
if (randomChoices.Length == 0)
{
@@ -118,7 +124,8 @@ namespace osu.Game.Skinning
return;
}
- CurrentSkinInfo.Value = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
+ var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
+ CurrentSkinInfo.Value = ModelStore.ConsumableItems.Single(i => i.ID == chosen.ID);
}
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };
diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs
index 03ab94d1da..d7ab769ac4 100644
--- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs
+++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs
@@ -2,14 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using System.Runtime.CompilerServices;
-using osu.Framework.Platform;
+using osu.Framework.Testing;
namespace osu.Game.Tests
{
///
/// A headless host which cleans up before running (removing any remnants from a previous execution).
///
- public class CleanRunHeadlessGameHost : HeadlessGameHost
+ public class CleanRunHeadlessGameHost : TestRunHeadlessGameHost
{
///
/// Create a new instance.
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index f2da66d666..2c0ca0b872 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected override Task JoinRoom(long roomId, string? password = null)
{
- var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId);
+ var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId);
if (password != apiRoom.Password.Value)
throw new InvalidOperationException("Invalid password.");
@@ -260,7 +260,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Debug.Assert(Room != null);
- var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == Room.RoomID);
+ var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID);
var set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet
?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet;
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestRequestHandlingMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestRequestHandlingMultiplayerRoomManager.cs
index 2e56c8a094..5de518990a 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestRequestHandlingMultiplayerRoomManager.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestRequestHandlingMultiplayerRoomManager.cs
@@ -1,160 +1,36 @@
// 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.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
///
- /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a .
+ /// A for use in multiplayer test scenes, backed by a .
+ /// Should generally not be used by itself outside of a .
///
- ///
- /// This implementation will pretend to be a server, handling room retrieval and manipulation requests
- /// and returning a roughly expected state, without the need for a server to be running.
- ///
public class TestRequestHandlingMultiplayerRoomManager : MultiplayerRoomManager
{
- [Resolved]
- private IAPIProvider api { get; set; }
+ public IReadOnlyList ServerSideRooms => handler.ServerSideRooms;
- [Resolved]
- private OsuGameBase game { get; set; }
-
- [Cached]
- public readonly Bindable Filter = new Bindable(new FilterCriteria());
-
- public new readonly List Rooms = new List();
-
- private int currentRoomId;
- private int currentPlaylistItemId;
+ private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
[BackgroundDependencyLoader]
- private void load()
+ private void load(IAPIProvider api, OsuGameBase game)
{
- int currentScoreId = 0;
-
- // Handling here is pretending to be a server, while also updating the local state to match
- // how the server would eventually respond and update the RoomManager.
- ((DummyAPIAccess)api).HandleRequest = req =>
- {
- switch (req)
- {
- case CreateRoomRequest createRoomRequest:
- var apiRoom = new Room();
-
- apiRoom.CopyFrom(createRoomRequest.Room);
-
- // Passwords are explicitly not copied between rooms.
- apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
- apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
-
- AddRoom(apiRoom);
-
- var responseRoom = new APICreatedRoom();
- responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
-
- createRoomRequest.TriggerSuccess(responseRoom);
- return true;
-
- case JoinRoomRequest joinRoomRequest:
- {
- var room = Rooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
-
- if (joinRoomRequest.Password != room.Password.Value)
- {
- joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
- return true;
- }
-
- joinRoomRequest.TriggerSuccess();
- return true;
- }
-
- case PartRoomRequest partRoomRequest:
- partRoomRequest.TriggerSuccess();
- return true;
-
- case GetRoomsRequest getRoomsRequest:
- var roomsWithoutParticipants = new List();
-
- foreach (var r in Rooms)
- roomsWithoutParticipants.Add(createResponseRoom(r, false));
-
- getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
- return true;
-
- case GetRoomRequest getRoomRequest:
- getRoomRequest.TriggerSuccess(createResponseRoom(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
- return true;
-
- case GetBeatmapSetRequest getBeatmapSetRequest:
- var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type);
- onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res);
- onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e);
-
- // Get the online API from the game's dependencies.
- game.Dependencies.Get().Queue(onlineReq);
- return true;
-
- case CreateRoomScoreRequest createRoomScoreRequest:
- createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
- return true;
-
- case SubmitRoomScoreRequest submitRoomScoreRequest:
- submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore
- {
- ID = currentScoreId++,
- Accuracy = 1,
- EndedAt = DateTimeOffset.Now,
- Passed = true,
- Rank = ScoreRank.S,
- MaxCombo = 1000,
- TotalScore = 1000000,
- User = api.LocalUser.Value,
- Statistics = new Dictionary()
- });
- return true;
- }
-
- return false;
- };
+ ((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
}
- public void AddRoom(Room room)
- {
- room.RoomID.Value ??= currentRoomId++;
- for (int i = 0; i < room.Playlist.Count; i++)
- room.Playlist[i].ID = currentPlaylistItemId++;
-
- Rooms.Add(room);
- }
-
- public new void RemoveRoom(Room room) => base.RemoveRoom(room);
-
- private Room createResponseRoom(Room room, bool withParticipants)
- {
- var responseRoom = new Room();
- responseRoom.CopyFrom(room);
- responseRoom.Password.Value = null;
- if (!withParticipants)
- responseRoom.RecentParticipants.Clear();
- return responseRoom;
- }
-
- public new void ClearRooms() => base.ClearRooms();
-
- public new void Schedule(Action action) => base.Schedule(action);
+ ///
+ /// Adds a room to a local "server-side" list that's returned when a is fired.
+ ///
+ /// The room.
+ public void AddServerSideRoom(Room room) => handler.AddServerSideRoom(room);
}
}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs
index 6e1e831d9b..71acefb158 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs
@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
namespace osu.Game.Tests.Visual.OnlinePlay
{
@@ -23,11 +22,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
///
IRoomManager RoomManager { get; }
- ///
- /// The cached .
- ///
- Bindable Filter { get; }
-
///
/// The cached .
///
diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs
index 997c910dd4..8716646074 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
namespace osu.Game.Tests.Visual.OnlinePlay
{
@@ -20,7 +19,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
{
public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom;
public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager;
- public Bindable Filter => OnlinePlayDependencies?.Filter;
public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker;
public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker;
diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
index 05ba509a73..defc971eef 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
namespace osu.Game.Tests.Visual.OnlinePlay
{
@@ -20,7 +19,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
{
public Bindable SelectedRoom { get; }
public IRoomManager RoomManager { get; }
- public Bindable Filter { get; }
public OngoingOperationTracker OngoingOperationTracker { get; }
public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; }
@@ -36,7 +34,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
{
SelectedRoom = new Bindable();
RoomManager = CreateRoomManager();
- Filter = new Bindable(new FilterCriteria());
OngoingOperationTracker = new OngoingOperationTracker();
AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
@@ -44,7 +41,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
CacheAs(SelectedRoom);
CacheAs(RoomManager);
- CacheAs(Filter);
CacheAs(OngoingOperationTracker);
CacheAs(AvailabilityTracker);
CacheAs(new OverlayColourProvider(OverlayColourScheme.Plum));
@@ -75,6 +71,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
drawableComponents.Add(drawable);
}
- protected virtual IRoomManager CreateRoomManager() => new BasicTestRoomManager();
+ protected virtual IRoomManager CreateRoomManager() => new TestRequestHandlingRoomManager();
}
}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRequestHandlingRoomManager.cs
similarity index 52%
rename from osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs
rename to osu.Game/Tests/Visual/OnlinePlay/TestRequestHandlingRoomManager.cs
index d37a64fa4b..d88fd68b20 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/TestRequestHandlingRoomManager.cs
@@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
-using osu.Framework.Bindables;
+using osu.Framework.Allocation;
using osu.Game.Beatmaps;
+using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
-using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
@@ -16,46 +15,24 @@ namespace osu.Game.Tests.Visual.OnlinePlay
///
/// A very simple for use in online play test scenes.
///
- public class BasicTestRoomManager : IRoomManager
+ public class TestRequestHandlingRoomManager : RoomManager
{
- public event Action RoomsUpdated;
-
- public readonly BindableList Rooms = new BindableList();
-
public Action JoinRoomRequested;
- public IBindable InitialRoomsReceived { get; } = new Bindable(true);
+ private int currentRoomId;
- IBindableList IRoomManager.Rooms => Rooms;
+ private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
- public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
+ [BackgroundDependencyLoader]
+ private void load(IAPIProvider api, OsuGameBase game)
{
- room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1;
- onSuccess?.Invoke(room);
-
- AddRoom(room);
+ ((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
}
- public void AddRoom(Room room)
- {
- Rooms.Add(room);
- RoomsUpdated?.Invoke();
- }
-
- public void RemoveRoom(Room room)
- {
- Rooms.Remove(room);
- RoomsUpdated?.Invoke();
- }
-
- public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null)
+ public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null)
{
JoinRoomRequested?.Invoke(room, password);
- onSuccess?.Invoke(room);
- }
-
- public void PartRoom()
- {
+ base.JoinRoom(room, password, onSuccess, onError);
}
public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false)
@@ -64,15 +41,16 @@ namespace osu.Game.Tests.Visual.OnlinePlay
{
var room = new Room
{
- RoomID = { Value = i },
- Position = { Value = i },
- Name = { Value = $"Room {i}" },
- Host = { Value = new User { Username = "Host" } },
+ RoomID = { Value = -currentRoomId },
+ Name = { Value = $@"Room {currentRoomId}" },
+ Host = { Value = new User { Username = @"Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal },
- Password = { Value = withPassword ? "password" : string.Empty }
};
+ if (withPassword)
+ room.Password.Value = @"password";
+
if (ruleset != null)
{
room.Playlist.Add(new PlaylistItem
@@ -89,6 +67,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay
}
CreateRoom(room);
+
+ currentRoomId++;
}
}
}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
new file mode 100644
index 0000000000..7f975c9985
--- /dev/null
+++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
@@ -0,0 +1,147 @@
+// 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.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.OnlinePlay
+{
+ ///
+ /// Represents a handler which pretends to be a server, handling room retrieval and manipulation requests
+ /// and returning a roughly expected state, without the need for a server to be running.
+ ///
+ public class TestRoomRequestsHandler
+ {
+ public IReadOnlyList ServerSideRooms => serverSideRooms;
+
+ private readonly List serverSideRooms = new List();
+
+ private int currentRoomId;
+ private int currentPlaylistItemId;
+ private int currentScoreId;
+
+ ///
+ /// Handles an API request, while also updating the local state to match
+ /// how the server would eventually respond and update an .
+ ///
+ /// The API request to handle.
+ /// The local user to store in responses where required.
+ /// The game base for cases where actual online requests need to be sent.
+ /// Whether the request was successfully handled.
+ public bool HandleRequest(APIRequest request, User localUser, OsuGameBase game)
+ {
+ switch (request)
+ {
+ case CreateRoomRequest createRoomRequest:
+ var apiRoom = new Room();
+
+ apiRoom.CopyFrom(createRoomRequest.Room);
+
+ // Passwords are explicitly not copied between rooms.
+ apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
+ apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
+
+ AddServerSideRoom(apiRoom);
+
+ var responseRoom = new APICreatedRoom();
+ responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
+
+ createRoomRequest.TriggerSuccess(responseRoom);
+ return true;
+
+ case JoinRoomRequest joinRoomRequest:
+ {
+ var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
+
+ if (joinRoomRequest.Password != room.Password.Value)
+ {
+ joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
+ return true;
+ }
+
+ joinRoomRequest.TriggerSuccess();
+ return true;
+ }
+
+ case PartRoomRequest partRoomRequest:
+ partRoomRequest.TriggerSuccess();
+ return true;
+
+ case GetRoomsRequest getRoomsRequest:
+ var roomsWithoutParticipants = new List();
+
+ foreach (var r in ServerSideRooms)
+ roomsWithoutParticipants.Add(createResponseRoom(r, false));
+
+ getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
+ return true;
+
+ case GetRoomRequest getRoomRequest:
+ getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
+ return true;
+
+ case GetBeatmapSetRequest getBeatmapSetRequest:
+ var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type);
+ onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res);
+ onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e);
+
+ // Get the online API from the game's dependencies.
+ game.Dependencies.Get().Queue(onlineReq);
+ return true;
+
+ case CreateRoomScoreRequest createRoomScoreRequest:
+ createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
+ return true;
+
+ case SubmitRoomScoreRequest submitRoomScoreRequest:
+ submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore
+ {
+ ID = currentScoreId++,
+ Accuracy = 1,
+ EndedAt = DateTimeOffset.Now,
+ Passed = true,
+ Rank = ScoreRank.S,
+ MaxCombo = 1000,
+ TotalScore = 1000000,
+ User = localUser,
+ Statistics = new Dictionary()
+ });
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Adds a room to a local "server-side" list that's returned when a is fired.
+ ///
+ /// The room.
+ public void AddServerSideRoom(Room room)
+ {
+ room.RoomID.Value ??= currentRoomId++;
+ for (int i = 0; i < room.Playlist.Count; i++)
+ room.Playlist[i].ID = currentPlaylistItemId++;
+
+ serverSideRooms.Add(room);
+ }
+
+ private Room createResponseRoom(Room room, bool withParticipants)
+ {
+ var responseRoom = new Room();
+ responseRoom.CopyFrom(room);
+ responseRoom.Password.Value = null;
+ if (!withParticipants)
+ responseRoom.RecentParticipants.Clear();
+ return responseRoom;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs
similarity index 92%
rename from osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
rename to osu.Game/Tests/Visual/OsuGameTestScene.cs
index c9a1471e41..f38aaa9358 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs
@@ -22,9 +22,8 @@ using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
-using IntroSequence = osu.Game.Configuration.IntroSequence;
-namespace osu.Game.Tests.Visual.Navigation
+namespace osu.Game.Tests.Visual
{
///
/// A scene which tests full game flow.
@@ -62,7 +61,7 @@ namespace osu.Game.Tests.Visual.Navigation
Game.Dispose();
}
- RecycleLocalStorage();
+ RecycleLocalStorage(false);
CreateGame();
});
@@ -73,15 +72,18 @@ namespace osu.Game.Tests.Visual.Navigation
ConfirmAtMainMenu();
}
+ [TearDownSteps]
+ public void TearDownSteps()
+ {
+ AddStep("exit game", () => Game.Exit());
+ AddUntilStep("wait for game exit", () => Game.Parent == null);
+ }
+
protected void CreateGame()
{
Game = new TestOsuGame(LocalStorage, API);
Game.SetHost(host);
- // todo: this can be removed once we can run audio tracks without a device present
- // see https://github.com/ppy/osu/issues/1302
- Game.LocalConfig.SetValue(OsuSetting.IntroSequence, IntroSequence.Circles);
-
Add(Game);
}
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 57e400a77e..ef9181c8a6 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -7,7 +7,6 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
-using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
@@ -100,7 +99,7 @@ namespace osu.Game.Tests.Visual
return factory;
});
- RecycleLocalStorage();
+ RecycleLocalStorage(false);
var baseDependencies = base.CreateChildDependencies(parent);
@@ -140,7 +139,7 @@ namespace osu.Game.Tests.Visual
protected virtual bool UseFreshStoragePerRun => false;
- public virtual void RecycleLocalStorage()
+ public virtual void RecycleLocalStorage(bool isDisposing)
{
if (localStorage?.IsValueCreated == true)
{
@@ -155,7 +154,7 @@ namespace osu.Game.Tests.Visual
}
localStorage =
- new Lazy(() => isolatedHostStorage ?? new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}")));
+ new Lazy(() => isolatedHostStorage ?? new TemporaryNativeStorage($"{GetType().Name}-{Guid.NewGuid()}"));
}
[Resolved]
@@ -199,7 +198,7 @@ namespace osu.Game.Tests.Visual
if (contextFactory?.IsValueCreated == true)
contextFactory.Value.ResetDatabase();
- RecycleLocalStorage();
+ RecycleLocalStorage(true);
}
protected override ITestSceneTestRunner CreateRunner() => new OsuTestSceneTestRunner();
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index d4dba9330f..0abd9adf77 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,8 +36,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 7e514afe74..d03985a0c2 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -93,7 +93,7 @@
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 139ee02b96..e42b30e944 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -105,8 +105,9 @@
HINT
HINT
WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
WARNING
- DO_NOT_SHOW
WARNING
WARNING
WARNING
@@ -304,6 +305,7 @@
AABB
API
BPM
+ FPS
GC
GL
GLSL