mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
Reduce allocations in CSS parsing and style updates
- reuse reentrant property-resolution storage during variable substitution - skip redundant resolution-slot copies using structural source comparison - retain variable cache capacity and resolve variables in place - avoid copying canonical names for definition-backed properties - propagate resolved inherited properties by reference through widget trees - parse animation and transition declarations with allocation-light string views - share declaration tokenization between animation and transition parsers - avoid empty animation-map and action-query allocations - parse border radii without temporary split vectors - accept string views when converting UI lengths - format color hex values directly without string streams - use inline storage for small polygon point collections - preserve animation parsing semantics and fix longhand direction handling - save eeiv configuration when closing through keyboard shortcuts - extend animation, variable-resolution, property-name, and color tests
This commit is contained in:
@@ -6,8 +6,6 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
namespace EE { namespace System {
|
||||
@@ -611,14 +609,21 @@ Color Color::div( int divisor, bool divAlpha ) {
|
||||
}
|
||||
|
||||
std::string Color::toHexString( const bool& prependHashtag ) const {
|
||||
std::stringstream stream;
|
||||
static constexpr char hex[] = "0123456789abcdef";
|
||||
const std::size_t offset = prependHashtag ? 1 : 0;
|
||||
std::string result( offset + ( a == 255 ? 6 : 8 ), '0' );
|
||||
if ( prependHashtag )
|
||||
stream << "#";
|
||||
stream << std::setfill( '0' ) << std::setw( sizeof( Color ) * 2 ) << std::hex << getValue();
|
||||
std::string str = stream.str();
|
||||
if ( this->a == 255 )
|
||||
return str.substr( 0, prependHashtag ? 7 : 6 );
|
||||
return str;
|
||||
result[0] = '#';
|
||||
const auto writeByte = [&]( std::size_t position, Uint8 value ) {
|
||||
result[offset + position] = hex[value >> 4];
|
||||
result[offset + position + 1] = hex[value & 0x0F];
|
||||
};
|
||||
writeByte( 0, r );
|
||||
writeByte( 2, g );
|
||||
writeByte( 4, b );
|
||||
if ( a != 255 )
|
||||
writeByte( 6, a );
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Color::toRgbaString() const {
|
||||
|
||||
+13
-4
@@ -145,11 +145,20 @@ BorderStyle Borders::toBorderStyle( const std::string& borderStyle ) {
|
||||
}
|
||||
|
||||
Sizef Borders::radiusFromString( const UINode* node, const std::string& val ) {
|
||||
auto split = String::split( val, ' ' );
|
||||
std::string_view values = String::trim( std::string_view{ val }, " \t\n\r\f\v" );
|
||||
const auto firstEnd = values.find_first_of( " \t\n\r\f\v" );
|
||||
const std::string_view first = values.substr( 0, firstEnd );
|
||||
std::string_view second = first;
|
||||
if ( firstEnd != std::string_view::npos ) {
|
||||
values.remove_prefix( firstEnd );
|
||||
values = String::trim( values, " \t\n\r\f\v" );
|
||||
const auto secondEnd = values.find_first_of( " \t\n\r\f\v" );
|
||||
if ( !values.empty() )
|
||||
second = values.substr( 0, secondEnd );
|
||||
}
|
||||
Sizef size;
|
||||
size.x = node->lengthFromValue( split[0], CSS::PropertyRelativeTarget::LocalBlockRadiusWidth );
|
||||
size.y = node->lengthFromValue( split[split.size() > 1 ? 1 : 0],
|
||||
CSS::PropertyRelativeTarget::LocalBlockRadiusHeight );
|
||||
size.x = node->lengthFromValue( first, CSS::PropertyRelativeTarget::LocalBlockRadiusWidth );
|
||||
size.y = node->lengthFromValue( second, CSS::PropertyRelativeTarget::LocalBlockRadiusHeight );
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
#include <eepp/system/functionstring.hpp>
|
||||
#include <eepp/ui/css/animationdefinition.hpp>
|
||||
#include <eepp/ui/css/declarationparser.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/css/timingfunction.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace CSS {
|
||||
|
||||
inline bool isTimingFunction( const std::string& str ) {
|
||||
return Ease::Interpolation::None != Ease::fromName( str, Ease::Interpolation::None );
|
||||
namespace {
|
||||
|
||||
bool isTime( std::string_view value ) {
|
||||
value = String::trim( value, " \t\n\r\f\v" );
|
||||
if ( value.empty() )
|
||||
return false;
|
||||
auto lower = []( char character ) {
|
||||
return character >= 'A' && character <= 'Z' ? character + ( 'a' - 'A' ) : character;
|
||||
};
|
||||
std::size_t suffixSize = 0;
|
||||
if ( value.size() >= 2 && lower( value[value.size() - 2] ) == 'm' &&
|
||||
lower( value.back() ) == 's' )
|
||||
suffixSize = 2;
|
||||
else if ( lower( value.back() ) == 's' || lower( value.back() ) == 'm' )
|
||||
suffixSize = 1;
|
||||
if ( 0 == suffixSize || value.size() == suffixSize )
|
||||
return false;
|
||||
double number = 0;
|
||||
std::string_view numberView = value.substr( 0, value.size() - suffixSize );
|
||||
if ( !numberView.empty() && numberView.front() == '+' )
|
||||
numberView.remove_prefix( 1 );
|
||||
return String::fromString( number, numberView );
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UnorderedMap<std::string, AnimationDefinition> AnimationDefinition::parseAnimationProperties(
|
||||
const std::vector<const StyleSheetProperty*>& stylesheetProperties ) {
|
||||
AnimationsMap animations;
|
||||
std::vector<std::string> names;
|
||||
std::vector<Time> durations;
|
||||
std::vector<Time> delays;
|
||||
std::vector<Int32> iterations;
|
||||
std::vector<Ease::Interpolation> timingFunctions;
|
||||
std::vector<std::vector<double>> timingFunctionParameters;
|
||||
std::vector<AnimationDirection> directions;
|
||||
std::vector<AnimationFillMode> fillModes;
|
||||
std::vector<bool> pausedStates;
|
||||
SmallVector<std::string_view, 4> names;
|
||||
SmallVector<Time, 4> durations;
|
||||
SmallVector<Time, 4> delays;
|
||||
SmallVector<Int32, 4> iterations;
|
||||
SmallVector<TimingFunction, 4> timingFunctions;
|
||||
SmallVector<AnimationDirection, 4> directions;
|
||||
SmallVector<AnimationFillMode, 4> fillModes;
|
||||
SmallVector<Uint8, 4> pausedStates;
|
||||
|
||||
for ( auto& prop : stylesheetProperties ) {
|
||||
if ( prop->getPropertyDefinition() == NULL )
|
||||
@@ -30,52 +51,49 @@ UnorderedMap<std::string, AnimationDefinition> AnimationDefinition::parseAnimati
|
||||
|
||||
switch ( propDef->getPropertyId() ) {
|
||||
case PropertyId::Animation: {
|
||||
bool durationSet = false;
|
||||
|
||||
for ( size_t i = 0; i < prop->getPropertyIndexCount(); i++ ) {
|
||||
const StyleSheetProperty& iProp = prop->getPropertyIndex( i );
|
||||
|
||||
auto parts = String::split( iProp.getValue(), ' ' );
|
||||
auto parts = DeclarationParser::splitWhitespaceTokens<8>( iProp.getValue() );
|
||||
|
||||
if ( parts.size() >= 2 ) {
|
||||
AnimationDefinition animationDef;
|
||||
bool durationSet = false;
|
||||
|
||||
for ( auto& part : parts ) {
|
||||
std::string val( String::trim( String::toLower( part ) ) );
|
||||
for ( std::string_view part : parts ) {
|
||||
const auto hash = DeclarationParser::lowerHash( part );
|
||||
|
||||
if ( isDirectionString( val ) ) {
|
||||
animationDef.setDirection( directionFromString( val ) );
|
||||
} else if ( isAnimationFillModeString( val ) ) {
|
||||
animationDef.setFillMode( fillModeFromString( val ) );
|
||||
} else if ( "infinite" == val ) {
|
||||
if ( isDirectionStringView( part ) ) {
|
||||
animationDef.setDirection( directionFromStringView( part ) );
|
||||
} else if ( isAnimationFillModeStringView( part ) ) {
|
||||
animationDef.setFillMode( fillModeFromStringView( part ) );
|
||||
} else if ( hash == String::hash( "infinite" ) ) {
|
||||
animationDef.setIterations( -1 );
|
||||
} else if ( "paused" == val ) {
|
||||
} else if ( hash == String::hash( "paused" ) ) {
|
||||
animationDef.setPaused( true );
|
||||
} else if ( "running" == val ) {
|
||||
} else if ( hash == String::hash( "running" ) ) {
|
||||
animationDef.setPaused( false );
|
||||
} else if ( isTimingFunction( val ) ) {
|
||||
TimingFunction tf( TimingFunction::parse( val ) );
|
||||
} else if ( TimingFunction tf = TimingFunction::parse( part );
|
||||
Ease::Interpolation::None != tf.interpolation ) {
|
||||
animationDef.setTimingFunction( tf.interpolation );
|
||||
animationDef.setTimingFunctionParameters( tf.parameters );
|
||||
} else if ( Time::isValid( val ) ) {
|
||||
} else if ( isTime( part ) ) {
|
||||
if ( durationSet ) {
|
||||
animationDef.setDelay( Time::fromString( val ) );
|
||||
animationDef.setDelay( DeclarationParser::parseTime( part ) );
|
||||
} else {
|
||||
animationDef.setDuration( Time::fromString( val ) );
|
||||
animationDef.setDuration(
|
||||
DeclarationParser::parseTime( part ) );
|
||||
durationSet = true;
|
||||
}
|
||||
} else if ( String::isNumber( val, true ) ) {
|
||||
int iterations = 1;
|
||||
|
||||
if ( String::fromString( iterations, val ) ) {
|
||||
animationDef.setIterations( iterations );
|
||||
}
|
||||
} else if ( int iterations = 1;
|
||||
String::fromString( iterations, part ) ) {
|
||||
animationDef.setIterations( iterations );
|
||||
} else {
|
||||
animationDef.setName( part );
|
||||
animationDef.setNameView( part );
|
||||
}
|
||||
}
|
||||
|
||||
animations[animationDef.getName()] = std::move( animationDef );
|
||||
animations.insert_or_assign( animationDef.getName(),
|
||||
std::move( animationDef ) );
|
||||
}
|
||||
}
|
||||
return animations;
|
||||
@@ -86,41 +104,44 @@ UnorderedMap<std::string, AnimationDefinition> AnimationDefinition::parseAnimati
|
||||
case PropertyId::AnimationDuration:
|
||||
case PropertyId::AnimationFillMode:
|
||||
case PropertyId::AnimationPlayState:
|
||||
case PropertyId::AnimationDirection:
|
||||
case PropertyId::AnimationIterationCount:
|
||||
case PropertyId::AnimationTimingFunction: {
|
||||
for ( size_t i = 0; i < prop->getPropertyIndexCount(); i++ ) {
|
||||
const StyleSheetProperty& iProp = prop->getPropertyIndex( i );
|
||||
std::string val( String::trim( String::toLower( iProp.getValue() ) ) );
|
||||
const std::string_view val =
|
||||
String::trim( std::string_view{ iProp.getValue() }, " \t\n\r\f\v" );
|
||||
const auto hash = DeclarationParser::lowerHash( val );
|
||||
switch ( propDef->getPropertyId() ) {
|
||||
case PropertyId::AnimationName:
|
||||
names.push_back( iProp.getValue() );
|
||||
names.emplace_back( iProp.getValue() );
|
||||
break;
|
||||
case PropertyId::AnimationDelay:
|
||||
delays.push_back( Time::fromString( val ) );
|
||||
delays.emplace_back( DeclarationParser::parseTime( val ) );
|
||||
break;
|
||||
case PropertyId::AnimationDuration:
|
||||
durations.push_back( Time::fromString( val ) );
|
||||
durations.emplace_back( DeclarationParser::parseTime( val ) );
|
||||
break;
|
||||
case PropertyId::AnimationFillMode:
|
||||
fillModes.push_back( fillModeFromString( val ) );
|
||||
fillModes.emplace_back( fillModeFromStringView( val ) );
|
||||
break;
|
||||
case PropertyId::AnimationPlayState:
|
||||
pausedStates.push_back( val == "paused" ? true : false );
|
||||
pausedStates.emplace_back( hash == String::hash( "paused" ) );
|
||||
break;
|
||||
case PropertyId::AnimationDirection:
|
||||
directions.emplace_back( directionFromStringView( val ) );
|
||||
break;
|
||||
case PropertyId::AnimationIterationCount: {
|
||||
int iVal;
|
||||
if ( val == "infinite" ) {
|
||||
iterations.push_back( -1 );
|
||||
if ( hash == String::hash( "infinite" ) ) {
|
||||
iterations.emplace_back( -1 );
|
||||
} else if ( String::fromString( iVal, val ) && iVal >= -1 ) {
|
||||
iterations.push_back( iVal );
|
||||
iterations.emplace_back( iVal );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PropertyId::AnimationTimingFunction: {
|
||||
TimingFunction tf( TimingFunction::parse( val ) );
|
||||
timingFunctions.emplace_back( tf.interpolation );
|
||||
timingFunctionParameters.emplace_back( tf.parameters.begin(),
|
||||
tf.parameters.end() );
|
||||
timingFunctions.emplace_back( TimingFunction::parse( val ) );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -136,7 +157,7 @@ UnorderedMap<std::string, AnimationDefinition> AnimationDefinition::parseAnimati
|
||||
|
||||
for ( size_t i = 0; i < names.size(); i++ ) {
|
||||
AnimationDefinition animationDef;
|
||||
animationDef.setName( names[i] );
|
||||
animationDef.setNameView( names[i] );
|
||||
|
||||
if ( !delays.empty() )
|
||||
animationDef.setDelay( delays[i % delays.size()] );
|
||||
@@ -147,35 +168,45 @@ UnorderedMap<std::string, AnimationDefinition> AnimationDefinition::parseAnimati
|
||||
if ( !fillModes.empty() )
|
||||
animationDef.setFillMode( fillModes[i % fillModes.size()] );
|
||||
|
||||
if ( !directions.empty() )
|
||||
animationDef.setDirection( directions[i % directions.size()] );
|
||||
|
||||
if ( !pausedStates.empty() )
|
||||
animationDef.setPaused( pausedStates[i % pausedStates.size()] );
|
||||
|
||||
if ( !iterations.empty() )
|
||||
animationDef.setIterations( iterations[i % iterations.size()] );
|
||||
|
||||
if ( !timingFunctions.empty() )
|
||||
animationDef.setTimingFunction( timingFunctions[i % timingFunctions.size()] );
|
||||
if ( !timingFunctions.empty() ) {
|
||||
const TimingFunction& timing = timingFunctions[i % timingFunctions.size()];
|
||||
animationDef.setTimingFunction( timing.interpolation );
|
||||
animationDef.setTimingFunctionParameters( timing.parameters );
|
||||
}
|
||||
|
||||
if ( !timingFunctionParameters.empty() )
|
||||
animationDef.setTimingFunctionParameters(
|
||||
timingFunctionParameters[i % timingFunctionParameters.size()] );
|
||||
|
||||
animations[animationDef.getName()] = std::move( animationDef );
|
||||
animations.insert_or_assign( animationDef.getName(), std::move( animationDef ) );
|
||||
}
|
||||
|
||||
return animations;
|
||||
}
|
||||
|
||||
bool AnimationDefinition::isDirectionString( const std::string str ) {
|
||||
String::HashType id = String::hash( str );
|
||||
return isDirectionStringView( str );
|
||||
}
|
||||
|
||||
bool AnimationDefinition::isDirectionStringView( std::string_view str ) {
|
||||
String::HashType id = DeclarationParser::lowerHash( str );
|
||||
return id == AlternateReverse || id == Alternate || id == Reverse || id == Normal;
|
||||
}
|
||||
|
||||
AnimationDefinition::AnimationDirection
|
||||
AnimationDefinition::directionFromString( std::string str ) {
|
||||
String::trimInPlace( str );
|
||||
String::toLowerInPlace( str );
|
||||
switch ( String::hash( str ) ) {
|
||||
return directionFromStringView( str );
|
||||
}
|
||||
|
||||
AnimationDefinition::AnimationDirection
|
||||
AnimationDefinition::directionFromStringView( std::string_view str ) {
|
||||
str = String::trim( str, " \t\n\r\f\v" );
|
||||
switch ( DeclarationParser::lowerHash( str ) ) {
|
||||
case AlternateReverse:
|
||||
return AlternateReverse;
|
||||
case Alternate:
|
||||
@@ -189,14 +220,22 @@ AnimationDefinition::directionFromString( std::string str ) {
|
||||
}
|
||||
|
||||
bool AnimationDefinition::isAnimationFillModeString( const std::string& str ) {
|
||||
Uint32 id = String::hash( str );
|
||||
return isAnimationFillModeStringView( str );
|
||||
}
|
||||
|
||||
bool AnimationDefinition::isAnimationFillModeStringView( std::string_view str ) {
|
||||
Uint32 id = DeclarationParser::lowerHash( str );
|
||||
return id == None || id == Forwards || id == Backwards || id == Both;
|
||||
}
|
||||
|
||||
AnimationDefinition::AnimationFillMode AnimationDefinition::fillModeFromString( std::string str ) {
|
||||
String::trimInPlace( str );
|
||||
String::toLowerInPlace( str );
|
||||
switch ( String::hash( str ) ) {
|
||||
return fillModeFromStringView( str );
|
||||
}
|
||||
|
||||
AnimationDefinition::AnimationFillMode
|
||||
AnimationDefinition::fillModeFromStringView( std::string_view str ) {
|
||||
str = String::trim( str, " \t\n\r\f\v" );
|
||||
switch ( DeclarationParser::lowerHash( str ) ) {
|
||||
case None:
|
||||
return None;
|
||||
case Forwards:
|
||||
@@ -240,7 +279,11 @@ const Ease::Interpolation& AnimationDefinition::getTimingFunction() const {
|
||||
}
|
||||
|
||||
void AnimationDefinition::setName( const std::string& value ) {
|
||||
mName = value;
|
||||
setNameView( value );
|
||||
}
|
||||
|
||||
void AnimationDefinition::setNameView( std::string_view value ) {
|
||||
mName.assign( value );
|
||||
mId = String::hash( mName );
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef EE_UI_CSS_DECLARATIONPARSER_HPP
|
||||
#define EE_UI_CSS_DECLARATIONPARSER_HPP
|
||||
|
||||
#include <cctype>
|
||||
#include <eepp/core/small_vector.hpp>
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/system/functionstring.hpp>
|
||||
#include <eepp/system/time.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace CSS { namespace DeclarationParser {
|
||||
|
||||
template <typename Callback> void forEachCommaItem( std::string_view value, Callback&& callback ) {
|
||||
System::FunctionString::forEachParameter( value, [&]( std::string_view item, bool ) {
|
||||
callback( item );
|
||||
return true;
|
||||
} );
|
||||
}
|
||||
|
||||
template <std::size_t InlineCapacity>
|
||||
SmallVector<std::string_view, InlineCapacity> splitWhitespaceTokens( std::string_view value ) {
|
||||
SmallVector<std::string_view, InlineCapacity> tokens;
|
||||
std::size_t start = 0;
|
||||
int parenthesisDepth = 0;
|
||||
for ( std::size_t i = 0; i <= value.size(); ++i ) {
|
||||
if ( i < value.size() ) {
|
||||
if ( value[i] == '(' )
|
||||
++parenthesisDepth;
|
||||
else if ( value[i] == ')' && parenthesisDepth > 0 )
|
||||
--parenthesisDepth;
|
||||
}
|
||||
if ( i == value.size() ||
|
||||
( std::isspace( static_cast<unsigned char>( value[i] ) ) && parenthesisDepth == 0 ) ) {
|
||||
if ( i > start )
|
||||
tokens.emplace_back( value.substr( start, i - start ) );
|
||||
while ( i + 1 < value.size() &&
|
||||
std::isspace( static_cast<unsigned char>( value[i + 1] ) ) )
|
||||
++i;
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
inline System::Time parseTime( std::string_view value ) {
|
||||
value = String::trim( value, " \t\n\r\f\v" );
|
||||
auto lower = []( char character ) {
|
||||
return character >= 'A' && character <= 'Z' ? character + ( 'a' - 'A' ) : character;
|
||||
};
|
||||
bool milliseconds = value.size() >= 2 && lower( value[value.size() - 2] ) == 'm' &&
|
||||
lower( value.back() ) == 's';
|
||||
bool seconds = !milliseconds && !value.empty() && lower( value.back() ) == 's';
|
||||
bool minutes = !milliseconds && !seconds && !value.empty() && lower( value.back() ) == 'm';
|
||||
std::size_t numberLength = value.size() - ( milliseconds ? 2 : ( seconds || minutes ? 1 : 0 ) );
|
||||
double number = 0;
|
||||
const char* numberStart = value.data();
|
||||
if ( numberLength > 0 && *numberStart == '+' ) {
|
||||
++numberStart;
|
||||
--numberLength;
|
||||
}
|
||||
if ( !String::fromString( number, std::string_view{ numberStart, numberLength } ) )
|
||||
return System::Time::Zero;
|
||||
if ( milliseconds )
|
||||
return System::Milliseconds( number );
|
||||
if ( minutes )
|
||||
return System::Minutes( number );
|
||||
return System::Seconds( number );
|
||||
}
|
||||
|
||||
inline String::HashType lowerHash( std::string_view value ) {
|
||||
return String::hashToLower( value.data(), static_cast<Int64>( value.size() ) );
|
||||
}
|
||||
|
||||
inline std::string lowerString( std::string_view value ) {
|
||||
std::string result{ value };
|
||||
String::toLowerInPlace( result );
|
||||
return result;
|
||||
}
|
||||
|
||||
}}}} // namespace EE::UI::CSS::DeclarationParser
|
||||
|
||||
#endif
|
||||
@@ -23,7 +23,6 @@ StyleSheetProperty::StyleSheetProperty() :
|
||||
StyleSheetProperty::StyleSheetProperty( const PropertyDefinition* definition,
|
||||
const std::string& value, const Uint32& index,
|
||||
bool trimValue, bool cachedProperty ) :
|
||||
mName( definition->getName() ),
|
||||
mNameHash( definition->getId() ),
|
||||
mValue( trimValue ? String::trim( value ) : value ),
|
||||
mValueHash( String::hash( mValue ) ),
|
||||
@@ -40,17 +39,11 @@ StyleSheetProperty::StyleSheetProperty( const PropertyDefinition* definition,
|
||||
checkImportant();
|
||||
createIndexed();
|
||||
checkVars();
|
||||
|
||||
if ( NULL == mShorthandDefinition && NULL == mPropertyDefinition &&
|
||||
!String::startsWith( mName, "-" ) && !isDataPropertyName( mName ) ) {
|
||||
Log::warning( "Property \"%s\" is not defined!", mName );
|
||||
}
|
||||
}
|
||||
|
||||
StyleSheetProperty::StyleSheetProperty( bool isVolatile, const PropertyDefinition* definition,
|
||||
const std::string& value, const Int64& /*specificity*/,
|
||||
const Uint32& index ) :
|
||||
mName( definition->getName() ),
|
||||
mNameHash( definition->getId() ),
|
||||
mValue( String::trim( value ) ),
|
||||
mValueHash( String::hash( mValue ) ),
|
||||
@@ -64,11 +57,6 @@ StyleSheetProperty::StyleSheetProperty( bool isVolatile, const PropertyDefinitio
|
||||
cleanValue();
|
||||
checkImportant();
|
||||
checkVars();
|
||||
|
||||
if ( NULL == mShorthandDefinition && NULL == mPropertyDefinition &&
|
||||
!String::startsWith( mName, "-" ) && !isDataPropertyName( mName ) ) {
|
||||
Log::warning( "Property \"%s\" is not defined!", mName );
|
||||
}
|
||||
}
|
||||
|
||||
StyleSheetProperty::StyleSheetProperty( const std::string& name, const std::string& value,
|
||||
@@ -140,6 +128,12 @@ ShorthandId StyleSheetProperty::getShorthandId() const {
|
||||
}
|
||||
|
||||
const std::string& StyleSheetProperty::getName() const {
|
||||
if ( !mName.empty() )
|
||||
return mName;
|
||||
if ( nullptr != mPropertyDefinition )
|
||||
return mPropertyDefinition->getName();
|
||||
if ( nullptr != mShorthandDefinition )
|
||||
return mShorthandDefinition->getName();
|
||||
return mName;
|
||||
}
|
||||
|
||||
@@ -162,7 +156,7 @@ void StyleSheetProperty::setSpecificity( const Int64& specificity ) {
|
||||
}
|
||||
|
||||
bool StyleSheetProperty::isEmpty() const {
|
||||
return mName.empty();
|
||||
return mName.empty() && nullptr == mPropertyDefinition && nullptr == mShorthandDefinition;
|
||||
}
|
||||
|
||||
void StyleSheetProperty::setName( const std::string& name ) {
|
||||
@@ -174,7 +168,6 @@ void StyleSheetProperty::setValue( const std::string& value, bool updateHash ) {
|
||||
mValue = value;
|
||||
if ( updateHash )
|
||||
mValueHash = String::hash( value );
|
||||
mVarCache.clear();
|
||||
mIsVarValue = false;
|
||||
mIsLightDarkValue = false;
|
||||
createIndexed();
|
||||
@@ -197,6 +190,26 @@ bool StyleSheetProperty::operator!=( const StyleSheetProperty& property ) const
|
||||
return mNameHash != property.mNameHash || mValueHash != property.mValueHash;
|
||||
}
|
||||
|
||||
bool StyleSheetProperty::hasSameResolutionSource( const StyleSheetProperty& property ) const {
|
||||
if ( mName != property.mName || mNameHash != property.mNameHash || mValue != property.mValue ||
|
||||
mValueHash != property.mValueHash || mSpecificity != property.mSpecificity ||
|
||||
mIndex != property.mIndex || mVolatile != property.mVolatile ||
|
||||
mImportant != property.mImportant || mIsVarValue != property.mIsVarValue ||
|
||||
mIsLightDarkValue != property.mIsLightDarkValue ||
|
||||
mCachedProperty != property.mCachedProperty ||
|
||||
mPropertyDefinition != property.mPropertyDefinition ||
|
||||
mShorthandDefinition != property.mShorthandDefinition ||
|
||||
mIndexedProperty.size() != property.mIndexedProperty.size() )
|
||||
return false;
|
||||
|
||||
for ( size_t i = 0; i < mIndexedProperty.size(); ++i ) {
|
||||
if ( !mIndexedProperty[i].hasSameResolutionSource( property.mIndexedProperty[i] ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const String::HashType& StyleSheetProperty::getNameHash() const {
|
||||
return mNameHash;
|
||||
}
|
||||
@@ -225,52 +238,46 @@ void StyleSheetProperty::createIndexed() {
|
||||
}
|
||||
|
||||
void StyleSheetProperty::checkVars() {
|
||||
auto varCache( checkVars( mValue ) );
|
||||
mVarCacheSize = 0;
|
||||
mIsVarValue = false;
|
||||
if ( !varCache.empty() ) {
|
||||
mIsVarValue = true;
|
||||
mVarCache = std::move( varCache );
|
||||
}
|
||||
|
||||
mIsLightDarkValue = mValue.find( "light-dark(" ) != std::string::npos;
|
||||
}
|
||||
|
||||
static void varToVal( VariableFunctionCache& varCache, const std::string& varDef ) {
|
||||
FunctionString functionType = FunctionString::parse( varDef );
|
||||
if ( !functionType.getParameters().empty() ) {
|
||||
auto varToVal = []( auto&& self, VariableFunctionCache& varCache,
|
||||
std::string_view varDef ) -> void {
|
||||
auto function = FunctionString::parseView( varDef );
|
||||
if ( function.isEmpty() )
|
||||
return;
|
||||
bool foundVar = false;
|
||||
for ( auto& val : functionType.getParameters() ) {
|
||||
function.forEachParameter( [&]( std::string_view val, bool ) {
|
||||
if ( String::startsWith( val, "--" ) ) {
|
||||
varCache.variableList.emplace_back( val );
|
||||
varCache.addVariable( val );
|
||||
foundVar = true;
|
||||
} else if ( String::startsWith( val, "var(" ) ) {
|
||||
varToVal( varCache, val );
|
||||
self( self, varCache, val );
|
||||
foundVar = true;
|
||||
} else if ( foundVar ) {
|
||||
// This is a fallback value (comes after the variable name)
|
||||
varCache.variableList.emplace_back( val );
|
||||
varCache.addVariable( val );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} );
|
||||
};
|
||||
|
||||
std::vector<VariableFunctionCache> StyleSheetProperty::checkVars( const std::string& value ) {
|
||||
std::vector<VariableFunctionCache> vars;
|
||||
std::string::size_type tokenStart = 0;
|
||||
std::string::size_type tokenEnd = 0;
|
||||
|
||||
while ( true ) {
|
||||
tokenStart = value.find( "var(", tokenStart );
|
||||
tokenStart = mValue.find( "var(", tokenStart );
|
||||
if ( tokenStart != std::string::npos ) {
|
||||
tokenEnd = String::findCloseBracket( value, tokenStart, '(', ')' );
|
||||
const std::string::size_type tokenEnd =
|
||||
String::findCloseBracket( mValue, tokenStart, '(', ')' );
|
||||
if ( tokenEnd != std::string::npos ) {
|
||||
mIsVarValue = true;
|
||||
VariableFunctionCache variableFuncCache;
|
||||
variableFuncCache.definition =
|
||||
value.substr( tokenStart, tokenEnd + 1 - tokenStart );
|
||||
varToVal( variableFuncCache, variableFuncCache.definition );
|
||||
if ( mVarCacheSize >= mVarCache.size() )
|
||||
mVarCache.emplace_back();
|
||||
VariableFunctionCache& variableFuncCache = mVarCache[mVarCacheSize++];
|
||||
variableFuncCache.clear();
|
||||
variableFuncCache.definition.assign( mValue, tokenStart,
|
||||
tokenEnd + 1 - tokenStart );
|
||||
varToVal( varToVal, variableFuncCache, variableFuncCache.definition );
|
||||
tokenStart = tokenEnd;
|
||||
vars.emplace_back( std::move( variableFuncCache ) );
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -279,7 +286,7 @@ std::vector<VariableFunctionCache> StyleSheetProperty::checkVars( const std::str
|
||||
}
|
||||
};
|
||||
|
||||
return vars;
|
||||
mIsLightDarkValue = mValue.find( "light-dark(" ) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string StyleSheetProperty::asString( const std::string& defaultValue ) const {
|
||||
@@ -753,8 +760,8 @@ const String::HashType& StyleSheetProperty::getValueHash() const {
|
||||
return mValueHash;
|
||||
}
|
||||
|
||||
const std::vector<VariableFunctionCache>& StyleSheetProperty::getVarCache() const {
|
||||
return mVarCache;
|
||||
std::span<const VariableFunctionCache> StyleSheetProperty::getVarCache() const {
|
||||
return { mVarCache.data(), mVarCacheSize };
|
||||
}
|
||||
|
||||
StyleSheetProperty& StyleSheetProperty::setCachedProperty( bool cached ) {
|
||||
|
||||
@@ -73,7 +73,7 @@ void StyleSheetPropertyAnimation::tweenProperty(
|
||||
static_cast<Int32>( startColor.a + ( endColor.a - startColor.a ) * progress ),
|
||||
255 ) );
|
||||
widget->applyProperty(
|
||||
StyleSheetProperty( property, resColor.toHexString(), propertyIndex ) );
|
||||
StyleSheetProperty( property, resColor.toHexString(), propertyIndex ) );
|
||||
break;
|
||||
}
|
||||
case PropertyType::NumberLength: {
|
||||
|
||||
@@ -1,81 +1,12 @@
|
||||
#include <cctype>
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/system/functionstring.hpp>
|
||||
#include <eepp/ui/css/declarationparser.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/css/timingfunction.hpp>
|
||||
#include <eepp/ui/css/transitiondefinition.hpp>
|
||||
|
||||
namespace EE { namespace UI { namespace CSS {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Callback> void forEachCommaItem( std::string_view value, Callback&& callback ) {
|
||||
System::FunctionString::forEachParameter( value, [&]( std::string_view item, bool ) {
|
||||
callback( item );
|
||||
return true;
|
||||
} );
|
||||
}
|
||||
|
||||
SmallVector<std::string_view, 4> splitTransitionTokens( std::string_view value ) {
|
||||
SmallVector<std::string_view, 4> tokens;
|
||||
std::size_t start = 0;
|
||||
int parenthesisDepth = 0;
|
||||
for ( std::size_t i = 0; i <= value.size(); ++i ) {
|
||||
if ( i < value.size() ) {
|
||||
if ( value[i] == '(' )
|
||||
++parenthesisDepth;
|
||||
else if ( value[i] == ')' && parenthesisDepth > 0 )
|
||||
--parenthesisDepth;
|
||||
}
|
||||
if ( i == value.size() ||
|
||||
( std::isspace( static_cast<unsigned char>( value[i] ) ) && parenthesisDepth == 0 ) ) {
|
||||
if ( i > start )
|
||||
tokens.emplace_back( value.substr( start, i - start ) );
|
||||
while ( i + 1 < value.size() &&
|
||||
std::isspace( static_cast<unsigned char>( value[i + 1] ) ) )
|
||||
++i;
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
Time parseTime( std::string_view value ) {
|
||||
value = String::trim( value, " \t\n\r\f\v" );
|
||||
auto lower = []( char character ) {
|
||||
return character >= 'A' && character <= 'Z' ? character + ( 'a' - 'A' ) : character;
|
||||
};
|
||||
bool milliseconds = value.size() >= 2 && lower( value[value.size() - 2] ) == 'm' &&
|
||||
lower( value.back() ) == 's';
|
||||
bool seconds = !milliseconds && !value.empty() && lower( value.back() ) == 's';
|
||||
bool minutes = !milliseconds && !seconds && !value.empty() && lower( value.back() ) == 'm';
|
||||
std::size_t numberLength = value.size() - ( milliseconds ? 2 : ( seconds || minutes ? 1 : 0 ) );
|
||||
double number = 0;
|
||||
const char* numberStart = value.data();
|
||||
if ( numberLength > 0 && *numberStart == '+' ) {
|
||||
++numberStart;
|
||||
--numberLength;
|
||||
}
|
||||
if ( !String::fromString( number, std::string_view{ numberStart, numberLength } ) )
|
||||
return Time::Zero;
|
||||
if ( milliseconds )
|
||||
return Milliseconds( number );
|
||||
if ( minutes )
|
||||
return Minutes( number );
|
||||
return Seconds( number );
|
||||
}
|
||||
|
||||
String::HashType lowerHash( std::string_view value ) {
|
||||
return String::hashToLower( value.data(), static_cast<Int64>( value.size() ) );
|
||||
}
|
||||
|
||||
std::string lowerString( std::string_view value ) {
|
||||
std::string result{ value };
|
||||
String::toLowerInPlace( result );
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
using namespace DeclarationParser;
|
||||
|
||||
UnorderedMap<std::string, TransitionDefinition> TransitionDefinition::parseTransitionProperties(
|
||||
const std::vector<const StyleSheetProperty*>& styleSheetProperties ) {
|
||||
@@ -92,7 +23,7 @@ UnorderedMap<std::string, TransitionDefinition> TransitionDefinition::parseTrans
|
||||
switch ( property->getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::Transition:
|
||||
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
|
||||
auto tokens = splitTransitionTokens( item );
|
||||
auto tokens = splitWhitespaceTokens<4>( item );
|
||||
if ( tokens.size() < 2 )
|
||||
return;
|
||||
TransitionDefinition transition;
|
||||
@@ -192,7 +123,7 @@ ComputedTransitions::parse( const std::vector<const StyleSheetProperty*>& styleS
|
||||
switch ( property->getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::Transition:
|
||||
forEachCommaItem( property->getValue(), [&]( std::string_view item ) {
|
||||
auto tokens = splitTransitionTokens( item );
|
||||
auto tokens = splitWhitespaceTokens<4>( item );
|
||||
if ( tokens.size() < 2 )
|
||||
return;
|
||||
ComputedTransitionDefinition transition;
|
||||
|
||||
@@ -1678,8 +1678,7 @@ Float UINode::getPropertyRelativeTargetContainerLength(
|
||||
return containerLength;
|
||||
}
|
||||
|
||||
Float UINode::lengthFromValue( const std::string& value,
|
||||
const PropertyRelativeTarget& relativeTarget,
|
||||
Float UINode::lengthFromValue( std::string_view value, const PropertyRelativeTarget& relativeTarget,
|
||||
const Float& defaultValue, const Uint32& propertyIndex ) const {
|
||||
Float containerLength =
|
||||
getPropertyRelativeTargetContainerLength( relativeTarget, defaultValue, propertyIndex );
|
||||
|
||||
+116
-104
@@ -6,7 +6,6 @@
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
#include <eepp/ui/uithememanager.hpp>
|
||||
#include <eepp/ui/uiwidget.hpp>
|
||||
#include <optional>
|
||||
|
||||
using namespace EE::UI::CSS;
|
||||
using namespace EE::Scene;
|
||||
@@ -17,18 +16,6 @@ UIStyle* UIStyle::New( UIWidget* widget ) {
|
||||
return eeNew( UIStyle, ( widget ) );
|
||||
}
|
||||
|
||||
static const StyleSheetProperty&
|
||||
resolveInheritedProperty( UIStyle* inheritedStyle, StyleSheetProperty* inheritedProperty,
|
||||
std::optional<StyleSheetProperty>& resolvedProperty ) {
|
||||
if ( inheritedStyle && inheritedProperty->needsValueSubstitution() ) {
|
||||
resolvedProperty.emplace( *inheritedProperty );
|
||||
inheritedStyle->applyVarValues( &*resolvedProperty );
|
||||
return *resolvedProperty;
|
||||
}
|
||||
|
||||
return *inheritedProperty;
|
||||
}
|
||||
|
||||
UIStyle::UIStyle( UIWidget* widget ) :
|
||||
UIState(),
|
||||
mWidget( widget ),
|
||||
@@ -46,6 +33,27 @@ UIStyle::~UIStyle() {
|
||||
unsubscribeNonCacheableStyles();
|
||||
}
|
||||
|
||||
UIStyle::PropertyResolution::PropertyResolution( PropertyResolution&& other ) noexcept :
|
||||
mOwner( other.mOwner ), mProperty( other.mProperty ), mSlot( other.mSlot ) {
|
||||
other.mOwner = nullptr;
|
||||
other.mProperty = nullptr;
|
||||
other.mSlot = NoSlot;
|
||||
}
|
||||
|
||||
UIStyle::PropertyResolution::~PropertyResolution() {
|
||||
release();
|
||||
}
|
||||
|
||||
void UIStyle::PropertyResolution::release() {
|
||||
if ( nullptr == mOwner || NoSlot == mSlot )
|
||||
return;
|
||||
eeASSERT( mOwner->mPropertyResolutionDepth == mSlot + 1 );
|
||||
--mOwner->mPropertyResolutionDepth;
|
||||
mOwner = nullptr;
|
||||
mProperty = nullptr;
|
||||
mSlot = NoSlot;
|
||||
}
|
||||
|
||||
bool UIStyle::stateExists( const EE::Uint32& ) const {
|
||||
return true;
|
||||
}
|
||||
@@ -118,10 +126,8 @@ void UIStyle::applyInheritedProperties() {
|
||||
UIStyle* inheritedStyle = nullptr;
|
||||
auto inheritedProp = getInheritedProperty( propId, &inheritedStyle );
|
||||
if ( inheritedProp ) {
|
||||
std::optional<StyleSheetProperty> resolvedProperty;
|
||||
const auto& propertyToApply =
|
||||
resolveInheritedProperty( inheritedStyle, inheritedProp, resolvedProperty );
|
||||
mWidget->applyProperty( propertyToApply );
|
||||
auto resolvedProperty = inheritedStyle->resolveProperty( inheritedProp );
|
||||
mWidget->applyProperty( *resolvedProperty.get() );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,15 +146,14 @@ bool UIStyle::hasTransition( const std::string& propertyName ) {
|
||||
}
|
||||
|
||||
StyleSheetPropertyAnimation* UIStyle::getAnimation( const PropertyDefinition* propertyDef ) {
|
||||
std::vector<Action*> actions = mWidget->getActionsByTag( propertyDef->getId() );
|
||||
if ( !actions.empty() ) {
|
||||
for ( auto& action : actions ) {
|
||||
if ( action->getId() == StyleSheetPropertyAnimation::ID ) {
|
||||
StyleSheetPropertyAnimation* animation =
|
||||
static_cast<StyleSheetPropertyAnimation*>( action );
|
||||
if ( animation->getAnimationOrigin() == AnimationOrigin::Animation ) {
|
||||
return animation;
|
||||
}
|
||||
SmallVector<Action*, 4> actions;
|
||||
mWidget->getActionsByTag( propertyDef->getId(), actions );
|
||||
for ( auto* action : actions ) {
|
||||
if ( action->getId() == StyleSheetPropertyAnimation::ID ) {
|
||||
StyleSheetPropertyAnimation* animation =
|
||||
static_cast<StyleSheetPropertyAnimation*>( action );
|
||||
if ( animation->getAnimationOrigin() == AnimationOrigin::Animation ) {
|
||||
return animation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,36 +186,46 @@ const bool& UIStyle::isChangingState() const {
|
||||
}
|
||||
|
||||
StyleSheetVariable UIStyle::getVariable( const std::string& variable ) {
|
||||
const StyleSheetVariable* resolved = getVariableRef( variable );
|
||||
return resolved ? *resolved : StyleSheetVariable();
|
||||
}
|
||||
|
||||
const StyleSheetVariable* UIStyle::getVariableRef( const std::string& variable ) {
|
||||
if ( NULL != mWidget && NULL != mWidget->getUISceneNode() )
|
||||
resetGlobalDefinition();
|
||||
|
||||
StyleSheetVariable localVariable =
|
||||
mElementStyle ? mElementStyle->getVariableByName( variable ) : StyleSheetVariable();
|
||||
const auto nameHash = String::hash( variable );
|
||||
const StyleSheetVariable* localVariable = nullptr;
|
||||
if ( mElementStyle ) {
|
||||
auto it = mElementStyle->getVariables().find( nameHash );
|
||||
if ( it != mElementStyle->getVariables().end() )
|
||||
localVariable = &it->second;
|
||||
}
|
||||
|
||||
if ( NULL != mGlobalDefinition ) {
|
||||
auto it = mGlobalDefinition->getVariables().find( String::hash( variable ) );
|
||||
auto it = mGlobalDefinition->getVariables().find( nameHash );
|
||||
|
||||
if ( it != mGlobalDefinition->getVariables().end() ) {
|
||||
if ( localVariable.isEmpty() ||
|
||||
it->second.getSpecificity() > localVariable.getSpecificity() )
|
||||
return it->second;
|
||||
if ( nullptr == localVariable ||
|
||||
it->second.getSpecificity() > localVariable->getSpecificity() )
|
||||
return &it->second;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !localVariable.isEmpty() )
|
||||
if ( nullptr != localVariable )
|
||||
return localVariable;
|
||||
|
||||
Node* parentWidget = mWidget->getParentWidget();
|
||||
Node* parentWidget = mWidget ? mWidget->getParentWidget() : nullptr;
|
||||
|
||||
if ( NULL != parentWidget ) {
|
||||
UIStyle* style = parentWidget->asType<UIWidget>()->getUIStyle();
|
||||
|
||||
if ( NULL != style ) {
|
||||
return style->getVariable( variable );
|
||||
return style->getVariableRef( variable );
|
||||
}
|
||||
}
|
||||
|
||||
return StyleSheetVariable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UIStyle::getForceReapplyProperties() const {
|
||||
@@ -334,22 +349,22 @@ void UIStyle::applyLightDarkValue( std::string& value ) {
|
||||
};
|
||||
}
|
||||
|
||||
void UIStyle::setVariableFromValue( StyleSheetProperty* property, const std::string& value ) {
|
||||
void UIStyle::setVariableFromValue( StyleSheetProperty* property ) {
|
||||
if ( property->getVarCache().empty() && !property->isLightDarkValue() )
|
||||
return;
|
||||
|
||||
std::string newValue( value );
|
||||
std::string& value = property->mutableValue();
|
||||
static constexpr int maxDepth = 16;
|
||||
|
||||
for ( int depth = 0; depth < maxDepth; depth++ ) {
|
||||
bool changed = false;
|
||||
if ( !property->getVarCache().empty() ) {
|
||||
const auto& varCache = property->getVarCache();
|
||||
const auto varCache = property->getVarCache();
|
||||
for ( const auto& var : varCache ) {
|
||||
for ( const auto& val : var.variableList ) {
|
||||
StyleSheetVariable variable( getVariable( val ) );
|
||||
if ( !variable.isEmpty() ) {
|
||||
String::replaceAll( newValue, var.definition, variable.getValue() );
|
||||
const StyleSheetVariable* variable = getVariableRef( val );
|
||||
if ( nullptr != variable ) {
|
||||
String::replaceAll( value, var.definition, variable->getValue() );
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
@@ -357,12 +372,12 @@ void UIStyle::setVariableFromValue( StyleSheetProperty* property, const std::str
|
||||
}
|
||||
}
|
||||
if ( property->isLightDarkValue() ) {
|
||||
applyLightDarkValue( newValue );
|
||||
applyLightDarkValue( value );
|
||||
changed = true;
|
||||
}
|
||||
if ( !changed )
|
||||
break;
|
||||
property->setValue( newValue );
|
||||
property->setValue( value );
|
||||
if ( property->getVarCache().empty() && !property->isLightDarkValue() )
|
||||
break;
|
||||
}
|
||||
@@ -377,10 +392,10 @@ void UIStyle::applyVarValues( StyleSheetProperty* property ) {
|
||||
property->getPropertyIndexCount() > 0 ) {
|
||||
for ( size_t i = 0; i < property->getPropertyIndexCount(); i++ ) {
|
||||
StyleSheetProperty* realProperty = property->getPropertyIndexRef( i );
|
||||
setVariableFromValue( realProperty, realProperty->getValue() );
|
||||
setVariableFromValue( realProperty );
|
||||
}
|
||||
} else {
|
||||
setVariableFromValue( property, property->getValue() );
|
||||
setVariableFromValue( property );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,8 +453,8 @@ void UIStyle::onStateChange() {
|
||||
mWidget->beginAttributesTransaction();
|
||||
|
||||
for ( PropertyId prop : changedProperties ) {
|
||||
std::optional<StyleSheetProperty> resolvedProperty;
|
||||
StyleSheetProperty* property = getResolvedLocalProperty( prop, resolvedProperty );
|
||||
auto resolvedProperty = getResolvedLocalProperty( prop );
|
||||
const StyleSheetProperty* property = resolvedProperty.get();
|
||||
|
||||
if ( nullptr == property || NULL == property->getPropertyDefinition() ) {
|
||||
const auto def = StyleSheetSpecification::instance()->getProperty( prop );
|
||||
@@ -448,11 +463,10 @@ void UIStyle::onStateChange() {
|
||||
StyleSheetProperty* inheritedProp =
|
||||
getInheritedProperty( prop, &inheritedStyle );
|
||||
if ( inheritedProp ) {
|
||||
std::optional<StyleSheetProperty> resolvedInheritedProperty;
|
||||
const auto& inheritedPropertyToApply = resolveInheritedProperty(
|
||||
inheritedStyle, inheritedProp, resolvedInheritedProperty );
|
||||
mWidget->applyProperty( inheritedPropertyToApply );
|
||||
mWidget->propagateInheritedProperty( inheritedPropertyToApply );
|
||||
auto resolvedInheritedProperty =
|
||||
inheritedStyle->resolveProperty( inheritedProp );
|
||||
mWidget->applyProperty( *resolvedInheritedProperty.get() );
|
||||
mWidget->propagateInheritedProperty( *resolvedInheritedProperty.get() );
|
||||
}
|
||||
}
|
||||
continue;
|
||||
@@ -478,11 +492,10 @@ void UIStyle::onStateChange() {
|
||||
mWidget->propagateInheritedProperty( resolved );
|
||||
}
|
||||
} else {
|
||||
std::optional<StyleSheetProperty> resolvedInheritedProperty;
|
||||
const auto& inheritedPropertyToApply = resolveInheritedProperty(
|
||||
inheritedStyle, inheritedProp, resolvedInheritedProperty );
|
||||
mWidget->applyProperty( inheritedPropertyToApply );
|
||||
mWidget->propagateInheritedProperty( inheritedPropertyToApply );
|
||||
auto resolvedInheritedProperty =
|
||||
inheritedStyle->resolveProperty( inheritedProp );
|
||||
mWidget->applyProperty( *resolvedInheritedProperty.get() );
|
||||
mWidget->propagateInheritedProperty( *resolvedInheritedProperty.get() );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -612,13 +625,8 @@ void UIStyle::removeRelatedWidgets() {
|
||||
|
||||
void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalProperty,
|
||||
std::shared_ptr<ElementDefinition> prevDefinition ) {
|
||||
const StyleSheetProperty* property = &originalProperty;
|
||||
std::optional<StyleSheetProperty> resolvedProperty;
|
||||
if ( originalProperty.needsValueSubstitution() ) {
|
||||
resolvedProperty.emplace( originalProperty );
|
||||
applyVarValues( &*resolvedProperty );
|
||||
property = &*resolvedProperty;
|
||||
}
|
||||
auto resolvedProperty = resolveProperty( &originalProperty );
|
||||
const StyleSheetProperty* property = resolvedProperty.get();
|
||||
|
||||
const PropertyDefinition* propertyDefinition = property->getPropertyDefinition();
|
||||
|
||||
@@ -650,20 +658,13 @@ void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalPropert
|
||||
if ( nullptr != prevDefinition ) {
|
||||
auto prevProp = prevDefinition->getProperty( property->getPropertyId() );
|
||||
if ( nullptr != prevProp ) {
|
||||
StyleSheetProperty* curProperty = prevProp;
|
||||
std::optional<StyleSheetProperty> resolvedPrevProperty;
|
||||
if ( prevProp->needsValueSubstitution() ) {
|
||||
resolvedPrevProperty.emplace( *prevProp );
|
||||
curProperty = &*resolvedPrevProperty;
|
||||
}
|
||||
auto resolvedPrevProperty = resolveProperty( prevProp );
|
||||
const StyleSheetProperty* curProperty = resolvedPrevProperty.get();
|
||||
if ( propertyDefinition->isIndexed() &&
|
||||
property->getIndex() < curProperty->getPropertyIndexCount() ) {
|
||||
StyleSheetProperty* indexedProperty =
|
||||
curProperty->getPropertyIndexRef( property->getIndex() );
|
||||
applyVarValues( indexedProperty );
|
||||
startValue = indexedProperty->getValue();
|
||||
startValue =
|
||||
curProperty->getPropertyIndex( property->getIndex() ).getValue();
|
||||
} else {
|
||||
applyVarValues( curProperty );
|
||||
startValue = curProperty->getValue();
|
||||
}
|
||||
}
|
||||
@@ -746,24 +747,26 @@ void UIStyle::updateAnimations() {
|
||||
if ( nullptr == mDefinition )
|
||||
return;
|
||||
|
||||
bool isDifferent = false;
|
||||
CSS::AnimationsMap animations;
|
||||
|
||||
if ( !mDefinition->getAnimationProperties().empty() ) {
|
||||
animations =
|
||||
AnimationDefinition::parseAnimationProperties( mDefinition->getAnimationProperties() );
|
||||
if ( animations.size() == mAnimations.size() ) {
|
||||
for ( auto& animation : animations ) {
|
||||
auto animIt = mAnimations.find( animation.second.getName() );
|
||||
if ( animIt == mAnimations.end() || animIt->second != animation.second ) {
|
||||
isDifferent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isDifferent = true;
|
||||
if ( mDefinition->getAnimationProperties().empty() ) {
|
||||
if ( !mAnimations.empty() ) {
|
||||
mAnimations.clear();
|
||||
removeAllAnimations();
|
||||
}
|
||||
} else if ( !mAnimations.empty() && mDefinition->getAnimationProperties().empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isDifferent = false;
|
||||
CSS::AnimationsMap animations =
|
||||
AnimationDefinition::parseAnimationProperties( mDefinition->getAnimationProperties() );
|
||||
if ( animations.size() == mAnimations.size() ) {
|
||||
for ( auto& animation : animations ) {
|
||||
auto animIt = mAnimations.find( animation.second.getName() );
|
||||
if ( animIt == mAnimations.end() || animIt->second != animation.second ) {
|
||||
isDifferent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isDifferent = true;
|
||||
}
|
||||
|
||||
@@ -773,7 +776,7 @@ void UIStyle::updateAnimations() {
|
||||
removeAllAnimations();
|
||||
|
||||
startAnimations( animations );
|
||||
} else if ( !mDefinition->getAnimationProperties().empty() ) {
|
||||
} else {
|
||||
updateAnimationsPlayState();
|
||||
}
|
||||
}
|
||||
@@ -944,16 +947,25 @@ StyleSheetProperty* UIStyle::getLocalProperty( PropertyId propId ) {
|
||||
return defProperty ? defProperty : elemProperty;
|
||||
}
|
||||
|
||||
StyleSheetProperty*
|
||||
UIStyle::getResolvedLocalProperty( PropertyId propId,
|
||||
std::optional<StyleSheetProperty>& resolvedProperty ) {
|
||||
StyleSheetProperty* property = getLocalProperty( propId );
|
||||
if ( property && property->needsValueSubstitution() ) {
|
||||
resolvedProperty.emplace( *property );
|
||||
applyVarValues( &*resolvedProperty );
|
||||
return &*resolvedProperty;
|
||||
}
|
||||
return property;
|
||||
UIStyle::PropertyResolution UIStyle::resolveProperty( const StyleSheetProperty* property ) {
|
||||
if ( nullptr == property || !property->needsValueSubstitution() )
|
||||
return PropertyResolution{ nullptr, property };
|
||||
|
||||
const Uint32 slotIndex = mPropertyResolutionDepth;
|
||||
if ( slotIndex >= mPropertyResolutionSlots.size() )
|
||||
mPropertyResolutionSlots.emplace_back( std::make_unique<StyleSheetProperty>() );
|
||||
StyleSheetProperty* slot = mPropertyResolutionSlots[slotIndex].get();
|
||||
|
||||
++mPropertyResolutionDepth;
|
||||
PropertyResolution resolution{ this, slot, slotIndex };
|
||||
if ( !slot->hasSameResolutionSource( *property ) )
|
||||
*slot = *property;
|
||||
applyVarValues( slot );
|
||||
return resolution;
|
||||
}
|
||||
|
||||
UIStyle::PropertyResolution UIStyle::getResolvedLocalProperty( PropertyId propId ) {
|
||||
return resolveProperty( getLocalProperty( propId ) );
|
||||
}
|
||||
|
||||
void UIStyle::addStructurallyVolatileWidgetFromParent() {
|
||||
|
||||
+27
-13
@@ -18,6 +18,7 @@
|
||||
#include <eepp/ui/uiwidget.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
#include <optional>
|
||||
|
||||
#define PUGIXML_HEADER_ONLY
|
||||
#include <pugixml/pugixml.hpp>
|
||||
@@ -1994,14 +1995,18 @@ void UIWidget::setStyleSheetInlineProperty( const std::string& name, const std::
|
||||
}
|
||||
|
||||
void UIWidget::propagateInheritedProperty( const CSS::StyleSheetProperty& property ) {
|
||||
CSS::StyleSheetProperty propToPropagate = property;
|
||||
const CSS::StyleSheetProperty* propertyToPropagate = &property;
|
||||
std::optional<CSS::StyleSheetProperty> resolvedProperty;
|
||||
|
||||
if ( propToPropagate.needsValueSubstitution() && NULL != mStyle )
|
||||
mStyle->applyVarValues( &propToPropagate );
|
||||
if ( property.needsValueSubstitution() && NULL != mStyle ) {
|
||||
resolvedProperty.emplace( property );
|
||||
mStyle->applyVarValues( &*resolvedProperty );
|
||||
propertyToPropagate = &*resolvedProperty;
|
||||
}
|
||||
|
||||
if ( propToPropagate.getPropertyDefinition() &&
|
||||
propToPropagate.getPropertyDefinition()->getPropertyId() == PropertyId::FontSize ) {
|
||||
StyleSheetLength length( propToPropagate.value() );
|
||||
if ( propertyToPropagate->getPropertyDefinition() &&
|
||||
propertyToPropagate->getPropertyDefinition()->getPropertyId() == PropertyId::FontSize ) {
|
||||
StyleSheetLength length( propertyToPropagate->value() );
|
||||
Float pxSize = 0;
|
||||
|
||||
if ( length.getUnit() == StyleSheetLength::Unit::Rem ) {
|
||||
@@ -2029,14 +2034,23 @@ void UIWidget::propagateInheritedProperty( const CSS::StyleSheetProperty& proper
|
||||
else
|
||||
pxSize = ( length.getValue() / 100.f ) * parentFontSize;
|
||||
} else {
|
||||
pxSize = lengthFromValue( propToPropagate );
|
||||
pxSize = lengthFromValue( *propertyToPropagate );
|
||||
}
|
||||
|
||||
propToPropagate = CSS::StyleSheetProperty(
|
||||
propToPropagate.getName(), String::fromFloat( PixelDensity::pxToDp( pxSize ), "dp" ),
|
||||
propToPropagate.getSpecificity() );
|
||||
const PropertyDefinition* propertyDefinition = propertyToPropagate->getPropertyDefinition();
|
||||
const Uint32 propertyIndex = propertyToPropagate->getIndex();
|
||||
const Int64 specificity = propertyToPropagate->getSpecificity();
|
||||
resolvedProperty.emplace( propertyDefinition,
|
||||
String::fromFloat( PixelDensity::pxToDp( pxSize ), "dp" ),
|
||||
propertyIndex );
|
||||
resolvedProperty->setSpecificity( specificity );
|
||||
propertyToPropagate = &*resolvedProperty;
|
||||
}
|
||||
|
||||
propagateInheritedPropertyResolved( *propertyToPropagate );
|
||||
}
|
||||
|
||||
void UIWidget::propagateInheritedPropertyResolved( const CSS::StyleSheetProperty& property ) {
|
||||
Node* child = getFirstChild();
|
||||
while ( child ) {
|
||||
if ( child->isWidget() ) {
|
||||
@@ -2044,9 +2058,9 @@ void UIWidget::propagateInheritedProperty( const CSS::StyleSheetProperty& proper
|
||||
UIStyle* childStyle = childWidget->getUIStyle();
|
||||
// Only propagate if the child doesn't explicitly override it
|
||||
if ( childStyle && !childStyle->hasLocalProperty(
|
||||
propToPropagate.getPropertyDefinition()->getPropertyId() ) ) {
|
||||
childWidget->applyProperty( propToPropagate );
|
||||
childWidget->propagateInheritedProperty( propToPropagate );
|
||||
property.getPropertyDefinition()->getPropertyId() ) ) {
|
||||
childWidget->applyProperty( property );
|
||||
childWidget->propagateInheritedPropertyResolved( property );
|
||||
}
|
||||
}
|
||||
child = child->getNextNode();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "utest.h"
|
||||
#include <eepp/scene/actions/delay.hpp>
|
||||
#include <eepp/scene/actions/runnable.hpp>
|
||||
#include <eepp/ui/css/animationdefinition.hpp>
|
||||
#include <eepp/ui/css/stylesheetspecification.hpp>
|
||||
#include <eepp/ui/css/transitiondefinition.hpp>
|
||||
|
||||
@@ -123,3 +124,57 @@ UTEST( Transitions, allocationLightParserPreservesAcceptedSpelling ) {
|
||||
EXPECT_EQ( opacity->timingFunctionParameters.size(), 4u );
|
||||
EXPECT_TRUE( TimingFunction::parse( "cubic-bezier(" ).interpolation == Ease::None );
|
||||
}
|
||||
|
||||
UTEST( Animations, allocationLightShorthandPreservesSemantics ) {
|
||||
auto* specification = StyleSheetSpecification::instance();
|
||||
StyleSheetProperty shorthand(
|
||||
specification->getProperty( PropertyId::Animation ),
|
||||
"fade +150MS CUBIC-BEZIER(0.1, 0.2, 0.3, 0.4) +25MS 3 alternate both paused, "
|
||||
"slide 2s linear" );
|
||||
std::vector<const StyleSheetProperty*> properties{ &shorthand };
|
||||
|
||||
AnimationsMap animations = AnimationDefinition::parseAnimationProperties( properties );
|
||||
auto fade = animations.find( "fade" );
|
||||
auto slide = animations.find( "slide" );
|
||||
ASSERT_TRUE( fade != animations.end() );
|
||||
ASSERT_TRUE( slide != animations.end() );
|
||||
EXPECT_EQ( fade->second.getDuration().asMilliseconds(), 150.0 );
|
||||
EXPECT_EQ( fade->second.getDelay().asMilliseconds(), 25.0 );
|
||||
EXPECT_EQ( fade->second.getIterations(), 3 );
|
||||
EXPECT_TRUE( fade->second.getDirection() == AnimationDefinition::Alternate );
|
||||
EXPECT_TRUE( fade->second.getFillMode() == AnimationDefinition::Both );
|
||||
EXPECT_TRUE( fade->second.isPaused() );
|
||||
EXPECT_TRUE( fade->second.getTimingFunction() == Ease::CubizBezier );
|
||||
EXPECT_EQ( fade->second.getTimingFunctionParametersInline().size(), 4u );
|
||||
EXPECT_EQ( slide->second.getDuration().asSeconds(), 2.0 );
|
||||
EXPECT_EQ( slide->second.getDelay().asSeconds(), 0.0 );
|
||||
}
|
||||
|
||||
UTEST( Animations, allocationLightLonghandListsCycleIndependently ) {
|
||||
auto* specification = StyleSheetSpecification::instance();
|
||||
StyleSheetProperty names( specification->getProperty( PropertyId::AnimationName ),
|
||||
"first,second,third" );
|
||||
StyleSheetProperty durations( specification->getProperty( PropertyId::AnimationDuration ),
|
||||
"1s,2s" );
|
||||
StyleSheetProperty delays( specification->getProperty( PropertyId::AnimationDelay ),
|
||||
"10ms,20ms" );
|
||||
StyleSheetProperty directions( specification->getProperty( PropertyId::AnimationDirection ),
|
||||
"reverse,alternate" );
|
||||
StyleSheetProperty timing( specification->getProperty( PropertyId::AnimationTimingFunction ),
|
||||
"linear,cubic-bezier(0.1, 0.2, 0.3, 0.4)" );
|
||||
std::vector<const StyleSheetProperty*> properties{ &names, &durations, &delays, &directions,
|
||||
&timing };
|
||||
|
||||
AnimationsMap animations = AnimationDefinition::parseAnimationProperties( properties );
|
||||
ASSERT_EQ( animations.size(), 3u );
|
||||
EXPECT_EQ( animations.at( "first" ).getDuration().asSeconds(), 1.0 );
|
||||
EXPECT_EQ( animations.at( "second" ).getDuration().asSeconds(), 2.0 );
|
||||
EXPECT_EQ( animations.at( "third" ).getDuration().asSeconds(), 1.0 );
|
||||
EXPECT_EQ( animations.at( "third" ).getDelay().asMilliseconds(), 10.0 );
|
||||
EXPECT_TRUE( animations.at( "first" ).getDirection() == AnimationDefinition::Reverse );
|
||||
EXPECT_TRUE( animations.at( "second" ).getDirection() == AnimationDefinition::Alternate );
|
||||
EXPECT_TRUE( animations.at( "third" ).getDirection() == AnimationDefinition::Reverse );
|
||||
EXPECT_TRUE( animations.at( "first" ).getTimingFunction() == Ease::Linear );
|
||||
EXPECT_TRUE( animations.at( "second" ).getTimingFunction() == Ease::CubizBezier );
|
||||
EXPECT_EQ( animations.at( "second" ).getTimingFunctionParametersInline().size(), 4u );
|
||||
}
|
||||
|
||||
@@ -280,3 +280,10 @@ UTEST( ColorParsing, hsvaWithThreeLegacy ) {
|
||||
EXPECT_COLOREQ( Color( 255, 0, 0, 255 ),
|
||||
Color::fromString( "hsva(0, 100%, 100%)" ) );
|
||||
}
|
||||
|
||||
UTEST( ColorParsing, hexSerialization ) {
|
||||
EXPECT_STDSTREQ( "#01020f", Color( 1, 2, 15 ).toHexString() );
|
||||
EXPECT_STDSTREQ( "01020f", Color( 1, 2, 15 ).toHexString( false ) );
|
||||
EXPECT_STDSTREQ( "#01020f10", Color( 1, 2, 15, 16 ).toHexString() );
|
||||
EXPECT_STDSTREQ( "01020f10", Color( 1, 2, 15, 16 ).toHexString( false ) );
|
||||
}
|
||||
|
||||
@@ -31,6 +31,44 @@ using namespace EE::UI::CSS;
|
||||
using namespace EE::Scene;
|
||||
using namespace EE::Graphics;
|
||||
|
||||
namespace {
|
||||
|
||||
class ResolutionStorageTestStyle : public UIStyle {
|
||||
public:
|
||||
ResolutionStorageTestStyle() : UIStyle( nullptr ) {}
|
||||
|
||||
struct Probe {
|
||||
const StyleSheetProperty* firstAddress{};
|
||||
const StyleSheetProperty* nestedAddress{};
|
||||
const StyleSheetProperty* reusedAddress{};
|
||||
std::string firstValueBeforeNested;
|
||||
std::string firstValueAfterNested;
|
||||
std::string nestedValue;
|
||||
};
|
||||
|
||||
Probe probe( const StyleSheetProperty& first, const StyleSheetProperty& nested ) {
|
||||
Probe probe;
|
||||
{
|
||||
auto firstResolution = resolveProperty( &first );
|
||||
probe.firstAddress = firstResolution.get();
|
||||
probe.firstValueBeforeNested = firstResolution.get()->getValue();
|
||||
{
|
||||
auto nestedResolution = resolveProperty( &nested );
|
||||
probe.nestedAddress = nestedResolution.get();
|
||||
probe.nestedValue = nestedResolution.get()->getValue();
|
||||
}
|
||||
probe.firstValueAfterNested = firstResolution.get()->getValue();
|
||||
}
|
||||
{
|
||||
auto reusedResolution = resolveProperty( &first );
|
||||
probe.reusedAddress = reusedResolution.get();
|
||||
}
|
||||
return probe;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
UTEST( CSSParser, UnknownPropertiesKeepDistinctNameHashes ) {
|
||||
StyleSheetProperty first( "data-first", "one" );
|
||||
StyleSheetProperty second( "data-second", "two" );
|
||||
@@ -44,6 +82,17 @@ UTEST( CSSParser, UnknownPropertiesKeepDistinctNameHashes ) {
|
||||
EXPECT_TRUE( first.getShorthandId() == ShorthandId::Invalid );
|
||||
}
|
||||
|
||||
UTEST( CSSParser, DefinitionBackedPropertiesUseCanonicalDefinitionName ) {
|
||||
auto* definition =
|
||||
StyleSheetSpecification::instance()->getProperty( PropertyId::BackgroundColor );
|
||||
ASSERT_TRUE( nullptr != definition );
|
||||
StyleSheetProperty property( definition, "#010203" );
|
||||
|
||||
EXPECT_FALSE( property.isEmpty() );
|
||||
EXPECT_STDSTREQ( definition->getName(), property.getName() );
|
||||
EXPECT_EQ( definition->getId(), property.getNameHash() );
|
||||
}
|
||||
|
||||
UTEST( CSSParser, CommentsPreserveDeclarationContext ) {
|
||||
StyleSheetPropertiesParser parser( R"css(
|
||||
color /* before colon */ : red;
|
||||
@@ -487,6 +536,40 @@ UTEST( CSSVariables, StyleSheetPropertyNeedsValueSubstitution ) {
|
||||
.needsValueSubstitution() );
|
||||
}
|
||||
|
||||
UTEST( CSSVariables, ResolutionStorageIsReentrantAndReusable ) {
|
||||
ResolutionStorageTestStyle style;
|
||||
style.setStyleSheetVariable( StyleSheetVariable( "--first", "#123456" ) );
|
||||
style.setStyleSheetVariable( StyleSheetVariable( "--nested", "#ABCDEF" ) );
|
||||
|
||||
const auto probe = style.probe( StyleSheetProperty( "color", "var(--first)" ),
|
||||
StyleSheetProperty( "background-color", "var(--nested)" ) );
|
||||
|
||||
EXPECT_TRUE( nullptr != probe.firstAddress );
|
||||
EXPECT_TRUE( nullptr != probe.nestedAddress );
|
||||
EXPECT_NE( probe.firstAddress, probe.nestedAddress );
|
||||
EXPECT_EQ( probe.firstAddress, probe.reusedAddress );
|
||||
EXPECT_STDSTREQ( "#123456", probe.firstValueBeforeNested );
|
||||
EXPECT_STDSTREQ( "#123456", probe.firstValueAfterNested );
|
||||
EXPECT_STDSTREQ( "#ABCDEF", probe.nestedValue );
|
||||
}
|
||||
|
||||
UTEST( CSSVariables, StyleSheetPropertyReusesVariableCacheStorage ) {
|
||||
StyleSheetProperty property( "color", "var(--a-variable-name-long-enough-to-allocate)" );
|
||||
auto firstCache = property.getVarCache();
|
||||
ASSERT_EQ( 1u, firstCache.size() );
|
||||
const auto* cacheAddress = &*firstCache.begin();
|
||||
const auto* variableStorage = firstCache.begin()->variableList.data();
|
||||
|
||||
property.setValue( "#123456" );
|
||||
EXPECT_TRUE( property.getVarCache().empty() );
|
||||
|
||||
property.setValue( "var(--a-variable-name-long-enough-to-allocate)" );
|
||||
auto reusedCache = property.getVarCache();
|
||||
ASSERT_EQ( 1u, reusedCache.size() );
|
||||
EXPECT_EQ( cacheAddress, &*reusedCache.begin() );
|
||||
EXPECT_EQ( variableStorage, reusedCache.begin()->variableList.data() );
|
||||
}
|
||||
|
||||
UTEST( CSSVariables, StyleAttributeVarOnRichTextAndTextSpan ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - CSS Style Attribute Var Test", WindowStyle::Default,
|
||||
|
||||
@@ -281,8 +281,10 @@ bool App::init() {
|
||||
mConsole->setMaxLogLines( 1024000 );
|
||||
mConsole->setFontSize( mConfig.ConsoleFontSize );
|
||||
mConsole->on( Event::KeyDown, [this]( const Event* event ) {
|
||||
if ( event->asKeyEvent()->getKeyCode() == KEY_ESCAPE )
|
||||
if ( event->asKeyEvent()->getKeyCode() == KEY_ESCAPE ) {
|
||||
saveConfig();
|
||||
mUIApplication->getWindow()->close();
|
||||
}
|
||||
} );
|
||||
mConsoleCommands = std::make_unique<ConsoleCommands>( this, mConsole );
|
||||
registerConsoleCommands();
|
||||
@@ -357,9 +359,10 @@ void App::registerKeyBindings() {
|
||||
}
|
||||
if ( mConsole->isActive() )
|
||||
return;
|
||||
if ( key == KEY_ESCAPE || key == KEY_Q )
|
||||
if ( key == KEY_ESCAPE || key == KEY_Q ) {
|
||||
saveConfig();
|
||||
getWindow()->close();
|
||||
else if ( key == KEY_TAB && keyEvent->getMod() & KEYMOD_ALT )
|
||||
} else if ( key == KEY_TAB && keyEvent->getMod() & KEYMOD_ALT )
|
||||
getWindow()->minimize();
|
||||
else if ( key == KEY_F || ( key == KEY_RETURN && keyEvent->getMod() & KEYMOD_ALT ) )
|
||||
getWindow()->toggleFullscreen();
|
||||
|
||||
Reference in New Issue
Block a user