Skip to content
Snippets Groups Projects
Commit 3171ba3a authored by StarToaster's avatar StarToaster
Browse files

A bunch of documentation. Some renaming of structs.

parent 75a56767
No related branches found
No related tags found
No related merge requests found
Showing
with 445 additions and 25 deletions
book
[book]
authors = ["StarToaster"]
language = "en"
multilingual = false
src = "src"
title = "Kayak UI"
# 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
# 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()
}
```
# 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>
}
}
```
# Chapter 3 - State
# Chapter 4 - Context
# 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
# 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.
......@@ -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()
......
......@@ -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()
......
......@@ -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)
......
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()
}
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()
}
......@@ -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()
......
......@@ -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()
......
......@@ -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()
......
......@@ -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()
......
......@@ -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()
......
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>,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment