chapter_3.md 5.16 KiB
Chapter 3 - Creating custom widgets!
Kayak UI allows users to create custom widgets.
Widgets are structured in a few different ways:
- Widgets at a bare minimum must include a Props component, Widget Bundle, and the update and render systems.
- Widgets can include custom components and data but the default
widget_update
system only supports diffing of props and state. - Widgets have some base components which are auto checked these are: KStyles and KChildren.
I think it's best if we showcase a simple example:
// At a bare minimum the widget props component must include these derives.
// This is because we need to diff the previous values of these.
// Default is used to make creating widgets a little easier.
// And component is required since this is a bevy component!
#[derive(Component, Clone, PartialEq, Default)]
pub struct MyButtonProps {
}
// In the future this will tell Kayak that these Props belongs to a widget.
// For now it's use to get the `WidgetName` component.
impl Widget for MyButtonProps { }
// Now we need a widget bundle this can represent a collection of components our widget might have
// Note: You can include custom data here. Just don't expect it to get diffed during update!
#[derive(Bundle)]
pub struct MyButtonBundle {
pub props: MyButtonProps,
pub styles: KStyle,
pub computed_styles: ComputedStyles,
pub children: KChildren,
// This allows us to hook into on click events!
pub on_event: OnEvent,
// Widget name is required by Kayak UI!
pub widget_name: WidgetName,
}
impl Default for MyButtonBundle {
fn default() -> Self {
Self {
props: MyButtonProps::default(),
styles: KStyle::default(),
computed_styles: ComputedStyles::default(),
children: KChildren::default(),
on_event: OnEvent::default(),
// Kayak uses this component to find out more information about your widget.
// This is done because bevy does not have the ability to query traits.
widget_name: MyButtonProps::default().get_name(),
}
}
}
// Now we need to create our systems.
// Since our button doesn't have any custom data we can diff using the default widget_update system.
// We do need to create a render system!
pub fn my_button_render(
// This is a bevy feature which allows custom parameters to be passed into a system.
// In this case Kayak UI gives the system an `Entity`.
In(entity): In<Entity>,
// This struct allows us to make changes to the widget tree.
mut widget_context: ResMut<KayakWidgetContext>,
// The rest of the parameters are just like those found in a bevy system!
// In fact you can add whatever you would like here including more queries or lookups
// to resources within bevy's ECS.
mut commands: Commands,
// In this case we really only care about our buttons children! Let's query for them.
mut query: Query<&KChildren>,
) -> bool {
// Grab our children for our button widget:
if let Ok(children) = query.get(entity) {
let background_styles = KStyle {
// Lets use red for our button background!
background_color: StyleProp::Value(Color::RED),
// 50 pixel border radius.
border_radius: Corner::all(50.0).into(),
..Default::default()
};
let parent_id = Some(entity);
rsx! {
<BackgroundBundle
styles={background_styles}
// We pass the children to the background bundle!
children={children.clone()}
/>
};
}
// The boolean returned here tells kayak UI to update the tree. You can avoid tree updates by
// returning false, but in practice this should be done rarely. As kayak diff's the tree and
// will avoid tree updates if nothing has changed!
true
}
// Finally we need to let the core widget context know about our new widget!
fn startup(...) {
// Default kayak startup stuff.
...
// We need to register the prop and state types.
// State is empty so you can use the `EmptyState` component!
widget_context.add_widget_data::<MyButtonProps, EmptyState>();
// Next we need to add the systems
widget_context.add_widget_system(
// We are registering these systems with a specific WidgetName.
MyButtonProps::default().get_name(),
// widget_update auto diffs props and state.
// Optionally if you have context you can use: widget_update_with_context
// otherwise you will need to create your own widget update system!
widget_update::<MyButtonProps, EmptyState>,
// Add our render system!
my_button_render,
);
// We can now create our widget like:
rsx! {
<KayakAppBundle>
<MyButtonBundle>
<TextWidgetBundle
text={TextProps {
content: "Click me!".into(),
..Default::default()
}}
/>
</MyButtonBundle>
</KayakAppBundle>
}
}