From 6cfb52bb792b41325b5dcf4ed8a9cdde9f22f16d Mon Sep 17 00:00:00 2001 From: StarToaster <startoaster23@gmail.com> Date: Sun, 23 Oct 2022 19:32:36 -0400 Subject: [PATCH] Updates, Props, State, and Context version 2.0! --- Cargo.toml | 1 + examples/context.rs | 343 ++++++++++---------- examples/demo.rs | 74 +++++ examples/quads.rs | 63 +++- examples/simple.rs | 86 ----- examples/simple_state.rs | 43 ++- examples/tabs/tab.rs | 59 ++-- examples/tabs/tab_button.rs | 93 +++--- examples/tabs/tab_context.rs | 41 +-- examples/tabs/tabs.rs | 62 ++-- examples/text.rs | 10 +- examples/text_box.rs | 51 ++- examples/todo/input.rs | 11 +- examples/todo/items.rs | 17 +- examples/todo/todo.rs | 27 +- examples/vec.rs | 10 +- kayak_ui_macros/src/widget.rs | 4 +- src/calculate_nodes.rs | 22 +- src/children.rs | 2 +- src/clone_component.rs | 47 +++ src/context.rs | 229 +++++++++++-- src/event_dispatcher.rs | 10 +- src/lib.rs | 4 + src/on_event.rs | 20 +- src/render/extract.rs | 11 +- src/styles/style.rs | 2 +- src/widget.rs | 144 ++++++++- src/widget_context.rs | 25 +- src/widget_state.rs | 43 +++ src/widgets/app.rs | 47 +-- src/widgets/background.rs | 20 +- src/widgets/button.rs | 15 +- src/widgets/clip.rs | 14 +- src/widgets/element.rs | 17 +- src/widgets/image.rs | 5 +- src/widgets/mod.rs | 103 +++++- src/widgets/nine_patch.rs | 16 +- src/widgets/scroll/scroll_bar.rs | 465 +++++++++++++-------------- src/widgets/scroll/scroll_box.rs | 302 +++++++++-------- src/widgets/scroll/scroll_content.rs | 103 +++--- src/widgets/scroll/scroll_context.rs | 21 +- src/widgets/text.rs | 25 +- src/widgets/text_box.rs | 216 ++++++------- src/widgets/texture_atlas.rs | 5 +- src/widgets/window.rs | 220 ++++++------- 45 files changed, 1849 insertions(+), 1299 deletions(-) create mode 100644 examples/demo.rs delete mode 100644 examples/simple.rs create mode 100644 src/clone_component.rs create mode 100644 src/widget_state.rs diff --git a/Cargo.toml b/Cargo.toml index 394a145..be47d22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ kayak_font = { path = "./kayak_font" } morphorm = { git = "https://github.com/geom3trik/morphorm", rev = "1243152d4cebea46fd3e5098df26402c73acae91" } kayak_ui_macros = { path = "./kayak_ui_macros" } indexmap = "1.9" +log = "0.4" [dev-dependencies] fastrand = "1.8" diff --git a/examples/context.rs b/examples/context.rs index c37b684..f82b831 100644 --- a/examples/context.rs +++ b/examples/context.rs @@ -9,8 +9,8 @@ use bevy::{ prelude::{ - App as BevyApp, AssetServer, Bundle, Changed, Color, Commands, Component, Entity, - ImageSettings, In, ParamSet, Query, Res, ResMut, Vec2, + App as BevyApp, AssetServer, Bundle, Color, Commands, Component, Entity, ImageSettings, In, + Query, Res, ResMut, Vec2, }, DefaultPlugins, }; @@ -52,15 +52,17 @@ impl Theme { } } -#[derive(Component, Debug, Default, Clone)] +#[derive(Component, Debug, Default, Clone, PartialEq)] struct ThemeButton { pub theme: Theme, } impl Widget for ThemeButton {} +impl WidgetProps for ThemeButton {} #[derive(Bundle)] pub struct ThemeButtonBundle { theme_button: ThemeButton, + styles: KStyle, widget_name: WidgetName, } @@ -68,6 +70,7 @@ impl Default for ThemeButtonBundle { fn default() -> Self { Self { theme_button: Default::default(), + styles: KStyle::default(), widget_name: ThemeButton::default().get_name(), } } @@ -77,76 +80,74 @@ fn update_theme_button( In((widget_context, theme_button_entity)): In<(WidgetContext, Entity)>, mut commands: Commands, query: Query<&ThemeButton>, - changed_query: Query<&ThemeButton, Changed<ThemeButton>>, - mut context_query: ParamSet<(Query<&mut Theme>, Query<&Theme, Changed<Theme>>)>, + mut context_query: Query<&mut Theme>, ) -> bool { - if !context_query.p1().is_empty() || !changed_query.is_empty() { - if let Ok(theme_button) = query.get(theme_button_entity) { - if let Some(theme_context_entity) = - widget_context.get_context_entity::<Theme>(theme_button_entity) - { - if let Ok(theme) = context_query.p0().get_mut(theme_context_entity) { - let mut box_style = KStyle { - width: StyleProp::Value(Units::Pixels(30.0)), - height: StyleProp::Value(Units::Pixels(30.0)), - background_color: StyleProp::Value(theme_button.theme.primary), - ..Default::default() - }; - - if theme_button.theme.name == theme.name { - box_style.top = StyleProp::Value(Units::Pixels(3.0)); - box_style.left = StyleProp::Value(Units::Pixels(3.0)); - box_style.bottom = StyleProp::Value(Units::Pixels(3.0)); - box_style.right = StyleProp::Value(Units::Pixels(3.0)); - box_style.width = StyleProp::Value(Units::Pixels(24.0)); - box_style.height = StyleProp::Value(Units::Pixels(24.0)); - } + if let Ok(theme_button) = query.get(theme_button_entity) { + if let Some(theme_context_entity) = + widget_context.get_context_entity::<Theme>(theme_button_entity) + { + if let Ok(theme) = context_query.get_mut(theme_context_entity) { + let mut box_style = KStyle { + width: StyleProp::Value(Units::Pixels(30.0)), + height: StyleProp::Value(Units::Pixels(30.0)), + background_color: StyleProp::Value(theme_button.theme.primary), + ..Default::default() + }; + + if theme_button.theme.name == theme.name { + box_style.top = StyleProp::Value(Units::Pixels(3.0)); + box_style.left = StyleProp::Value(Units::Pixels(3.0)); + box_style.bottom = StyleProp::Value(Units::Pixels(3.0)); + box_style.right = StyleProp::Value(Units::Pixels(3.0)); + box_style.width = StyleProp::Value(Units::Pixels(24.0)); + box_style.height = StyleProp::Value(Units::Pixels(24.0)); + } - let parent_id = Some(theme_button_entity); - rsx! { - <BackgroundBundle - styles={box_style} - on_event={OnEvent::new( - move |In((event_dispatcher_context, event, _entity)): In<( - EventDispatcherContext, - Event, - Entity, - )>, - query: Query<&ThemeButton>, - mut context_query: Query<&mut Theme>, - | { - match event.event_type { - EventType::Click(..) => { - if let Ok(button) = query.get(theme_button_entity) { - if let Ok(mut context_theme) = context_query.get_mut(theme_context_entity) { - *context_theme = button.theme.clone(); - } + let parent_id = Some(theme_button_entity); + rsx! { + <BackgroundBundle + styles={box_style} + on_event={OnEvent::new( + move |In((event_dispatcher_context, _, event, _entity)): In<( + EventDispatcherContext, + WidgetState, + Event, + Entity, + )>, + query: Query<&ThemeButton>, + mut context_query: Query<&mut Theme>, + | { + match event.event_type { + EventType::Click(..) => { + if let Ok(button) = query.get(theme_button_entity) { + if let Ok(mut context_theme) = context_query.get_mut(theme_context_entity) { + *context_theme = button.theme.clone(); } - }, - _ => {} - } - (event_dispatcher_context, event) - }, - )} - /> - } - - return true; + } + }, + _ => {} + } + (event_dispatcher_context, event) + }, + )} + /> } } } } - false + true } -#[derive(Component, Debug, Default, Clone)] +#[derive(Component, Debug, Default, Clone, PartialEq)] struct ThemeSelector; impl Widget for ThemeSelector {} +impl WidgetProps for ThemeSelector {} #[derive(Bundle)] pub struct ThemeSelectorBundle { theme_selector: ThemeSelector, + styles: KStyle, widget_name: WidgetName, } @@ -154,6 +155,11 @@ impl Default for ThemeSelectorBundle { fn default() -> Self { Self { theme_selector: Default::default(), + styles: KStyle { + height: StyleProp::Value(Units::Auto), + padding_bottom: Units::Pixels(40.0).into(), + ..Default::default() + }, widget_name: ThemeSelector::default().get_name(), } } @@ -162,7 +168,7 @@ impl Default for ThemeSelectorBundle { fn update_theme_selector( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - query: Query<&ThemeSelector, Changed<ThemeSelector>>, + query: Query<&ThemeSelector>, ) -> bool { if let Ok(_) = query.get(entity) { let button_container_style = KStyle { @@ -185,23 +191,23 @@ fn update_theme_selector( <ThemeButtonBundle theme_button={ThemeButton { theme: vector_theme }} /> </ElementBundle> } - - return true; } - false + true } -#[derive(Component, Debug, Default, Clone)] +#[derive(Component, Debug, Default, Clone, PartialEq)] pub struct ThemeDemo { is_root: bool, context_entity: Option<Entity>, } impl Widget for ThemeDemo {} +impl WidgetProps for ThemeDemo {} #[derive(Bundle)] pub struct ThemeDemoBundle { theme_demo: ThemeDemo, + styles: KStyle, widget_name: WidgetName, } @@ -209,6 +215,7 @@ impl Default for ThemeDemoBundle { fn default() -> Self { Self { theme_demo: Default::default(), + styles: KStyle::default(), widget_name: ThemeDemo::default().get_name(), } } @@ -217,126 +224,121 @@ impl Default for ThemeDemoBundle { fn update_theme_demo( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - mut query_set: ParamSet<(Query<&mut ThemeDemo>, Query<&ThemeDemo, Changed<ThemeDemo>>)>, + mut query_set: Query<&mut ThemeDemo>, theme_context: Query<&Theme>, - detect_changes_query: Query<&Theme, Changed<Theme>>, ) -> bool { - if !detect_changes_query.is_empty() || !query_set.p1().is_empty() { - if let Ok(mut theme_demo) = query_set.p0().get_mut(entity) { - if let Some(theme_context_entity) = widget_context.get_context_entity::<Theme>(entity) { - if let Ok(theme) = theme_context.get(theme_context_entity) { - let select_lbl = if theme_demo.is_root { - format!("Select Theme (Current: {})", theme.name) - } else { - format!("Select A Different Theme (Current: {})", theme.name) - }; - - if theme_demo.is_root { - if theme_demo.context_entity.is_none() { - let theme_entity = commands.spawn(Theme::vector()).id(); - theme_demo.context_entity = Some(theme_entity); - } + if let Ok(mut theme_demo) = query_set.get_mut(entity) { + if let Some(theme_context_entity) = widget_context.get_context_entity::<Theme>(entity) { + if let Ok(theme) = theme_context.get(theme_context_entity) { + let select_lbl = if theme_demo.is_root { + format!("Select Theme (Current: {})", theme.name) + } else { + format!("Select A Different Theme (Current: {})", theme.name) + }; + + if theme_demo.is_root { + if theme_demo.context_entity.is_none() { + let theme_entity = commands.spawn(Theme::vector()).id(); + theme_demo.context_entity = Some(theme_entity); } + } - let context_entity = if let Some(entity) = theme_demo.context_entity { - entity - } else { - Entity::from_raw(1000000) - }; - let text_styles = KStyle { - color: StyleProp::Value(theme.primary), - height: StyleProp::Value(Units::Pixels(28.0)), - ..Default::default() - }; - let btn_style = KStyle { - background_color: StyleProp::Value(theme.secondary), - width: StyleProp::Value(Units::Stretch(0.75)), - height: StyleProp::Value(Units::Pixels(32.0)), - top: StyleProp::Value(Units::Pixels(5.0)), - left: StyleProp::Value(Units::Stretch(1.0)), - right: StyleProp::Value(Units::Stretch(1.0)), - ..Default::default() - }; - - let parent_id = Some(entity); - let mut children = kayak_ui::prelude::KChildren::new(); - rsx! { - <> + let context_entity = if let Some(entity) = theme_demo.context_entity { + entity + } else { + Entity::from_raw(1000000) + }; + let text_styles = KStyle { + color: StyleProp::Value(theme.primary), + height: StyleProp::Value(Units::Pixels(28.0)), + ..Default::default() + }; + let btn_style = KStyle { + background_color: StyleProp::Value(theme.secondary), + width: StyleProp::Value(Units::Stretch(0.75)), + height: StyleProp::Value(Units::Pixels(32.0)), + top: StyleProp::Value(Units::Pixels(5.0)), + left: StyleProp::Value(Units::Stretch(1.0)), + right: StyleProp::Value(Units::Stretch(1.0)), + ..Default::default() + }; + + let parent_id = Some(entity); + let mut children = kayak_ui::prelude::KChildren::new(); + rsx! { + <> + <TextWidgetBundle + text={TextProps { + content: select_lbl, + size: 14.0, + line_height: Some(28.0), + ..Default::default() + }} + styles={KStyle { + height: StyleProp::Value(Units::Pixels(28.0)), + ..Default::default() + }} + /> + <ThemeSelectorBundle /> + <BackgroundBundle + styles={KStyle { + background_color: StyleProp::Value(theme.background), + top: StyleProp::Value(Units::Pixels(15.0)), + width: StyleProp::Value(Units::Stretch(1.0)), + height: StyleProp::Value(Units::Stretch(1.0)), + ..Default::default() + }} + > <TextWidgetBundle text={TextProps { - content: select_lbl, - size: 14.0, - line_height: Some(28.0), - ..Default::default() - }} - styles={KStyle { - height: StyleProp::Value(Units::Pixels(28.0)), + content: "Lorem ipsum dolor...".into(), + size: 12.0, ..Default::default() }} + styles={text_styles.clone()} /> - <ThemeSelectorBundle /> - <BackgroundBundle - styles={KStyle { - background_color: StyleProp::Value(theme.background), - top: StyleProp::Value(Units::Pixels(15.0)), - width: StyleProp::Value(Units::Stretch(1.0)), - height: StyleProp::Value(Units::Stretch(1.0)), - ..Default::default() - }} + <KButtonBundle + styles={btn_style.clone()} > <TextWidgetBundle text={TextProps { - content: "Lorem ipsum dolor...".into(), - size: 12.0, + content: "BUTTON".into(), + size: 14.0, ..Default::default() }} - styles={text_styles.clone()} /> - <KButtonBundle - styles={btn_style.clone()} - > - <TextWidgetBundle - text={TextProps { - content: "BUTTON".into(), - size: 14.0, - ..Default::default() - }} - /> - </KButtonBundle> - { - if theme_demo.is_root { - widget_context.set_context_entity::<Theme>( - parent_id, - context_entity, - ); - constructor! { - <ElementBundle - styles={KStyle { - top: StyleProp::Value(Units::Pixels(10.0)), - left: StyleProp::Value(Units::Pixels(10.0)), - bottom: StyleProp::Value(Units::Pixels(10.0)), - right: StyleProp::Value(Units::Pixels(10.0)), - ..Default::default() - }} - > - <ThemeDemoBundle /> - </ElementBundle> - } + </KButtonBundle> + { + if theme_demo.is_root { + widget_context.set_context_entity::<Theme>( + parent_id, + context_entity, + ); + constructor! { + <ElementBundle + styles={KStyle { + top: StyleProp::Value(Units::Pixels(10.0)), + left: StyleProp::Value(Units::Pixels(10.0)), + bottom: StyleProp::Value(Units::Pixels(10.0)), + right: StyleProp::Value(Units::Pixels(10.0)), + ..Default::default() + }} + > + <ThemeDemoBundle /> + </ElementBundle> } } - </BackgroundBundle> - </> - } - - children.process(&widget_context, parent_id); - - return true; + } + </BackgroundBundle> + </> } + + children.process(&widget_context, parent_id); } } } - false + true } fn startup( @@ -349,9 +351,24 @@ fn startup( commands.spawn(UICameraBundle::new()); let mut widget_context = Context::new(); - widget_context.add_widget_system(ThemeDemo::default().get_name(), update_theme_demo); - widget_context.add_widget_system(ThemeButton::default().get_name(), update_theme_button); - widget_context.add_widget_system(ThemeSelector::default().get_name(), update_theme_selector); + widget_context.add_widget_data::<ThemeDemo, EmptyState>(); + widget_context.add_widget_data::<ThemeButton, EmptyState>(); + widget_context.add_widget_data::<ThemeSelector, EmptyState>(); + widget_context.add_widget_system( + ThemeDemo::default().get_name(), + widget_update_with_context::<ThemeDemo, EmptyState, Theme>, + update_theme_demo, + ); + widget_context.add_widget_system( + ThemeButton::default().get_name(), + widget_update_with_context::<ThemeButton, EmptyState, Theme>, + update_theme_button, + ); + widget_context.add_widget_system( + ThemeSelector::default().get_name(), + widget_update_with_context::<ThemeSelector, EmptyState, Theme>, + update_theme_selector, + ); let parent_id = None; rsx! { <KayakAppBundle> diff --git a/examples/demo.rs b/examples/demo.rs new file mode 100644 index 0000000..96e2a20 --- /dev/null +++ b/examples/demo.rs @@ -0,0 +1,74 @@ +use bevy::prelude::*; +use kayak_ui::prelude::{widgets::*, *}; + +#[derive(Debug, Component, Default, Clone, PartialEq)] +pub struct MyWidget { + pub foo: u32, +} + +fn my_widget_1_render( + In((_widget_context, entity)): In<(WidgetContext, Entity)>, + mut _commands: Commands, + query: Query<&MyWidget>, +) -> bool { + if let Ok(my_widget) = query.get(entity) { + dbg!(my_widget.foo); + } + + true +} + +impl Widget for MyWidget {} +impl WidgetProps for MyWidget {} + +fn startup(mut commands: Commands) { + let mut context = Context::new(); + context.add_widget_system( + MyWidget::default().get_name(), + widget_update::<MyWidget, EmptyState>, + my_widget_1_render, + ); + context.add_widget_data::<MyWidget, EmptyState>(); + + let app_entity = commands.spawn_empty().id(); + let mut children = KChildren::default(); + let entity = commands + .spawn(( + MyWidget { foo: 0 }, + kayak_ui::prelude::KStyle::default(), + MyWidget::default().get_name(), + )) + .id(); + children.add(entity); + + commands.entity(app_entity).insert(KayakAppBundle { + children, + ..KayakAppBundle::default() + }); + context.add_widget(None, app_entity); + + commands.insert_resource(context); +} + +// Note this example shows prop changing not state changing which is quite different. +// For state changes please see simple_state example. +fn update_resource( + keyboard_input: Res<Input<KeyCode>>, + mut query: Query<&mut MyWidget, Without<PreviousWidget>>, +) { + if keyboard_input.just_pressed(KeyCode::Space) { + for mut my_widget in query.iter_mut() { + my_widget.foo += 1; + } + } +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(ContextPlugin) + .add_plugin(KayakWidgets) + .add_startup_system(startup) + .add_system(update_resource) + .run() +} diff --git a/examples/quads.rs b/examples/quads.rs index 7ad875f..d59e9cc 100644 --- a/examples/quads.rs +++ b/examples/quads.rs @@ -1,15 +1,15 @@ use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::{ - App as BevyApp, AssetServer, Bundle, Changed, Color, Commands, Component, Entity, In, - Query, Res, ResMut, Vec2, + App as BevyApp, AssetServer, Bundle, Color, Commands, Component, Entity, In, Query, Res, + ResMut, Vec2, }, DefaultPlugins, }; use kayak_ui::prelude::{widgets::*, KStyle, *}; use morphorm::{PositionType, Units}; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] pub struct MyQuad { pos: Vec2, pub size: Vec2, @@ -18,28 +18,56 @@ pub struct MyQuad { fn my_quad_update( In((_widget_context, entity)): In<(WidgetContext, Entity)>, - mut query: Query<(&MyQuad, &mut KStyle), Changed<MyQuad>>, + mut query: Query<(&MyQuad, &mut KStyle, &mut OnEvent)>, ) -> bool { - if let Ok((quad, mut style)) = query.get_mut(entity) { - style.render_command = StyleProp::Value(RenderCommand::Quad); - style.position_type = StyleProp::Value(PositionType::SelfDirected); - style.left = StyleProp::Value(Units::Pixels(quad.pos.x)); - style.top = StyleProp::Value(Units::Pixels(quad.pos.y)); - style.width = StyleProp::Value(Units::Pixels(quad.size.x)); - style.height = StyleProp::Value(Units::Pixels(quad.size.y)); - style.background_color = StyleProp::Value(quad.color); - return true; + if let Ok((quad, mut style, mut on_event)) = query.get_mut(entity) { + if style.render_command.resolve() != RenderCommand::Quad { + style.render_command = StyleProp::Value(RenderCommand::Quad); + style.position_type = StyleProp::Value(PositionType::SelfDirected); + style.left = StyleProp::Value(Units::Pixels(quad.pos.x)); + style.top = StyleProp::Value(Units::Pixels(quad.pos.y)); + style.width = StyleProp::Value(Units::Pixels(quad.size.x)); + style.height = StyleProp::Value(Units::Pixels(quad.size.y)); + style.background_color = StyleProp::Value(quad.color); + } + + *on_event = OnEvent::new( + move |In((event_dispatcher_context, _, event, entity)): In<( + EventDispatcherContext, + WidgetState, + Event, + Entity, + )>, + mut query: Query<(&mut KStyle, &MyQuad)>| { + match event.event_type { + EventType::MouseIn(..) => { + if let Ok((mut styles, _)) = query.get_mut(entity) { + styles.background_color = StyleProp::Value(Color::WHITE); + } + } + EventType::MouseOut(..) => { + if let Ok((mut styles, my_quad)) = query.get_mut(entity) { + styles.background_color = StyleProp::Value(my_quad.color); + } + } + _ => {} + } + (event_dispatcher_context, event) + }, + ); } - false + true } impl Widget for MyQuad {} +impl WidgetProps for MyQuad {} #[derive(Bundle)] pub struct MyQuadBundle { my_quad: MyQuad, styles: KStyle, + on_event: OnEvent, widget_name: WidgetName, } @@ -48,6 +76,7 @@ impl Default for MyQuadBundle { Self { my_quad: Default::default(), styles: KStyle::default(), + on_event: OnEvent::default(), widget_name: MyQuad::default().get_name(), } } @@ -63,7 +92,11 @@ fn startup( commands.spawn(UICameraBundle::new()); let mut widget_context = Context::new(); - widget_context.add_widget_system(MyQuad::default().get_name(), my_quad_update); + widget_context.add_widget_system( + MyQuad::default().get_name(), + widget_update::<MyQuad, EmptyState>, + my_quad_update, + ); let parent_id = None; rsx! { diff --git a/examples/simple.rs b/examples/simple.rs deleted file mode 100644 index c3f65f8..0000000 --- a/examples/simple.rs +++ /dev/null @@ -1,86 +0,0 @@ -use bevy::prelude::*; -use kayak_ui::prelude::*; - -#[derive(Component, Default)] -pub struct MyWidget2 { - bar: u32, -} - -fn my_widget_2_update( - In((_widget_context, entity)): In<(WidgetContext, Entity)>, - query: Query<&MyWidget2, Or<(Added<MyWidget2>, Changed<MyWidget2>)>>, -) -> bool { - if let Ok(my_widget2) = query.get(entity) { - dbg!(my_widget2.bar); - } - - true -} - -impl Widget for MyWidget2 {} - -#[derive(Component, Default)] -pub struct MyWidget { - pub foo: u32, -} - -fn my_widget_1_update( - In((widget_context, entity)): In<(WidgetContext, Entity)>, - mut commands: Commands, - my_resource: Res<MyResource>, - mut query: Query<&mut MyWidget>, -) -> bool { - if my_resource.is_changed() { - if let Ok(mut my_widget) = query.get_mut(entity) { - my_widget.foo = my_resource.0; - dbg!(my_widget.foo); - - let my_child = MyWidget2 { bar: my_widget.foo }; - let should_update = my_widget.foo == my_child.bar; - let child_id = commands - .spawn((my_child, MyWidget2::default().get_name())) - .id(); - widget_context.add_widget(Some(entity), child_id); - - return should_update; - } - } - - false -} - -impl Widget for MyWidget {} - -#[derive(Resource)] -pub struct MyResource(pub u32); - -fn startup(mut commands: Commands) { - let mut context = Context::new(); - context.add_widget_system(MyWidget::default().get_name(), my_widget_1_update); - context.add_widget_system(MyWidget2::default().get_name(), my_widget_2_update); - let entity = commands - .spawn(( - MyWidget { foo: 0 }, - kayak_ui::prelude::KStyle::default(), - MyWidget::default().get_name(), - )) - .id(); - context.add_widget(None, entity); - commands.insert_resource(context); -} - -fn update_resource(keyboard_input: Res<Input<KeyCode>>, mut my_resource: ResMut<MyResource>) { - if keyboard_input.just_pressed(KeyCode::Space) { - my_resource.0 += 1; - } -} - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_plugin(ContextPlugin) - .insert_resource(MyResource(1)) - .add_startup_system(startup) - .add_system(update_resource) - .run() -} diff --git a/examples/simple_state.rs b/examples/simple_state.rs index 8d9422c..6fa8f47 100644 --- a/examples/simple_state.rs +++ b/examples/simple_state.rs @@ -1,16 +1,22 @@ use bevy::{ prelude::{ - App as BevyApp, AssetServer, Bundle, Changed, Commands, Component, Entity, In, Query, Res, - ResMut, Vec2, + App as BevyApp, AssetServer, Bundle, Commands, Component, Entity, In, Query, Res, ResMut, + Vec2, }, DefaultPlugins, }; use kayak_ui::prelude::{widgets::*, *}; -#[derive(Component, Default)] -struct CurrentCount(pub u32); +#[derive(Component, Default, PartialEq, Clone)] +struct CurrentCount; impl Widget for CurrentCount {} +impl WidgetProps for CurrentCount {} + +#[derive(Component, Default, PartialEq, Clone)] +struct CurrentCountState { + foo: u32, +} #[derive(Bundle)] struct CurrentCountBundle { @@ -29,18 +35,20 @@ impl Default for CurrentCountBundle { } } -fn current_count_update( +fn current_count_render( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - query: Query<&CurrentCount, Changed<CurrentCount>>, + query: Query<&CurrentCountState>, ) -> bool { - if let Ok(current_count) = query.get(entity) { + let state_entity = + widget_context.use_state(&mut commands, entity, CurrentCountState::default()); + if let Ok(current_count) = query.get(state_entity) { let parent_id = Some(entity); rsx! { <TextWidgetBundle text={ TextProps { - content: format!("Current Count: {}", current_count.0).into(), + content: format!("Current Count: {}", current_count.foo).into(), size: 16.0, line_height: Some(40.0), ..Default::default() @@ -66,7 +74,12 @@ fn startup( let mut widget_context = Context::new(); let parent_id = None; - widget_context.add_widget_system(CurrentCount::default().get_name(), current_count_update); + widget_context.add_widget_data::<CurrentCount, CurrentCountState>(); + widget_context.add_widget_system( + CurrentCount::default().get_name(), + widget_update::<CurrentCount, CurrentCountState>, + current_count_render, + ); rsx! { <KayakAppBundle> <WindowBundle @@ -81,14 +94,14 @@ fn startup( <CurrentCountBundle id={"current_count_entity"} /> <KButtonBundle on_event={OnEvent::new( - move |In((event_dispatcher_context, event, _entity)): In<(EventDispatcherContext, Event, Entity)>, - mut query: Query<&mut CurrentCount>| { + move |In((event_dispatcher_context, widget_state, event, _entity)): In<(EventDispatcherContext, WidgetState, Event, Entity)>, + mut query: Query<&mut CurrentCountState>| { match event.event_type { EventType::Click(..) => { - if let Ok(mut current_count) = - query.get_mut(current_count_entity) - { - current_count.0 += 1; + if let Some(state_entity) = widget_state.get(current_count_entity) { + if let Ok(mut current_count) = query.get_mut(state_entity) { + current_count.foo += 1; + } } } _ => {} diff --git a/examples/tabs/tab.rs b/examples/tabs/tab.rs index 314138e..9041a30 100644 --- a/examples/tabs/tab.rs +++ b/examples/tabs/tab.rs @@ -1,24 +1,24 @@ -use bevy::prelude::{ - Bundle, ChangeTrackers, Changed, Color, Commands, Component, Entity, In, ParamSet, Query, -}; +use bevy::prelude::{Bundle, Color, Commands, Component, Entity, In, Query}; use kayak_ui::prelude::{ widgets::BackgroundBundle, Edge, KChildren, KStyle, StyleProp, Units, Widget, WidgetContext, - WidgetName, + WidgetName, WidgetProps, }; use kayak_ui_macros::rsx; use crate::tab_context::TabContext; -#[derive(Component, Default)] +#[derive(Component, Default, PartialEq, Clone)] pub struct Tab { pub index: usize, } impl Widget for Tab {} +impl WidgetProps for Tab {} #[derive(Bundle)] pub struct TabBundle { pub tab: Tab, + pub styles: KStyle, pub children: KChildren, pub widget_name: WidgetName, } @@ -28,40 +28,41 @@ impl Default for TabBundle { Self { tab: Default::default(), children: Default::default(), + styles: KStyle { + height: Units::Auto.into(), + top: Units::Pixels(0.0).into(), + ..Default::default() + }, widget_name: Tab::default().get_name(), } } } -pub fn tab_update( +pub fn tab_render( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - mut query: Query<(&KChildren, &mut Tab)>, - mut tab_context_query: ParamSet<( - Query<ChangeTrackers<TabContext>>, - Query<&mut TabContext, Changed<TabContext>>, - )>, + query: Query<(&KChildren, &Tab)>, + tab_context_query: Query<&TabContext>, ) -> bool { - if !tab_context_query.p1().is_empty() { - if let Ok((children, tab)) = query.get_mut(entity) { - let context_entity = widget_context - .get_context_entity::<TabContext>(entity) - .unwrap(); - if let Ok(tab_context) = tab_context_query.p1().get(context_entity) { - let parent_id = Some(entity); - let styles = KStyle { - background_color: StyleProp::Value(Color::rgba(0.0781, 0.0898, 0.101, 1.0)), - padding: StyleProp::Value(Edge::all(Units::Pixels(5.0))), - ..Default::default() - }; - if tab_context.current_index == tab.index { - rsx! { - <BackgroundBundle styles={styles} children={children.clone()} /> - } + if let Ok((children, tab)) = query.get(entity) { + let context_entity = widget_context + .get_context_entity::<TabContext>(entity) + .unwrap(); + if let Ok(tab_context) = tab_context_query.get(context_entity) { + let parent_id = Some(entity); + let styles = KStyle { + background_color: StyleProp::Value(Color::rgba(0.0781, 0.0898, 0.101, 1.0)), + padding: StyleProp::Value(Edge::all(Units::Pixels(15.0))), + height: Units::Pixels(100.0).into(), + width: Units::Stretch(1.0).into(), + ..Default::default() + }; + if tab_context.current_index == tab.index { + rsx! { + <BackgroundBundle styles={styles} children={children.clone()} /> } - return true; } } } - false + true } diff --git a/examples/tabs/tab_button.rs b/examples/tabs/tab_button.rs index a1ec59d..a11df53 100644 --- a/examples/tabs/tab_button.rs +++ b/examples/tabs/tab_button.rs @@ -1,20 +1,21 @@ -use bevy::prelude::{Bundle, Changed, Color, Commands, Component, Entity, In, Query}; +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, + WidgetContext, WidgetName, WidgetProps, WidgetState, }; use crate::tab_context::TabContext; -#[derive(Component, Default)] +#[derive(Component, Default, PartialEq, Clone)] pub struct TabButton { pub index: usize, pub title: String, } impl Widget for TabButton {} +impl WidgetProps for TabButton {} #[derive(Bundle)] pub struct TabButtonBundle { @@ -33,59 +34,57 @@ impl Default for TabButtonBundle { } } -pub fn tab_button_update( +pub fn tab_button_render( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, query: Query<&TabButton>, - tab_context_query: Query<&mut TabContext, Changed<TabContext>>, + tab_context_query: Query<&mut TabContext>, ) -> bool { - if !tab_context_query.is_empty() { - if let Ok(tab_button) = query.get(entity) { - let context_entity = widget_context - .get_context_entity::<TabContext>(entity) - .unwrap(); - if let Ok(tab_context) = tab_context_query.get(context_entity) { - let background_color = if tab_context.current_index == tab_button.index { - Color::rgba(0.0781, 0.0898, 0.101, 1.0) - } else { - Color::rgba(0.0781, 0.0898, 0.101, 0.75) - }; - let parent_id = Some(entity); + if let Ok(tab_button) = query.get(entity) { + let context_entity = widget_context + .get_context_entity::<TabContext>(entity) + .unwrap(); + if let Ok(tab_context) = tab_context_query.get(context_entity) { + let background_color = if tab_context.current_index == tab_button.index { + Color::rgba(0.0781, 0.0898, 0.101, 1.0) + } else { + Color::rgba(0.0781, 0.0898, 0.101, 0.75) + }; + let parent_id = Some(entity); - let button_index = tab_button.index; - let on_event = OnEvent::new( - move |In((event_dispatcher_context, event, _entity)): In<( - EventDispatcherContext, - Event, - Entity, - )>, - mut query: Query<&mut TabContext>| { - match event.event_type { - EventType::Click(..) => { - if let Ok(mut tab_context) = query.get_mut(context_entity) { - tab_context.current_index = button_index; - } + let button_index = tab_button.index; + let on_event = OnEvent::new( + move |In((event_dispatcher_context, _, event, _entity)): In<( + EventDispatcherContext, + WidgetState, + Event, + Entity, + )>, + mut query: Query<&mut TabContext>| { + match event.event_type { + EventType::Click(..) => { + if let Ok(mut tab_context) = query.get_mut(context_entity) { + tab_context.current_index = button_index; } - _ => {} } - (event_dispatcher_context, event) - }, - ); + _ => {} + } + (event_dispatcher_context, event) + }, + ); - rsx! { - <KButtonBundle - on_event={on_event} - styles={KStyle { - background_color: StyleProp::Value(background_color), - height: StyleProp::Value(Units::Pixels(25.0)), - ..Default::default() - }}> - <TextWidgetBundle text={TextProps { content: tab_button.title.clone(), ..Default::default() }} /> - </KButtonBundle> - } - return true; + rsx! { + <KButtonBundle + on_event={on_event} + styles={KStyle { + background_color: StyleProp::Value(background_color), + height: StyleProp::Value(Units::Pixels(25.0)), + ..Default::default() + }}> + <TextWidgetBundle text={TextProps { content: tab_button.title.clone(), size: 16.0, line_height: Some(16.0), ..Default::default() }} /> + </KButtonBundle> } } } - false + true } diff --git a/examples/tabs/tab_context.rs b/examples/tabs/tab_context.rs index b4682dc..64fb0e6 100644 --- a/examples/tabs/tab_context.rs +++ b/examples/tabs/tab_context.rs @@ -1,22 +1,24 @@ -use bevy::prelude::{Bundle, Changed, Commands, Component, Entity, In, Query}; -use kayak_ui::prelude::{KChildren, Widget, WidgetContext, WidgetName}; +use bevy::prelude::{Bundle, Commands, Component, Entity, In, Query}; +use kayak_ui::prelude::*; -#[derive(Component, Default)] +#[derive(Component, Default, PartialEq, Clone)] pub struct TabContext { pub current_index: usize, } -#[derive(Component, Default)] +#[derive(Component, Default, PartialEq, Clone)] pub struct TabContextProvider { pub initial_index: usize, } impl Widget for TabContextProvider {} +impl WidgetProps for TabContextProvider {} #[derive(Bundle)] pub struct TabContextProviderBundle { pub tab_provider: TabContextProvider, pub children: KChildren, + pub styles: KStyle, pub widget_name: WidgetName, } @@ -25,30 +27,33 @@ impl Default for TabContextProviderBundle { Self { tab_provider: Default::default(), children: Default::default(), + styles: KStyle { + ..Default::default() + }, widget_name: TabContextProvider::default().get_name(), } } } -pub fn tab_context_update( +pub fn tab_context_render( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - query: Query< - (&KChildren, &TabContextProvider), - (Changed<KChildren>, Changed<TabContextProvider>), - >, + query: Query<(&KChildren, &TabContextProvider)>, ) -> bool { if let Ok((children, tab_context_provider)) = query.get(entity) { - let context_entity = commands - .spawn(TabContext { - current_index: tab_context_provider.initial_index, - }) - .id(); - widget_context.set_context_entity::<TabContext>(Some(entity), context_entity); + if widget_context + .get_context_entity::<TabContext>(entity) + .is_none() + { + let context_entity = commands + .spawn(TabContext { + current_index: tab_context_provider.initial_index, + }) + .id(); + widget_context.set_context_entity::<TabContext>(Some(entity), context_entity); + } children.process(&widget_context, Some(entity)); - - return true; } - false + true } diff --git a/examples/tabs/tabs.rs b/examples/tabs/tabs.rs index 95eec95..ad1b104 100644 --- a/examples/tabs/tabs.rs +++ b/examples/tabs/tabs.rs @@ -1,15 +1,14 @@ -use bevy::{ - prelude::{App as BevyApp, AssetServer, Commands, ImageSettings, Res, ResMut, Vec2}, - DefaultPlugins, -}; +use bevy::prelude::*; use kayak_ui::prelude::{widgets::*, *}; mod tab; mod tab_button; mod tab_context; -use tab::{tab_update, Tab, TabBundle}; -use tab_button::{tab_button_update, TabButton, TabButtonBundle}; -use tab_context::{tab_context_update, TabContextProvider, TabContextProviderBundle}; +use tab::{tab_render, Tab, TabBundle}; +use tab_button::{tab_button_render, TabButton, TabButtonBundle}; +use tab_context::{tab_context_render, TabContextProvider, TabContextProviderBundle}; + +use crate::tab_context::TabContext; fn startup( mut commands: Commands, @@ -21,9 +20,24 @@ fn startup( commands.spawn(UICameraBundle::new()); let mut widget_context = Context::new(); - widget_context.add_widget_system(Tab::default().get_name(), tab_update); - widget_context.add_widget_system(TabContextProvider::default().get_name(), tab_context_update); - widget_context.add_widget_system(TabButton::default().get_name(), tab_button_update); + widget_context.add_widget_data::<Tab, EmptyState>(); + widget_context.add_widget_data::<TabContextProvider, EmptyState>(); + widget_context.add_widget_data::<TabButton, EmptyState>(); + widget_context.add_widget_system( + Tab::default().get_name(), + widget_update_with_context::<Tab, EmptyState, TabContext>, + tab_render, + ); + widget_context.add_widget_system( + TabContextProvider::default().get_name(), + widget_update_with_context::<TabContextProvider, EmptyState, TabContext>, + tab_context_render, + ); + widget_context.add_widget_system( + TabButton::default().get_name(), + widget_update_with_context::<TabButton, EmptyState, TabContext>, + tab_button_render, + ); let parent_id = None; rsx! { @@ -41,20 +55,30 @@ fn startup( <ElementBundle styles={KStyle { layout_type: StyleProp::Value(LayoutType::Row), - height: StyleProp::Value(Units::Auto), + height: StyleProp::Value(Units::Pixels(25.0)), width: StyleProp::Value(Units::Stretch(1.0)), ..Default::default() }} > - <TabButtonBundle tab_button={TabButton { index: 0, title: "Tab 1".into() }} /> + <TabButtonBundle + tab_button={TabButton { index: 0, title: "Tab 1".into(), }} + /> <TabButtonBundle tab_button={TabButton { index: 1, title: "Tab 2".into() }} /> </ElementBundle> - <TabBundle tab={Tab { index: 0 }}> - <TextWidgetBundle text={TextProps { content: "Tab 1".into(), ..Default::default() }} /> - </TabBundle> - <TabBundle tab={Tab { index: 1 }}> - <TextWidgetBundle text={TextProps { content: "Tab 2".into(), ..Default::default() }} /> - </TabBundle> + <ElementBundle + styles={KStyle { + height: StyleProp::Value(Units::Stretch(1.0)), + width: StyleProp::Value(Units::Stretch(1.0)), + ..Default::default() + }} + > + <TabBundle tab={Tab { index: 0 }}> + <TextWidgetBundle text={TextProps { content: "Tab 1 Content".into(), size: 14.0, line_height: Some(14.0), ..Default::default() }} /> + </TabBundle> + <TabBundle tab={Tab { index: 1 }}> + <TextWidgetBundle text={TextProps { content: "Tab 2 Content".into(), size: 14.0, line_height: Some(14.0), ..Default::default() }} /> + </TabBundle> + </ElementBundle> </TabContextProviderBundle> </WindowBundle> </KayakAppBundle> @@ -64,7 +88,7 @@ fn startup( } fn main() { - BevyApp::new() + App::new() .insert_resource(ImageSettings::default_nearest()) .add_plugins(DefaultPlugins) .add_plugin(ContextPlugin) diff --git a/examples/text.rs b/examples/text.rs index b0bdaf2..6356897 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -7,7 +7,7 @@ use bevy::{ }; use kayak_ui::prelude::{widgets::*, KStyle, *}; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] pub struct MyWidgetProps { pub foo: u32, } @@ -33,6 +33,7 @@ fn my_widget_1_update( } impl Widget for MyWidgetProps {} +impl WidgetProps for MyWidgetProps {} #[derive(Bundle)] pub struct MyWidgetBundle { @@ -65,7 +66,12 @@ fn startup( let mut widget_context = Context::new(); let parent_id = None; - widget_context.add_widget_system(MyWidgetProps::default().get_name(), my_widget_1_update); + widget_context.add_widget_data::<MyWidgetProps, EmptyState>(); + widget_context.add_widget_system( + MyWidgetProps::default().get_name(), + widget_update::<MyWidgetProps, EmptyState>, + my_widget_1_update, + ); rsx! { <KayakAppBundle><MyWidgetBundle props={MyWidgetProps { foo: 0 }} /></KayakAppBundle> } diff --git a/examples/text_box.rs b/examples/text_box.rs index 038cf2f..ba5a728 100644 --- a/examples/text_box.rs +++ b/examples/text_box.rs @@ -1,22 +1,23 @@ use bevy::{ prelude::{ - Added, App as BevyApp, AssetServer, Bundle, Changed, Commands, Component, Entity, In, Or, - ParamSet, Query, Res, ResMut, Vec2, With, + App as BevyApp, AssetServer, Bundle, Commands, Component, Entity, In, Query, Res, ResMut, + Vec2, }, DefaultPlugins, }; use kayak_ui::prelude::{widgets::*, *}; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] struct TextBoxExample; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] struct TextBoxExampleState { pub value1: String, pub value2: String, } impl Widget for TextBoxExample {} +impl WidgetProps for TextBoxExample {} #[derive(Bundle)] struct TextBoxExampleBundle { @@ -38,32 +39,18 @@ impl Default for TextBoxExampleBundle { fn update_text_box_example( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - props_query: Query< - &TextBoxExample, - Or<(Changed<TextBoxExample>, Changed<KStyle>, With<Mounted>)>, - >, - mut state_query: ParamSet<( - Query<Entity, Or<(Added<TextBoxExampleState>, Changed<TextBoxExampleState>)>>, - Query<&TextBoxExampleState>, - )>, + state_query: Query<&TextBoxExampleState>, ) -> bool { - if !props_query.is_empty() || !state_query.p0().is_empty() { - let state_entity = widget_context.get_context_entity::<TextBoxExampleState>(entity); - if state_entity.is_none() { - let state_entity = commands - .spawn(TextBoxExampleState { - value1: "Hello World".into(), - value2: "Hello World2".into(), - }) - .id(); - widget_context.set_context_entity::<TextBoxExampleState>(Some(entity), state_entity); - return false; - } - let state_entity = state_entity.unwrap(); - - let p1 = state_query.p1(); - let textbox_state = p1.get(state_entity).unwrap(); + let state_entity = widget_context.use_state::<TextBoxExampleState>( + &mut commands, + entity, + TextBoxExampleState { + value1: "Hello World".into(), + value2: "Hello World2".into(), + }, + ); + if let Ok(textbox_state) = state_query.get(state_entity) { let on_change = OnChange::new( move |In((_widget_context, _, value)): In<(WidgetContext, Entity, String)>, mut state_query: Query<&mut TextBoxExampleState>| { @@ -99,11 +86,8 @@ fn update_text_box_example( /> </ElementBundle> } - - return true; } - - false + true } fn startup( @@ -116,8 +100,11 @@ fn startup( commands.spawn(UICameraBundle::new()); let mut widget_context = Context::new(); + + widget_context.add_widget_data::<TextBoxExample, TextBoxExampleState>(); widget_context.add_widget_system( TextBoxExample::default().get_name(), + widget_update::<TextBoxExample, TextBoxExampleState>, update_text_box_example, ); let parent_id = None; diff --git a/examples/todo/input.rs b/examples/todo/input.rs index ef8a227..c89225f 100644 --- a/examples/todo/input.rs +++ b/examples/todo/input.rs @@ -3,12 +3,13 @@ use kayak_ui::prelude::{widgets::*, *}; use crate::TodoList; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] pub struct TodoInputProps { has_focus: bool, } impl Widget for TodoInputProps {} +impl WidgetProps for TodoInputProps {} #[derive(Bundle)] pub struct TodoInputBundle { @@ -36,7 +37,7 @@ impl Default for TodoInputBundle { } } -pub fn update_todo_input( +pub fn render_todo_input( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, mut todo_list: ResMut<TodoList>, @@ -62,8 +63,9 @@ pub fn update_todo_input( } let handle_click = OnEvent::new( - move |In((event_dispatcher_context, event, _)): In<( + move |In((event_dispatcher_context, _, event, _)): In<( EventDispatcherContext, + WidgetState, Event, Entity, )>, @@ -81,8 +83,9 @@ pub fn update_todo_input( ); let handle_focus = OnEvent::new( - move |In((event_dispatcher_context, event, _)): In<( + move |In((event_dispatcher_context, _, event, _)): In<( EventDispatcherContext, + WidgetState, Event, Entity, )>, diff --git a/examples/todo/items.rs b/examples/todo/items.rs index dc084b4..3acd87d 100644 --- a/examples/todo/items.rs +++ b/examples/todo/items.rs @@ -3,10 +3,11 @@ use kayak_ui::prelude::{widgets::*, *}; use crate::TodoList; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] pub struct TodoItemsProps; impl Widget for TodoItemsProps {} +impl WidgetProps for TodoItemsProps {} #[derive(Bundle)] pub struct TodoItemsBundle { @@ -21,7 +22,7 @@ impl Default for TodoItemsBundle { widget: TodoItemsProps::default(), styles: KStyle { render_command: StyleProp::Value(RenderCommand::Layout), - // height: StyleProp::Value(Units::Stretch(1.0)), + height: StyleProp::Value(Units::Auto), width: StyleProp::Value(Units::Stretch(1.0)), ..KStyle::default() }, @@ -30,7 +31,7 @@ impl Default for TodoItemsBundle { } } -pub fn update_todo_items( +pub fn render_todo_items( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, todo_list: Res<TodoList>, @@ -39,11 +40,17 @@ pub fn update_todo_items( if query.is_empty() || todo_list.is_changed() { let parent_id = Some(entity); rsx! { - <ElementBundle> + <ElementBundle + styles={KStyle { + height: Units::Auto.into(), + ..Default::default() + }} + > {todo_list.items.iter().enumerate().for_each(|(index, content)| { let handle_click = OnEvent::new( - move |In((event_dispatcher_context, event, _)): In<( + move |In((event_dispatcher_context, _, event, _)): In<( EventDispatcherContext, + WidgetState, Event, Entity, )>, diff --git a/examples/todo/todo.rs b/examples/todo/todo.rs index f995060..a6b05f9 100644 --- a/examples/todo/todo.rs +++ b/examples/todo/todo.rs @@ -29,6 +29,18 @@ impl TodoList { } } +// Our own version of widget_update that handles resource change events. +pub fn widget_update_with_resource< + Props: WidgetProps + PartialEq + Component + Clone, + State: PartialEq + Component + Clone, +>( + In((widget_context, entity, previous_entity)): In<(WidgetContext, Entity, Entity)>, + todo_list: Res<TodoList>, + widget_param: WidgetParam<Props, State>, +) -> bool { + widget_param.has_changed(&widget_context, entity, previous_entity) || todo_list.is_changed() +} + fn startup( mut commands: Commands, mut font_mapping: ResMut<FontMapping>, @@ -39,8 +51,19 @@ fn startup( commands.spawn(UICameraBundle::new()); let mut widget_context = Context::new(); - widget_context.add_widget_system(TodoItemsProps::default().get_name(), update_todo_items); - widget_context.add_widget_system(TodoInputProps::default().get_name(), update_todo_input); + widget_context.add_widget_data::<TodoItemsProps, EmptyState>(); + widget_context.add_widget_data::<TodoInputProps, EmptyState>(); + + widget_context.add_widget_system( + TodoItemsProps::default().get_name(), + widget_update_with_resource::<TodoItemsProps, EmptyState>, + render_todo_items, + ); + widget_context.add_widget_system( + TodoInputProps::default().get_name(), + widget_update_with_resource::<TodoInputProps, EmptyState>, + render_todo_input, + ); let parent_id = None; rsx! { <KayakAppBundle> diff --git a/examples/vec.rs b/examples/vec.rs index bf85f58..c12dedd 100644 --- a/examples/vec.rs +++ b/examples/vec.rs @@ -7,7 +7,7 @@ use bevy::{ }; use kayak_ui::prelude::{widgets::*, KStyle, *}; -#[derive(Component, Default)] +#[derive(Component, Default, PartialEq, Clone)] pub struct MyWidgetProps {} fn my_widget_1_update( @@ -42,6 +42,7 @@ fn my_widget_1_update( } impl Widget for MyWidgetProps {} +impl WidgetProps for MyWidgetProps {} #[derive(Bundle)] pub struct MyWidgetBundle { @@ -71,7 +72,12 @@ fn startup( let mut widget_context = Context::new(); let parent_id = None; - widget_context.add_widget_system(MyWidgetProps::default().get_name(), my_widget_1_update); + widget_context.add_widget_data::<MyWidgetProps, EmptyState>(); + widget_context.add_widget_system( + MyWidgetProps::default().get_name(), + widget_update::<MyWidgetProps, EmptyState>, + my_widget_1_update, + ); rsx! { <KayakAppBundle><MyWidgetBundle /></KayakAppBundle> } diff --git a/kayak_ui_macros/src/widget.rs b/kayak_ui_macros/src/widget.rs index c44898d..55f3ef4 100644 --- a/kayak_ui_macros/src/widget.rs +++ b/kayak_ui_macros/src/widget.rs @@ -199,8 +199,8 @@ impl Widget { let props = quote! { let entity = widget_context.get_child_at(parent_id); let #entity_id = if let Some(entity) = entity { - use bevy::prelude::DespawnRecursiveExt; - commands.entity(entity).despawn_recursive(); + // use bevy::prelude::DespawnRecursiveExt; + // commands.entity(entity).despawn_recursive(); commands.get_or_spawn(entity).id() } else { commands.spawn_empty().id() diff --git a/src/calculate_nodes.rs b/src/calculate_nodes.rs index d7c5902..02234d9 100644 --- a/src/calculate_nodes.rs +++ b/src/calculate_nodes.rs @@ -136,38 +136,24 @@ pub fn calculate_nodes( commands.entity(entity).insert(node); if !needs_layout { commands.entity(entity).remove::<DirtyNode>(); + log::trace!("{:?} needs layout!", entity.id()); } } - // if has_new_nodes { - // build_nodes_tree(&mut context, &tree, &node_query); - // } - - // dbg!("STARTING MORPHORM CALC!"); - // dbg!("node_tree"); - // context.node_tree.dump(); - // if let Ok(tree) = context.tree.try_read() { - // dbg!("tree"); - // dbg!(&tree); - // tree.dump(); - // } { let context = context.as_mut(); if let Ok(tree) = context.tree.try_read() { + // tree.dump(); let node_tree = &*tree; if let Ok(mut cache) = context.layout_cache.try_write() { let mut data_cache = DataCache { cache: &mut cache, query: &nodes_no_entity_query, }; - - // dbg!(&node_tree); - morphorm::layout(&mut data_cache, node_tree, &nodes_no_entity_query); } } } - // dbg!("FINISHED MORPHORM CALC!"); } } @@ -200,6 +186,10 @@ fn create_primitive( if let Some(parent_layout) = context.get_layout(&parent_id) { properties.max_size = (parent_layout.width, parent_layout.height); + if properties.max_size.0 == 0.0 || properties.max_size.1 == 0.0 { + needs_layout = true; + } + // --- Calculate Text Layout --- // *text_layout = font.measure(&content, *properties); let measurement = text_layout.size(); diff --git a/src/children.rs b/src/children.rs index 70962ee..0116694 100644 --- a/src/children.rs +++ b/src/children.rs @@ -3,7 +3,7 @@ use bevy::prelude::*; use crate::prelude::WidgetContext; /// Defers widgets being added to the widget tree. -#[derive(Component, Debug, Default, Clone)] +#[derive(Component, Debug, Default, Clone, PartialEq)] pub struct KChildren { inner: Vec<Entity>, } diff --git a/src/clone_component.rs b/src/clone_component.rs new file mode 100644 index 0000000..5cfb47b --- /dev/null +++ b/src/clone_component.rs @@ -0,0 +1,47 @@ +use bevy::{ecs::system::CommandQueue, prelude::*}; + +use crate::widget_state::WidgetState; + +#[derive(Component, Default)] +pub struct PreviousWidget; + +#[derive(Default)] +pub(crate) struct EntityCloneSystems( + pub Vec<( + fn(&mut World, Entity, Entity), + fn(&mut World, Entity, Entity, &WidgetState), + )>, +); + +pub(crate) fn clone_system<T: Clone + Component>( + world: &mut World, + target: Entity, + reference: Entity, +) { + if let Some(v) = world.entity(reference).get::<T>() { + let v = v.clone(); + world.entity_mut(target).insert(v); + } +} + +pub(crate) fn clone_state<State: Component + PartialEq + Clone>( + world: &mut World, + target: Entity, + reference: Entity, + widget_state: &WidgetState, +) { + if let Some(reference_state_entity) = widget_state.get(reference) { + if let Some(v) = world.entity(reference_state_entity).get::<State>() { + if let Some(target_state_entity) = widget_state.get(target) { + let v = v.clone(); + world.entity_mut(target_state_entity).insert(v); + } else { + let mut command_queue = CommandQueue::default(); + let mut commands = Commands::new(&mut command_queue, world); + let state_entity = widget_state.add::<State>(&mut commands, target, v.clone()); + commands.entity(state_entity).insert(PreviousWidget); + command_queue.apply(world); + } + } + } +} diff --git a/src/context.rs b/src/context.rs index e12a25e..b1143a8 100644 --- a/src/context.rs +++ b/src/context.rs @@ -9,6 +9,8 @@ use morphorm::Hierarchy; use crate::{ calculate_nodes::calculate_nodes, + children::KChildren, + clone_component::{clone_state, clone_system, EntityCloneSystems, PreviousWidget}, context_entities::ContextEntities, event_dispatcher::EventDispatcher, focus_tree::FocusTree, @@ -17,7 +19,10 @@ use crate::{ node::{DirtyNode, WrappedIndex}, prelude::WidgetContext, render_primitive::RenderPrimitive, + styles::KStyle, tree::{Change, Tree}, + widget::WidgetProps, + widget_state::WidgetState, Focusable, WindowSize, }; @@ -27,15 +32,26 @@ pub struct Mounted; 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>>, + ), +>; + #[derive(Resource)] pub struct Context { pub tree: Arc<RwLock<Tree>>, pub(crate) layout_cache: Arc<RwLock<LayoutCache>>, pub(crate) focus_tree: Arc<RwLock<FocusTree>>, - systems: HashMap<String, Box<dyn System<In = (WidgetContext, Entity), Out = bool>>>, + systems: WidgetSystems, pub(crate) current_z: f32, pub(crate) context_entities: ContextEntities, pub(crate) current_cursor: CursorIcon, + pub(crate) clone_systems: Arc<RwLock<EntityCloneSystems>>, + pub(crate) cloned_widget_entities: Arc<RwLock<HashMap<Entity, Entity>>>, + pub(crate) widget_state: WidgetState, } impl Context { @@ -48,6 +64,9 @@ impl Context { current_z: 0.0, context_entities: ContextEntities::new(), current_cursor: CursorIcon::Default, + clone_systems: Default::default(), + cloned_widget_entities: Default::default(), + widget_state: Default::default(), } } @@ -59,13 +78,29 @@ impl Context { } } - pub fn add_widget_system<Params>( + pub fn add_widget_system<Params, Params2>( &mut self, type_name: impl Into<String>, - system: impl IntoSystem<(WidgetContext, Entity), bool, Params>, + update: impl IntoSystem<(WidgetContext, Entity, Entity), bool, Params>, + render: impl IntoSystem<(WidgetContext, Entity), bool, Params2>, ) { - let system = IntoSystem::into_system(system); - self.systems.insert(type_name.into(), Box::new(system)); + let update_system = Box::new(IntoSystem::into_system(update)); + let render_system = Box::new(IntoSystem::into_system(render)); + self.systems + .insert(type_name.into(), (update_system, render_system)); + } + + pub fn add_widget_data< + Props: WidgetProps + Component + Clone + PartialEq, + State: Component + Clone + PartialEq, + >( + &mut self, + ) { + if let Ok(mut clone_systems) = self.clone_systems.try_write() { + clone_systems + .0 + .push((clone_system::<Props>, clone_state::<State>)); + } } pub fn add_widget(&mut self, parent: Option<Entity>, entity: Entity) { @@ -105,6 +140,7 @@ impl Context { pub fn build_render_primitives( &self, nodes: &Query<&crate::node::Node>, + widget_names: &Query<&WidgetName>, ) -> Vec<RenderPrimitive> { let node_tree = self.tree.try_read(); if node_tree.is_err() { @@ -123,6 +159,7 @@ impl Context { &*node_tree, &self.layout_cache, nodes, + widget_names, node_tree.root_node.unwrap(), 0.0, RenderPrimitive::Empty, @@ -134,6 +171,7 @@ fn recurse_node_tree_to_build_primitives( node_tree: &Tree, layout_cache: &Arc<RwLock<LayoutCache>>, nodes: &Query<&crate::node::Node>, + widget_names: &Query<&WidgetName>, current_node: WrappedIndex, mut main_z_index: f32, mut prev_clip: RenderPrimitive, @@ -151,6 +189,15 @@ fn recurse_node_tree_to_build_primitives( }; layout.z_index = new_z_index; render_primitive.set_layout(layout); + + if matches!(render_primitive, RenderPrimitive::Empty) { + log::trace!( + "No render primitive for node: {}-{}", + widget_names.get(current_node.0).unwrap().0, + current_node.0.id(), + ); + } + render_primitives.push(render_primitive.clone()); let new_prev_clip = if matches!(render_primitive, RenderPrimitive::Clip { .. }) { @@ -167,6 +214,7 @@ fn recurse_node_tree_to_build_primitives( node_tree, layout_cache, nodes, + widget_names, *child, main_z_index, new_prev_clip.clone(), @@ -185,13 +233,40 @@ fn recurse_node_tree_to_build_primitives( render_primitives.push(prev_clip.clone()); } } + } else { + log::trace!( + "No children for node: {}-{}", + widget_names.get(current_node.0).unwrap().0, + current_node.0.id() + ); } } else { - println!("No layout for: {:?}", current_node.0.id()); + log::warn!( + "No layout for node: {}-{}", + widget_names.get(current_node.0).unwrap().0, + current_node.0.id() + ); } } } else { - println!("No node for: {:?}", current_node.0.id()); + log::error!( + "No render node: {}-{} > {}-{}", + node_tree + .get_parent(current_node) + .and_then(|v| Some(v.0.id() as i32)) + .unwrap_or(-1), + widget_names + .get( + node_tree + .get_parent(current_node) + .and_then(|v| Some(v.0)) + .unwrap_or(Entity::from_raw(0)) + ) + .and_then(|v| Ok(v.0.clone())) + .unwrap_or("None".into()), + widget_names.get(current_node.0).unwrap().0, + current_node.0.id() + ); } render_primitives @@ -229,6 +304,9 @@ fn update_widgets_sys(world: &mut World) { tree_iterator, &context.context_entities, &context.focus_tree, + &context.clone_systems, + &context.cloned_widget_entities, + &context.widget_state, &mut new_ticks, ); @@ -245,9 +323,11 @@ fn update_widgets_sys(world: &mut World) { for (key, system) in context.systems.iter_mut() { if let Some(new_tick) = new_ticks.get(key) { - system.set_last_change_tick(*new_tick); + system.0.set_last_change_tick(*new_tick); + system.1.set_last_change_tick(*new_tick); } else { - system.set_last_change_tick(tick); + system.0.set_last_change_tick(tick); + system.1.set_last_change_tick(tick); } // system.apply_buffers(world); } @@ -263,10 +343,13 @@ fn update_widgets( world: &mut World, tree: &Arc<RwLock<Tree>>, layout_cache: &Arc<RwLock<LayoutCache>>, - systems: &mut HashMap<String, Box<dyn System<In = (WidgetContext, Entity), Out = bool>>>, + systems: &mut WidgetSystems, widgets: Vec<WrappedIndex>, context_entities: &ContextEntities, focus_tree: &Arc<RwLock<FocusTree>>, + clone_systems: &Arc<RwLock<EntityCloneSystems>>, + cloned_widget_entities: &Arc<RwLock<HashMap<Entity, Entity>>>, + widget_state: &WidgetState, new_ticks: &mut HashMap<String, u32>, ) { for entity in widgets.iter() { @@ -276,6 +359,7 @@ fn update_widgets( tree.clone(), context_entities.clone(), layout_cache.clone(), + widget_state.clone(), ); widget_context.copy_from_point(&tree, *entity); let children_before = widget_context.get_children(entity.0); @@ -287,6 +371,9 @@ fn update_widgets( widget_type.0.clone(), widget_context, children_before, + clone_systems, + cloned_widget_entities, + widget_state, new_ticks, ); @@ -321,6 +408,9 @@ fn update_widgets( children, context_entities, focus_tree, + clone_systems, + cloned_widget_entities, + widget_state, new_ticks, ); // } @@ -340,26 +430,85 @@ fn update_widgets( } fn update_widget( - systems: &mut HashMap<String, Box<dyn System<In = (WidgetContext, Entity), Out = bool>>>, + systems: &mut WidgetSystems, tree: &Arc<RwLock<Tree>>, world: &mut World, entity: WrappedIndex, widget_type: String, widget_context: WidgetContext, previous_children: Vec<Entity>, + clone_systems: &Arc<RwLock<EntityCloneSystems>>, + cloned_widget_entities: &Arc<RwLock<HashMap<Entity, Entity>>>, + widget_state: &WidgetState, new_ticks: &mut HashMap<String, u32>, ) -> (Tree, bool) { + // Check if we should update this widget + + let should_rerender = { + let old_props_entity = + if let Ok(mut cloned_widget_entities) = cloned_widget_entities.try_write() { + if let Some(entity) = cloned_widget_entities.get(&entity.0) { + *entity + } else { + let target = world.spawn_empty().insert(PreviousWidget).id(); + cloned_widget_entities.insert(entity.0, target); + target + } + } else { + panic!("Couldn't get write lock!") + }; + + let widget_update_system = &mut systems.get_mut(&widget_type).unwrap().0; + let old_tick = widget_update_system.get_last_change_tick(); + let should_rerender = + widget_update_system.run((widget_context.clone(), entity.0, old_props_entity), world); + let new_tick = widget_update_system.get_last_change_tick(); + new_ticks.insert(widget_type.clone(), new_tick); + widget_update_system.set_last_change_tick(old_tick); + + if should_rerender { + if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() { + let target_entity = cloned_widget_entities.get(&entity.0).unwrap(); + if let Ok(clone_systems) = clone_systems.try_read() { + for s in clone_systems.0.iter() { + s.0(world, *target_entity, entity.0); + s.1(world, *target_entity, entity.0, &widget_state); + if let Some(styles) = world.entity(entity.0).get::<KStyle>().cloned() { + world.entity_mut(*target_entity).insert(styles); + } + if let Some(children) = world.entity(entity.0).get::<KChildren>().cloned() { + world.entity_mut(*target_entity).insert(children); + } + } + } + } + } + + should_rerender + }; + + if !should_rerender { + return (widget_context.take(), false); + } + + // if let Ok(tree) = tree.try_read() { + // if tree.root_node.unwrap() != entity { + + // } + let should_update_children; + log::trace!("Re-rendering: {:?} {:?}", &widget_type, entity.0.id()); { // Remove children from previous render. widget_context.remove_children(previous_children); - let widget_system = systems.get_mut(&widget_type).unwrap(); - let old_tick = widget_system.get_last_change_tick(); - should_update_children = widget_system.run((widget_context.clone(), entity.0), world); - let new_tick = widget_system.get_last_change_tick(); + let widget_render_system = &mut systems.get_mut(&widget_type).unwrap().1; + let old_tick = widget_render_system.get_last_change_tick(); + should_update_children = + widget_render_system.run((widget_context.clone(), entity.0), world); + let new_tick = widget_render_system.get_last_change_tick(); new_ticks.insert(widget_type.clone(), new_tick); - widget_system.set_last_change_tick(old_tick); - widget_system.apply_buffers(world); + widget_render_system.set_last_change_tick(old_tick); + widget_render_system.apply_buffers(world); } let widget_context = widget_context.take(); let mut command_queue = CommandQueue::default(); @@ -367,22 +516,34 @@ fn update_widget( commands.entity(entity.0).remove::<Mounted>(); - // Mark node as needing a recalculation of rendering/layout. - if should_update_children { - commands.entity(entity.0).insert(DirtyNode); - } - let diff = if let Ok(tree) = tree.read() { tree.diff_children(&widget_context, entity, UPDATE_DEPTH) } else { panic!("Failed to acquire read lock."); }; + + log::trace!("Diff: {:?}", &diff); + + // Always mark widget dirty if it's re-rendered. + // Mark node as needing a recalculation of rendering/layout. + commands.entity(entity.0).insert(DirtyNode); + + for child in widget_context.child_iter(entity) { + commands.entity(child.0).insert(DirtyNode); + } + + if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() { + let target_entity = cloned_widget_entities.get(&entity.0).unwrap(); + if let Some(styles) = world.entity(entity.0).get::<KStyle>().cloned() { + commands.entity(*target_entity).insert(styles); + } + if let Some(children) = world.entity(entity.0).get::<KChildren>().cloned() { + commands.entity(*target_entity).insert(children); + } + } + if should_update_children { for (_, changed_entity, _, changes) in diff.changes.iter() { - if changes.iter().any(|change| *change != Change::Deleted) { - commands.entity(changed_entity.0).insert(DirtyNode); - } - if changes.iter().any(|change| *change == Change::Deleted) { // commands.entity(changed_entity.0).despawn(); commands.entity(changed_entity.0).remove::<DirtyNode>(); @@ -394,13 +555,27 @@ fn update_widget( } command_queue.apply(world); + if should_update_children { + for (_, child_entity, _, changes) in diff.changes.iter() { + // Clone to entity. + if changes.iter().any(|change| *change == Change::Deleted) { + if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() { + if let Some(entity) = cloned_widget_entities.get(&child_entity.0) { + world.despawn(*entity); + } + } + } + } + } + (widget_context, should_update_children) } fn init_systems(world: &mut World) { let mut context = world.remove_resource::<Context>().unwrap(); for system in context.systems.values_mut() { - system.initialize(world); + system.0.initialize(world); + system.1.initialize(world); } world.insert_resource(context); diff --git a/src/event_dispatcher.rs b/src/event_dispatcher.rs index 5aa0e41..559764b 100644 --- a/src/event_dispatcher.rs +++ b/src/event_dispatcher.rs @@ -221,8 +221,13 @@ impl EventDispatcher { cursor_capture: self.cursor_capture, }; - (event_dispatcher_context, node_event) = - on_event.try_call(event_dispatcher_context, index.0, node_event, world); + (event_dispatcher_context, node_event) = on_event.try_call( + event_dispatcher_context, + context.widget_state.clone(), + index.0, + node_event, + world, + ); world.entity_mut(index.0).insert(on_event); event_dispatcher_context.merge(self); @@ -232,6 +237,7 @@ impl EventDispatcher { context.tree.clone(), context.context_entities.clone(), context.layout_cache.clone(), + context.widget_state.clone(), ); node_event.run_on_change(world, widget_context); } diff --git a/src/lib.rs b/src/lib.rs index 9c33444..5e96322 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod calculate_nodes; mod camera; mod children; +mod clone_component; mod context; mod context_entities; mod cursor; @@ -24,6 +25,7 @@ mod styles; mod tree; mod widget; mod widget_context; +mod widget_state; mod widgets; mod window_size; @@ -43,6 +45,7 @@ pub mod prelude { pub mod widgets { pub use crate::widgets::*; } + pub use crate::clone_component::PreviousWidget; pub use crate::event::*; pub use crate::event_dispatcher::EventDispatcherContext; pub use crate::focus_tree::Focusable; @@ -56,6 +59,7 @@ pub mod prelude { pub use crate::styles::*; pub use crate::widget::*; pub use crate::widget_context::*; + pub use crate::widget_state::*; pub use kayak_font::Alignment; pub use kayak_ui_macros::{constructor, rsx}; } diff --git a/src/on_event.rs b/src/on_event.rs index 2561372..efe92c5 100644 --- a/src/on_event.rs +++ b/src/on_event.rs @@ -4,6 +4,7 @@ use std::sync::{Arc, RwLock}; use crate::event::Event; use crate::event_dispatcher::EventDispatcherContext; +use crate::widget_state::WidgetState; /// A container for a function that handles events /// @@ -16,7 +17,7 @@ pub struct OnEvent { system: Arc< RwLock< dyn System< - In = (EventDispatcherContext, Event, Entity), + In = (EventDispatcherContext, WidgetState, Event, Entity), Out = (EventDispatcherContext, Event), >, >, @@ -25,9 +26,11 @@ pub struct OnEvent { impl Default for OnEvent { fn default() -> Self { - Self::new(|In((event_dispatcher_context, event, _entity))| { - (event_dispatcher_context, event) - }) + Self::new( + |In((event_dispatcher_context, _widget_state, event, _entity))| { + (event_dispatcher_context, event) + }, + ) } } @@ -39,7 +42,7 @@ impl OnEvent { /// 2. The event pub fn new<Params>( system: impl IntoSystem< - (EventDispatcherContext, Event, Entity), + (EventDispatcherContext, WidgetState, Event, Entity), (EventDispatcherContext, Event), Params, >, @@ -56,6 +59,7 @@ impl OnEvent { pub fn try_call( &mut self, mut event_dispatcher_context: EventDispatcherContext, + widget_state: WidgetState, entity: Entity, mut event: Event, world: &mut World, @@ -65,8 +69,10 @@ impl OnEvent { system.initialize(world); self.has_initialized = true; } - (event_dispatcher_context, event) = - system.run((event_dispatcher_context, event, entity), world); + (event_dispatcher_context, event) = system.run( + (event_dispatcher_context, widget_state, event, entity), + world, + ); system.apply_buffers(world); } (event_dispatcher_context, event) diff --git a/src/render/extract.rs b/src/render/extract.rs index fd055e6..188caeb 100644 --- a/src/render/extract.rs +++ b/src/render/extract.rs @@ -1,6 +1,10 @@ -use crate::{context::Context, node::Node, render_primitive::RenderPrimitive, styles::Corner}; +use crate::{ + context::{Context, WidgetName}, + node::Node, + render_primitive::RenderPrimitive, + styles::Corner, +}; use bevy::{ - // math::Vec2, prelude::{Assets, Color, Commands, Image, Plugin, Query, Rect, Res, Vec2}, render::{Extract, RenderApp, RenderStage}, window::Windows, @@ -31,11 +35,12 @@ pub fn extract( fonts: Extract<Res<Assets<KayakFont>>>, font_mapping: Extract<Res<FontMapping>>, node_query: Extract<Query<&Node>>, + widget_names: Extract<Query<&WidgetName>>, images: Extract<Res<Assets<Image>>>, windows: Extract<Res<Windows>>, ) { // dbg!("STARTED"); - let render_primitives = context.build_render_primitives(&node_query); + let render_primitives = context.build_render_primitives(&node_query, &widget_names); // dbg!("FINISHED"); let dpi = if let Some(window) = windows.get_primary() { diff --git a/src/styles/style.rs b/src/styles/style.rs index a28d0c3..f02b567 100644 --- a/src/styles/style.rs +++ b/src/styles/style.rs @@ -397,7 +397,7 @@ impl KStyle { padding_top: StyleProp::Default, pointer_events: StyleProp::Default, position_type: StyleProp::Default, - render_command: StyleProp::Value(RenderCommand::Empty), + render_command: StyleProp::Value(RenderCommand::Layout), right: StyleProp::Default, row_between: StyleProp::Default, top: StyleProp::Default, diff --git a/src/widget.rs b/src/widget.rs index 5429f10..851a1da 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,7 +1,149 @@ -use crate::context::WidgetName; +use bevy::{ + ecs::system::SystemParam, + prelude::{Changed, Component, Entity, In, Query, With}, +}; + +use crate::{ + children::KChildren, + context::{Mounted, WidgetName}, + prelude::WidgetContext, + styles::KStyle, +}; pub trait Widget: Send + Sync { fn get_name(&self) -> WidgetName { WidgetName(std::any::type_name::<Self>().into()) } } + +#[derive(Component, Default, PartialEq, Clone)] +pub struct EmptyState; + +/// Used to diff widget props. +pub trait WidgetProps {} + +pub fn widget_update< + Props: WidgetProps + PartialEq + Component + Clone, + State: PartialEq + Component + Clone, +>( + In((widget_context, entity, previous_entity)): In<(WidgetContext, Entity, Entity)>, + widget_param: WidgetParam<Props, State>, +) -> bool { + widget_param.has_changed(&widget_context, entity, previous_entity) +} + +pub fn widget_update_with_context< + Props: WidgetProps + PartialEq + Component + Clone, + State: PartialEq + Component + Clone, + Context: PartialEq + Component + Clone + Default, +>( + In((widget_context, entity, previous_entity)): In<(WidgetContext, Entity, Entity)>, + widget_param: WidgetParam<Props, State>, + context_query: Query<Entity, Changed<Context>>, +) -> bool { + // Uses bevy state changes to see if context has changed. + if let Some(context_entity) = widget_context.get_context_entity::<Context>(entity) { + if context_query.contains(context_entity) { + return true; + } + } + + widget_param.has_changed(&widget_context, entity, previous_entity) +} + +#[derive(SystemParam)] +pub struct WidgetParam< + 'w, + 's, + Props: WidgetProps + PartialEq + Component, + State: PartialEq + Component, +> { + pub props_query: Query<'w, 's, &'static Props>, + pub old_props_query: Query<'w, 's, &'static Props>, + pub mounted_query: Query<'w, 's, Entity, With<Mounted>>, + pub style_query: Query<'w, 's, &'static KStyle>, + pub children_query: Query<'w, 's, &'static KChildren>, + pub state_query: Query<'w, 's, &'static State>, + pub widget_names: Query<'w, 's, &'static WidgetName>, +} + +impl<'w, 's, Props: WidgetProps + PartialEq + Component, State: PartialEq + Component> + WidgetParam<'w, 's, Props, State> +{ + pub fn has_changed( + &self, + widget_context: &WidgetContext, + current_entity: Entity, + previous_entity: Entity, + ) -> bool { + if !self.mounted_query.is_empty() { + return true; + } + + // Compare styles + if let (Ok(style), Ok(old_style)) = ( + self.style_query.get(current_entity), + self.style_query.get(previous_entity), + ) { + if style != old_style { + log::trace!( + "Entity styles have changed! {}-{}", + self.widget_names.get(current_entity).unwrap().0, + current_entity.id() + ); + return true; + } + } + + // Compare children + // If children don't exist ignore as mount will add them! + if let (Ok(children), Ok(old_children)) = ( + self.children_query.get(current_entity), + self.children_query.get(previous_entity), + ) { + if children != old_children { + return true; + } + } + + // Check props + if let (Ok(props), Ok(previous_props)) = ( + self.props_query.get(current_entity), + self.old_props_query.get(previous_entity), + ) { + if previous_props != props { + log::trace!( + "Entity props have changed! {}-{}", + self.widget_names.get(current_entity).unwrap().0, + current_entity.id() + ); + return true; + } + } + + // Check state + let previous_state_entity = widget_context.get_state(previous_entity); + let current_state_entity = widget_context.get_state(current_entity); + + // Check if state was nothing but now is something + if current_state_entity.is_some() && previous_state_entity.is_none() { + return true; + } + + // Check state + if current_state_entity.is_some() && previous_state_entity.is_some() { + let previous_state_entity = previous_state_entity.unwrap(); + let current_state_entity = current_state_entity.unwrap(); + if let (Ok(state), Ok(previous_state)) = ( + self.state_query.get(current_state_entity), + self.state_query.get(previous_state_entity), + ) { + if previous_state != state { + return true; + } + } + } + + false + } +} diff --git a/src/widget_context.rs b/src/widget_context.rs index a8ddd41..3fd8aef 100644 --- a/src/widget_context.rs +++ b/src/widget_context.rs @@ -1,10 +1,14 @@ use std::sync::{Arc, RwLock}; -use bevy::{prelude::Entity, utils::HashMap}; +use bevy::{ + prelude::{Commands, Component, Entity}, + utils::HashMap, +}; use morphorm::Hierarchy; use crate::{ context_entities::ContextEntities, layout::LayoutCache, node::WrappedIndex, prelude::Tree, + widget_state::WidgetState, }; #[derive(Clone)] @@ -14,6 +18,7 @@ pub struct WidgetContext { context_entities: ContextEntities, layout_cache: Arc<RwLock<LayoutCache>>, index: Arc<RwLock<HashMap<Entity, usize>>>, + widget_state: WidgetState, } impl WidgetContext { @@ -21,6 +26,7 @@ impl WidgetContext { old_tree: Arc<RwLock<Tree>>, context_entities: ContextEntities, layout_cache: Arc<RwLock<LayoutCache>>, + widget_state: WidgetState, ) -> Self { Self { old_tree, @@ -28,6 +34,7 @@ impl WidgetContext { context_entities, layout_cache, index: Arc::new(RwLock::new(HashMap::default())), + widget_state, } } @@ -131,6 +138,22 @@ impl WidgetContext { 0 } + /// Creates or grabs the existing state entity + pub fn use_state<State: Component + PartialEq + Clone + Default>( + &self, + commands: &mut Commands, + widget_entity: Entity, + initial_state: State, + ) -> Entity { + self.widget_state + .add(commands, widget_entity, initial_state) + } + + /// Grabs the existing state returns none if it does not exist. + pub fn get_state(&self, widget_entity: Entity) -> Option<Entity> { + self.widget_state.get(widget_entity) + } + pub fn get_child_at(&self, entity: Option<Entity>) -> Option<Entity> { if let Some(entity) = entity { let children = self.get_children_old(entity); diff --git a/src/widget_state.rs b/src/widget_state.rs new file mode 100644 index 0000000..6762396 --- /dev/null +++ b/src/widget_state.rs @@ -0,0 +1,43 @@ +use std::sync::{Arc, RwLock}; + +use bevy::{ + prelude::{Commands, Component, Entity}, + utils::HashMap, +}; + +/// Stores mappings between widget entities and their corresponding state entities. +#[derive(Default, Debug, Clone)] +pub struct WidgetState { + mapping: Arc<RwLock<HashMap<Entity, Entity>>>, +} + +impl WidgetState { + /// Attempts to create a state entity or return the existing entity. + pub fn add<State: Component + PartialEq + Clone>( + &self, + commands: &mut Commands, + widget_entity: Entity, + initial_state: State, + ) -> Entity { + if let Ok(mut mapping) = self.mapping.try_write() { + if mapping.contains_key(&widget_entity) { + *mapping.get(&widget_entity).unwrap() + } else { + let state_entity = commands.spawn(initial_state).id(); + mapping.insert(widget_entity, state_entity); + state_entity + } + } else { + panic!("Couldn't get mapping lock!"); + } + } + + /// Attempts to get a state entity + pub fn get(&self, widget_entity: Entity) -> Option<Entity> { + if let Ok(mapping) = self.mapping.try_read() { + return mapping.get(&widget_entity).cloned(); + } + + None + } +} diff --git a/src/widgets/app.rs b/src/widgets/app.rs index 1853c47..47995ed 100644 --- a/src/widgets/app.rs +++ b/src/widgets/app.rs @@ -1,21 +1,19 @@ -use bevy::{ - prelude::{Bundle, Commands, Component, Entity, In, Or, Query, Res, With}, - window::Windows, -}; +use bevy::prelude::*; use morphorm::Units; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, prelude::WidgetContext, styles::{KStyle, RenderCommand, StyleProp}, - widget::Widget, + widget::{EmptyState, Widget, WidgetParam, WidgetProps}, }; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] pub struct KayakApp; impl Widget for KayakApp {} +impl WidgetProps for KayakApp {} #[derive(Bundle)] pub struct KayakAppBundle { @@ -36,31 +34,44 @@ impl Default for KayakAppBundle { } } -/// TODO: USE CAMERA INSTEAD OF WINDOW!! pub fn app_update( + In((widget_context, entity, previous_props_entity)): In<(WidgetContext, Entity, Entity)>, + windows: Res<Windows>, + widget_param: WidgetParam<KayakApp, EmptyState>, +) -> bool { + let primary_window = windows.get_primary().unwrap(); + + let mut window_change = false; + if let Ok(app_style) = widget_param.style_query.get(entity) { + if app_style.width != StyleProp::Value(Units::Pixels(primary_window.width())) { + window_change = true; + } + if app_style.height != StyleProp::Value(Units::Pixels(primary_window.height())) { + window_change = true; + } + } + + widget_param.has_changed(&widget_context, entity, previous_props_entity) || window_change +} + +/// TODO: USE CAMERA INSTEAD OF WINDOW!! +pub fn app_render( In((widget_context, entity)): In<(WidgetContext, Entity)>, _: Commands, windows: Res<Windows>, - mut query: Query<(&mut KStyle, &KChildren), Or<(With<KayakApp>, With<Mounted>)>>, + mut query: Query<(&mut KStyle, &KChildren)>, ) -> bool { - let mut has_changed = false; let primary_window = windows.get_primary().unwrap(); if let Ok((mut app_style, children)) = query.get_mut(entity) { if app_style.width != StyleProp::Value(Units::Pixels(primary_window.width())) { app_style.width = StyleProp::Value(Units::Pixels(primary_window.width())); - has_changed = true; } if app_style.height != StyleProp::Value(Units::Pixels(primary_window.height())) { app_style.height = StyleProp::Value(Units::Pixels(primary_window.height())); - has_changed = true; } - app_style.render_command = StyleProp::Value(RenderCommand::Layout); - - if has_changed { - children.process(&widget_context, Some(entity)); - } + children.process(&widget_context, Some(entity)); } - has_changed + true } diff --git a/src/widgets/background.rs b/src/widgets/background.rs index 262bf61..3b500d9 100644 --- a/src/widgets/background.rs +++ b/src/widgets/background.rs @@ -1,18 +1,19 @@ -use bevy::prelude::{Bundle, Changed, Commands, Component, Entity, In, Or, Query, With}; +use bevy::prelude::{Bundle, Commands, Component, Entity, In, Query}; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, on_event::OnEvent, prelude::WidgetContext, styles::{KStyle, RenderCommand, StyleProp}, - widget::Widget, + widget::{Widget, WidgetProps}, }; -#[derive(Component, Default)] +#[derive(Component, PartialEq, Clone, Default)] pub struct Background; impl Widget for Background {} +impl WidgetProps for Background {} #[derive(Bundle)] pub struct BackgroundBundle { @@ -38,18 +39,11 @@ impl Default for BackgroundBundle { pub fn update_background( In((widget_context, entity)): In<(WidgetContext, Entity)>, _: Commands, - mut query: Query< - (&mut KStyle, &KChildren), - Or<( - (Changed<KStyle>, Changed<KChildren>, With<Background>), - With<Mounted>, - )>, - >, + mut query: Query<(&mut KStyle, &KChildren)>, ) -> bool { if let Ok((mut style, children)) = query.get_mut(entity) { style.render_command = StyleProp::Value(RenderCommand::Quad); children.process(&widget_context, Some(entity)); - return true; } - false + true } diff --git a/src/widgets/button.rs b/src/widgets/button.rs index c0e0fc6..a8cc4e2 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -1,17 +1,17 @@ use bevy::{ - prelude::{Bundle, Changed, Color, Commands, Component, Entity, In, Or, Query, With}, + prelude::{Bundle, Color, Commands, Component, Entity, In, Query}, window::CursorIcon, }; use crate::{ - context::{Mounted, WidgetName}, + context::WidgetName, on_event::OnEvent, prelude::{KChildren, Units, WidgetContext}, styles::{Corner, KCursorIcon, KStyle, RenderCommand, StyleProp}, - widget::Widget, + widget::{Widget, WidgetProps}, }; -#[derive(Component, Default)] +#[derive(Component, PartialEq, Clone, Default)] pub struct KButton; #[derive(Bundle)] @@ -36,11 +36,12 @@ impl Default for KButtonBundle { } impl Widget for KButton {} +impl WidgetProps for KButton {} pub fn button_update( In((widget_context, entity)): In<(WidgetContext, Entity)>, _: Commands, - mut query: Query<(&mut KStyle, &KChildren), Or<(Changed<KButton>, With<Mounted>)>>, + mut query: Query<(&mut KStyle, &KChildren)>, ) -> bool { if let Ok((mut style, children)) = query.get_mut(entity) { *style = KStyle::default() @@ -63,9 +64,7 @@ pub fn button_update( }); children.process(&widget_context, Some(entity)); - - return true; } - false + true } diff --git a/src/widgets/clip.rs b/src/widgets/clip.rs index c07bcdf..3eff77c 100644 --- a/src/widgets/clip.rs +++ b/src/widgets/clip.rs @@ -1,17 +1,18 @@ -use bevy::prelude::{Bundle, Changed, Commands, Component, Entity, In, Or, Query, With}; +use bevy::prelude::{Bundle, Commands, Component, Entity, In, Query}; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, prelude::WidgetContext, styles::{KStyle, RenderCommand, StyleProp, Units}, - widget::Widget, + widget::{Widget, WidgetProps}, }; -#[derive(Component, Default)] +#[derive(Component, PartialEq, Clone, Default)] pub struct Clip; impl Widget for Clip {} +impl WidgetProps for Clip {} #[derive(Bundle)] pub struct ClipBundle { @@ -40,11 +41,10 @@ impl Default for ClipBundle { pub fn update_clip( In((widget_context, entity)): In<(WidgetContext, Entity)>, _: Commands, - mut query: Query<(&KStyle, &KChildren), Or<((Changed<KStyle>, With<Clip>), With<Mounted>)>>, + mut query: Query<(&KStyle, &KChildren)>, ) -> bool { if let Ok((_, children)) = query.get_mut(entity) { children.process(&widget_context, Some(entity)); - return true; } - false + true } diff --git a/src/widgets/element.rs b/src/widgets/element.rs index 5bfd3cb..72ef9d3 100644 --- a/src/widgets/element.rs +++ b/src/widgets/element.rs @@ -1,18 +1,19 @@ -use bevy::prelude::{Bundle, Changed, Commands, Component, Entity, In, Or, Query, With}; +use bevy::prelude::{Bundle, Commands, Component, Entity, In, Query}; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, on_event::OnEvent, prelude::WidgetContext, styles::{KStyle, RenderCommand, StyleProp}, - widget::Widget, + widget::{Widget, WidgetProps}, }; -#[derive(Component, Default)] +#[derive(Component, PartialEq, Clone, Default)] pub struct Element; impl Widget for Element {} +impl WidgetProps for Element {} #[derive(Bundle)] pub struct ElementBundle { @@ -38,10 +39,7 @@ impl Default for ElementBundle { pub fn update_element( In((mut widget_context, entity)): In<(WidgetContext, Entity)>, _: Commands, - mut query: Query< - (&mut KStyle, &KChildren), - Or<((Changed<KStyle>, With<Element>), With<Mounted>)>, - >, + mut query: Query<(&mut KStyle, &KChildren)>, ) -> bool { if let Ok((mut style, children)) = query.get_mut(entity) { *style = KStyle::default() @@ -51,7 +49,6 @@ pub fn update_element( ..Default::default() }); children.process(&mut widget_context, Some(entity)); - return true; } - false + true } diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 6968c24..c2e0d2c 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -4,13 +4,14 @@ use crate::{ context::{Mounted, WidgetName}, prelude::WidgetContext, styles::{KStyle, RenderCommand, StyleProp}, - widget::Widget, + widget::{Widget, WidgetProps}, }; -#[derive(Component, Default)] +#[derive(Component, PartialEq, Clone, Default)] pub struct Image(pub Handle<bevy::prelude::Image>); impl Widget for Image {} +impl WidgetProps for Image {} #[derive(Bundle)] pub struct ImageBundle { diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index fab668a..69967c7 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -29,11 +29,11 @@ pub use scroll::{ }, }; pub use text::{TextProps, TextWidgetBundle}; -pub use text_box::{TextBoxBundle, TextBoxProps}; +pub use text_box::{TextBoxBundle, TextBoxProps, TextBoxState}; pub use texture_atlas::{TextureAtlas, TextureAtlasBundle}; pub use window::{KWindow, WindowBundle}; -use app::app_update; +use app::{app_render, app_update}; use background::update_background; use button::button_update; use clip::update_clip; @@ -44,12 +44,15 @@ use scroll::{ scroll_bar::update_scroll_bar, scroll_box::update_scroll_box, scroll_content::update_scroll_content, scroll_context::update_scroll_context, }; -use text::text_update; +use text::text_render; use text_box::update_text_box; use texture_atlas::update_texture_atlas; use window::window_update; -use crate::{context::Context, widget::Widget}; +use crate::{ + context::Context, + widget::{widget_update, widget_update_with_context, EmptyState, Widget}, +}; pub struct KayakWidgets; @@ -60,25 +63,91 @@ impl Plugin for KayakWidgets { } fn add_widget_systems(mut context: ResMut<Context>) { - context.add_widget_system(KayakApp::default().get_name(), app_update); - context.add_widget_system(KButton::default().get_name(), button_update); - context.add_widget_system(TextProps::default().get_name(), text_update); - context.add_widget_system(KWindow::default().get_name(), window_update); - context.add_widget_system(Background::default().get_name(), update_background); - context.add_widget_system(Clip::default().get_name(), update_clip); - context.add_widget_system(Image::default().get_name(), update_image); - context.add_widget_system(TextureAtlas::default().get_name(), update_texture_atlas); - context.add_widget_system(NinePatch::default().get_name(), update_nine_patch); - context.add_widget_system(Element::default().get_name(), update_element); - context.add_widget_system(ScrollBarProps::default().get_name(), update_scroll_bar); + context.add_widget_data::<KayakApp, EmptyState>(); + context.add_widget_data::<KButton, EmptyState>(); + context.add_widget_data::<TextProps, EmptyState>(); + context.add_widget_data::<KWindow, EmptyState>(); + context.add_widget_data::<Background, EmptyState>(); + context.add_widget_data::<Clip, EmptyState>(); + context.add_widget_data::<Image, EmptyState>(); + context.add_widget_data::<TextureAtlas, EmptyState>(); + context.add_widget_data::<NinePatch, EmptyState>(); + context.add_widget_data::<Element, EmptyState>(); + context.add_widget_data::<ScrollBarProps, EmptyState>(); + context.add_widget_data::<ScrollContentProps, EmptyState>(); + context.add_widget_data::<ScrollBoxProps, EmptyState>(); + context.add_widget_data::<ScrollContextProvider, EmptyState>(); + context.add_widget_data::<TextBoxProps, TextBoxState>(); + + context.add_widget_system(KayakApp::default().get_name(), app_update, app_render); + context.add_widget_system( + KButton::default().get_name(), + widget_update::<KButton, EmptyState>, + button_update, + ); + context.add_widget_system( + TextProps::default().get_name(), + widget_update::<TextProps, EmptyState>, + text_render, + ); + context.add_widget_system( + KWindow::default().get_name(), + widget_update::<KWindow, EmptyState>, + window_update, + ); + context.add_widget_system( + Background::default().get_name(), + widget_update::<Background, EmptyState>, + update_background, + ); + context.add_widget_system( + Clip::default().get_name(), + widget_update::<Clip, EmptyState>, + update_clip, + ); + context.add_widget_system( + Image::default().get_name(), + widget_update::<Image, EmptyState>, + update_image, + ); + context.add_widget_system( + TextureAtlas::default().get_name(), + widget_update::<TextureAtlas, EmptyState>, + update_texture_atlas, + ); + context.add_widget_system( + NinePatch::default().get_name(), + widget_update::<NinePatch, EmptyState>, + update_nine_patch, + ); + context.add_widget_system( + Element::default().get_name(), + widget_update::<Element, EmptyState>, + update_element, + ); + context.add_widget_system( + ScrollBarProps::default().get_name(), + widget_update_with_context::<ScrollBarProps, EmptyState, ScrollContext>, + update_scroll_bar, + ); context.add_widget_system( ScrollContentProps::default().get_name(), + widget_update_with_context::<ScrollContentProps, EmptyState, ScrollContext>, update_scroll_content, ); - context.add_widget_system(ScrollBoxProps::default().get_name(), update_scroll_box); + context.add_widget_system( + ScrollBoxProps::default().get_name(), + widget_update_with_context::<ScrollBoxProps, EmptyState, ScrollContext>, + update_scroll_box, + ); context.add_widget_system( ScrollContextProvider::default().get_name(), + widget_update::<ScrollContextProvider, EmptyState>, update_scroll_context, ); - context.add_widget_system(TextBoxProps::default().get_name(), update_text_box); + context.add_widget_system( + TextBoxProps::default().get_name(), + widget_update::<TextBoxProps, TextBoxState>, + update_text_box, + ); } diff --git a/src/widgets/nine_patch.rs b/src/widgets/nine_patch.rs index d368a66..f13f994 100644 --- a/src/widgets/nine_patch.rs +++ b/src/widgets/nine_patch.rs @@ -1,16 +1,14 @@ -use bevy::prelude::{ - Bundle, Changed, Commands, Component, Entity, Handle, Image, In, Or, Query, With, -}; +use bevy::prelude::{Bundle, Commands, Component, Entity, Handle, Image, In, Query}; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, prelude::WidgetContext, styles::{Edge, KStyle, RenderCommand, StyleProp}, - widget::Widget, + widget::{Widget, WidgetProps}, }; -#[derive(Component, Default, Debug)] +#[derive(Component, PartialEq, Clone, Default, Debug)] pub struct NinePatch { /// The handle to image pub handle: Handle<Image>, @@ -19,6 +17,7 @@ pub struct NinePatch { } impl Widget for NinePatch {} +impl WidgetProps for NinePatch {} #[derive(Bundle)] pub struct NinePatchBundle { @@ -42,10 +41,7 @@ impl Default for NinePatchBundle { pub fn update_nine_patch( In((widget_context, entity)): In<(WidgetContext, Entity)>, _: Commands, - mut query: Query< - (&mut KStyle, &NinePatch, &KChildren), - Or<((Changed<NinePatch>, Changed<KStyle>), With<Mounted>)>, - >, + mut query: Query<(&mut KStyle, &NinePatch, &KChildren)>, ) -> bool { if let Ok((mut style, nine_patch, children)) = query.get_mut(entity) { style.render_command = StyleProp::Value(RenderCommand::NinePatch { diff --git a/src/widgets/scroll/scroll_bar.rs b/src/widgets/scroll/scroll_bar.rs index 8748945..ba56e72 100644 --- a/src/widgets/scroll/scroll_bar.rs +++ b/src/widgets/scroll/scroll_bar.rs @@ -1,16 +1,15 @@ -use bevy::prelude::{ - Bundle, Changed, Color, Commands, Component, Entity, In, Or, ParamSet, Query, With, -}; +use bevy::prelude::{Bundle, Color, Commands, Component, Entity, In, Query}; use kayak_ui_macros::rsx; use crate::{ - context::{Mounted, WidgetName}, + context::WidgetName, event::{Event, EventType}, event_dispatcher::EventDispatcherContext, on_event::OnEvent, prelude::{KChildren, WidgetContext}, styles::{Corner, Edge, KStyle, PositionType, RenderCommand, Units}, - widget::Widget, + widget::{Widget, WidgetProps}, + widget_state::WidgetState, widgets::{BackgroundBundle, ClipBundle}, }; @@ -36,6 +35,7 @@ pub struct ScrollBarProps { } impl Widget for ScrollBarProps {} +impl WidgetProps for ScrollBarProps {} #[derive(Bundle)] pub struct ScrollBarBundle { @@ -57,264 +57,255 @@ impl Default for ScrollBarBundle { pub fn update_scroll_bar( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - mut query: ParamSet<( - Query<Entity, Or<(Changed<ScrollBarProps>, With<Mounted>)>>, - Query<(&ScrollBarProps, &mut KStyle)>, - )>, - mut context_query: ParamSet<(Query<Entity, Changed<ScrollContext>>, Query<&ScrollContext>)>, + mut query: Query<(&ScrollBarProps, &mut KStyle)>, + context_query: Query<&ScrollContext>, ) -> bool { - if !context_query.p0().is_empty() | !query.p0().is_empty() { - if let Ok((scrollbar, mut styles)) = query.p1().get_mut(entity) { - if let Some(context_entity) = widget_context.get_context_entity::<ScrollContext>(entity) - { - if let Ok(scroll_context) = context_query.p1().get(context_entity) { - let scroll_x = scroll_context.scroll_x(); - let scroll_y = scroll_context.scroll_y(); - let content_width = scroll_context.content_width(); - let content_height = scroll_context.content_height(); - let scrollable_width = scroll_context.scrollable_width(); - let scrollable_height = scroll_context.scrollable_height(); + if let Ok((scrollbar, mut styles)) = query.get_mut(entity) { + if let Some(context_entity) = widget_context.get_context_entity::<ScrollContext>(entity) { + if let Ok(scroll_context) = context_query.get(context_entity) { + let scroll_x = scroll_context.scroll_x(); + let scroll_y = scroll_context.scroll_y(); + let content_width = scroll_context.content_width(); + let content_height = scroll_context.content_height(); + let scrollable_width = scroll_context.scrollable_width(); + let scrollable_height = scroll_context.scrollable_height(); - let layout = widget_context.get_layout(entity).unwrap_or_default(); + let layout = widget_context.get_layout(entity).unwrap_or_default(); - // === Configuration === // - // let disabled = scrollbar.disabled; - let horizontal = scrollbar.horizontal; - let _thickness = scrollbar.thickness; - let thickness = scrollbar.thickness; - let thumb_color = scrollbar - .thumb_color - .unwrap_or_else(|| Color::rgba(0.2981, 0.3098, 0.321, 0.95)); - let thumb_styles = scrollbar.thumb_styles.clone(); - let track_color = scrollbar - .track_color - .unwrap_or_else(|| Color::rgba(0.1581, 0.1758, 0.191, 0.15)); - let track_styles = scrollbar.track_styles.clone(); - // The size of the thumb as a percentage - let thumb_size_percent = (if scrollbar.horizontal { - layout.width / (content_width - thickness).max(1.0) - } else { - layout.height / (content_height - thickness).max(1.0) - }) - .clamp(0.1, 1.0); - // The size of the thumb in pixels - let thumb_size_pixels = thumb_size_percent - * if scrollbar.horizontal { - layout.width - } else { - layout.height - }; - let thumb_extents = thumb_size_pixels / 2.0; - let percent_scrolled = if scrollbar.horizontal { - scroll_context.percent_x() + // === Configuration === // + // let disabled = scrollbar.disabled; + let horizontal = scrollbar.horizontal; + let _thickness = scrollbar.thickness; + let thickness = scrollbar.thickness; + let thumb_color = scrollbar + .thumb_color + .unwrap_or_else(|| Color::rgba(0.2981, 0.3098, 0.321, 0.95)); + let thumb_styles = scrollbar.thumb_styles.clone(); + let track_color = scrollbar + .track_color + .unwrap_or_else(|| Color::rgba(0.1581, 0.1758, 0.191, 0.15)); + let track_styles = scrollbar.track_styles.clone(); + // The size of the thumb as a percentage + let thumb_size_percent = (if scrollbar.horizontal { + layout.width / (content_width - thickness).max(1.0) + } else { + layout.height / (content_height - thickness).max(1.0) + }) + .clamp(0.1, 1.0); + // The size of the thumb in pixels + let thumb_size_pixels = thumb_size_percent + * if scrollbar.horizontal { + layout.width } else { - scroll_context.percent_y() + layout.height }; - // The thumb's offset as a percentage - let thumb_offset = map_range( - percent_scrolled * 100.0, - (0.0, 100.0), - (0.0, 100.0 - thumb_size_percent * 100.0), - ); + let thumb_extents = thumb_size_pixels / 2.0; + let percent_scrolled = if scrollbar.horizontal { + scroll_context.percent_x() + } else { + scroll_context.percent_y() + }; + // The thumb's offset as a percentage + let thumb_offset = map_range( + percent_scrolled * 100.0, + (0.0, 100.0), + (0.0, 100.0 - thumb_size_percent * 100.0), + ); - // === Styles === // - *styles = KStyle::default().with_style(KStyle { - render_command: RenderCommand::Layout.into(), - width: if horizontal { - Units::Stretch(1.0) - } else { - Units::Pixels(thickness) - } - .into(), - height: if horizontal { - Units::Pixels(thickness) - } else { - Units::Stretch(1.0) - } - .into(), - ..Default::default() - }); - - let mut track_style = - KStyle::default() - .with_style(&track_styles) - .with_style(KStyle { - background_color: track_color.into(), - border_radius: Corner::all(thickness / 2.0).into(), - ..Default::default() - }); - - let mut border_color = thumb_color; - match &mut border_color { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - *alpha = (*alpha - 0.2).max(0.0); - *red = (*red + 0.1).min(1.0); - *green = (*green + 0.1).min(1.0); - *blue = (*blue + 0.1).min(1.0); - } - _ => {} + // === Styles === // + *styles = KStyle::default().with_style(KStyle { + render_command: RenderCommand::Layout.into(), + width: if horizontal { + Units::Stretch(1.0) + } else { + Units::Pixels(thickness) + } + .into(), + height: if horizontal { + Units::Pixels(thickness) + } else { + Units::Stretch(1.0) } + .into(), + ..Default::default() + }); - let mut thumb_style = KStyle::default() + let mut track_style = + KStyle::default() + .with_style(&track_styles) .with_style(KStyle { - position_type: PositionType::SelfDirected.into(), - ..Default::default() - }) - .with_style(&thumb_styles) - .with_style(KStyle { - background_color: thumb_color.into(), + background_color: track_color.into(), border_radius: Corner::all(thickness / 2.0).into(), - border: Edge::all(1.0).into(), - border_color: border_color.into(), ..Default::default() }); - if scrollbar.horizontal { - track_style.apply(KStyle { - height: Units::Pixels(thickness).into(), - width: Units::Stretch(1.0).into(), - ..Default::default() - }); - thumb_style.apply(KStyle { - height: Units::Pixels(thickness).into(), - width: Units::Percentage(thumb_size_percent * 100.0).into(), - top: Units::Pixels(0.0).into(), - left: Units::Percentage(-thumb_offset).into(), - ..Default::default() - }); - } else { - track_style.apply(KStyle { - width: Units::Pixels(thickness).into(), - height: Units::Stretch(1.0).into(), - ..Default::default() - }); - thumb_style.apply(KStyle { - width: Units::Pixels(thickness).into(), - height: Units::Percentage(thumb_size_percent * 100.0).into(), - top: Units::Percentage(-thumb_offset).into(), - left: Units::Pixels(0.0).into(), - ..Default::default() - }); + let mut border_color = thumb_color; + match &mut border_color { + Color::Rgba { + red, + green, + blue, + alpha, + } => { + *alpha = (*alpha - 0.2).max(0.0); + *red = (*red + 0.1).min(1.0); + *green = (*green + 0.1).min(1.0); + *blue = (*blue + 0.1).min(1.0); } + _ => {} + } - // === Events === // - let on_event = OnEvent::new( - move |In((mut event_dispatcher_context, mut event, _entity)): In<( - EventDispatcherContext, - Event, - Entity, - )>, - mut query: Query<&mut ScrollContext>| { - if let Ok(mut scroll_context) = query.get_mut(context_entity) { - match event.event_type { - EventType::MouseDown(data) => { - // --- Capture Cursor --- // - event_dispatcher_context - .capture_cursor(event.current_target); - scroll_context.start_pos = data.position.into(); - scroll_context.is_dragging = true; + let mut thumb_style = KStyle::default() + .with_style(KStyle { + position_type: PositionType::SelfDirected.into(), + ..Default::default() + }) + .with_style(&thumb_styles) + .with_style(KStyle { + background_color: thumb_color.into(), + border_radius: Corner::all(thickness / 2.0).into(), + border: Edge::all(1.0).into(), + border_color: border_color.into(), + ..Default::default() + }); - // --- Calculate Start Offsets --- // - // Steps: - // 1. Get position relative to this widget - // 2. Convert relative pos to percentage [0-1] - // 3. Multiply by desired scrollable dimension - // 4. Map value to range padded by half thumb_size (both sides) - // 5. Update scroll - let offset: (f32, f32) = if horizontal { - // 1. - let mut x = data.position.0 - layout.posx; - // 2. - x /= layout.width; - // 3. - x *= -scrollable_width; - // 4. - x = map_range( - x, - (-scrollable_width, 0.0), - (-scrollable_width - thumb_extents, thumb_extents), - ); - // 5. - scroll_context.set_scroll_x(x); + if scrollbar.horizontal { + track_style.apply(KStyle { + height: Units::Pixels(thickness).into(), + width: Units::Stretch(1.0).into(), + ..Default::default() + }); + thumb_style.apply(KStyle { + height: Units::Pixels(thickness).into(), + width: Units::Percentage(thumb_size_percent * 100.0).into(), + top: Units::Pixels(0.0).into(), + left: Units::Percentage(-thumb_offset).into(), + ..Default::default() + }); + } else { + track_style.apply(KStyle { + width: Units::Pixels(thickness).into(), + height: Units::Stretch(1.0).into(), + ..Default::default() + }); + thumb_style.apply(KStyle { + width: Units::Pixels(thickness).into(), + height: Units::Percentage(thumb_size_percent * 100.0).into(), + top: Units::Percentage(-thumb_offset).into(), + left: Units::Pixels(0.0).into(), + ..Default::default() + }); + } - (x, scroll_y) - } else { - // 1. - let mut y = data.position.1 - layout.posy; - // 2. - y /= layout.height; - // 3. - y *= -scrollable_height; - // 4. - y = map_range( - y, - (-scrollable_height, 0.0), - (-scrollable_height - thumb_extents, thumb_extents), - ); - // 5. - scroll_context.set_scroll_y(y); + // === Events === // + let on_event = OnEvent::new( + move |In((mut event_dispatcher_context, _, mut event, _entity)): In<( + EventDispatcherContext, + WidgetState, + Event, + Entity, + )>, + mut query: Query<&mut ScrollContext>| { + if let Ok(mut scroll_context) = query.get_mut(context_entity) { + match event.event_type { + EventType::MouseDown(data) => { + // --- Capture Cursor --- // + event_dispatcher_context.capture_cursor(event.current_target); + scroll_context.start_pos = data.position.into(); + scroll_context.is_dragging = true; - (scroll_x, y) - }; - scroll_context.start_offset = offset.into(); - } - EventType::MouseUp(..) => { - // --- Release Cursor --- // - event_dispatcher_context - .release_cursor(event.current_target); - scroll_context.is_dragging = false; - } - EventType::Hover(data) => { - if scroll_context.is_dragging { - // --- Move Thumb --- // - // Positional difference (scaled by thumb size) - let pos_diff = ( - (scroll_context.start_pos.x - data.position.0) - / thumb_size_percent, - (scroll_context.start_pos.y - data.position.1) - / thumb_size_percent, - ); - let start_offset = scroll_context.start_offset; - if horizontal { - scroll_context - .set_scroll_x(start_offset.x + pos_diff.0); - } else { - scroll_context - .set_scroll_y(start_offset.y + pos_diff.1); - } + // --- Calculate Start Offsets --- // + // Steps: + // 1. Get position relative to this widget + // 2. Convert relative pos to percentage [0-1] + // 3. Multiply by desired scrollable dimension + // 4. Map value to range padded by half thumb_size (both sides) + // 5. Update scroll + let offset: (f32, f32) = if horizontal { + // 1. + let mut x = data.position.0 - layout.posx; + // 2. + x /= layout.width; + // 3. + x *= -scrollable_width; + // 4. + x = map_range( + x, + (-scrollable_width, 0.0), + (-scrollable_width - thumb_extents, thumb_extents), + ); + // 5. + scroll_context.set_scroll_x(x); + + (x, scroll_y) + } else { + // 1. + let mut y = data.position.1 - layout.posy; + // 2. + y /= layout.height; + // 3. + y *= -scrollable_height; + // 4. + y = map_range( + y, + (-scrollable_height, 0.0), + (-scrollable_height - thumb_extents, thumb_extents), + ); + // 5. + scroll_context.set_scroll_y(y); + + (scroll_x, y) + }; + scroll_context.start_offset = offset.into(); + } + EventType::MouseUp(..) => { + // --- Release Cursor --- // + event_dispatcher_context.release_cursor(event.current_target); + scroll_context.is_dragging = false; + } + EventType::Hover(data) => { + if scroll_context.is_dragging { + // --- Move Thumb --- // + // Positional difference (scaled by thumb size) + let pos_diff = ( + (scroll_context.start_pos.x - data.position.0) + / thumb_size_percent, + (scroll_context.start_pos.y - data.position.1) + / thumb_size_percent, + ); + let start_offset = scroll_context.start_offset; + if horizontal { + scroll_context + .set_scroll_x(start_offset.x + pos_diff.0); + } else { + scroll_context + .set_scroll_y(start_offset.y + pos_diff.1); } } - EventType::Scroll(..) if scroll_context.is_dragging => { - // Prevent scrolling while dragging - // This is a bit of a hack to prevent issues when scrolling while dragging - event.stop_propagation(); - } - _ => {} } + EventType::Scroll(..) if scroll_context.is_dragging => { + // Prevent scrolling while dragging + // This is a bit of a hack to prevent issues when scrolling while dragging + event.stop_propagation(); + } + _ => {} } + } - (event_dispatcher_context, event) - }, - ); - - let parent_id = Some(entity); - rsx! { - <BackgroundBundle on_event={on_event} styles={track_style}> - <ClipBundle> - <BackgroundBundle styles={thumb_style} /> - </ClipBundle> - </BackgroundBundle> - } + (event_dispatcher_context, event) + }, + ); - return true; + let parent_id = Some(entity); + rsx! { + <BackgroundBundle on_event={on_event} styles={track_style}> + <ClipBundle> + <BackgroundBundle styles={thumb_style} /> + </ClipBundle> + </BackgroundBundle> } } } } - false + true } diff --git a/src/widgets/scroll/scroll_box.rs b/src/widgets/scroll/scroll_box.rs index 9790e7a..8615a79 100644 --- a/src/widgets/scroll/scroll_box.rs +++ b/src/widgets/scroll/scroll_box.rs @@ -1,10 +1,8 @@ -use bevy::prelude::{ - Bundle, Changed, Color, Commands, Component, Entity, In, Or, ParamSet, Query, With, -}; +use bevy::prelude::{Bundle, Color, Commands, Component, Entity, In, ParamSet, Query}; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, cursor::ScrollUnit, event::{Event, EventType}, event_dispatcher::EventDispatcherContext, @@ -13,7 +11,8 @@ use crate::{ on_layout::OnLayout, prelude::{constructor, rsx, WidgetContext}, styles::{KStyle, LayoutType, PositionType, RenderCommand, Units}, - widget::Widget, + widget::{Widget, WidgetProps}, + widget_state::WidgetState, widgets::{ scroll::{ scroll_bar::{ScrollBarBundle, ScrollBarProps}, @@ -25,7 +24,7 @@ use crate::{ use super::scroll_context::ScrollContext; -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] pub struct ScrollBoxProps { /// If true, always shows scrollbars even when there's nothing to scroll /// @@ -55,6 +54,7 @@ pub struct ScrollBoxProps { } impl Widget for ScrollBoxProps {} +impl WidgetProps for ScrollBoxProps {} #[derive(Bundle)] pub struct ScrollBoxBundle { @@ -80,183 +80,156 @@ impl Default for ScrollBoxBundle { pub fn update_scroll_box( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - mut query: ParamSet<( - Query<Entity, Or<(Changed<ScrollBoxProps>, Changed<KChildren>, With<Mounted>)>>, - Query<(&ScrollBoxProps, &mut KStyle, &KChildren, &mut OnLayout)>, - )>, - mut context_query: ParamSet<( - Query<Entity, Changed<ScrollContext>>, - Query<&ScrollContext>, - Query<&mut ScrollContext>, - )>, + mut query: Query<(&ScrollBoxProps, &mut KStyle, &KChildren, &mut OnLayout)>, + mut context_query: ParamSet<(Query<&ScrollContext>, Query<&mut ScrollContext>)>, ) -> bool { - if !context_query.p0().is_empty() || !query.p0().is_empty() { - if let Ok((scroll_box, mut styles, scroll_box_children, mut on_layout)) = - query.p1().get_mut(entity) - { - if let Some(context_entity) = widget_context.get_context_entity::<ScrollContext>(entity) - { - if let Ok(scroll_context) = context_query.p1().get(context_entity).cloned() { - // === Configuration === // - let always_show_scrollbar = scroll_box.always_show_scrollbar; - let disable_horizontal = scroll_box.disable_horizontal; - let disable_vertical = scroll_box.disable_vertical; - let hide_horizontal = scroll_box.hide_horizontal; - let hide_vertical = scroll_box.hide_vertical; - let scrollbar_thickness = scroll_box.scrollbar_thickness.unwrap_or(10.0); - let scroll_line = scroll_box.scroll_line.unwrap_or(16.0); - let thumb_color = scroll_box.thumb_color; - let thumb_styles = scroll_box.thumb_styles.clone(); - let track_color = scroll_box.track_color; - let track_styles = scroll_box.track_styles.clone(); + if let Ok((scroll_box, mut styles, scroll_box_children, mut on_layout)) = query.get_mut(entity) + { + if let Some(context_entity) = widget_context.get_context_entity::<ScrollContext>(entity) { + if let Ok(scroll_context) = context_query.p1().get(context_entity).cloned() { + // === Configuration === // + let always_show_scrollbar = scroll_box.always_show_scrollbar; + let disable_horizontal = scroll_box.disable_horizontal; + let disable_vertical = scroll_box.disable_vertical; + let hide_horizontal = scroll_box.hide_horizontal; + let hide_vertical = scroll_box.hide_vertical; + let scrollbar_thickness = scroll_box.scrollbar_thickness.unwrap_or(10.0); + let scroll_line = scroll_box.scroll_line.unwrap_or(16.0); + let thumb_color = scroll_box.thumb_color; + let thumb_styles = scroll_box.thumb_styles.clone(); + let track_color = scroll_box.track_color; + let track_styles = scroll_box.track_styles.clone(); - let scroll_x = scroll_context.scroll_x(); - let scroll_y = scroll_context.scroll_y(); - let scrollable_width = scroll_context.scrollable_width(); - let scrollable_height = scroll_context.scrollable_height(); + let scroll_x = scroll_context.scroll_x(); + let scroll_y = scroll_context.scroll_y(); + let scrollable_width = scroll_context.scrollable_width(); + let scrollable_height = scroll_context.scrollable_height(); - let hori_thickness = scrollbar_thickness; - let vert_thickness = scrollbar_thickness; + let hori_thickness = scrollbar_thickness; + let vert_thickness = scrollbar_thickness; - let hide_horizontal = hide_horizontal - || !always_show_scrollbar && scrollable_width < f32::EPSILON; - let hide_vertical = - hide_vertical || !always_show_scrollbar && scrollable_height < f32::EPSILON; + let hide_horizontal = + hide_horizontal || !always_show_scrollbar && scrollable_width < f32::EPSILON; + let hide_vertical = + hide_vertical || !always_show_scrollbar && scrollable_height < f32::EPSILON; - let pad_x = if hide_vertical { 0.0 } else { vert_thickness }; - let pad_y = if hide_horizontal { 0.0 } else { hori_thickness }; + let pad_x = if hide_vertical { 0.0 } else { vert_thickness }; + let pad_y = if hide_horizontal { 0.0 } else { hori_thickness }; - if pad_x != scroll_context.pad_x || pad_y != scroll_context.pad_y { - if let Ok(mut scroll_context_mut) = - context_query.p2().get_mut(context_entity) - { - scroll_context_mut.pad_x = pad_x; - scroll_context_mut.pad_y = pad_y; - } + if pad_x != scroll_context.pad_x || pad_y != scroll_context.pad_y { + if let Ok(mut scroll_context_mut) = context_query.p1().get_mut(context_entity) { + scroll_context_mut.pad_x = pad_x; + scroll_context_mut.pad_y = pad_y; } + } - *on_layout = OnLayout::new( - move |In((event, _entity)): In<(LayoutEvent, Entity)>, - mut query: Query<&mut ScrollContext>| { - if event.flags.intersects( - GeometryChanged::WIDTH_CHANGED | GeometryChanged::HEIGHT_CHANGED, - ) { - if let Ok(mut scroll) = query.get_mut(context_entity) { - scroll.scrollbox_width = event.layout.width; - scroll.scrollbox_height = event.layout.height; - } + *on_layout = OnLayout::new( + move |In((event, _entity)): In<(LayoutEvent, Entity)>, + mut query: Query<&mut ScrollContext>| { + if event.flags.intersects( + GeometryChanged::WIDTH_CHANGED | GeometryChanged::HEIGHT_CHANGED, + ) { + if let Ok(mut scroll) = query.get_mut(context_entity) { + scroll.scrollbox_width = event.layout.width; + scroll.scrollbox_height = event.layout.height; } + } - event - }, - ); - - // === Styles === // - *styles = KStyle::default() - .with_style(KStyle { - render_command: RenderCommand::Layout.into(), - ..Default::default() - }) - .with_style(styles.clone()) - .with_style(KStyle { - width: Units::Stretch(1.0).into(), - height: Units::Stretch(1.0).into(), - ..Default::default() - }); + event + }, + ); - let hbox_styles = KStyle::default().with_style(KStyle { + // === Styles === // + *styles = KStyle::default() + .with_style(KStyle { render_command: RenderCommand::Layout.into(), - layout_type: LayoutType::Row.into(), - width: Units::Stretch(1.0).into(), ..Default::default() - }); - let vbox_styles = KStyle::default().with_style(KStyle { - render_command: RenderCommand::Layout.into(), - layout_type: LayoutType::Column.into(), + }) + .with_style(styles.clone()) + .with_style(KStyle { width: Units::Stretch(1.0).into(), + height: Units::Stretch(1.0).into(), ..Default::default() }); - let content_styles = KStyle::default().with_style(KStyle { - position_type: PositionType::SelfDirected.into(), - top: Units::Pixels(scroll_y).into(), - left: Units::Pixels(scroll_x).into(), - ..Default::default() - }); + let hbox_styles = KStyle::default().with_style(KStyle { + render_command: RenderCommand::Layout.into(), + layout_type: LayoutType::Row.into(), + width: Units::Stretch(1.0).into(), + ..Default::default() + }); + let vbox_styles = KStyle::default().with_style(KStyle { + render_command: RenderCommand::Layout.into(), + layout_type: LayoutType::Column.into(), + width: Units::Stretch(1.0).into(), + ..Default::default() + }); - let event_handler = OnEvent::new( - move |In((event_dispatcher_context, mut event, _entity)): In<( - EventDispatcherContext, - Event, - Entity, - )>, - mut query: Query<&mut ScrollContext>| { - if let Ok(mut scroll_context) = query.get_mut(context_entity) { - match event.event_type { - EventType::Scroll(evt) => { - match evt.delta { - ScrollUnit::Line { x, y } => { - if !disable_horizontal { - scroll_context - .set_scroll_x(scroll_x - x * scroll_line); - } - if !disable_vertical { - scroll_context - .set_scroll_y(scroll_y + y * scroll_line); - } + let content_styles = KStyle::default().with_style(KStyle { + position_type: PositionType::SelfDirected.into(), + top: Units::Pixels(scroll_y).into(), + left: Units::Pixels(scroll_x).into(), + ..Default::default() + }); + + let event_handler = OnEvent::new( + move |In((event_dispatcher_context, _, mut event, _entity)): In<( + EventDispatcherContext, + WidgetState, + Event, + Entity, + )>, + mut query: Query<&mut ScrollContext>| { + if let Ok(mut scroll_context) = query.get_mut(context_entity) { + match event.event_type { + EventType::Scroll(evt) => { + match evt.delta { + ScrollUnit::Line { x, y } => { + if !disable_horizontal { + scroll_context + .set_scroll_x(scroll_x - x * scroll_line); } - ScrollUnit::Pixel { x, y } => { - if !disable_horizontal { - scroll_context.set_scroll_x(scroll_x - x); - } - if !disable_vertical { - scroll_context.set_scroll_y(scroll_y + y); - } + if !disable_vertical { + scroll_context + .set_scroll_y(scroll_y + y * scroll_line); + } + } + ScrollUnit::Pixel { x, y } => { + if !disable_horizontal { + scroll_context.set_scroll_x(scroll_x - x); + } + if !disable_vertical { + scroll_context.set_scroll_y(scroll_y + y); } } - event.stop_propagation(); } - _ => {} + event.stop_propagation(); } + _ => {} } - (event_dispatcher_context, event) - }, - ); + } + (event_dispatcher_context, event) + }, + ); - let parent_id = Some(entity); - rsx! { - <ElementBundle on_event={event_handler} styles={hbox_styles}> - <ElementBundle styles={vbox_styles}> - <ClipBundle> - <ScrollContentBundle - children={scroll_box_children.clone()} - styles={content_styles} - /> - </ClipBundle> - {if !hide_horizontal { - constructor! { - <ScrollBarBundle - scrollbar_props={ScrollBarProps { - disabled: disable_horizontal, - horizontal: true, - thickness: hori_thickness, - thumb_color: thumb_color, - thumb_styles: thumb_styles.clone(), - track_color: track_color, - track_styles: track_styles.clone(), - ..Default::default() - }} - /> - } - }} - </ElementBundle> - {if !hide_vertical { + let parent_id = Some(entity); + rsx! { + <ElementBundle on_event={event_handler} styles={hbox_styles}> + <ElementBundle styles={vbox_styles}> + <ClipBundle> + <ScrollContentBundle + children={scroll_box_children.clone()} + styles={content_styles} + /> + </ClipBundle> + {if !hide_horizontal { constructor! { <ScrollBarBundle scrollbar_props={ScrollBarProps { - disabled: disable_vertical, + disabled: disable_horizontal, + horizontal: true, thickness: hori_thickness, - thumb_color: thumb_color.clone(), + thumb_color: thumb_color, thumb_styles: thumb_styles.clone(), track_color: track_color, track_styles: track_styles.clone(), @@ -266,12 +239,25 @@ pub fn update_scroll_box( } }} </ElementBundle> - } - - return true; + {if !hide_vertical { + constructor! { + <ScrollBarBundle + scrollbar_props={ScrollBarProps { + disabled: disable_vertical, + thickness: hori_thickness, + thumb_color: thumb_color.clone(), + thumb_styles: thumb_styles.clone(), + track_color: track_color, + track_styles: track_styles.clone(), + ..Default::default() + }} + /> + } + }} + </ElementBundle> } } } } - false + true } diff --git a/src/widgets/scroll/scroll_content.rs b/src/widgets/scroll/scroll_content.rs index d59e703..7ea79ca 100644 --- a/src/widgets/scroll/scroll_content.rs +++ b/src/widgets/scroll/scroll_content.rs @@ -1,22 +1,23 @@ -use bevy::prelude::{Bundle, Changed, Component, Entity, In, Or, ParamSet, Query, With}; +use bevy::prelude::{Bundle, Component, Entity, In, Query, With}; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, layout::GeometryChanged, layout::LayoutEvent, on_layout::OnLayout, prelude::WidgetContext, styles::{KStyle, LayoutType, RenderCommand, Units}, - widget::Widget, + widget::{Widget, WidgetProps}, }; use super::scroll_context::ScrollContext; -#[derive(Component, Default)] +#[derive(Component, Default, PartialEq, Clone)] pub struct ScrollContentProps; impl Widget for ScrollContentProps {} +impl WidgetProps for ScrollContentProps {} #[derive(Bundle)] pub struct ScrollContentBundle { @@ -41,66 +42,52 @@ impl Default for ScrollContentBundle { pub fn update_scroll_content( In((widget_context, entity)): In<(WidgetContext, Entity)>, - mut query: ParamSet<( - Query< - Entity, - Or<( - Changed<ScrollContentProps>, - Changed<KChildren>, - With<Mounted>, - )>, - >, - Query<(&mut KStyle, &KChildren, &mut OnLayout), With<ScrollContentProps>>, - )>, - mut context_query: ParamSet<(Query<Entity, Changed<ScrollContext>>, Query<&ScrollContext>)>, + mut query: Query<(&mut KStyle, &KChildren, &mut OnLayout), With<ScrollContentProps>>, + context_query: Query<&ScrollContext>, ) -> bool { - if !context_query.p0().is_empty() || !query.p0().is_empty() { - if let Ok((mut styles, children, mut on_layout)) = query.p1().get_mut(entity) { - if let Some(context_entity) = widget_context.get_context_entity::<ScrollContext>(entity) - { - if let Ok(scroll_context) = context_query.p1().get(context_entity) { - // === OnLayout === // - *on_layout = OnLayout::new( - move |In((event, _entity)): In<(LayoutEvent, Entity)>, - mut query: Query<&mut ScrollContext>| { - if event.flags.intersects( - GeometryChanged::WIDTH_CHANGED | GeometryChanged::HEIGHT_CHANGED, - ) { - if let Ok(mut scroll) = query.get_mut(context_entity) { - scroll.content_width = event.layout.width; - scroll.content_height = event.layout.height; - } + if let Ok((mut styles, children, mut on_layout)) = query.get_mut(entity) { + if let Some(context_entity) = widget_context.get_context_entity::<ScrollContext>(entity) { + if let Ok(scroll_context) = context_query.get(context_entity) { + // === OnLayout === // + *on_layout = OnLayout::new( + move |In((event, _entity)): In<(LayoutEvent, Entity)>, + mut query: Query<&mut ScrollContext>| { + if event.flags.intersects( + GeometryChanged::WIDTH_CHANGED | GeometryChanged::HEIGHT_CHANGED, + ) { + if let Ok(mut scroll) = query.get_mut(context_entity) { + scroll.content_width = event.layout.width; + scroll.content_height = event.layout.height; } + } - event - }, - ); + event + }, + ); - // === Styles === // - *styles = KStyle::default() - .with_style(KStyle { - render_command: RenderCommand::Layout.into(), - layout_type: LayoutType::Column.into(), - min_width: Units::Pixels( - scroll_context.scrollbox_width - scroll_context.pad_x, - ) - .into(), - min_height: Units::Stretch( - scroll_context.scrollbox_height - scroll_context.pad_y, - ) - .into(), - width: Units::Auto.into(), - height: Units::Auto.into(), - ..Default::default() - }) - .with_style(styles.clone()); + // === Styles === // + *styles = KStyle::default() + .with_style(KStyle { + render_command: RenderCommand::Layout.into(), + layout_type: LayoutType::Column.into(), + min_width: Units::Pixels( + scroll_context.scrollbox_width - scroll_context.pad_x, + ) + .into(), + min_height: Units::Stretch( + scroll_context.scrollbox_height - scroll_context.pad_y, + ) + .into(), + width: Units::Auto.into(), + height: Units::Auto.into(), + ..Default::default() + }) + .with_style(styles.clone()); - children.process(&widget_context, Some(entity)); - - return true; - } + children.process(&widget_context, Some(entity)); } } } - false + + true } diff --git a/src/widgets/scroll/scroll_context.rs b/src/widgets/scroll/scroll_context.rs index d73b4cd..413c124 100644 --- a/src/widgets/scroll/scroll_context.rs +++ b/src/widgets/scroll/scroll_context.rs @@ -1,11 +1,11 @@ -use bevy::prelude::{Bundle, Changed, Commands, Component, Entity, In, Or, Query, Vec2, With}; +use bevy::prelude::{Bundle, Commands, Component, Entity, In, Query, Vec2}; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, prelude::WidgetContext, styles::KStyle, - widget::Widget, + widget::{Widget, WidgetProps}, }; /// Context data provided by a [`ScrollBox`](crate::ScrollBox) widget @@ -134,12 +134,13 @@ impl ScrollContext { } } -#[derive(Component, Default)] +#[derive(Component, Default, PartialEq, Clone)] pub struct ScrollContextProvider { initial_value: ScrollContext, } impl Widget for ScrollContextProvider {} +impl WidgetProps for ScrollContextProvider {} #[derive(Bundle)] pub struct ScrollContextProviderBundle { @@ -163,21 +164,13 @@ impl Default for ScrollContextProviderBundle { pub fn update_scroll_context( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - mut query: Query< - (&ScrollContextProvider, &KChildren), - Or<( - Changed<ScrollContextProvider>, - Changed<KChildren>, - With<Mounted>, - )>, - >, + mut query: Query<(&ScrollContextProvider, &KChildren)>, ) -> bool { if let Ok((context_provider, children)) = query.get_mut(entity) { let context_entity = commands.spawn(context_provider.initial_value).id(); widget_context.set_context_entity::<ScrollContext>(Some(entity), context_entity); children.process(&widget_context, Some(entity)); - return true; } - false + true } diff --git a/src/widgets/text.rs b/src/widgets/text.rs index b6e4d01..13b307d 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -2,13 +2,13 @@ use bevy::prelude::*; use kayak_font::Alignment; use crate::{ - context::{Mounted, WidgetName}, + context::WidgetName, prelude::WidgetContext, - styles::{KStyle, RenderCommand, StyleProp}, - widget::Widget, + styles::{KCursorIcon, KStyle, RenderCommand, StyleProp}, + widget::{Widget, WidgetProps}, }; -#[derive(Component)] +#[derive(Component, Debug, PartialEq, Clone)] pub struct TextProps { /// The string to display pub content: String, @@ -44,6 +44,7 @@ impl Default for TextProps { } impl Widget for TextProps {} +impl WidgetProps for TextProps {} #[derive(Bundle)] pub struct TextWidgetBundle { @@ -62,9 +63,9 @@ impl Default for TextWidgetBundle { } } -pub fn text_update( +pub fn text_render( In((_, entity)): In<(WidgetContext, Entity)>, - mut query: Query<(&mut KStyle, &TextProps), Or<(Changed<TextProps>, With<Mounted>)>>, + mut query: Query<(&mut KStyle, &TextProps)>, ) -> bool { if let Ok((mut style, text)) = query.get_mut(entity) { style.render_command = StyleProp::Value(RenderCommand::Text { @@ -75,9 +76,9 @@ pub fn text_update( if let Some(ref font) = text.font { style.font = StyleProp::Value(font.clone()); } - // if text.show_cursor { - // style.cursor = StyleProp::Value(CursorIcon::Text); - // } + if text.show_cursor { + style.cursor = StyleProp::Value(KCursorIcon(CursorIcon::Text)); + } if text.size >= 0.0 { style.font_size = StyleProp::Value(text.size); } @@ -85,10 +86,8 @@ pub fn text_update( style.line_height = StyleProp::Value(line_height); } - // style.cursor = CursorIcon::Hand.into(); - - return true; + // style.cursor = StyleProp::Value(KCursorIcon(CursorIcon::Hand)); } - false + true } diff --git a/src/widgets/text_box.rs b/src/widgets/text_box.rs index f0d6708..817b4f9 100644 --- a/src/widgets/text_box.rs +++ b/src/widgets/text_box.rs @@ -1,17 +1,16 @@ -use bevy::prelude::{ - Bundle, Changed, Color, Commands, Component, Entity, In, Or, ParamSet, Query, With, -}; +use bevy::prelude::{Bundle, Color, Commands, Component, Entity, In, Query}; use kayak_ui_macros::rsx; use crate::{ - context::{Mounted, WidgetName}, + context::WidgetName, event::{Event, EventType}, event_dispatcher::EventDispatcherContext, on_event::OnEvent, on_layout::OnLayout, prelude::{KChildren, OnChange, WidgetContext}, styles::{Corner, KStyle, RenderCommand, StyleProp, Units}, - widget::Widget, + widget::{Widget, WidgetProps}, + widget_state::WidgetState, widgets::{ text::{TextProps, TextWidgetBundle}, BackgroundBundle, ClipBundle, @@ -20,7 +19,7 @@ use crate::{ }; /// Props used by the [`TextBox`] widget -#[derive(Component, Default, Debug, PartialEq, Clone)] +#[derive(Component, PartialEq, Default, Debug, Clone)] pub struct TextBoxProps { /// If true, prevents the widget from being focused (and consequently edited) pub disabled: bool, @@ -33,7 +32,7 @@ pub struct TextBoxProps { pub value: String, } -#[derive(Component, Default)] +#[derive(Component, Default, Clone, PartialEq)] pub struct TextBoxState { pub focused: bool, } @@ -41,6 +40,7 @@ pub struct TextBoxState { pub struct TextBoxValue(pub String); impl Widget for TextBoxProps {} +impl WidgetProps for TextBoxProps {} /// A widget that displays a text input field /// @@ -83,125 +83,115 @@ impl Default for TextBoxBundle { pub fn update_text_box( In((widget_context, entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - mut query: ParamSet<( - Query<Entity, Or<(Changed<TextBoxProps>, Changed<KStyle>, With<Mounted>)>>, - Query<(&mut KStyle, &TextBoxProps, &mut OnEvent, &OnChange)>, - )>, - mut context_query: ParamSet<(Query<Entity, Changed<TextBoxState>>, Query<&TextBoxState>)>, + mut query: Query<(&mut KStyle, &TextBoxProps, &mut OnEvent, &OnChange)>, ) -> bool { - if !query.p0().is_empty() || !context_query.p0().is_empty() { - if let Ok((mut styles, text_box, mut on_event, on_change)) = query.p1().get_mut(entity) { - let state_entity = widget_context.get_context_entity::<TextBoxState>(entity); - if state_entity.is_none() { - let state_entity = commands.spawn(TextBoxState::default()).id(); - widget_context.set_context_entity::<TextBoxState>(Some(entity), state_entity); - return false; - } - - let state_entity = state_entity.unwrap(); - - *styles = KStyle::default() - // Required styles - .with_style(KStyle { - render_command: RenderCommand::Layout.into(), - ..Default::default() - }) - // Apply any prop-given styles - .with_style(&*styles) - // If not set by props, apply these styles - .with_style(KStyle { - top: Units::Pixels(0.0).into(), - bottom: Units::Pixels(0.0).into(), - height: Units::Pixels(26.0).into(), - // cursor: CursorIcon::Text.into(), - ..Default::default() - }); - - let background_styles = KStyle { - background_color: StyleProp::Value(Color::rgba(0.176, 0.196, 0.215, 1.0)), - border_radius: Corner::all(5.0).into(), + if let Ok((mut styles, text_box, mut on_event, on_change)) = query.get_mut(entity) { + let state_entity = widget_context.use_state::<TextBoxState>( + &mut commands, + entity, + TextBoxState::default(), + ); + + *styles = KStyle::default() + // Required styles + .with_style(KStyle { + render_command: RenderCommand::Layout.into(), + ..Default::default() + }) + // Apply any prop-given styles + .with_style(&*styles) + // If not set by props, apply these styles + .with_style(KStyle { + top: Units::Pixels(0.0).into(), + bottom: Units::Pixels(0.0).into(), height: Units::Pixels(26.0).into(), - padding_left: Units::Pixels(5.0).into(), - padding_right: Units::Pixels(5.0).into(), + // cursor: CursorIcon::Text.into(), ..Default::default() - }; - - let current_value = text_box.value.clone(); - let cloned_on_change = on_change.clone(); - - *on_event = OnEvent::new( - move |In((event_dispatcher_context, mut event, _entity)): In<( - EventDispatcherContext, - Event, - Entity, - )>, - mut state_query: Query<&mut TextBoxState>| { - match event.event_type { - EventType::CharInput { c } => { - let mut current_value = current_value.clone(); - let cloned_on_change = cloned_on_change.clone(); - if let Ok(state) = state_query.get(state_entity) { - if !state.focused { - return (event_dispatcher_context, event); - } - } else { + }); + + let background_styles = KStyle { + render_command: StyleProp::Value(RenderCommand::Quad), + background_color: StyleProp::Value(Color::rgba(0.176, 0.196, 0.215, 1.0)), + border_radius: Corner::all(5.0).into(), + height: Units::Pixels(26.0).into(), + padding_left: Units::Pixels(5.0).into(), + padding_right: Units::Pixels(5.0).into(), + ..Default::default() + }; + + let current_value = text_box.value.clone(); + let cloned_on_change = on_change.clone(); + + *on_event = OnEvent::new( + move |In((event_dispatcher_context, _, mut event, _entity)): In<( + EventDispatcherContext, + WidgetState, + Event, + Entity, + )>, + mut state_query: Query<&mut TextBoxState>| { + match event.event_type { + EventType::CharInput { c } => { + let mut current_value = current_value.clone(); + let cloned_on_change = cloned_on_change.clone(); + if let Ok(state) = state_query.get(state_entity) { + if !state.focused { return (event_dispatcher_context, event); } - if is_backspace(c) { - if !current_value.is_empty() { - current_value.truncate(current_value.len() - 1); - } - } else if !c.is_control() { - current_value.push(c); - } - cloned_on_change.set_value(current_value); - event.add_system(cloned_on_change); + } else { + return (event_dispatcher_context, event); } - EventType::Focus => { - if let Ok(mut state) = state_query.get_mut(state_entity) { - state.focused = true; + if is_backspace(c) { + if !current_value.is_empty() { + current_value.truncate(current_value.len() - 1); } + } else if !c.is_control() { + current_value.push(c); } - EventType::Blur => { - if let Ok(mut state) = state_query.get_mut(state_entity) { - state.focused = false; - } + cloned_on_change.set_value(current_value); + event.add_system(cloned_on_change); + } + EventType::Focus => { + if let Ok(mut state) = state_query.get_mut(state_entity) { + state.focused = true; } - _ => {} } - (event_dispatcher_context, event) - }, - ); - - let parent_id = Some(entity); - rsx! { - <BackgroundBundle styles={background_styles}> - <ClipBundle styles={KStyle { - height: Units::Pixels(26.0).into(), - padding_left: StyleProp::Value(Units::Stretch(0.0)), - padding_right: StyleProp::Value(Units::Stretch(0.0)), - padding_bottom: StyleProp::Value(Units::Stretch(1.0)), - padding_top: StyleProp::Value(Units::Stretch(1.0)), - ..Default::default() - }}> - <TextWidgetBundle - text={TextProps { - content: text_box.value.clone(), - size: 14.0, - line_height: Some(18.0), - ..Default::default() - }} - // styles={text_styles} - /> - </ClipBundle> - </BackgroundBundle> - } - - return true; + EventType::Blur => { + if let Ok(mut state) = state_query.get_mut(state_entity) { + state.focused = false; + } + } + _ => {} + } + (event_dispatcher_context, event) + }, + ); + + let parent_id = Some(entity); + rsx! { + <BackgroundBundle styles={background_styles}> + <ClipBundle styles={KStyle { + height: Units::Pixels(26.0).into(), + padding_left: StyleProp::Value(Units::Stretch(0.0)), + padding_right: StyleProp::Value(Units::Stretch(0.0)), + padding_bottom: StyleProp::Value(Units::Stretch(1.0)), + padding_top: StyleProp::Value(Units::Stretch(1.0)), + ..Default::default() + }}> + <TextWidgetBundle + text={TextProps { + content: text_box.value.clone(), + size: 14.0, + line_height: Some(18.0), + ..Default::default() + }} + /> + </ClipBundle> + </BackgroundBundle> } } - false + true } /// Checks if the given character contains the "Backspace" sequence diff --git a/src/widgets/texture_atlas.rs b/src/widgets/texture_atlas.rs index 7396ca5..555cb62 100644 --- a/src/widgets/texture_atlas.rs +++ b/src/widgets/texture_atlas.rs @@ -4,7 +4,7 @@ use crate::{ context::{Mounted, WidgetName}, prelude::WidgetContext, styles::{KStyle, RenderCommand, StyleProp}, - widget::Widget, + widget::{Widget, WidgetProps}, }; /// A widget that renders a texture atlas @@ -22,7 +22,7 @@ use crate::{ /// | `on_layout` | ✅ | /// | `focusable` | ✅ | /// -#[derive(Component, Default, Debug)] +#[derive(Component, PartialEq, Clone, Default, Debug)] pub struct TextureAtlas { /// The handle to image pub handle: Handle<Image>, @@ -33,6 +33,7 @@ pub struct TextureAtlas { } impl Widget for TextureAtlas {} +impl WidgetProps for TextureAtlas {} #[derive(Bundle)] pub struct TextureAtlasBundle { diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 4218956..776c4ff 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -1,25 +1,29 @@ -use bevy::prelude::{ - Bundle, Changed, Color, Commands, Component, Entity, In, Or, Query, Vec2, With, +use bevy::{ + prelude::{Bundle, Color, Commands, Component, Entity, In, Query, Vec2}, + window::CursorIcon, }; +use kayak_ui_macros::rsx; use crate::{ children::KChildren, - context::{Mounted, WidgetName}, + context::WidgetName, event::{Event, EventType}, event_dispatcher::EventDispatcherContext, on_event::OnEvent, prelude::WidgetContext, - styles::{Corner, Edge, KStyle, PositionType, RenderCommand, StyleProp, Units}, - widget::Widget, + styles::{Corner, Edge, KCursorIcon, KStyle, PositionType, RenderCommand, StyleProp, Units}, + widget::{Widget, WidgetProps}, + widget_state::WidgetState, }; use super::{ background::BackgroundBundle, clip::ClipBundle, text::{TextProps, TextWidgetBundle}, + ElementBundle, }; -#[derive(Component, Debug, Default)] +#[derive(Component, PartialEq, Clone, Debug, Default)] pub struct KWindow { /// If true, allows the window to be draggable by its title bar pub draggable: bool, @@ -32,11 +36,10 @@ pub struct KWindow { pub is_dragging: bool, pub offset: Vec2, - pub title_bar_entity: Option<Entity>, - // pub children: Vec<Entity>, } impl Widget for KWindow {} +impl WidgetProps for KWindow {} #[derive(Bundle)] pub struct WindowBundle { @@ -60,59 +63,34 @@ impl Default for WindowBundle { pub fn window_update( In((widget_context, window_entity)): In<(WidgetContext, Entity)>, mut commands: Commands, - mut query: Query< - (&mut KStyle, &KChildren, &mut KWindow), - Or<( - Changed<KWindow>, - Changed<KStyle>, - Changed<KChildren>, - With<Mounted>, - )>, - >, + mut query: Query<(&KStyle, &KChildren, &KWindow)>, ) -> bool { - let mut has_changed = false; - if let Ok((mut window_style, children, mut window)) = query.get_mut(window_entity) { - *window_style = KStyle { - background_color: StyleProp::Value(Color::rgba(0.125, 0.125, 0.125, 1.0)), - border_color: StyleProp::Value(Color::rgba(0.0781, 0.0898, 0.101, 1.0)), - border: StyleProp::Value(Edge::all(4.0)), - border_radius: StyleProp::Value(Corner::all(5.0)), - render_command: StyleProp::Value(RenderCommand::Quad), - position_type: StyleProp::Value(PositionType::SelfDirected), - left: StyleProp::Value(Units::Pixels(window.position.x)), - top: StyleProp::Value(Units::Pixels(window.position.y)), - width: StyleProp::Value(Units::Pixels(window.size.x)), - height: StyleProp::Value(Units::Pixels(window.size.y)), - min_width: StyleProp::Value(Units::Pixels(window.size.x)), - min_height: StyleProp::Value(Units::Pixels(window.size.y)), - ..window_style.clone() - }; + if let Ok((window_style, window_children, window)) = query.get_mut(window_entity) { + let title = window.title.clone(); - if window.title_bar_entity.is_none() { - let title = window.title.clone(); - - let mut title_children = KChildren::new(); - // Spawn title children - let title_entity = commands - .spawn(TextWidgetBundle { - text: TextProps { - content: title.clone(), - size: 16.0, - line_height: Some(25.0), - ..Default::default() - }, - styles: KStyle { - height: StyleProp::Value(Units::Pixels(25.0)), - ..KStyle::default() - }, - ..Default::default() - }) - .id(); - title_children.add(title_entity); - - let title_background_entity = commands - .spawn(BackgroundBundle { - styles: KStyle { + let parent_id = Some(window_entity); + rsx! { + <ElementBundle + styles={KStyle { + background_color: StyleProp::Value(Color::rgba(0.125, 0.125, 0.125, 1.0)), + border_color: StyleProp::Value(Color::rgba(0.0781, 0.0898, 0.101, 1.0)), + border: StyleProp::Value(Edge::all(4.0)), + border_radius: StyleProp::Value(Corner::all(5.0)), + render_command: StyleProp::Value(RenderCommand::Quad), + position_type: StyleProp::Value(PositionType::SelfDirected), + left: StyleProp::Value(Units::Pixels(window.position.x)), + top: StyleProp::Value(Units::Pixels(window.position.y)), + width: StyleProp::Value(Units::Pixels(window.size.x)), + height: StyleProp::Value(Units::Pixels(window.size.y)), + min_width: StyleProp::Value(Units::Pixels(window.size.x)), + min_height: StyleProp::Value(Units::Pixels(window.size.y)), + ..window_style.clone() + }} + > + <BackgroundBundle + id={"title_bar_entity"} + styles={KStyle { + cursor: StyleProp::Value(KCursorIcon(CursorIcon::Hand)), render_command: StyleProp::Value(RenderCommand::Quad), background_color: StyleProp::Value(Color::rgba(0.0781, 0.0898, 0.101, 1.0)), border_radius: StyleProp::Value(Corner::all(5.0)), @@ -124,69 +102,73 @@ pub fn window_update( bottom: StyleProp::Value(Units::Pixels(0.0)), padding_left: StyleProp::Value(Units::Pixels(5.0)), ..KStyle::default() - }, - children: title_children, - ..BackgroundBundle::default() - }) - .id(); - window.title_bar_entity = Some(title_background_entity); - - if window.draggable { - commands - .entity(window.title_bar_entity.unwrap()) - .insert(OnEvent::new( - move |In((mut event_dispatcher_context, event, entity)): In<( - EventDispatcherContext, - Event, - Entity, - )>, - mut query: Query<&mut KWindow>| { - if let Ok(mut window) = query.get_mut(window_entity) { - match event.event_type { - EventType::MouseDown(data) => { - event_dispatcher_context.capture_cursor(entity); - window.is_dragging = true; - window.offset = Vec2::new( - window.position.x - data.position.0, - window.position.y - data.position.1, - ); - window.title_bar_entity = None; - } - EventType::MouseUp(..) => { - event_dispatcher_context.release_cursor(entity); - window.is_dragging = false; - window.title_bar_entity = None; - } - EventType::Hover(data) => { - if window.is_dragging { - window.position = Vec2::new( - window.offset.x + data.position.0, - window.offset.y + data.position.1, - ); - window.title_bar_entity = None; + }} + > + <TextWidgetBundle + text={TextProps { + content: title.clone(), + size: 14.0, + line_height: Some(25.0), + ..Default::default() + }} + styles={KStyle { + height: StyleProp::Value(Units::Pixels(25.0)), + ..KStyle::default() + }} + /> + </BackgroundBundle> + { + if window.draggable { + commands + .entity(title_bar_entity) + .insert(OnEvent::new( + move |In((mut event_dispatcher_context, _, event, entity)): In<( + EventDispatcherContext, + WidgetState, + Event, + Entity, + )>, + mut query: Query<&mut KWindow>| { + if let Ok(mut window) = query.get_mut(window_entity) { + match event.event_type { + EventType::MouseDown(data) => { + event_dispatcher_context.capture_cursor(entity); + window.is_dragging = true; + window.offset = Vec2::new( + window.position.x - data.position.0, + window.position.y - data.position.1, + ); + } + EventType::MouseUp(..) => { + event_dispatcher_context.release_cursor(entity); + window.is_dragging = false; + } + EventType::Hover(data) => { + if window.is_dragging { + window.position = Vec2::new( + window.offset.x + data.position.0, + window.offset.y + data.position.1, + ); + } + } + _ => {} } } - _ => {} - } - } - (event_dispatcher_context, event) - }, - )); - } - widget_context.add_widget(Some(window_entity), window.title_bar_entity.unwrap()); - - let mut clip_bundle = ClipBundle { - children: children.clone(), - ..ClipBundle::default() - }; - clip_bundle.styles.padding = StyleProp::Value(Edge::all(Units::Pixels(10.0))); - - let clip_entity = commands.spawn(clip_bundle).id(); - widget_context.add_widget(Some(window_entity), clip_entity); - // let children = widget_context.get_children(window_entity); - has_changed = true; + (event_dispatcher_context, event) + }, + )); + } + } + <ClipBundle + styles={KStyle { + padding: StyleProp::Value(Edge::all(Units::Pixels(10.0))), + ..Default::default() + }} + children={window_children.clone()} + /> + </ElementBundle> } } - has_changed + true } -- GitLab