using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Newtonsoft.Json; using RecrownedAthenaeum.ContentReaders; using RecrownedAthenaeum.Data; using RecrownedAthenaeum.SpecialTypes; using System; using System.Collections.Generic; using System.IO; using System.Threading; namespace RecrownedAthenaeum.UI.SkinSystem { /// /// Called when the skin manager has completed a async action. /// /// The completed action. public delegate void AsyncComplete(SkinManager.Action actionCompleted); /// /// Manages reference to default and loading of custom skins. /// public class SkinManager { private const string EXTENSION = ".rbskin"; private readonly MergedSkin mergedSkin = new MergedSkin(); private Thread thread; private Action action; private GraphicsDevice graphicsDevice; private string selectedSkinPath; private SkinData skinDataToUse; /// /// Whether or not the skin manager is set up with a and . /// public bool MergingSkins { get { return (loadedSkin != null && SkinUseable); } } /// /// Whether or not this manager has been set up with at least a base skin. /// public bool SkinUseable { get { return BaseSkin == null; } } /// /// The list of paths for all found skins by . May be null. /// public volatile List skinPaths; /// /// The event that is called when a state changes. /// public event AsyncComplete AsyncCompleteEvent; /// /// The various possible states a skin manager could be in. /// public enum Action { /// /// After a search has completed. /// SEARCH, /// /// Having the skin generated to be in a useable state. /// LOAD } /// /// the skin that favors the selected skin, but still has a fallback to the default skin in case anything is missing. /// public ISkin Skin { get { return mergedSkin; } } /// /// The user loaded skin. Set by the skin loaded by calling . /// public ISkin loadedSkin { get { return mergedSkin.mainSkin; } private set { mergedSkin.mainSkin = value; } } /// /// The default skin in case the selected skin doesn't cover a specific definition or color. /// public ISkin BaseSkin { get { return mergedSkin.alternateSkin; } set { mergedSkin.alternateSkin = value; } } /// /// The directory that contains the skins. /// public string skinsDirectory; /// /// Performs a recursive asynchronous search of the directory given in a path set by . /// public void SearchSkinDirectory() { action = Action.SEARCH; AttemptAsync(); } /// /// Reads skin data if extension is valid. /// /// the path to load from. /// A that holds all the information and some metadata for the loaded skin. public SkinData ReadSkinData(string path) { if (path.ToLower().EndsWith(EXTENSION)) { return JsonConvert.DeserializeObject(File.ReadAllText(path)); } throw new ArgumentException("The path given does not point to a file with the required extension \"" + EXTENSION + "\" rather, has \"" + Path.GetExtension(path) + "\"."); } /// /// loads a skin asynchronously to the . /// /// Graphics device to use for texture creation. Uses graphics device from by default. /// The data to generate from. /// The path pointing to the file with the extension "". public void LoadSkin(SkinData skinData, string path, GraphicsDevice graphicsDevice = null) { if (graphicsDevice == null) graphicsDevice = Configuration.graphicsDeviceManager.GraphicsDevice; action = Action.LOAD; this.graphicsDevice = graphicsDevice; this.selectedSkinPath = path; this.skinDataToUse = skinData; AttemptAsync(); } private void AttemptAsync() { if (thread != null && thread.IsAlive) throw new InvalidOperationException("Already performing task: " + action); thread = new Thread(ThreadStart); } private void ThreadStart() { switch (action) { case Action.SEARCH: SearchForSkins(); OnAsyncComplete(action); break; case Action.LOAD: loadedSkin = LoadSkinFromData(skinDataToUse, selectedSkinPath, graphicsDevice); OnAsyncComplete(action); break; } } private void SearchForSkins() { skinPaths.Clear(); skinPaths = RecursiveSkinSearch(skinsDirectory); } private Skin LoadSkinFromData(SkinData skinData, string path, GraphicsDevice graphicsDevice) { TextureAtlasDataReader textureAtlasDataReader = new TextureAtlasDataReader(); FileInfo[] skinFiles = Directory.GetParent(path).GetFiles(); Dictionary fileNameWithPath = new Dictionary(); for (int i = 0; i < skinFiles.Length; i++) { fileNameWithPath.Add(skinFiles[i].Name, skinFiles[i].FullName); } TextureAtlasData atlasData = JsonConvert.DeserializeObject(fileNameWithPath[skinData.nameOfTextureAtlas]); Texture2D atlasTexture; using (FileStream stream = new FileStream(fileNameWithPath[atlasData.textureName], FileMode.Open)) { atlasTexture = Texture2D.FromStream(graphicsDevice, stream); } TextureAtlas.Region[] regions = textureAtlasDataReader.GenerateAtlasRegionsFromData(atlasData, atlasTexture); TextureAtlas textureAtlas = new TextureAtlas(atlasTexture, regions); Texture2D cursorTexture; if (Path.HasExtension(skinData.cursorTextureName) && fileNameWithPath.ContainsKey(skinData.cursorTextureName)) { using (FileStream stream = new FileStream(fileNameWithPath[skinData.cursorTextureName], FileMode.Open)) { cursorTexture = Texture2D.FromStream(graphicsDevice, stream); } } else { cursorTexture = textureAtlas[skinData.cursorTextureName].AsTexture2D(graphicsDevice); } Skin skin = new Skin(new TextureAtlas(atlasTexture, regions), cursorTexture); for (int i = 0; i < skinData.colors.Length; i++) { SkinData.ColorData colorData = skinData.colors[i]; skin.AddColor(colorData.name, new Color(colorData.r, colorData.g, colorData.b, colorData.a)); } for (int i = 0; i < skinData.definitions.Length; i++) { SkinData.DefinitionData definitionData = skinData.definitions[i]; skin.AddDefinition(definitionData.name, definitionData.skin); } return skin; } private List RecursiveSkinSearch(string path) { string[] files = Directory.GetFiles(path); string[] folders = Directory.GetDirectories(path); List skins = new List(); for (int i = 0; i < files.Length; i++) { if (files[i].ToLower().EndsWith(EXTENSION)) { skins.Add(files[i]); } } for (int i = 0; i < folders.Length; i++) { skins.AddRange(RecursiveSkinSearch(folders[i])); } return skins; } private void OnAsyncComplete(Action newState) { AsyncCompleteEvent?.Invoke(newState); } } }