diff --git a/Directory.Build.props b/Directory.Build.props
index 2d3478f256..551cb75077 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,9 +16,9 @@
-
+
-
+
$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
diff --git a/global.json b/global.json
index a9a531f59c..10b61047ac 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.1.1"
+ "Microsoft.Build.Traversal": "2.2.3"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 2d531cf01e..a4bcbd289d 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
index ab840e1c46..e8c2472c3b 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
@@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests
objects.Add(new Note { StartTime = time });
+ // don't hit the first note
if (i > 0)
{
frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1));
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 937473e824..6841ecd23c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Osu.Mods
///
private const float target_clamp = 1;
- private readonly float targetBreakMultiplier = 0;
- private readonly float easing = 1;
+ private readonly float targetBreakMultiplier;
+ private readonly float easing;
private readonly CompositeDrawable restrictTo;
@@ -86,6 +86,9 @@ namespace osu.Game.Rulesets.Osu.Mods
{
this.restrictTo = restrictTo;
this.beatmap = beatmap;
+
+ targetBreakMultiplier = 0;
+ easing = 1;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs
index fde42bec04..9bfb6aa839 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Performance;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 80fbda8e1d..b941313103 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -821,15 +821,13 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- await manager.Import(temp);
-
- var imported = manager.GetAllUsableBeatmapSets();
+ var importedSet = await manager.Import(temp);
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return imported.LastOrDefault();
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
}
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 58cc324233..de46f9d1cf 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -81,8 +81,8 @@ namespace osu.Game.Tests.Gameplay
private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation
{
- public bool NewCombo { get; set; } = false;
- public int ComboOffset { get; } = 0;
+ public bool NewCombo { get; set; }
+ public int ComboOffset => 0;
public Bindable IndexInCurrentComboBindable { get; } = new Bindable();
diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
new file mode 100644
index 0000000000..fba0d92d4b
--- /dev/null
+++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
@@ -0,0 +1,196 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Configuration;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Tests.Visual.Background
+{
+ public class TestSceneSeasonalBackgroundLoader : ScreenTestScene
+ {
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ [Resolved]
+ private SessionStatics statics { get; set; }
+
+ [Cached(typeof(LargeTextureStore))]
+ private LookupLoggingTextureStore textureStore = new LookupLoggingTextureStore();
+
+ private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
+
+ private SeasonalBackgroundLoader backgroundLoader;
+ private Container backgroundContainer;
+
+ // in real usages these would be online URLs, but correct execution of this test
+ // shouldn't be coupled to existence of online assets.
+ private static readonly List seasonal_background_urls = new List
+ {
+ "Backgrounds/bg2",
+ "Backgrounds/bg4",
+ "Backgrounds/bg3"
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(LargeTextureStore wrappedStore)
+ {
+ textureStore.AddStore(wrappedStore);
+
+ Add(backgroundContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ });
+ }
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ // reset API response in statics to avoid test crosstalk.
+ statics.Set(Static.SeasonalBackgrounds, null);
+ textureStore.PerformedLookups.Clear();
+ dummyAPI.SetState(APIState.Online);
+
+ backgroundContainer.Clear();
+ });
+
+ [TestCase(-5)]
+ [TestCase(5)]
+ public void TestAlwaysSeasonal(int daysOffset)
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
+
+ createLoader();
+
+ for (int i = 0; i < 4; ++i)
+ loadNextBackground();
+
+ AddAssert("all backgrounds cycled", () => new HashSet(textureStore.PerformedLookups).SetEquals(seasonal_background_urls));
+ }
+
+ [TestCase(-5)]
+ [TestCase(5)]
+ public void TestNeverSeasonal(int daysOffset)
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Never);
+
+ createLoader();
+
+ assertNoBackgrounds();
+ }
+
+ [Test]
+ public void TestSometimesInSeason()
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(5));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
+
+ createLoader();
+
+ assertAnyBackground();
+ }
+
+ [Test]
+ public void TestSometimesOutOfSeason()
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(-10));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
+
+ createLoader();
+
+ assertNoBackgrounds();
+ }
+
+ [Test]
+ public void TestDelayedConnectivity()
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(30));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
+ AddStep("go offline", () => dummyAPI.SetState(APIState.Offline));
+
+ createLoader();
+ assertNoBackgrounds();
+
+ AddStep("go online", () => dummyAPI.SetState(APIState.Online));
+
+ assertAnyBackground();
+ }
+
+ private void registerBackgroundsResponse(DateTimeOffset endDate)
+ => AddStep("setup request handler", () =>
+ {
+ dummyAPI.HandleRequest = request =>
+ {
+ if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
+ return;
+
+ backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
+ {
+ Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
+ EndDate = endDate
+ });
+ };
+ });
+
+ private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
+ => AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode));
+
+ private void createLoader()
+ => AddStep("create loader", () =>
+ {
+ if (backgroundLoader != null)
+ Remove(backgroundLoader);
+
+ Add(backgroundLoader = new SeasonalBackgroundLoader());
+ });
+
+ private void loadNextBackground()
+ {
+ SeasonalBackground background = null;
+
+ AddStep("create next background", () =>
+ {
+ background = backgroundLoader.LoadNextBackground();
+ LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
+ });
+
+ AddUntilStep("background loaded", () => background.IsLoaded);
+ }
+
+ private void assertAnyBackground()
+ {
+ loadNextBackground();
+ AddAssert("background looked up", () => textureStore.PerformedLookups.Any());
+ }
+
+ private void assertNoBackgrounds()
+ {
+ AddAssert("no background available", () => backgroundLoader.LoadNextBackground() == null);
+ AddAssert("no lookups performed", () => !textureStore.PerformedLookups.Any());
+ }
+
+ private class LookupLoggingTextureStore : LargeTextureStore
+ {
+ public List PerformedLookups { get; } = new List();
+
+ public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
+ {
+ PerformedLookups.Add(name);
+ return base.Get(name, wrapModeS, wrapModeT);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index 6ec673704c..f9914e0193 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -2,29 +2,23 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestSceneHUDOverlay : SkinnableTestScene
+ public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
private HUDOverlay hudOverlay;
- private IEnumerable hudOverlays => CreatedDrawables.OfType();
-
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
@@ -37,17 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
- AddRepeatStep("increase combo", () =>
- {
- foreach (var hud in hudOverlays)
- hud.ComboCounter.Current.Value++;
- }, 10);
+ AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10);
- AddStep("reset combo", () =>
- {
- foreach (var hud in hudOverlays)
- hud.ComboCounter.Current.Value = 0;
- });
+ AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; });
}
[Test]
@@ -77,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
- AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
@@ -86,10 +72,32 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
}
+ [Test]
+ public void TestMomentaryShowHUD()
+ {
+ createNew();
+
+ HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
+
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
+
+ AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
+
+ AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
+
+ AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft));
+ AddUntilStep("wait for visible", () => hideTarget.IsPresent);
+
+ AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
+ AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
+
+ AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue));
+ }
+
[Test]
public void TestExternalHideDoesntAffectConfig()
{
- HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks;
+ HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
createNew();
@@ -113,14 +121,14 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set keycounter visible false", () =>
{
config.Set(OsuSetting.KeyOverlay, false);
- hudOverlays.ForEach(h => h.KeyCounter.AlwaysVisible.Value = false);
+ hudOverlay.KeyCounter.AlwaysVisible.Value = false;
});
- AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent);
- AddStep("set showhud true", () => hudOverlays.ForEach(h => h.ShowHud.Value = true));
+ AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
@@ -131,22 +139,17 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("create overlay", () =>
{
- SetContents(() =>
- {
- hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
+ hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
- // Add any key just to display the key counter visually.
- hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
+ // Add any key just to display the key counter visually.
+ hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
- hudOverlay.ComboCounter.Current.Value = 1;
+ hudOverlay.ComboCounter.Current.Value = 1;
- action?.Invoke(hudOverlay);
+ action?.Invoke(hudOverlay);
- return hudOverlay;
- });
+ Child = hudOverlay;
});
}
-
- protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
new file mode 100644
index 0000000000..fec1610160
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
@@ -0,0 +1,99 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Configuration;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSkinnableHUDOverlay : SkinnableTestScene
+ {
+ private HUDOverlay hudOverlay;
+
+ private IEnumerable hudOverlays => CreatedDrawables.OfType();
+
+ // best way to check without exposing.
+ private Drawable hideTarget => hudOverlay.KeyCounter;
+ private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ [Test]
+ public void TestComboCounterIncrementing()
+ {
+ createNew();
+
+ AddRepeatStep("increase combo", () =>
+ {
+ foreach (var hud in hudOverlays)
+ hud.ComboCounter.Current.Value++;
+ }, 10);
+
+ AddStep("reset combo", () =>
+ {
+ foreach (var hud in hudOverlays)
+ hud.ComboCounter.Current.Value = 0;
+ });
+ }
+
+ [Test]
+ public void TestFadesInOnLoadComplete()
+ {
+ float? initialAlpha = null;
+
+ createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
+ AddUntilStep("wait for load", () => hudOverlay.IsAlive);
+ AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
+ }
+
+ [Test]
+ public void TestHideExternally()
+ {
+ createNew();
+
+ AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
+
+ AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
+ AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
+
+ // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
+ AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
+ }
+
+ private void createNew(Action action = null)
+ {
+ AddStep("create overlay", () =>
+ {
+ SetContents(() =>
+ {
+ hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
+
+ // Add any key just to display the key counter visually.
+ hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
+
+ hudOverlay.ComboCounter.Current.Value = 1;
+
+ action?.Invoke(hudOverlay);
+
+ return hudOverlay;
+ });
+ });
+ }
+
+ protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
index fdc20dc477..07ff56b5c3 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
@@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public Bindable InitialRoomsReceived { get; } = new Bindable(true);
- public IBindableList Rooms { get; } = null;
+ public IBindableList Rooms => null;
public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
{
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
index 250fdc5ebd..5af55e99f8 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Ranking
private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () =>
{
- Child = panel = new ScorePanel(score)
+ Child = panel = new ScorePanel(score, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
index e02ebf3be1..0b2c0ce63b 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
@@ -197,8 +197,8 @@ namespace osu.Game.Tests.Visual.SongSelect
private class TestHitObject : ConvertHitObject, IHasPosition
{
- public float X { get; } = 0;
- public float Y { get; } = 0;
+ public float X => 0;
+ public float Y => 0;
public Vector2 Position { get; } = Vector2.Zero;
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 80a4d6dea4..80fd6c22bb 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -417,7 +417,7 @@ namespace osu.Game.Beatmaps.Formats
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
int volume = samples.FirstOrDefault()?.Volume ?? 100;
- sb.Append(":");
+ sb.Append(':');
sb.Append(FormattableString.Invariant($"{customSampleBank}:"));
sb.Append(FormattableString.Invariant($"{volume}:"));
sb.Append(FormattableString.Invariant($"{sampleFilename}"));
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index e2550d1ca4..8d8ca523d5 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -347,7 +347,7 @@ namespace osu.Game.Beatmaps.Formats
/// The line which may contains variables.
private void decodeVariables(ref string line)
{
- while (line.IndexOf('$') >= 0)
+ while (line.Contains('$'))
{
string origLine = line;
diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs
index b0b55dd811..10f3f65355 100644
--- a/osu.Game/Configuration/HUDVisibilityMode.cs
+++ b/osu.Game/Configuration/HUDVisibilityMode.cs
@@ -12,9 +12,6 @@ namespace osu.Game.Configuration
[Description("Hide during gameplay")]
HideDuringGameplay,
- [Description("Hide during breaks")]
- HideDuringBreaks,
-
Always
}
}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 7d601c0cb9..e0971d238a 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -131,6 +131,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
+ Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
}
public OsuConfigManager(Storage storage)
@@ -170,6 +171,7 @@ namespace osu.Game.Configuration
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")),
+ new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription())),
new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
};
}
@@ -239,5 +241,6 @@ namespace osu.Game.Configuration
HitLighting,
MenuBackgroundSource,
GameplayDisableWinKey,
+ SeasonalBackgroundMode
}
}
diff --git a/osu.Game/Configuration/SeasonalBackgroundMode.cs b/osu.Game/Configuration/SeasonalBackgroundMode.cs
new file mode 100644
index 0000000000..6ef835ce5f
--- /dev/null
+++ b/osu.Game/Configuration/SeasonalBackgroundMode.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Configuration
+{
+ public enum SeasonalBackgroundMode
+ {
+ ///
+ /// Seasonal backgrounds are shown regardless of season, if at all available.
+ ///
+ Always,
+
+ ///
+ /// Seasonal backgrounds are shown only during their corresponding season.
+ ///
+ Sometimes,
+
+ ///
+ /// Seasonal backgrounds are never shown.
+ ///
+ Never
+ }
+}
diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs
index 40b2adb867..03bc434aac 100644
--- a/osu.Game/Configuration/SessionStatics.cs
+++ b/osu.Game/Configuration/SessionStatics.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Online.API.Requests.Responses;
+
namespace osu.Game.Configuration
{
///
@@ -12,12 +14,19 @@ namespace osu.Game.Configuration
{
Set(Static.LoginOverlayDisplayed, false);
Set(Static.MutedAudioNotificationShownOnce, false);
+ Set(Static.SeasonalBackgrounds, null);
}
}
public enum Static
{
LoginOverlayDisplayed,
- MutedAudioNotificationShownOnce
+ MutedAudioNotificationShownOnce,
+
+ ///
+ /// Info about seasonal backgrounds available fetched from API - see .
+ /// Value under this lookup can be null if there are no backgrounds available (or API is not reachable).
+ ///
+ SeasonalBackgrounds,
}
}
diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs
index 1fd2f23d50..ddafd77066 100644
--- a/osu.Game/Database/DatabaseWriteUsage.cs
+++ b/osu.Game/Database/DatabaseWriteUsage.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Database
/// Whether this write usage will commit a transaction on completion.
/// If false, there is a parent usage responsible for transaction commit.
///
- public bool IsTransactionLeader = false;
+ public bool IsTransactionLeader;
protected void Dispose(bool disposing)
{
diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs
new file mode 100644
index 0000000000..a48da37804
--- /dev/null
+++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs
@@ -0,0 +1,103 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Utils;
+using osu.Game.Configuration;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Graphics.Backgrounds
+{
+ public class SeasonalBackgroundLoader : Component
+ {
+ ///
+ /// Fired when background should be changed due to receiving backgrounds from API
+ /// or when the user setting is changed (as it might require unloading the seasonal background).
+ ///
+ public event Action SeasonalBackgroundChanged;
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ private readonly IBindable apiState = new Bindable();
+ private Bindable seasonalBackgroundMode;
+ private Bindable seasonalBackgrounds;
+
+ private int current;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config, SessionStatics sessionStatics)
+ {
+ seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode);
+ seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke());
+
+ seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds);
+ seasonalBackgrounds.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke());
+
+ apiState.BindTo(api.State);
+ apiState.BindValueChanged(fetchSeasonalBackgrounds, true);
+ }
+
+ private void fetchSeasonalBackgrounds(ValueChangedEvent stateChanged)
+ {
+ if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online)
+ return;
+
+ var request = new GetSeasonalBackgroundsRequest();
+ request.Success += response =>
+ {
+ seasonalBackgrounds.Value = response;
+ current = RNG.Next(0, response.Backgrounds?.Count ?? 0);
+ };
+
+ api.PerformAsync(request);
+ }
+
+ public SeasonalBackground LoadNextBackground()
+ {
+ if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never
+ || (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason))
+ {
+ return null;
+ }
+
+ var backgrounds = seasonalBackgrounds.Value?.Backgrounds;
+ if (backgrounds == null || !backgrounds.Any())
+ return null;
+
+ current = (current + 1) % backgrounds.Count;
+ string url = backgrounds[current].Url;
+
+ return new SeasonalBackground(url);
+ }
+
+ private bool isInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate;
+ }
+
+ [LongRunningLoad]
+ public class SeasonalBackground : Background
+ {
+ private readonly string url;
+ private const string fallback_texture_name = @"Backgrounds/bg1";
+
+ public SeasonalBackground(string url)
+ {
+ this.url = url;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(LargeTextureStore textures)
+ {
+ Sprite.Texture = textures.Get(url) ?? textures.Get(fallback_texture_name);
+ // ensure we're not loading in without a transition.
+ this.FadeInFromZero(200, Easing.InOutSine);
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
index ed5c73bee6..b9122d254d 100644
--- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers
/// Allows controlling the scroll bar from any position in the container using the right mouse button.
/// Uses the value of to smoothly scroll to the dragged location.
///
- public bool RightMouseScrollbar = false;
+ public bool RightMouseScrollbar;
///
/// Controls the rate with which the target position is approached when performing a relative drag. Default is 0.02.
diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs
index a30f961daf..f74574e60c 100644
--- a/osu.Game/IO/Archives/ArchiveReader.cs
+++ b/osu.Game/IO/Archives/ArchiveReader.cs
@@ -41,7 +41,7 @@ namespace osu.Game.IO.Archives
return null;
byte[] buffer = new byte[input.Length];
- await input.ReadAsync(buffer, 0, buffer.Length);
+ await input.ReadAsync(buffer);
return buffer;
}
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 41be4cfcc3..3de4bb1f9d 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -67,6 +67,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed),
new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
+ new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
};
public IEnumerable AudioControlKeyBindings => new[]
@@ -187,5 +188,8 @@ namespace osu.Game.Input.Bindings
[Description("Timing Mode")]
EditorTimingMode,
+
+ [Description("Hold for HUD")]
+ HoldForHUD,
}
}
diff --git a/osu.Game/Online/API/Requests/GetSeasonalBackgroundsRequest.cs b/osu.Game/Online/API/Requests/GetSeasonalBackgroundsRequest.cs
new file mode 100644
index 0000000000..941b47244a
--- /dev/null
+++ b/osu.Game/Online/API/Requests/GetSeasonalBackgroundsRequest.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Online.API.Requests
+{
+ public class GetSeasonalBackgroundsRequest : APIRequest
+ {
+ protected override string Target => @"seasonal-backgrounds";
+ }
+}
diff --git a/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs b/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs
new file mode 100644
index 0000000000..8e395f7397
--- /dev/null
+++ b/osu.Game/Online/API/Requests/Responses/APISeasonalBackgrounds.cs
@@ -0,0 +1,24 @@
+// 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 Newtonsoft.Json;
+
+namespace osu.Game.Online.API.Requests.Responses
+{
+ public class APISeasonalBackgrounds
+ {
+ [JsonProperty("ends_at")]
+ public DateTimeOffset EndDate;
+
+ [JsonProperty("backgrounds")]
+ public List Backgrounds { get; set; }
+ }
+
+ public class APISeasonalBackground
+ {
+ [JsonProperty("url")]
+ public string Url { get; set; }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
index d5de32ed05..7682967d10 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
@@ -39,6 +39,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
LabelText = "Background source",
Current = config.GetBindable(OsuSetting.MenuBackgroundSource),
Items = Enum.GetValues(typeof(BackgroundSource)).Cast()
+ },
+ new SettingsDropdown
+ {
+ LabelText = "Seasonal backgrounds",
+ Current = config.GetBindable(OsuSetting.SeasonalBackgroundMode),
+ Items = Enum.GetValues(typeof(SeasonalBackgroundMode)).Cast()
}
};
}
diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
index cf5c88b8fd..b671f4c68c 100644
--- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
+++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
@@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Replays
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
///
- public bool FrameAccuratePlayback = false;
+ public bool FrameAccuratePlayback;
protected bool HasFrames => Frames.Count > 0;
diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs
index 9a0217a1eb..4cadfa9ad4 100644
--- a/osu.Game/Rulesets/UI/HitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Performance;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.UI
diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
index ef41c5be3d..8beb955824 100644
--- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
+++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -25,6 +26,7 @@ namespace osu.Game.Screens.Backgrounds
private Bindable skin;
private Bindable mode;
private Bindable introSequence;
+ private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader();
[Resolved]
private IBindable beatmap { get; set; }
@@ -42,15 +44,18 @@ namespace osu.Game.Screens.Backgrounds
mode = config.GetBindable(OsuSetting.MenuBackgroundSource);
introSequence = config.GetBindable(OsuSetting.IntroSequence);
+ AddInternal(seasonalBackgroundLoader);
+
user.ValueChanged += _ => Next();
skin.ValueChanged += _ => Next();
mode.ValueChanged += _ => Next();
beatmap.ValueChanged += _ => Next();
introSequence.ValueChanged += _ => Next();
+ seasonalBackgroundLoader.SeasonalBackgroundChanged += Next;
currentDisplay = RNG.Next(0, background_count);
- display(createBackground());
+ Next();
}
private void display(Background newBackground)
@@ -63,11 +68,14 @@ namespace osu.Game.Screens.Backgrounds
}
private ScheduledDelegate nextTask;
+ private CancellationTokenSource cancellationTokenSource;
public void Next()
{
nextTask?.Cancel();
- nextTask = Scheduler.AddDelayed(() => { LoadComponentAsync(createBackground(), display); }, 100);
+ cancellationTokenSource?.Cancel();
+ cancellationTokenSource = new CancellationTokenSource();
+ nextTask = Scheduler.AddDelayed(() => LoadComponentAsync(createBackground(), display, cancellationTokenSource.Token), 100);
}
private Background createBackground()
@@ -75,6 +83,14 @@ namespace osu.Game.Screens.Backgrounds
Background newBackground;
string backgroundName;
+ var seasonalBackground = seasonalBackgroundLoader.LoadNextBackground();
+
+ if (seasonalBackground != null)
+ {
+ seasonalBackground.Depth = currentDisplay;
+ return seasonalBackground;
+ }
+
switch (introSequence.Value)
{
case IntroSequence.Welcome:
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index 5ac360d029..fa98358dbe 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -210,10 +210,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
if (DragBox.State == Visibility.Visible)
- {
DragBox.Hide();
- SelectionHandler.UpdateVisibility();
- }
}
protected override bool OnKeyDown(KeyDownEvent e)
@@ -352,11 +349,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
///
/// Selects all s.
///
- private void selectAll()
- {
- SelectionBlueprints.ToList().ForEach(m => m.Select());
- SelectionHandler.UpdateVisibility();
- }
+ private void selectAll() => SelectionBlueprints.ToList().ForEach(m => m.Select());
///
/// Deselects all selected s.
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
index 01e23bafc5..e346630235 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
@@ -201,8 +201,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
// there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once.
if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject))
EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject);
-
- UpdateVisibility();
}
///
@@ -214,8 +212,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
selectedBlueprints.Remove(blueprint);
EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
-
- UpdateVisibility();
}
///
@@ -226,13 +222,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state)
{
if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right))
- EditorBeatmap.Remove(blueprint.HitObject);
+ handleQuickDeletion(blueprint);
else if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left))
blueprint.ToggleSelection();
else
ensureSelected(blueprint);
}
+ private void handleQuickDeletion(SelectionBlueprint blueprint)
+ {
+ if (!blueprint.IsSelected)
+ EditorBeatmap.Remove(blueprint.HitObject);
+ else
+ deleteSelected();
+ }
+
private void ensureSelected(SelectionBlueprint blueprint)
{
if (blueprint.IsSelected)
@@ -254,23 +258,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
///
/// Updates whether this is visible.
///
- internal void UpdateVisibility()
+ private void updateVisibility()
{
int count = selectedBlueprints.Count;
selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty;
- if (count > 0)
- {
- Show();
- OnSelectionChanged();
- }
- else
- Hide();
+ this.FadeTo(count > 0 ? 1 : 0);
+ OnSelectionChanged();
}
///
- /// Triggered whenever more than one object is selected, on each change.
+ /// Triggered whenever the set of selected objects changes.
/// Should update the selection box's state to match supported operations.
///
protected virtual void OnSelectionChanged()
@@ -421,7 +420,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
// bring in updates from selection changes
EditorBeatmap.HitObjectUpdated += _ => UpdateTernaryStates();
- EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => UpdateTernaryStates();
+ EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) =>
+ {
+ Scheduler.AddOnce(updateVisibility);
+ UpdateTernaryStates();
+ };
}
///
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
index 84328466c3..10913a8bb9 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
@@ -9,10 +9,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
@@ -137,8 +137,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private class TimelineDragBox : DragBox
{
- private Vector2 lastMouseDown;
- private float localMouseDown;
+ // the following values hold the start and end X positions of the drag box in the timeline's local space,
+ // but with zoom unapplied in order to be able to compensate for positional changes
+ // while the timeline is being zoomed in/out.
+ private float? selectionStart;
+ private float selectionEnd;
+
+ [Resolved]
+ private Timeline timeline { get; set; }
public TimelineDragBox(Action performSelect)
: base(performSelect)
@@ -153,21 +159,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public override bool HandleDrag(MouseButtonEvent e)
{
- // store the original position of the mouse down, as we may be scrolled during selection.
- if (lastMouseDown != e.ScreenSpaceMouseDownPosition)
- {
- lastMouseDown = e.ScreenSpaceMouseDownPosition;
- localMouseDown = e.MouseDownPosition.X;
- }
+ selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom;
- float selection1 = localMouseDown;
- float selection2 = e.MousePosition.X;
+ // only calculate end when a transition is not in progress to avoid bouncing.
+ if (Precision.AlmostEquals(timeline.CurrentZoom, timeline.Zoom))
+ selectionEnd = e.MousePosition.X / timeline.CurrentZoom;
- Box.X = Math.Min(selection1, selection2);
- Box.Width = Math.Abs(selection1 - selection2);
+ updateDragBoxPosition();
+ return true;
+ }
+
+ private void updateDragBoxPosition()
+ {
+ if (selectionStart == null)
+ return;
+
+ float rescaledStart = selectionStart.Value * timeline.CurrentZoom;
+ float rescaledEnd = selectionEnd * timeline.CurrentZoom;
+
+ Box.X = Math.Min(rescaledStart, rescaledEnd);
+ Box.Width = Math.Abs(rescaledStart - rescaledEnd);
PerformSelection?.Invoke(Box.ScreenSpaceDrawQuad.AABBFloat);
- return true;
+ }
+
+ public override void Hide()
+ {
+ base.Hide();
+ selectionStart = null;
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
index 227eecf9c7..f90658e99c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
@@ -29,9 +29,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private readonly Container zoomedContent;
protected override Container Content => zoomedContent;
-
private float currentZoom = 1;
+ ///
+ /// The current zoom level of .
+ /// It may differ from during transitions.
+ ///
+ public float CurrentZoom => currentZoom;
+
[Resolved(canBeNull: true)]
private IFrameBasedClock editorClock { get; set; }
diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
index 60c6aa1d8a..c7c37cbc0d 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset));
if (!string.IsNullOrEmpty(criteria.SearchString))
- matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0);
+ matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase));
r.MatchingFilter = matchingFilter;
}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index b047d44f8a..e83dded075 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -8,8 +8,10 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
+using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
@@ -22,7 +24,7 @@ using osuTK.Input;
namespace osu.Game.Screens.Play
{
[Cached]
- public class HUDOverlay : Container
+ public class HUDOverlay : Container, IKeyBindingHandler
{
public const float FADE_DURATION = 400;
@@ -67,6 +69,8 @@ namespace osu.Game.Screens.Play
internal readonly IBindable IsBreakTime = new Bindable();
+ private bool holdingForHUD;
+
private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods)
@@ -217,17 +221,18 @@ namespace osu.Game.Screens.Play
if (ShowHud.Disabled)
return;
+ if (holdingForHUD)
+ {
+ ShowHud.Value = true;
+ return;
+ }
+
switch (configVisibilityMode.Value)
{
case HUDVisibilityMode.Never:
ShowHud.Value = false;
break;
- case HUDVisibilityMode.HideDuringBreaks:
- // always show during replay as we want the seek bar to be visible.
- ShowHud.Value = replayLoaded.Value || !IsBreakTime.Value;
- break;
-
case HUDVisibilityMode.HideDuringGameplay:
// always show during replay as we want the seek bar to be visible.
ShowHud.Value = replayLoaded.Value || IsBreakTime.Value;
@@ -277,9 +282,21 @@ namespace osu.Game.Screens.Play
switch (e.Key)
{
case Key.Tab:
- configVisibilityMode.Value = configVisibilityMode.Value != HUDVisibilityMode.Never
- ? HUDVisibilityMode.Never
- : HUDVisibilityMode.HideDuringGameplay;
+ switch (configVisibilityMode.Value)
+ {
+ case HUDVisibilityMode.Never:
+ configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay;
+ break;
+
+ case HUDVisibilityMode.HideDuringGameplay:
+ configVisibilityMode.Value = HUDVisibilityMode.Always;
+ break;
+
+ case HUDVisibilityMode.Always:
+ configVisibilityMode.Value = HUDVisibilityMode.Never;
+ break;
+ }
+
return true;
}
}
@@ -351,5 +368,29 @@ namespace osu.Game.Screens.Play
HealthDisplay?.BindHealthProcessor(processor);
FailingLayer?.BindHealthProcessor(processor);
}
+
+ public bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.HoldForHUD:
+ holdingForHUD = true;
+ updateVisibility();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.HoldForHUD:
+ holdingForHUD = false;
+ updateVisibility();
+ break;
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index 30747438c3..cb4560802b 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -29,6 +29,8 @@ namespace osu.Game.Screens.Ranking.Expanded
private const float padding = 10;
private readonly ScoreInfo score;
+ private readonly bool withFlair;
+
private readonly List statisticDisplays = new List();
private FillFlowContainer starAndModDisplay;
@@ -41,9 +43,11 @@ namespace osu.Game.Screens.Ranking.Expanded
/// Creates a new .
///
/// The score to display.
- public ExpandedPanelMiddleContent(ScoreInfo score)
+ /// Whether to add flair for a new score being set.
+ public ExpandedPanelMiddleContent(ScoreInfo score, bool withFlair = false)
{
this.score = score;
+ this.withFlair = withFlair;
RelativeSizeAxes = Axes.Both;
Masking = true;
@@ -266,6 +270,9 @@ namespace osu.Game.Screens.Ranking.Expanded
delay += 200;
}
}
+
+ if (!withFlair)
+ FinishTransforms(true);
});
}
}
diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs
index 026ce01857..f8bdf0140c 100644
--- a/osu.Game/Screens/Ranking/ResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/ResultsScreen.cs
@@ -149,7 +149,7 @@ namespace osu.Game.Screens.Ranking
};
if (Score != null)
- ScorePanelList.AddScore(Score);
+ ScorePanelList.AddScore(Score, true);
if (player != null && allowRetry)
{
diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs
index ee97ee55eb..df710e4eb8 100644
--- a/osu.Game/Screens/Ranking/ScorePanel.cs
+++ b/osu.Game/Screens/Ranking/ScorePanel.cs
@@ -85,6 +85,8 @@ namespace osu.Game.Screens.Ranking
public readonly ScoreInfo Score;
+ private bool displayWithFlair;
+
private Container content;
private Container topLayerContainer;
@@ -97,9 +99,10 @@ namespace osu.Game.Screens.Ranking
private Container middleLayerContentContainer;
private Drawable middleLayerContent;
- public ScorePanel(ScoreInfo score)
+ public ScorePanel(ScoreInfo score, bool isNewLocalScore = false)
{
Score = score;
+ displayWithFlair = isNewLocalScore;
}
[BackgroundDependencyLoader]
@@ -188,7 +191,7 @@ namespace osu.Game.Screens.Ranking
state = value;
- if (LoadState >= LoadState.Ready)
+ if (IsLoaded)
updateState();
StateChanged?.Invoke(value);
@@ -209,7 +212,10 @@ namespace osu.Game.Screens.Ranking
middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint);
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0));
- middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0));
+ middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0));
+
+ // only the first expanded display should happen with flair.
+ displayWithFlair = false;
break;
case PanelState.Contracted:
diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs
index 0d7d339df0..77b3d8fc3b 100644
--- a/osu.Game/Screens/Ranking/ScorePanelList.cs
+++ b/osu.Game/Screens/Ranking/ScorePanelList.cs
@@ -95,9 +95,10 @@ namespace osu.Game.Screens.Ranking
/// Adds a to this list.
///
/// The to add.
- public ScorePanel AddScore(ScoreInfo score)
+ /// Whether this is a score that has just been achieved locally. Controls whether flair is added to the display or not.
+ public ScorePanel AddScore(ScoreInfo score, bool isNewLocalScore = false)
{
- var panel = new ScorePanel(score)
+ var panel = new ScorePanel(score, isNewLocalScore)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -117,19 +118,24 @@ namespace osu.Game.Screens.Ranking
d.Origin = Anchor.Centre;
}));
- if (SelectedScore.Value == score)
- selectedScoreChanged(new ValueChangedEvent(SelectedScore.Value, SelectedScore.Value));
- else
+ if (IsLoaded)
{
- // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done.
- // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel.
- if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score))
+ if (SelectedScore.Value == score)
{
- // A somewhat hacky property is used here because we need to:
- // 1) Scroll after the scroll container's visible range is updated.
- // 2) Scroll before the scroll container's scroll position is updated.
- // Without this, we would have a 1-frame positioning error which looks very jarring.
- scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing;
+ SelectedScore.TriggerChange();
+ }
+ else
+ {
+ // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done.
+ // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel.
+ if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score))
+ {
+ // A somewhat hacky property is used here because we need to:
+ // 1) Scroll after the scroll container's visible range is updated.
+ // 2) Scroll before the scroll container's scroll position is updated.
+ // Without this, we would have a 1-frame positioning error which looks very jarring.
+ scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing;
+ }
}
}
@@ -142,11 +148,15 @@ namespace osu.Game.Screens.Ranking
/// The to present.
private void selectedScoreChanged(ValueChangedEvent score)
{
- // Contract the old panel.
- foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue))
+ // avoid contracting panels unnecessarily when TriggerChange is fired manually.
+ if (score.OldValue != score.NewValue)
{
- t.Panel.State = PanelState.Contracted;
- t.Margin = new MarginPadding();
+ // Contract the old panel.
+ foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue))
+ {
+ t.Panel.State = PanelState.Contracted;
+ t.Margin = new MarginPadding();
+ }
}
// Find the panel corresponding to the new score.
@@ -162,12 +172,16 @@ namespace osu.Game.Screens.Ranking
expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing };
expandedPanel.State = PanelState.Expanded;
- // Scroll to the new panel. This is done manually since we need:
- // 1) To scroll after the scroll container's visible range is updated.
- // 2) To account for the centre anchor/origins of panels.
- // In the end, it's easier to compute the scroll position manually.
- float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing);
- scroll.ScrollTo(scrollOffset);
+ // requires schedule after children to ensure the flow (and thus ScrollContainer's ScrollableExtent) has been updated.
+ ScheduleAfterChildren(() =>
+ {
+ // Scroll to the new panel. This is done manually since we need:
+ // 1) To scroll after the scroll container's visible range is updated.
+ // 2) To account for the centre anchor/origins of panels.
+ // In the end, it's easier to compute the scroll position manually.
+ float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing);
+ scroll.ScrollTo(scrollOffset);
+ });
}
protected override void Update()
diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs
index 0ee52f3e48..71f78c5c95 100644
--- a/osu.Game/Screens/Select/BeatmapDetails.cs
+++ b/osu.Game/Screens/Select/BeatmapDetails.cs
@@ -300,6 +300,7 @@ namespace osu.Game.Screens.Select
public MetadataSection(string title)
{
+ Alpha = 0;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
index dce4028f17..1aab50037a 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Screens.Select.Carousel
var terms = Beatmap.SearchableTerms;
foreach (var criteriaTerm in criteria.SearchTerms)
- match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0);
+ match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase));
// if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs.
// this should be done after text matching so we can prioritise matching numbers in metadata.
diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs
index f34f8f6505..7bddb3e51b 100644
--- a/osu.Game/Screens/Select/FilterCriteria.cs
+++ b/osu.Game/Screens/Select/FilterCriteria.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Screens.Select
if (string.IsNullOrEmpty(value))
return false;
- return value.IndexOf(SearchTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
+ return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase);
}
public string SearchTerm;
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index 19769f487d..ee8825640c 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -32,11 +32,7 @@ namespace osu.Game.Screens.Select
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () =>
- {
- ValidForResume = false;
- Edit();
- });
+ BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => Edit());
((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += PresentScore;
}
diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
index ab4fb38657..1e43e5d148 100644
--- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
+++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
@@ -180,9 +180,8 @@ namespace osu.Game.Tests.Beatmaps
private readonly BeatmapInfo skinBeatmapInfo;
private readonly IResourceStore resourceStore;
- public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio,
- double length = 60000)
- : base(beatmap, storyboard, referenceClock, audio, length)
+ public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio)
+ : base(beatmap, storyboard, referenceClock, audio)
{
this.skinBeatmapInfo = skinBeatmapInfo;
this.resourceStore = resourceStore;
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index b59a1db403..e32ed07863 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -23,6 +23,7 @@ using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens;
using osu.Game.Storyboards;
@@ -222,18 +223,23 @@ namespace osu.Game.Tests.Visual
/// The storyboard.
/// An optional clock which should be used instead of a stopwatch for virtual time progression.
/// Audio manager. Required if a reference clock isn't provided.
- /// The length of the returned virtual track.
- public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000)
+ public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio)
: base(beatmap, storyboard, audio)
{
+ double trackLength = 60000;
+
+ if (beatmap.HitObjects.Count > 0)
+ // add buffer after last hitobject to allow for final replay frames etc.
+ trackLength = Math.Max(trackLength, beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000);
+
if (referenceClock != null)
{
store = new TrackVirtualStore(referenceClock);
audio.AddItem(store);
- track = store.GetVirtual(length);
+ track = store.GetVirtual(trackLength);
}
else
- track = audio?.Tracks.GetVirtual(length);
+ track = audio?.Tracks.GetVirtual(trackLength);
}
~ClockBackedTestWorkingBeatmap()
diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs
index aa3bd2e4b7..088e997de9 100644
--- a/osu.Game/Tests/Visual/PlayerTestScene.cs
+++ b/osu.Game/Tests/Visual/PlayerTestScene.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual
///
/// Whether custom test steps are provided. Custom tests should invoke to create the test steps.
///
- protected virtual bool HasCustomSteps { get; } = false;
+ protected virtual bool HasCustomSteps => false;
protected TestPlayer Player;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ca588b89d9..9be933c74a 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9c22dec330..e26f8cc8b4 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -80,7 +80,7 @@
-
+