diff --git a/include/eepp/scene/nodeattribute.hpp b/include/eepp/scene/nodeattribute.hpp index b9c378cfd..04af077a5 100644 --- a/include/eepp/scene/nodeattribute.hpp +++ b/include/eepp/scene/nodeattribute.hpp @@ -53,6 +53,8 @@ class NodeAttribute { std::vector names; }; + NodeAttribute(); + NodeAttribute( std::string name, std::string value ); std::string getName() const; diff --git a/include/eepp/ui/css/stylesheetparser.hpp b/include/eepp/ui/css/stylesheetparser.hpp new file mode 100644 index 000000000..2ff73c67a --- /dev/null +++ b/include/eepp/ui/css/stylesheetparser.hpp @@ -0,0 +1,302 @@ +#ifndef EE_UI_CSS_STYLESHEETPARSER +#define EE_UI_CSS_STYLESHEETPARSER + +#include +#include +#include +#include +#include +#include +using namespace EE; +using namespace EE::System; + +namespace EE { namespace UI { namespace CSS { + +// This is not how it should be. +class StyleSheetSelector { + public: + enum SelectorType { + TagName = 1 << 0, + Id = 1 << 1, + Class = 1 << 2, + PseudoClass = 1 << 3 + }; + + enum SpecificityVal { + SpecificityId = 1000000, + SpecificityClass = 100000, + SpecificityTag = 10000, + SpecificityPseudoClass = 0 + }; + + explicit StyleSheetSelector( const std::string& selectorName ) : + name( selectorName ), + specificity(0) + { + auto parts = String::split( name, ' ' ); + + for ( auto it = parts.begin(); it != parts.end(); ++it ) { + parseSelector( *it ); + } + } + + Uint32 getRequiredFlags() { + Uint32 flags = 0; + + if ( hasTagName() ) + flags |= TagName; + + if ( hasId() ) + flags |= Id; + + if ( hasClasses() ) + flags |= Class; + + if ( hasPseudoClass() ) + flags |= PseudoClass; + + return flags; + } + + const std::string& getName() const { return name; }; + + const std::string& getTagName() const { return tagName; } + + const std::string getId() const { return id; } + + const std::vector getClasses() const { return classes; } + + const std::string& getPseudoClass() const; + + bool hasTagName() { return !tagName.empty(); } + + bool hasId() { return !id.empty(); } + + bool hasClasses() { return !classes.empty(); } + + bool hasClass( std::string cls ) { return std::find(classes.begin(), classes.end(), cls) != classes.end(); } + + bool hasPseudoClass() { return !pseudoClass.empty(); } + + Uint32 getSpecificity() { return specificity; } + protected: + std::string name; + std::string tagName; + std::string id; + std::vector classes; + std::string pseudoClass; + Uint32 specificity; + + void parseSelector( const std::string& selector ) { + auto selPseudo = String::split( selector, ':' ); + + if ( !selPseudo.empty() ) { + std::string rselector( selPseudo[0] ); + + if ( !rselector.empty() ) { + if ( rselector[0] == '.' ) { + classes.push_back( rselector.substr(1) ); + specificity += SpecificityClass; + } else if ( selector[0] == '#' ) { + id = rselector.substr(1); + specificity += SpecificityId; + } else { + tagName = rselector; + specificity += SpecificityTag; + } + } + + if ( selPseudo.size() > 1 ) { + pseudoClass = selPseudo[1]; + specificity += SpecificityPseudoClass; + } + } + } +}; + +class StyleSheetProperty { + public: + StyleSheetProperty() + {} + + explicit StyleSheetProperty( const std::string& name, const std::string& value ) : + name( String::trim( name ) ), + value( String::trim( value ) ) + {} + + std::string name; + std::string value; +}; + +typedef std::map StyleSheetProperties; + +class StyleSheetSelectorParser { + public: + StyleSheetSelectorParser(){} + + explicit StyleSheetSelectorParser( std::string name ) + { + std::vector sels = String::split( name, ',' ); + + for ( std::vector::iterator it = sels.begin(); it != sels.end(); ++it ) + { + std::string cur = String::trim( *it ); + + selectors.push_back( StyleSheetSelector( cur ) ); + } + } + + std::vector selectors; +}; + +class StyleSheetPropertiesParser { + public: + typedef std::map PropertiesDictionary; + + StyleSheetPropertiesParser(){} + + explicit StyleSheetPropertiesParser( const std::string& propsstr ) { + std::vector props = String::split( propsstr, ';' ); + + if ( !props.empty() ) { + parse( propsstr ); + } + }; + + void print(); + + PropertiesDictionary properties; + protected: + enum ReadState { + ReadingPropertyName, + ReadingPropertyValue, + ReadingValueUrl, + ReadingComment + }; + + ReadState prevRs; + + void parse( std::string propsstr ); + + int readPropertyName( ReadState& rs, std::size_t pos, std::string& buffer, const std::string& str ); + + int readPropertyValue( ReadState& rs, std::size_t pos, std::string& buffer, const std::string& str ); + + int readComment( ReadState& rs, std::size_t pos, std::string& buffer, const std::string& str ); + + int readValueUrl( ReadState& rs, std::size_t pos, std::string& buffer, const std::string& str ); +}; + +class StyleSheetNode { + public: + explicit StyleSheetNode( const std::string& selector, const StyleSheetProperties& properties ) : + selector( selector ), + properties( properties ) + {} + + void print() { + std::cout << selector.getName() << " {" << std::endl; + + for ( StyleSheetProperties::iterator it = properties.begin(); it != properties.end(); ++it ) { + StyleSheetProperty& prop = it->second; + + std::cout << "\t" << prop.name << ": " << prop.value << ";" << std::endl; + } + + std::cout << "}" << std::endl; + } + + StyleSheetSelector selector; + StyleSheetProperties properties; +}; + +class StyleSheet { + public: + StyleSheet() {} + + void addNode( StyleSheetNode node ) { + nodes.push_back( node ); + } + + StyleSheetProperties find( const std::string& tagName = "", const std::string& id = "", const std::vector& classes = {}, const std::string& pseudoClass = "" ) { + StyleSheetProperties propertiesSelected; + Uint32 lastSpecificity = -1; + + for ( auto it = nodes.begin(); it != nodes.end(); ++it ) { + StyleSheetNode& node = *it; + StyleSheetSelector& selector = node.selector; + + Uint32 flags = 0; + + if ( selector.hasTagName() && !tagName.empty() && selector.getTagName() == tagName ) { + flags |= StyleSheetSelector::TagName; + } + + if ( selector.hasId() && !id.empty() && selector.getId() == id ) { + flags |= StyleSheetSelector::Id; + } + + if ( selector.hasClasses() && !classes.empty() ) { + bool hasClasses = true; + for ( auto cit = classes.begin(); cit != classes.end(); ++cit ) { + if ( !selector.hasClass( *cit ) ) { + hasClasses = false; + break; + } + } + + if ( hasClasses ) { + flags |= StyleSheetSelector::Class; + } + } + + if ( selector.hasPseudoClass() && !pseudoClass.empty() && selector.getPseudoClass() == pseudoClass ) { + flags |= StyleSheetSelector::PseudoClass; + } + + if ( flags == selector.getRequiredFlags() && selector.getSpecificity() > lastSpecificity ) { + propertiesSelected = node.properties; + lastSpecificity = selector.getSpecificity(); + } + } + + return propertiesSelected; + } + + std::vector nodes; +}; + +class StyleSheetParser { + public: + StyleSheetParser(); + + bool loadFromStream( IOStream& stream ); + + bool loadFromFile( const std::string& file ); + + void print(); + protected: + enum ReadState { + ReadingStyle, + ReadingProperty, + ReadingComment + }; + + std::string mCSS; + + StyleSheet mStyleSheet; + + std::vector mComments; + + bool parse(); + + int readStyle( ReadState& rs, std::size_t pos, std::string& buffer ); + + int readComment( ReadState& rs, std::size_t pos, std::string& buffer ); + + int readProperty( ReadState& rs, std::size_t pos, std::string& buffer ); +}; + +}}} + +#endif diff --git a/include/eepp/ui/uistyle.hpp b/include/eepp/ui/uistyle.hpp index 736d9b508..f5f23c2d6 100644 --- a/include/eepp/ui/uistyle.hpp +++ b/include/eepp/ui/uistyle.hpp @@ -16,12 +16,15 @@ class EE_API UIStyle : public UIState { virtual ~UIStyle(); - bool stateExists( const Uint32& State ); + bool stateExists( const Uint32& state ); + + void addAttribute( int state, NodeAttribute attribute ); protected: - std::map > states;; + typedef std::map AttributesMap; + + std::map mStates; }; }} - #endif diff --git a/premake4.lua b/premake4.lua index a93f76d37..518bf172a 100644 --- a/premake4.lua +++ b/premake4.lua @@ -729,6 +729,7 @@ function build_eepp( build_name ) "src/eepp/scene/actions/*.cpp", "src/eepp/ui/*.cpp", "src/eepp/ui/actions/*.cpp", + "src/eepp/ui/css/*.cpp", "src/eepp/ui/tools/*.cpp", "src/eepp/physics/*.cpp", "src/eepp/physics/constraints/*.cpp", diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 707abacc1..7835f89a8 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -293,6 +293,7 @@ ../../include/eepp/thirdparty/PlusCallback/callback.hpp ../../include/eepp/ui/base.hpp ../../include/eepp/ui.hpp +../../include/eepp/ui/css/stylesheetparser.hpp ../../include/eepp/ui/marginmove/scale.hpp ../../include/eepp/ui/tools/textureatlaseditor.hpp ../../include/eepp/ui/uicheckbox.hpp @@ -692,6 +693,7 @@ ../../src/eepp/system/translator.cpp ../../src/eepp/system/virtualfilesystem.cpp ../../src/eepp/system/zip.cpp +../../src/eepp/ui/css/stylesheetparser.cpp ../../src/eepp/ui/tools/textureatlaseditor.cpp ../../src/eepp/ui/tools/textureatlasnew.cpp ../../src/eepp/ui/tools/textureatlasnew.hpp diff --git a/src/eepp/scene/nodeattribute.cpp b/src/eepp/scene/nodeattribute.cpp index dea0dcae1..89eb1a75a 100644 --- a/src/eepp/scene/nodeattribute.cpp +++ b/src/eepp/scene/nodeattribute.cpp @@ -28,6 +28,9 @@ const std::vector& NodeAttribute::Info::getNames() const { return names; } +NodeAttribute::NodeAttribute() +{} + NodeAttribute::NodeAttribute( std::string name, std::string value ) : mName( String::toLower( name ) ), mValue( value ) diff --git a/src/eepp/ui/css/stylesheetparser.cpp b/src/eepp/ui/css/stylesheetparser.cpp new file mode 100644 index 000000000..fc9a10960 --- /dev/null +++ b/src/eepp/ui/css/stylesheetparser.cpp @@ -0,0 +1,289 @@ +#include +#include + +namespace EE { namespace UI { namespace CSS { + +void StyleSheetPropertiesParser::parse( std::string propsstr ) { + ReadState rs = ReadingPropertyName; + prevRs = rs; + std::size_t pos = 0; + std::string buffer; + + while ( pos < propsstr.size() ) { + switch (rs) { + case ReadingPropertyName: + { + pos = readPropertyName(rs, pos, buffer, propsstr); + break; + } + case ReadingPropertyValue: + { + pos = readPropertyValue(rs, pos, buffer, propsstr); + break; + } + case ReadingComment: + { + pos = readComment(rs, pos, buffer, propsstr); + } + default: + break; + } + } +} + +int StyleSheetPropertiesParser::readPropertyName(StyleSheetPropertiesParser::ReadState & rs, std::size_t pos, std::string & buffer, const std::string& str) { + prevRs = rs; + buffer.clear(); + + while ( pos < str.size() ) { + if ( str[pos] == '/' && str.size() > pos + 1 && str[pos+1] == '*' ) { + rs = ReadingComment; + return pos; + } + + if ( str[pos] == ':' ) { + rs = ReadingPropertyValue; + return pos + 1; + } + + if ( str[pos] != '\n' && str[pos] != '\t' ) + buffer += str[pos]; + + pos++; + } + + return pos; +} + +int StyleSheetPropertiesParser::readPropertyValue(StyleSheetPropertiesParser::ReadState & rs, std::size_t pos, std::string & buffer, const std::string& str) { + std::string propName( buffer ); + + buffer.clear(); + + prevRs = rs; + + while ( pos < str.size() ) { + if ( str[pos] == '/' && str.size() > pos + 1 && str[pos+1] == '*' ) { + rs = ReadingComment; + return pos; + } + + if ( buffer.size() == 4 && buffer.substr(0,4) == "url(" ) { + rs = ReadingValueUrl; + pos = readValueUrl( rs, pos, buffer, str ); + } + + if ( str[pos] == ';' ) { + rs = ReadingPropertyName; + + properties[ propName ] = StyleSheetProperty( propName, buffer ); + + return pos + 1; + } + + if ( str[pos] != '\n' && str[pos] != '\t' ) + buffer += str[pos]; + + pos++; + + if ( pos == str.size() ) { + rs = ReadingPropertyName; + + properties[ propName ] = StyleSheetProperty( propName, buffer ); + + return pos + 1; + } + } + + return pos; +} + +int StyleSheetPropertiesParser::readComment(StyleSheetPropertiesParser::ReadState & rs, std::size_t pos, std::string & buffer, const std::string & str) { + buffer.clear(); + + while ( pos < str.size() ) { + if ( str[pos] == '*' && str.size() > pos + 1 && str[pos+1] == '/' ) { + rs = prevRs; + return pos + 2; + } + + buffer += str[pos]; + + pos++; + } + + return pos; +} + +int StyleSheetPropertiesParser::readValueUrl(StyleSheetPropertiesParser::ReadState & rs, std::size_t pos, std::string & buffer, const std::string& str) { + bool quoted = false; + + while ( pos < str.size() ) { + if ( !quoted && str[pos] == '"' ) { + buffer += str[pos]; + + quoted = true; + } else if ( !quoted ) { + if ( str[pos] != '\n' && str[pos] != '\t' ) + buffer += str[pos]; + + if ( str[pos] == ')' ) { + rs = ReadingPropertyValue; + return pos + 1; + } + } + else { + buffer += str[pos]; + + if ( quoted && str[pos] == '"' ) { + quoted = false; + } + } + + pos++; + } + + return pos; +} + +StyleSheetParser::StyleSheetParser() { +} + +bool StyleSheetParser::loadFromFile( const std::string& file ) { + IOStreamFile stream( file ); + return loadFromStream( stream ); +} + +void StyleSheetParser::print() { + for ( auto it = mStyleSheet.nodes.begin(); it != mStyleSheet.nodes.end(); ++it ) { + StyleSheetNode& style = *it; + + style.print(); + } + + std::cout << "Comments: " << std::endl; + + for ( std::size_t i = 0; i < mComments.size(); i++ ) { + std::cout << mComments[i] << std::endl; + } +} + +bool StyleSheetParser::parse() { + ReadState rs = ReadingStyle; + std::size_t pos = 0; + std::string buffer; + + while ( pos < mCSS.size() ) { + switch (rs) { + case ReadingStyle: + { + pos = readStyle(rs, pos, buffer); + break; + } + case ReadingComment: + { + pos = readComment(rs, pos, buffer); + break; + } + case ReadingProperty: + { + pos = readProperty(rs, pos, buffer); + break; + } + default: + break; + } + } + + return true; +} + +int StyleSheetParser::readStyle(ReadState& rs, std::size_t pos, std::string& buffer) { + buffer.clear(); + + while ( pos < mCSS.size() ) { + if ( mCSS[pos] == '/' && mCSS.size() > pos + 1 && mCSS[pos+1] == '*' ) { + rs = ReadingComment; + return pos; + } + + if ( mCSS[pos] == '{' ) { + rs = ReadingProperty; + return pos + 1; + } + + if ( mCSS[pos] != '\n' && mCSS[pos] != '\t' ) + buffer += mCSS[pos]; + + pos++; + } + + return pos; +} + +int StyleSheetParser::readComment(ReadState& rs, std::size_t pos, std::string& buffer) { + buffer.clear(); + + while ( pos < mCSS.size() ) { + if ( mCSS[pos] == '*' && mCSS.size() > pos + 1 && mCSS[pos+1] == '/' ) { + rs = ReadingStyle; + + buffer += mCSS[pos]; + buffer += mCSS[pos+1]; + + mComments.push_back( buffer ); + + return pos + 2; + } + + buffer += mCSS[pos]; + + pos++; + } + + return pos; +} + +int StyleSheetParser::readProperty( ReadState& rs, std::size_t pos, std::string& buffer ) { + std::string selectorName( buffer ); + + buffer.clear(); + + while ( pos < mCSS.size() ) { + if ( mCSS[pos] == '{' ) { + pos++; + continue; + } else if ( mCSS[pos] == '}' ) { + selectorName = String::trim( selectorName ); + + StyleSheetSelectorParser selectorParse( selectorName ); + StyleSheetPropertiesParser propertiesParse( buffer ); + + if ( !selectorParse.selectors.empty() ) { + for ( auto it = selectorParse.selectors.begin(); it != selectorParse.selectors.end(); ++it ) { + StyleSheetNode node( selectorName, propertiesParse.properties ); + + mStyleSheet.addNode( node ); + } + } + + rs = ReadingStyle; + return pos + 1; + } + + if ( mCSS[pos] != '\n' && mCSS[pos] != '\t' ) + buffer += mCSS[pos]; + + pos++; + } + + return pos; +} + +bool StyleSheetParser::loadFromStream( IOStream& stream ) { + mCSS.resize( stream.getSize(), '\0' ); + stream.read( &mCSS[0], stream.getSize() ); + return parse(); +} + +}}} diff --git a/src/eepp/ui/uistyle.cpp b/src/eepp/ui/uistyle.cpp index b46b97a1c..6fb5d0faa 100644 --- a/src/eepp/ui/uistyle.cpp +++ b/src/eepp/ui/uistyle.cpp @@ -14,8 +14,12 @@ UIStyle::~UIStyle() { } -bool UIStyle::stateExists( const EE::Uint32 & State ) { +bool UIStyle::stateExists( const EE::Uint32 & ) { return false; } +void UIStyle::addAttribute(int state, NodeAttribute attribute) { + mStates[ state ][ attribute.getName() ] = attribute; +} + }}