mirror of
https://github.com/osukey/osukey.git
synced 2025-08-04 23:24:04 +09:00
Merge branch 'master' into ruleset-resources-skin
This commit is contained in:
@ -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).
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable
|
||||
{
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public LegacyAccuracyCounter()
|
||||
{
|
||||
Anchor = Anchor.TopRight;
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public bool IsEditable => false;
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
private readonly Action<Container> applyDefaults;
|
||||
|
||||
/// <summary>
|
||||
|
@ -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"/>
|
||||
|
Reference in New Issue
Block a user