Merge branch 'master' into skin-editor-closest-anchor

This commit is contained in:
Dean Herbert
2021-06-18 18:54:07 +09:00
committed by GitHub
313 changed files with 4777 additions and 1903 deletions

View File

@ -22,6 +22,8 @@ namespace osu.Game.Skinning
{
public class DefaultSkin : Skin
{
private readonly IStorageResourceProvider resources;
public DefaultSkin(IStorageResourceProvider resources)
: this(SkinInfo.Default, resources)
{
@ -31,12 +33,23 @@ namespace osu.Game.Skinning
public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources)
: base(skin, resources)
{
this.resources = resources;
Configuration = new DefaultSkinConfiguration();
}
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
public override ISample GetSample(ISampleInfo sampleInfo) => null;
public override ISample GetSample(ISampleInfo sampleInfo)
{
foreach (var lookup in sampleInfo.LookupNames)
{
var sample = resources.AudioManager.Samples.Get(lookup);
if (sample != null)
return sample;
}
return null;
}
public override Drawable GetDrawableComponent(ISkinComponent component)
{

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;
@ -33,10 +30,13 @@ namespace osu.Game.Skinning
public bool UsesFixedAnchor { get; set; }
[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.
@ -78,7 +78,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)
{
@ -97,7 +97,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");
@ -128,9 +128,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,9 +150,9 @@ namespace osu.Game.Skinning
}
}
internal class LegacyOldStyleFill : LegacyHealthPiece
internal abstract class LegacyFill : LegacyHealthPiece
{
public LegacyOldStyleFill(ISkinSource skin)
protected LegacyFill(ISkin skin)
{
// required for sizing correctly..
var firstFrame = getTexture(skin, "colour-0");
@ -164,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 LegacyNewStyleFill(ISkinSource skin)
public LegacyOldStyleFill(ISkin skin)
: base(skin)
{
InternalChild = new Sprite
{
Texture = getTexture(skin, "colour"),
};
Position = new Vector2(3, 10) * 1.6f;
}
}
Size = InternalChild.Size;
internal class LegacyNewStyleFill : LegacyFill
{
public LegacyNewStyleFill(ISkin skin)
: base(skin)
{
Position = new Vector2(7.5f, 7.8f) * 1.6f;
Masking = true;
}
protected override void Update()

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,7 +493,9 @@ 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 => Source.SourceChanged += value;
remove => Source.SourceChanged -= value;
}
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -70,33 +71,50 @@ namespace osu.Game.Skinning
updateSample();
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
protected override void LoadComplete()
{
base.SkinChanged(skin, allowFallback);
base.LoadComplete();
CurrentSkin.SourceChanged += skinChangedImmediate;
}
private void skinChangedImmediate()
{
// Clean up the previous sample immediately on a source change.
// This avoids a potential call to Play() of an already disposed sample (samples are disposed along with the skin, but SkinChanged is scheduled).
clearPreviousSamples();
}
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin);
updateSample();
}
/// <summary>
/// Whether this sample was playing before a skin source change.
/// </summary>
private bool wasPlaying;
private void clearPreviousSamples()
{
// only run if the samples aren't already cleared.
// this ensures the "wasPlaying" state is stored correctly even if multiple clear calls are executed.
if (!sampleContainer.Any()) return;
wasPlaying = Playing;
sampleContainer.Clear();
Sample = null;
}
private void updateSample()
{
if (sampleInfo == null)
return;
bool wasPlaying = Playing;
sampleContainer.Clear();
Sample = null;
var sample = CurrentSkin.GetSample(sampleInfo);
if (sample == null && AllowDefaultFallback)
{
foreach (var lookup in sampleInfo.LookupNames)
{
if ((sample = sampleStore.Get(lookup)) != null)
break;
}
}
if (sample == null)
return;
@ -155,6 +173,14 @@ namespace osu.Game.Skinning
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (CurrentSkin != null)
CurrentSkin.SourceChanged -= skinChangedImmediate;
}
#region Re-expose AudioContainer
public BindableNumber<double> Volume => sampleContainer.Volume;

View File

@ -39,7 +39,7 @@ namespace osu.Game.Skinning
private readonly IResourceStore<byte[]> resources;
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin(null));
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>();
public readonly Bindable<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
public override IEnumerable<string> HandledExtensions => new[] { ".osk" };
@ -48,6 +48,10 @@ namespace osu.Game.Skinning
protected override string ImportFromStablePath => "Skins";
private readonly Skin defaultLegacySkin;
private readonly Skin defaultSkin;
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore<byte[]> resources, AudioManager audio)
: base(storage, contextFactory, new SkinStore(contextFactory, storage), host)
{
@ -55,7 +59,12 @@ namespace osu.Game.Skinning
this.host = host;
this.resources = resources;
defaultLegacySkin = new DefaultLegacySkin(this);
defaultSkin = new DefaultSkin(this);
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
CurrentSkin.Value = defaultSkin;
CurrentSkin.ValueChanged += skin =>
{
if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value)
@ -204,13 +213,43 @@ 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;
if (lookupFunction(defaultSkin))
return defaultSkin;
return null;
}
private T lookupWithFallback<T>(Func<ISkin, T> lookupFunction)
where T : class
{
if (lookupFunction(CurrentSkin.Value) 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);
}
#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,10 +21,14 @@ namespace osu.Game.Skinning
{
public event Action SourceChanged;
[CanBeNull]
private readonly ISkin skin;
[CanBeNull]
private ISkinSource fallbackSource;
private readonly NoFallbackProxy noFallbackLookupProxy;
protected virtual bool AllowDrawableLookup(ISkinComponent component) => true;
protected virtual bool AllowTextureLookup(string componentName) => true;
@ -39,57 +44,106 @@ namespace osu.Game.Skinning
this.skin = skin;
RelativeSizeAxes = Axes.Both;
noFallbackLookupProxy = new NoFallbackProxy(this);
if (skin is ISkinSource source)
source.SourceChanged += TriggerSourceChanged;
}
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)
{
// 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)
{
Drawable sourceDrawable;
if (AllowDrawableLookup(component) && (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)
{
Texture sourceTexture;
if (AllowTextureLookup(componentName) && (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)
{
ISample sourceChannel;
if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null)
return sourceChannel;
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 (skin != null)
{
if (lookup is GlobalSkinColours || lookup is SkinCustomColourLookup)
return lookupWithFallback<TLookup, TValue>(lookup, AllowColourLookup);
return lookupWithFallback<TLookup, TValue>(lookup, AllowColourLookup, fallback);
return lookupWithFallback<TLookup, TValue>(lookup, AllowConfigurationLookup);
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)
private IBindable<TValue> lookupWithFallback<TLookup, TValue>(TLookup lookup, bool canUseSkinLookup, bool canUseFallback)
{
if (canUseSkinLookup)
{
var bindable = skin.GetConfig<TLookup, TValue>(lookup);
var bindable = skin?.GetConfig<TLookup, TValue>(lookup);
if (bindable != null)
return bindable;
}
if (!canUseFallback)
return null;
return fallbackSource?.GetConfig<TLookup, TValue>(lookup);
}
@ -117,6 +171,40 @@ namespace osu.Game.Skinning
if (fallbackSource != null)
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<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
=> provider.GetConfig<TLookup, TValue>(lookup, false);
public event Action SourceChanged
{
add => provider.SourceChanged += value;
remove => provider.SourceChanged -= value;
}
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) =>
provider.FindProvider(lookupFunction);
}
}
}

View File

@ -22,22 +22,6 @@ namespace osu.Game.Skinning
/// </summary>
protected ISkinSource CurrentSkin { get; private set; }
private readonly Func<ISkinSource, bool> allowFallback;
/// <summary>
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
/// </summary>
protected bool AllowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin);
/// <summary>
/// Create a new <see cref="SkinReloadableDrawable"/>
/// </summary>
/// <param name="allowFallback">A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.</param>
protected SkinReloadableDrawable(Func<ISkinSource, bool> allowFallback = null)
{
this.allowFallback = allowFallback;
}
[BackgroundDependencyLoader]
private void load(ISkinSource source)
{
@ -58,7 +42,7 @@ namespace osu.Game.Skinning
private void skinChanged()
{
SkinChanged(CurrentSkin, AllowDefaultFallback);
SkinChanged(CurrentSkin);
OnSkinChanged?.Invoke();
}
@ -66,8 +50,7 @@ namespace osu.Game.Skinning
/// Called when a change is made to the skin.
/// </summary>
/// <param name="skin">The new skin.</param>
/// <param name="allowFallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected virtual void SkinChanged(ISkinSource skin, bool allowFallback)
protected virtual void SkinChanged(ISkinSource skin)
{
}

View File

@ -40,17 +40,14 @@ namespace osu.Game.Skinning
/// </summary>
/// <param name="component">The namespace-complete resource name for this skinnable element.</param>
/// <param name="defaultImplementation">A function to create the default skin implementation of this element.</param>
/// <param name="allowFallback">A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.</param>
/// <param name="confineMode">How (if at all) the <see cref="Drawable"/> should be resize to fit within our own bounds.</param>
public SkinnableDrawable(ISkinComponent component, Func<ISkinComponent, Drawable> defaultImplementation = null, Func<ISkinSource, bool> allowFallback = null,
ConfineMode confineMode = ConfineMode.NoScaling)
: this(component, allowFallback, confineMode)
public SkinnableDrawable(ISkinComponent component, Func<ISkinComponent, Drawable> defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
: this(component, confineMode)
{
createDefault = defaultImplementation;
}
protected SkinnableDrawable(ISkinComponent component, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(allowFallback)
protected SkinnableDrawable(ISkinComponent component, ConfineMode confineMode = ConfineMode.NoScaling)
{
this.component = component;
this.confineMode = confineMode;
@ -76,13 +73,13 @@ namespace osu.Game.Skinning
/// </summary>
protected virtual bool ApplySizeRestrictionsToDefault => false;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
protected override void SkinChanged(ISkinSource skin)
{
Drawable = skin.GetDrawableComponent(component);
isDefault = false;
if (Drawable == null && allowFallback)
if (Drawable == null)
{
Drawable = CreateDefault(component);
isDefault = true;

View File

@ -1,7 +1,6 @@
// 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
@ -19,8 +18,8 @@ namespace osu.Game.Skinning
[Resolved]
private TextureStore textures { get; set; }
public SkinnableSprite(string textureName, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(new SpriteComponent(textureName), allowFallback, confineMode)
public SkinnableSprite(string textureName, ConfineMode confineMode = ConfineMode.NoScaling)
: base(new SpriteComponent(textureName), confineMode)
{
}

View File

@ -9,14 +9,14 @@ namespace osu.Game.Skinning
{
public class SkinnableSpriteText : SkinnableDrawable, IHasText
{
public SkinnableSpriteText(ISkinComponent component, Func<ISkinComponent, SpriteText> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(component, defaultImplementation, allowFallback, confineMode)
public SkinnableSpriteText(ISkinComponent component, Func<ISkinComponent, SpriteText> defaultImplementation, ConfineMode confineMode = ConfineMode.NoScaling)
: base(component, defaultImplementation, confineMode)
{
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin, allowFallback);
base.SkinChanged(skin);
if (Drawable is IHasText textDrawable)
textDrawable.Text = Text;

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"/>
@ -73,9 +79,9 @@ namespace osu.Game.Skinning
components.Remove(component);
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin, allowFallback);
base.SkinChanged(skin);
Reload();
}