From e65f9ce940f6b5f92be74dafb840f8f1d263596d Mon Sep 17 00:00:00 2001 From: StarArawn <toasterthegamer@gmail.com> Date: Sat, 18 Dec 2021 20:44:59 -0500 Subject: [PATCH] Fully working todo.. --- Cargo.toml | 4 + bevy_kayak_ui/src/render/unified/mod.rs | 2 + examples/text_box.rs | 4 +- examples/todo/add_button.rs | 46 +++++++++ examples/todo/card.rs | 40 ++++++++ examples/todo/cards.rs | 22 +++++ examples/todo/delete_button.rs | 45 +++++++++ examples/todo/todo.rs | 126 ++++++++++++++++++++++++ kayak_core/src/lib.rs | 30 +++++- kayak_core/src/node.rs | 22 ++++- kayak_core/src/styles.rs | 4 + kayak_core/src/vec.rs | 2 +- kayak_core/src/widget_manager.rs | 32 +++++- kayak_render_macros/src/lib.rs | 3 +- kayak_widgets/src/element.rs | 12 ++- kayak_widgets/src/text_box.rs | 11 ++- kayak_widgets/src/window.rs | 8 +- 17 files changed, 393 insertions(+), 20 deletions(-) create mode 100644 examples/todo/add_button.rs create mode 100644 examples/todo/card.rs create mode 100644 examples/todo/cards.rs create mode 100644 examples/todo/delete_button.rs create mode 100644 examples/todo/todo.rs diff --git a/Cargo.toml b/Cargo.toml index e76dc57..4357ec4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,7 @@ kayak_render_macros = { path = "kayak_render_macros" } [dev-dependencies] bevy = { git = "https://github.com/bevyengine/bevy", rev = "9a16a4d01830297987db40b45f03382ed3acad62" } kayak_widgets = { path = "kayak_widgets", features = ["bevy_renderer"] } + +[[example]] +name = "todo" +path = "examples/todo/todo.rs" diff --git a/bevy_kayak_ui/src/render/unified/mod.rs b/bevy_kayak_ui/src/render/unified/mod.rs index 6fff2f9..ee0390d 100644 --- a/bevy_kayak_ui/src/render/unified/mod.rs +++ b/bevy_kayak_ui/src/render/unified/mod.rs @@ -78,6 +78,8 @@ pub fn extract( vec![] }; + // dbg!(&render_primitives); + let dpi = if let Some(window) = windows.get_primary() { window.scale_factor() as f32 } else { diff --git a/examples/text_box.rs b/examples/text_box.rs index bbf01ec..5a91981 100644 --- a/examples/text_box.rs +++ b/examples/text_box.rs @@ -38,8 +38,8 @@ fn TextBoxExample(context: &mut KayakContext) { rsx! { <> <Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"TextBox Example".to_string()}> - <TextBox styles={Some(input_styles)} value={current_value} on_change={Some(on_change)} /> - <TextBox styles={Some(input_styles)} value={current_value2} on_change={Some(on_change2)} /> + <TextBox styles={Some(input_styles)} value={current_value} on_change={Some(on_change)} placeholder={None as Option<String>} /> + <TextBox styles={Some(input_styles)} value={current_value2} on_change={Some(on_change2)} placeholder={None as Option<String>} /> </Window> </> } diff --git a/examples/todo/add_button.rs b/examples/todo/add_button.rs new file mode 100644 index 0000000..febec97 --- /dev/null +++ b/examples/todo/add_button.rs @@ -0,0 +1,46 @@ +use kayak_ui::core::{ + color::Color, + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp, Units}, + use_state, widget, Bound, Children, EventType, MutableBound, OnEvent, +}; + +use kayak_widgets::{Background, Text}; + +#[widget] +pub fn AddButton(children: Children, styles: Option<Style>) { + let (color, set_color) = use_state!(Color::new(0.0781, 0.0898, 0.101, 1.0)); + + let base_styles = styles.clone().unwrap_or_default(); + *styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::Layout), + height: StyleProp::Value(Units::Pixels(32.0)), + width: StyleProp::Value(Units::Pixels(30.0)), + ..base_styles + }); + + let background_styles = Some(Style { + border_radius: StyleProp::Value((5.0, 5.0, 5.0, 5.0)), + background_color: StyleProp::Value(color), + padding_left: StyleProp::Value(Units::Pixels(9.0)), + padding_bottom: StyleProp::Value(Units::Pixels(6.0)), + ..Style::default() + }); + + let on_event = OnEvent::new(move |_, event| match event.event_type { + EventType::MouseIn => { + set_color(Color::new(0.0791, 0.0998, 0.201, 1.0)); + } + EventType::MouseOut => { + set_color(Color::new(0.0781, 0.0898, 0.101, 1.0)); + } + _ => {} + }); + + rsx! { + <Background styles={background_styles} on_event={Some(on_event)}> + <Text content={"+".to_string()} size={20.0} /> + </Background> + } +} diff --git a/examples/todo/card.rs b/examples/todo/card.rs new file mode 100644 index 0000000..7bb615c --- /dev/null +++ b/examples/todo/card.rs @@ -0,0 +1,40 @@ +use kayak_core::{ + rsx, + styles::{LayoutType, Style, StyleProp, Units}, + widget, Color, EventType, Handler, OnEvent, +}; +use kayak_widgets::{Background, Text}; + +use super::delete_button::DeleteButton; + +#[widget] +pub fn Card(card_id: usize, name: String, on_delete: Handler<usize>) { + let name = name.clone(); + let background_styles = Style { + layout_type: StyleProp::Value(LayoutType::Row), + background_color: StyleProp::Value(Color::new(0.176, 0.196, 0.215, 1.0)), + height: StyleProp::Value(Units::Pixels(26.0)), + top: StyleProp::Value(Units::Pixels(10.0)), + padding_left: StyleProp::Value(Units::Pixels(5.0)), + padding_right: StyleProp::Value(Units::Pixels(5.0)), + padding_top: StyleProp::Value(Units::Pixels(5.0)), + padding_bottom: StyleProp::Value(Units::Pixels(5.0)), + ..Style::default() + }; + + let on_delete = on_delete.clone(); + let card_id = *card_id; + let on_event = OnEvent::new(move |_, event| match event.event_type { + EventType::Click => { + on_delete.call(card_id); + } + _ => (), + }); + + rsx! { + <Background styles={Some(background_styles)}> + <Text size={14.0} content={name} /> + <DeleteButton on_event={Some(on_event)} /> + </Background> + } +} diff --git a/examples/todo/cards.rs b/examples/todo/cards.rs new file mode 100644 index 0000000..04e059b --- /dev/null +++ b/examples/todo/cards.rs @@ -0,0 +1,22 @@ +use kayak_core::{constructor, rsx, widget, Handler, VecTracker}; +use kayak_widgets::Element; + +use super::{card::Card, Todo}; + +#[widget] +pub fn Cards(cards: Vec<Todo>, on_delete: Handler<usize>) { + let cards = cards.clone(); + + let on_delete = on_delete.clone(); + rsx! { + <Element> + {VecTracker::from( + cards + .clone() + .into_iter() + .enumerate() + .map(|(index, todo)| constructor! { <Card card_id={index} name={todo.name.clone()} on_delete={on_delete.clone()} /> }), + )} + </Element> + } +} diff --git a/examples/todo/delete_button.rs b/examples/todo/delete_button.rs new file mode 100644 index 0000000..e7a5971 --- /dev/null +++ b/examples/todo/delete_button.rs @@ -0,0 +1,45 @@ +use kayak_ui::core::{ + color::Color, + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp, Units}, + use_state, widget, Bound, Children, EventType, MutableBound, OnEvent, +}; + +use kayak_widgets::{Background, Text}; + +#[widget] +pub fn DeleteButton(children: Children, styles: Option<Style>) { + let (color, set_color) = use_state!(Color::new(0.0781, 0.0898, 0.101, 1.0)); + + let base_styles = styles.clone().unwrap_or_default(); + *styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::Layout), + height: StyleProp::Value(Units::Pixels(32.0)), + width: StyleProp::Value(Units::Pixels(30.0)), + ..base_styles + }); + + let background_styles = Some(Style { + border_radius: StyleProp::Value((5.0, 5.0, 5.0, 5.0)), + background_color: StyleProp::Value(color), + padding_left: StyleProp::Value(Units::Pixels(8.0)), + ..Style::default() + }); + + let on_event = OnEvent::new(move |_, event| match event.event_type { + EventType::MouseIn => { + set_color(Color::new(0.0791, 0.0998, 0.201, 1.0)); + } + EventType::MouseOut => { + set_color(Color::new(0.0781, 0.0898, 0.101, 1.0)); + } + _ => {} + }); + + rsx! { + <Background styles={background_styles} on_event={Some(on_event)}> + <Text content={"X".to_string()} size={20.0} /> + </Background> + } +} diff --git a/examples/todo/todo.rs b/examples/todo/todo.rs new file mode 100644 index 0000000..6685015 --- /dev/null +++ b/examples/todo/todo.rs @@ -0,0 +1,126 @@ +use bevy::{ + prelude::{App as BevyApp, AssetServer, Commands, Res, ResMut}, + window::WindowDescriptor, + DefaultPlugins, +}; +use kayak_ui::bevy::{BevyContext, BevyKayakUIPlugin, FontMapping, UICameraBundle}; +use kayak_ui::core::{ + render, rsx, + styles::{LayoutType, Style, StyleProp, Units}, + use_state, widget, Bound, EventType, Handler, Index, MutableBound, OnEvent, +}; +use kayak_widgets::{App, Element, OnChange, TextBox, Window}; + +mod add_button; +mod card; +mod cards; +mod delete_button; +use add_button::AddButton; +use cards::Cards; + +#[derive(Debug, Clone, PartialEq)] +pub struct Todo { + name: String, +} + +#[widget] +fn TodoApp() { + let (todos, set_todos) = use_state!(vec![ + Todo { + name: "Use bevy to make a game!".to_string(), + }, + Todo { + name: "Help contribute to bevy!".to_string(), + }, + Todo { + name: "Join the bevy discord!".to_string(), + }, + ]); + + let (new_todo_value, set_new_todo_value) = use_state!("".to_string()); + + let text_box_styles = Style { + right: StyleProp::Value(Units::Pixels(10.0)), + ..Style::default() + }; + + let top_area_styles = Style { + layout_type: StyleProp::Value(LayoutType::Row), + bottom: StyleProp::Value(Units::Pixels(10.0)), + height: StyleProp::Value(Units::Pixels(30.0)), + padding_top: StyleProp::Value(Units::Stretch(1.0)), + padding_bottom: StyleProp::Value(Units::Stretch(1.0)), + ..Style::default() + }; + + let on_change = OnChange::new(move |event| { + set_new_todo_value(event.value); + }); + + let new_todo_value_cloned = new_todo_value.clone(); + let mut todos_cloned = todos.clone(); + let cloned_set_todos = set_todos.clone(); + let add_events = OnEvent::new(move |_, event| match event.event_type { + EventType::Click => { + if !new_todo_value_cloned.is_empty() { + todos_cloned.push(Todo { + name: new_todo_value_cloned.clone(), + }); + cloned_set_todos(todos_cloned.clone()); + } + } + _ => {} + }); + + let mut todos_cloned = todos.clone(); + let cloned_set_todos = set_todos.clone(); + let handle_delete = Handler::new(move |card_id: usize| { + todos_cloned.remove(card_id); + cloned_set_todos(todos_cloned.clone()); + }); + + let placeholder = Some("Type here to add a new todo!".to_string()); + rsx! { + <Window position={(415.0, 50.0)} size={(450.0, 600.0)} title={"Todo!".to_string()}> + <Element styles={Some(top_area_styles)}> + <TextBox styles={Some(text_box_styles)} value={new_todo_value} placeholder={placeholder} on_change={Some(on_change)} /> + <AddButton on_event={Some(add_events)} /> + </Element> + <Cards cards={todos} on_delete={handle_delete} /> + </Window> + } +} + +fn startup( + mut commands: Commands, + mut font_mapping: ResMut<FontMapping>, + asset_server: Res<AssetServer>, +) { + commands.spawn_bundle(UICameraBundle::new()); + + font_mapping.add(asset_server.load("roboto.kayak_font")); + + let context = BevyContext::new(|context| { + render! { + <App> + <TodoApp /> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs index 9fa9138..0a12999 100644 --- a/kayak_core/src/lib.rs +++ b/kayak_core/src/lib.rs @@ -32,6 +32,9 @@ pub use resources::Resources; pub use tree::{Tree, WidgetTree}; pub use vec::VecTracker; pub use widget::Widget; +pub mod derivative { + pub use derivative::*; +} pub type Children = Option< Arc<dyn Fn(WidgetTree, Option<crate::Index>, &mut crate::context::KayakContext) + Send + Sync>, @@ -52,6 +55,29 @@ impl OnEvent { } } -pub mod derivative { - pub use derivative::*; +#[derive(Clone)] +pub struct Handler<T>(pub Arc<RwLock<dyn FnMut(T) + Send + Sync + 'static>>); + +impl<T> Handler<T> { + pub fn new<F: FnMut(T) + Send + Sync + 'static>(f: F) -> Handler<T> { + Handler(Arc::new(RwLock::new(f))) + } + + pub fn call(&self, data: T) { + if let Ok(mut handler) = self.0.write() { + handler(data); + } + } +} + +impl<T> PartialEq for Handler<T> { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl<T> std::fmt::Debug for Handler<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Handler").finish() + } } diff --git a/kayak_core/src/node.rs b/kayak_core/src/node.rs index 9bd99bd..91023eb 100644 --- a/kayak_core/src/node.rs +++ b/kayak_core/src/node.rs @@ -116,11 +116,29 @@ impl<'a> morphorm::Node<'a> for Index { return Some(morphorm::Units::Stretch(1.0)); } - fn min_width(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + fn min_width(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(*self) { + if let Some(node) = node { + return match node.styles.min_width { + StyleProp::Default => Some(morphorm::Units::Stretch(1.0)), + StyleProp::Value(prop) => Some(prop), + _ => Some(morphorm::Units::Auto), + }; + } + } Some(morphorm::Units::Auto) } - fn min_height(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { + fn min_height(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { + if let Some(node) = store.get(*self) { + if let Some(node) = node { + return match node.styles.min_height { + StyleProp::Default => Some(morphorm::Units::Stretch(1.0)), + StyleProp::Value(prop) => Some(prop), + _ => Some(morphorm::Units::Auto), + }; + } + } Some(morphorm::Units::Auto) } diff --git a/kayak_core/src/styles.rs b/kayak_core/src/styles.rs index a1671dc..850f17b 100644 --- a/kayak_core/src/styles.rs +++ b/kayak_core/src/styles.rs @@ -53,6 +53,8 @@ pub struct Style { pub margin_right: StyleProp<Units>, pub margin_top: StyleProp<Units>, pub margin_bottom: StyleProp<Units>, + pub min_width: StyleProp<Units>, + pub min_height: StyleProp<Units>, } impl Default for Style { @@ -78,6 +80,8 @@ impl Default for Style { margin_right: StyleProp::Default, margin_top: StyleProp::Default, margin_bottom: StyleProp::Default, + min_width: StyleProp::Default, + min_height: StyleProp::Default, } } } diff --git a/kayak_core/src/vec.rs b/kayak_core/src/vec.rs index ff8ff99..5bc2776 100644 --- a/kayak_core/src/vec.rs +++ b/kayak_core/src/vec.rs @@ -3,7 +3,7 @@ use derivative::*; use crate::{context::KayakContext, styles::Style, Index, Widget}; #[derive(Derivative)] -#[derivative(Debug, PartialEq)] +#[derivative(Debug, PartialEq, Clone)] pub struct VecTracker<T> { pub id: Index, pub styles: Option<Style>, diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs index c10c225..e8a54ff 100644 --- a/kayak_core/src/widget_manager.rs +++ b/kayak_core/src/widget_manager.rs @@ -256,6 +256,7 @@ impl WidgetManager { nodes: &Arena<Option<Node>>, current_node: Index, mut main_z_index: f32, + mut prev_clip: RenderPrimitive, ) -> Vec<RenderPrimitive> { let mut render_primitives = Vec::new(); @@ -272,6 +273,14 @@ impl WidgetManager { render_primitive.set_layout(layout); render_primitives.push(render_primitive.clone()); + let new_prev_clip = if matches!(render_primitive, RenderPrimitive::Clip { .. }) { + render_primitive.clone() + } else { + prev_clip + }; + + prev_clip = new_prev_clip.clone(); + if node_tree.children.contains_key(¤t_node) { for child in node_tree.children.get(¤t_node).unwrap() { main_z_index += 1.0; @@ -281,22 +290,36 @@ impl WidgetManager { nodes, *child, main_z_index, + new_prev_clip.clone(), )); main_z_index = layout.z_index; // Between each child node we need to reset the clip. - if matches!(render_primitive, RenderPrimitive::Clip { .. }) { - main_z_index = new_z_index; - match &mut render_primitive { + if matches!(prev_clip, RenderPrimitive::Clip { .. }) { + // main_z_index = new_z_index; + match &mut prev_clip { RenderPrimitive::Clip { layout } => { layout.z_index = main_z_index + 0.1; } _ => {} }; - render_primitives.push(render_primitive.clone()); + render_primitives.push(prev_clip.clone()); } } } + + // if matches!(render_primitive, RenderPrimitive::Clip { .. }) + // && matches!(prev_clip, RenderPrimitive::Clip { .. }) + // { + // // main_z_index = new_z_index; + // match &mut prev_clip { + // RenderPrimitive::Clip { layout } => { + // layout.z_index = main_z_index + 0.1; + // } + // _ => {} + // }; + // render_primitives.push(render_primitive.clone()); + // } } } @@ -310,6 +333,7 @@ impl WidgetManager { &self.nodes, self.node_tree.root_node.unwrap(), 0.0, + RenderPrimitive::Empty, ) } diff --git a/kayak_render_macros/src/lib.rs b/kayak_render_macros/src/lib.rs index 916372f..0d66f43 100644 --- a/kayak_render_macros/src/lib.rs +++ b/kayak_render_macros/src/lib.rs @@ -104,7 +104,6 @@ pub fn dyn_partial_eq(_: TokenStream, input: TokenStream) -> TokenStream { .into() } - /// Create a state and its setter /// /// # Arguments @@ -151,4 +150,4 @@ pub fn use_state(initial_state: TokenStream) -> TokenStream { (state.get(), set_state) }}; TokenStream::from(result) -} \ No newline at end of file +} diff --git a/kayak_widgets/src/element.rs b/kayak_widgets/src/element.rs index be6c883..3c14e83 100644 --- a/kayak_widgets/src/element.rs +++ b/kayak_widgets/src/element.rs @@ -1,7 +1,17 @@ -use kayak_ui::core::{rsx, widget, Children}; +use kayak_ui::core::{ + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, Children, +}; #[widget] pub fn Element(children: Children) { + *styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::Layout), + ..styles.clone().unwrap_or_default() + }); + rsx! { <> {children} diff --git a/kayak_widgets/src/text_box.rs b/kayak_widgets/src/text_box.rs index 0da96fe..3ba7dc4 100644 --- a/kayak_widgets/src/text_box.rs +++ b/kayak_widgets/src/text_box.rs @@ -38,9 +38,12 @@ impl std::fmt::Debug for OnChange { pub struct Focus(pub bool); #[widget(focusable)] -pub fn TextBox(value: String, on_change: Option<OnChange>) { +pub fn TextBox(value: String, on_change: Option<OnChange>, placeholder: Option<String>) { *styles = Some(Style { render_command: StyleProp::Value(RenderCommand::Layout), + height: StyleProp::Value(Units::Pixels(26.0)), + top: StyleProp::Value(Units::Pixels(0.0)), + bottom: StyleProp::Value(Units::Pixels(0.0)), ..styles.clone().unwrap_or_default() }); @@ -83,7 +86,11 @@ pub fn TextBox(value: String, on_change: Option<OnChange>) { _ => {} })); - let value = value.clone(); + let value = if value.is_empty() { + placeholder.clone().unwrap_or(value.clone()) + } else { + value.clone() + }; rsx! { <Background styles={Some(background_styles)}> <Clip> diff --git a/kayak_widgets/src/window.rs b/kayak_widgets/src/window.rs index 186bec3..c2cdee3 100644 --- a/kayak_widgets/src/window.rs +++ b/kayak_widgets/src/window.rs @@ -56,10 +56,10 @@ pub fn Window( }; let content_styles = Style { - padding_left: StyleProp::Value(Units::Stretch(1.0)), - padding_right: StyleProp::Value(Units::Stretch(1.0)), - padding_top: StyleProp::Value(Units::Stretch(1.0)), - padding_bottom: StyleProp::Value(Units::Stretch(1.0)), + padding_left: StyleProp::Value(Units::Pixels(10.0)), + padding_right: StyleProp::Value(Units::Pixels(10.0)), + padding_top: StyleProp::Value(Units::Pixels(10.0)), + padding_bottom: StyleProp::Value(Units::Pixels(10.0)), ..Style::default() }; -- GitLab