// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using JetBrains.Annotations; 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.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; namespace osu.Game.Skinning { /// /// A container which adds a local to the hierarchy. /// public class SkinProvidingContainer : Container, ISkinSource { public event Action SourceChanged; /// /// The list of skins provided by this . /// protected readonly List SkinSources = new List(); [CanBeNull] private ISkinSource fallbackSource; private readonly NoFallbackProxy noFallbackLookupProxy; protected virtual bool AllowDrawableLookup(ISkinComponent component) => true; protected virtual bool AllowTextureLookup(string componentName) => true; protected virtual bool AllowSampleLookup(ISampleInfo componentName) => true; protected virtual bool AllowConfigurationLookup => true; protected virtual bool AllowColourLookup => true; /// /// Constructs a new with a single skin added to the protected list. /// public SkinProvidingContainer(ISkin skin) : this() { SkinSources.Add(skin); } /// /// Constructs a new with no sources. /// Up to the implementation for adding to the list. /// protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; noFallbackLookupProxy = new NoFallbackProxy(this); if (skin is ISkinSource source) source.SourceChanged += TriggerSourceChanged; } public ISkin FindProvider(Func lookupFunction) { foreach (var skin in SkinSources) { // a proxy must be used here to correctly pass through the "Allow" checks without implicitly falling back to the fallbackSource. if (lookupFunction(noFallbackLookupProxy)) return skin; } return fallbackSource?.FindProvider(lookupFunction); } public Drawable GetDrawableComponent(ISkinComponent component) => GetDrawableComponent(component, true); public Drawable GetDrawableComponent(ISkinComponent component, bool fallback) { if (AllowDrawableLookup(component)) { foreach (var skin in SkinSources) { Drawable sourceDrawable; if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null) return sourceDrawable; } } if (!fallback) return null; 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) { if (AllowTextureLookup(componentName)) { foreach (var skin in SkinSources) { Texture sourceTexture; if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } } if (!fallback) return null; return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) => GetSample(sampleInfo, true); public ISample GetSample(ISampleInfo sampleInfo, bool fallback) { if (AllowSampleLookup(sampleInfo)) { foreach (var skin in SkinSources) { ISample sourceSample; if ((sourceSample = skin?.GetSample(sampleInfo)) != null) return sourceSample; } } if (!fallback) return null; return fallbackSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) => GetConfig(lookup, true); public IBindable GetConfig(TLookup lookup, bool fallback) { if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) return lookupWithFallback(lookup, AllowColourLookup); return lookupWithFallback(lookup, AllowConfigurationLookup); if (skin != null) { if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup) return lookupWithFallback(lookup, AllowColourLookup, fallback); return lookupWithFallback(lookup, AllowConfigurationLookup, fallback); } if (!fallback) return null; return fallbackSource?.GetConfig(lookup); } private IBindable lookupWithFallback(TLookup lookup, bool canUseSkinLookup, bool canUseFallback) { if (canUseSkinLookup) { foreach (var skin in SkinSources) { IBindable bindable; if ((bindable = skin?.GetConfig(lookup)) != null) return bindable; } } if (!canUseFallback) return null; return fallbackSource?.GetConfig(lookup); } protected virtual void OnSourceChanged() => SourceChanged?.Invoke(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); fallbackSource = dependencies.Get(); if (fallbackSource != null) fallbackSource.SourceChanged += OnSourceChanged; dependencies.CacheAs(this); return dependencies; } protected override void Dispose(bool isDisposing) { // Must be done before base.Dispose() SourceChanged = null; base.Dispose(isDisposing); if (fallbackSource != null) fallbackSource.SourceChanged -= OnSourceChanged; fallbackSource.SourceChanged -= TriggerSourceChanged; if (skin is ISkinSource source) source.SourceChanged -= TriggerSourceChanged; } private class NoFallbackProxy : ISkinSource { private readonly SkinProvidingContainer provider; public NoFallbackProxy(SkinProvidingContainer provider) { 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 GetConfig(TLookup lookup) => provider.GetConfig(lookup, false); public event Action SourceChanged { add => provider.SourceChanged += value; remove => provider.SourceChanged -= value; } public ISkin FindProvider(Func lookupFunction) => provider.FindProvider(lookupFunction); } } }