osu/osu.Game/Skinning/SkinManager.cs

137 lines
5.7 KiB
C#
Raw Normal View History

// 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.
2018-04-13 09:19:50 +00:00
using System;
using System.Collections.Generic;
using System.IO;
2018-04-13 09:19:50 +00:00
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
2018-04-13 09:19:50 +00:00
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables;
2018-04-13 09:19:50 +00:00
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
2018-04-13 09:19:50 +00:00
using osu.Framework.Platform;
2019-08-23 11:32:43 +00:00
using osu.Game.Audio;
2018-04-13 09:19:50 +00:00
using osu.Game.Database;
using osu.Game.IO.Archives;
namespace osu.Game.Skinning
{
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource
{
private readonly AudioManager audio;
private readonly IResourceStore<byte[]> legacyDefaultResources;
2018-04-13 09:19:50 +00:00
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin());
public readonly Bindable<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
public override string[] HandledExtensions => new[] { ".osk" };
protected override string[] HashableFileTypes => new[] { ".ini" };
protected override string ImportFromStablePath => "Skins";
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio, IResourceStore<byte[]> legacyDefaultResources)
2018-11-28 10:01:22 +00:00
: base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost)
{
this.audio = audio;
this.legacyDefaultResources = legacyDefaultResources;
2018-11-28 10:01:22 +00:00
ItemRemoved += removedInfo =>
{
// check the removed skin is not the current user choice. if it is, switch back to default.
if (removedInfo.ID == CurrentSkinInfo.Value.ID)
CurrentSkinInfo.Value = SkinInfo.Default;
};
2019-07-26 10:29:06 +00:00
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
CurrentSkin.ValueChanged += skin =>
2018-11-28 10:01:22 +00:00
{
if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value)
2018-11-28 10:01:22 +00:00
throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.");
SourceChanged?.Invoke();
};
}
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk";
2018-04-13 09:19:50 +00:00
/// <summary>
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
2018-04-13 09:19:50 +00:00
/// </summary>
/// <returns>A list of available <see cref="SkinInfo"/>.</returns>
public List<SkinInfo> GetAllUsableSkins()
{
var userSkins = GetAllUserSkins();
2018-04-13 09:19:50 +00:00
userSkins.Insert(0, SkinInfo.Default);
userSkins.Insert(1, DefaultLegacySkin.Info);
2018-04-13 09:19:50 +00:00
return userSkins;
}
/// <summary>
/// Returns a list of all usable <see cref="SkinInfo"/>s that have been loaded by the user.
/// </summary>
/// <returns>A list of available <see cref="SkinInfo"/>.</returns>
public List<SkinInfo> GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
2018-11-28 10:01:22 +00:00
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };
2018-04-13 09:19:50 +00:00
protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
2018-04-13 09:19:50 +00:00
{
await base.Populate(model, archive, cancellationToken);
2018-04-13 09:19:50 +00:00
2019-07-26 10:29:06 +00:00
Skin reference = GetSkin(model);
2019-04-01 03:16:05 +00:00
2018-04-13 09:19:50 +00:00
if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name))
{
model.Name = reference.Configuration.SkinInfo.Name;
model.Creator = reference.Configuration.SkinInfo.Creator;
}
else
{
model.Name = model.Name.Replace(".osk", "");
2019-11-12 10:35:08 +00:00
model.Creator ??= "Unknown";
2018-04-13 09:19:50 +00:00
}
}
/// <summary>
/// Retrieve a <see cref="Skin"/> instance for the provided <see cref="SkinInfo"/>
/// </summary>
/// <param name="skinInfo">The skin to lookup.</param>
/// <returns>A <see cref="Skin"/> instance correlating to the provided <see cref="SkinInfo"/>.</returns>
2019-07-26 10:29:06 +00:00
public Skin GetSkin(SkinInfo skinInfo)
2018-04-13 09:19:50 +00:00
{
if (skinInfo == SkinInfo.Default)
return new DefaultSkin();
if (skinInfo == DefaultLegacySkin.Info)
return new DefaultLegacySkin(legacyDefaultResources, audio);
2018-04-13 09:19:50 +00:00
return new LegacySkin(skinInfo, Files.Store, audio);
}
/// <summary>
/// Perform a lookup query on available <see cref="SkinInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <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 event Action SourceChanged;
public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component);
2018-04-13 09:19:50 +00:00
public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
2019-08-23 11:32:43 +00:00
public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
2018-04-13 09:19:50 +00:00
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => CurrentSkin.Value.GetConfig<TLookup, TValue>(lookup);
2018-04-13 09:19:50 +00:00
}
}