viridia / quill

A reactive UI framework for Bevy
Apache License 2.0
125 stars 12 forks source link

Dynamic optional view #28

Open keis opened 2 months ago

keis commented 2 months ago

Being able to use Option<View> as a view is very convenient but does not allow for the state Some/None to change. Being able to dynamically change this would be really useful.

I've been able to emulate something like this by stitching Cond together with a helper Unwrap ViewTemplate. So looks pretty doable to me but I've yet to fully wrap my head around the state tracking in quill works.

keis commented 2 months ago

For reference my hacky solution right now looks like this

Cond::new(inner.is_some(), Unwrap(inner), ())

And the create of that Unwrap wrapper of Option looks like

fn create(&self, cx: &mut Cx) -> Self ::View {
    self.0.as_ref().unwrap().create(cx)
}
viridia commented 2 months ago

I've run into this issue as well, and have resorted to similar ugly workarounds.

The basic problem is that normally, view states (that is, the associated type View::State) don't have runtime type identification, so dynamically changing the view type (in this case from Some<View> to None) means that the State type no longer corresponds to the view.

The cleanest solution, for now, is to wrap the optional view in Dynamic. What Dynamic does is wrap the State in a boxed Any. It also keeps around a copy of the previously-rendered view. When the view is rebuilt, it does a type check to determine if the stored state still matches the View::State type. If it doesn't, then the we raze and rebuild the view.

The downside is that this has an extra cost of boxing and type checking. For Quill, because the view hierarchy is re-created every update, it's designed to be cheap - it's just filling in slots in a giant tuple. For bevy_reactor, OTOH, I took a different approach because the view hierarchy is only built once, so there's less pressure to try and optimize the building of views.