1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 05:42:56 +08:00

Merge pull request #13264 from peppy/legacy-skin-default-fallback

Allow fallback to legacy defaults from legacy skins
This commit is contained in:
Dan Balasescu 2021-06-08 17:30:27 +09:00 committed by GitHub
commit e5e3509c87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 264 additions and 55 deletions

View File

@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private void sourceChanged()
{
isLegacySkin = new Lazy<bool>(() => Source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
hasKeyTexture = new Lazy<bool>(() => Source.GetAnimation(
isLegacySkin = new Lazy<bool>(() => FindProvider(s => s.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null) != null);
hasKeyTexture = new Lazy<bool>(() => FindProvider(s => s.GetAnimation(
this.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value
?? "mania-key1", true, true) != null);
?? "mania-key1", true, true) != null) != null);
}
public override Drawable GetDrawableComponent(ISkinComponent component)

View File

@ -0,0 +1,10 @@
osu file format v14
[General]
Mode: 0
[TimingPoints]
0,300,4,1,2,100,1,0
[HitObjects]
444,320,1000,5,0,0:0:0:0:

View File

@ -39,18 +39,28 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test]
public void TestLegacySmoothCursorTrail()
{
createTest(() => new LegacySkinContainer(false)
createTest(() =>
{
Child = new LegacyCursorTrail()
var skinContainer = new LegacySkinContainer(false);
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
skinContainer.Child = legacyCursorTrail;
return skinContainer;
});
}
[Test]
public void TestLegacyDisjointCursorTrail()
{
createTest(() => new LegacySkinContainer(true)
createTest(() =>
{
Child = new LegacyCursorTrail()
var skinContainer = new LegacySkinContainer(true);
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
skinContainer.Child = legacyCursorTrail;
return skinContainer;
});
}
@ -102,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Tests
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
public event Action SourceChanged
{
add { }

View File

@ -113,6 +113,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public Drawable GetDrawableComponent(ISkinComponent component) => null;
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public ISample GetSample(ISampleInfo sampleInfo) => null;
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Reflection;
using NUnit.Framework;
using osu.Framework.IO.Stores;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneOsuHitObjectSamples : HitObjectSampleTest
{
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneOsuHitObjectSamples)));
[TestCase("normal-hitnormal")]
[TestCase("hitnormal")]
public void TestDefaultCustomSampleFromBeatmap(string expectedSample)
{
SetupSkins(expectedSample, expectedSample);
CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu");
AssertBeatmapLookup(expectedSample);
}
[TestCase("normal-hitnormal")]
[TestCase("hitnormal")]
public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample)
{
SetupSkins(string.Empty, expectedSample);
CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu");
AssertUserLookup(expectedSample);
}
[TestCase("normal-hitnormal2")]
public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample)
{
SetupSkins(string.Empty, unwantedSample);
CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu");
AssertNoLookup(unwantedSample);
}
}
}

View File

@ -166,6 +166,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => default;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
public event Action SourceChanged;

View File

@ -11,10 +11,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
public class LegacyCursor : OsuCursorSprite
{
private readonly ISkin skin;
private bool spin;
public LegacyCursor()
public LegacyCursor(ISkin skin)
{
this.skin = skin;
Size = new Vector2(50);
Anchor = Anchor.Centre;
@ -22,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
private void load()
{
bool centre = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorCentre)?.Value ?? true;
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;

View File

@ -14,14 +14,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
public class LegacyCursorTrail : CursorTrail
{
private readonly ISkin skin;
private const double disjoint_trail_time_separation = 1000 / 60.0;
private bool disjointTrail;
private double lastTrailTime;
private IBindable<float> cursorSize;
public LegacyCursorTrail(ISkin skin)
{
this.skin = skin;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, OsuConfigManager config)
private void load(OsuConfigManager config)
{
Texture = skin.GetTexture("cursortrail");
disjointTrail = skin.GetTexture("cursormiddle") == null;

View File

@ -14,18 +14,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
private readonly Drawable animationContent;
private readonly ISkin skin;
private Sprite layerNd;
private Sprite layerSpec;
public LegacySliderBall(Drawable animationContent)
public LegacySliderBall(Drawable animationContent, ISkin skin)
{
this.animationContent = animationContent;
this.skin = skin;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
private void load()
{
var ballColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBall)?.Value ?? Color4.White;

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private void sourceChanged()
{
hasHitCircle = new Lazy<bool>(() => Source.GetTexture("hitcircle") != null);
hasHitCircle = new Lazy<bool>(() => FindProvider(s => s.GetTexture("hitcircle") != null) != null);
}
public override Drawable GetDrawableComponent(ISkinComponent component)
@ -49,13 +49,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return followCircle;
case OsuSkinComponents.SliderBall:
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
// specular and nd layers must come from the same source as the ball texure.
var ballProvider = Source.FindProvider(s => s.GetTexture("sliderb") != null || s.GetTexture("sliderb0") != null);
var sliderBallContent = ballProvider.GetAnimation("sliderb", true, true, animationSeparator: "");
// todo: slider ball has a custom frame delay based on velocity
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
if (sliderBallContent != null)
return new LegacySliderBall(sliderBallContent);
return new LegacySliderBall(sliderBallContent, ballProvider);
return null;
@ -84,14 +87,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
case OsuSkinComponents.Cursor:
if (Source.GetTexture("cursor") != null)
return new LegacyCursor();
var cursorProvider = Source.FindProvider(s => s.GetTexture("cursor") != null);
if (cursorProvider != null)
return new LegacyCursor(cursorProvider);
return null;
case OsuSkinComponents.CursorTrail:
if (Source.GetTexture("cursortrail") != null)
return new LegacyCursorTrail();
var trailProvider = Source.FindProvider(s => s.GetTexture("cursortrail") != null);
if (trailProvider != null)
return new LegacyCursorTrail(trailProvider);
return null;

View File

@ -152,32 +152,35 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}");
}
public override ISample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
public override ISample GetSample(ISampleInfo sampleInfo)
{
if (sampleInfo is HitSampleInfo hitSampleInfo)
return Source.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo));
return base.GetSample(sampleInfo);
}
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Source.GetConfig<TLookup, TValue>(lookup);
private class LegacyTaikoSampleInfo : ISampleInfo
private class LegacyTaikoSampleInfo : HitSampleInfo
{
private readonly ISampleInfo source;
public LegacyTaikoSampleInfo(HitSampleInfo sampleInfo)
: base(sampleInfo.Name, sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume)
public LegacyTaikoSampleInfo(ISampleInfo source)
{
this.source = source;
}
public IEnumerable<string> LookupNames
public override IEnumerable<string> LookupNames
{
get
{
foreach (var name in source.LookupNames)
foreach (var name in base.LookupNames)
yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-");
foreach (var name in source.LookupNames)
foreach (var name in base.LookupNames)
yield return name;
}
}
public int Volume => source.Volume;
}
}
}

View File

@ -85,8 +85,12 @@ namespace osu.Game.Rulesets.Taiko.UI
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
private void load(ISkinSource source)
{
ISkin skin = source.FindProvider(s => getAnimationFrame(s, state, 0) != null);
if (skin == null) return;
for (int frameIndex = 0; true; frameIndex++)
{
var texture = getAnimationFrame(skin, state, frameIndex);
@ -112,8 +116,12 @@ namespace osu.Game.Rulesets.Taiko.UI
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
private void load(ISkinSource source)
{
ISkin skin = source.FindProvider(s => getAnimationFrame(s, TaikoMascotAnimationState.Clear, 0) != null);
if (skin == null) return;
foreach (var frameIndex in clear_animation_sequence)
{
var texture = getAnimationFrame(skin, TaikoMascotAnimationState.Clear, frameIndex);

View File

@ -123,6 +123,8 @@ namespace osu.Game.Tests.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)

View File

@ -61,6 +61,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotSupportedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
}
private class TestAnimationTimeReference : IAnimationTimeReference

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.IO;
using System.Linq;
@ -222,6 +223,8 @@ namespace osu.Game.Tests.Skins
public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => skin.GetConfig<TLookup, TValue>(lookup);
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => skin.FindProvider(lookupFunction);
}
}
}

View File

@ -301,6 +301,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => throw new NotImplementedException();
}
private class SecondarySource : ISkin
@ -312,6 +314,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => throw new NotImplementedException();
}
[Cached(typeof(ISkinSource))]
@ -325,6 +329,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => throw new NotImplementedException();
public event Action SourceChanged
{
add { }

View File

@ -147,6 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT);
public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source?.GetConfig<TLookup, TValue>(lookup);
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => source?.FindProvider(lookupFunction);
public void TriggerSourceChanged()
{

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using JetBrains.Annotations;
namespace osu.Game.Skinning
{
@ -11,5 +12,13 @@ namespace osu.Game.Skinning
public interface ISkinSource : ISkin
{
event Action SourceChanged;
/// <summary>
/// Find the first (if any) skin that can fulfill the lookup.
/// This should be used for cases where subsequent lookups (for related components) need to occur on the same skin.
/// </summary>
/// <returns>The skin to be used for subsequent lookups, or <c>null</c> if none is available.</returns>
[CanBeNull]
ISkin FindProvider(Func<ISkin, bool> lookupFunction);
}
}

View File

@ -20,9 +20,6 @@ namespace osu.Game.Skinning
{
private const double epic_cutoff = 0.5;
[Resolved]
private ISkinSource skin { get; set; }
private LegacyHealthPiece fill;
private LegacyHealthPiece marker;
@ -31,10 +28,13 @@ namespace osu.Game.Skinning
private bool isNewStyle;
[BackgroundDependencyLoader]
private void load()
private void load(ISkinSource source)
{
AutoSizeAxes = Axes.Both;
var skin = source.FindProvider(s => getTexture(s, "bg") != null);
// the marker lookup to decide which display style must be performed on the source of the bg, which is the most common element.
isNewStyle = getTexture(skin, "marker") != null;
// background implementation is the same for both versions.
@ -76,7 +76,7 @@ namespace osu.Game.Skinning
protected override void Flash(JudgementResult result) => marker.Flash(result);
private static Texture getTexture(ISkinSource skin, string name) => skin.GetTexture($"scorebar-{name}");
private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"scorebar-{name}");
private static Color4 getFillColour(double hp)
{
@ -95,7 +95,7 @@ namespace osu.Game.Skinning
private readonly Texture dangerTexture;
private readonly Texture superDangerTexture;
public LegacyOldStyleMarker(ISkinSource skin)
public LegacyOldStyleMarker(ISkin skin)
{
normalTexture = getTexture(skin, "ki");
dangerTexture = getTexture(skin, "kidanger");
@ -126,9 +126,9 @@ namespace osu.Game.Skinning
public class LegacyNewStyleMarker : LegacyMarker
{
private readonly ISkinSource skin;
private readonly ISkin skin;
public LegacyNewStyleMarker(ISkinSource skin)
public LegacyNewStyleMarker(ISkin skin)
{
this.skin = skin;
}
@ -150,7 +150,7 @@ namespace osu.Game.Skinning
internal class LegacyOldStyleFill : LegacyHealthPiece
{
public LegacyOldStyleFill(ISkinSource skin)
public LegacyOldStyleFill(ISkin skin)
{
// required for sizing correctly..
var firstFrame = getTexture(skin, "colour-0");
@ -173,7 +173,7 @@ namespace osu.Game.Skinning
internal class LegacyNewStyleFill : LegacyHealthPiece
{
public LegacyNewStyleFill(ISkinSource skin)
public LegacyNewStyleFill(ISkin skin)
{
InternalChild = new Sprite
{

View File

@ -60,10 +60,17 @@ namespace osu.Game.Skinning
{
}
protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, string filename)
/// <summary>
/// Construct a new legacy skin instance.
/// </summary>
/// <param name="skin">The model for this skin.</param>
/// <param name="storage">A storage for looking up files within this skin using user-facing filenames.</param>
/// <param name="resources">Access to raw game resources.</param>
/// <param name="configurationFilename">The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file.</param>
protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename)
: base(skin, resources)
{
using (var stream = storage?.GetStream(filename))
using (var stream = storage?.GetStream(configurationFilename))
{
if (stream != null)
{
@ -127,7 +134,7 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookup maniaLookup:
if (!AllowManiaSkin)
return null;
break;
var result = lookupForMania<TValue>(maniaLookup);
if (result != null)
@ -390,6 +397,7 @@ namespace osu.Game.Skinning
return null;
case GameplaySkinComponent<HitResult> resultComponent:
// TODO: this should be inside the judgement pieces.
Func<Drawable> createDrawable = () => getJudgementAnimation(resultComponent.Component);
// kind of wasteful that we throw this away, but should do for now.
@ -485,8 +493,10 @@ namespace osu.Game.Skinning
var sample = Samples?.Get(lookup);
if (sample != null)
{
return sample;
}
}
return null;
}
@ -518,8 +528,7 @@ namespace osu.Game.Skinning
yield return componentName;
// Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
string lastPiece = componentName.Split('/').Last();
yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece;
yield return componentName.Split('/').Last();
}
protected override void Dispose(bool isDisposing)

View File

@ -27,6 +27,18 @@ namespace osu.Game.Skinning
{
Texture texture;
// find the first source which provides either the animated or non-animated version.
ISkin skin = (source as ISkinSource)?.FindProvider(s =>
{
if (animatable && s.GetTexture(getFrameName(0)) != null)
return true;
return s.GetTexture(componentName, wrapModeS, wrapModeT) != null;
}) ?? source;
if (skin == null)
return null;
if (animatable)
{
var textures = getTextures().ToArray();
@ -35,7 +47,7 @@ namespace osu.Game.Skinning
{
var animation = new SkinnableTextureAnimation(startAtCurrentTime)
{
DefaultFrameLength = frameLength ?? getFrameLength(source, applyConfigFrameRate, textures),
DefaultFrameLength = frameLength ?? getFrameLength(skin, applyConfigFrameRate, textures),
Loop = looping,
};
@ -47,7 +59,7 @@ namespace osu.Game.Skinning
}
// if an animation was not allowed or not found, fall back to a sprite retrieval.
if ((texture = source.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
if ((texture = skin.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
return new Sprite { Texture = texture };
return null;
@ -56,12 +68,14 @@ namespace osu.Game.Skinning
{
for (int i = 0; true; i++)
{
if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}", wrapModeS, wrapModeT)) == null)
if ((texture = skin.GetTexture(getFrameName(i), wrapModeS, wrapModeT)) == null)
break;
yield return texture;
}
}
string getFrameName(int frameIndex) => $"{componentName}{animationSeparator}{frameIndex}";
}
public static bool HasFont(this ISkin source, LegacyFont font)

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
@ -15,7 +16,7 @@ namespace osu.Game.Skinning
/// <summary>
/// Transformer used to handle support of legacy features for individual rulesets.
/// </summary>
public abstract class LegacySkinTransformer : ISkin
public abstract class LegacySkinTransformer : ISkinSource
{
/// <summary>
/// Source of the <see cref="ISkin"/> which is being transformed.
@ -47,5 +48,13 @@ namespace osu.Game.Skinning
}
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => Source.FindProvider(lookupFunction);
public event Action SourceChanged
{
add { throw new NotSupportedException(); }
remove { }
}
}
}

View File

@ -48,6 +48,8 @@ namespace osu.Game.Skinning
protected override string ImportFromStablePath => "Skins";
private readonly Skin defaultLegacySkin;
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore<byte[]> resources, AudioManager audio)
: base(storage, contextFactory, new SkinStore(contextFactory, storage), host)
{
@ -55,6 +57,8 @@ namespace osu.Game.Skinning
this.host = host;
this.resources = resources;
defaultLegacySkin = new DefaultLegacySkin(this);
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
CurrentSkin.ValueChanged += skin =>
{
@ -204,13 +208,41 @@ namespace osu.Game.Skinning
public event Action SourceChanged;
public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component);
public Drawable GetDrawableComponent(ISkinComponent component) => lookupWithFallback(s => s.GetDrawableComponent(component));
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => CurrentSkin.Value.GetTexture(componentName, wrapModeS, wrapModeT);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => lookupWithFallback(s => s.GetTexture(componentName, wrapModeS, wrapModeT));
public ISample GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
public ISample GetSample(ISampleInfo sampleInfo) => lookupWithFallback(s => s.GetSample(sampleInfo));
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => CurrentSkin.Value.GetConfig<TLookup, TValue>(lookup);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => lookupWithFallback(s => s.GetConfig<TLookup, TValue>(lookup));
public ISkin FindProvider(Func<ISkin, bool> lookupFunction)
{
if (lookupFunction(CurrentSkin.Value))
return CurrentSkin.Value;
if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin))
return defaultLegacySkin;
return null;
}
private T lookupWithFallback<T>(Func<ISkin, T> func)
where T : class
{
var selectedSkin = func(CurrentSkin.Value);
if (selectedSkin != null)
return selectedSkin;
// TODO: we also want to return a DefaultLegacySkin here if the current *beatmap* is providing any skinned elements.
// When attempting to address this, we may want to move the full DefaultLegacySkin fallback logic to within Player itself (to better allow
// for beatmap skin visibility).
if (CurrentSkin.Value is LegacySkin)
return func(defaultLegacySkin);
return null;
}
#region IResourceStorageProvider

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
@ -20,8 +21,10 @@ namespace osu.Game.Skinning
{
public event Action SourceChanged;
[CanBeNull]
private readonly ISkin skin;
[CanBeNull]
private ISkinSource fallbackSource;
protected virtual bool AllowDrawableLookup(ISkinComponent component) => true;
@ -41,6 +44,22 @@ namespace osu.Game.Skinning
RelativeSizeAxes = Axes.Both;
}
public ISkin FindProvider(Func<ISkin, bool> lookupFunction)
{
if (skin is ISkinSource source)
{
if (source.FindProvider(lookupFunction) is ISkin found)
return found;
}
else if (skin != null)
{
if (lookupFunction(skin))
return skin;
}
return fallbackSource?.FindProvider(lookupFunction);
}
public Drawable GetDrawableComponent(ISkinComponent component)
{
Drawable sourceDrawable;
@ -85,7 +104,7 @@ namespace osu.Game.Skinning
{
if (canUseSkinLookup)
{
var bindable = skin.GetConfig<TLookup, TValue>(lookup);
var bindable = skin?.GetConfig<TLookup, TValue>(lookup);
if (bindable != null)
return bindable;
}

View File

@ -158,6 +158,8 @@ namespace osu.Game.Tests.Beatmaps
add { }
remove { }
}
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
}
}
}