diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f5d7d15a47..54a279e977 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -105,6 +105,7 @@ namespace osu.Game runMigrations(); dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); + dependencies.CacheAs(SkinManager); var api = new APIAccess(LocalConfig); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 02f88d9ee0..945cd928d4 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -19,7 +18,7 @@ using OpenTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { - public abstract class DrawableHitObject : CompositeDrawable, IHasAccentColour + public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColour { 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() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8502812f26..b0472f0e0d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Ranking; +using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; namespace osu.Game.Screens.Play @@ -163,7 +164,11 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Alpha = 0, }, - RulesetContainer, + new LocalSkinOverrideContainer(working.Skin) + { + RelativeSizeAxes = Axes.Both, + Child = RulesetContainer + }, new SkipOverlay(firstObjectTime) { Clock = Clock, // skip button doesn't want to use the audio clock directly diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index c469e91250..aa891646c8 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using OpenTK.Graphics; namespace osu.Game.Skinning { @@ -11,17 +12,20 @@ namespace osu.Game.Skinning public DefaultSkin() : 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) - { - return null; - } + public override Drawable GetDrawableComponent(string componentName) => null; - public override SampleChannel GetSample(string sampleName) - { - return null; - } + public override SampleChannel GetSample(string sampleName) => null; } } diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs new file mode 100644 index 0000000000..ffa520ae6a --- /dev/null +++ b/osu.Game/Skinning/ISkinSource.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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 +{ + /// + /// Provides access to skinnable elements. + /// + public interface ISkinSource + { + event Action SourceChanged; + + Drawable GetDrawableComponent(string componentName); + + SampleChannel GetSample(string sampleName); + + Color4? GetComboColour(IHasComboIndex comboObject); + } +} diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs new file mode 100644 index 0000000000..66080bac17 --- /dev/null +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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(); + if (fallbackSource != null) + fallbackSource.SourceChanged += () => SourceChanged?.Invoke(); + + dependencies.CacheAs(this); + + return dependencies; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (fallbackSource != null) + fallbackSource.SourceChanged -= SourceChanged; + } + } +} diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 53bcf30b0e..8f8fe94337 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -4,19 +4,26 @@ using System; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Types; +using OpenTK.Graphics; namespace osu.Game.Skinning { - public abstract class Skin : IDisposable + public abstract class Skin : IDisposable, ISkinSource { public readonly SkinInfo SkinInfo; public virtual SkinConfiguration Configuration { get; protected set; } + public event Action SourceChanged; + public abstract Drawable GetDrawableComponent(string componentName); 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) { SkinInfo = skin; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index fa65b923fb..e4149404cd 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -7,14 +7,18 @@ using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Configuration; +using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.IO.Archives; +using osu.Game.Rulesets.Objects.Types; +using OpenTK.Graphics; namespace osu.Game.Skinning { - public class SkinManager : ArchiveModelManager + public class SkinManager : ArchiveModelManager, ISkinSource { private readonly AudioManager audio; @@ -89,6 +93,8 @@ namespace osu.Game.Skinning { if (skin.SkinInfo != CurrentSkinInfo.Value) 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 @@ -108,5 +114,13 @@ namespace osu.Game.Skinning /// The query. /// The first result for the provided query, or null if no results were found. public SkinInfo Query(Expression> 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); } } diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 3e33f952cd..04ba8427b2 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning @@ -12,7 +11,7 @@ namespace osu.Game.Skinning /// public abstract class SkinReloadableDrawable : CompositeDrawable { - private Bindable skin; + private ISkinSource skin; /// /// 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] - private void load(SkinManager skinManager) + private void load(ISkinSource source) { - skin = skinManager.CurrentSkin.GetBoundCopy(); - skin.ValueChanged += skin => SkinChanged(skin, allowDefaultFallback || skin.SkinInfo == SkinInfo.Default); + skin = source; + skin.SourceChanged += onChange; } + private void onChange() => SkinChanged(skin, allowDefaultFallback); + protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); - skin.TriggerChange(); + onChange(); } /// @@ -46,7 +47,7 @@ namespace osu.Game.Skinning /// /// The new skin. /// Whether fallback to default skin should be allowed if the custom skin is missing this resource. - protected virtual void SkinChanged(Skin skin, bool allowFallback) + protected virtual void SkinChanged(ISkinSource skin, bool allowFallback) { } } diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 81abc9e80c..77af44d5d6 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -40,7 +40,7 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } - protected override void SkinChanged(Skin skin, bool allowFallback) + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { var drawable = skin.GetDrawableComponent(componentName); if (drawable != null) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index fd52d62d59..07c8fd3735 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -31,7 +31,7 @@ namespace osu.Game.Skinning 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 => { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9a240b6899..1c902a158f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -874,8 +874,10 @@ + +