diff --git a/docs/articles/css_specification.md b/docs/articles/css_specification.md index 3dee3c48d..693218d68 100644 --- a/docs/articles/css_specification.md +++ b/docs/articles/css_specification.md @@ -27,13 +27,12 @@ but the layout model depends on the type of UI being styled. * Attribute selectors are supported. For native widgets they operate on properties exposed by the widget property system. HTML `data-*` attributes are also available to selectors on HTML widgets. -* State-dependent style rollback is not yet a complete CSS computed-style rollback implementation. - When a pseudo-class or other volatile selector introduces a property that has no matching - stateless declaration, eepp attempts to preserve the previous value by serializing the current - widget property and using it as a fallback. This works for many properties, but restoration may - be incomplete when a property cannot be serialized, when a property first appears during a - transition between non-normal states, or when the original value came from dynamic/inherited - state that later changes. +* When a pseudo-class or other volatile selector stops matching, eepp restores the current + underlying local or stylesheet declaration. If there is no such declaration, it restores the + current inherited value or a saved native widget value. Native fallback requires the widget to + serialize that property through `getPropertyString()`; properties without a serializable native + value may not roll back. Application changes to a property while a state-dependent declaration + overrides it have no defined rollback behavior. * Relative resource URLs are resolved against the relevant stylesheet/document base URI when one is available. eepp also supports resource locators for resources already loaded by the application. diff --git a/docs/articles/ui_css_for_applications.md b/docs/articles/ui_css_for_applications.md index f24f87f15..fefd90ed1 100644 --- a/docs/articles/ui_css_for_applications.md +++ b/docs/articles/ui_css_for_applications.md @@ -534,7 +534,11 @@ Use pseudo-classes for widget state managed by the UI system: } ``` -State changes cause eepp to recompute the matching style. +State changes cause eepp to recompute the matching style. When a state-dependent declaration stops +matching, a lower local or stylesheet declaration takes effect again. If there is no lower +declaration, eepp restores the current inherited value or the widget's saved native value. Native +rollback requires `getPropertyString()` to serialize the property. Changes made by application code +to a property while a state rule overrides it have no defined rollback behavior. Common native state pseudo-classes include: diff --git a/include/eepp/ui/uihtmltable.hpp b/include/eepp/ui/uihtmltable.hpp index e64b3a97d..6d69ff810 100644 --- a/include/eepp/ui/uihtmltable.hpp +++ b/include/eepp/ui/uihtmltable.hpp @@ -25,7 +25,7 @@ class EE_API UIHTMLTable : public UIHTMLWidget { virtual std::vector getPropertiesImplemented() const; virtual std::string getPropertyString( const PropertyDefinition* propertyDef, - const Uint32& state = 0 ) const; + const Uint32& propertyIndex = 0 ) const; virtual bool applyProperty( const StyleSheetProperty& attribute ); @@ -57,7 +57,7 @@ class EE_API UIHTMLTableCell : public UIRichText { virtual std::vector getPropertiesImplemented() const; virtual std::string getPropertyString( const PropertyDefinition* propertyDef, - const Uint32& state = 0 ) const; + const Uint32& propertyIndex = 0 ) const; virtual bool applyProperty( const StyleSheetProperty& attribute ); diff --git a/include/eepp/ui/uihtmlwidget.hpp b/include/eepp/ui/uihtmlwidget.hpp index fdd7d0e92..504cc1309 100644 --- a/include/eepp/ui/uihtmlwidget.hpp +++ b/include/eepp/ui/uihtmlwidget.hpp @@ -349,7 +349,7 @@ class EE_API UIHTMLWidget : public UILayout { using UIWidget::getPropertyString; virtual std::string getPropertyString( const PropertyDefinition* propertyDef, - const Uint32& state = 0 ) const; + const Uint32& propertyIndex = 0 ) const; virtual bool applyProperty( const StyleSheetProperty& attribute ); diff --git a/include/eepp/ui/uistyle.hpp b/include/eepp/ui/uistyle.hpp index a6a73723a..b71c123c6 100644 --- a/include/eepp/ui/uistyle.hpp +++ b/include/eepp/ui/uistyle.hpp @@ -145,6 +145,14 @@ class EE_API UIStyle : public UIState { * needs no container allocation; nested slots are retained for later reuse. */ std::unique_ptr mPropertyResolutionSlot; SmallVector, 2> mNestedPropertyResolutionSlots; + struct PropertyFallback { + CSS::PropertyId propertyId; + Uint32 index; + const CSS::PropertyDefinition* definition; + std::string nativeValue; + bool inherited; + }; + SmallVector mPropertyFallbacks; bool mChangingState; bool mForceReapplyProperties; bool mDisableAnimations; @@ -175,7 +183,18 @@ class EE_API UIStyle : public UIState { void removeRelatedWidgets(); void applyStyleSheetProperty( const CSS::StyleSheetProperty& property, - std::shared_ptr prevDefinition ); + std::shared_ptr prevDefinition, + bool captureFallback = true ); + + void capturePropertyFallback( const CSS::StyleSheetProperty& property ); + + bool isTransientProperty( CSS::PropertyId propertyId ) const; + + bool restorePropertyFallbacks( CSS::PropertyId propertyId, + std::shared_ptr prevDefinition, + Uint32 firstIndex = 0 ); + + void clearPropertyFallbacks( CSS::PropertyId propertyId ); void updateAnimationsPlayState(); diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp index 2f1ae39ba..1b4bae7bb 100644 --- a/include/eepp/ui/uitreeview.hpp +++ b/include/eepp/ui/uitreeview.hpp @@ -64,6 +64,8 @@ class EE_API UITreeView : public UIAbstractTableView { void collapseAll( const ModelIndex& index = {} ); + virtual void selectAll(); + UIIcon* getExpandIcon() const; void setExpandedIcon( UIIcon* expandIcon ); diff --git a/src/eepp/ui/uihtmltable.cpp b/src/eepp/ui/uihtmltable.cpp index 7335c7c85..6b30f5d5c 100644 --- a/src/eepp/ui/uihtmltable.cpp +++ b/src/eepp/ui/uihtmltable.cpp @@ -31,7 +31,7 @@ std::vector UIHTMLTable::getPropertiesImplemented() const { } std::string UIHTMLTable::getPropertyString( const PropertyDefinition* propertyDef, - const Uint32& state ) const { + const Uint32& propertyIndex ) const { if ( NULL == propertyDef ) return ""; @@ -55,7 +55,7 @@ std::string UIHTMLTable::getPropertyString( const PropertyDefinition* propertyDe case PropertyId::TableLayout: return mTopEq; default: - return UIHTMLWidget::getPropertyString( propertyDef ); + return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex ); } } @@ -223,7 +223,7 @@ std::vector UIHTMLTableCell::getPropertiesImplemented() const { } std::string UIHTMLTableCell::getPropertyString( const PropertyDefinition* propertyDef, - const Uint32& state ) const { + const Uint32& propertyIndex ) const { if ( NULL == propertyDef ) return ""; @@ -231,7 +231,7 @@ std::string UIHTMLTableCell::getPropertyString( const PropertyDefinition* proper case PropertyId::ColSpan: return String::format( "%lld", mColSpan ); default: - return UIHTMLWidget::getPropertyString( propertyDef ); + return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex ); } } diff --git a/src/eepp/ui/uihtmlwidget.cpp b/src/eepp/ui/uihtmlwidget.cpp index 1be3fd929..bfcf31737 100644 --- a/src/eepp/ui/uihtmlwidget.cpp +++ b/src/eepp/ui/uihtmlwidget.cpp @@ -1191,7 +1191,7 @@ std::vector UIHTMLWidget::getPropertiesImplemented() const { } std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyDef, - const Uint32& state ) const { + const Uint32& propertyIndex ) const { if ( NULL == propertyDef ) return ""; @@ -1271,7 +1271,7 @@ std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyD case PropertyId::JustifySelf: return CSSJustifySelfHelper::toString( getJustifySelf() ); default: - return UILayout::getPropertyString( propertyDef ); + return UILayout::getPropertyString( propertyDef, propertyIndex ); } } diff --git a/src/eepp/ui/uistyle.cpp b/src/eepp/ui/uistyle.cpp index 9d42241fd..0e060c56a 100644 --- a/src/eepp/ui/uistyle.cpp +++ b/src/eepp/ui/uistyle.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -463,6 +464,9 @@ void UIStyle::onStateChange() { if ( nullptr == property || NULL == property->getPropertyDefinition() ) { const auto def = StyleSheetSpecification::instance()->getProperty( prop ); + if ( restorePropertyFallbacks( prop, prevDefinition ) ) { + continue; + } if ( def && def->isInherited() ) { UIStyle* inheritedStyle = nullptr; StyleSheetProperty* inheritedProp = @@ -504,12 +508,30 @@ void UIStyle::onStateChange() { } } } else { + const bool transient = isTransientProperty( prop ); if ( property->getPropertyDefinition()->isIndexed() ) { - for ( size_t i = 0; i < property->getPropertyIndexCount(); i++ ) { - applyStyleSheetProperty( property->getPropertyIndex( i ), prevDefinition ); + // Capture every layer before applying any of them. An earlier layer can + // change the widget's indexed backing storage. + if ( transient ) { + for ( size_t i = 0; i < property->getPropertyIndexCount(); i++ ) { + capturePropertyFallback( property->getPropertyIndex( i ) ); + } } + for ( size_t i = 0; i < property->getPropertyIndexCount(); i++ ) { + applyStyleSheetProperty( property->getPropertyIndex( i ), prevDefinition, + false ); + } + // A shorter transient declaration can leave higher indexes without a + // winner even while another state remains active. + restorePropertyFallbacks( + prop, prevDefinition, + static_cast( property->getPropertyIndexCount() ) ); + if ( !transient ) + clearPropertyFallbacks( prop ); } else { applyStyleSheetProperty( *property, prevDefinition ); + if ( !transient ) + clearPropertyFallbacks( prop ); } if ( property->getPropertyDefinition()->isInherited() ) @@ -543,26 +565,122 @@ UIStyle::getStatelessStyleSheetProperty( const PropertyId& propertyId ) const { if ( propertyId == PropertyId::Invalid ) return nullptr; - if ( !mElementStyle->getSelector().hasPseudoClasses() ) { - const StyleSheetProperty* property = mElementStyle->getPropertyById( propertyId ); - - if ( property ) - return property; - } + const StyleSheetProperty* winner = !mElementStyle->getSelector().hasPseudoClasses() + ? mElementStyle->getPropertyById( propertyId ) + : nullptr; if ( nullptr == mDefinition ) - return nullptr; + return winner; for ( auto style : mDefinition->getStyles() ) { if ( style->getSelector().isCacheable() && !style->getSelector().hasPseudoClasses() ) { const StyleSheetProperty* property = style->getPropertyById( propertyId ); - - if ( property ) - return property; + if ( property && ( !winner || property->getSpecificity() >= winner->getSpecificity() ) ) + winner = property; } } - return nullptr; + return winner; +} + +void UIStyle::capturePropertyFallback( const StyleSheetProperty& property ) { + const PropertyDefinition* definition = property.getPropertyDefinition(); + if ( nullptr == definition ) + return; + const StyleSheetProperty* stateless = + getStatelessStyleSheetProperty( property.getPropertyId() ); + if ( stateless && + ( !definition->isIndexed() || property.getIndex() < stateless->getPropertyIndexCount() ) ) + return; + + for ( const auto& fallback : mPropertyFallbacks ) { + if ( fallback.propertyId == property.getPropertyId() && + fallback.index == property.getIndex() ) + return; + } + + const bool inherited = + definition->isInherited() && getInheritedProperty( property.getPropertyId() ); + // Keep a serialized value in case the inherited declaration disappears before rollback. + std::string value = mWidget->getPropertyString( definition, property.getIndex() ); + if ( inherited || !value.empty() ) { + mPropertyFallbacks.push_back( { property.getPropertyId(), property.getIndex(), definition, + std::move( value ), inherited } ); + } +} + +bool UIStyle::isTransientProperty( PropertyId propertyId ) const { + const StyleSheetProperty* local = + mElementStyle ? mElementStyle->getPropertyById( propertyId ) : nullptr; + const StyleSheetProperty* winner = + mDefinition ? mDefinition->getProperty( propertyId ) : nullptr; + if ( nullptr == winner || ( local && local->getSpecificity() >= winner->getSpecificity() ) ) + return false; + if ( winner->isVolatile() ) + return true; + if ( mCurrentState == UIState::StateFlagNormal ) + return false; + + const StyleSheetProperty* sourceProperty = nullptr; + const StyleSheetStyle* sourceStyle = nullptr; + for ( const auto* style : mDefinition->getStyles() ) { + const StyleSheetProperty* candidate = style->getPropertyById( propertyId ); + if ( candidate && ( !sourceProperty || + candidate->getSpecificity() >= sourceProperty->getSpecificity() ) ) { + sourceProperty = candidate; + sourceStyle = style; + } + } + return sourceStyle && sourceStyle->getSelector().hasPseudoClasses(); +} + +bool UIStyle::restorePropertyFallbacks( PropertyId propertyId, + std::shared_ptr prevDefinition, + Uint32 firstIndex ) { + bool restored = false; + for ( const auto& fallback : mPropertyFallbacks ) { + if ( fallback.propertyId != propertyId || fallback.index < firstIndex ) + continue; + + if ( fallback.inherited ) { + UIStyle* ownerStyle = nullptr; + StyleSheetProperty* inherited = getInheritedProperty( propertyId, &ownerStyle ); + if ( inherited ) { + auto resolved = ownerStyle->resolveProperty( inherited ); + StyleSheetProperty value( *resolved.get() ); + value.setVolatile( false ); + applyStyleSheetProperty( value, prevDefinition, false ); + mWidget->propagateInheritedProperty( value ); + } else if ( !fallback.nativeValue.empty() ) { + StyleSheetProperty value( fallback.definition, fallback.nativeValue, + fallback.index ); + applyStyleSheetProperty( value, prevDefinition, false ); + mWidget->propagateInheritedProperty( value ); + } + } else { + StyleSheetProperty value( fallback.definition, fallback.nativeValue, fallback.index ); + applyStyleSheetProperty( value, prevDefinition, false ); + } + restored = true; + } + if ( restored ) { + mPropertyFallbacks.erase( + std::remove_if( mPropertyFallbacks.begin(), mPropertyFallbacks.end(), + [propertyId, firstIndex]( const PropertyFallback& fallback ) { + return fallback.propertyId == propertyId && + fallback.index >= firstIndex; + } ), + mPropertyFallbacks.end() ); + } + return restored; +} + +void UIStyle::clearPropertyFallbacks( PropertyId propertyId ) { + mPropertyFallbacks.erase( std::remove_if( mPropertyFallbacks.begin(), mPropertyFallbacks.end(), + [propertyId]( const PropertyFallback& fallback ) { + return fallback.propertyId == propertyId; + } ), + mPropertyFallbacks.end() ); } void UIStyle::updateState() { @@ -628,26 +746,15 @@ void UIStyle::removeRelatedWidgets() { } void UIStyle::applyStyleSheetProperty( const StyleSheetProperty& originalProperty, - std::shared_ptr prevDefinition ) { + std::shared_ptr prevDefinition, + bool captureFallback ) { auto resolvedProperty = resolveProperty( &originalProperty ); const StyleSheetProperty* property = resolvedProperty.get(); const PropertyDefinition* propertyDefinition = property->getPropertyDefinition(); - // Save default value if possible and not available. - if ( mCurrentState != UIState::StateFlagNormal || - ( mCurrentState == UIState::StateFlagNormal && property->isVolatile() ) ) { - const StyleSheetProperty* oldAttribute = - getStatelessStyleSheetProperty( property->getPropertyId() ); - if ( nullptr == oldAttribute && getPreviousState() == UIState::StateFlagNormal ) { - std::string value( - mWidget->getPropertyString( propertyDefinition, property->getIndex() ) ); - if ( !value.empty() ) { - setStyleSheetProperty( StyleSheetProperty( propertyDefinition, value, - property->getIndex(), true, true ) ); - } - } - } + if ( captureFallback && isTransientProperty( property->getPropertyId() ) ) + capturePropertyFallback( *property ); if ( !mDisableAnimations && !mFirstState && !mWidget->isSceneNodeLoading() && NULL != propertyDefinition && diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 6af0af80e..18375242c 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -94,6 +94,18 @@ void UITreeView::traverseTree( TreeViewCallback callback ) const { } } +void UITreeView::selectAll() { + if ( !getModel() ) + return; + std::vector indexes; + indexes.reserve( getItemCount() ); + traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& ) { + indexes.push_back( index ); + return IterationDecision::Continue; + } ); + getSelection().set( indexes ); +} + void UITreeView::createOrUpdateColumns( bool resetColumnData ) { if ( !getModel() ) { updateContentSize(); diff --git a/src/tests/unit_tests/eproc_process_tree_tests.cpp b/src/tests/unit_tests/eproc_process_tree_tests.cpp index 7bb34f80b..5a17cf804 100644 --- a/src/tests/unit_tests/eproc_process_tree_tests.cpp +++ b/src/tests/unit_tests/eproc_process_tree_tests.cpp @@ -57,8 +57,20 @@ UTEST( EProcProcessTree, ParentAndChildIndexesFollowPids ) { view->setPixelsSize( 500, 200 ); view->setModel( tree ); EXPECT_TRUE( view->getHeaderColumn( ProcessModel::ColMemory ) != nullptr ); + view->setSelectionKind( UIAbstractView::SelectionKind::Multiple ); + view->selectAll(); + EXPECT_EQ( view->getSelection().size(), 2 ); + EXPECT_TRUE( view->getSelection().contains( init ) ); + EXPECT_FALSE( view->getSelection().contains( child ) ); view->setExpanded( init, true ); + view->selectAll(); + EXPECT_EQ( view->getSelection().size(), 3 ); + EXPECT_TRUE( view->getSelection().contains( child ) ); + EXPECT_FALSE( view->getSelection().contains( grandchild ) ); view->setExpanded( child, true ); + view->selectAll(); + EXPECT_EQ( view->getSelection().size(), 4 ); + EXPECT_TRUE( view->getSelection().contains( grandchild ) ); EXPECT_TRUE( view->isExpanded( init ) ); EXPECT_TRUE( view->isExpanded( child ) ); } diff --git a/src/tests/unit_tests/uicss_inheritance_tests.cpp b/src/tests/unit_tests/uicss_inheritance_tests.cpp index 63b8c63dd..8231932c4 100644 --- a/src/tests/unit_tests/uicss_inheritance_tests.cpp +++ b/src/tests/unit_tests/uicss_inheritance_tests.cpp @@ -724,6 +724,405 @@ UTEST( CSSVariables, ParentStateChildOpacityReverts ) { EXPECT_EQ( child->getAlpha(), 0.f ); } +UTEST( CSSVariables, NativeFallbackSurvivesMultipleStates ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Native Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + EXPECT_EQ( child->getAlpha(), 255.f ); + for ( int cycle = 0; cycle < 32; ++cycle ) { + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + child->pushState( UIState::StatePressed ); + EXPECT_EQ( child->getAlpha(), 63.75f ); + child->popState( UIState::StatePressed ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 255.f ); + EXPECT_FALSE( child->getUIStyle()->hasLocalProperty( PropertyId::Opacity ) ); + } +} + +UTEST( CSSVariables, NativeFallbackFirstAppearsOutsideNormalState ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Late Native Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + const Color nativeBackground = child->getBackgroundColor(); + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + child->pushState( UIState::StatePressed ); + EXPECT_TRUE( child->getBackgroundColor() == Color::Blue ); + child->popState( UIState::StatePressed ); + EXPECT_TRUE( child->getBackgroundColor() == nativeBackground ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 255.f ); +} + +UTEST( CSSVariables, InlineLowerWinnerSurvivesStateRollback ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Inline State Rollback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + EXPECT_EQ( child->getAlpha(), 191.25f ); + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 191.25f ); + EXPECT_TRUE( child->getUIStyle()->hasLocalProperty( PropertyId::Opacity ) ); +} + +UTEST( CSSVariables, VariableLowerWinnerUpdatesWhileOverridden ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Variable State Rollback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + EXPECT_EQ( child->getAlpha(), 191.25f ); + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + app.getUI()->combineStyleSheet( ":root { --base-opacity: 0.25; }" ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 63.75f ); +} + +UTEST( CSSVariables, LightDarkLowerWinnerUpdatesWhileOverridden ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Light Dark State Rollback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UISceneNode* sceneNode = app.getUI(); + sceneNode->setColorSchemePreference( ColorSchemePreference::Light ); + UIWidget* root = sceneNode->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + EXPECT_EQ( child->getAlpha(), 191.25f ); + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + sceneNode->setColorSchemePreference( ColorSchemePreference::Dark ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 63.75f ); +} + +UTEST( CSSVariables, StylesheetReloadUsesNewLowerWinner ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Reload State Rollback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UISceneNode* sceneNode = app.getUI(); + sceneNode->setStyleSheet( R"css( + #child { opacity: 0.75; } + #child:selected { opacity: 0.5; } + )css" ); + UIWidget* child = UIWidget::New(); + child->setId( "child" ); + child->setParent( sceneNode->getRoot() ); + sceneNode->update( Time::Zero ); + EXPECT_EQ( child->getAlpha(), 191.25f ); + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + sceneNode->setStyleSheet( R"css( + #child { opacity: 0.25; } + #child:selected { opacity: 0.5; } + )css" ); + sceneNode->update( Time::Zero ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 63.75f ); +} + +UTEST( CSSVariables, NativeFallbackSurvivesLowerRuleReloads ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Reload Native Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UISceneNode* sceneNode = app.getUI(); + sceneNode->setStyleSheet( "#child:selected { opacity: 0.5; }" ); + UIWidget* child = UIWidget::New(); + child->setId( "child" ); + child->setParent( sceneNode->getRoot() ); + sceneNode->update( Time::Zero ); + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + sceneNode->setStyleSheet( "#child { opacity: 0.25; } #child:selected { opacity: 0.5; }" ); + sceneNode->update( Time::Zero ); + sceneNode->setStyleSheet( "#child:selected { opacity: 0.5; }" ); + sceneNode->update( Time::Zero ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 255.f ); +} + +UTEST( CSSVariables, InheritedFallbackUsesCurrentParentValue ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Inherited Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
Text
+ + )html" ); + UIWidget* parent = root->querySelector( "#parent" ); + UITextSpan* child = root->querySelector( "#child" )->asType(); + EXPECT_TRUE( parent != nullptr ); + EXPECT_TRUE( child != nullptr ); + EXPECT_TRUE( child->getFontColor() == Color::White ); + child->pushState( UIState::StateSelected ); + EXPECT_TRUE( child->getFontColor() == Color::Red ); + parent->setStyleSheetInlineProperty( "color", "#00ff00" ); + parent->reloadStyle( true, false, true, true ); + child->popState( UIState::StateSelected ); + EXPECT_TRUE( child->getFontColor() == Color( "#00ff00" ) ); +} + +UTEST( CSSVariables, InheritedFallbackRestoresWhenSourceDisappears ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Inherited Source Removal Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UISceneNode* sceneNode = app.getUI(); + sceneNode->setStyleSheet( "#parent { color: white; } #child:selected { color: red; }" ); + UIWidget* root = sceneNode->loadLayoutFromString( + "
Text
" ); + UITextSpan* child = root->querySelector( "#child" )->asType(); + EXPECT_TRUE( child != nullptr ); + EXPECT_TRUE( child->getFontColor() == Color::White ); + child->pushState( UIState::StateSelected ); + EXPECT_TRUE( child->getFontColor() == Color::Red ); + sceneNode->setStyleSheet( "#child:selected { color: red; }" ); + sceneNode->update( Time::Zero ); + child->popState( UIState::StateSelected ); + EXPECT_TRUE( child->getFontColor() == Color::White ); +} + +UTEST( CSSVariables, VolatileNativeFallbackRestores ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Volatile Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* parent = root->querySelector( "#parent" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( parent != nullptr ); + EXPECT_TRUE( child != nullptr ); + EXPECT_EQ( child->getAlpha(), 255.f ); + parent->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + parent->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 255.f ); +} + +UTEST( CSSVariables, SiblingStateNativeFallbackRestores ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Sibling Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* source = root->querySelector( "#source" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( source != nullptr ); + EXPECT_TRUE( child != nullptr ); + EXPECT_EQ( child->getAlpha(), 255.f ); + source->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + source->popState( UIState::StateSelected ); + EXPECT_EQ( child->getAlpha(), 255.f ); +} + +UTEST( CSSVariables, IndexedNativeFallbackRestoresEachLayer ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Indexed Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + child->setBackgroundTint( Color( "#112233" ), 0 ); + child->setBackgroundTint( Color( "#445566" ), 1 ); + EXPECT_STDSTREQ( "#112233", child->getBackgroundTint( 0 ).toHexString() ); + EXPECT_STDSTREQ( "#445566", child->getBackgroundTint( 1 ).toHexString() ); + for ( int cycle = 0; cycle < 8; ++cycle ) { + child->pushState( UIState::StateSelected ); + EXPECT_TRUE( child->getBackgroundTint( 0 ) == Color::Red ); + EXPECT_TRUE( child->getBackgroundTint( 1 ) == Color::Blue ); + child->popState( UIState::StateSelected ); + EXPECT_TRUE( child->getBackgroundTint( 0 ) == Color( "#112233" ) ); + const std::string secondTint = child->getBackgroundTint( 1 ).toHexString(); + EXPECT_STDSTREQ( Color( "#445566" ).toHexString(), secondTint ); + EXPECT_FALSE( child->getUIStyle()->hasLocalProperty( PropertyId::BackgroundTint ) ); + } +} + +UTEST( CSSVariables, IndexedFallbackRestoresLayerWhileAnotherStateRemains ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Indexed State Rollback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + const Color originalTint( "#445566" ); + child->setBackgroundTint( originalTint, 1 ); + child->pushState( UIState::StateSelected ); + EXPECT_TRUE( child->getBackgroundTint( 1 ) == Color::Blue ); + child->pushState( UIState::StatePressed ); + child->popState( UIState::StateSelected ); + EXPECT_TRUE( child->getBackgroundTint( 0 ) == Color::Green ); + EXPECT_TRUE( child->getBackgroundTint( 1 ) == originalTint ); + child->popState( UIState::StatePressed ); + EXPECT_TRUE( child->getBackgroundTint( 1 ) == originalTint ); +} + +UTEST( CSSVariables, IndexedFallbackRestoresUncoveredLayer ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Partial Indexed Fallback Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + child->setBackgroundTint( Color( "#445566" ), 1 ); + child->pushState( UIState::StateSelected ); + EXPECT_TRUE( child->getBackgroundTint( 0 ) == Color::Red ); + EXPECT_TRUE( child->getBackgroundTint( 1 ) == Color::Blue ); + child->popState( UIState::StateSelected ); + EXPECT_TRUE( child->getBackgroundTint( 0 ) == Color( "#112233" ) ); + EXPECT_TRUE( child->getBackgroundTint( 1 ) == Color( "#445566" ) ); +} + +UTEST( CSSVariables, HTMLTableIndexedPropertyGetterForwardsIndex ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Table Indexed Getter Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIWidget* root = app.getUI()->loadLayoutFromString( R"html( +
Text
+ )html" ); + const PropertyDefinition* tint = + StyleSheetSpecification::instance()->getProperty( PropertyId::BackgroundTint ); + for ( const char* selector : { "#table", "#cell" } ) { + UIWidget* widget = root->querySelector( selector ); + EXPECT_TRUE( widget != nullptr ); + widget->setBackgroundTint( Color( "#112233" ), 0 ); + widget->setBackgroundTint( Color( "#445566" ), 1 ); + EXPECT_STDSTREQ( "#445566", widget->getPropertyString( tint, 1 ) ); + } +} + +UTEST( CSSVariables, NativeFallbackTransitionsInBothDirections ) { + UIApplication app( + WindowSettings( 800, 600, "eepp - CSS Native Fallback Transition Test", + WindowStyle::Default, WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UISceneNode* sceneNode = app.getUI(); + UIWidget* root = sceneNode->loadLayoutFromString( R"html( + + +
+
+ )html" ); + UIWidget* child = root->querySelector( "#child" ); + EXPECT_TRUE( child != nullptr ); + const PropertyDefinition* opacity = + StyleSheetSpecification::instance()->getProperty( PropertyId::Opacity ); + child->pushState( UIState::StateSelected ); + EXPECT_EQ( child->getActionsByTag( opacity->getId() ).size(), 1UL ); + sceneNode->update( Seconds( 0.2f ) ); + EXPECT_EQ( child->getAlpha(), 127.5f ); + child->popState( UIState::StateSelected ); + EXPECT_EQ( child->getActionsByTag( opacity->getId() ).size(), 1UL ); + sceneNode->update( Seconds( 0.2f ) ); + EXPECT_EQ( child->getAlpha(), 255.f ); +} + UTEST( CSSVariables, ParentStateChildOpacityTransitionReverts ) { UIApplication app( WindowSettings( 800, 600, "eepp - CSS Parent State Child Opacity Transition Test", diff --git a/src/tools/eproc/eproc.cpp b/src/tools/eproc/eproc.cpp index f5c27d039..85afd36a1 100644 --- a/src/tools/eproc/eproc.cpp +++ b/src/tools/eproc/eproc.cpp @@ -1,4 +1,5 @@ #include "eproc.hpp" +#include "process_view.hpp" #include "settingspanel.hpp" #include @@ -6,6 +7,7 @@ #include #include #include +#include #include #include @@ -604,6 +606,8 @@ bool App::init() { } ); } + UIWidgetCreator::registerWidget( "eproc-table-view", ProcessTableView::New ); + UIWidgetCreator::registerWidget( "eproc-tree-view", ProcessTreeView::New ); mRoot = ui->loadLayoutFromString( R"xml(