afonsolage / bevy_ecss

Bevy crate which uses a subset of CSS to update Bevy ECS components
Apache License 2.0
98 stars 11 forks source link

My StyleSheets aren't cascading to new children #61

Open Zeenobit opened 4 months ago

Zeenobit commented 4 months ago

I'm trying to use bevy_ecss in my project, but for some reason my stylesheets aren't cascading to child nodes that are added after the initial UI setup.

I have some entities that spawn in the world, and each entities is associated with a named button. All buttons have the exact same name: ControlButton and are added to a parent node named ControlButtonContainer.

The system which adds the buttons on entity spawn runs in Update schedule. The systems which spawns the button container (for which the cascading does seem to work) run in Startup schedule.

In my CSS, I have:

#ControlButtonContainer {
    flex-direction: row;
    align-self: flex-start;
    width: 100%;
    padding: 0 8px 0px 8px; /* Left Top Right Bottom */
    background-color: yellow;
}

#ControlButton {
    width: 64px;
    height: 64px;
    margin: 0px 0px 0px 8px; /* Top Right Bottom Left */
    background-color: purple;
}

For some reason the ControlButtonContainer gets the correct style. But ControlButton never gets the correct style.

I can work around the issue by just adding the style sheet to every ControlButton. This is what makes me think the issue is from the library and not my code.

Is there something I'm missing?

I also can't seem to get hot reload to work either (changing the CSS file and saving doesn't do anything). I'm also not sure why I'm getting inconsistent ordering between padding and margin in my CSS file (which I figured through pure trial and error). I'm not sure if these issues are related!

This is how I'm initializing the plugin:

app.add_plugins(EcssPlugin::with_hot_reload());

Any help would be appreciated!

Zeenobit commented 4 months ago

I can also get it to work by manually refreshing the stylesheet on the container entity every time a button is added:

let mut style_sheet = style_sheet_query.get_mut(container_entity).unwrap();
style_sheet.refresh();

Am I just missing a setting that should do this automatically, or is this the intended usage?

afonsolage commented 4 months ago

This is a limitation on current implementation, since if we tracked all entities insertion/deletion on the whole tree, this could be very expensive. I tried to do that, but the FPS hit was very noticeable, so I removed.

When observers get merged (probably 0.14), I'll implement this hierarchy change detection again and with observers, this should be fine, performance-wise.

Until there, I'll leave the issue open, to track this problem.

Xenira commented 3 months ago

Ran into this as well. Ended up doing something like this as I only have one stylesheet:

fn refresh_css(
    q_node_added: Query<Entity, Added<Style>>,
    mut q_ui_root: Query<&mut StyleSheet, With<UiRoot>>,
) {
    for _ in q_node_added.iter() {
        for mut style in &mut q_ui_root {
            style.refresh();
        }
    }
}
Zeenobit commented 3 months ago

Could the problem be simplified by introducing a concept of "Global Stylesheets" to the crate?

If we assume most users would have a single global stylesheet, or a flat stack of global stylesheets, maybe this could be defined as a resource. Then any time a new node is added, it gets all the global stylesheets in order of the stack.

The hierarchical node-based stylesheets could then be implemented as the next iteration.

I think even with hierarchical stylesheets, the idea of global stylesheets would still be useful.

Just a suggestion!

Zeenobit commented 3 months ago

Also, as it stands, it doesn't seem like the styles get updated based on style sheet rules either. I tried adding classes to an element, and it doesn't update the style to match the selector (i.e. I had #ControlButton .selected defined in CSS, but adding the "selected" class doesn't update the node to match the selected style).

Is this the same problem? Do I need to manually refresh the stylesheet when adding classes?

Xenira commented 3 months ago

From what I can see those are only updated on StyleSheet change:

https://github.com/afonsolage/bevy_ecss/blob/8633b54f9a4b498ac2e736e8b6a63813fec7bf37/src/system.rs#L59

Only thing that is reacting to change are pseudoclasses, as those have all entities as MatchedEntities and select FilteredEntities based on Interaction.

Xenira commented 3 months ago

You could try the update system with Query<Entity, Or<(Added<Style>, Added<Class>, Changed<Class>)> while the change detection is not working. (untested)

Zeenobit commented 3 months ago

So I've managed to work around most of my issues by essentially having a root style sheet and refreshing it manually when a new node is added as @Xenira suggested.

This doesn't seem to work for removed nodes though. When a control button is removed, my application crashes in bevy_ecss:

fn any_component<T: Component>(world: &World, entities: &SmallVec<[Entity; 8]>) -> bool {
    // ...
    for e in entities {
        if let Some(ticks) = world.entity(*e).get_change_ticks::<T>() { // !!! CRASH: Entity doesn't exist
            // ...
        }
    }
    false
}

I tried to work around this by listening to nodes being removed, and modifying my refresh system as such:

fn refresh_style(
    added: Query<(), Added<Node>>,
    removed: RemovedComponents<Node>,
    mut style_sheet: Query<&mut StyleSheet>,
) {
    let root_entity: Entity = ...;
    if !added.is_empty() || !removed.is_empty() {
        style_sheet.get_mut(root_entity).unwrap().refresh();
    }
}

But this doesn't seem to resolve the crash.

Is there anything I can do to "untrack" an entity when it's removed?

Edit: Solved. It was an ordering issue with my refresh_style system. Moving it to run after my UI logic is updated solved the problem.