2018-04-13 17:19:50 +08:00
|
|
|
|
// 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 System.Collections.Generic;
|
|
|
|
|
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.Graphics.Textures;
|
|
|
|
|
using osu.Framework.Platform;
|
|
|
|
|
using osu.Game.Database;
|
|
|
|
|
using osu.Game.IO.Archives;
|
|
|
|
|
|
|
|
|
|
namespace osu.Game.Skinning
|
|
|
|
|
{
|
|
|
|
|
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource
|
|
|
|
|
{
|
|
|
|
|
private readonly AudioManager audio;
|
|
|
|
|
|
|
|
|
|
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" };
|
|
|
|
|
|
2018-11-28 18:16:05 +08:00
|
|
|
|
protected override string[] HashableFileTypes => new[] { ".ini" };
|
|
|
|
|
|
2018-08-31 17:28:53 +08:00
|
|
|
|
protected override string ImportFromStablePath => "Skins";
|
|
|
|
|
|
2018-11-28 18:01:22 +08:00
|
|
|
|
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio)
|
|
|
|
|
: base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost)
|
|
|
|
|
{
|
|
|
|
|
this.audio = audio;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = getSkin(info);
|
|
|
|
|
CurrentSkin.ValueChanged += skin =>
|
|
|
|
|
{
|
|
|
|
|
if (skin.SkinInfo != CurrentSkinInfo.Value)
|
|
|
|
|
throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.");
|
|
|
|
|
|
|
|
|
|
SourceChanged?.Invoke();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-13 17:19:50 +08:00
|
|
|
|
/// <summary>
|
2018-08-31 17:28:53 +08:00
|
|
|
|
/// 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 17:19:50 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>A list of available <see cref="SkinInfo"/>.</returns>
|
|
|
|
|
public List<SkinInfo> GetAllUsableSkins()
|
|
|
|
|
{
|
2018-08-31 17:28:53 +08:00
|
|
|
|
var userSkins = GetAllUserSkins();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
userSkins.Insert(0, SkinInfo.Default);
|
|
|
|
|
return userSkins;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-31 17:28:53 +08:00
|
|
|
|
/// <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 18:01:22 +08:00
|
|
|
|
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
protected override void Populate(SkinInfo model, ArchiveReader archive)
|
|
|
|
|
{
|
|
|
|
|
base.Populate(model, archive);
|
|
|
|
|
|
2018-11-28 18:01:22 +08:00
|
|
|
|
Skin reference = getSkin(model);
|
2018-04-13 17:19:50 +08: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", "");
|
|
|
|
|
model.Creator = "Unknown";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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>
|
2018-11-28 18:01:22 +08:00
|
|
|
|
private Skin getSkin(SkinInfo skinInfo)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
if (skinInfo == SkinInfo.Default)
|
|
|
|
|
return new DefaultSkin();
|
|
|
|
|
|
|
|
|
|
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(string componentName) => CurrentSkin.Value.GetDrawableComponent(componentName);
|
|
|
|
|
|
|
|
|
|
public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
|
|
|
|
|
|
|
|
|
|
public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName);
|
|
|
|
|
|
2019-01-07 19:12:39 +08:00
|
|
|
|
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
}
|