// 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; [CanBeNull] protected ISkinSource ParentSource { get; private set; } /// /// Whether falling back to parent s is allowed in this container. /// protected virtual bool AllowFallingBackToParent => true; 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; /// /// A dictionary mapping each source to a wrapper which handles lookup allowances. /// private readonly List<(ISkin skin, DisableableSkinSource wrapped)> skinSources = new List<(ISkin, DisableableSkinSource)>(); /// /// Constructs a new initialised with a single skin source. /// public SkinProvidingContainer([CanBeNull] ISkin skin) : this() { if (skin != null) AddSource(skin); } /// /// Constructs a new with no sources. /// protected SkinProvidingContainer() { RelativeSizeAxes = Axes.Both; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); ParentSource = dependencies.Get(); if (ParentSource != null) ParentSource.SourceChanged += TriggerSourceChanged; dependencies.CacheAs(this); TriggerSourceChanged(); return dependencies; } public ISkin FindProvider(Func lookupFunction) { foreach (var (skin, lookupWrapper) in skinSources) { if (lookupFunction(lookupWrapper)) return skin; } if (!AllowFallingBackToParent) return null; return ParentSource?.FindProvider(lookupFunction); } public IEnumerable AllSources { get { foreach (var i in skinSources) yield return i.skin; if (AllowFallingBackToParent && ParentSource != null) { foreach (var skin in ParentSource.AllSources) yield return skin; } } } public Drawable GetDrawableComponent(ISkinComponent component) { foreach (var (_, lookupWrapper) in skinSources) { Drawable sourceDrawable; if ((sourceDrawable = lookupWrapper.GetDrawableComponent(component)) != null) return sourceDrawable; } if (!AllowFallingBackToParent) return null; return ParentSource?.GetDrawableComponent(component); } public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { foreach (var (_, lookupWrapper) in skinSources) { Texture sourceTexture; if ((sourceTexture = lookupWrapper.GetTexture(componentName, wrapModeS, wrapModeT)) != null) return sourceTexture; } if (!AllowFallingBackToParent) return null; return ParentSource?.GetTexture(componentName, wrapModeS, wrapModeT); } public ISample GetSample(ISampleInfo sampleInfo) { foreach (var (_, lookupWrapper) in skinSources) { ISample sourceSample; if ((sourceSample = lookupWrapper.GetSample(sampleInfo)) != null) return sourceSample; } if (!AllowFallingBackToParent) return null; return ParentSource?.GetSample(sampleInfo); } public IBindable GetConfig(TLookup lookup) { foreach (var (_, lookupWrapper) in skinSources) { IBindable bindable; if ((bindable = lookupWrapper.GetConfig(lookup)) != null) return bindable; } if (!AllowFallingBackToParent) return null; return ParentSource?.GetConfig(lookup); } /// /// Add a new skin to this provider. Will be added to the end of the lookup order precedence. /// /// The skin to add. protected void AddSource(ISkin skin) { skinSources.Add((skin, new DisableableSkinSource(skin, this))); if (skin is ISkinSource source) source.SourceChanged += TriggerSourceChanged; } /// /// Remove a skin from this provider. /// /// The skin to remove. protected void RemoveSource(ISkin skin) { if (skinSources.RemoveAll(s => s.skin == skin) == 0) return; if (skin is ISkinSource source) source.SourceChanged -= TriggerSourceChanged; } /// /// Clears all skin sources. /// protected void ResetSources() { foreach (var i in skinSources.ToArray()) RemoveSource(i.skin); } /// /// Invoked when any source has changed (either or a source registered via ). /// This is also invoked once initially during to ensure sources are ready for children consumption. /// protected virtual void OnSourceChanged() { } protected void TriggerSourceChanged() { // Expose to implementations, giving them a chance to react before notifying external consumers. OnSourceChanged(); SourceChanged?.Invoke(); } protected override void Dispose(bool isDisposing) { // Must be done before base.Dispose() SourceChanged = null; base.Dispose(isDisposing); if (ParentSource != null) ParentSource.SourceChanged -= TriggerSourceChanged; foreach (var i in skinSources) { if (i.skin is ISkinSource source) source.SourceChanged -= TriggerSourceChanged; } } private class DisableableSkinSource : ISkin { private readonly ISkin skin; private readonly SkinProvidingContainer provider; public DisableableSkinSource(ISkin skin, SkinProvidingContainer provider) { this.skin = skin; this.provider = provider; } public Drawable GetDrawableComponent(ISkinComponent component) { if (provider.AllowDrawableLookup(component)) return skin.GetDrawableComponent(component); return null; } 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 GetConfig(TLookup lookup) { switch (lookup) { case GlobalSkinColours _: case SkinCustomColourLookup _: if (provider.AllowColourLookup) return skin.GetConfig(lookup); break; default: if (provider.AllowConfigurationLookup) return skin.GetConfig(lookup); break; } return null; } } } }