2013-11-12 23:28:15 +00:00
# include "ThemeData.h"
2014-01-03 14:26:39 +00:00
# include "components/ImageComponent.h"
# include "components/TextComponent.h"
2018-01-09 22:55:09 +00:00
# include "utils/FileSystemUtil.h"
2019-07-06 14:50:50 +00:00
# include "utils/StringUtil.h"
2017-11-01 22:21:10 +00:00
# include "Log.h"
# include "platform.h"
# include "Settings.h"
2017-11-10 19:16:42 +00:00
# include <pugixml/src/pugixml.hpp>
2018-01-29 22:50:10 +00:00
# include <algorithm>
2014-01-26 22:20:21 +00:00
2018-03-22 07:03:12 +00:00
std : : vector < std : : string > ThemeData : : sSupportedViews { { " system " } , { " basic " } , { " detailed " } , { " grid " } , { " video " } } ;
2019-07-22 03:13:48 +00:00
std : : vector < std : : string > ThemeData : : sSupportedFeatures { { " video " } , { " carousel " } , { " z-index " } , { " visible " } } ;
2014-01-26 22:20:21 +00:00
2017-11-03 00:33:08 +00:00
std : : map < std : : string , std : : map < std : : string , ThemeData : : ElementPropertyType > > ThemeData : : sElementMap {
{ " image " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
{ " maxSize " , NORMALIZED_PAIR } ,
{ " origin " , NORMALIZED_PAIR } ,
2019-07-06 14:50:50 +00:00
{ " rotation " , FLOAT } ,
2017-11-03 00:33:08 +00:00
{ " rotationOrigin " , NORMALIZED_PAIR } ,
{ " path " , PATH } ,
{ " default " , PATH } ,
{ " tile " , BOOLEAN } ,
{ " color " , COLOR } ,
2019-07-04 01:10:17 +00:00
{ " colorEnd " , COLOR } ,
{ " gradientType " , STRING } ,
2019-07-22 03:13:48 +00:00
{ " visible " , BOOLEAN } ,
2017-11-03 00:33:08 +00:00
{ " zIndex " , FLOAT } } } ,
2018-03-31 13:59:14 +00:00
{ " imagegrid " , {
{ " pos " , NORMALIZED_PAIR } ,
2018-04-07 19:23:10 +00:00
{ " size " , NORMALIZED_PAIR } ,
2018-04-08 22:58:30 +00:00
{ " margin " , NORMALIZED_PAIR } ,
2019-07-06 14:50:50 +00:00
{ " padding " , NORMALIZED_RECT } ,
{ " autoLayout " , NORMALIZED_PAIR } ,
{ " autoLayoutSelectedZoom " , FLOAT } ,
2018-04-15 12:29:02 +00:00
{ " gameImage " , PATH } ,
{ " folderImage " , PATH } ,
2019-07-06 14:50:50 +00:00
{ " imageSource " , STRING } ,
{ " scrollDirection " , STRING } ,
{ " centerSelection " , BOOLEAN } ,
{ " scrollLoop " , BOOLEAN } ,
2019-10-09 22:51:33 +00:00
{ " animate " , BOOLEAN } ,
2019-07-06 14:50:50 +00:00
{ " zIndex " , FLOAT } } } ,
2018-04-07 19:23:10 +00:00
{ " gridtile " , {
{ " size " , NORMALIZED_PAIR } ,
{ " padding " , NORMALIZED_PAIR } ,
{ " imageColor " , COLOR } ,
2018-05-10 20:26:29 +00:00
{ " backgroundImage " , PATH } ,
{ " backgroundCornerSize " , NORMALIZED_PAIR } ,
{ " backgroundColor " , COLOR } ,
{ " backgroundCenterColor " , COLOR } ,
{ " backgroundEdgeColor " , COLOR } } } ,
2017-11-03 00:33:08 +00:00
{ " text " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
{ " origin " , NORMALIZED_PAIR } ,
{ " rotation " , FLOAT } ,
{ " rotationOrigin " , NORMALIZED_PAIR } ,
{ " text " , STRING } ,
{ " backgroundColor " , COLOR } ,
{ " fontPath " , PATH } ,
{ " fontSize " , FLOAT } ,
{ " color " , COLOR } ,
{ " alignment " , STRING } ,
{ " forceUppercase " , BOOLEAN } ,
{ " lineSpacing " , FLOAT } ,
{ " value " , STRING } ,
2019-07-22 03:13:48 +00:00
{ " visible " , BOOLEAN } ,
2017-11-03 00:33:08 +00:00
{ " zIndex " , FLOAT } } } ,
{ " textlist " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
{ " origin " , NORMALIZED_PAIR } ,
{ " selectorHeight " , FLOAT } ,
{ " selectorOffsetY " , FLOAT } ,
{ " selectorColor " , COLOR } ,
2019-07-04 01:10:17 +00:00
{ " selectorColorEnd " , COLOR } ,
{ " selectorGradientType " , STRING } ,
2017-11-03 00:33:08 +00:00
{ " selectorImagePath " , PATH } ,
{ " selectorImageTile " , BOOLEAN } ,
{ " selectedColor " , COLOR } ,
{ " primaryColor " , COLOR } ,
{ " secondaryColor " , COLOR } ,
{ " fontPath " , PATH } ,
{ " fontSize " , FLOAT } ,
{ " scrollSound " , PATH } ,
{ " alignment " , STRING } ,
{ " horizontalMargin " , FLOAT } ,
{ " forceUppercase " , BOOLEAN } ,
{ " lineSpacing " , FLOAT } ,
{ " zIndex " , FLOAT } } } ,
{ " container " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
{ " origin " , NORMALIZED_PAIR } ,
2019-07-22 03:13:48 +00:00
{ " visible " , BOOLEAN } ,
{ " zIndex " , FLOAT } } } ,
2017-11-03 00:33:08 +00:00
{ " ninepatch " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
{ " path " , PATH } ,
2019-07-22 03:13:48 +00:00
{ " visible " , BOOLEAN } ,
2017-11-03 00:33:08 +00:00
{ " zIndex " , FLOAT } } } ,
{ " datetime " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
2018-10-13 01:08:15 +00:00
{ " origin " , NORMALIZED_PAIR } ,
{ " rotation " , FLOAT } ,
{ " rotationOrigin " , NORMALIZED_PAIR } ,
{ " backgroundColor " , COLOR } ,
2017-11-03 00:33:08 +00:00
{ " fontPath " , PATH } ,
{ " fontSize " , FLOAT } ,
2018-10-13 01:08:15 +00:00
{ " color " , COLOR } ,
{ " alignment " , STRING } ,
2017-11-03 00:33:08 +00:00
{ " forceUppercase " , BOOLEAN } ,
2018-10-13 01:08:15 +00:00
{ " lineSpacing " , FLOAT } ,
{ " value " , STRING } ,
{ " format " , STRING } ,
{ " displayRelative " , BOOLEAN } ,
2019-07-22 03:13:48 +00:00
{ " visible " , BOOLEAN } ,
{ " zIndex " , FLOAT } } } ,
2017-11-03 00:33:08 +00:00
{ " rating " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
{ " origin " , NORMALIZED_PAIR } ,
{ " rotation " , FLOAT } ,
{ " rotationOrigin " , NORMALIZED_PAIR } ,
{ " color " , COLOR } ,
{ " filledPath " , PATH } ,
{ " unfilledPath " , PATH } ,
2019-07-22 03:13:48 +00:00
{ " visible " , BOOLEAN } ,
2017-11-03 00:33:08 +00:00
{ " zIndex " , FLOAT } } } ,
{ " sound " , {
{ " path " , PATH } } } ,
{ " helpsystem " , {
{ " pos " , NORMALIZED_PAIR } ,
2018-04-15 18:41:25 +00:00
{ " origin " , NORMALIZED_PAIR } ,
2017-11-03 00:33:08 +00:00
{ " textColor " , COLOR } ,
{ " iconColor " , COLOR } ,
{ " fontPath " , PATH } ,
{ " fontSize " , FLOAT } } } ,
{ " video " , {
{ " pos " , NORMALIZED_PAIR } ,
{ " size " , NORMALIZED_PAIR } ,
{ " maxSize " , NORMALIZED_PAIR } ,
{ " origin " , NORMALIZED_PAIR } ,
{ " rotation " , FLOAT } ,
{ " rotationOrigin " , NORMALIZED_PAIR } ,
{ " default " , PATH } ,
{ " delay " , FLOAT } ,
2019-07-22 03:13:48 +00:00
{ " visible " , BOOLEAN } ,
{ " zIndex " , FLOAT } ,
2017-11-03 00:33:08 +00:00
{ " showSnapshotNoVideo " , BOOLEAN } ,
{ " showSnapshotDelay " , BOOLEAN } } } ,
{ " carousel " , {
{ " type " , STRING } ,
{ " size " , NORMALIZED_PAIR } ,
{ " pos " , NORMALIZED_PAIR } ,
{ " origin " , NORMALIZED_PAIR } ,
{ " color " , COLOR } ,
2019-07-04 01:10:17 +00:00
{ " colorEnd " , COLOR } ,
{ " gradientType " , STRING } ,
2017-11-03 00:33:08 +00:00
{ " logoScale " , FLOAT } ,
{ " logoRotation " , FLOAT } ,
{ " logoRotationOrigin " , NORMALIZED_PAIR } ,
{ " logoSize " , NORMALIZED_PAIR } ,
{ " logoAlignment " , STRING } ,
{ " maxLogoCount " , FLOAT } ,
{ " zIndex " , FLOAT } } }
} ;
2013-12-30 23:23:34 +00:00
2014-06-09 18:12:21 +00:00
# define MINIMUM_THEME_FORMAT_VERSION 3
2018-04-07 19:23:10 +00:00
# define CURRENT_THEME_FORMAT_VERSION 6
2013-12-30 23:23:34 +00:00
2013-12-31 03:48:28 +00:00
// helper
unsigned int getHexColor ( const char * str )
{
ThemeException error ;
if ( ! str )
throw error < < " Empty color " ;
size_t len = strlen ( str ) ;
if ( len ! = 6 & & len ! = 8 )
throw error < < " Invalid color (bad length, \" " < < str < < " \" - must be 6 or 8) " ;
unsigned int val ;
std : : stringstream ss ;
ss < < str ;
ss > > std : : hex > > val ;
if ( len = = 6 )
val = ( val < < 8 ) | 0xFF ;
return val ;
}
2017-05-14 04:07:28 +00:00
std : : map < std : : string , std : : string > mVariables ;
2013-12-31 03:48:28 +00:00
2017-05-14 04:07:28 +00:00
std : : string resolvePlaceholders ( const char * in )
{
std : : string inStr ( in ) ;
2017-11-29 19:57:43 +00:00
if ( inStr . empty ( ) )
return inStr ;
const size_t variableBegin = inStr . find ( " ${ " ) ;
const size_t variableEnd = inStr . find ( " } " , variableBegin ) ;
if ( ( variableBegin = = std : : string : : npos ) | | ( variableEnd = = std : : string : : npos ) )
return inStr ;
std : : string prefix = inStr . substr ( 0 , variableBegin ) ;
std : : string replace = inStr . substr ( variableBegin + 2 , variableEnd - ( variableBegin + 2 ) ) ;
2018-01-08 02:43:18 +00:00
std : : string suffix = resolvePlaceholders ( inStr . substr ( variableEnd + 1 ) . c_str ( ) ) ;
2017-11-29 19:57:43 +00:00
return prefix + mVariables [ replace ] + suffix ;
2017-05-14 04:07:28 +00:00
}
2013-12-30 23:23:34 +00:00
ThemeData : : ThemeData ( )
2013-11-12 23:28:15 +00:00
{
2013-12-30 23:23:34 +00:00
mVersion = 0 ;
2013-11-12 23:28:15 +00:00
}
2017-05-14 04:07:28 +00:00
void ThemeData : : loadFile ( std : : map < std : : string , std : : string > sysDataMap , const std : : string & path )
2013-11-12 23:28:15 +00:00
{
2013-12-31 03:48:28 +00:00
mPaths . push_back ( path ) ;
2013-12-30 23:23:34 +00:00
2013-12-31 03:48:28 +00:00
ThemeException error ;
error . setFiles ( mPaths ) ;
2013-12-30 23:23:34 +00:00
2018-01-09 22:55:09 +00:00
if ( ! Utils : : FileSystem : : exists ( path ) )
2013-12-31 03:48:28 +00:00
throw error < < " File does not exist! " ;
2013-12-30 23:23:34 +00:00
mVersion = 0 ;
mViews . clear ( ) ;
2017-05-14 04:07:28 +00:00
mVariables . clear ( ) ;
2017-11-11 14:56:22 +00:00
mVariables . insert ( sysDataMap . cbegin ( ) , sysDataMap . cend ( ) ) ;
2013-12-30 23:23:34 +00:00
pugi : : xml_document doc ;
pugi : : xml_parse_result res = doc . load_file ( path . c_str ( ) ) ;
if ( ! res )
throw error < < " XML parsing error: \n " < < res . description ( ) ;
pugi : : xml_node root = doc . child ( " theme " ) ;
if ( ! root )
throw error < < " Missing <theme> tag! " ;
// parse version
2014-06-09 18:12:21 +00:00
mVersion = root . child ( " formatVersion " ) . text ( ) . as_float ( - 404 ) ;
2013-12-30 23:23:34 +00:00
if ( mVersion = = - 404 )
2014-06-09 18:12:21 +00:00
throw error < < " <formatVersion> tag missing! \n It's either out of date or you need to add <formatVersion> " < < CURRENT_THEME_FORMAT_VERSION < < " </formatVersion> inside your <theme> tag. " ;
2013-12-30 23:23:34 +00:00
2014-06-09 18:12:21 +00:00
if ( mVersion < MINIMUM_THEME_FORMAT_VERSION )
throw error < < " Theme uses format version " < < mVersion < < " . Minimum supported version is " < < MINIMUM_THEME_FORMAT_VERSION < < " . " ;
2013-12-30 23:23:34 +00:00
2017-05-14 04:07:28 +00:00
parseVariables ( root ) ;
2013-12-31 03:48:28 +00:00
parseIncludes ( root ) ;
parseViews ( root ) ;
2017-03-10 18:49:15 +00:00
parseFeatures ( root ) ;
2013-11-12 23:28:15 +00:00
}
2013-12-31 03:48:28 +00:00
void ThemeData : : parseIncludes ( const pugi : : xml_node & root )
2013-11-12 23:28:15 +00:00
{
2013-12-30 23:23:34 +00:00
ThemeException error ;
2013-12-31 03:48:28 +00:00
error . setFiles ( mPaths ) ;
2013-12-30 23:23:34 +00:00
2013-12-31 03:48:28 +00:00
for ( pugi : : xml_node node = root . child ( " include " ) ; node ; node = node . next_sibling ( " include " ) )
2013-12-30 23:23:34 +00:00
{
2018-10-24 23:19:27 +00:00
std : : string relPath = resolvePlaceholders ( node . text ( ) . as_string ( ) ) ;
2018-01-29 22:50:10 +00:00
std : : string path = Utils : : FileSystem : : resolveRelativePath ( relPath , mPaths . back ( ) , true ) ;
2013-12-31 03:48:28 +00:00
if ( ! ResourceManager : : getInstance ( ) - > fileExists ( path ) )
throw error < < " Included file \" " < < relPath < < " \" not found! (resolved to \" " < < path < < " \" ) " ;
2013-12-30 23:23:34 +00:00
2013-12-31 03:48:28 +00:00
error < < " from included file \" " < < relPath < < " \" : \n " ;
2013-12-30 23:23:34 +00:00
2013-12-31 03:48:28 +00:00
mPaths . push_back ( path ) ;
2013-12-30 23:23:34 +00:00
2013-12-31 03:48:28 +00:00
pugi : : xml_document includeDoc ;
pugi : : xml_parse_result result = includeDoc . load_file ( path . c_str ( ) ) ;
if ( ! result )
throw error < < " Error parsing file: \n " < < result . description ( ) ;
2017-10-28 20:07:31 +00:00
pugi : : xml_node theme = includeDoc . child ( " theme " ) ;
if ( ! theme )
2013-12-31 03:48:28 +00:00
throw error < < " Missing <theme> tag! " ;
2013-12-30 23:23:34 +00:00
2017-10-28 20:07:31 +00:00
parseVariables ( theme ) ;
parseIncludes ( theme ) ;
parseViews ( theme ) ;
parseFeatures ( theme ) ;
2013-12-31 03:48:28 +00:00
mPaths . pop_back ( ) ;
}
2013-11-12 23:28:15 +00:00
}
2017-03-10 18:49:15 +00:00
void ThemeData : : parseFeatures ( const pugi : : xml_node & root )
{
ThemeException error ;
error . setFiles ( mPaths ) ;
for ( pugi : : xml_node node = root . child ( " feature " ) ; node ; node = node . next_sibling ( " feature " ) )
{
if ( ! node . attribute ( " supported " ) )
throw error < < " Feature missing \" supported \" attribute! " ;
const std : : string supportedAttr = node . attribute ( " supported " ) . as_string ( ) ;
2017-11-11 14:56:22 +00:00
if ( std : : find ( sSupportedFeatures . cbegin ( ) , sSupportedFeatures . cend ( ) , supportedAttr ) ! = sSupportedFeatures . cend ( ) )
2017-03-10 18:49:15 +00:00
{
parseViews ( node ) ;
}
}
}
2017-05-14 04:07:28 +00:00
void ThemeData : : parseVariables ( const pugi : : xml_node & root )
{
ThemeException error ;
error . setFiles ( mPaths ) ;
2019-08-25 15:23:02 +00:00
2017-05-14 04:07:28 +00:00
pugi : : xml_node variables = root . child ( " variables " ) ;
if ( ! variables )
return ;
2019-08-25 15:23:02 +00:00
2017-05-14 04:07:28 +00:00
for ( pugi : : xml_node_iterator it = variables . begin ( ) ; it ! = variables . end ( ) ; + + it )
{
std : : string key = it - > name ( ) ;
std : : string val = it - > text ( ) . as_string ( ) ;
if ( ! val . empty ( ) )
mVariables . insert ( std : : pair < std : : string , std : : string > ( key , val ) ) ;
}
}
2013-12-31 03:48:28 +00:00
void ThemeData : : parseViews ( const pugi : : xml_node & root )
2013-11-12 23:28:15 +00:00
{
2013-12-30 23:23:34 +00:00
ThemeException error ;
2013-12-31 03:48:28 +00:00
error . setFiles ( mPaths ) ;
2013-11-12 23:28:15 +00:00
2013-12-31 03:48:28 +00:00
// parse views
for ( pugi : : xml_node node = root . child ( " view " ) ; node ; node = node . next_sibling ( " view " ) )
{
if ( ! node . attribute ( " name " ) )
throw error < < " View missing \" name \" attribute! " ;
2013-11-12 23:28:15 +00:00
2014-03-07 03:35:13 +00:00
const char * delim = " \t \r \n , " ;
const std : : string nameAttr = node . attribute ( " name " ) . as_string ( ) ;
size_t prevOff = nameAttr . find_first_not_of ( delim , 0 ) ;
size_t off = nameAttr . find_first_of ( delim , prevOff ) ;
std : : string viewKey ;
while ( off ! = std : : string : : npos | | prevOff ! = std : : string : : npos )
{
viewKey = nameAttr . substr ( prevOff , off - prevOff ) ;
prevOff = nameAttr . find_first_not_of ( delim , off ) ;
off = nameAttr . find_first_of ( delim , prevOff ) ;
2019-08-25 15:23:02 +00:00
2017-11-11 14:56:22 +00:00
if ( std : : find ( sSupportedViews . cbegin ( ) , sSupportedViews . cend ( ) , viewKey ) ! = sSupportedViews . cend ( ) )
2017-03-10 18:49:15 +00:00
{
ThemeView & view = mViews . insert ( std : : pair < std : : string , ThemeView > ( viewKey , ThemeView ( ) ) ) . first - > second ;
parseView ( node , view ) ;
}
2014-03-07 03:35:13 +00:00
}
2013-12-31 03:48:28 +00:00
}
2013-11-12 23:28:15 +00:00
}
2013-12-31 03:48:28 +00:00
void ThemeData : : parseView ( const pugi : : xml_node & root , ThemeView & view )
2013-11-12 23:28:15 +00:00
{
2013-12-31 03:48:28 +00:00
ThemeException error ;
error . setFiles ( mPaths ) ;
2013-11-12 23:28:15 +00:00
2013-12-31 03:48:28 +00:00
for ( pugi : : xml_node node = root . first_child ( ) ; node ; node = node . next_sibling ( ) )
2013-11-12 23:28:15 +00:00
{
2013-12-31 03:48:28 +00:00
if ( ! node . attribute ( " name " ) )
throw error < < " Element of type \" " < < node . name ( ) < < " \" missing \" name \" attribute! " ;
2013-11-12 23:28:15 +00:00
2013-12-31 03:48:28 +00:00
auto elemTypeIt = sElementMap . find ( node . name ( ) ) ;
2017-11-11 14:56:22 +00:00
if ( elemTypeIt = = sElementMap . cend ( ) )
2013-12-31 03:48:28 +00:00
throw error < < " Unknown element of type \" " < < node . name ( ) < < " \" ! " ;
2014-03-07 03:35:13 +00:00
const char * delim = " \t \r \n , " ;
2014-01-25 01:25:15 +00:00
const std : : string nameAttr = node . attribute ( " name " ) . as_string ( ) ;
size_t prevOff = nameAttr . find_first_not_of ( delim , 0 ) ;
size_t off = nameAttr . find_first_of ( delim , prevOff ) ;
while ( off ! = std : : string : : npos | | prevOff ! = std : : string : : npos )
{
std : : string elemKey = nameAttr . substr ( prevOff , off - prevOff ) ;
prevOff = nameAttr . find_first_not_of ( delim , off ) ;
off = nameAttr . find_first_of ( delim , prevOff ) ;
2019-08-25 15:23:02 +00:00
parseElement ( node , elemTypeIt - > second ,
2014-01-26 22:20:21 +00:00
view . elements . insert ( std : : pair < std : : string , ThemeElement > ( elemKey , ThemeElement ( ) ) ) . first - > second ) ;
2014-01-25 01:25:15 +00:00
2017-11-11 14:56:22 +00:00
if ( std : : find ( view . orderedKeys . cbegin ( ) , view . orderedKeys . cend ( ) , elemKey ) = = view . orderedKeys . cend ( ) )
2014-01-25 01:25:15 +00:00
view . orderedKeys . push_back ( elemKey ) ;
}
2013-12-31 03:48:28 +00:00
}
2013-11-12 23:28:15 +00:00
}
2013-12-31 03:48:28 +00:00
void ThemeData : : parseElement ( const pugi : : xml_node & root , const std : : map < std : : string , ElementPropertyType > & typeMap , ThemeElement & element )
2013-12-30 23:23:34 +00:00
{
ThemeException error ;
2013-12-31 03:48:28 +00:00
error . setFiles ( mPaths ) ;
2013-11-12 23:28:15 +00:00
2014-01-01 05:39:22 +00:00
element . type = root . name ( ) ;
2013-12-30 23:23:34 +00:00
element . extra = root . attribute ( " extra " ) . as_bool ( false ) ;
2019-08-25 15:23:02 +00:00
2013-12-30 23:23:34 +00:00
for ( pugi : : xml_node node = root . first_child ( ) ; node ; node = node . next_sibling ( ) )
2013-11-12 23:28:15 +00:00
{
2013-12-30 23:23:34 +00:00
auto typeIt = typeMap . find ( node . name ( ) ) ;
2017-11-11 14:56:22 +00:00
if ( typeIt = = typeMap . cend ( ) )
2013-12-30 23:23:34 +00:00
throw error < < " Unknown property type \" " < < node . name ( ) < < " \" (for element of type " < < root . name ( ) < < " ). " ;
2013-11-12 23:28:15 +00:00
2017-05-14 04:07:28 +00:00
std : : string str = resolvePlaceholders ( node . text ( ) . as_string ( ) ) ;
2013-12-30 23:23:34 +00:00
switch ( typeIt - > second )
2013-11-12 23:28:15 +00:00
{
2019-07-06 14:50:50 +00:00
case NORMALIZED_RECT :
{
Vector4f val ;
auto splits = Utils : : String : : delimitedStringToVector ( str , " " ) ;
if ( splits . size ( ) = = 2 )
{
val = Vector4f ( ( float ) atof ( splits . at ( 0 ) . c_str ( ) ) , ( float ) atof ( splits . at ( 1 ) . c_str ( ) ) ,
( float ) atof ( splits . at ( 0 ) . c_str ( ) ) , ( float ) atof ( splits . at ( 1 ) . c_str ( ) ) ) ;
}
else if ( splits . size ( ) = = 4 )
{
val = Vector4f ( ( float ) atof ( splits . at ( 0 ) . c_str ( ) ) , ( float ) atof ( splits . at ( 1 ) . c_str ( ) ) ,
( float ) atof ( splits . at ( 2 ) . c_str ( ) ) , ( float ) atof ( splits . at ( 3 ) . c_str ( ) ) ) ;
}
element . properties [ node . name ( ) ] = val ;
break ;
}
2013-12-30 23:23:34 +00:00
case NORMALIZED_PAIR :
2013-11-12 23:28:15 +00:00
{
2013-12-30 23:23:34 +00:00
size_t divider = str . find ( ' ' ) ;
2019-08-25 15:23:02 +00:00
if ( divider = = std : : string : : npos )
2014-01-25 01:25:15 +00:00
throw error < < " invalid normalized pair (property \" " < < node . name ( ) < < " \" , value \" " < < str . c_str ( ) < < " \" ) " ;
2013-11-12 23:28:15 +00:00
2013-12-30 23:23:34 +00:00
std : : string first = str . substr ( 0 , divider ) ;
std : : string second = str . substr ( divider , std : : string : : npos ) ;
2017-11-17 14:58:52 +00:00
Vector2f val ( ( float ) atof ( first . c_str ( ) ) , ( float ) atof ( second . c_str ( ) ) ) ;
2013-12-30 23:23:34 +00:00
element . properties [ node . name ( ) ] = val ;
break ;
}
case STRING :
2017-05-14 04:07:28 +00:00
element . properties [ node . name ( ) ] = str ;
2013-12-30 23:23:34 +00:00
break ;
case PATH :
2013-11-12 23:28:15 +00:00
{
2018-01-29 22:50:10 +00:00
std : : string path = Utils : : FileSystem : : resolveRelativePath ( str , mPaths . back ( ) , true ) ;
2013-12-31 03:48:28 +00:00
if ( ! ResourceManager : : getInstance ( ) - > fileExists ( path ) )
{
std : : stringstream ss ;
ss < < " Warning " < < error . msg ; // "from theme yadda yadda, included file yadda yadda
ss < < " could not find file \" " < < node . text ( ) . get ( ) < < " \" " ;
if ( node . text ( ) . get ( ) ! = path )
ss < < " (which resolved to \" " < < path < < " \" ) " ;
LOG ( LogWarning ) < < ss . str ( ) ;
}
2013-12-30 23:23:34 +00:00
element . properties [ node . name ( ) ] = path ;
break ;
}
case COLOR :
2017-05-14 04:07:28 +00:00
element . properties [ node . name ( ) ] = getHexColor ( str . c_str ( ) ) ;
2013-12-30 23:23:34 +00:00
break ;
case FLOAT :
2017-05-14 04:07:28 +00:00
{
float floatVal = static_cast < float > ( strtod ( str . c_str ( ) , 0 ) ) ;
element . properties [ node . name ( ) ] = floatVal ;
2013-12-30 23:23:34 +00:00
break ;
2017-05-14 04:07:28 +00:00
}
2013-12-30 23:23:34 +00:00
case BOOLEAN :
2017-05-14 04:07:28 +00:00
{
// only look at first char
char first = str [ 0 ] ;
// 1*, t* (true), T* (True), y* (yes), Y* (YES)
bool boolVal = ( first = = ' 1 ' | | first = = ' t ' | | first = = ' T ' | | first = = ' y ' | | first = = ' Y ' ) ;
element . properties [ node . name ( ) ] = boolVal ;
2013-12-30 23:23:34 +00:00
break ;
2017-05-14 04:07:28 +00:00
}
2013-12-30 23:23:34 +00:00
default :
2014-01-25 01:25:15 +00:00
throw error < < " Unknown ElementPropertyType for \" " < < root . attribute ( " name " ) . as_string ( ) < < " \" , property " < < node . name ( ) ;
2013-11-12 23:28:15 +00:00
}
}
}
2013-12-31 03:48:28 +00:00
2017-02-25 04:19:29 +00:00
bool ThemeData : : hasView ( const std : : string & view )
{
auto viewIt = mViews . find ( view ) ;
2017-11-11 14:56:22 +00:00
return ( viewIt ! = mViews . cend ( ) ) ;
2017-02-25 04:19:29 +00:00
}
2014-01-01 05:39:22 +00:00
const ThemeData : : ThemeElement * ThemeData : : getElement ( const std : : string & view , const std : : string & element , const std : : string & expectedType ) const
{
auto viewIt = mViews . find ( view ) ;
2017-11-11 14:56:22 +00:00
if ( viewIt = = mViews . cend ( ) )
2014-03-07 03:35:13 +00:00
return NULL ; // not found
2014-01-01 05:39:22 +00:00
auto elemIt = viewIt - > second . elements . find ( element ) ;
2017-11-11 14:56:22 +00:00
if ( elemIt = = viewIt - > second . elements . cend ( ) ) return NULL ;
2014-01-01 05:39:22 +00:00
if ( elemIt - > second . type ! = expectedType & & ! expectedType . empty ( ) )
{
2019-08-25 15:23:02 +00:00
LOG ( LogWarning ) < < " requested mismatched theme type for [ " < < view < < " . " < < element < < " ] - expected \" "
2014-01-01 05:39:22 +00:00
< < expectedType < < " \" , got \" " < < elemIt - > second . type < < " \" " ;
return NULL ;
}
return & elemIt - > second ;
}
2014-01-03 16:40:36 +00:00
const std : : shared_ptr < ThemeData > & ThemeData : : getDefault ( )
2014-01-01 05:39:22 +00:00
{
2014-01-03 16:40:36 +00:00
static std : : shared_ptr < ThemeData > theme = nullptr ;
if ( theme = = nullptr )
2014-01-01 05:39:22 +00:00
{
2014-01-03 16:40:36 +00:00
theme = std : : shared_ptr < ThemeData > ( new ThemeData ( ) ) ;
2014-01-10 20:58:03 +00:00
2018-01-27 20:04:08 +00:00
const std : : string path = Utils : : FileSystem : : getHomePath ( ) + " /.emulationstation/es_theme_default.xml " ;
2018-01-09 22:55:09 +00:00
if ( Utils : : FileSystem : : exists ( path ) )
2014-01-10 20:58:03 +00:00
{
try
{
2017-05-14 04:07:28 +00:00
std : : map < std : : string , std : : string > emptyMap ;
theme - > loadFile ( emptyMap , path ) ;
2014-01-10 20:58:03 +00:00
} catch ( ThemeException & e )
{
LOG ( LogError ) < < e . what ( ) ;
theme = std : : shared_ptr < ThemeData > ( new ThemeData ( ) ) ; //reset to empty
}
}
2014-01-01 05:39:22 +00:00
}
2014-01-03 14:26:39 +00:00
2014-01-03 16:40:36 +00:00
return theme ;
}
2014-01-03 14:26:39 +00:00
std : : vector < GuiComponent * > ThemeData : : makeExtras ( const std : : shared_ptr < ThemeData > & theme , const std : : string & view , Window * window )
{
std : : vector < GuiComponent * > comps ;
auto viewIt = theme - > mViews . find ( view ) ;
2017-11-11 14:56:22 +00:00
if ( viewIt = = theme - > mViews . cend ( ) )
2014-01-03 14:26:39 +00:00
return comps ;
2019-08-25 15:23:02 +00:00
2017-11-11 14:56:22 +00:00
for ( auto it = viewIt - > second . orderedKeys . cbegin ( ) ; it ! = viewIt - > second . orderedKeys . cend ( ) ; it + + )
2014-01-03 14:26:39 +00:00
{
2014-01-09 23:13:52 +00:00
ThemeElement & elem = viewIt - > second . elements . at ( * it ) ;
if ( elem . extra )
2014-01-03 14:26:39 +00:00
{
GuiComponent * comp = NULL ;
2014-01-09 23:13:52 +00:00
const std : : string & t = elem . type ;
2014-01-03 14:26:39 +00:00
if ( t = = " image " )
comp = new ImageComponent ( window ) ;
else if ( t = = " text " )
comp = new TextComponent ( window ) ;
2017-04-22 14:15:16 +00:00
comp - > setDefaultZIndex ( 10 ) ;
2014-01-09 23:13:52 +00:00
comp - > applyTheme ( theme , view , * it , ThemeFlags : : ALL ) ;
2014-01-03 14:26:39 +00:00
comps . push_back ( comp ) ;
}
}
return comps ;
}
2014-05-01 02:12:45 +00:00
std : : map < std : : string , ThemeSet > ThemeData : : getThemeSets ( )
{
std : : map < std : : string , ThemeSet > sets ;
static const size_t pathCount = 2 ;
2018-01-29 22:50:10 +00:00
std : : string paths [ pathCount ] =
2019-08-25 15:23:02 +00:00
{
" /etc/emulationstation/themes " ,
Utils : : FileSystem : : getHomePath ( ) + " /.emulationstation/themes "
2014-05-01 02:12:45 +00:00
} ;
for ( size_t i = 0 ; i < pathCount ; i + + )
{
2018-01-29 22:50:10 +00:00
if ( ! Utils : : FileSystem : : isDirectory ( paths [ i ] ) )
2014-05-01 02:12:45 +00:00
continue ;
2018-01-29 22:50:10 +00:00
Utils : : FileSystem : : stringList dirContent = Utils : : FileSystem : : getDirContent ( paths [ i ] ) ;
2018-01-27 20:04:08 +00:00
for ( Utils : : FileSystem : : stringList : : const_iterator it = dirContent . cbegin ( ) ; it ! = dirContent . cend ( ) ; + + it )
2014-05-01 02:12:45 +00:00
{
2018-01-27 20:04:08 +00:00
if ( Utils : : FileSystem : : isDirectory ( * it ) )
2014-05-01 02:12:45 +00:00
{
ThemeSet set = { * it } ;
sets [ set . getName ( ) ] = set ;
}
}
}
return sets ;
}
2018-01-29 22:50:10 +00:00
std : : string ThemeData : : getThemeFromCurrentSet ( const std : : string & system )
2014-05-01 02:12:45 +00:00
{
2018-01-29 22:50:10 +00:00
std : : map < std : : string , ThemeSet > themeSets = ThemeData : : getThemeSets ( ) ;
2014-05-01 02:12:45 +00:00
if ( themeSets . empty ( ) )
{
// no theme sets available
return " " ;
}
2017-11-11 14:56:22 +00:00
std : : map < std : : string , ThemeSet > : : const_iterator set = themeSets . find ( Settings : : getInstance ( ) - > getString ( " ThemeSet " ) ) ;
if ( set = = themeSets . cend ( ) )
2014-05-01 02:12:45 +00:00
{
// currently selected theme set is missing, so just pick the first available set
2017-11-11 14:56:22 +00:00
set = themeSets . cbegin ( ) ;
2014-05-01 02:12:45 +00:00
Settings : : getInstance ( ) - > setString ( " ThemeSet " , set - > first ) ;
}
return set - > second . getThemePath ( system ) ;
}