viridia / quill

A reactive UI framework for Bevy
Apache License 2.0
106 stars 13 forks source link

How to pass a font asset handle to a style builder? #29

Open musjj opened 2 weeks ago

musjj commented 2 weeks ago

I'm trying to pass a handle:

#[derive(Clone, PartialEq)]
pub struct MyUi;

impl ViewTemplate for MyUi {
    type View = impl View;

    fn create(&self, cx: &mut Cx) -> Self::View {
        let font_assets = cx.use_resource::<MyFontAssets>();
        let font = font_assets.comic_sans.clone();

        Element::<NodeBundle>::new().style(move |ss: &mut StyleBuilder| {
            ss.font(font);
        })
    }
}

But it doesn't work because the style builder function must be Fn:

expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
required for `{closure@src/my_ui.rs:26:44: 26:72}` to implement `bevy_mod_stylebuilder::StyleTuple`

Is there a way to make font handles work with quill?

viridia commented 2 weeks ago

Hmmm, not sure why this doesn't work. That being said, there's a few things you can do right now:

1) Use style_dyn, and pass in the font handle as an explicit dependency.

2) Instead of passing in a handle, ss.font() should be able to accept the asset path string directly, e.g. .font("embedded://bevy_quill_obsidian/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf").

musjj commented 2 weeks ago

Thanks, but I ended up inserting InheritableFontStyles at the root node instead:

Element::<NodeBundle>::new()
    .insert(InheritableFontStyles {
        ...
    })

Use style_dyn, and pass in the font handle as an explicit dependency.

I'll try this if I ever need to override the font on a specific node.

Instead of passing in a handle, ss.font() should be able to accept the asset path string directly, e.g. .font("embedded://bevy_quill_obsidian/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf").

This won't work well for me, because I want to have the font fully loaded before the game starts (I'm using bevy_asset_loader)

EDIT: It looks like even when passing it as dep with style_dyn you still need to call .clone() on the resource or you'll get a lifetime error.

To clarify, this is what my resource looks like:

#[derive(AssetCollection, Clone, Resource, PartialEq, PartialOrd, Eq)]
pub struct MyAssets {
    #[asset(path = "foobar.png")]
    pub foobar: Handle<Image>,
    // ...and tens of other asset fields
}

I wonder if there's a more efficient approach that avoids cloning.

musjj commented 2 weeks ago

Ok, I figured it out:

#[derive(Clone, PartialEq)]
pub struct MyUi;

impl ViewTemplate for MyUi {
    type View = impl View;

    fn create(&self, cx: &mut Cx) -> Self::View {
        let font_assets = cx.use_resource::<MyFontAssets>();
        let font = font_assets.comic_sans.clone();

        Element::<NodeBundle>::new().style(move |ss: &mut StyleBuilder| {
            ss.font(font.clone()); // <- you need to clone it so that the closure becomes `Fn`
                                   // See: https://doc.rust-lang.org/std/keyword.move.html
        })
    }
}

It seems that this is more of a Rust issue, so I think I'll close this. But before that, would it be possible for .style() and other callbacks to be a FnOnce instead? Do they need to be called multiple times?

viridia commented 2 weeks ago

FnOnce: Certainly worth investigating.