From a090d1a666a2a8c46ebe0bfc6d44aaa2ea35c6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Tue, 29 Sep 2026 14:56:01 -0300 Subject: [PATCH] Fix CSS state rollback for transient properties. eproc: add keyboard navigation between search and process views, fix Send Signal menu events. Store native and inherited fallback values outside local styles, then restore them when state rules stop matching. Handle indexed properties across overlapping states and retain a serialized value when an inherited source disappears. Add rollback regression tests and update the CSS documentation Focus and select the first process row when pressing Down in the search input. Pressing Up from the first row returns focus to search in both table and tree views. --- docs/articles/css_specification.md | 13 +- docs/articles/ui_css_for_applications.md | 6 +- include/eepp/ui/uihtmltable.hpp | 4 +- include/eepp/ui/uihtmlwidget.hpp | 2 +- include/eepp/ui/uistyle.hpp | 21 +- include/eepp/ui/uitreeview.hpp | 2 + src/eepp/ui/uihtmltable.cpp | 8 +- src/eepp/ui/uihtmlwidget.cpp | 4 +- src/eepp/ui/uistyle.cpp | 163 +++++-- src/eepp/ui/uitreeview.cpp | 12 + .../unit_tests/eproc_process_tree_tests.cpp | 12 + .../unit_tests/uicss_inheritance_tests.cpp | 399 ++++++++++++++++++ src/tools/eproc/eproc.cpp | 95 +++-- src/tools/eproc/process_view.hpp | 59 +++ 14 files changed, 718 insertions(+), 82 deletions(-) create mode 100644 src/tools/eproc/process_view.hpp 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(