2019-01-16 01:34:59 +00:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
using Newtonsoft.Json ;
2019-01-21 03:45:15 +00:00
using RecrownedAthenaeum.ContentReaders ;
2019-01-20 07:08:38 +00:00
using RecrownedAthenaeum.Data ;
2019-01-16 01:34:59 +00:00
using RecrownedAthenaeum.SpecialTypes ;
using System ;
using System.Collections.Generic ;
using System.IO ;
2019-01-21 00:05:40 +00:00
using System.Threading ;
2019-01-16 01:34:59 +00:00
2019-01-27 23:39:18 +00:00
namespace RecrownedAthenaeum.UI.SkinSystem
2019-01-16 01:34:59 +00:00
{
2019-01-21 00:05:40 +00:00
/// <summary>
/// Called when the skin manager has completed a async action.
/// </summary>
/// <param name="actionCompleted">The completed action.</param>
public delegate void AsyncComplete ( SkinManager . Action actionCompleted ) ;
2019-01-16 01:34:59 +00:00
/// <summary>
/// Manages reference to default and loading of custom skins.
/// </summary>
public class SkinManager
{
private const string EXTENSION = ".rbskin" ;
2019-01-20 07:08:38 +00:00
private readonly MergedSkin mergedSkin = new MergedSkin ( ) ;
2019-01-21 00:05:40 +00:00
private Thread thread ;
private Action action ;
private GraphicsDevice graphicsDevice ;
private string selectedSkinPath ;
private SkinData skinDataToUse ;
2019-01-21 03:10:52 +00:00
/// <summary>
/// Whether or not the skin manager is set up with a <see cref="BaseSkin"/> and <see cref="loadedSkin"/>.
/// </summary>
2019-01-27 04:56:53 +00:00
public bool MergingSkins { get { return ( loadedSkin ! = null & & SkinUseable ) ; } }
/// <summary>
/// Whether or not this manager has been set up with at least a base skin.
/// </summary>
public bool SkinUseable { get { return BaseSkin = = null ; } }
2019-01-21 01:15:19 +00:00
2019-01-21 00:05:40 +00:00
/// <summary>
/// The list of paths for all found skins by <see cref="SearchSkinDirectory"/>. May be null.
/// </summary>
public volatile List < string > skinPaths ;
/// <summary>
/// The event that is called when a state changes.
/// </summary>
public event AsyncComplete AsyncCompleteEvent ;
/// <summary>
/// The various possible states a skin manager could be in.
/// </summary>
public enum Action
{
/// <summary>
/// After a search has completed.
/// </summary>
SEARCH ,
/// <summary>
/// Having the skin generated to be in a useable state.
/// </summary>
2019-01-21 01:15:19 +00:00
LOAD
2019-01-21 00:05:40 +00:00
}
2019-01-20 07:08:38 +00:00
/// <summary>
/// the skin that favors the selected skin, but still has a fallback to the default skin in case anything is missing.
/// </summary>
public ISkin Skin { get { return mergedSkin ; } }
/// <summary>
2019-01-22 01:56:51 +00:00
/// The user loaded skin. Set by the skin loaded by calling <see cref="LoadSkin(SkinData, string, GraphicsDevice)"/>.
2019-01-20 07:08:38 +00:00
/// </summary>
2019-01-21 03:10:52 +00:00
public ISkin loadedSkin { get { return mergedSkin . mainSkin ; } private set { mergedSkin . mainSkin = value ; } }
2019-01-20 07:08:38 +00:00
/// <summary>
2019-01-27 20:57:35 +00:00
/// The default skin in case the selected skin doesn't cover a specific definition or color.
2019-01-20 07:08:38 +00:00
/// </summary>
public ISkin BaseSkin { get { return mergedSkin . alternateSkin ; } set { mergedSkin . alternateSkin = value ; } }
2019-01-16 01:34:59 +00:00
/// <summary>
/// The directory that contains the skins.
/// </summary>
public string skinsDirectory ;
2019-01-20 07:08:38 +00:00
2019-01-16 01:34:59 +00:00
/// <summary>
2019-01-21 00:05:40 +00:00
/// Performs a recursive asynchronous search of the directory given in a path set by <see cref="skinsDirectory"/>.
2019-01-16 01:34:59 +00:00
/// </summary>
2019-01-21 00:05:40 +00:00
public void SearchSkinDirectory ( )
2019-01-16 01:34:59 +00:00
{
2019-01-21 00:05:40 +00:00
action = Action . SEARCH ;
AttemptAsync ( ) ;
2019-01-16 01:34:59 +00:00
}
/// <summary>
2019-01-21 01:15:19 +00:00
/// Reads skin data if extension is valid.
2019-01-16 01:34:59 +00:00
/// </summary>
/// <param name="path">the path to load from.</param>
/// <returns>A <see cref="SkinData"/> that holds all the information and some metadata for the loaded skin.</returns>
2019-01-21 01:15:19 +00:00
public SkinData ReadSkinData ( string path )
2019-01-16 01:34:59 +00:00
{
if ( path . ToLower ( ) . EndsWith ( EXTENSION ) )
{
return JsonConvert . DeserializeObject < SkinData > ( 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 ) + "\"." ) ;
}
/// <summary>
2019-01-27 05:44:22 +00:00
/// loads a skin asynchronously to the <see cref="loadedSkin"/>.
2019-01-16 01:34:59 +00:00
/// </summary>
2019-01-22 04:18:22 +00:00
/// <param name="graphicsDevice">Graphics device to use for texture creation. Uses graphics device from <see cref="Configuration"/>by default.</param>
2019-01-21 00:05:40 +00:00
/// <param name="skinData">The data to generate from.</param>
2019-01-20 07:08:38 +00:00
/// <param name="path">The path pointing to the file with the extension "<see cref="EXTENSION"/>".</param>
2019-01-22 01:56:51 +00:00
public void LoadSkin ( SkinData skinData , string path , GraphicsDevice graphicsDevice = null )
2019-01-21 00:05:40 +00:00
{
2019-01-22 04:18:22 +00:00
if ( graphicsDevice = = null ) graphicsDevice = Configuration . graphicsDeviceManager . GraphicsDevice ;
2019-01-21 01:15:19 +00:00
action = Action . LOAD ;
2019-01-21 00:05:40 +00:00
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 ;
2019-01-21 01:15:19 +00:00
case Action . LOAD :
2019-01-21 03:10:52 +00:00
loadedSkin = LoadSkinFromData ( skinDataToUse , selectedSkinPath , graphicsDevice ) ;
2019-01-21 00:05:40 +00:00
OnAsyncComplete ( action ) ;
break ;
}
}
private void SearchForSkins ( )
{
skinPaths . Clear ( ) ;
skinPaths = RecursiveSkinSearch ( skinsDirectory ) ;
}
2019-01-21 01:15:19 +00:00
private Skin LoadSkinFromData ( SkinData skinData , string path , GraphicsDevice graphicsDevice )
2019-01-16 01:34:59 +00:00
{
TextureAtlasDataReader textureAtlasDataReader = new TextureAtlasDataReader ( ) ;
2019-01-20 07:08:38 +00:00
FileInfo [ ] skinFiles = Directory . GetParent ( path ) . GetFiles ( ) ;
Dictionary < string , string > fileNameWithPath = new Dictionary < string , string > ( ) ;
for ( int i = 0 ; i < skinFiles . Length ; i + + )
{
fileNameWithPath . Add ( skinFiles [ i ] . Name , skinFiles [ i ] . FullName ) ;
}
TextureAtlasData atlasData = JsonConvert . DeserializeObject < TextureAtlasData > ( fileNameWithPath [ skinData . nameOfTextureAtlas ] ) ;
2019-01-16 01:34:59 +00:00
Texture2D atlasTexture ;
2019-01-20 07:08:38 +00:00
using ( FileStream stream = new FileStream ( fileNameWithPath [ atlasData . textureName ] , FileMode . Open ) )
2019-01-16 01:34:59 +00:00
{
atlasTexture = Texture2D . FromStream ( graphicsDevice , stream ) ;
}
TextureAtlas . Region [ ] regions = textureAtlasDataReader . GenerateAtlasRegionsFromData ( atlasData , atlasTexture ) ;
2019-01-20 07:08:38 +00:00
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 ) ;
}
2019-01-21 00:05:40 +00:00
}
else
2019-01-20 07:08:38 +00:00
{
cursorTexture = textureAtlas [ skinData . cursorTextureName ] . AsTexture2D ( graphicsDevice ) ;
}
Skin skin = new Skin ( new TextureAtlas ( atlasTexture , regions ) , cursorTexture ) ;
2019-01-16 01:34:59 +00:00
for ( int i = 0 ; i < skinData . colors . Length ; i + + )
{
SkinData . ColorData colorData = skinData . colors [ i ] ;
2019-01-20 07:08:38 +00:00
skin . AddColor ( colorData . name , new Color ( colorData . r , colorData . g , colorData . b , colorData . a ) ) ;
2019-01-16 01:34:59 +00:00
}
for ( int i = 0 ; i < skinData . definitions . Length ; i + + )
{
SkinData . DefinitionData definitionData = skinData . definitions [ i ] ;
2019-01-29 01:43:41 +00:00
skin . AddDefinition ( definitionData . skin , definitionData . name ) ;
2019-01-16 01:34:59 +00:00
}
return skin ;
}
private List < string > RecursiveSkinSearch ( string path )
{
string [ ] files = Directory . GetFiles ( path ) ;
string [ ] folders = Directory . GetDirectories ( path ) ;
List < string > skins = new List < string > ( ) ;
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 ;
}
2019-01-20 07:08:38 +00:00
2019-01-21 00:05:40 +00:00
private void OnAsyncComplete ( Action newState )
{
AsyncCompleteEvent ? . Invoke ( newState ) ;
}
2019-01-16 01:34:59 +00:00
}
}