2019-12-28 21:41:06 +00:00
using OpenTK ;
using OpenTK.Graphics ;
2019-01-16 01:34:59 +00:00
using Newtonsoft.Json ;
2019-01-20 07:08:38 +00:00
using RecrownedAthenaeum.Data ;
2019-11-24 20:49:53 +00:00
using RecrownedAthenaeum.Types ;
2019-01-16 01:34:59 +00:00
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-12-28 21:41:06 +00:00
namespace RecrownedAthenaeum.Graphics.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-03-24 00:04:43 +00:00
/// The user loaded skin resulted from asynchronous <see cref="LoadSkin(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-20 07:08:38 +00:00
/// <param name="path">The path pointing to the file with the extension "<see cref="EXTENSION"/>".</param>
2019-03-24 00:04:43 +00:00
/// <param name="graphicsDevice">Graphics device to use for texture creation.</param>
public void LoadSkin ( string path , GraphicsDevice graphicsDevice )
2019-01-21 00:05:40 +00:00
{
2019-01-21 01:15:19 +00:00
action = Action . LOAD ;
2019-03-24 00:04:43 +00:00
this . graphicsDevice = graphicsDevice ? ? throw new ArgumentNullException ( "Requires graphics device to create textures." ) ;
selectedSkinPath = path ? ? throw new ArgumentNullException ( "Requires path to find textures." ) ;
skinDataToUse = ReadSkinData ( path ) ;
2019-01-21 00:05:40 +00:00
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 ( ) ;
2019-03-21 00:28:16 +00:00
Dictionary < string , string > filePath = new Dictionary < string , string > ( ) ;
2019-01-20 07:08:38 +00:00
for ( int i = 0 ; i < skinFiles . Length ; i + + )
{
2019-03-21 00:28:16 +00:00
filePath . Add ( skinFiles [ i ] . Name , skinFiles [ i ] . FullName ) ;
}
TextureAtlasDataReader tatlasDataReader = new TextureAtlasDataReader ( ) ;
TextureAtlasData atlasData ;
2019-03-24 00:04:43 +00:00
atlasData = JsonConvert . DeserializeObject < TextureAtlasData > ( File . ReadAllText ( filePath [ skinData . nameOfTextureAtlas ] ) ) ;
2019-01-16 01:34:59 +00:00
Texture2D atlasTexture ;
2019-03-21 00:28:16 +00:00
using ( FileStream stream = new FileStream ( filePath [ atlasData . textureName ] , FileMode . Open ) )
2019-01-16 01:34:59 +00:00
{
atlasTexture = Texture2D . FromStream ( graphicsDevice , stream ) ;
2019-03-24 00:04:43 +00:00
Vector4 [ ] data = new Vector4 [ atlasTexture . Width * atlasTexture . Height ] ;
atlasTexture . GetData ( data ) ;
for ( int i = 0 ; i < data . Length ; i + + )
{
2019-12-28 21:41:06 +00:00
Color4Ext . FromNonPremultiplied ( ref data [ i ] ) ;
2019-03-24 00:04:43 +00:00
}
atlasTexture . SetData ( data ) ;
2019-01-16 01:34:59 +00:00
}
TextureAtlas . Region [ ] regions = textureAtlasDataReader . GenerateAtlasRegionsFromData ( atlasData , atlasTexture ) ;
2019-01-20 07:08:38 +00:00
TextureAtlas textureAtlas = new TextureAtlas ( atlasTexture , regions ) ;
Texture2D cursorTexture ;
2019-03-21 00:28:16 +00:00
if ( Path . HasExtension ( skinData . cursorTextureName ) & & filePath . ContainsKey ( skinData . cursorTextureName ) )
2019-01-20 07:08:38 +00:00
{
2019-03-21 00:28:16 +00:00
using ( FileStream stream = new FileStream ( filePath [ skinData . cursorTextureName ] , FileMode . Open ) )
2019-01-20 07:08:38 +00:00
{
cursorTexture = Texture2D . FromStream ( graphicsDevice , stream ) ;
2019-03-24 00:04:43 +00:00
Vector4 [ ] data = new Vector4 [ cursorTexture . Width * cursorTexture . Height ] ;
atlasTexture . GetData ( data ) ;
for ( int i = 0 ; i < data . Length ; i + + )
{
2019-12-28 21:41:06 +00:00
Color4Ext . FromNonPremultiplied ( ref data [ i ] ) ;
2019-03-24 00:04:43 +00:00
}
cursorTexture . SetData ( data ) ;
2019-01-20 07:08:38 +00:00
}
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-12-28 21:41:06 +00:00
skin . AddColor ( colorData . name , new Color4 ( 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
}
}