Merge pull request #13423 from frenzibyte/transformers-per-skin

Refactor ruleset skin transforming logic to be per-`ISkin` rather than one `ISkinSource`
This commit is contained in:
Dean Herbert
2021-06-22 21:24:56 +09:00
committed by GitHub
23 changed files with 414 additions and 248 deletions

View File

@ -127,7 +127,7 @@ namespace osu.Game.Rulesets
[CanBeNull]
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().FirstOrDefault();
public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null;
public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null;
protected Ruleset()
{

View File

@ -73,15 +73,7 @@ namespace osu.Game.Screens.Edit.Compose
{
Debug.Assert(ruleset != null);
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(beatmap.Value.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, EditorBeatmap.PlayableBeatmap));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content));
return new RulesetSkinProvidingContainer(ruleset, EditorBeatmap.PlayableBeatmap, beatmap.Value.Skin).WithChild(content);
}
#region Input Handling

View File

@ -228,29 +228,23 @@ namespace osu.Game.Screens.Play
dependencies.CacheAs(GameplayBeatmap);
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin);
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider));
GameplayClockContainer.Add(rulesetSkinProvider);
rulesetSkinProvider.AddRange(new[]
{
// underlay and gameplay should have access the to skinning sources.
// underlay and gameplay should have access to the skinning sources.
createUnderlayComponents(),
createGameplayComponents(Beatmap.Value, playableBeatmap)
});
// also give the HUD a ruleset container to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
var hudRulesetContainer = new SkinProvidingContainer(GameplayRuleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap));
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
GameplayClockContainer.Add(hudRulesetContainer.WithChild(createOverlayComponents(Beatmap.Value)));
// also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
rulesetSkinProvider.Add(createOverlayComponents(Beatmap.Value));
if (!DrawableRuleset.AllowGameplayOverlays)
{

View File

@ -83,9 +83,9 @@ namespace osu.Game.Skinning
[BackgroundDependencyLoader]
private void load()
{
beatmapSkins.BindValueChanged(_ => TriggerSourceChanged());
beatmapColours.BindValueChanged(_ => TriggerSourceChanged());
beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged());
beatmapSkins.BindValueChanged(_ => OnSourceChanged());
beatmapColours.BindValueChanged(_ => OnSourceChanged());
beatmapHitsounds.BindValueChanged(_ => OnSourceChanged());
}
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace osu.Game.Skinning
@ -20,5 +21,10 @@ namespace osu.Game.Skinning
/// <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);
/// <summary>
/// Retrieve all sources available for lookup, with highest priority source first.
/// </summary>
IEnumerable<ISkin> AllSources { get; }
}
}

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.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -16,45 +17,38 @@ namespace osu.Game.Skinning
/// <summary>
/// Transformer used to handle support of legacy features for individual rulesets.
/// </summary>
public abstract class LegacySkinTransformer : ISkinSource
public abstract class LegacySkinTransformer : ISkin
{
/// <summary>
/// Source of the <see cref="ISkin"/> which is being transformed.
/// The <see cref="ISkin"/> which is being transformed.
/// </summary>
protected ISkinSource Source { get; }
[NotNull]
protected ISkin Skin { get; }
protected LegacySkinTransformer(ISkinSource source)
protected LegacySkinTransformer([NotNull] ISkin skin)
{
Source = source;
Skin = skin ?? throw new ArgumentNullException(nameof(skin));
}
public abstract Drawable GetDrawableComponent(ISkinComponent component);
public virtual Drawable GetDrawableComponent(ISkinComponent component) => Skin.GetDrawableComponent(component);
public Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
=> Source.GetTexture(componentName, wrapModeS, wrapModeT);
=> Skin.GetTexture(componentName, wrapModeS, wrapModeT);
public virtual ISample GetSample(ISampleInfo sampleInfo)
{
if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample))
return Source.GetSample(sampleInfo);
return Skin.GetSample(sampleInfo);
var playLayeredHitSounds = GetConfig<LegacySetting, bool>(LegacySetting.LayeredHitSounds);
if (legacySample.IsLayered && playLayeredHitSounds?.Value == false)
return new SampleVirtual();
return Source.GetSample(sampleInfo);
return Skin.GetSample(sampleInfo);
}
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => Source.FindProvider(lookupFunction);
public event Action SourceChanged
{
add => Source.SourceChanged += value;
remove => Source.SourceChanged -= value;
}
public virtual IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Skin.GetConfig<TLookup, TValue>(lookup);
}
}

View File

@ -0,0 +1,100 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.UI;
namespace osu.Game.Skinning
{
/// <summary>
/// A type of <see cref="SkinProvidingContainer"/> specialized for <see cref="DrawableRuleset"/> and other gameplay-related components.
/// Providing access to parent skin sources and the beatmap skin each surrounded with the ruleset legacy skin transformer.
/// </summary>
public class RulesetSkinProvidingContainer : SkinProvidingContainer
{
protected readonly Ruleset Ruleset;
protected readonly IBeatmap Beatmap;
/// <remarks>
/// This container already re-exposes all parent <see cref="ISkinSource"/> sources in a ruleset-usable form.
/// Therefore disallow falling back to any parent <see cref="ISkinSource"/> any further.
/// </remarks>
protected override bool AllowFallingBackToParent => false;
protected override Container<Drawable> Content { get; }
public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin)
{
Ruleset = ruleset;
Beatmap = beatmap;
InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin is LegacySkin ? GetLegacyRulesetTransformedSkin(beatmapSkin) : beatmapSkin)
{
Child = Content = new Container
{
RelativeSizeAxes = Axes.Both,
}
};
}
[Resolved]
private ISkinSource skinSource { get; set; }
[BackgroundDependencyLoader]
private void load()
{
UpdateSkins();
skinSource.SourceChanged += OnSourceChanged;
}
protected override void OnSourceChanged()
{
UpdateSkins();
base.OnSourceChanged();
}
protected virtual void UpdateSkins()
{
SkinSources.Clear();
foreach (var skin in skinSource.AllSources)
{
switch (skin)
{
case LegacySkin legacySkin:
SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin));
break;
default:
SkinSources.Add(skin);
break;
}
}
}
protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin)
{
if (legacySkin == null)
return null;
var rulesetTransformed = Ruleset.CreateLegacySkinProvider(legacySkin, Beatmap);
if (rulesetTransformed != null)
return rulesetTransformed;
return legacySkin;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (skinSource != null)
skinSource.SourceChanged -= OnSourceChanged;
}
}
}

View File

@ -30,6 +30,13 @@ using osu.Game.IO.Archives;
namespace osu.Game.Skinning
{
/// <summary>
/// Handles the storage and retrieval of <see cref="Skin"/>s.
/// </summary>
/// <remarks>
/// This is also exposed and cached as <see cref="ISkinSource"/> to allow for any component to potentially have skinning support.
/// For gameplay components, see <see cref="RulesetSkinProvidingContainer"/> which adds extra legacy and toggle logic that may affect the lookup process.
/// </remarks>
[ExcludeFromDynamicCompile]
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource, IStorageResourceProvider
{
@ -48,9 +55,15 @@ namespace osu.Game.Skinning
protected override string ImportFromStablePath => "Skins";
private readonly Skin defaultLegacySkin;
/// <summary>
/// The default skin.
/// </summary>
public Skin DefaultSkin { get; }
private readonly Skin defaultSkin;
/// <summary>
/// The default legacy skin.
/// </summary>
public Skin DefaultLegacySkin { get; }
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore<byte[]> resources, AudioManager audio)
: base(storage, contextFactory, new SkinStore(contextFactory, storage), host)
@ -59,12 +72,12 @@ namespace osu.Game.Skinning
this.host = host;
this.resources = resources;
defaultLegacySkin = new DefaultLegacySkin(this);
defaultSkin = new DefaultSkin(this);
DefaultLegacySkin = new DefaultLegacySkin(this);
DefaultSkin = new DefaultSkin(this);
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
CurrentSkin.Value = defaultSkin;
CurrentSkin.Value = DefaultSkin;
CurrentSkin.ValueChanged += skin =>
{
if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value)
@ -83,8 +96,8 @@ namespace osu.Game.Skinning
public List<SkinInfo> GetAllUsableSkins()
{
var userSkins = GetAllUserSkins();
userSkins.Insert(0, SkinInfo.Default);
userSkins.Insert(1, DefaultLegacySkin.Info);
userSkins.Insert(0, DefaultSkin.SkinInfo);
userSkins.Insert(1, DefaultLegacySkin.SkinInfo);
return userSkins;
}
@ -223,32 +236,39 @@ namespace osu.Game.Skinning
public ISkin FindProvider(Func<ISkin, bool> lookupFunction)
{
if (lookupFunction(CurrentSkin.Value))
return CurrentSkin.Value;
if (CurrentSkin.Value is LegacySkin && lookupFunction(defaultLegacySkin))
return defaultLegacySkin;
if (lookupFunction(defaultSkin))
return defaultSkin;
foreach (var source in AllSources)
{
if (lookupFunction(source))
return source;
}
return null;
}
public IEnumerable<ISkin> AllSources
{
get
{
yield return CurrentSkin.Value;
if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultLegacySkin)
yield return DefaultLegacySkin;
if (CurrentSkin.Value != DefaultSkin)
yield return DefaultSkin;
}
}
private T lookupWithFallback<T>(Func<ISkin, T> lookupFunction)
where T : class
{
if (lookupFunction(CurrentSkin.Value) is T skinSourced)
return skinSourced;
foreach (var source in AllSources)
{
if (lookupFunction(source) is T skinSourced)
return skinSourced;
}
// 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 && lookupFunction(defaultLegacySkin) is T legacySourced)
return legacySourced;
// Finally fall back to the (non-legacy) default.
return lookupFunction(defaultSkin);
return null;
}
#region IResourceStorageProvider

View File

@ -2,6 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@ -21,13 +24,24 @@ namespace osu.Game.Skinning
{
public event Action SourceChanged;
[CanBeNull]
private readonly ISkin skin;
/// <summary>
/// Skins which should be exposed by this container, in order of lookup precedence.
/// </summary>
protected readonly BindableList<ISkin> SkinSources = new BindableList<ISkin>();
/// <summary>
/// A dictionary mapping each <see cref="ISkin"/> from the <see cref="SkinSources"/>
/// to one that performs the "allow lookup" checks before proceeding with a lookup.
/// </summary>
private readonly Dictionary<ISkin, DisableableSkinSource> disableableSkinSources = new Dictionary<ISkin, DisableableSkinSource>();
[CanBeNull]
private ISkinSource fallbackSource;
private readonly NoFallbackProxy noFallbackLookupProxy;
/// <summary>
/// Whether falling back to parent <see cref="ISkinSource"/>s is allowed in this container.
/// </summary>
protected virtual bool AllowFallingBackToParent => true;
protected virtual bool AllowDrawableLookup(ISkinComponent component) => true;
@ -39,123 +53,159 @@ namespace osu.Game.Skinning
protected virtual bool AllowColourLookup => true;
public SkinProvidingContainer(ISkin skin)
/// <summary>
/// Constructs a new <see cref="SkinProvidingContainer"/> initialised with a single skin source.
/// </summary>
public SkinProvidingContainer([CanBeNull] ISkin skin)
: this()
{
this.skin = skin;
if (skin != null)
SkinSources.Add(skin);
}
/// <summary>
/// Constructs a new <see cref="SkinProvidingContainer"/> with no sources.
/// Implementations can add or change sources through the <see cref="SkinSources"/> list.
/// </summary>
protected SkinProvidingContainer()
{
RelativeSizeAxes = Axes.Both;
noFallbackLookupProxy = new NoFallbackProxy(this);
SkinSources.BindCollectionChanged(((_, args) =>
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var skin in args.NewItems.Cast<ISkin>())
{
disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this));
if (skin is ISkinSource source)
source.SourceChanged += TriggerSourceChanged;
if (skin is ISkinSource source)
source.SourceChanged += OnSourceChanged;
}
break;
case NotifyCollectionChangedAction.Reset:
case NotifyCollectionChangedAction.Remove:
foreach (var skin in args.OldItems.Cast<ISkin>())
{
disableableSkinSources.Remove(skin);
if (skin is ISkinSource source)
source.SourceChanged -= OnSourceChanged;
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var skin in args.OldItems.Cast<ISkin>())
{
disableableSkinSources.Remove(skin);
if (skin is ISkinSource source)
source.SourceChanged -= OnSourceChanged;
}
foreach (var skin in args.NewItems.Cast<ISkin>())
{
disableableSkinSources.Add(skin, new DisableableSkinSource(skin, this));
if (skin is ISkinSource source)
source.SourceChanged += OnSourceChanged;
}
break;
}
}), true);
}
public ISkin FindProvider(Func<ISkin, bool> lookupFunction)
{
if (skin is ISkinSource source)
foreach (var skin in SkinSources)
{
if (source.FindProvider(lookupFunction) is ISkin found)
return found;
}
else if (skin != null)
{
// a proxy must be used here to correctly pass through the "Allow" checks without implicitly falling back to the fallbackSource.
if (lookupFunction(noFallbackLookupProxy))
if (lookupFunction(disableableSkinSources[skin]))
return skin;
}
return fallbackSource?.FindProvider(lookupFunction);
}
public Drawable GetDrawableComponent(ISkinComponent component)
=> GetDrawableComponent(component, true);
public Drawable GetDrawableComponent(ISkinComponent component, bool fallback)
public IEnumerable<ISkin> AllSources
{
Drawable sourceDrawable;
if (AllowDrawableLookup(component) && (sourceDrawable = skin?.GetDrawableComponent(component)) != null)
return sourceDrawable;
get
{
foreach (var skin in SkinSources)
yield return skin;
if (!fallback)
return null;
if (fallbackSource != null)
{
foreach (var skin in fallbackSource.AllSources)
yield return skin;
}
}
}
public Drawable GetDrawableComponent(ISkinComponent component)
{
foreach (var skin in SkinSources)
{
Drawable sourceDrawable;
if ((sourceDrawable = disableableSkinSources[skin]?.GetDrawableComponent(component)) != null)
return sourceDrawable;
}
return fallbackSource?.GetDrawableComponent(component);
}
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
=> GetTexture(componentName, wrapModeS, wrapModeT, true);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool fallback)
{
Texture sourceTexture;
if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
return sourceTexture;
if (!fallback)
return null;
foreach (var skin in SkinSources)
{
Texture sourceTexture;
if ((sourceTexture = disableableSkinSources[skin]?.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
return sourceTexture;
}
return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT);
}
public ISample GetSample(ISampleInfo sampleInfo)
=> GetSample(sampleInfo, true);
public ISample GetSample(ISampleInfo sampleInfo, bool fallback)
{
ISample sourceChannel;
if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null)
return sourceChannel;
if (!fallback)
return null;
foreach (var skin in SkinSources)
{
ISample sourceSample;
if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null)
return sourceSample;
}
return fallbackSource?.GetSample(sampleInfo);
}
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
=> GetConfig<TLookup, TValue>(lookup, true);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup, bool fallback)
{
if (skin != null)
foreach (var skin in SkinSources)
{
if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup)
return lookupWithFallback<TLookup, TValue>(lookup, AllowColourLookup, fallback);
return lookupWithFallback<TLookup, TValue>(lookup, AllowConfigurationLookup, fallback);
}
if (!fallback)
return null;
return fallbackSource?.GetConfig<TLookup, TValue>(lookup);
}
private IBindable<TValue> lookupWithFallback<TLookup, TValue>(TLookup lookup, bool canUseSkinLookup, bool canUseFallback)
{
if (canUseSkinLookup)
{
var bindable = skin?.GetConfig<TLookup, TValue>(lookup);
if (bindable != null)
IBindable<TValue> bindable;
if ((bindable = disableableSkinSources[skin]?.GetConfig<TLookup, TValue>(lookup)) != null)
return bindable;
}
if (!canUseFallback)
return null;
return fallbackSource?.GetConfig<TLookup, TValue>(lookup);
}
protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke();
protected virtual void OnSourceChanged() => SourceChanged?.Invoke();
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
fallbackSource = dependencies.Get<ISkinSource>();
if (fallbackSource != null)
fallbackSource.SourceChanged += TriggerSourceChanged;
if (AllowFallingBackToParent)
{
fallbackSource = dependencies.Get<ISkinSource>();
if (fallbackSource != null)
fallbackSource.SourceChanged += OnSourceChanged;
}
dependencies.CacheAs<ISkinSource>(this);
@ -170,41 +220,67 @@ namespace osu.Game.Skinning
base.Dispose(isDisposing);
if (fallbackSource != null)
fallbackSource.SourceChanged -= TriggerSourceChanged;
fallbackSource.SourceChanged -= OnSourceChanged;
if (skin is ISkinSource source)
source.SourceChanged -= TriggerSourceChanged;
foreach (var source in SkinSources.OfType<ISkinSource>())
source.SourceChanged -= OnSourceChanged;
}
private class NoFallbackProxy : ISkinSource
private class DisableableSkinSource : ISkin
{
private readonly ISkin skin;
private readonly SkinProvidingContainer provider;
public NoFallbackProxy(SkinProvidingContainer provider)
public DisableableSkinSource(ISkin skin, SkinProvidingContainer provider)
{
this.skin = skin;
this.provider = provider;
}
public Drawable GetDrawableComponent(ISkinComponent component)
=> provider.GetDrawableComponent(component, false);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
=> provider.GetTexture(componentName, wrapModeS, wrapModeT, false);
public ISample GetSample(ISampleInfo sampleInfo)
=> provider.GetSample(sampleInfo, false);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
=> provider.GetConfig<TLookup, TValue>(lookup, false);
public event Action SourceChanged
{
add => provider.SourceChanged += value;
remove => provider.SourceChanged -= value;
if (provider.AllowDrawableLookup(component))
return skin.GetDrawableComponent(component);
return null;
}
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) =>
provider.FindProvider(lookupFunction);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
{
if (provider.AllowTextureLookup(componentName))
return skin.GetTexture(componentName, wrapModeS, wrapModeT);
return null;
}
public ISample GetSample(ISampleInfo sampleInfo)
{
if (provider.AllowSampleLookup(sampleInfo))
return skin.GetSample(sampleInfo);
return null;
}
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case GlobalSkinColours _:
case SkinCustomColourLookup _:
if (provider.AllowColourLookup)
return skin.GetConfig<TLookup, TValue>(lookup);
break;
default:
if (provider.AllowConfigurationLookup)
return skin.GetConfig<TLookup, TValue>(lookup);
break;
}
return null;
}
}
}
}

View File

@ -159,7 +159,9 @@ namespace osu.Game.Tests.Beatmaps
remove { }
}
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : null;
public IEnumerable<ISkin> AllSources => new[] { this };
}
}
}