Merge pull request #2254 from peppy/skin--completion

Add skin/beatmap lookup hierarchy
This commit is contained in:
Dan Balasescu
2018-03-22 19:28:35 +09:00
committed by GitHub
20 changed files with 228 additions and 34 deletions

View File

@ -8,6 +8,8 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
@ -57,6 +59,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
} }
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
private const float preempt = 1000; private const float preempt = 1000;
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)

View File

@ -5,6 +5,9 @@ using System.ComponentModel;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -34,6 +37,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein); protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein);
protected virtual void UpdateCurrentState(ArmedState state) protected virtual void UpdateCurrentState(ArmedState state)

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingMode.Additive, Blending = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0.2f, Alpha = 0.2f,
}, false); }, s => s.GetTexture("Play/osu/hitcircle") == null);
} }
} }
} }

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
} }
}, false); }, s => s.GetTexture("Play/osu/hitcircle") == null);
} }
} }
} }

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Texture = textures.Get(name), Texture = textures.Get(name),
Blending = BlendingMode.Additive, Blending = BlendingMode.Additive,
Alpha = 0.5f Alpha = 0.5f
}, false); }, s => s.GetTexture("Play/osu/hitcircle") == null);
} }
} }
} }

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.5f), Colour = Color4.White.Opacity(0.5f),
}, },
Child = new Box() Child = new Box()
}, false), }, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new OsuSpriteText number = new OsuSpriteText
{ {
Text = @"1", Text = @"1",

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;

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
/// </summary>
public interface IHasComboIndex : IHasCombo
{
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int IndexInCurrentCombo { get; set; }
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int ComboIndex { get; set; }
/// <summary>
/// Whether this is the last object in the current combo.
/// </summary>
bool LastInCombo { get; set; }
}
}

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,8 @@
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using OpenTK.Graphics;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
@ -11,17 +13,22 @@ 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 Texture GetTexture(string componentName) => null;
{
return null; public override SampleChannel GetSample(string sampleName) => null;
}
} }
} }

View File

@ -0,0 +1,28 @@
// 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.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
/// <summary>
/// Provides access to skinnable elements.
/// </summary>
public interface ISkinSource
{
event Action SourceChanged;
Drawable GetDrawableComponent(string componentName);
Texture GetTexture(string componentName);
SampleChannel GetSample(string sampleName);
TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class;
TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct;
}
}

View File

@ -56,12 +56,14 @@ namespace osu.Game.Skinning
break; break;
} }
var texture = Textures.Get(componentName); var texture = GetTexture(componentName);
if (texture == null) return null; if (texture == null) return null;
return new Sprite { Texture = texture }; return new Sprite { Texture = texture };
} }
public override Texture GetTexture(string componentName) => Textures.Get(componentName);
public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName); public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName);
protected class LegacySkinResourceStore<T> : IResourceStore<byte[]> protected class LegacySkinResourceStore<T> : IResourceStore<byte[]>

View File

@ -0,0 +1,72 @@
// 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.Framework.Graphics.Textures;
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 Texture GetTexture(string componentName) => source.GetTexture(componentName) ?? fallbackSource.GetTexture(componentName);
public SampleChannel GetSample(string sampleName) => source.GetSample(sampleName) ?? fallbackSource?.GetSample(sampleName);
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct
{
TValue? val = null;
var conf = (source as Skin)?.Configuration as TConfiguration;
if (conf != null)
val = query?.Invoke(conf);
return val ?? fallbackSource?.GetValue(query);
}
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class
{
TValue val = null;
var conf = (source as Skin)?.Configuration as TConfiguration;
if (conf != null)
val = query?.Invoke(conf);
return val ?? fallbackSource?.GetValue(query);
}
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,30 @@
using System; using System;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
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 abstract Texture GetTexture(string componentName);
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class
=> Configuration is TConfiguration conf ? query?.Invoke(conf) : null;
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct
=> Configuration is TConfiguration conf ? query?.Invoke(conf) : null;
protected Skin(SkinInfo skin) protected Skin(SkinInfo skin)
{ {
SkinInfo = skin; SkinInfo = skin;

View File

@ -7,14 +7,17 @@ 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.Graphics.Textures;
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;
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 +92,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 +113,17 @@ 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 Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName);
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class => CurrentSkin.Value.GetValue(query);
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct => CurrentSkin.Value.GetValue(query);
} }
} }

View File

@ -1,8 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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 System;
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,33 +12,36 @@ namespace osu.Game.Skinning
/// </summary> /// </summary>
public abstract class SkinReloadableDrawable : CompositeDrawable public abstract class SkinReloadableDrawable : CompositeDrawable
{ {
private Bindable<Skin> skin; private readonly Func<ISkinSource, bool> allowFallback;
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.
/// </summary> /// </summary>
private readonly bool allowDefaultFallback; private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(skin);
/// <summary> /// <summary>
/// Create a new <see cref="SkinReloadableDrawable"/> /// Create a new <see cref="SkinReloadableDrawable"/>
/// </summary> /// </summary>
/// <param name="fallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param> /// <param name="fallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected SkinReloadableDrawable(bool fallback = true) protected SkinReloadableDrawable(Func<ISkinSource, bool> allowFallback = null)
{ {
allowDefaultFallback = fallback; this.allowFallback = allowFallback;
} }
[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 +49,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

@ -9,8 +9,8 @@ namespace osu.Game.Skinning
{ {
public class SkinnableDrawable : SkinnableDrawable<Drawable> public class SkinnableDrawable : SkinnableDrawable<Drawable>
{ {
public SkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, bool fallback = true, bool restrictSize = true) public SkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true)
: base(name, defaultImplementation, fallback, restrictSize) : base(name, defaultImplementation, allowFallback, restrictSize)
{ {
} }
} }
@ -31,7 +31,7 @@ namespace osu.Game.Skinning
/// <param name="defaultImplementation">A function to create the default skin implementation of this element.</param> /// <param name="defaultImplementation">A function to create the default skin implementation of this element.</param>
/// <param name="fallback">Whther to fallback to the default implementation when a custom skin is specified but not implementation is present.</param> /// <param name="fallback">Whther to fallback to the default implementation when a custom skin is specified but not implementation is present.</param>
/// <param name="restrictSize">Whether a user-skin drawable should be limited to the size of our parent.</param> /// <param name="restrictSize">Whether a user-skin drawable should be limited to the size of our parent.</param>
public SkinnableDrawable(string name, Func<string, T> defaultImplementation, bool fallback = true, bool restrictSize = true) : base(fallback) public SkinnableDrawable(string name, Func<string, T> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true) : base(allowFallback)
{ {
componentName = name; componentName = name;
createDefault = defaultImplementation; createDefault = defaultImplementation;
@ -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\LegacyBeatmapSkin.cs" /> <Compile Include="Skinning\LegacyBeatmapSkin.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" />