From 60d6f92eb88654b5be895d972ea14f83ab77ced2 Mon Sep 17 00:00:00 2001 From: StarArawn <toasterthegamer@gmail.com> Date: Fri, 26 Nov 2021 10:19:09 -0500 Subject: [PATCH] A bunch of new changes: - Added counter example. - Added input events and widget event handling. - Fixed multiple bugs. - Turned off component diffing until I can figure out a good way of diffing props passed directly to children. --- bevy_kayak_ui/src/bevy_context.rs | 2 + bevy_kayak_ui/src/lib.rs | 43 +++++- .../src/render/unified/quad/extract.rs | 2 +- examples/bevy.rs | 7 +- examples/counter.rs | 86 +++++++++++ kayak_components/src/button.rs | 31 ++++ kayak_components/src/lib.rs | 2 + kayak_components/src/window.rs | 23 +-- kayak_core/examples/test3.rs | 1 + kayak_core/src/context.rs | 69 ++++++++- kayak_core/src/event.rs | 24 +++ kayak_core/src/fragment.rs | 12 +- kayak_core/src/input_event.rs | 4 + kayak_core/src/layout_cache.rs | 7 + kayak_core/src/lib.rs | 45 +++++- kayak_core/src/node.rs | 32 ++-- kayak_core/src/render_command.rs | 1 + kayak_core/src/render_primitive.rs | 16 +- kayak_core/src/styles.rs | 2 +- kayak_core/src/widget.rs | 4 +- kayak_core/src/widget_manager.rs | 143 +++++++++++++++--- kayak_render_macros/examples/main.rs | 10 ++ kayak_render_macros/src/children.rs | 6 +- kayak_render_macros/src/function_component.rs | 21 ++- kayak_render_macros/src/widget_attributes.rs | 24 ++- 25 files changed, 528 insertions(+), 89 deletions(-) create mode 100644 examples/counter.rs create mode 100644 kayak_components/src/button.rs create mode 100644 kayak_core/src/event.rs create mode 100644 kayak_core/src/input_event.rs diff --git a/bevy_kayak_ui/src/bevy_context.rs b/bevy_kayak_ui/src/bevy_context.rs index 9322b73..0c2c974 100644 --- a/bevy_kayak_ui/src/bevy_context.rs +++ b/bevy_kayak_ui/src/bevy_context.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, RwLock}; use kayak_core::{ context::KayakContext, + render_command::RenderCommand, styles::{Style, StyleProp, Units}, }; @@ -12,6 +13,7 @@ pub struct BevyContext { impl BevyContext { pub fn new<F: Fn(&mut Style, &mut KayakContext)>(width: f32, height: f32, f: F) -> Self { let mut app_styles = Style { + render_command: StyleProp::Value(RenderCommand::Window), width: StyleProp::Value(Units::Pixels(width)), height: StyleProp::Value(Units::Pixels(height)), ..Style::default() diff --git a/bevy_kayak_ui/src/lib.rs b/bevy_kayak_ui/src/lib.rs index c5f8e11..fee1bbc 100644 --- a/bevy_kayak_ui/src/lib.rs +++ b/bevy_kayak_ui/src/lib.rs @@ -1,4 +1,10 @@ -use bevy::{prelude::{Plugin, ResMut}, render2::color::Color}; +use bevy::{ + input::{mouse::MouseButtonInput, ElementState}, + math::Vec2, + prelude::{EventReader, MouseButton, Plugin, Res, ResMut}, + render2::color::Color, + window::{CursorMoved, Windows}, +}; mod bevy_context; mod camera; @@ -6,6 +12,7 @@ mod render; pub use bevy_context::BevyContext; pub use camera::*; +use kayak_core::InputEvent; #[derive(Default)] pub struct BevyKayakUIPlugin; @@ -22,8 +29,40 @@ pub(crate) fn to_bevy_color(color: &kayak_core::color::Color) -> Color { Color::rgba(color.r, color.g, color.b, color.a) } -pub fn update(bevy_context: ResMut<BevyContext>) { +pub fn update( + bevy_context: ResMut<BevyContext>, + mut cursor_moved_events: EventReader<CursorMoved>, + mut mouse_button_input_events: EventReader<MouseButtonInput>, + windows: Res<Windows>, +) { + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + if let Ok(mut context) = bevy_context.kayak_context.write() { context.render(); + + let mut input_events = Vec::new(); + for event in cursor_moved_events.iter() { + input_events.push(InputEvent::MouseMoved(( + event.position.x as f32, + window_size.y - event.position.y as f32, + ))); + } + + for event in mouse_button_input_events.iter() { + match event.button { + MouseButton::Left => { + if event.state == ElementState::Pressed { + input_events.push(InputEvent::MouseLeftClick); + } + } + _ => {} + } + } + + context.process_events(input_events); } } diff --git a/bevy_kayak_ui/src/render/unified/quad/extract.rs b/bevy_kayak_ui/src/render/unified/quad/extract.rs index 523ed83..fe74d22 100644 --- a/bevy_kayak_ui/src/render/unified/quad/extract.rs +++ b/bevy_kayak_ui/src/render/unified/quad/extract.rs @@ -36,7 +36,7 @@ pub fn extract_quads(mut commands: Commands, context: Res<BevyContext>) { extracted_quad: ExtractedQuad { rect: Rect { min: Vec2::new(layout.posx, layout.posy), - max: Vec2::new(layout.width, layout.height), + max: Vec2::new(layout.posx + layout.width, layout.posy + layout.height), }, color: to_bevy_color(background_color), vertex_index: 0, diff --git a/examples/bevy.rs b/examples/bevy.rs index dedfde9..4895e2a 100644 --- a/examples/bevy.rs +++ b/examples/bevy.rs @@ -42,7 +42,12 @@ fn startup(mut commands: Commands, windows: Res<Windows>) { let parent_id: Option<Index> = None; rsx! { <App styles={Some(styles.clone())}> - <TestState>{}</TestState> + <Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"Window 1".to_string()}> + {} + </Window> + <Window position={(800.0, 50.0)} size={(200.0, 200.0)} title={"Window 2".to_string()}> + {} + </Window> </App> } }); diff --git a/examples/counter.rs b/examples/counter.rs new file mode 100644 index 0000000..457f5c9 --- /dev/null +++ b/examples/counter.rs @@ -0,0 +1,86 @@ +use bevy::{ + math::Vec2, + prelude::{App as BevyApp, Commands, Res}, + window::{WindowDescriptor, Windows}, + PipelinedDefaultPlugins, +}; +use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, UICameraBundle}; +use kayak_components::{Button, Text, Window}; +use kayak_core::{ + styles::{Style, StyleProp, Units}, + EventType, Index, OnEvent, +}; +use kayak_ui::components::App; +use kayak_ui::core::{rsx, widget}; + +#[widget] +fn Counter() { + let count = { + let x = context.create_state(0i32).unwrap(); + *x + }; + let text_styles = Style { + bottom: StyleProp::Value(Units::Stretch(1.0)), + left: StyleProp::Value(Units::Stretch(1.0)), + right: StyleProp::Value(Units::Stretch(1.0)), + top: StyleProp::Value(Units::Stretch(1.0)), + height: StyleProp::Value(Units::Pixels(26.0)), + ..Default::default() + }; + + let id = self.get_id(); + let on_event = OnEvent::new(move |context, event| match event.event_type { + EventType::Click => { + context.set_current_id(id); + context.set_state(count + 1); + } + _ => {} + }); + + rsx! { + <> + <Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"Counter Example".to_string()}> + <Text size={16.0} content={format!("Current Count: {}", count).to_string()}>{}</Text> + <Button on_event={Some(on_event)}> + <Text styles={Some(text_styles)} size={24.0} content={"Count!".to_string()}>{}</Text> + </Button> + </Window> + </> + } +} + +fn startup(mut commands: Commands, windows: Res<Windows>) { + commands.spawn_bundle(UICameraBundle::new()); + + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + panic!("Couldn't find primary window!"); + }; + + let context = BevyContext::new(window_size.x, window_size.y, |styles, context| { + // Hack to trick the proc macro for right now.. + let parent_id: Option<Index> = None; + rsx! { + <App styles={Some(styles.clone())}> + <Counter /> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} diff --git a/kayak_components/src/button.rs b/kayak_components/src/button.rs new file mode 100644 index 0000000..9ca7e9b --- /dev/null +++ b/kayak_components/src/button.rs @@ -0,0 +1,31 @@ +use kayak_core::{ + color::Color, + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp, Units}, + widget, Children, Fragment, +}; + +#[widget] +pub fn Button(children: Children, styles: Option<Style>) { + let base_styles = styles.clone().unwrap_or_default(); + *styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::Quad), + height: if base_styles.height == StyleProp::Default { + StyleProp::Value(Units::Pixels(45.0)) + } else { + base_styles.height + }, + background_color: if matches!(base_styles.background_color, StyleProp::Default) { + StyleProp::Value(Color::new(0.0781, 0.0898, 0.101, 1.0)) + } else { + base_styles.background_color + }, + ..base_styles + }); + rsx! { + <Fragment> + {children} + </Fragment> + } +} diff --git a/kayak_components/src/lib.rs b/kayak_components/src/lib.rs index 9e72b31..a9b9976 100644 --- a/kayak_components/src/lib.rs +++ b/kayak_components/src/lib.rs @@ -1,11 +1,13 @@ mod app; mod background; +mod button; mod clip; mod text; mod window; pub use app::*; pub use background::*; +pub use button::*; pub use clip::*; pub use text::*; pub use window::*; diff --git a/kayak_components/src/window.rs b/kayak_components/src/window.rs index 3abf6e4..0671ff4 100644 --- a/kayak_components/src/window.rs +++ b/kayak_components/src/window.rs @@ -16,22 +16,6 @@ pub fn Window( size: (f32, f32), title: String, ) { - // let mut changed_styles = styles.clone().unwrap_or_default(); - // changed_styles.render_command = RenderCommand::Quad; - // changed_styles.position_type = Some(PositionType::Absolute); - // changed_styles.background = Some(Color::new(0.0588, 0.0588, 0.588, 1.0)); - // changed_styles.position = Some(Rect { - // start: Dimension::Points(position.x), - // end: Dimension::Points(position.x + size.width), - // top: Dimension::Points(position.y), - // bottom: Dimension::Points(position.y + size.height), - // }); - // changed_styles.size = Some(Size { - // width: Dimension::Points(size.width), - // height: Dimension::Points(size.height), - // }); - // styles = Some(changed_styles); - *styles = Some(Style { background_color: StyleProp::Value(Color::new(0.125, 0.125, 0.125, 1.0)), render_command: StyleProp::Value(RenderCommand::Quad), @@ -49,12 +33,7 @@ pub fn Window( ..Style::default() }; - let title_text_styles = Style { - position_type: StyleProp::Value(PositionType::SelfDirected), - top: StyleProp::Value(Units::Pixels(-22.0)), - left: StyleProp::Value(Units::Pixels(5.0)), - ..Style::default() - }; + let title_text_styles = Style { ..Style::default() }; let title = title.clone(); rsx! { diff --git a/kayak_core/examples/test3.rs b/kayak_core/examples/test3.rs index 34079bc..12c5683 100644 --- a/kayak_core/examples/test3.rs +++ b/kayak_core/examples/test3.rs @@ -44,6 +44,7 @@ fn main() { height: StyleProp::Value(Units::Pixels(720.0)), ..Style::default() }), + on_event: None, }; let (_, widget_id) = context.widget_manager.create_widget(0, my_widget, None); diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs index 74cd09b..b18aa0e 100644 --- a/kayak_core/src/context.rs +++ b/kayak_core/src/context.rs @@ -2,12 +2,13 @@ use std::collections::HashMap; use resources::Ref; -use crate::widget_manager::WidgetManager; +use crate::{node::NodeIndex, widget_manager::WidgetManager, Event, EventType, Index, InputEvent}; pub struct KayakContext { component_states: HashMap<crate::Index, resources::Resources>, current_id: crate::Index, pub widget_manager: WidgetManager, + last_mouse_position: (f32, f32), } impl std::fmt::Debug for KayakContext { @@ -24,6 +25,7 @@ impl KayakContext { component_states: HashMap::new(), current_id: crate::Index::default(), widget_manager: WidgetManager::new(), + last_mouse_position: (0.0, 0.0), } } @@ -72,10 +74,7 @@ impl KayakContext { ); } } else { - panic!( - "No state created for component with id: {:?}!", - self.current_id - ); + // Do nothing.. } } @@ -99,4 +98,64 @@ impl KayakContext { self.widget_manager.render(); self.widget_manager.calculate_layout(); } + + pub fn process_events(&mut self, input_events: Vec<InputEvent>) { + let mut events_stream = Vec::new(); + for (index, _) in self.widget_manager.nodes.iter() { + if let Some(layout) = self.widget_manager.layout_cache.rect.get(&NodeIndex(index)) { + for input_event in input_events.iter() { + match input_event { + InputEvent::MouseMoved(point) => { + // Hover event. + if layout.contains(point) { + let hover_event = Event { + target: index, + event_type: EventType::Hover, + ..Event::default() + }; + events_stream.push(hover_event); + } + self.last_mouse_position = *point; + } + InputEvent::MouseLeftClick => { + if layout.contains(&self.last_mouse_position) { + let click_event = Event { + target: index, + event_type: EventType::Click, + ..Event::default() + }; + events_stream.push(click_event); + } + } + } + } + } + } + + // Propagate Events + for event in events_stream.iter_mut() { + let mut parents: Vec<Index> = Vec::new(); + self.get_all_parents(event.target, &mut parents); + + // First call target + let mut target_widget = self.widget_manager.take(event.target); + target_widget.on_event(self, event); + self.widget_manager.repossess(target_widget); + + for parent in parents { + if event.should_propagate { + let mut parent_widget = self.widget_manager.take(parent); + parent_widget.on_event(self, event); + self.widget_manager.repossess(parent_widget); + } + } + } + } + + fn get_all_parents(&self, current: Index, parents: &mut Vec<Index>) { + if let Some(parent) = self.widget_manager.tree.parents.get(¤t) { + parents.push(*parent); + self.get_all_parents(*parent, parents); + } + } } diff --git a/kayak_core/src/event.rs b/kayak_core/src/event.rs new file mode 100644 index 0000000..92d5495 --- /dev/null +++ b/kayak_core/src/event.rs @@ -0,0 +1,24 @@ +use crate::Index; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Event { + pub target: Index, + pub event_type: EventType, + pub(crate) should_propagate: bool, +} + +impl Default for Event { + fn default() -> Self { + Self { + target: Default::default(), + event_type: EventType::Click, + should_propagate: true, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum EventType { + Click, + Hover, +} diff --git a/kayak_core/src/fragment.rs b/kayak_core/src/fragment.rs index 837a5e6..744e444 100644 --- a/kayak_core/src/fragment.rs +++ b/kayak_core/src/fragment.rs @@ -6,9 +6,11 @@ use crate::{context::KayakContext, styles::Style, Index, Widget}; #[derivative(Debug, PartialEq)] pub struct Fragment { pub id: Index, + pub styles: Option<Style>, #[derivative(Debug = "ignore", PartialEq = "ignore")] pub children: crate::Children, - pub styles: Option<Style>, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<crate::OnEvent>, } impl Widget for Fragment { @@ -24,6 +26,14 @@ impl Widget for Fragment { self.styles.clone() } + fn get_name(&self) -> String { + String::from("Fragment") + } + + fn on_event(&mut self, _context: &mut KayakContext, _event: &mut crate::Event) { + // Do nothing. + } + fn render(&mut self, context: &mut KayakContext) { dbg!("Rendering fragment children!"); if let Some(children) = self.children.as_ref() { diff --git a/kayak_core/src/input_event.rs b/kayak_core/src/input_event.rs new file mode 100644 index 0000000..18856f7 --- /dev/null +++ b/kayak_core/src/input_event.rs @@ -0,0 +1,4 @@ +pub enum InputEvent { + MouseMoved((f32, f32)), + MouseLeftClick, +} diff --git a/kayak_core/src/layout_cache.rs b/kayak_core/src/layout_cache.rs index 59f251e..c843be7 100644 --- a/kayak_core/src/layout_cache.rs +++ b/kayak_core/src/layout_cache.rs @@ -13,6 +13,13 @@ pub struct Rect { pub z_index: f32, } +impl Rect { + pub fn contains(&self, point: &(f32, f32)) -> bool { + (point.0 >= self.posx && point.0 <= self.posx + self.width) + && (point.1 >= self.posy && point.1 <= self.posy + self.height) + } +} + #[derive(Debug, Default, Clone, Copy)] pub struct Space { pub left: f32, diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs index 73ad0d8..47009cc 100644 --- a/kayak_core/src/lib.rs +++ b/kayak_core/src/lib.rs @@ -1,6 +1,9 @@ pub mod color; pub mod context; +pub mod event; pub mod fragment; +pub(crate) mod generational_arena; +mod input_event; pub mod layout_cache; pub mod node; pub mod render_command; @@ -10,19 +13,47 @@ pub mod tree; pub mod widget; pub mod widget_manager; -pub(crate) mod generational_arena; +use std::sync::{Arc, RwLock}; +pub use event::*; +pub use fragment::Fragment; pub use generational_arena::{Arena, Index}; - +pub use input_event::*; +pub use kayak_render_macros::{render, rsx, widget}; pub use widget::Widget; -pub use kayak_render_macros::{render, rsx, widget}; +pub type Children = + Option<Arc<dyn Fn(Option<crate::Index>, &mut crate::context::KayakContext) + Send + Sync>>; -pub use fragment::Fragment; +#[derive(Clone)] +pub struct OnEvent( + pub Arc< + RwLock<dyn FnMut(&mut crate::context::KayakContext, &mut Event) + Send + Sync + 'static>, + >, +); + +impl OnEvent { + pub fn new<F: FnMut(&mut crate::context::KayakContext, &mut Event) + Send + Sync + 'static>( + f: F, + ) -> OnEvent { + OnEvent(Arc::new(RwLock::new(f))) + } +} + +// impl std::ops::Deref for OnEvent { +// type Target = +// Arc<RwLock<dyn FnMut(&mut crate::context::KayakContext, &mut Event) + Send + Sync>>; + +// fn deref(&self) -> &Self::Target { +// &self.0 +// } +// } -pub type Children = Option< - std::sync::Arc<dyn Fn(Option<crate::Index>, &mut crate::context::KayakContext) + Send + Sync>, ->; +// impl std::ops::DerefMut for OnEvent { +// fn deref_mut(&mut self) -> &mut Self::Target { +// &mut self.0 +// } +// } pub mod derivative { pub use derivative::*; diff --git a/kayak_core/src/node.rs b/kayak_core/src/node.rs index 704cc01..47d79ca 100644 --- a/kayak_core/src/node.rs +++ b/kayak_core/src/node.rs @@ -73,11 +73,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.layout_type { StyleProp::Default => Some(morphorm::LayoutType::default()), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::LayoutType::default()), }; } } - return None; + return Some(morphorm::LayoutType::default()); } fn position_type(&self, store: &'_ Self::Data) -> Option<morphorm::PositionType> { @@ -86,11 +86,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.position_type { StyleProp::Default => Some(morphorm::PositionType::default()), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::PositionType::default()), }; } } - return None; + return Some(morphorm::PositionType::default()); } fn width(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { @@ -99,11 +99,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.width { StyleProp::Default => Some(morphorm::Units::Stretch(1.0)), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::Units::Stretch(1.0)), }; } } - return None; + return Some(morphorm::Units::Stretch(1.0)); } fn height(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { @@ -112,11 +112,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.height { StyleProp::Default => Some(morphorm::Units::Stretch(1.0)), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::Units::Stretch(1.0)), }; } } - return None; + return Some(morphorm::Units::Stretch(1.0)); } fn min_width(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { @@ -141,11 +141,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.left { StyleProp::Default => Some(morphorm::Units::Auto), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::Units::Auto), }; } } - return None; + return Some(morphorm::Units::Auto); } fn right(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { @@ -154,11 +154,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.right { StyleProp::Default => Some(morphorm::Units::Auto), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::Units::Auto), }; } } - return None; + return Some(morphorm::Units::Auto); } fn top(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { @@ -167,11 +167,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.top { StyleProp::Default => Some(morphorm::Units::Auto), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::Units::Auto), }; } } - return None; + return Some(morphorm::Units::Auto); } fn bottom(&self, store: &'_ Self::Data) -> Option<morphorm::Units> { @@ -180,11 +180,11 @@ impl<'a> morphorm::Node<'a> for NodeIndex { return match node.styles.bottom { StyleProp::Default => Some(morphorm::Units::Auto), StyleProp::Value(prop) => Some(prop), - _ => None, + _ => Some(morphorm::Units::Auto), }; } } - return None; + return Some(morphorm::Units::Auto); } fn min_left(&self, _store: &'_ Self::Data) -> Option<morphorm::Units> { diff --git a/kayak_core/src/render_command.rs b/kayak_core/src/render_command.rs index eb75d0b..31f034e 100644 --- a/kayak_core/src/render_command.rs +++ b/kayak_core/src/render_command.rs @@ -1,6 +1,7 @@ #[derive(Debug, Clone, PartialEq)] pub enum RenderCommand { Empty, + Window, Clip, Quad, Text { diff --git a/kayak_core/src/render_primitive.rs b/kayak_core/src/render_primitive.rs index 1c54054..6fa65a7 100644 --- a/kayak_core/src/render_primitive.rs +++ b/kayak_core/src/render_primitive.rs @@ -1,4 +1,9 @@ -use crate::{color::Color, layout_cache::Rect, render_command::RenderCommand, styles::Style}; +use crate::{ + color::Color, + layout_cache::Rect, + render_command::RenderCommand, + styles::{Style, StyleProp}, +}; #[derive(Debug, Clone, PartialEq)] pub enum RenderPrimitive { @@ -34,13 +39,20 @@ impl From<&Style> for RenderPrimitive { fn from(style: &Style) -> Self { let render_command = style.render_command.resolve(); + let background_color = if matches!(style.background_color, StyleProp::Default) { + Color::TRANSPARENT + } else { + style.background_color.resolve() + }; + match render_command { + RenderCommand::Window => Self::Empty, RenderCommand::Empty => Self::Empty, RenderCommand::Clip => Self::Clip { layout: Rect::default(), }, RenderCommand::Quad => Self::Quad { - background_color: style.background_color.resolve(), + background_color: background_color, layout: Rect::default(), }, RenderCommand::Text { diff --git a/kayak_core/src/styles.rs b/kayak_core/src/styles.rs index d75533a..7302522 100644 --- a/kayak_core/src/styles.rs +++ b/kayak_core/src/styles.rs @@ -49,7 +49,7 @@ pub struct Style { impl Default for Style { fn default() -> Self { Self { - background_color: StyleProp::Value(Color::TRANSPARENT), + background_color: StyleProp::Default, render_command: StyleProp::Value(RenderCommand::Empty), bottom: StyleProp::Default, color: StyleProp::Inherit, diff --git a/kayak_core/src/widget.rs b/kayak_core/src/widget.rs index 3e63d87..265e746 100644 --- a/kayak_core/src/widget.rs +++ b/kayak_core/src/widget.rs @@ -1,11 +1,13 @@ use as_any::AsAny; -use crate::{context::KayakContext, styles::Style, Index}; +use crate::{context::KayakContext, styles::Style, Event, Index}; pub trait Widget: std::fmt::Debug + AsAny + Send + Sync { fn get_id(&self) -> Index; fn set_id(&mut self, id: Index); fn get_styles(&self) -> Option<Style>; + fn get_name(&self) -> String; + fn on_event(&mut self, context: &mut KayakContext, event: &mut Event); fn render(&mut self, context: &mut KayakContext); } diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs index f4e3b70..f6792ba 100644 --- a/kayak_core/src/widget_manager.rs +++ b/kayak_core/src/widget_manager.rs @@ -1,6 +1,7 @@ use crate::{ layout_cache::LayoutCache, node::{Node, NodeBuilder, NodeIndex}, + render_command::RenderCommand, render_primitive::RenderPrimitive, styles::Style, tree::Tree, @@ -10,11 +11,12 @@ use as_any::Downcast; #[derive(Debug)] pub struct WidgetManager { - current_widgets: Arena<Option<Box<dyn Widget>>>, + pub(crate) current_widgets: Arena<Option<Box<dyn Widget>>>, pub(crate) dirty_render_nodes: Vec<Index>, pub(crate) dirty_nodes: Vec<Index>, pub(crate) nodes: Arena<Option<Node>>, pub tree: Tree, + pub node_tree: Tree, pub layout_cache: LayoutCache, current_z: f32, } @@ -27,6 +29,7 @@ impl WidgetManager { dirty_nodes: Vec::new(), nodes: Arena::new(), tree: Tree::default(), + node_tree: Tree::default(), layout_cache: LayoutCache::default(), current_z: 0.0, } @@ -63,23 +66,23 @@ impl WidgetManager { self.dirty_nodes.remove(index); } - if &widget - != self.current_widgets[*widget_id] - .as_ref() - .unwrap() - .downcast_ref::<T>() - .unwrap() - { - let boxed_widget: Box<dyn Widget> = Box::new(widget); - *self.current_widgets[*widget_id].as_mut().unwrap() = boxed_widget; - dbg!("Widget changed!"); - // Tell renderer that the nodes changed. - self.dirty_render_nodes.push(*widget_id); - return (true, *widget_id); - } else { - dbg!("No widget changes!"); - return (false, *widget_id); - } + // if &widget + // != self.current_widgets[*widget_id] + // .as_ref() + // .unwrap() + // .downcast_ref::<T>() + // .unwrap() + // { + let boxed_widget: Box<dyn Widget> = Box::new(widget); + *self.current_widgets[*widget_id].as_mut().unwrap() = boxed_widget; + dbg!("Widget changed!"); + // Tell renderer that the nodes changed. + self.dirty_render_nodes.push(*widget_id); + return (true, *widget_id); + // } else { + // dbg!("No widget changes!"); + // return (false, *widget_id); + // } } } } @@ -169,7 +172,6 @@ impl WidgetManager { z } }; - dbg!(current_z); let mut styles = dirty_widget.get_styles(); if styles.is_some() { @@ -181,19 +183,59 @@ impl WidgetManager { .get(&dirty_node_index) .cloned() .unwrap_or(vec![]); + let styles = styles.unwrap_or(default_styles.clone()); + if matches!(styles.render_command.resolve(), RenderCommand::Empty) { + continue; + } let mut node = NodeBuilder::empty() .with_id(dirty_node_index) - .with_styles(styles.unwrap_or(default_styles.clone())) + .with_styles(styles) .with_children(children) .build(); node.z = current_z; self.nodes[dirty_node_index] = Some(node); } + + self.node_tree = self.build_nodes_tree(); + + // let mut last_parent = Index::default(); + // let mut space_count_lookup = HashMap::<Index, u32>::new(); + // let mut space_count: u32 = 0; + // for node in self.node_tree.down_iter() { + // space_count_lookup.insert(node.0, space_count); + // let child_widget = &self.current_widgets[node.0].as_ref().unwrap(); + // let (child_id, _) = node.0.into_raw_parts(); + // println!( + // "{:indent$}Widget: {} {}", + // "", + // child_widget.get_name(), + // child_id, + // indent = space_count as usize, + // ); + // if let Some(parent_id) = self.node_tree.parents.get(&node.0) { + // let parent_widget = &self.current_widgets[*parent_id].as_ref().unwrap(); + // println!( + // "{:indent$}parent: {} {}", + // "", + // parent_widget.get_name(), + // parent_id.into_raw_parts().0, + // indent = space_count as usize, + // ); + // if last_parent != *parent_id { + // if let Some(stored_space_count) = space_count_lookup.get(parent_id) { + // space_count = *stored_space_count; + // } + // } + // last_parent = *parent_id; + // } + // space_count += 2; + // } + // panic!(); } pub fn calculate_layout(&mut self) { - morphorm::layout(&mut self.layout_cache, &self.tree, &self.nodes); + morphorm::layout(&mut self.layout_cache, &self.node_tree, &self.nodes); } pub fn build_render_primitives(&self) -> Vec<RenderPrimitive> { @@ -213,4 +255,63 @@ impl WidgetManager { render_primitives } + + fn build_nodes_tree(&self) -> Tree { + let mut tree = Tree::default(); + let (root_node_id, _) = self.current_widgets.iter().next().unwrap(); + tree.root_node = root_node_id; + tree.children + .insert(tree.root_node, self.get_valid_node_children(tree.root_node)); + for (widget_id, widget) in self.current_widgets.iter().skip(1) { + let widget_styles = widget.as_ref().unwrap().get_styles(); + if let Some(widget_styles) = widget_styles { + // Only add widgets who have renderable nodes. + if widget_styles.render_command.resolve() != RenderCommand::Empty { + let valid_children = self.get_valid_node_children(widget_id); + tree.children.insert(widget_id, valid_children); + let valid_parent = self.get_valid_parent(widget_id); + if let Some(valid_parent) = valid_parent { + tree.parents.insert(widget_id, valid_parent); + } + } + } + } + tree + } + + fn get_valid_node_children(&self, node_id: Index) -> Vec<Index> { + let mut children = Vec::new(); + if let Some(node_children) = self.tree.children.get(&node_id) { + for child_id in node_children { + if let Some(child_widget) = &self.current_widgets[*child_id] { + if let Some(child_styles) = child_widget.get_styles() { + if child_styles.render_command.resolve() != RenderCommand::Empty { + children.push(*child_id); + } else { + children.extend(self.get_valid_node_children(*child_id)); + } + } else { + children.extend(self.get_valid_node_children(*child_id)); + } + } + } + } + + children + } + + fn get_valid_parent(&self, node_id: Index) -> Option<Index> { + if let Some(parent_id) = self.tree.parents.get(&node_id) { + if let Some(parent_widget) = &self.current_widgets[*parent_id] { + if let Some(parent_styles) = parent_widget.get_styles() { + if parent_styles.render_command.resolve() != RenderCommand::Empty { + return Some(*parent_id); + } + } + + return self.get_valid_parent(*parent_id); + } + } + None + } } diff --git a/kayak_render_macros/examples/main.rs b/kayak_render_macros/examples/main.rs index 75d7ccc..5fcb608 100644 --- a/kayak_render_macros/examples/main.rs +++ b/kayak_render_macros/examples/main.rs @@ -11,6 +11,8 @@ struct Test { foo: u32, #[derivative(Debug = "ignore", PartialEq = "ignore")] children: Children, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<kayak_core::OnEvent>, } impl Widget for Test { @@ -26,6 +28,14 @@ impl Widget for Test { todo!() } + fn get_name(&self) -> String { + todo!() + } + + fn on_event(&mut self, _context: &mut KayakContext, _event: &mut kayak_core::Event) { + todo!() + } + fn render(&mut self, _context: &mut KayakContext) { todo!() } diff --git a/kayak_render_macros/src/children.rs b/kayak_render_macros/src/children.rs index db5093c..f948e67 100644 --- a/kayak_render_macros/src/children.rs +++ b/kayak_render_macros/src/children.rs @@ -16,10 +16,6 @@ impl Children { Children { nodes } } - pub fn len(&self) -> usize { - self.nodes.len() - } - pub fn get_clonable_attributes(&self, index: usize) -> Vec<proc_macro2::TokenStream> { let mut tokens = Vec::new(); @@ -63,7 +59,7 @@ impl Children { .collect(); match children_quotes.len() { - 0 => quote! { Option::<()>::None }, + 0 => quote! { None }, 1 => { let child = if children_quotes[0].to_string() == "{ }" { quote! { None } diff --git a/kayak_render_macros/src/function_component.rs b/kayak_render_macros/src/function_component.rs index 55c82f3..2ad9e3a 100644 --- a/kayak_render_macros/src/function_component.rs +++ b/kayak_render_macros/src/function_component.rs @@ -85,6 +85,13 @@ pub fn create_function_component(f: syn::ItemFn) -> TokenStream { pub children: kayak_core::Children }, ), + ( + vec!["on_event : Option<kayak_core::OnEvent>"], + quote! { + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<kayak_core::OnEvent> + }, + ), ]; for (names, token) in missing_struct_inputs { @@ -144,8 +151,20 @@ pub fn create_function_component(f: syn::ItemFn) -> TokenStream { self.styles.clone() } + fn get_name(&self) -> String { + String::from(stringify!(#struct_name)) + } + + fn on_event(&mut self, context: &mut ::kayak_core::context::KayakContext, event: &mut ::kayak_core::Event) { + if let Some(on_event) = self.on_event.as_ref() { + if let Ok(mut on_event) = on_event.0.write() { + on_event(context, event); + } + } + } + fn render(&mut self, context: &mut ::kayak_core::context::KayakContext) { - dbg!(stringify!(Rendering widget: #struct_name)); + // dbg!(stringify!(Rendering widget: #struct_name)); let parent_id = self.get_id(); context.set_current_id(parent_id); let parent_id = Some(parent_id); diff --git a/kayak_render_macros/src/widget_attributes.rs b/kayak_render_macros/src/widget_attributes.rs index 3f1c9db..020f54a 100644 --- a/kayak_render_macros/src/widget_attributes.rs +++ b/kayak_render_macros/src/widget_attributes.rs @@ -83,21 +83,39 @@ impl<'a, 'c> ToTokens for CustomWidgetAttributes<'a, 'c> { }) .collect(); - if self.children.len() > 0 { + { let children_tuple = self.children.as_option_of_tuples_tokens(); attrs.push(quote! { children: #children_tuple }); } + + let missing = vec![ + ("styles", quote! { styles: None }), + ("on_event", quote! { on_event: None }), + ]; + + for missed in missing { + if !self.attributes.iter().any(|attribute| { + attribute + .ident() + .to_token_stream() + .to_string() + .contains(missed.0) + }) { + attrs.push(missed.1); + } + } + let quoted = if attrs.len() == 0 { - quote!({ id: kayak_core::Index::default(), styles: None, children: None }) + quote!({ id: kayak_core::Index::default(), styles: None, children: None, on_event: None, }) } else { if !self .attributes .iter() .any(|attribute| attribute.ident().to_token_stream().to_string() == "styles") { - quote!({ #(#attrs),*, id: kayak_core::Index::default(), styles: None }) + quote!({ #(#attrs),*, id: kayak_core::Index::default() }) } else { quote!({ #(#attrs),*, id: kayak_core::Index::default() }) } -- GitLab