Merge branch 'master' into ruleset-resources-skin

This commit is contained in:
Salman Ahmed
2021-06-22 20:36:49 +03:00
269 changed files with 5653 additions and 1787 deletions

View File

@ -149,13 +149,21 @@ namespace osu.Game.Skinning.Editor
{
foreach (var c in SelectedBlueprints)
{
Drawable drawable = (Drawable)c.Item;
var item = c.Item;
Drawable drawable = (Drawable)item;
drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
if (item.UsesFixedAnchor) continue;
applyClosestAnchor(drawable);
}
return true;
}
private static void applyClosestAnchor(Drawable drawable) => applyAnchor(drawable, getClosestAnchor(drawable));
protected override void OnSelectionChanged()
{
base.OnSelectionChanged();
@ -171,20 +179,27 @@ namespace osu.Game.Skinning.Editor
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<ISkinnableDrawable>> selection)
{
var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors())
{
State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) }
};
yield return new OsuMenuItem("Anchor")
{
Items = createAnchorItems(d => d.Anchor, applyAnchor).ToArray()
Items = createAnchorItems((d, a) => d.UsesFixedAnchor && ((Drawable)d).Anchor == a, applyFixedAnchors)
.Prepend(closestItem)
.ToArray()
};
yield return new OsuMenuItem("Origin")
{
Items = createAnchorItems(d => d.Origin, applyOrigin).ToArray()
Items = createAnchorItems((d, o) => ((Drawable)d).Origin == o, applyOrigins).ToArray()
};
foreach (var item in base.GetContextMenuItemsForSelection(selection))
yield return item;
IEnumerable<TernaryStateMenuItem> createAnchorItems(Func<Drawable, Anchor> checkFunction, Action<Anchor> applyFunction)
IEnumerable<TernaryStateMenuItem> createAnchorItems(Func<ISkinnableDrawable, Anchor, bool> checkFunction, Action<Anchor> applyFunction)
{
var displayableAnchors = new[]
{
@ -198,12 +213,11 @@ namespace osu.Game.Skinning.Editor
Anchor.BottomCentre,
Anchor.BottomRight,
};
return displayableAnchors.Select(a =>
{
return new TernaryStateRadioMenuItem(a.ToString(), MenuItemType.Standard, _ => applyFunction(a))
{
State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == a) }
State = { Value = GetStateFromSelection(selection, c => checkFunction(c.Item, a)) }
};
});
}
@ -215,15 +229,21 @@ namespace osu.Game.Skinning.Editor
drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition;
}
private void applyOrigin(Anchor anchor)
private void applyOrigins(Anchor origin)
{
foreach (var item in SelectedItems)
{
var drawable = (Drawable)item;
if (origin == drawable.Origin) continue;
var previousOrigin = drawable.OriginPosition;
drawable.Origin = anchor;
drawable.Origin = origin;
drawable.Position += drawable.OriginPosition - previousOrigin;
if (item.UsesFixedAnchor) continue;
applyClosestAnchor(drawable);
}
}
@ -234,18 +254,86 @@ namespace osu.Game.Skinning.Editor
private Quad getSelectionQuad() =>
GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray()));
private void applyAnchor(Anchor anchor)
private void applyFixedAnchors(Anchor anchor)
{
foreach (var item in SelectedItems)
{
var drawable = (Drawable)item;
var previousAnchor = drawable.AnchorPosition;
drawable.Anchor = anchor;
drawable.Position -= drawable.AnchorPosition - previousAnchor;
item.UsesFixedAnchor = true;
applyAnchor(drawable, anchor);
}
}
private void applyClosestAnchors()
{
foreach (var item in SelectedItems)
{
item.UsesFixedAnchor = false;
applyClosestAnchor((Drawable)item);
}
}
private static Anchor getClosestAnchor(Drawable drawable)
{
var parent = drawable.Parent;
if (parent == null)
return drawable.Anchor;
var screenPosition = getScreenPosition();
var absolutePosition = parent.ToLocalSpace(screenPosition);
var factor = parent.RelativeToAbsoluteFactor;
var result = default(Anchor);
static Anchor getAnchorFromPosition(float xOrY, Anchor anchor0, Anchor anchor1, Anchor anchor2)
{
if (xOrY >= 2 / 3f)
return anchor2;
if (xOrY >= 1 / 3f)
return anchor1;
return anchor0;
}
result |= getAnchorFromPosition(absolutePosition.X / factor.X, Anchor.x0, Anchor.x1, Anchor.x2);
result |= getAnchorFromPosition(absolutePosition.Y / factor.Y, Anchor.y0, Anchor.y1, Anchor.y2);
return result;
Vector2 getScreenPosition()
{
var quad = drawable.ScreenSpaceDrawQuad;
var origin = drawable.Origin;
var pos = quad.TopLeft;
if (origin.HasFlagFast(Anchor.x2))
pos.X += quad.Width;
else if (origin.HasFlagFast(Anchor.x1))
pos.X += quad.Width / 2f;
if (origin.HasFlagFast(Anchor.y2))
pos.Y += quad.Height;
else if (origin.HasFlagFast(Anchor.y1))
pos.Y += quad.Height / 2f;
return pos;
}
}
private static void applyAnchor(Drawable drawable, Anchor anchor)
{
if (anchor == drawable.Anchor) return;
var previousAnchor = drawable.AnchorPosition;
drawable.Anchor = anchor;
drawable.Position -= drawable.AnchorPosition - previousAnchor;
}
private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference)
{
// cancel out scale in axes we don't care about (based on which drag handle was used).

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

@ -14,5 +14,12 @@ namespace osu.Game.Skinning
/// Whether this component should be editable by an end user.
/// </summary>
bool IsEditable => true;
/// <summary>
/// In the context of the skin layout editor, whether this <see cref="ISkinnableDrawable"/> has a permanent anchor defined.
/// If <see langword="false"/>, this <see cref="ISkinnableDrawable"/>'s <see cref="Drawable.Anchor"/> is automatically determined by proximity,
/// If <see langword="true"/>, a fixed anchor point has been defined.
/// </summary>
bool UsesFixedAnchor { get; set; }
}
}

View File

@ -12,6 +12,8 @@ namespace osu.Game.Skinning
{
public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable
{
public bool UsesFixedAnchor { get; set; }
public LegacyAccuracyCounter()
{
Anchor = Anchor.TopRight;

View File

@ -27,6 +27,8 @@ namespace osu.Game.Skinning
private bool isNewStyle;
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader]
private void load(ISkinSource source)
{
@ -148,9 +150,9 @@ namespace osu.Game.Skinning
}
}
internal class LegacyOldStyleFill : LegacyHealthPiece
internal abstract class LegacyFill : LegacyHealthPiece
{
public LegacyOldStyleFill(ISkin skin)
protected LegacyFill(ISkin skin)
{
// required for sizing correctly..
var firstFrame = getTexture(skin, "colour-0");
@ -162,27 +164,29 @@ namespace osu.Game.Skinning
}
else
{
InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Drawable.Empty();
InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Empty();
Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight);
}
Position = new Vector2(3, 10) * 1.6f;
Masking = true;
}
}
internal class LegacyNewStyleFill : LegacyHealthPiece
internal class LegacyOldStyleFill : LegacyFill
{
public LegacyOldStyleFill(ISkin skin)
: base(skin)
{
Position = new Vector2(3, 10) * 1.6f;
}
}
internal class LegacyNewStyleFill : LegacyFill
{
public LegacyNewStyleFill(ISkin skin)
: base(skin)
{
InternalChild = new Sprite
{
Texture = getTexture(skin, "colour"),
};
Size = InternalChild.Size;
Position = new Vector2(7.5f, 7.8f) * 1.6f;
Masking = true;
}
protected override void Update()

View File

@ -13,6 +13,8 @@ namespace osu.Game.Skinning
protected override double RollingDuration => 1000;
protected override Easing RollingEasing => Easing.Out;
public bool UsesFixedAnchor { get; set; }
public LegacyScoreCounter()
: base(6)
{

View File

@ -30,7 +30,7 @@ namespace osu.Game.Skinning
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);
@ -49,6 +49,6 @@ namespace osu.Game.Skinning
return Skin.GetSample(sampleInfo);
}
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
public virtual IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Skin.GetConfig<TLookup, TValue>(lookup);
}
}

View File

@ -9,18 +9,25 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.UI;
namespace osu.Game.Skinning
{
/// <summary>
/// A type of <see cref="SkinProvidingContainer"/> that provides access to the beatmap skin and user skin,
/// each transformed with the ruleset's own skin transformer individually.
/// 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)
@ -28,7 +35,7 @@ namespace osu.Game.Skinning
Ruleset = ruleset;
Beatmap = beatmap;
InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin == null ? null : ruleset.CreateLegacySkinProvider(beatmapSkin, beatmap))
InternalChild = new BeatmapSkinProvidingContainer(beatmapSkin is LegacySkin ? GetLegacyRulesetTransformedSkin(beatmapSkin) : beatmapSkin)
{
Child = Content = new Container
{
@ -45,11 +52,13 @@ namespace osu.Game.Skinning
[Resolved]
private SkinManager skinManager { get; set; }
private ISkinSource skinSource { get; set; }
[BackgroundDependencyLoader]
private void load()
{
UpdateSkins();
skinSource.SourceChanged += OnSourceChanged;
}
protected override void OnSourceChanged()
@ -62,14 +71,42 @@ namespace osu.Game.Skinning
{
SkinSources.Clear();
SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.CurrentSkin.Value, Beatmap));
foreach (var skin in skinSource.AllSources)
{
switch (skin)
{
case LegacySkin legacySkin:
SkinSources.Add(GetLegacyRulesetTransformedSkin(legacySkin));
break;
if (skinManager.CurrentSkin.Value is LegacySkin)
SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultLegacySkin, Beatmap));
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);
SkinSources.Add(Ruleset.CreateLegacySkinProvider(skinManager.DefaultSkin, Beatmap));
SkinSources.Add(new RulesetResourcesSkin(Ruleset, host, audio));
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,19 +55,15 @@ namespace osu.Game.Skinning
protected override string ImportFromStablePath => "Skins";
private readonly Skin defaultSkin;
/// <summary>
/// The default skin.
/// </summary>
public Skin DefaultSkin { get; }
/// <summary>
/// An <see cref="ISkin"/> providing the resources of the default skin.
/// The default legacy skin.
/// </summary>
public ISkin DefaultSkin => defaultSkin;
private readonly Skin defaultLegacySkin;
/// <summary>
/// An <see cref="ISkin"/> providing the resources of the default legacy skin.
/// </summary>
public ISkin DefaultLegacySkin => defaultLegacySkin;
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)
@ -69,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)
@ -93,8 +96,8 @@ namespace osu.Game.Skinning
public List<SkinInfo> GetAllUsableSkins()
{
var userSkins = GetAllUserSkins();
userSkins.Insert(0, SkinInfo.Default);
userSkins.Insert(1, Skinning.DefaultLegacySkin.Info);
userSkins.Insert(0, DefaultSkin.SkinInfo);
userSkins.Insert(1, DefaultLegacySkin.SkinInfo);
return userSkins;
}
@ -233,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,7 @@
// 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;
@ -24,14 +25,23 @@ namespace osu.Game.Skinning
public event Action SourceChanged;
/// <summary>
/// The list of skins provided by this <see cref="SkinProvidingContainer"/>.
/// 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;
@ -44,37 +54,66 @@ namespace osu.Game.Skinning
protected virtual bool AllowColourLookup => true;
/// <summary>
/// Constructs a new <see cref="SkinProvidingContainer"/> with a single skin added to the protected <see cref="SkinSources"/> list.
/// Constructs a new <see cref="SkinProvidingContainer"/> initialised with a single skin source.
/// </summary>
public SkinProvidingContainer(ISkin skin)
public SkinProvidingContainer([CanBeNull] ISkin skin)
: this()
{
SkinSources.Add(skin);
if (skin != null)
SkinSources.Add(skin);
}
/// <summary>
/// Constructs a new <see cref="SkinProvidingContainer"/> with no sources.
/// Up to the implementation for adding to the <see cref="SkinSources"/> list.
/// 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 source in args.NewItems.Cast<ISkin>().OfType<ISkinSource>())
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;
case NotifyCollectionChangedAction.Reset:
case NotifyCollectionChangedAction.Remove:
foreach (var source in args.OldItems.Cast<ISkin>().OfType<ISkinSource>())
source.SourceChanged -= OnSourceChanged;
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;
}
@ -85,103 +124,73 @@ namespace osu.Game.Skinning
{
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))
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
{
if (AllowDrawableLookup(component))
get
{
foreach (var skin in SkinSources)
yield return skin;
if (fallbackSource != null)
{
Drawable sourceDrawable;
if ((sourceDrawable = skin?.GetDrawableComponent(component)) != null)
return sourceDrawable;
foreach (var skin in fallbackSource.AllSources)
yield return skin;
}
}
}
if (!fallback)
return null;
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)
{
if (AllowTextureLookup(componentName))
foreach (var skin in SkinSources)
{
foreach (var skin in SkinSources)
{
Texture sourceTexture;
if ((sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
return sourceTexture;
}
Texture sourceTexture;
if ((sourceTexture = disableableSkinSources[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)
{
foreach (var skin in SkinSources)
{
ISample sourceSample;
if ((sourceSample = skin?.GetSample(sampleInfo)) != null)
return sourceSample;
}
ISample sourceSample;
if ((sourceSample = disableableSkinSources[skin]?.GetSample(sampleInfo)) != null)
return sourceSample;
}
if (!fallback)
return null;
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 (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup)
return lookupWithFallback<TLookup, TValue>(lookup, AllowColourLookup, fallback);
return lookupWithFallback<TLookup, TValue>(lookup, AllowConfigurationLookup, fallback);
}
private IBindable<TValue> lookupWithFallback<TLookup, TValue>(TLookup lookup, bool canUseSkinLookup, bool canUseFallback)
{
if (canUseSkinLookup)
foreach (var skin in SkinSources)
{
foreach (var skin in SkinSources)
{
IBindable<TValue> bindable;
if ((bindable = skin?.GetConfig<TLookup, TValue>(lookup)) != null)
return bindable;
}
IBindable<TValue> bindable;
if ((bindable = disableableSkinSources[skin]?.GetConfig<TLookup, TValue>(lookup)) != null)
return bindable;
}
if (!canUseFallback)
return null;
return fallbackSource?.GetConfig<TLookup, TValue>(lookup);
}
@ -191,9 +200,12 @@ namespace osu.Game.Skinning
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
fallbackSource = dependencies.Get<ISkinSource>();
if (fallbackSource != null)
fallbackSource.SourceChanged += OnSourceChanged;
if (AllowFallingBackToParent)
{
fallbackSource = dependencies.Get<ISkinSource>();
if (fallbackSource != null)
fallbackSource.SourceChanged += OnSourceChanged;
}
dependencies.CacheAs<ISkinSource>(this);
@ -214,35 +226,61 @@ namespace osu.Game.Skinning
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

@ -17,6 +17,8 @@ namespace osu.Game.Skinning
{
public bool IsEditable => false;
public bool UsesFixedAnchor { get; set; }
private readonly Action<Container> applyDefaults;
/// <summary>

View File

@ -18,6 +18,8 @@ namespace osu.Game.Skinning
private readonly BindableList<ISkinnableDrawable> components = new BindableList<ISkinnableDrawable>();
public bool ComponentsLoaded { get; private set; }
public SkinnableTargetContainer(SkinnableTarget target)
{
Target = target;
@ -30,6 +32,7 @@ namespace osu.Game.Skinning
{
ClearInternal();
components.Clear();
ComponentsLoaded = false;
content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer;
@ -39,8 +42,11 @@ namespace osu.Game.Skinning
{
AddInternal(wrapper);
components.AddRange(wrapper.Children.OfType<ISkinnableDrawable>());
ComponentsLoaded = true;
});
}
else
ComponentsLoaded = true;
}
/// <inheritdoc cref="ISkinnableTarget"/>