diff --git a/appveyor.yml b/appveyor.yml
index 4dcaa7b45e..be1727e7d7 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,7 +2,5 @@ clone_depth: 1
version: '{branch}-{build}'
image: Previous Visual Studio 2017
test: off
-install:
- - cmd: git submodule update --init --recursive --depth=5
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
new file mode 100644
index 0000000000..d36298175b
--- /dev/null
+++ b/appveyor_deploy.yml
@@ -0,0 +1,10 @@
+clone_depth: 1
+version: '{build}'
+image: Previous Visual Studio 2017
+test: off
+skip_non_tags: true
+build_script:
+ - cmd: PowerShell -Version 2.0 .\build.ps1
+deploy:
+ - provider: Environment
+ name: nuget
diff --git a/osu.Android.props b/osu.Android.props
index 90d1854c39..adc340a734 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -61,6 +61,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index e96c7d8f92..720ef1db42 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osuTK.Graphics;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -99,8 +100,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public Texture GetTexture(string componentName) =>
throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration =>
- throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index fe73e7c861..02c65db6ad 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
@@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default;
+ public IBindable GetConfig(TLookup lookup) => null;
public event Action SourceChanged;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 1749ea1f60..00c953c393 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -166,12 +167,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.SkinChanged(skin, allowFallback);
- Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
- sliderPathRadius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+ Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
+ sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
updatePathRadius();
- Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value;
- Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
+ Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
+ Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
}
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index 7c871c6ccd..ef7b077480 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -11,6 +11,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
@@ -218,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both;
- float radius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+ float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
InternalChild = new CircularContainer
{
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 27899ab56e..ceb9ed9343 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
- public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkin(source);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
public override int? LegacyID => 0;
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
index ec838c596d..81c02199d0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, DrawableHitObject drawableObject)
{
- animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White;
+ animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
InternalChildren = new[]
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
similarity index 70%
rename from osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs
rename to osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index e3e302b81c..5957b81d7e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -3,21 +3,19 @@
using System;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning
{
- public class OsuLegacySkin : ISkin
+ public class OsuLegacySkinTransformer : ISkin
{
private readonly ISkin source;
- private Lazy configuration;
-
private Lazy hasHitCircle;
///
@@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
///
private const float legacy_circle_radius = 64 - 5;
- public OsuLegacySkin(ISkinSource source)
+ public OsuLegacySkinTransformer(ISkinSource source)
{
this.source = source;
@@ -37,21 +35,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
private void sourceChanged()
{
- // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source.
- configuration = new Lazy(() =>
- {
- var config = new SkinConfiguration();
- if (hasHitCircle.Value)
- config.SliderPathRadius = legacy_circle_radius;
-
- // defaults should only be applied for non-beatmap skins (which are parsed via this constructor).
- config.CustomColours["SliderBall"] =
- source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null)
- ?? new Color4(2, 170, 255, 255);
-
- return config;
- });
-
hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null);
}
@@ -96,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
case OsuSkinComponents.HitCircleText:
- string font = GetValue(config => config.HitCircleFont);
- var overlap = GetValue(config => config.HitCircleOverlap);
+ var font = GetConfig(OsuSkinConfiguration.HitCircleFont)?.Value ?? "default";
+ var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? 0;
return !hasFont(font)
? null
@@ -116,13 +99,27 @@ namespace osu.Game.Rulesets.Osu.Skinning
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
+ public IBindable GetConfig(TLookup lookup)
{
- TValue val;
- if (configuration.Value is TConfiguration conf && (val = query.Invoke(conf)) != null)
- return val;
+ switch (lookup)
+ {
+ case OsuSkinColour colour:
+ return source.GetConfig(new SkinCustomColourLookup(colour));
- return source.GetValue(query);
+ case OsuSkinConfiguration osuLookup:
+ switch (osuLookup)
+ {
+ case OsuSkinConfiguration.SliderPathRadius:
+ if (hasHitCircle.Value)
+ return SkinUtils.As(new BindableFloat(legacy_circle_radius));
+
+ break;
+ }
+
+ break;
+ }
+
+ return source.GetConfig(lookup);
}
private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null;
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
new file mode 100644
index 0000000000..4e6d3ef0e4
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.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.
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public enum OsuSkinColour
+ {
+ SliderTrackOverride,
+ SliderBorder,
+ SliderBall
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
new file mode 100644
index 0000000000..a6b87150ae
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -0,0 +1,14 @@
+// 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.Rulesets.Osu.Skinning
+{
+ public enum OsuSkinConfiguration
+ {
+ HitCircleFont,
+ HitCircleOverlap,
+ SliderBorderSize,
+ SliderPathRadius,
+ CursorExpand,
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
index 869c27dcac..ac641ecfbc 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
- cursorExpand = skin.GetValue(s => s.CursorExpand ?? true);
+ cursorExpand = skin.GetConfig(OsuSkinConfiguration.CursorExpand)?.Value ?? true;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Tests/Resources/skin.ini b/osu.Game.Tests/Resources/skin.ini
index 0e5737b4ea..7f7f0b32a6 100644
--- a/osu.Game.Tests/Resources/skin.ini
+++ b/osu.Game.Tests/Resources/skin.ini
@@ -1,5 +1,6 @@
[General]
Name: test skin
+TestLookup: TestValue
[Colours]
Combo1 : 142,199,255
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index 24ef9e4535..8bd846518b 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -41,5 +41,20 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
+
+ [Test]
+ public void TestDecodeGeneral()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin.ini"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var config = decoder.Decode(stream);
+
+ Assert.AreEqual("test skin", config.SkinInfo.Name);
+ Assert.AreEqual("TestValue", config.ConfigDictionary["TestLookup"]);
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
new file mode 100644
index 0000000000..bbcc4140a9
--- /dev/null
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -0,0 +1,156 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Skins
+{
+ [TestFixture]
+ public class TestSceneSkinConfigurationLookup : OsuTestScene
+ {
+ private LegacySkin source1;
+ private LegacySkin source2;
+ private SkinRequester requester;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ Add(new SkinProvidingContainer(source1 = new SkinSource())
+ .WithChild(new SkinProvidingContainer(source2 = new SkinSource())
+ .WithChild(requester = new SkinRequester())));
+ });
+
+ [Test]
+ public void TestBasicLookup()
+ {
+ AddStep("Add config values", () =>
+ {
+ source1.Configuration.ConfigDictionary["Lookup"] = "source1";
+ source2.Configuration.ConfigDictionary["Lookup"] = "source2";
+ });
+
+ AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2");
+ }
+
+ [Test]
+ public void TestFloatLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1");
+ AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f);
+ }
+
+ [Test]
+ public void TestBoolLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1");
+ AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true);
+ }
+
+ [Test]
+ public void TestEnumLookup()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2");
+ AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2);
+ }
+
+ [Test]
+ public void TestLookupFailure()
+ {
+ AddAssert("Check lookup failure", () => requester.GetConfig("Lookup") == null);
+ }
+
+ [Test]
+ public void TestLookupNull()
+ {
+ AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null);
+
+ AddAssert("Check lookup null", () =>
+ {
+ var bindable = requester.GetConfig("Lookup");
+ return bindable != null && bindable.Value == null;
+ });
+ }
+
+ [Test]
+ public void TestColourLookup()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+ AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red);
+ }
+
+ [Test]
+ public void TestGlobalLookup()
+ {
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ }
+
+ [Test]
+ public void TestWrongColourType()
+ {
+ AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red);
+
+ AddAssert("perform incorrect lookup", () =>
+ {
+ try
+ {
+ requester.GetConfig(new SkinCustomColourLookup("Lookup"));
+ return false;
+ }
+ catch
+ {
+ return true;
+ }
+ });
+ }
+
+ public enum LookupType
+ {
+ Test
+ }
+
+ public enum ValueType
+ {
+ Test1,
+ Test2,
+ Test3
+ }
+
+ public class SkinSource : LegacySkin
+ {
+ public SkinSource()
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ }
+ }
+
+ public class SkinRequester : Drawable, ISkin
+ {
+ private ISkinSource skin;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ this.skin = skin;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+
+ public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index cc275009ba..c1635ffc83 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
@@ -17,7 +17,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- [System.ComponentModel.Description("player pause/fail screens")]
+ [Description("player pause/fail screens")]
public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) };
@@ -152,7 +152,8 @@ namespace osu.Game.Tests.Visual.Gameplay
}
///
- /// Tests that entering menu with cursor initially on button selects it.
+ /// Tests that entering menu with cursor initially on button doesn't selects it immediately.
+ /// This is to allow for stable keyboard navigation.
///
[Test]
public void TestInitialButtonHover()
@@ -164,6 +165,10 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Hide overlay", () => pauseOverlay.Hide());
showOverlay();
+ AddAssert("First button not selected", () => !getButton(0).Selected.Value);
+
+ AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
+
AddAssert("First button selected", () => getButton(0).Selected.Value);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index ee5552c6e0..b3d4820737 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -5,7 +5,9 @@ using System;
using System.Globalization;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -133,11 +135,54 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
+ [Test]
+ public void TestSwitchOff()
+ {
+ SkinConsumer consumer = null;
+ SwitchableSkinProvidingContainer target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new SwitchableSkinProvidingContainer(new SecondarySource())
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddStep("disable", () => target.Disable());
+ AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
+ }
+
+ private class SwitchableSkinProvidingContainer : SkinProvidingContainer
+ {
+ private bool allow = true;
+
+ protected override bool AllowDrawableLookup(ISkinComponent component) => allow;
+
+ public void Disable()
+ {
+ allow = false;
+ TriggerSourceChanged();
+ }
+
+ public SwitchableSkinProvidingContainer(ISkin skin)
+ : base(skin)
+ {
+ }
+ }
+
private class ExposedSkinnableDrawable : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
- public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null,
+ ConfineMode confineMode = ConfineMode.ScaleDownToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
@@ -256,7 +301,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
private class SecondarySource : ISkin
@@ -267,10 +312,11 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
- private class SkinSourceContainer : Container, ISkin
+ [Cached(typeof(ISkinSource))]
+ private class SkinSourceContainer : Container, ISkinSource
{
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
@@ -278,7 +324,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
}
private class TestSkinComponent : ISkinComponent
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
index f2718b8e80..13116de320 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs
@@ -10,14 +10,9 @@ namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneDisclaimer : ScreenTestScene
{
- [Cached(typeof(IAPIProvider))]
- private readonly DummyAPIAccess api = new DummyAPIAccess();
-
[BackgroundDependencyLoader]
- private void load()
+ private void load(IAPIProvider api)
{
- Add(api);
-
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("toggle support", () =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
index fa3c392b2e..723e5fc03d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -14,6 +14,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboard : MultiplayerTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public TestSceneMatchLeaderboard()
{
Room.RoomID.Value = 3;
@@ -27,11 +29,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
}
- [Resolved]
- private IAPIProvider api { get; set; }
-
[BackgroundDependencyLoader]
- private void load()
+ private void load(IAPIProvider api)
{
var req = new GetRoomScoresRequest();
req.Success += v => { };
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
index 069e133c2b..b646433846 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestFixture]
public class TestSceneMultiScreen : ScreenTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(Screens.Multi.Multiplayer),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
index 35449f5687..66ab1fe18a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
@@ -25,17 +25,14 @@ namespace osu.Game.Tests.Visual.Online
typeof(AccountCreationScreen),
};
- [Cached(typeof(IAPIProvider))]
- private DummyAPIAccess api = new DummyAPIAccess();
+ private readonly Container userPanelArea;
public TestSceneAccountCreationOverlay()
{
- Container userPanelArea;
AccountCreationOverlay accountCreation;
Children = new Drawable[]
{
- api,
accountCreation = new AccountCreationOverlay(),
userPanelArea = new Container
{
@@ -46,11 +43,16 @@ namespace osu.Game.Tests.Visual.Online
},
};
+ AddStep("show", () => accountCreation.Show());
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IAPIProvider api)
+ {
api.Logout();
api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
- AddStep("show", () => accountCreation.Show());
- AddStep("logout", () => api.Logout());
+ AddStep("logout", api.Logout);
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index ee9e088dcc..5068064a1f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -42,6 +42,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapAvailability),
};
+ protected override bool RequiresAPIAccess => true;
+
private RulesetInfo taikoRuleset;
private RulesetInfo maniaRuleset;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index cf8bac7642..324291c9d7 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(Comments),
};
+ protected override bool RequiresAPIAccess => true;
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
index 75c2a2a6a1..14ae975806 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
{
private DirectOverlay direct;
+ protected override bool RequiresAPIAccess => true;
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
index 838347800f..c98f98c23d 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs
@@ -17,14 +17,15 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneHistoricalSection : OsuTestScene
{
- public override IReadOnlyList RequiredTypes =>
- new[]
- {
- typeof(HistoricalSection),
- typeof(PaginatedMostPlayedBeatmapContainer),
- typeof(DrawableMostPlayedBeatmap),
- typeof(DrawableProfileRow)
- };
+ protected override bool RequiresAPIAccess => true;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(HistoricalSection),
+ typeof(PaginatedMostPlayedBeatmapContainer),
+ typeof(DrawableMostPlayedBeatmap),
+ typeof(DrawableProfileRow)
+ };
public TestSceneHistoricalSection()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
index 5cb96c7ed2..806b36e855 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneSocialOverlay : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(UserPanel),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 2285c9b799..555d5334d8 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserProfileHeader : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(ProfileHeader),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 84c99d8c3a..42c8ffbf0a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserProfileOverlay : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
private readonly TestUserProfileOverlay profile;
[Resolved]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
index 9f0a8c769a..d777f9766a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneUserRanks : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
public TestSceneUserRanks()
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
index 9cdfcb6cc4..fdc50be3fa 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene
{
+ protected override bool RequiresAPIAccess => true;
+
private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
private RulesetStore rulesets;
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 81f517dd86..8014631eca 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
- Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
+ Icon = ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
}
};
}
diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs
index d5cdd7e4bc..02382cfd2b 100644
--- a/osu.Game/Configuration/DatabasedConfigManager.cs
+++ b/osu.Game/Configuration/DatabasedConfigManager.cs
@@ -16,11 +16,11 @@ namespace osu.Game.Configuration
private readonly int? variant;
- private readonly List databasedSettings;
+ private List databasedSettings;
private readonly RulesetInfo ruleset;
- private readonly bool legacySettingsExist;
+ private bool legacySettingsExist;
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
{
@@ -28,21 +28,31 @@ namespace osu.Game.Configuration
this.ruleset = ruleset;
this.variant = variant;
- databasedSettings = settings.Query(ruleset?.ID, variant);
- legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
+ Load();
InitialiseDefaults();
}
protected override void PerformLoad()
{
+ databasedSettings = settings.Query(ruleset?.ID, variant);
+ legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
}
protected override bool PerformSave()
{
+ lock (dirtySettings)
+ {
+ foreach (var setting in dirtySettings)
+ settings.Update(setting);
+ dirtySettings.Clear();
+ }
+
return true;
}
+ private readonly List dirtySettings = new List();
+
protected override void AddBindable(T lookup, Bindable bindable)
{
base.AddBindable(lookup, bindable);
@@ -80,7 +90,12 @@ namespace osu.Game.Configuration
bindable.ValueChanged += b =>
{
setting.Value = b.NewValue;
- settings.Update(setting);
+
+ lock (dirtySettings)
+ {
+ if (!dirtySettings.Contains(setting))
+ dirtySettings.Add(setting);
+ }
};
}
}
diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs
index d13475189d..0f923c3a28 100644
--- a/osu.Game/Graphics/Backgrounds/Background.cs
+++ b/osu.Game/Graphics/Backgrounds/Background.cs
@@ -57,8 +57,9 @@ namespace osu.Game.Graphics.Backgrounds
AddInternal(bufferedContainer = new BufferedContainer
{
- CacheDrawnFrameBuffer = true,
RelativeSizeAxes = Axes.Both,
+ CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
Child = Sprite
});
}
diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
index 24816deeb5..12688da9df 100644
--- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
@@ -55,6 +55,7 @@ namespace osu.Game.Graphics.Sprites
Origin = Anchor.Centre,
BlurSigma = new Vector2(4),
CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Size = new Vector2(3f),
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index de8f316b06..d6b8ad3e67 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -207,6 +207,7 @@ namespace osu.Game
FileStore.Cleanup();
AddInternal(API);
+ AddInternal(RulesetConfigCache);
GlobalActionContainer globalBinding;
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index a3243a655e..cf42c8005a 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -346,10 +346,12 @@ namespace osu.Game.Overlays
public Background(WorkingBeatmap beatmap = null)
{
this.beatmap = beatmap;
- CacheDrawnFrameBuffer = true;
+
Depth = float.MaxValue;
RelativeSizeAxes = Axes.Both;
+ CacheDrawnFrameBuffer = true;
+
Children = new Drawable[]
{
sprite = new Sprite
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index f7efa625a5..ea1e825e6b 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -239,7 +239,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
- AccentColour.Value = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
+ {
+ var comboColours = skin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
+
+ AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
+ }
}
///
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index b63292757d..197c089f71 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
+using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Edit;
@@ -83,6 +84,8 @@ namespace osu.Game.Rulesets
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
+ public virtual IResourceStore CreateReourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources");
+
public abstract string Description { get; }
public virtual RulesetSettingsSubsection CreateSettings() => null;
diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs
index 8c9e3c94e2..cdcd2666cf 100644
--- a/osu.Game/Rulesets/RulesetConfigCache.cs
+++ b/osu.Game/Rulesets/RulesetConfigCache.cs
@@ -36,5 +36,14 @@ namespace osu.Game.Rulesets
return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore));
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ // ensures any potential database operations are finalised before game destruction.
+ foreach (var c in configCache.Values)
+ (c as IDisposable)?.Dispose();
+ }
}
}
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index d9cff86265..6a69fd8dd0 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -20,7 +20,12 @@ namespace osu.Game.Rulesets
[JsonIgnore]
public bool Available { get; set; }
- public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this);
+ public virtual Ruleset CreateInstance()
+ {
+ if (!Available) return null;
+
+ return (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this);
+ }
public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index a32407d180..a34bb6e8ea 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -11,13 +11,20 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Cursor;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Input.Events;
+using osu.Framework.IO.Stores;
using osu.Game.Configuration;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers;
@@ -51,6 +58,10 @@ namespace osu.Game.Rulesets.UI
private readonly Lazy playfield;
+ private TextureStore textureStore;
+
+ private ISampleStore localSampleStore;
+
///
/// The playfield.
///
@@ -142,6 +153,18 @@ namespace osu.Game.Rulesets.UI
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ var resources = Ruleset.CreateReourceStore();
+
+ if (resources != null)
+ {
+ textureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, "Textures")));
+ textureStore.AddStore(dependencies.Get());
+ dependencies.Cache(textureStore);
+
+ localSampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples"));
+ dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get()));
+ }
+
onScreenDisplay = dependencies.Get();
Config = dependencies.Get().GetConfigFor(Ruleset);
@@ -314,6 +337,8 @@ namespace osu.Game.Rulesets.UI
{
base.Dispose(isDisposing);
+ localSampleStore?.Dispose();
+
if (Config != null)
{
onScreenDisplay?.StopTracking(this, Config);
@@ -443,4 +468,50 @@ namespace osu.Game.Rulesets.UI
{
}
}
+
+ ///
+ /// A sample store which adds a fallback source.
+ ///
+ ///
+ /// This is a temporary implementation to workaround ISampleStore limitations.
+ ///
+ public class FallbackSampleStore : ISampleStore
+ {
+ private readonly ISampleStore primary;
+ private readonly ISampleStore secondary;
+
+ public FallbackSampleStore(ISampleStore primary, ISampleStore secondary)
+ {
+ this.primary = primary;
+ this.secondary = secondary;
+ }
+
+ public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name);
+
+ public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name);
+
+ public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name);
+
+ public IEnumerable GetAvailableResources() => throw new NotImplementedException();
+
+ public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException();
+
+ public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException();
+
+ public BindableDouble Volume => throw new NotImplementedException();
+
+ public BindableDouble Balance => throw new NotImplementedException();
+
+ public BindableDouble Frequency => throw new NotImplementedException();
+
+ public int PlaybackConcurrency
+ {
+ get => throw new NotImplementedException();
+ set => throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ }
+ }
}
diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs
index 6984959e9c..59ab6ad265 100644
--- a/osu.Game/Screens/Menu/LogoVisualisation.cs
+++ b/osu.Game/Screens/Menu/LogoVisualisation.cs
@@ -122,7 +122,7 @@ namespace osu.Game.Screens.Menu
Color4 defaultColour = Color4.White.Opacity(0.2f);
if (user.Value?.IsSupporter ?? false)
- AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? defaultColour;
+ AccentColour = skin.Value.GetConfig(GlobalSkinColour.MenuGlow)?.Value ?? defaultColour;
else
AccentColour = defaultColour;
}
diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
index 393964561c..55a6a33e89 100644
--- a/osu.Game/Screens/Menu/MenuSideFlashes.cs
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -112,7 +112,7 @@ namespace osu.Game.Screens.Menu
Color4 baseColour = colours.Blue;
if (user.Value?.IsSupporter ?? false)
- baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? baseColour;
+ baseColour = skin.Value.GetConfig(GlobalSkinColour.MenuGlow)?.Value ?? baseColour;
// linear colour looks better in this case, so let's use it for now.
Color4 gradientDark = baseColour.Opacity(0).ToLinear();
diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
index f93d5d8b02..c5202fa792 100644
--- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs
+++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
@@ -304,6 +304,9 @@ namespace osu.Game.Screens.Play
private class Button : DialogButton
{
+ // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved)
+ protected override bool OnHover(HoverEvent e) => true;
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
Selected.Value = true;
diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs
index 9c56725c4e..05f6128ac2 100644
--- a/osu.Game/Screens/Play/SquareGraph.cs
+++ b/osu.Game/Screens/Play/SquareGraph.cs
@@ -103,6 +103,7 @@ namespace osu.Game.Screens.Play
var newColumns = new BufferedContainer
{
CacheDrawnFrameBuffer = true,
+ RedrawOnScale = false,
RelativeSizeAxes = Axes.Both,
};
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 5f6307e3b4..65ecd7b812 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -154,6 +154,8 @@ namespace osu.Game.Screens.Select
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
CacheDrawnFrameBuffer = true;
+ RedrawOnScale = false;
+
RelativeSizeAxes = Axes.Both;
titleBinding = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title)));
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index 97b6a78804..699e01bca7 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -146,6 +146,7 @@ namespace osu.Game.Screens.Select.Carousel
public PanelBackground(WorkingBeatmap working)
{
CacheDrawnFrameBuffer = true;
+ RedrawOnScale = false;
Children = new Drawable[]
{
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index b35c9c7b97..98f158c725 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -3,6 +3,7 @@
using osu.Framework.Audio;
using osu.Framework.IO.Stores;
+using osuTK.Graphics;
namespace osu.Game.Skinning
{
@@ -11,6 +12,7 @@ namespace osu.Game.Skinning
public DefaultLegacySkin(IResourceStore storage, AudioManager audioManager)
: base(Info, storage, audioManager, string.Empty)
{
+ Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
}
public static SkinInfo Info { get; } = new SkinInfo
diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index 9eda5d597a..529c1afca5 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -1,10 +1,13 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
+using osuTK.Graphics;
namespace osu.Game.Skinning
{
@@ -21,5 +24,24 @@ namespace osu.Game.Skinning
public override Texture GetTexture(string componentName) => null;
public override SampleChannel GetSample(ISampleInfo sampleInfo) => null;
+
+ public override IBindable GetConfig(TLookup lookup)
+ {
+ switch (lookup)
+ {
+ // todo: this code is pulled from LegacySkin and should not exist.
+ // will likely change based on how databased storage of skin configuration goes.
+ case GlobalSkinConfiguration global:
+ switch (global)
+ {
+ case GlobalSkinConfiguration.ComboColours:
+ return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ }
+
+ break;
+ }
+
+ return null;
+ }
}
}
diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs
index 722b35f102..f52fac6077 100644
--- a/osu.Game/Skinning/DefaultSkinConfiguration.cs
+++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs
@@ -12,8 +12,6 @@ namespace osu.Game.Skinning
{
public DefaultSkinConfiguration()
{
- HitCircleFont = "default";
-
ComboColours.AddRange(new[]
{
new Color4(17, 136, 170, 255),
@@ -21,8 +19,6 @@ namespace osu.Game.Skinning
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
});
-
- CursorExpand = true;
}
}
}
diff --git a/osu.Game/Skinning/GameplaySkinComponent.cs b/osu.Game/Skinning/GameplaySkinComponent.cs
index 8695b3d720..2aa380fa90 100644
--- a/osu.Game/Skinning/GameplaySkinComponent.cs
+++ b/osu.Game/Skinning/GameplaySkinComponent.cs
@@ -5,7 +5,7 @@ using System.Linq;
namespace osu.Game.Skinning
{
- public class GameplaySkinComponent : ISkinComponent where T : struct
+ public class GameplaySkinComponent : ISkinComponent
{
public readonly T Component;
diff --git a/osu.Game/Skinning/GlobalSkinColour.cs b/osu.Game/Skinning/GlobalSkinColour.cs
new file mode 100644
index 0000000000..d039be98ce
--- /dev/null
+++ b/osu.Game/Skinning/GlobalSkinColour.cs
@@ -0,0 +1,10 @@
+// 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.Skinning
+{
+ public enum GlobalSkinColour
+ {
+ MenuGlow
+ }
+}
diff --git a/osu.Game/Skinning/GlobalSkinConfiguration.cs b/osu.Game/Skinning/GlobalSkinConfiguration.cs
new file mode 100644
index 0000000000..66dc9a9395
--- /dev/null
+++ b/osu.Game/Skinning/GlobalSkinConfiguration.cs
@@ -0,0 +1,10 @@
+// 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.Skinning
+{
+ public enum GlobalSkinConfiguration
+ {
+ ComboColours
+ }
+}
diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs
index bc1ae634c9..cb2a379b8e 100644
--- a/osu.Game/Skinning/ISkin.cs
+++ b/osu.Game/Skinning/ISkin.cs
@@ -1,8 +1,9 @@
// 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.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -14,12 +15,36 @@ namespace osu.Game.Skinning
///
public interface ISkin
{
+ ///
+ /// Retrieve a component implementation.
+ ///
+ /// The requested component.
+ /// A drawable representation for the requested component, or null if unavailable.
+ [CanBeNull]
Drawable GetDrawableComponent(ISkinComponent component);
+ ///
+ /// Retrieve a .
+ ///
+ /// The requested texture.
+ /// A matching texture, or null if unavailable.
+ [CanBeNull]
Texture GetTexture(string componentName);
+ ///
+ /// Retrieve a .
+ ///
+ /// The requested sample.
+ /// A matching sample channel, or null if unavailable.
+ [CanBeNull]
SampleChannel GetSample(ISampleInfo sampleInfo);
- TValue GetValue(Func query) where TConfiguration : SkinConfiguration;
+ ///
+ /// Retrieve a configuration value.
+ ///
+ /// The requested configuration value.
+ /// A matching value boxed in an , or null if unavailable.
+ [CanBeNull]
+ IBindable GetConfig(TLookup lookup);
}
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 535471f455..0b1076be01 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.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.Collections.Generic;
using System.IO;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
@@ -16,29 +19,32 @@ namespace osu.Game.Skinning
{
public class LegacySkin : Skin
{
+ [CanBeNull]
protected TextureStore Textures;
+ [CanBeNull]
protected IResourceStore Samples;
public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager)
: this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini")
{
- // defaults should only be applied for non-beatmap skins (which are parsed via this constructor).
- if (!Configuration.CustomColours.ContainsKey("SliderBall")) Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
}
protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename)
: base(skin)
{
- Stream stream = storage.GetStream(filename);
+ Stream stream = storage?.GetStream(filename);
if (stream != null)
using (StreamReader reader = new StreamReader(stream))
Configuration = new LegacySkinDecoder().Decode(reader);
else
Configuration = new DefaultSkinConfiguration();
- Samples = audioManager.GetSampleStore(storage);
- Textures = new TextureStore(new TextureLoaderStore(storage));
+ if (storage != null)
+ {
+ Samples = audioManager?.GetSampleStore(storage);
+ Textures = new TextureStore(new TextureLoaderStore(storage));
+ }
}
protected override void Dispose(bool isDisposing)
@@ -48,6 +54,52 @@ namespace osu.Game.Skinning
Samples?.Dispose();
}
+ public override IBindable GetConfig(TLookup lookup)
+ {
+ switch (lookup)
+ {
+ case GlobalSkinConfiguration global:
+ switch (global)
+ {
+ case GlobalSkinConfiguration.ComboColours:
+ return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ }
+
+ break;
+
+ case GlobalSkinColour colour:
+ return SkinUtils.As(getCustomColour(colour.ToString()));
+
+ case SkinCustomColourLookup customColour:
+ return SkinUtils.As(getCustomColour(customColour.Lookup.ToString()));
+
+ default:
+ try
+ {
+ if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out var val))
+ {
+ // special case for handling skins which use 1 or 0 to signify a boolean state.
+ if (typeof(TValue) == typeof(bool))
+ val = val == "1" ? "true" : "false";
+
+ var bindable = new Bindable();
+ if (val != null)
+ bindable.Parse(val);
+ return bindable;
+ }
+ }
+ catch
+ {
+ }
+
+ break;
+ }
+
+ return null;
+ }
+
+ private IBindable getCustomColour(string lookup) => Configuration.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null;
+
public override Drawable GetDrawableComponent(ISkinComponent component)
{
switch (component)
@@ -79,12 +131,12 @@ namespace osu.Game.Skinning
componentName = getFallbackName(componentName);
float ratio = 2;
- var texture = Textures.Get($"{componentName}@2x");
+ var texture = Textures?.Get($"{componentName}@2x");
if (texture == null)
{
ratio = 1;
- texture = Textures.Get(componentName);
+ texture = Textures?.Get(componentName);
}
if (texture != null)
@@ -97,7 +149,7 @@ namespace osu.Game.Skinning
{
foreach (var lookup in sampleInfo.LookupNames)
{
- var sample = Samples.Get(getFallbackName(lookup));
+ var sample = Samples?.Get(getFallbackName(lookup));
if (sample != null)
return sample;
@@ -105,7 +157,7 @@ namespace osu.Game.Skinning
if (sampleInfo is HitSampleInfo hsi)
// Try fallback to non-bank samples.
- return Samples.Get(hsi.Name);
+ return Samples?.Get(hsi.Name);
return null;
}
diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs
index 0160755eed..e97664e75e 100644
--- a/osu.Game/Skinning/LegacySkinDecoder.cs
+++ b/osu.Game/Skinning/LegacySkinDecoder.cs
@@ -14,47 +14,31 @@ namespace osu.Game.Skinning
protected override void ParseLine(DefaultSkinConfiguration skin, Section section, string line)
{
- line = StripComments(line);
-
- var pair = SplitKeyVal(line);
-
- switch (section)
+ if (section != Section.Colours)
{
- case Section.General:
- switch (pair.Key)
- {
- case @"Name":
- skin.SkinInfo.Name = pair.Value;
- break;
+ line = StripComments(line);
- case @"Author":
- skin.SkinInfo.Creator = pair.Value;
- break;
+ var pair = SplitKeyVal(line);
- case @"CursorExpand":
- skin.CursorExpand = pair.Value != "0";
- break;
+ switch (section)
+ {
+ case Section.General:
+ switch (pair.Key)
+ {
+ case @"Name":
+ skin.SkinInfo.Name = pair.Value;
+ return;
- case @"SliderBorderSize":
- skin.SliderBorderSize = Parsing.ParseFloat(pair.Value);
- break;
- }
+ case @"Author":
+ skin.SkinInfo.Creator = pair.Value;
+ return;
+ }
- break;
+ break;
+ }
- case Section.Fonts:
- switch (pair.Key)
- {
- case "HitCirclePrefix":
- skin.HitCircleFont = pair.Value;
- break;
-
- case "HitCircleOverlap":
- skin.HitCircleOverlap = int.Parse(pair.Value);
- break;
- }
-
- break;
+ if (!string.IsNullOrEmpty(pair.Key))
+ skin.ConfigDictionary[pair.Key] = pair.Value;
}
base.ParseLine(skin, section, line);
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index 299f257e57..fa4aebd8a5 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -1,8 +1,9 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
@@ -13,7 +14,7 @@ namespace osu.Game.Skinning
{
public readonly SkinInfo SkinInfo;
- public virtual SkinConfiguration Configuration { get; protected set; }
+ public SkinConfiguration Configuration { get; protected set; }
public abstract Drawable GetDrawableComponent(ISkinComponent componentName);
@@ -21,8 +22,7 @@ namespace osu.Game.Skinning
public abstract Texture GetTexture(string componentName);
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
- => Configuration is TConfiguration conf ? query.Invoke(conf) : default;
+ public abstract IBindable GetConfig(TLookup lookup);
protected Skin(SkinInfo skin)
{
diff --git a/osu.Game/Skinning/SkinConfigManager.cs b/osu.Game/Skinning/SkinConfigManager.cs
new file mode 100644
index 0000000000..896444d1d2
--- /dev/null
+++ b/osu.Game/Skinning/SkinConfigManager.cs
@@ -0,0 +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 osu.Framework.Configuration;
+
+namespace osu.Game.Skinning
+{
+ public class SkinConfigManager : ConfigManager where T : struct
+ {
+ protected override void PerformLoad()
+ {
+ }
+
+ protected override bool PerformSave() => false;
+ }
+}
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index d585c58ef1..54aac86e3c 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -18,14 +18,6 @@ namespace osu.Game.Skinning
public Dictionary CustomColours { get; set; } = new Dictionary();
- public string HitCircleFont { get; set; }
-
- public int HitCircleOverlap { get; set; }
-
- public float? SliderBorderSize { get; set; }
-
- public float? SliderPathRadius { get; set; }
-
- public bool? CursorExpand { get; set; }
+ public readonly Dictionary ConfigDictionary = new Dictionary();
}
}
diff --git a/osu.Game/Skinning/SkinCustomColourLookup.cs b/osu.Game/Skinning/SkinCustomColourLookup.cs
new file mode 100644
index 0000000000..b8e5ac9b53
--- /dev/null
+++ b/osu.Game/Skinning/SkinCustomColourLookup.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Skinning
+{
+ public class SkinCustomColourLookup
+ {
+ public readonly object Lookup;
+
+ public SkinCustomColourLookup(object lookup)
+ {
+ Lookup = lookup;
+ }
+ }
+}
diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs
index a55a128dff..aa3b3981c2 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@@ -131,6 +131,6 @@ namespace osu.Game.Skinning
public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query);
+ public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup);
}
}
diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs
index 85a80655ea..ef7f5f381b 100644
--- a/osu.Game/Skinning/SkinProvidingContainer.cs
+++ b/osu.Game/Skinning/SkinProvidingContainer.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
@@ -64,13 +65,16 @@ namespace osu.Game.Skinning
return fallbackSource?.GetSample(sampleInfo);
}
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration
+ public IBindable GetConfig(TLookup lookup)
{
- TValue val;
- if (AllowConfigurationLookup && skin != null && (val = skin.GetValue(query)) != null)
- return val;
+ if (AllowConfigurationLookup && skin != null)
+ {
+ var bindable = skin.GetConfig(lookup);
+ if (bindable != null)
+ return bindable;
+ }
- return fallbackSource == null ? default : fallbackSource.GetValue(query);
+ return fallbackSource?.GetConfig(lookup);
}
protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke();
diff --git a/osu.Game/Skinning/SkinUtils.cs b/osu.Game/Skinning/SkinUtils.cs
new file mode 100644
index 0000000000..e3bc5e28b8
--- /dev/null
+++ b/osu.Game/Skinning/SkinUtils.cs
@@ -0,0 +1,21 @@
+// 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.Bindables;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Contains helper methods to assist in implementing s.
+ ///
+ public static class SkinUtils
+ {
+ ///
+ /// Converts an to a . Used for returning configuration values of specific types.
+ ///
+ /// The value.
+ /// The type of value , and the type of the resulting bindable.
+ /// The resulting bindable.
+ public static Bindable As(object value) => (Bindable)value;
+ }
+}
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index 3d0219ed93..bdf8be773b 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Audio;
@@ -20,7 +21,7 @@ namespace osu.Game.Skinning
private SampleChannel[] channels;
- private AudioManager audio;
+ private ISampleStore samples;
public SkinnableSound(IEnumerable hitSamples)
{
@@ -33,9 +34,9 @@ namespace osu.Game.Skinning
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load(ISampleStore samples)
{
- this.audio = audio;
+ this.samples = samples;
}
private bool looping;
@@ -81,7 +82,7 @@ namespace osu.Game.Skinning
if (ch == null && allowFallback)
foreach (var lookup in s.LookupNames)
- if ((ch = audio.Samples.Get($"Gameplay/{lookup}")) != null)
+ if ((ch = samples.Get($"Gameplay/{lookup}")) != null)
break;
if (ch != null)
@@ -102,8 +103,9 @@ namespace osu.Game.Skinning
{
base.Dispose(isDisposing);
- foreach (var c in channels)
- c.Dispose();
+ if (channels != null)
+ foreach (var c in channels)
+ c.Dispose();
}
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
index b04f1d4518..f3f8308964 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
@@ -64,5 +64,11 @@ namespace osu.Game.Storyboards.Drawables
LifetimeEnd = sampleInfo.StartTime;
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ channel?.Stop();
+ base.Dispose(isDisposing);
+ }
}
}
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 27d72f3950..dd68ed93e6 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -16,6 +16,7 @@ using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Database;
+using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Beatmaps;
@@ -47,6 +48,12 @@ namespace osu.Game.Tests.Visual
private readonly Lazy contextFactory;
protected DatabaseContextFactory ContextFactory => contextFactory.Value;
+ ///
+ /// Whether this test scene requires API access
+ /// Setting this will cache an actual .
+ ///
+ protected virtual bool RequiresAPIAccess => false;
+
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
// This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures
@@ -57,7 +64,17 @@ namespace osu.Game.Tests.Visual
Default = working
};
- return Dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ Dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ if (!RequiresAPIAccess)
+ {
+ var dummyAPI = new DummyAPIAccess();
+
+ Dependencies.CacheAs(dummyAPI);
+ Add(dummyAPI);
+ }
+
+ return Dependencies;
}
protected OsuTestScene()
diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs
index ccd996098c..2c5a51ca02 100644
--- a/osu.Game/Tests/Visual/PlayerTestScene.cs
+++ b/osu.Game/Tests/Visual/PlayerTestScene.cs
@@ -50,7 +50,11 @@ namespace osu.Game.Tests.Visual
Beatmap.Value = CreateWorkingBeatmap(beatmap);
if (!AllowFail)
- Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
+ {
+ var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail);
+ if (noFailMod != null)
+ Mods.Value = new[] { noFailMod };
+ }
if (Autoplay)
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 7d106f0484..5f2aad24dc 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -6,16 +6,27 @@
AnyCPU
true
+
+ osu!
+ ppy.osu.Game
+ ppy Pty Ltd
+ https://github.com/ppy/osu/blob/master/LICENCE.md
+ https://github.com/ppy/osu
+ https://github.com/ppy/osu
+ Automated release.
+ Copyright (c) 2019 ppy Pty Ltd
+ osu game
+
-
+
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 8390a2229b..5027a4ef8c 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -113,13 +113,13 @@
-
+
-
-
+
+