StarArawn / kayak_ui

Other
469 stars 50 forks source link

re-export KayakFont for anyone using bevy_asset_loader? #189

Closed Hellzbellz123 closed 1 year ago

Hellzbellz123 commented 1 year ago

when using the git version of kayak_ui, cargo doesnt seem to want to play nice with pulling kayak_font from github if i have it in my cargo.toml, however bevy assetloader needs the type in order to function correctly.

re-exporting the KayakFont would be helpful for anyone with this use case but i dont think anyone else would use it otherwise.

Hellzbellz123 commented 1 year ago

one other "gripe" i have is that before the rewrite all kayak components where contained under a single entity, this made it much more difficult too despawn the ui or control visiblity. i havent noticed how to control visiblity with the new examples or in the book, though i may have missed/not understood it

StarArawn commented 1 year ago

one other "gripe" i have is that before the rewrite all kayak components where contained under a single entity, this made it much more difficult too despawn the ui or control visiblity. i havent noticed how to control visiblity with the new examples or in the book, though i may have missed/not understood it

I think visibility will be handled with some sort of visibility enum on the styles struct. Likely with three values: Display, Hidden, None. Hidden would allow the widget to still affect the layout.

Hellzbellz123 commented 1 year ago

that sounds fantastic, currently im managing visibility with a global State and having buttons push/pop/set MenuState with if statements encapsulating the rsx! macro. the only problem is it seems to cause weird layout issues, but that could just be me not understanding what im doing. one of the next things to tackle is a slider bar for settings,

the nice thing about the whole ui being under a single entity was being able to use despawn recursive and keeping entity lists human readable, note below my EnemyContainer and MapContainer entitys, i dont know if thats doable with the new rewrite but if not a better way to despawn entitys in the context,

my current solution is to use the below system, but this makes it strange to say despawn the menu ui widgets and and only have the ingame ui entities spawned. if this is not a good approach i would love to hear others.

pub fn despawn_ui(
    mut commands: Commands,
    to_despawn: Query<Entity, With<CameraUIKayak>>,
    widts: Query<Entity, With<KStyle>>,
) {
    for entity in to_despawn.iter() {
        info!("despawning kayak_root_context: {:#?}", entity);
        commands.entity(entity).despawn_recursive();
        for widget in widts.iter() {
            commands.entity(widget).despawn_recursive();
        }
    }
}

image

StarArawn commented 1 year ago

Okay so I recently struggled with this in my own game. I found a few issues around diffing. Diffing widgets is hard when they have almost no changes! I made a few poor assumptions around that. I'm hoping I can fix those by implementing a better diffing strategy but I need to think about it more.

Here is a working example widget(from my game), you'll need to use the bugfixes branch and use the new context system. It's a bit broken of a branch at the moment(none of the examples work), but I should be wrapping it up and merging it soon.

#[derive(Component, Default, Clone, PartialEq, Eq)]
pub struct GameApp;

impl Widget for GameApp {}

#[derive(Bundle)]
pub struct GameAppBundle {
    pub app: GameApp,
    pub styles: KStyle,
    pub computed_styles: ComputedStyles,
    pub widget_name: WidgetName,
}

impl Default for GameAppBundle {
    fn default() -> Self {
        Self {
            app: Default::default(),
            styles: Default::default(),
            computed_styles: ComputedStyles::default(),
            widget_name: GameApp::default().get_name(),
        }
    }
}

pub fn update(
    In((widget_context, entity, previous_props_entity)): In<(KayakWidgetContext, Entity, Entity)>,
    mut prev_state: Local<GameState>,
    widget_param: WidgetParam<KayakApp, EmptyState>,
    game_state: Res<CurrentState<GameState>>,
    mut commands: Commands,
) -> bool {
    // Diffing fails here because to the context it looks like nothing changed until the children start to render.
    // Diffing needs to start looking a bit deeper to determine if the children of a widget have changed. 
    // And to figure out if a widget has gone from A to B.
    // As a work around we can tell the tree that we actually have an entirely new tree.
    if game_state.is_changed() && game_state.0 != *prev_state {
        *prev_state = game_state.0;
        for child in widget_context.get_children(entity).iter() {
            // Despawns entity data without losing track of entity id's 
            // and removes the widgets from the tree.
            // depsawn_safe can be found in the `bugfixes` branch, but the name will likely change in the future.
            // As it's not actually unsafe rust code...
            widget_context.despawn_safe(&mut commands, *child);
        }
    }

    widget_param.has_changed(&widget_context, entity, previous_props_entity)
        || game_state.is_changed()
}

pub fn render(
    In((widget_context, entity)): In<(KayakWidgetContext, Entity)>,
    mut commands: Commands,
    mut query: Query<(&KStyle, &mut ComputedStyles)>,
    game_state: Res<CurrentState<GameState>>,
) -> bool {
    if let Ok((app_style, mut computed_styles)) = query.get_mut(entity) {
        *computed_styles = KStyle::default()
            .with_style(KStyle {
                render_command: RenderCommand::Layout.into(),
                ..Default::default()
            })
            .with_style(app_style)
            .into();

        let parent_id = Some(entity);
        rsx! {
            <ElementBundle>
                {
                    match game_state.0 {
                        GameState::Generation | GameState::GenerateEditor => {
                            constructor! {
                                <GenerationWidgetBundle />
                            }
                        },
                        GameState::Playing => {
                            constructor! {
                                <ElementBundle>
                                    <TopBarBundle />
                                    <FpsWidgetBundle
                                        styles={KStyle {
                                            position_type: KPositionType::SelfDirected.into(),
                                            top: Units::Pixels(5.0).into(),
                                            right: Units::Pixels(5.0).into(),
                                            ..Default::default()
                                        }}
                                    />
                                </ElementBundle>
                            }
                        },
                    }
                }
            </ElementBundle>
        };
    }

    true
}

And how to spawn a context now:

    // Note: None of this is required except for the `CameraUiKayak` part.
    let camera_entity = commands.spawn(Camera2dBundle {
        projection: OrthographicProjection {
            far: 1000.0,
            scale: 0.5,
            ..Default::default()
        },
        camera: Camera {
            hdr: true,
            ..Camera::default()
        },
        tonemapping: Tonemapping::Enabled { deband_dither: true },
        ..Camera2dBundle::default()
    }).insert(CameraUIKayak::default()).id();

    let mut widget_context = KayakRootContext::new(camera_entity);
    widget_context.add_plugin(KayakWidgetsContextPlugin);
    widget_context.add_plugin(ui::GameUIPluign);

    let parent_id = None;
    rsx! {
        <KayakAppBundle>
            <ui::app::GameAppBundle />
        </KayakAppBundle>
    };

    commands.spawn((widget_context, EventDispatcher::default()));