Add skin source fallback chain

This commit is contained in:
Dean Herbert
2018-03-20 16:26:36 +09:00
parent fd0391daf7
commit 9ad4e9284a
12 changed files with 142 additions and 23 deletions

View File

@ -105,6 +105,7 @@ namespace osu.Game
runMigrations(); runMigrations();
dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio));
dependencies.CacheAs<ISkinSource>(SkinManager);
var api = new APIAccess(LocalConfig); var api = new APIAccess(LocalConfig);

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -19,7 +18,7 @@ using OpenTK.Graphics;
namespace osu.Game.Rulesets.Objects.Drawables namespace osu.Game.Rulesets.Objects.Drawables
{ {
public abstract class DrawableHitObject : CompositeDrawable, IHasAccentColour public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColour
{ {
public readonly HitObject HitObject; public readonly HitObject HitObject;
@ -103,6 +102,14 @@ namespace osu.Game.Rulesets.Objects.Drawables
} }
} }
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboIndex combo)
AccentColour = skin.GetComboColour(combo) ?? Color4.White;
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -26,6 +26,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Skinning;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
@ -163,7 +164,11 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0, Alpha = 0,
}, },
RulesetContainer, new LocalSkinOverrideContainer(working.Skin)
{
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
},
new SkipOverlay(firstObjectTime) new SkipOverlay(firstObjectTime)
{ {
Clock = Clock, // skip button doesn't want to use the audio clock directly Clock = Clock, // skip button doesn't want to use the audio clock directly

View File

@ -3,6 +3,7 @@
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using OpenTK.Graphics;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
@ -11,17 +12,20 @@ namespace osu.Game.Skinning
public DefaultSkin() public DefaultSkin()
: base(SkinInfo.Default) : base(SkinInfo.Default)
{ {
Configuration = new SkinConfiguration(); Configuration = new SkinConfiguration
{
ComboColours =
{
new Color4(17, 136, 170, 255),
new Color4(102, 136, 0, 255),
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
}
};
} }
public override Drawable GetDrawableComponent(string componentName) public override Drawable GetDrawableComponent(string componentName) => null;
{
return null;
}
public override SampleChannel GetSample(string sampleName) public override SampleChannel GetSample(string sampleName) => null;
{
return null;
}
} }
} }

View File

@ -0,0 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Skinning
{
/// <summary>
/// Provides access to skinnable elements.
/// </summary>
public interface ISkinSource
{
event Action SourceChanged;
Drawable GetDrawableComponent(string componentName);
SampleChannel GetSample(string sampleName);
Color4? GetComboColour(IHasComboIndex comboObject);
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Skinning
{
public class LocalSkinOverrideContainer : Container, ISkinSource
{
public event Action SourceChanged;
public Drawable GetDrawableComponent(string componentName) => source.GetDrawableComponent(componentName) ?? fallbackSource?.GetDrawableComponent(componentName);
public SampleChannel GetSample(string sampleName) => source.GetSample(sampleName) ?? fallbackSource?.GetSample(sampleName);
public Color4? GetComboColour(IHasComboIndex comboObject) => source.GetComboColour(comboObject) ?? fallbackSource?.GetComboColour(comboObject);
private readonly ISkinSource source;
private ISkinSource fallbackSource;
public LocalSkinOverrideContainer(ISkinSource source)
{
this.source = source;
}
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
fallbackSource = dependencies.Get<ISkinSource>();
if (fallbackSource != null)
fallbackSource.SourceChanged += () => SourceChanged?.Invoke();
dependencies.CacheAs<ISkinSource>(this);
return dependencies;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (fallbackSource != null)
fallbackSource.SourceChanged -= SourceChanged;
}
}
}

View File

@ -4,19 +4,26 @@
using System; using System;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
public abstract class Skin : IDisposable public abstract class Skin : IDisposable, ISkinSource
{ {
public readonly SkinInfo SkinInfo; public readonly SkinInfo SkinInfo;
public virtual SkinConfiguration Configuration { get; protected set; } public virtual SkinConfiguration Configuration { get; protected set; }
public event Action SourceChanged;
public abstract Drawable GetDrawableComponent(string componentName); public abstract Drawable GetDrawableComponent(string componentName);
public abstract SampleChannel GetSample(string sampleName); public abstract SampleChannel GetSample(string sampleName);
public virtual Color4? GetComboColour(IHasComboIndex comboObject) =>
Configuration.ComboColours.Count == 0 ? (Color4?)null : Configuration.ComboColours[comboObject.ComboIndex % Configuration.ComboColours.Count];
protected Skin(SkinInfo skin) protected Skin(SkinInfo skin)
{ {
SkinInfo = skin; SkinInfo = skin;

View File

@ -7,14 +7,18 @@ using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo> public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource
{ {
private readonly AudioManager audio; private readonly AudioManager audio;
@ -89,6 +93,8 @@ namespace osu.Game.Skinning
{ {
if (skin.SkinInfo != CurrentSkinInfo.Value) if (skin.SkinInfo != CurrentSkinInfo.Value)
throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.");
SourceChanged?.Invoke();
}; };
// migrate older imports which didn't have access to skin.ini // migrate older imports which didn't have access to skin.ini
@ -108,5 +114,13 @@ namespace osu.Game.Skinning
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns> /// <returns>The first result for the provided query, or null if no results were found.</returns>
public SkinInfo Query(Expression<Func<SkinInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); public SkinInfo Query(Expression<Func<SkinInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query);
public event Action SourceChanged;
public Drawable GetDrawableComponent(string componentName) => CurrentSkin.Value.GetDrawableComponent(componentName);
public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName);
public Color4? GetComboColour(IHasComboIndex comboObject) => CurrentSkin.Value.GetComboColour(comboObject);
} }
} }

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning namespace osu.Game.Skinning
@ -12,7 +11,7 @@ namespace osu.Game.Skinning
/// </summary> /// </summary>
public abstract class SkinReloadableDrawable : CompositeDrawable public abstract class SkinReloadableDrawable : CompositeDrawable
{ {
private Bindable<Skin> skin; private ISkinSource skin;
/// <summary> /// <summary>
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource. /// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
@ -29,16 +28,18 @@ namespace osu.Game.Skinning
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(SkinManager skinManager) private void load(ISkinSource source)
{ {
skin = skinManager.CurrentSkin.GetBoundCopy(); skin = source;
skin.ValueChanged += skin => SkinChanged(skin, allowDefaultFallback || skin.SkinInfo == SkinInfo.Default); skin.SourceChanged += onChange;
} }
private void onChange() => SkinChanged(skin, allowDefaultFallback);
protected override void LoadAsyncComplete() protected override void LoadAsyncComplete()
{ {
base.LoadAsyncComplete(); base.LoadAsyncComplete();
skin.TriggerChange(); onChange();
} }
/// <summary> /// <summary>
@ -46,7 +47,7 @@ namespace osu.Game.Skinning
/// </summary> /// </summary>
/// <param name="skin">The new skin.</param> /// <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> /// <param name="allowFallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected virtual void SkinChanged(Skin skin, bool allowFallback) protected virtual void SkinChanged(ISkinSource skin, bool allowFallback)
{ {
} }
} }

View File

@ -40,7 +40,7 @@ namespace osu.Game.Skinning
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
protected override void SkinChanged(Skin skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{ {
var drawable = skin.GetDrawableComponent(componentName); var drawable = skin.GetDrawableComponent(componentName);
if (drawable != null) if (drawable != null)

View File

@ -31,7 +31,7 @@ namespace osu.Game.Skinning
public void Play() => channels?.ForEach(c => c.Play()); public void Play() => channels?.ForEach(c => c.Play());
protected override void SkinChanged(Skin skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{ {
channels = samples.Select(s => channels = samples.Select(s =>
{ {

View File

@ -874,8 +874,10 @@
<Compile Include="Screens\Tournament\Teams\StorageBackedTeamList.cs" /> <Compile Include="Screens\Tournament\Teams\StorageBackedTeamList.cs" />
<Compile Include="Skinning\BeatmapSkin.cs" /> <Compile Include="Skinning\BeatmapSkin.cs" />
<Compile Include="Skinning\DefaultSkin.cs" /> <Compile Include="Skinning\DefaultSkin.cs" />
<Compile Include="Skinning\ISkinSource.cs" />
<Compile Include="Skinning\LegacySkin.cs" /> <Compile Include="Skinning\LegacySkin.cs" />
<Compile Include="Skinning\LegacySkinDecoder.cs" /> <Compile Include="Skinning\LegacySkinDecoder.cs" />
<Compile Include="Skinning\LocalSkinOverrideContainer.cs" />
<Compile Include="Skinning\Skin.cs" /> <Compile Include="Skinning\Skin.cs" />
<Compile Include="Skinning\SkinConfiguration.cs" /> <Compile Include="Skinning\SkinConfiguration.cs" />
<Compile Include="Skinning\SkinFileInfo.cs" /> <Compile Include="Skinning\SkinFileInfo.cs" />