diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7585238efedfc33acdd9494b0269951aaf3909ec --- /dev/null +++ b/book/.gitignore @@ -0,0 +1 @@ +book diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000000000000000000000000000000000000..e646d36a259fe2c9cecbb511644af3b6c2036931 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["StarToaster"] +language = "en" +multilingual = false +src = "src" +title = "Kayak UI" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000000000000000000000000000000000000..16791208130baea37c2506d8644e16220c831ca9 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,8 @@ +# Kayak UI + +- [Introduction](./introduction.md) +- [Chapter 1 - Installing and hello world!](./chapter_1.md) +- [Chapter 2 - Creating custom widgets!](./chapter_2.md) +- [Chapter 3 - State](./chapter_3.md) +- [Chapter 4 - Context](./chapter_4.md) +- [Chapter 5 - Fonts](./chapter_5.md) \ No newline at end of file diff --git a/book/src/chapter_1.md b/book/src/chapter_1.md new file mode 100644 index 0000000000000000000000000000000000000000..9fb64493b412847356d2f519620ca5fcc06f5faa --- /dev/null +++ b/book/src/chapter_1.md @@ -0,0 +1,124 @@ +# Chapter 1 - Installing and hello world! +Kayak UI is quite easy to setup! First make sure you add it to your cargo.toml file in your project. + +Because a crate has yet to be released this currently this looks like: +```toml +kayak_ui = { git = "https://github.com/StarArawn/kayak_ui/", rev = "75a56767830f980bfc43f29fe93659f1eaef30dd"} +``` + +Once you've added Kayak UI to your bevy project you can now start to use it! + +Hello World Example: +```rust +use bevy::prelude::*; +use kayak_ui::prelude::{widgets::*, *}; + +fn startup( + mut commands: Commands, + mut font_mapping: ResMut<FontMapping>, + asset_server: Res<AssetServer>, +) { + font_mapping.set_default(asset_server.load("roboto.kayak_font")); + + commands.spawn(UICameraBundle::new()); + + let mut widget_context = KayakRootContext::new(); + let parent_id = None; + + // The rsx! macro expects a parent_id, a widget_context from the user. + // It also expects `Commands` from bevy. + // This can be a little weird at first. + // See the rsx! docs for more info! + rsx! { + <KayakAppBundle> + <TextWidgetBundle + text={TextProps { + content: "Hello World".into(), + ..Default::default() + }} + /> + </KayakAppBundle> + } + commands.insert_resource(widget_context); +} + +fn main() { + App::new() + .insert_resource(ImageSettings::default_nearest()) + .add_plugins(DefaultPlugins) + .add_plugin(KayakContextPlugin) + .add_plugin(KayakWidgets) + .add_startup_system(startup) + .run() +} +``` + +## Wait where is the ECS? +Kayak UI encourages the use of our proc macro called `rsx!`. This proc macro simulates XML like syntax and turns it into bevy commands. This proc macro is completely optional though! + +Here is the same hello world example but this time we'll use bevy's ECS directly. + +```rust +use bevy::prelude::*; +use kayak_ui::prelude::{widgets::*, *}; +fn startup( + mut commands: Commands, + mut font_mapping: ResMut<FontMapping>, + asset_server: Res<AssetServer>, +) { + font_mapping.set_default(asset_server.load("roboto.kayak_font")); + commands.spawn(UICameraBundle::new()); + let mut widget_context = KayakRootContext::new(); + + let pre_existing_app_entity = widget_context.get_child_at(None); + let app_entity = if let Some(entity) = pre_existing_app_entity { + commands.get_or_spawn(entity).id() + } else { + commands.spawn_empty().id() + }; + + // Create default app bundle + let mut app_bundle = KayakAppBundle { + ..Default::default() + }; + + // Create app's children + let mut children = KChildren::new(); + + // Create the text child + let pre_existing_text_entity = widget_context.get_child_at(Some(app_entity)); + let text_entity = if let Some(entity) = pre_existing_text_entity { + commands.get_or_spawn(entity).id() + } else { + commands.spawn_empty().id() + }; + commands.entity(text_entity).insert(TextWidgetBundle { + text: TextProps { + content: "Hello World".into(), + ..Default::default() + }, + ..Default::default() + }); + // Add the text as a child of the App Widget. + children.add(text_entity); + + // Finalize app bundle and add to entity. + app_bundle.children = children; + commands.entity(app_entity).insert(app_bundle); + + // Add app widget to context. + widget_context.add_widget(None, app_entity); + + // Add widget context as resource. + commands.insert_resource(widget_context); +} +fn main() { + App::new() + .insert_resource(ImageSettings::default_nearest()) + .add_plugins(DefaultPlugins) + .add_plugin(KayakContextPlugin) + .add_plugin(KayakWidgets) + .add_startup_system(startup) + .run() +} +``` diff --git a/book/src/chapter_2.md b/book/src/chapter_2.md new file mode 100644 index 0000000000000000000000000000000000000000..256b25e6f0143cbd5a2727e284674e05054ed77c --- /dev/null +++ b/book/src/chapter_2.md @@ -0,0 +1,127 @@ +# Chapter 2 - Creating custom widgets! +Kayak UI allows users to create custom widgets. + +Widgets are structured in a few different ways: +1. Widgets at a bare minimum must include a Props component, Widget Bundle, and the update and render systems. +2. Widgets can include custom components and data but the default `widget_update` system only supports diffing of props and state. +3. 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: +```rust +// 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 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(), + 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 a `KayakWidgetContext` and an `Entity`. + In((mut widget_context, entity)): In<(KayakWidgetContext, Entity)>, + // 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>, +) { + // 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() + }; + + rsx! { + <BackgroundBundle + // 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! + context.add_widget_data::<MyButtonProps, EmptyState>(); + + // Next we need to add the systems + 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> + } +} +``` diff --git a/book/src/chapter_3.md b/book/src/chapter_3.md new file mode 100644 index 0000000000000000000000000000000000000000..e3248f6db508853675fdeb558a8f47fb635802e7 --- /dev/null +++ b/book/src/chapter_3.md @@ -0,0 +1 @@ +# Chapter 3 - State diff --git a/book/src/chapter_4.md b/book/src/chapter_4.md new file mode 100644 index 0000000000000000000000000000000000000000..e7ee8143b5191f03a621e6f7ee0062f63afe94a8 --- /dev/null +++ b/book/src/chapter_4.md @@ -0,0 +1 @@ +# Chapter 4 - Context diff --git a/book/src/chapter_5.md b/book/src/chapter_5.md new file mode 100644 index 0000000000000000000000000000000000000000..7581bf697f5edd856ac7505462b387f6e460cf04 --- /dev/null +++ b/book/src/chapter_5.md @@ -0,0 +1,17 @@ +# Chapter 5 - Fonts +Kayak UI uses SDF(signed distance fields) for rendering fonts. More specifically it uses multi-channel signed distance fields. Reasons for why we use MSDF: +- High Quality font rendering. +- Fast rendering! +- No need for a new asset for each font size. MSDF's can size to any font size! + +Font's are stored as an atlased image and a json file which tells Kayak about the font glyphs. Check out `roboto.kayak_font` and `roboto.png` in the `assets` folder. + +## Generating new fonts. +In order to create a new font you need to use the `msdf-atlas-gen` tool. This can be found at: +[https://github.com/Chlumsky/msdf-atlas-gen](https://github.com/Chlumsky/msdf-atlas-gen) + +Please refer to the documentation found in the link about for generating fonts. However a simple way is to use the following command line: + +``` +.\msdf-atlas-gen.exe -font .\my_font.ttf -type msdf -minsize 32 -format png -imageout my_font.png -json my_font.kayak_font +``` \ No newline at end of file diff --git a/book/src/introduction.md b/book/src/introduction.md new file mode 100644 index 0000000000000000000000000000000000000000..b6813a6eca4e790b2e595f4548504b66b27918f7 --- /dev/null +++ b/book/src/introduction.md @@ -0,0 +1,37 @@ +# Introduction + +Kayak UI is a declarative UI that can be used to make user interfaces in Rust primarily targeting games. It's free and open-source! + +## Features +- Full integration into Bevy ECS. +- Easy to use declarative syntax using a custom proc macro +- Basic widget, global, and context state management +- Input events (Mouse, Keyboard, Char) +- Fast and accurate layouts using morphorm: https://github.com/geom3trik/morphorm +- A few default widgets (check out Kayak's [built-in widgets](https://github.com/StarArawn/kayak_ui/tree/main/src/widgets)!) +- Style system built to kind of mimic CSS styles. +- Image and Nine patch rendering. +- Widget and Tree diff based updating + + +## This book +In this book you'll be learning: +- How to use widgets. +- How to create custom widgets. +- Accessing bevy data from a widget. +- Handling widget updates. + +And more! + +## License +Copyright © 2021-2022 John Mitchell. + +All code in the book is provided under the MIT-0 License. At your option, you may also use it under the regular MIT License. + +The text of the book is provided under the CC BY-NC-SA 4.0. + +## Contributions +Development of this book is hosted on GitHub. + +Please file GitHub Issues for any wrong/confusing/misleading information, as well as suggestions for new content you'd like to be added to the book. + diff --git a/examples/clipping.rs b/examples/clipping.rs index d6e2ba6ebde7b504dfbf93a9971eeda5fbd88038..86d30277efa2d6ba06aa87a9cb8296ad942fb969 100644 --- a/examples/clipping.rs +++ b/examples/clipping.rs @@ -15,7 +15,7 @@ fn startup( let image = asset_server.load("kenny/panel_brown.png"); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; let nine_patch_styles = KStyle { @@ -66,7 +66,7 @@ fn main() { App::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/context.rs b/examples/context.rs index adde9d08646a3691c76dd6b194485b637e2e8abe..3e26f1f3ec43205006a8a67298a262a7a5bde980 100644 --- a/examples/context.rs +++ b/examples/context.rs @@ -76,7 +76,7 @@ impl Default for ThemeButtonBundle { } fn update_theme_button( - In((widget_context, theme_button_entity)): In<(WidgetContext, Entity)>, + In((widget_context, theme_button_entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, query: Query<&ThemeButton>, mut context_query: Query<&mut Theme>, @@ -164,7 +164,7 @@ impl Default for ThemeSelectorBundle { } fn update_theme_selector( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, query: Query<&ThemeSelector>, ) -> bool { @@ -219,7 +219,7 @@ impl Default for ThemeDemoBundle { } fn update_theme_demo( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut query_set: Query<&mut ThemeDemo>, theme_context: Query<&Theme>, @@ -347,7 +347,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); widget_context.add_widget_data::<ThemeDemo, EmptyState>(); widget_context.add_widget_data::<ThemeButton, EmptyState>(); widget_context.add_widget_data::<ThemeSelector, EmptyState>(); @@ -398,7 +398,7 @@ fn main() { BevyApp::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/demo.rs b/examples/demo.rs index 8c704f1addf4430045b1740eb78c2d2b9730c9de..0f49c39f8275b97135974f8f54b56876c6c7186f 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -7,7 +7,7 @@ pub struct MyWidget { } fn my_widget_1_render( - In((_widget_context, entity)): In<(WidgetContext, Entity)>, + In((_widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut _commands: Commands, query: Query<&MyWidget>, ) -> bool { @@ -21,7 +21,7 @@ fn my_widget_1_render( impl Widget for MyWidget {} fn startup(mut commands: Commands) { - let mut context = Context::new(); + let mut context = KayakRootContext::new(); context.add_widget_system( MyWidget::default().get_name(), widget_update::<MyWidget, EmptyState>, @@ -65,7 +65,7 @@ fn update_resource( fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .add_system(update_resource) diff --git a/examples/hello_world.rs b/examples/hello_world.rs new file mode 100644 index 0000000000000000000000000000000000000000..b4269ab36499098921b1273eaafa695b5c92cf8e --- /dev/null +++ b/examples/hello_world.rs @@ -0,0 +1,36 @@ +use bevy::prelude::*; +use kayak_ui::prelude::{widgets::*, *}; + +fn startup( + mut commands: Commands, + mut font_mapping: ResMut<FontMapping>, + asset_server: Res<AssetServer>, +) { + font_mapping.set_default(asset_server.load("roboto.kayak_font")); + + commands.spawn(UICameraBundle::new()); + + let mut widget_context = KayakRootContext::new(); + let parent_id = None; + rsx! { + <KayakAppBundle> + <TextWidgetBundle + text={TextProps { + content: "Hello World".into(), + ..Default::default() + }} + /> + </KayakAppBundle> + } + commands.insert_resource(widget_context); +} + +fn main() { + App::new() + .insert_resource(ImageSettings::default_nearest()) + .add_plugins(DefaultPlugins) + .add_plugin(KayakContextPlugin) + .add_plugin(KayakWidgets) + .add_startup_system(startup) + .run() +} diff --git a/examples/hello_world_no_macro.rs b/examples/hello_world_no_macro.rs new file mode 100644 index 0000000000000000000000000000000000000000..7f951a46e7ce7103893ccef06791c642f9206930 --- /dev/null +++ b/examples/hello_world_no_macro.rs @@ -0,0 +1,62 @@ +use bevy::prelude::*; +use kayak_ui::prelude::{widgets::*, *}; +fn startup( + mut commands: Commands, + mut font_mapping: ResMut<FontMapping>, + asset_server: Res<AssetServer>, +) { + font_mapping.set_default(asset_server.load("roboto.kayak_font")); + commands.spawn(UICameraBundle::new()); + let mut widget_context = KayakRootContext::new(); + + let pre_existing_app_entity = widget_context.get_child_at(None); + let app_entity = if let Some(entity) = pre_existing_app_entity { + commands.get_or_spawn(entity).id() + } else { + commands.spawn_empty().id() + }; + + // Create default app bundle + let mut app_bundle = KayakAppBundle { + ..Default::default() + }; + + // Create app's children + let mut children = KChildren::new(); + + // Create the text child + let pre_existing_text_entity = widget_context.get_child_at(Some(app_entity)); + let text_entity = if let Some(entity) = pre_existing_text_entity { + commands.get_or_spawn(entity).id() + } else { + commands.spawn_empty().id() + }; + commands.entity(text_entity).insert(TextWidgetBundle { + text: TextProps { + content: "Hello World".into(), + ..Default::default() + }, + ..Default::default() + }); + // Add the text as a child of the App Widget. + children.add(text_entity); + + // Finalize app bundle and add to entity. + app_bundle.children = children; + commands.entity(app_entity).insert(app_bundle); + + // Add app widget to context. + widget_context.add_widget(None, app_entity); + + // Add widget context as resource. + commands.insert_resource(widget_context); +} +fn main() { + App::new() + .insert_resource(ImageSettings::default_nearest()) + .add_plugins(DefaultPlugins) + .add_plugin(KayakContextPlugin) + .add_plugin(KayakWidgets) + .add_startup_system(startup) + .run() +} diff --git a/examples/image.rs b/examples/image.rs index 7e626bcafe303ef86ae4c0452c1f025ba2f3d94c..7b89333a1333e55a9c999dd50caa3d524499f32b 100644 --- a/examples/image.rs +++ b/examples/image.rs @@ -15,7 +15,7 @@ fn startup( let image = asset_server.load("generic-rpg-vendor.png"); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; rsx! { <KayakAppBundle> @@ -40,7 +40,7 @@ fn main() { BevyApp::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/nine_patch.rs b/examples/nine_patch.rs index 14b4c19fb32182c2979de7528e8914a6a3283f00..f2558ddb5469aa67aeb5046d28b099f864c1673c 100644 --- a/examples/nine_patch.rs +++ b/examples/nine_patch.rs @@ -15,7 +15,7 @@ fn startup( let image = asset_server.load("panel.png"); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; // The border prop splits up the image into 9 quadrants like so: @@ -62,7 +62,7 @@ fn main() { BevyApp::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/quads.rs b/examples/quads.rs index 0d9e7c415c80563e5bc7bea15a76aa6163db945b..4bd8422b2735b5e91eff512f4b0fe0e21e4fc7e6 100644 --- a/examples/quads.rs +++ b/examples/quads.rs @@ -16,7 +16,7 @@ pub struct MyQuad { } fn my_quad_update( - In((_widget_context, entity)): In<(WidgetContext, Entity)>, + In((_widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut query: Query<(&MyQuad, &mut KStyle, &mut OnEvent)>, ) -> bool { if let Ok((quad, mut style, mut on_event)) = query.get_mut(entity) { @@ -89,7 +89,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); widget_context.add_widget_system( MyQuad::default().get_name(), widget_update::<MyQuad, EmptyState>, @@ -130,7 +130,7 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugin(LogDiagnosticsPlugin::default()) .add_plugin(FrameTimeDiagnosticsPlugin::default()) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/scrolling.rs b/examples/scrolling.rs index 028845ab1996b76941dde0cf11b9309a414a06bb..3c0498236bfb17dd0e04b65c063449981708c8ed 100644 --- a/examples/scrolling.rs +++ b/examples/scrolling.rs @@ -15,7 +15,7 @@ fn startup( let image = asset_server.load("kenny/panel_brown.png"); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; let nine_patch_styles = KStyle { @@ -68,7 +68,7 @@ fn main() { App::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/simple_state.rs b/examples/simple_state.rs index c32fc25b266a56a3b3a8ace5a0978c032076d667..bbae0446c66a9b52c0c6fd078d462284536890fd 100644 --- a/examples/simple_state.rs +++ b/examples/simple_state.rs @@ -35,7 +35,7 @@ impl Default for CurrentCountBundle { } fn current_count_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, query: Query<&CurrentCountState>, ) -> bool { @@ -71,7 +71,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; widget_context.add_widget_data::<CurrentCount, CurrentCountState>(); widget_context.add_widget_system( @@ -127,7 +127,7 @@ fn startup( fn main() { BevyApp::new() .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/tabs/tab.rs b/examples/tabs/tab.rs index 9367d6126ebf39af796e6ce83ef335b77ebfa236..4088274e824e777cf26cb2c2fcfaa35f73832e5f 100644 --- a/examples/tabs/tab.rs +++ b/examples/tabs/tab.rs @@ -1,7 +1,7 @@ use bevy::prelude::{Bundle, Color, Commands, Component, Entity, In, Query}; use kayak_ui::prelude::{ - rsx, widgets::BackgroundBundle, Edge, KChildren, KStyle, StyleProp, Units, Widget, - WidgetContext, WidgetName, + rsx, widgets::BackgroundBundle, Edge, KChildren, KStyle, KayakWidgetContext, StyleProp, Units, + Widget, WidgetName, }; use crate::tab_context::TabContext; @@ -37,7 +37,7 @@ impl Default for TabBundle { } pub fn tab_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, query: Query<(&KChildren, &Tab)>, tab_context_query: Query<&TabContext>, diff --git a/examples/tabs/tab_button.rs b/examples/tabs/tab_button.rs index 017cf70ced8f2016ca673789ada58cf8d7faf663..1e10a418a96ef771c8fbe6ebfa19fbd0db96ab26 100644 --- a/examples/tabs/tab_button.rs +++ b/examples/tabs/tab_button.rs @@ -2,8 +2,8 @@ use bevy::prelude::{Bundle, Color, Commands, Component, Entity, In, Query}; use kayak_ui::prelude::{ rsx, widgets::{KButtonBundle, TextProps, TextWidgetBundle}, - Event, EventDispatcherContext, EventType, KChildren, KStyle, OnEvent, StyleProp, Units, Widget, - WidgetContext, WidgetName, WidgetState, + Event, EventDispatcherContext, EventType, KChildren, KStyle, KayakWidgetContext, OnEvent, + StyleProp, Units, Widget, WidgetName, WidgetState, }; use crate::tab_context::TabContext; @@ -34,7 +34,7 @@ impl Default for TabButtonBundle { } pub fn tab_button_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, query: Query<&TabButton>, tab_context_query: Query<&mut TabContext>, diff --git a/examples/tabs/tab_context.rs b/examples/tabs/tab_context.rs index 671bb5ec2167a1c9100973b65e45298e6dde9425..2c6e5f2af2d2a8f477b15b6c4c69d0c27ab6287b 100644 --- a/examples/tabs/tab_context.rs +++ b/examples/tabs/tab_context.rs @@ -35,7 +35,7 @@ impl Default for TabContextProviderBundle { } pub fn tab_context_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, query: Query<(&KChildren, &TabContextProvider)>, ) -> bool { diff --git a/examples/tabs/tabs.rs b/examples/tabs/tabs.rs index 64f4096425d5a3d99de12d68871b4fb4f02a8e53..d0827ced5c1130e6272a2081448cb6fd4ca9902f 100644 --- a/examples/tabs/tabs.rs +++ b/examples/tabs/tabs.rs @@ -19,7 +19,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); widget_context.add_widget_data::<Tab, EmptyState>(); widget_context.add_widget_data::<TabContextProvider, EmptyState>(); widget_context.add_widget_data::<TabButton, EmptyState>(); @@ -91,7 +91,7 @@ fn main() { App::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/text.rs b/examples/text.rs index bcd828245c599bac98d5714ca3fc6301226f1637..b7d90b3d3c91efea7f74fcf0fb40a0a9465d719c 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -13,7 +13,7 @@ pub struct MyWidgetProps { } fn my_widget_1_update( - In((_widget_context, entity)): In<(WidgetContext, Entity)>, + In((_widget_context, entity)): In<(KayakWidgetContext, Entity)>, my_resource: Res<MyResource>, mut query: Query<(&mut MyWidgetProps, &mut KStyle)>, ) -> bool { @@ -63,7 +63,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; widget_context.add_widget_data::<MyWidgetProps, EmptyState>(); widget_context.add_widget_system( @@ -86,7 +86,7 @@ fn update_resource(keyboard_input: Res<Input<KeyCode>>, mut my_resource: ResMut< fn main() { BevyApp::new() .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .insert_resource(MyResource(1)) .add_startup_system(startup) diff --git a/examples/text_box.rs b/examples/text_box.rs index c5ad6b1a38545c3b3f928d64191a24b3e2ffc2ec..707d43da49baa888cfc62d2ac45ac8c6d1e8275a 100644 --- a/examples/text_box.rs +++ b/examples/text_box.rs @@ -36,7 +36,7 @@ impl Default for TextBoxExampleBundle { } fn update_text_box_example( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, state_query: Query<&TextBoxExampleState>, ) -> bool { @@ -51,7 +51,7 @@ fn update_text_box_example( if let Ok(textbox_state) = state_query.get(state_entity) { let on_change = OnChange::new( - move |In((_widget_context, _, value)): In<(WidgetContext, Entity, String)>, + move |In((_widget_context, _, value)): In<(KayakWidgetContext, Entity, String)>, mut state_query: Query<&mut TextBoxExampleState>| { if let Ok(mut state) = state_query.get_mut(state_entity) { state.value1 = value; @@ -60,7 +60,7 @@ fn update_text_box_example( ); let on_change2 = OnChange::new( - move |In((_widget_context, _, value)): In<(WidgetContext, Entity, String)>, + move |In((_widget_context, _, value)): In<(KayakWidgetContext, Entity, String)>, mut state_query: Query<&mut TextBoxExampleState>| { if let Ok(mut state) = state_query.get_mut(state_entity) { state.value2 = value; @@ -98,7 +98,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); widget_context.add_widget_data::<TextBoxExample, TextBoxExampleState>(); widget_context.add_widget_system( @@ -128,7 +128,7 @@ fn startup( fn main() { BevyApp::new() .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/texture_atlas.rs b/examples/texture_atlas.rs index 16a4754983bf3e416e06ad0a92acb48ef51b6e67..b609a879ebc0d456cda7f2455a92c018b47abead 100644 --- a/examples/texture_atlas.rs +++ b/examples/texture_atlas.rs @@ -33,7 +33,7 @@ fn startup( //The flower is in the 6(-1) row and 15 collumn let flower_index = columns * 5 + 15; - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; let atlas_styles = KStyle { @@ -79,7 +79,7 @@ fn main() { BevyApp::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/todo/input.rs b/examples/todo/input.rs index 8687c848514f86df6512e24bf3ba2a6aaa67fe51..487632b62f3589c86d1b4c7e534b42db2641a3f5 100644 --- a/examples/todo/input.rs +++ b/examples/todo/input.rs @@ -37,7 +37,7 @@ impl Default for TodoInputBundle { } pub fn render_todo_input( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut todo_list: ResMut<TodoList>, keyboard_input: Res<Input<KeyCode>>, @@ -47,7 +47,7 @@ pub fn render_todo_input( if todo_list.is_changed() || !change_query.is_empty() { if let Ok(props) = prop_query.get(entity) { let on_change = OnChange::new( - move |In((_widget_context, _, value)): In<(WidgetContext, Entity, String)>, + move |In((_widget_context, _, value)): In<(KayakWidgetContext, Entity, String)>, mut todo_list: ResMut<TodoList>| { todo_list.new_item = value; }, diff --git a/examples/todo/items.rs b/examples/todo/items.rs index fc839e032f056e69fe20f83221aea142437ff9a5..900eee4c2cbefcfaca09d565d1326b34fa8d7e2a 100644 --- a/examples/todo/items.rs +++ b/examples/todo/items.rs @@ -31,7 +31,7 @@ impl Default for TodoItemsBundle { } pub fn render_todo_items( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, todo_list: Res<TodoList>, query: Query<&TodoItemsProps, Or<(Changed<Style>, Changed<TodoItemsProps>, With<Mounted>)>>, diff --git a/examples/todo/todo.rs b/examples/todo/todo.rs index bd5ca718b65e6e24d8f621a01577d9d99e30eb94..25481cf48f8c49a1b10d245e2334d0c2d55eb937 100644 --- a/examples/todo/todo.rs +++ b/examples/todo/todo.rs @@ -34,7 +34,7 @@ pub fn widget_update_with_resource< Props: PartialEq + Component + Clone, State: PartialEq + Component + Clone, >( - In((widget_context, entity, previous_entity)): In<(WidgetContext, Entity, Entity)>, + In((widget_context, entity, previous_entity)): In<(KayakWidgetContext, Entity, Entity)>, todo_list: Res<TodoList>, widget_param: WidgetParam<Props, State>, ) -> bool { @@ -50,7 +50,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); widget_context.add_widget_data::<TodoItemsProps, EmptyState>(); widget_context.add_widget_data::<TodoInputProps, EmptyState>(); @@ -94,7 +94,7 @@ fn startup( fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .insert_non_send_resource(TodoList::new()) .add_startup_system(startup) diff --git a/examples/vec.rs b/examples/vec.rs index bb6df14d52c3ec583496bb13fb2b5ad5fd28950c..fa25565a4e060e5f73ebea999b41fa227a5127da 100644 --- a/examples/vec.rs +++ b/examples/vec.rs @@ -11,7 +11,7 @@ use kayak_ui::prelude::{widgets::*, KStyle, *}; pub struct MyWidgetProps {} fn my_widget_1_update( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, query: Query<Entity, Or<(With<Mounted>, Changed<MyWidgetProps>)>>, ) -> bool { @@ -69,7 +69,7 @@ fn startup( commands.spawn(UICameraBundle::new()); - let mut widget_context = Context::new(); + let mut widget_context = KayakRootContext::new(); let parent_id = None; widget_context.add_widget_data::<MyWidgetProps, EmptyState>(); widget_context.add_widget_system( @@ -86,7 +86,7 @@ fn startup( fn main() { BevyApp::new() .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) + .add_plugin(KayakContextPlugin) .add_plugin(KayakWidgets) .add_startup_system(startup) .run() diff --git a/examples/widget_template.rs b/examples/widget_template.rs index fad397b97df8b659a288c91ce02dff095af80a85..5743a9c557b9e274e9219789ae54156c3155078d 100644 --- a/examples/widget_template.rs +++ b/examples/widget_template.rs @@ -34,7 +34,7 @@ impl Default for WidgetBundle { } pub fn update_widget( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, _: Commands, mut query: Query< (&Style, &KChildren), diff --git a/kayak_ui_macros/src/lib.rs b/kayak_ui_macros/src/lib.rs index 67394c139d9b85e56a368dd92659fb688c603cd6..accdfac600e5fa4f64a19571ecd5c0d8451dbce7 100644 --- a/kayak_ui_macros/src/lib.rs +++ b/kayak_ui_macros/src/lib.rs @@ -15,6 +15,16 @@ pub(crate) mod widget_builder; /// A proc macro that turns RSX syntax into structure constructors and calls the /// context to create the widgets. +/// Note: This macro expects the following: +/// +/// `parent_id`: Variable in scope. Typed as: `Option<Entity>`. This should always +/// be None for the root node or `Some(parent_entity)` for any other widget +/// +/// `widget_context`: Variable in scope. Can either be a `WidgetRootContext` or a `WidgetContext`. +/// This is used to automatically add widgets to the tree when parsing the XML syntax into code. +/// +/// `commands`: Variable in scope. Must be a variable to a mutable bevy `Commands`. This is used to +/// spawn entities and insert bundles onto entities automatically after parsing the XML syntax to code. #[proc_macro] #[proc_macro_error] pub fn rsx(input: TokenStream) -> TokenStream { diff --git a/src/calculate_nodes.rs b/src/calculate_nodes.rs index 02234d9c3c4c2149632454b85668cf7e83b86fb9..593c86edf60875bacd6026eed8589e5680b772aa 100644 --- a/src/calculate_nodes.rs +++ b/src/calculate_nodes.rs @@ -7,7 +7,7 @@ use kayak_font::KayakFont; use crate::{ layout::{DataCache, Rect}, node::{DirtyNode, Node, NodeBuilder, WrappedIndex}, - prelude::{Context, KStyle}, + prelude::{KStyle, KayakRootContext}, render::font::FontMapping, render_primitive::RenderPrimitive, styles::{StyleProp, Units}, @@ -15,7 +15,7 @@ use crate::{ pub fn calculate_nodes( mut commands: Commands, - mut context: ResMut<Context>, + mut context: ResMut<KayakRootContext>, fonts: Res<Assets<KayakFont>>, font_mapping: Res<FontMapping>, query: Query<Entity, With<DirtyNode>>, @@ -159,7 +159,7 @@ pub fn calculate_nodes( fn create_primitive( commands: &mut Commands, - context: &Context, + context: &KayakRootContext, fonts: &Assets<KayakFont>, font_mapping: &FontMapping, // query: &Query<(Entity, &Node)>, diff --git a/src/camera/camera.rs b/src/camera/camera.rs index 197976c9df01e3f3a35079ca0b3357be1c71c51b..99a13402e4b2ef55524e4780a2dd768e46312634 100644 --- a/src/camera/camera.rs +++ b/src/camera/camera.rs @@ -11,6 +11,7 @@ use bevy::{ use super::ortho::UIOrthographicProjection; +/// Kayak UI's default UI camera. #[derive(Component, Clone, Default)] pub struct CameraUiKayak; @@ -23,6 +24,7 @@ impl ExtractComponent for CameraUiKayak { } } +/// Kayak UI's default UI camera bundle. #[derive(Bundle)] pub struct UICameraBundle { pub camera: Camera, diff --git a/src/camera/ortho.rs b/src/camera/ortho.rs index 9d8e717847c3117664d70eae058fdf5c183150aa..bb7a800f9c75ac5010c0f9ee193d6fc8dbd578b4 100644 --- a/src/camera/ortho.rs +++ b/src/camera/ortho.rs @@ -6,6 +6,10 @@ use bevy::{ render::camera::{CameraProjection, ScalingMode, WindowOrigin}, }; +/// Kayak UI's default orthographic projection matrix +/// This matrix uses top left as 0, 0 +/// and bottom right as width, height. +/// This projection layout is typical for most UI systems. #[derive(Debug, Clone, Component, Reflect)] #[reflect(Component)] pub struct UIOrthographicProjection { diff --git a/src/children.rs b/src/children.rs index 01166946f2059ce0ac98811046908caf4f574318..810be98b807207929765b14109c9687f04edd810 100644 --- a/src/children.rs +++ b/src/children.rs @@ -1,6 +1,6 @@ use bevy::prelude::*; -use crate::prelude::WidgetContext; +use crate::prelude::KayakWidgetContext; /// Defers widgets being added to the widget tree. #[derive(Component, Debug, Default, Clone, PartialEq)] @@ -37,7 +37,7 @@ impl KChildren { } /// Processes all widgets and adds them to the tree. - pub fn process(&self, widget_context: &WidgetContext, parent_id: Option<Entity>) { + pub fn process(&self, widget_context: &KayakWidgetContext, parent_id: Option<Entity>) { for child in self.inner.iter() { widget_context.add_widget(parent_id, *child); } diff --git a/src/context.rs b/src/context.rs index 237cb5f465967493c553b02d6b59e40bb698c879..d3f3d373757ea51466ebcd35f6dc38f1fd644e6e 100644 --- a/src/context.rs +++ b/src/context.rs @@ -17,7 +17,7 @@ use crate::{ layout::{LayoutCache, Rect}, layout_dispatcher::LayoutEventDispatcher, node::{DirtyNode, WrappedIndex}, - prelude::WidgetContext, + prelude::KayakWidgetContext, render_primitive::RenderPrimitive, styles::KStyle, tree::{Change, Tree}, @@ -34,13 +34,43 @@ const UPDATE_DEPTH: u32 = 0; type WidgetSystems = HashMap< String, ( - Box<dyn System<In = (WidgetContext, Entity, Entity), Out = bool>>, - Box<dyn System<In = (WidgetContext, Entity), Out = bool>>, + Box<dyn System<In = (KayakWidgetContext, Entity, Entity), Out = bool>>, + Box<dyn System<In = (KayakWidgetContext, Entity), Out = bool>>, ), >; +/// +/// Kayak Context +/// +/// This bevy resource keeps track of all of the necessary UI state. This includes the widgets, tree, input, layout, and other important data. +/// The Context provides some connivent helper functions for creating and using widgets, state, and context. +/// +/// Usage: +/// ```rust +/// use bevy::prelude::*; +/// use kayak_ui::prelude::{widgets::*, *}; +/// +/// // Bevy setup function +/// fn setup(mut commands: Commands) { +/// let mut widget_context = Context::new(); +/// let app_entity = commands.spawn(KayakAppBundle { +/// ..Default::default() +/// }).id(); +/// // Stores the kayak app widget in the widget context's tree. +/// widget_context.add_widget(None, app_entity); +/// commands.insert_resource(widget_context); +/// } +/// +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins) +/// .add_plugin(ContextPlugin) +/// .add_plugin(KayakWidgets) +/// .add_startup_system(setup); +/// } +/// ``` #[derive(Resource)] -pub struct Context { +pub struct KayakRootContext { pub tree: Arc<RwLock<Tree>>, pub(crate) layout_cache: Arc<RwLock<LayoutCache>>, pub(crate) focus_tree: Arc<RwLock<FocusTree>>, @@ -51,9 +81,11 @@ pub struct Context { pub(crate) clone_systems: Arc<RwLock<EntityCloneSystems>>, pub(crate) cloned_widget_entities: Arc<RwLock<HashMap<Entity, Entity>>>, pub(crate) widget_state: WidgetState, + index: Arc<RwLock<HashMap<Entity, usize>>>, } -impl Context { +impl KayakRootContext { + /// Creates a new widget context. pub fn new() -> Self { Self { tree: Arc::new(RwLock::new(Tree::default())), @@ -66,9 +98,11 @@ impl Context { clone_systems: Default::default(), cloned_widget_entities: Default::default(), widget_state: Default::default(), + index: Default::default(), } } + /// Get's the layout for th given widget index. pub(crate) fn get_layout(&self, id: &WrappedIndex) -> Option<Rect> { if let Ok(cache) = self.layout_cache.try_read() { cache.rect.get(id).cloned() @@ -77,11 +111,15 @@ impl Context { } } + /// Adds a new set of systems for a widget type. + /// Update systems are ran every frame and return true or false depending on if the widget has "changed". + /// Render systems are ran only if the widget has changed and are meant to re-render children and handle + /// tree changes. pub fn add_widget_system<Params, Params2>( &mut self, type_name: impl Into<String>, - update: impl IntoSystem<(WidgetContext, Entity, Entity), bool, Params>, - render: impl IntoSystem<(WidgetContext, Entity), bool, Params2>, + update: impl IntoSystem<(KayakWidgetContext, Entity, Entity), bool, Params>, + render: impl IntoSystem<(KayakWidgetContext, Entity), bool, Params2>, ) { let update_system = Box::new(IntoSystem::into_system(update)); let render_system = Box::new(IntoSystem::into_system(render)); @@ -89,6 +127,12 @@ impl Context { .insert(type_name.into(), (update_system, render_system)); } + /// Let's the widget context know what data types are used for a given widget. + /// This is useful as it allows Kayak to keep track of previous values for diffing. + /// When the default update widget system is called it checks the props and state of + /// the current widget with it's values from the previous frame. + /// This allows Kayak to diff data. Alternatively a custom widget update system can + /// be used and listen for events, resources, or any other bevy ECS data. pub fn add_widget_data< Props: Component + Clone + PartialEq, State: Component + Clone + PartialEq, @@ -102,6 +146,10 @@ impl Context { } } + /// Adds a widget to the tree. + /// Widgets are created using entities and components. + /// Once created their id's need to be added to the widget tree + /// so that the correct ordering is preserved for updates and rendering. pub fn add_widget(&mut self, parent: Option<Entity>, entity: Entity) { if let Ok(mut tree) = self.tree.write() { tree.add( @@ -115,6 +163,8 @@ impl Context { } /// Creates a new context using the context entity for the given type_id + parent id. + /// Context can be considered state that changes across multiple components. + /// Alternatively you can use bevy's resources. pub fn set_context_entity<T: Default + 'static>( &self, parent_id: Option<Entity>, @@ -126,16 +176,56 @@ impl Context { } } - pub fn get_child_at(&self, entity: Option<Entity>) -> Option<Entity> { + /// Returns a child entity or none if it does not exist. + /// Because a re-render can potentially spawn new entities it's advised to use this + /// to avoid creating a new entity. + /// + /// Usage: + /// fn setup() { + /// let mut widget_context = WidgetContext::new(); + /// // Root tree node, no parent node. + /// let root_entity = if let Some(root_entity) = widget_context.get_child_at(None) {\ + /// root_entity + /// } else { + /// // Spawn if it does not exist in the tree! + /// commands.spawn_empty().id() + /// }; + /// commands.entity(root_entity).insert(KayakAppBundle::default()); + /// widget_context.add_widget(None, root_entity); + /// } + /// + /// + pub fn get_child_at(&self, parent_entity: Option<Entity>) -> Option<Entity> { if let Ok(tree) = self.tree.try_read() { - if let Some(entity) = entity { + if let Some(entity) = parent_entity { let children = tree.child_iter(WrappedIndex(entity)).collect::<Vec<_>>(); - return children.get(0).cloned().map(|index| index.0); + return children + .get(self.get_and_add_index(entity)) + .cloned() + .map(|index| index.0); } } None } + fn get_and_add_index(&self, parent: Entity) -> usize { + if let Ok(mut hash_map) = self.index.try_write() { + if hash_map.contains_key(&parent) { + let index = hash_map.get_mut(&parent).unwrap(); + let current_index = index.clone(); + *index += 1; + return current_index; + } else { + hash_map.insert(parent, 1); + return 0; + } + } + + 0 + } + + /// Generates a flat list of widget render commands sorted by tree order. + /// There is no need to call this unless you are implementing your own custom renderer. pub fn build_render_primitives( &self, nodes: &Query<&crate::node::Node>, @@ -272,7 +362,7 @@ fn recurse_node_tree_to_build_primitives( } fn update_widgets_sys(world: &mut World) { - let mut context = world.remove_resource::<Context>().unwrap(); + let mut context = world.remove_resource::<KayakRootContext>().unwrap(); let tree_iterator = if let Ok(tree) = context.tree.read() { tree.down_iter().collect::<Vec<_>>() } else { @@ -354,7 +444,7 @@ fn update_widgets( for entity in widgets.iter() { if let Some(entity_ref) = world.get_entity(entity.0) { if let Some(widget_type) = entity_ref.get::<WidgetName>() { - let widget_context = WidgetContext::new( + let widget_context = KayakWidgetContext::new( tree.clone(), context_entities.clone(), layout_cache.clone(), @@ -434,7 +524,7 @@ fn update_widget( world: &mut World, entity: WrappedIndex, widget_type: String, - widget_context: WidgetContext, + widget_context: KayakWidgetContext, previous_children: Vec<Entity>, clone_systems: &Arc<RwLock<EntityCloneSystems>>, cloned_widget_entities: &Arc<RwLock<HashMap<Entity, Entity>>>, @@ -571,7 +661,7 @@ fn update_widget( } fn init_systems(world: &mut World) { - let mut context = world.remove_resource::<Context>().unwrap(); + let mut context = world.remove_resource::<KayakRootContext>().unwrap(); for system in context.systems.values_mut() { system.0.initialize(world); system.1.initialize(world); @@ -580,12 +670,14 @@ fn init_systems(world: &mut World) { world.insert_resource(context); } -pub struct ContextPlugin; +/// The default Kayak Context plugin +/// Creates systems and resources for kayak. +pub struct KayakContextPlugin; #[derive(Resource)] pub struct CustomEventReader<T: bevy::ecs::event::Event>(pub ManualEventReader<T>); -impl Plugin for ContextPlugin { +impl Plugin for KayakContextPlugin { fn build(&self, app: &mut App) { app.insert_resource(WindowSize::default()) .insert_resource(EventDispatcher::new()) @@ -623,12 +715,12 @@ fn calculate_ui(world: &mut World) { for _ in 0..5 { system.run((), world); system.apply_buffers(world); - world.resource_scope::<Context, _>(|world, mut context| { + world.resource_scope::<KayakRootContext, _>(|world, mut context| { LayoutEventDispatcher::dispatch(&mut context, world); }); } - world.resource_scope::<Context, _>(|world, mut context| { + world.resource_scope::<KayakRootContext, _>(|world, mut context| { world.resource_scope::<EventDispatcher, _>(|world, event_dispatcher| { if event_dispatcher.hovered.is_none() { context.current_cursor = CursorIcon::Default; @@ -657,6 +749,8 @@ fn calculate_ui(world: &mut World) { // dbg!("Finished dispatching layout events!"); } +/// A simple component that stores the type name of a widget +/// This is used by Kayak in order to find out which systems to run. #[derive(Component, Debug)] pub struct WidgetName(pub String); diff --git a/src/event.rs b/src/event.rs index 282c7e4d4c5f4eac4faf250c4d55442fa0ebd40e..7442a2382ddb9a8dc63d5eb78376bd7c0eefbcfa 100644 --- a/src/event.rs +++ b/src/event.rs @@ -3,7 +3,7 @@ use bevy::prelude::{Entity, World}; use crate::{ cursor::{CursorEvent, ScrollEvent}, keyboard_event::KeyboardEvent, - prelude::{OnChange, WidgetContext}, + prelude::{KayakWidgetContext, OnChange}, }; /// An event type sent to widgets @@ -76,7 +76,7 @@ impl Event { self.on_change_systems.push(system); } - pub(crate) fn run_on_change(&mut self, world: &mut World, widget_context: WidgetContext) { + pub(crate) fn run_on_change(&mut self, world: &mut World, widget_context: KayakWidgetContext) { for system in self.on_change_systems.drain(..) { system.try_call(self.current_target, world, widget_context.clone()); } diff --git a/src/event_dispatcher.rs b/src/event_dispatcher.rs index 559764b9e7b9175312edde64e047a51e3d78d27c..cb70a75c2137c6b7d26dfd2baae84965e0f90805 100644 --- a/src/event_dispatcher.rs +++ b/src/event_dispatcher.rs @@ -4,7 +4,7 @@ use bevy::{ }; use crate::{ - context::Context, + context::KayakRootContext, cursor::{CursorEvent, PointerEvents, ScrollEvent, ScrollUnit}, event::{Event, EventType}, focus_tree::FocusTree, @@ -13,7 +13,7 @@ use crate::{ layout::Rect, node::WrappedIndex, on_event::OnEvent, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, RenderCommand}, Focusable, }; @@ -178,7 +178,7 @@ impl EventDispatcher { pub fn process_events( &mut self, input_events: Vec<InputEvent>, - context: &mut Context, + context: &mut KayakRootContext, world: &mut World, ) { let events = { self.build_event_stream(&input_events, context, world) }; @@ -187,7 +187,12 @@ impl EventDispatcher { /// Dispatch an [Event](crate::Event) #[allow(dead_code)] - pub fn dispatch_event(&mut self, event: Event, context: &mut Context, world: &mut World) { + pub fn dispatch_event( + &mut self, + event: Event, + context: &mut KayakRootContext, + world: &mut World, + ) { self.dispatch_events(vec![event], context, world); } @@ -195,7 +200,7 @@ impl EventDispatcher { pub fn dispatch_events( &mut self, events: Vec<Event>, - context: &mut Context, + context: &mut KayakRootContext, world: &mut World, ) { // === Dispatch Events === // @@ -233,7 +238,7 @@ impl EventDispatcher { // Sometimes events will require systems to be called. // IE OnChange - let widget_context = WidgetContext::new( + let widget_context = KayakWidgetContext::new( context.tree.clone(), context.context_entities.clone(), context.layout_cache.clone(), @@ -300,7 +305,7 @@ impl EventDispatcher { fn build_event_stream( &mut self, input_events: &[InputEvent], - context: &mut Context, + context: &mut KayakRootContext, world: &mut World, ) -> Vec<Event> { let mut event_stream = Vec::<Event>::new(); @@ -500,7 +505,7 @@ impl EventDispatcher { tree_node: TreeNode, states: &mut HashMap<EventType, EventState>, world: &mut World, - context: &Context, + context: &KayakRootContext, ignore_layout: bool, ) -> Vec<Event> { let mut event_stream = Vec::<Event>::new(); @@ -739,7 +744,7 @@ impl EventDispatcher { } /// Executes default actions for events - fn execute_default(&mut self, event: Event, context: &mut Context, world: &mut World) { + fn execute_default(&mut self, event: Event, context: &mut KayakRootContext, world: &mut World) { match event.event_type { EventType::KeyDown(evt) => match evt.key() { KeyCode::Tab => { diff --git a/src/input.rs b/src/input.rs index f813f05faaef708e0e9ba093ff556c5a9011d8a3..c3eb172770f19104e544464e9052da5b1f5c0344 100644 --- a/src/input.rs +++ b/src/input.rs @@ -8,7 +8,7 @@ use bevy::{ }; use crate::{ - context::{Context, CustomEventReader}, + context::{CustomEventReader, KayakRootContext}, event_dispatcher::EventDispatcher, input_event::InputEvent, }; @@ -106,7 +106,7 @@ pub(crate) fn process_events(world: &mut World) { ); world.resource_scope::<EventDispatcher, _>(|world, mut event_dispatcher| { - world.resource_scope::<Context, _>(|world, mut context| { + world.resource_scope::<KayakRootContext, _>(|world, mut context| { event_dispatcher.process_events(input_events, &mut context, world); }); }); diff --git a/src/layout_dispatcher.rs b/src/layout_dispatcher.rs index 69e8c814c0aca1bb71baeebed364e5c1b318639e..a7320b6d54f2173baeea088cf92fe4af7a36ebba 100644 --- a/src/layout_dispatcher.rs +++ b/src/layout_dispatcher.rs @@ -9,13 +9,13 @@ use crate::{ layout::{LayoutCache, LayoutEvent}, node::WrappedIndex, on_layout::OnLayout, - prelude::Context, + prelude::KayakRootContext, }; pub(crate) struct LayoutEventDispatcher; impl LayoutEventDispatcher { - pub fn dispatch(context: &mut Context, world: &mut World) { + pub fn dispatch(context: &mut KayakRootContext, world: &mut World) { let on_event_entities = { let mut query = world.query_filtered::<Entity, With<OnLayout>>(); query diff --git a/src/lib.rs b/src/lib.rs index 5e9632256f5ae0fad8b169ab4189cac29b29a206..2c931209809809f5e744e4444707b98cc854a619 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ mod tree; mod widget; mod widget_context; mod widget_state; -mod widgets; +pub mod widgets; mod window_size; pub use window_size::WindowSize; @@ -39,13 +39,8 @@ pub const DEFAULT_FONT: &str = "Kayak-Default"; pub mod prelude { pub use crate::camera::UICameraBundle; pub use crate::children::KChildren; - pub use crate::context::*; - pub use crate::render::font::FontMapping; - pub use crate::tree::*; - pub mod widgets { - pub use crate::widgets::*; - } pub use crate::clone_component::PreviousWidget; + pub use crate::context::*; pub use crate::event::*; pub use crate::event_dispatcher::EventDispatcherContext; pub use crate::focus_tree::Focusable; @@ -56,10 +51,13 @@ pub mod prelude { pub use crate::on_change::OnChange; pub use crate::on_event::OnEvent; pub use crate::on_layout::OnLayout; + pub use crate::render::font::FontMapping; pub use crate::styles::*; + pub use crate::tree::*; pub use crate::widget::*; pub use crate::widget_context::*; pub use crate::widget_state::*; + pub use crate::widgets; pub use kayak_font::Alignment; pub use kayak_ui_macros::{constructor, rsx}; } diff --git a/src/on_change.rs b/src/on_change.rs index 1bdadbf968501b64a6e82befd316a4fc0abd4633..40c4a2c60df343eb87b3cb494d7fa49fbd5c5051 100644 --- a/src/on_change.rs +++ b/src/on_change.rs @@ -3,7 +3,7 @@ use bevy::prelude::{Component, Entity, In, IntoSystem, System, World}; use std::fmt::{Debug, Formatter}; use std::sync::{Arc, RwLock}; -use crate::prelude::WidgetContext; +use crate::prelude::KayakWidgetContext; pub trait ChangeValue: Component<Storage = TableStorage> + Default {} @@ -16,7 +16,7 @@ pub trait ChangeValue: Component<Storage = TableStorage> + Default {} pub struct OnChange { value: Arc<RwLock<String>>, has_initialized: Arc<RwLock<bool>>, - system: Arc<RwLock<dyn System<In = (WidgetContext, Entity, String), Out = ()>>>, + system: Arc<RwLock<dyn System<In = (KayakWidgetContext, Entity, String), Out = ()>>>, } impl Default for OnChange { @@ -31,7 +31,7 @@ impl OnChange { /// The handler should be a closure that takes the following arguments: /// 1. The LayoutEvent pub fn new<Params>( - system: impl IntoSystem<(WidgetContext, Entity, String), (), Params>, + system: impl IntoSystem<(KayakWidgetContext, Entity, String), (), Params>, ) -> Self { Self { value: Default::default(), @@ -49,7 +49,7 @@ impl OnChange { /// Call the layout event handler /// /// Returns true if the handler was successfully invoked. - pub fn try_call(&self, entity: Entity, world: &mut World, widget_context: WidgetContext) { + pub fn try_call(&self, entity: Entity, world: &mut World, widget_context: KayakWidgetContext) { if let Ok(value) = self.value.try_read() { if let Ok(mut init) = self.has_initialized.try_write() { if let Ok(mut system) = self.system.try_write() { diff --git a/src/render/extract.rs b/src/render/extract.rs index 188caeb33196b7ac5faf223bd2f606da536f1edf..8312995bceb85f40d015104cc376003d904a4af1 100644 --- a/src/render/extract.rs +++ b/src/render/extract.rs @@ -1,5 +1,5 @@ use crate::{ - context::{Context, WidgetName}, + context::{KayakRootContext, WidgetName}, node::Node, render_primitive::RenderPrimitive, styles::Corner, @@ -31,7 +31,7 @@ impl Plugin for BevyKayakUIExtractPlugin { pub fn extract( mut commands: Commands, - context: Extract<Res<Context>>, + context: Extract<Res<KayakRootContext>>, fonts: Extract<Res<Assets<KayakFont>>>, font_mapping: Extract<Res<FontMapping>>, node_query: Extract<Query<&Node>>, diff --git a/src/render/font/mod.rs b/src/render/font/mod.rs index 55a330d7bdb161b2568bb59b1c8cb06167d929b9..39515cb5e9b037184b24b7c4c797c51a3a3c88c8 100644 --- a/src/render/font/mod.rs +++ b/src/render/font/mod.rs @@ -7,7 +7,7 @@ mod font_mapping; pub use extract::extract_texts; pub use font_mapping::*; -use crate::context::Context; +use crate::context::KayakRootContext; #[derive(Default)] pub struct TextRendererPlugin; @@ -22,7 +22,7 @@ impl Plugin for TextRendererPlugin { fn process_loaded_fonts( mut font_mapping: ResMut<FontMapping>, _fonts: Res<Assets<KayakFont>>, - context_resource: Res<Context>, + context_resource: Res<KayakRootContext>, ) { // if let Some(context = context_resource.as_ref() { if context_resource.is_added() { diff --git a/src/render/mod.rs b/src/render/mod.rs index cd66d6c88e7bf3998c460e02c04e83cf20f0c279..d014cc5629669b33f7ed53024c81ecd606f7000c 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -33,6 +33,9 @@ pub mod draw_ui_graph { } } +/// The default Kayak UI rendering plugin. +/// Use this to render the UI. +/// Or you can write your own renderer. pub struct BevyKayakUIRenderPlugin; impl Plugin for BevyKayakUIRenderPlugin { diff --git a/src/widget.rs b/src/widget.rs index c29b1491a01b6358aabbe5be8e6140283b84bb82..c079b2d35c72b40d8d162a8da805d87639a6abc0 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -6,7 +6,7 @@ use bevy::{ use crate::{ children::KChildren, context::{Mounted, WidgetName}, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::KStyle, }; @@ -20,7 +20,7 @@ pub trait Widget: Send + Sync { pub struct EmptyState; pub fn widget_update<Props: PartialEq + Component + Clone, State: PartialEq + Component + Clone>( - In((widget_context, entity, previous_entity)): In<(WidgetContext, Entity, Entity)>, + In((widget_context, entity, previous_entity)): In<(KayakWidgetContext, Entity, Entity)>, widget_param: WidgetParam<Props, State>, ) -> bool { widget_param.has_changed(&widget_context, entity, previous_entity) @@ -31,7 +31,7 @@ pub fn widget_update_with_context< State: PartialEq + Component + Clone, Context: PartialEq + Component + Clone + Default, >( - In((widget_context, entity, previous_entity)): In<(WidgetContext, Entity, Entity)>, + In((widget_context, entity, previous_entity)): In<(KayakWidgetContext, Entity, Entity)>, widget_param: WidgetParam<Props, State>, context_query: Query<Entity, Changed<Context>>, ) -> bool { @@ -61,7 +61,7 @@ impl<'w, 's, Props: PartialEq + Component, State: PartialEq + Component> { pub fn has_changed( &self, - widget_context: &WidgetContext, + widget_context: &KayakWidgetContext, current_entity: Entity, previous_entity: Entity, ) -> bool { diff --git a/src/widget_context.rs b/src/widget_context.rs index 3fd8aef13b13f2cfcb38a98635a2dbe2750c8ac5..dd9cdb901f87df05627a0de15262220ac7b4a568 100644 --- a/src/widget_context.rs +++ b/src/widget_context.rs @@ -11,8 +11,14 @@ use crate::{ widget_state::WidgetState, }; +/// KayakWidgetContext manages tree, state, and context updates within a single widget. +/// Unlike the root context this manages a single widget and it's children. +/// At the end of a render system call KayakWidgetContext will be consumed by the root context. +/// It has some knowledge about the existing tree and it knows about a subset of the new tree. +/// It is not possible to create a KayakWidgetContext from scratch. One will be provided +/// to the render system via it's In parameters. #[derive(Clone)] -pub struct WidgetContext { +pub struct KayakWidgetContext { old_tree: Arc<RwLock<Tree>>, new_tree: Arc<RwLock<Tree>>, context_entities: ContextEntities, @@ -21,7 +27,7 @@ pub struct WidgetContext { widget_state: WidgetState, } -impl WidgetContext { +impl KayakWidgetContext { pub(crate) fn new( old_tree: Arc<RwLock<Tree>>, context_entities: ContextEntities, @@ -94,12 +100,16 @@ impl WidgetContext { } } + /// Removes all children from the new tree. + /// Changes to the current tree will happen when KayakWidgetContext + /// is consumed. pub fn clear_children(&self, entity: Entity) { if let Ok(mut tree) = self.new_tree.write() { tree.children.insert(WrappedIndex(entity), vec![]); } } + /// Retrieves a list of all children. pub fn get_children(&self, entity: Entity) -> Vec<Entity> { let mut children = vec![]; if let Ok(tree) = self.new_tree.read() { @@ -154,6 +164,9 @@ impl WidgetContext { self.widget_state.get(widget_entity) } + /// Returns a child entity or none if it does not exist. + /// Because a re-render can potentially spawn new entities it's advised to use this + /// to avoid creating a new entity. pub fn get_child_at(&self, entity: Option<Entity>) -> Option<Entity> { if let Some(entity) = entity { let children = self.get_children_old(entity); @@ -162,6 +175,7 @@ impl WidgetContext { None } + /// Removes all matching children from the tree. pub fn remove_children(&self, children_to_remove: Vec<Entity>) { if let Ok(mut tree) = self.new_tree.write() { for child in children_to_remove.iter() { @@ -170,6 +184,7 @@ impl WidgetContext { } } + /// Adds a new widget to the tree with a given parent. pub fn add_widget(&self, parent: Option<Entity>, entity: Entity) { if let Ok(mut tree) = self.new_tree.write() { tree.add( @@ -193,13 +208,17 @@ impl WidgetContext { } } + /// Dumps the tree to the console in a human readable format. + /// This is relatively slow to do if the tree is large + /// so avoid doing unless necessary. pub fn dbg_tree(&self) { if let Ok(tree) = self.new_tree.read() { tree.dump() } } - pub fn take(self) -> Tree { + /// Consumes the tree + pub(crate) fn take(self) -> Tree { Arc::try_unwrap(self.new_tree) .unwrap() .into_inner() diff --git a/src/widgets/app.rs b/src/widgets/app.rs index 606a210bbd97c10af4b227bf5b89a7ea67ceb7c1..9051362f0c5417e092894049fb6d0964a5a97563 100644 --- a/src/widgets/app.rs +++ b/src/widgets/app.rs @@ -4,7 +4,7 @@ use morphorm::Units; use crate::{ children::KChildren, context::WidgetName, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, RenderCommand, StyleProp}, widget::{EmptyState, Widget, WidgetParam}, }; @@ -14,6 +14,9 @@ pub struct KayakApp; impl Widget for KayakApp {} +/// Kayak's default root widget +/// This widget provides a width/height that matches the screen size in bevy +/// It will auto update if bevy's window changes as well. #[derive(Bundle)] pub struct KayakAppBundle { pub app: KayakApp, @@ -34,7 +37,7 @@ impl Default for KayakAppBundle { } pub fn app_update( - In((widget_context, entity, previous_props_entity)): In<(WidgetContext, Entity, Entity)>, + In((widget_context, entity, previous_props_entity)): In<(KayakWidgetContext, Entity, Entity)>, windows: Res<Windows>, widget_param: WidgetParam<KayakApp, EmptyState>, ) -> bool { @@ -55,7 +58,7 @@ pub fn app_update( /// TODO: USE CAMERA INSTEAD OF WINDOW!! pub fn app_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, _: Commands, windows: Res<Windows>, mut query: Query<(&mut KStyle, &KChildren)>, diff --git a/src/widgets/background.rs b/src/widgets/background.rs index 166c4e15b122484dc414bc609ee0d88f165c19bf..63f0cba1d142f6b1688ac5fe696f7dca8b29a553 100644 --- a/src/widgets/background.rs +++ b/src/widgets/background.rs @@ -4,7 +4,7 @@ use crate::{ children::KChildren, context::WidgetName, on_event::OnEvent, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, RenderCommand, StyleProp}, widget::Widget, }; @@ -14,6 +14,12 @@ pub struct Background; impl Widget for Background {} +/// Background Widget +/// +/// The name of this widget is slightly misleading. +/// In actuality this widget renders a quad or multiple quads if a border is used. +/// You can customize the colors, border, border-radius, by passing in custom styles. +/// Children are rendered inside of the quad. #[derive(Bundle)] pub struct BackgroundBundle { pub background: Background, @@ -36,7 +42,7 @@ impl Default for BackgroundBundle { } pub fn background_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, _: Commands, mut query: Query<(&mut KStyle, &KChildren)>, ) -> bool { diff --git a/src/widgets/button.rs b/src/widgets/button.rs index f49779fae7c3f6508e1aac125184ae0f47108a2a..30910648107f4f6ab9867f53e805ab352900fdb3 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -6,7 +6,7 @@ use bevy::{ use crate::{ context::WidgetName, on_event::OnEvent, - prelude::{KChildren, Units, WidgetContext}, + prelude::{KChildren, KayakWidgetContext, Units}, styles::{Corner, KCursorIcon, KStyle, RenderCommand, StyleProp}, widget::Widget, }; @@ -14,6 +14,8 @@ use crate::{ #[derive(Component, PartialEq, Clone, Default)] pub struct KButton; +/// Default button widget +/// Accepts an OnEvent component #[derive(Bundle)] pub struct KButtonBundle { pub button: KButton, @@ -38,7 +40,7 @@ impl Default for KButtonBundle { impl Widget for KButton {} pub fn button_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, _: Commands, mut query: Query<(&mut KStyle, &KChildren)>, ) -> bool { diff --git a/src/widgets/clip.rs b/src/widgets/clip.rs index b37dd16c50dbe523d6b5869dc1da08a757e7bea9..8f81862f859f26402459b6ff9f11dedd0eda492e 100644 --- a/src/widgets/clip.rs +++ b/src/widgets/clip.rs @@ -3,7 +3,7 @@ use bevy::prelude::{Bundle, Commands, Component, Entity, In, Query}; use crate::{ children::KChildren, context::WidgetName, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, RenderCommand, StyleProp, Units}, widget::Widget, }; @@ -13,6 +13,11 @@ pub struct Clip; impl Widget for Clip {} +/// Clips are used to "clip" or cut away sections of the screen. +/// For example text inside of another widget likely should not +/// overflow out of the widget's bounds. This widget will cut or clip +/// the text. +/// Note: Clips roughly translate to wGPU scissor commands. #[derive(Bundle)] pub struct ClipBundle { pub clip: Clip, @@ -38,7 +43,7 @@ impl Default for ClipBundle { } pub fn clip_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, _: Commands, mut query: Query<(&KStyle, &KChildren)>, ) -> bool { diff --git a/src/widgets/element.rs b/src/widgets/element.rs index 86c4c5d07d32f4b778111c38a12b1921a1521ba6..64507d7358d8d7c4492e4dc96af2d30818a3e65b 100644 --- a/src/widgets/element.rs +++ b/src/widgets/element.rs @@ -4,7 +4,7 @@ use crate::{ children::KChildren, context::WidgetName, on_event::OnEvent, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, RenderCommand, StyleProp}, widget::Widget, }; @@ -14,6 +14,9 @@ pub struct Element; impl Widget for Element {} +/// A generic widget +/// You can consider this to kind behave like a div in html +/// Accepts: KStyle, OnEvent, and KChildren. #[derive(Bundle)] pub struct ElementBundle { pub element: Element, @@ -36,7 +39,7 @@ impl Default for ElementBundle { } pub fn element_render( - In((mut widget_context, entity)): In<(WidgetContext, Entity)>, + In((mut widget_context, entity)): In<(KayakWidgetContext, Entity)>, _: Commands, mut query: Query<(&mut KStyle, &KChildren)>, ) -> bool { diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 321a4ea60bc1a066c95a4a125e98c8c10179f51d..2e8d9349e44d991ac344c41bd26c6d119d306c16 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -2,11 +2,13 @@ use bevy::prelude::{Bundle, Changed, Component, Entity, Handle, In, Or, Query, W use crate::{ context::{Mounted, WidgetName}, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, RenderCommand, StyleProp}, widget::Widget, }; +/// Renders a bevy image asset within the GUI +/// The rendered image respects the styles. #[derive(Component, PartialEq, Clone, Default)] pub struct Image(pub Handle<bevy::prelude::Image>); @@ -30,7 +32,7 @@ impl Default for ImageBundle { } pub fn image_render( - In((_widget_context, entity)): In<(WidgetContext, Entity)>, + In((_widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut query: Query<(&mut KStyle, &Image), Or<((Changed<Image>, Changed<KStyle>), With<Mounted>)>>, ) -> bool { if let Ok((mut style, image)) = query.get_mut(entity) { diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 036829cd1b1a8c5b8bb15835f05050b333f96f1e..8ff16809a39145dc4833a52dabb22e802bffce15 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,3 +1,25 @@ +//! A small collection of default widgets +//! These widgets can be useful as default widgets for debugging purposes. +//! Kayak recommends that you use these widgets as a guide for building your own widgets. +//! Some of the widgets are useful regardless. A list: +//! +//! - KayakApp +//! - Background +//! - Clip +//! - Element +//! - Image +//! - NinePatch +//! - TextBox +//! - Text +//! - Texture Atlas +//! - Scroll +//! +//! Widgets like: +//! - Window +//! - Button +//! +//! Should be a guide for creating your own set of widgets. + use bevy::prelude::*; mod app; @@ -50,7 +72,7 @@ use texture_atlas::texture_atlas_render; use window::window_render; use crate::{ - context::Context, + context::KayakRootContext, widget::{widget_update, widget_update_with_context, EmptyState, Widget}, }; @@ -64,7 +86,7 @@ impl Plugin for KayakWidgets { } } -fn add_widget_systems(mut context: ResMut<Context>) { +fn add_widget_systems(mut context: ResMut<KayakRootContext>) { context.add_widget_data::<KayakApp, EmptyState>(); context.add_widget_data::<KButton, EmptyState>(); context.add_widget_data::<TextProps, EmptyState>(); diff --git a/src/widgets/nine_patch.rs b/src/widgets/nine_patch.rs index 9fb7fea5c9031e576e06bba75f8eb8ab54d5c721..68d7d05c3cc6802ffc51d5c7b35b3f9fe07800af 100644 --- a/src/widgets/nine_patch.rs +++ b/src/widgets/nine_patch.rs @@ -3,7 +3,7 @@ use bevy::prelude::{Bundle, Commands, Component, Entity, Handle, Image, In, Quer use crate::{ children::KChildren, context::WidgetName, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{Edge, KStyle, RenderCommand, StyleProp}, widget::Widget, }; @@ -18,6 +18,35 @@ pub struct NinePatch { impl Widget for NinePatch {} +/// +/// Render's a nine-patch image as a UI widget. +/// +/// Also know as 9-slicing. This 2D technique allows users to render UI images at multiple +/// resolutions while maintaining a level of quality. The image in the middle is repeated. +/// +/// Accepts Children and Styles. +/// +/// Example: The border prop splits up the image into 9 quadrants like so: +/// 1----2----3 +/// | | +/// 4 9 5 +/// | | +/// 6----7----8 +/// The sizes of sprites for a 15 pixel border are as follows: +/// TopLeft = (15, 15) +/// TopRight = (15, 15) +/// LeftCenter = (15, image_height) +/// RightCenter = (15, image_height) +/// TopCenter = (image_width, 15) +/// BottomCenter = (image_width, 15) +/// BottomRight = (15, 15) +/// BottomLeft = (15, 15) +/// Middle = ( +/// 30 being left border + right border +/// image_width - 30 +/// 30 being top border + bottom border +/// image_height - 30 +/// ) #[derive(Bundle)] pub struct NinePatchBundle { pub nine_patch: NinePatch, @@ -38,7 +67,7 @@ impl Default for NinePatchBundle { } pub fn nine_patch_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, _: Commands, mut query: Query<(&mut KStyle, &NinePatch, &KChildren)>, ) -> bool { diff --git a/src/widgets/scroll/scroll_bar.rs b/src/widgets/scroll/scroll_bar.rs index 790faba4ee976860612850df9133604cd3ba491b..81ef57f5811bf865012ffa27fbd85f7408a46e7c 100644 --- a/src/widgets/scroll/scroll_bar.rs +++ b/src/widgets/scroll/scroll_bar.rs @@ -6,7 +6,7 @@ use crate::{ event::{Event, EventType}, event_dispatcher::EventDispatcherContext, on_event::OnEvent, - prelude::{KChildren, WidgetContext}, + prelude::{KChildren, KayakWidgetContext}, styles::{Corner, Edge, KStyle, PositionType, RenderCommand, Units}, widget::Widget, widget_state::WidgetState, @@ -54,7 +54,7 @@ impl Default for ScrollBarBundle { } pub fn scroll_bar_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut query: Query<(&ScrollBarProps, &mut KStyle)>, context_query: Query<&ScrollContext>, diff --git a/src/widgets/scroll/scroll_box.rs b/src/widgets/scroll/scroll_box.rs index e42332bd24b90f8da50ba5f4f10ebc8c52d071ce..dba219b779088cf09b7b4ed414118227959287ab 100644 --- a/src/widgets/scroll/scroll_box.rs +++ b/src/widgets/scroll/scroll_box.rs @@ -9,7 +9,7 @@ use crate::{ layout::{GeometryChanged, LayoutEvent}, on_event::OnEvent, on_layout::OnLayout, - prelude::{constructor, rsx, WidgetContext}, + prelude::{constructor, rsx, KayakWidgetContext}, styles::{KStyle, LayoutType, PositionType, RenderCommand, Units}, widget::Widget, widget_state::WidgetState, @@ -77,7 +77,7 @@ impl Default for ScrollBoxBundle { } pub fn scroll_box_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut query: Query<(&ScrollBoxProps, &mut KStyle, &KChildren, &mut OnLayout)>, mut context_query: ParamSet<(Query<&ScrollContext>, Query<&mut ScrollContext>)>, diff --git a/src/widgets/scroll/scroll_content.rs b/src/widgets/scroll/scroll_content.rs index e41c038b35f059f076db8e0fd5a0b2b94b0a1a2e..e3af6da6199e0afdc0f557a34c7bc9ae2f437305 100644 --- a/src/widgets/scroll/scroll_content.rs +++ b/src/widgets/scroll/scroll_content.rs @@ -6,7 +6,7 @@ use crate::{ layout::GeometryChanged, layout::LayoutEvent, on_layout::OnLayout, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, LayoutType, RenderCommand, Units}, widget::Widget, }; @@ -40,7 +40,7 @@ impl Default for ScrollContentBundle { } pub fn scroll_content_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut query: Query<(&mut KStyle, &KChildren, &mut OnLayout), With<ScrollContentProps>>, context_query: Query<&ScrollContext>, ) -> bool { diff --git a/src/widgets/scroll/scroll_context.rs b/src/widgets/scroll/scroll_context.rs index 09a0bb58aa2dad6dc1ab4363345bec7116a57266..0c249dc49da4db896abd02c0586602b638c872a1 100644 --- a/src/widgets/scroll/scroll_context.rs +++ b/src/widgets/scroll/scroll_context.rs @@ -1,7 +1,7 @@ use bevy::prelude::{Bundle, Commands, Component, Entity, In, Query, Vec2}; use crate::{ - children::KChildren, context::WidgetName, prelude::WidgetContext, styles::KStyle, + children::KChildren, context::WidgetName, prelude::KayakWidgetContext, styles::KStyle, widget::Widget, }; @@ -158,7 +158,7 @@ impl Default for ScrollContextProviderBundle { } pub fn scroll_context_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut query: Query<(&ScrollContextProvider, &KChildren)>, ) -> bool { diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 7cb20700e8c518ff2b047b6a4f8f91531baf421e..467628e57451296eb1649b64475d9b9ec8b6b4a7 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -3,7 +3,7 @@ use kayak_font::Alignment; use crate::{ context::WidgetName, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KCursorIcon, KStyle, RenderCommand, StyleProp}, widget::Widget, }; @@ -45,6 +45,8 @@ impl Default for TextProps { impl Widget for TextProps {} +/// A widget that renders text +/// #[derive(Bundle)] pub struct TextWidgetBundle { pub text: TextProps, @@ -63,7 +65,7 @@ impl Default for TextWidgetBundle { } pub fn text_render( - In((_, entity)): In<(WidgetContext, Entity)>, + In((_, entity)): In<(KayakWidgetContext, Entity)>, mut query: Query<(&mut KStyle, &TextProps)>, ) -> bool { if let Ok((mut style, text)) = query.get_mut(entity) { diff --git a/src/widgets/text_box.rs b/src/widgets/text_box.rs index f775de459949c088e2e50c32544b19ec08e38864..b66738c1965bddf77c02117e578b1e53331261f6 100644 --- a/src/widgets/text_box.rs +++ b/src/widgets/text_box.rs @@ -7,7 +7,7 @@ use crate::{ event_dispatcher::EventDispatcherContext, on_event::OnEvent, on_layout::OnLayout, - prelude::{KChildren, OnChange, WidgetContext}, + prelude::{KChildren, KayakWidgetContext, OnChange}, styles::{Corner, KStyle, RenderCommand, StyleProp, Units}, widget::Widget, widget_state::WidgetState, @@ -42,17 +42,8 @@ pub struct TextBoxValue(pub String); impl Widget for TextBoxProps {} /// A widget that displays a text input field -/// -/// # Props -/// -/// __Type:__ [`TextBoxProps`] -/// -/// | Common Prop | Accepted | -/// | :---------: | :------: | -/// | `children` | ⌠| -/// | `styles` | ✅ | -/// | `on_event` | ✅ | -/// | `on_layout` | ✅ | +/// A text box allows users to input text. +/// This text box is fairly simple and only supports basic input. /// #[derive(Bundle)] pub struct TextBoxBundle { @@ -80,7 +71,7 @@ impl Default for TextBoxBundle { } pub fn text_box_render( - In((widget_context, entity)): In<(WidgetContext, Entity)>, + In((widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut query: Query<(&mut KStyle, &TextBoxProps, &mut OnEvent, &OnChange)>, ) -> bool { diff --git a/src/widgets/texture_atlas.rs b/src/widgets/texture_atlas.rs index 0df6efed82ae14ec5a2b16ec0aeafd02e40d923e..8e410c0e19a8161564f389a3553aa39b24e18131 100644 --- a/src/widgets/texture_atlas.rs +++ b/src/widgets/texture_atlas.rs @@ -2,7 +2,7 @@ use bevy::prelude::{Bundle, Changed, Component, Entity, Handle, Image, In, Or, Q use crate::{ context::{Mounted, WidgetName}, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{KStyle, RenderCommand, StyleProp}, widget::Widget, }; @@ -34,6 +34,7 @@ pub struct TextureAtlas { impl Widget for TextureAtlas {} +/// A widget that renders a bevy texture atlas #[derive(Bundle)] pub struct TextureAtlasBundle { pub atlas: TextureAtlas, @@ -52,7 +53,7 @@ impl Default for TextureAtlasBundle { } pub fn texture_atlas_render( - In((_widget_context, entity)): In<(WidgetContext, Entity)>, + In((_widget_context, entity)): In<(KayakWidgetContext, Entity)>, mut query: Query< (&mut KStyle, &TextureAtlas), Or<(Changed<TextureAtlas>, Changed<KStyle>, With<Mounted>)>, diff --git a/src/widgets/window.rs b/src/widgets/window.rs index bcbdec501fd005dff9b4224674eae5fbd9890f1c..1a6dc82a89d5b585a3ab99f04d7ebf25eb735fe7 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -10,7 +10,7 @@ use crate::{ event::{Event, EventType}, event_dispatcher::EventDispatcherContext, on_event::OnEvent, - prelude::WidgetContext, + prelude::KayakWidgetContext, styles::{Corner, Edge, KCursorIcon, KStyle, PositionType, RenderCommand, StyleProp, Units}, widget::Widget, widget_state::WidgetState, @@ -44,6 +44,9 @@ pub struct KWindowState { impl Widget for KWindow {} +/// Default window widget +/// A simple widget that renders a window. +/// Does not support much customization. #[derive(Bundle)] pub struct WindowBundle { pub window: KWindow, @@ -64,7 +67,7 @@ impl Default for WindowBundle { } pub fn window_render( - In((widget_context, window_entity)): In<(WidgetContext, Entity)>, + In((widget_context, window_entity)): In<(KayakWidgetContext, Entity)>, mut commands: Commands, mut query: Query<(&KStyle, &KChildren, &KWindow)>, state_query: Query<&KWindowState>,