From 151ac0625b68623980b3606024992938eb80f43e Mon Sep 17 00:00:00 2001 From: StarArawn <toasterthegamer@gmail.com> Date: Fri, 17 Dec 2021 17:07:45 -0500 Subject: [PATCH] Fixed up focus states. Fixed up state management with multiple of the same type. --- kayak_core/src/context.rs | 130 +++++++++++++++--- kayak_core/src/event.rs | 2 + kayak_core/src/fragment.rs | 4 + kayak_core/src/lib.rs | 1 + kayak_core/src/multi_state.rs | 26 ++++ kayak_core/src/render_command.rs | 2 + kayak_core/src/render_primitive.rs | 1 + kayak_core/src/vec.rs | 4 + kayak_core/src/widget.rs | 1 + kayak_core/src/widget_manager.rs | 4 - kayak_render_macros/examples/main.rs | 4 + kayak_render_macros/src/function_component.rs | 18 ++- kayak_render_macros/src/lib.rs | 12 +- kayak_widgets/src/text_box.rs | 33 ++++- 14 files changed, 218 insertions(+), 24 deletions(-) create mode 100644 kayak_core/src/multi_state.rs diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs index 6014126..89ca892 100644 --- a/kayak_core/src/context.rs +++ b/kayak_core/src/context.rs @@ -1,18 +1,25 @@ use flo_binding::Changeable; +use morphorm::Hierarchy; use std::collections::HashMap; -use crate::{widget_manager::WidgetManager, Event, EventType, Index, InputEvent}; +use crate::{ + multi_state::MultiState, widget_manager::WidgetManager, Event, EventType, Index, InputEvent, +}; pub struct KayakContext { widget_states: HashMap<crate::Index, resources::Resources>, global_bindings: HashMap<crate::Index, Vec<flo_binding::Uuid>>, widget_state_lifetimes: HashMap<crate::Index, HashMap<flo_binding::Uuid, Box<dyn crate::Releasable>>>, - current_id: Index, + pub current_id: Index, pub widget_manager: WidgetManager, last_mouse_position: (f32, f32), pub global_state: resources::Resources, previous_events: HashMap<Index, Option<EventType>>, + current_focus: Index, + last_focus: Index, + last_state_type_id: Option<std::any::TypeId>, + current_state_index: usize, } impl std::fmt::Debug for KayakContext { @@ -35,6 +42,10 @@ impl KayakContext { last_mouse_position: (0.0, 0.0), global_state: resources::Resources::default(), previous_events: HashMap::new(), + current_focus: Index::default(), + last_focus: Index::default(), + last_state_type_id: None, + current_state_index: 0, } } @@ -90,15 +101,43 @@ impl KayakContext { pub fn set_current_id(&mut self, id: crate::Index) { self.current_id = id; + self.current_state_index = 0; + self.last_state_type_id = None; } pub fn create_state<T: resources::Resource + Clone + PartialEq>( &mut self, initial_state: T, ) -> Option<crate::Binding<T>> { + let state_type_id = initial_state.type_id(); + if let Some(last_state_type_id) = self.last_state_type_id { + if state_type_id != last_state_type_id { + self.current_state_index = 0; + } + } + if self.widget_states.contains_key(&self.current_id) { let states = self.widget_states.get_mut(&self.current_id).unwrap(); - if !states.contains::<crate::Binding<T>>() { + if !states.contains::<MultiState<crate::Binding<T>>>() { + let state = crate::bind(initial_state); + let dirty_nodes = self.widget_manager.dirty_nodes.clone(); + let cloned_id = self.current_id; + let lifetime = state.when_changed(crate::notify(move || { + if let Ok(mut dirty_nodes) = dirty_nodes.lock() { + dirty_nodes.insert(cloned_id); + } + })); + Self::insert_state_lifetime( + &mut self.widget_state_lifetimes, + self.current_id, + state.id, + lifetime, + ); + states.insert(MultiState::new(state)); + self.last_state_type_id = Some(state_type_id); + self.current_state_index += 1; + } else { + // Add new value to the multi-state. let state = crate::bind(initial_state); let dirty_nodes = self.widget_manager.dirty_nodes.clone(); let cloned_id = self.current_id; @@ -113,7 +152,10 @@ impl KayakContext { state.id, lifetime, ); - states.insert(state); + let mut multi_state = states.remove::<MultiState<crate::Binding<T>>>().unwrap(); + multi_state.get_or_add(state, &mut self.current_state_index); + states.insert(multi_state); + self.last_state_type_id = Some(state_type_id); } } else { let mut states = resources::Resources::default(); @@ -131,12 +173,24 @@ impl KayakContext { state.id, lifetime, ); - states.insert(state); + states.insert(MultiState::new(state)); self.widget_states.insert(self.current_id, states); + self.current_state_index += 1; + self.last_state_type_id = Some(state_type_id); } return self.get_state(); } + fn get_state<T: resources::Resource + Clone + PartialEq>(&self) -> Option<T> { + if self.widget_states.contains_key(&self.current_id) { + let states = self.widget_states.get(&self.current_id).unwrap(); + if let Ok(state) = states.get::<MultiState<T>>() { + return Some(state.get(self.current_state_index - 1).clone()); + } + } + return None; + } + fn insert_state_lifetime( lifetimes: &mut HashMap< crate::Index, @@ -177,16 +231,6 @@ impl KayakContext { } } - fn get_state<T: resources::Resource + Clone + PartialEq>(&self) -> Option<T> { - if self.widget_states.contains_key(&self.current_id) { - let states = self.widget_states.get(&self.current_id).unwrap(); - if let Ok(state) = states.get::<T>() { - return Some(state.clone()); - } - } - return None; - } - pub fn set_global_state<T: resources::Resource>(&mut self, state: T) { self.global_state.insert(state); } @@ -222,7 +266,11 @@ impl KayakContext { pub fn process_events(&mut self, input_events: Vec<InputEvent>) { let mut events_stream = Vec::new(); - for (index, _) in self.widget_manager.nodes.iter() { + + let mut was_click_event = false; + let mut was_focus_event = false; + + for index in self.widget_manager.node_tree.down_iter() { if let Some(layout) = self.widget_manager.layout_cache.rect.get(&index) { for input_event in input_events.iter() { match input_event { @@ -278,6 +326,7 @@ impl KayakContext { self.last_mouse_position = *point; } InputEvent::MouseLeftClick => { + was_click_event = true; if layout.contains(&self.last_mouse_position) { let click_event = Event { target: index, @@ -285,6 +334,28 @@ impl KayakContext { ..Event::default() }; events_stream.push(click_event); + + if let Some(widget) = + self.widget_manager.current_widgets.get(index).unwrap() + { + if widget.focusable() { + was_focus_event = true; + if let Some(widget) = + self.widget_manager.current_widgets.get(index).unwrap() + { + dbg!(index); + dbg!(widget.get_name()); + } + let focus_event = Event { + target: index, + event_type: EventType::Focus, + ..Event::default() + }; + events_stream.push(focus_event); + self.last_focus = self.current_focus; + self.current_focus = index; + } + } } } InputEvent::CharEvent { c } => events_stream.push(Event { @@ -302,6 +373,25 @@ impl KayakContext { } } + if was_click_event && !was_focus_event && self.current_focus != Index::default() { + let focus_event = Event { + target: self.current_focus, + event_type: EventType::Blur, + ..Event::default() + }; + events_stream.push(focus_event); + self.current_focus = Index::default(); + } + + if was_click_event && was_focus_event && self.current_focus != self.last_focus { + let focus_event = Event { + target: self.last_focus, + event_type: EventType::Blur, + ..Event::default() + }; + events_stream.push(focus_event); + } + // Propagate Events for event in events_stream.iter_mut() { let mut parents: Vec<Index> = Vec::new(); @@ -312,6 +402,14 @@ impl KayakContext { target_widget.on_event(self, event); self.widget_manager.repossess(target_widget); + // Event debugging + // if matches!(event.event_type, EventType::Click) { + // dbg!("Click event!"); + // let widget = self.widget_manager.take(event.target); + // dbg!(widget.get_name()); + // self.widget_manager.repossess(widget); + // } + // TODO: Restore propagation. // for parent in parents { // if event.should_propagate { diff --git a/kayak_core/src/event.rs b/kayak_core/src/event.rs index 7b56d25..3142a86 100644 --- a/kayak_core/src/event.rs +++ b/kayak_core/src/event.rs @@ -23,6 +23,8 @@ pub enum EventType { Hover, MouseIn, MouseOut, + Focus, + Blur, CharInput { c: char }, KeyboardInput { key: KeyCode }, } diff --git a/kayak_core/src/fragment.rs b/kayak_core/src/fragment.rs index 1315064..9829b57 100644 --- a/kayak_core/src/fragment.rs +++ b/kayak_core/src/fragment.rs @@ -18,6 +18,10 @@ impl Widget for Fragment { self.id } + fn focusable(&self) -> bool { + false + } + fn set_id(&mut self, id: Index) { self.id = id; } diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs index 54edfee..95f4d48 100644 --- a/kayak_core/src/lib.rs +++ b/kayak_core/src/lib.rs @@ -15,6 +15,7 @@ pub mod tree; mod vec; pub mod widget; pub mod widget_manager; +mod multi_state; use std::sync::{Arc, RwLock}; diff --git a/kayak_core/src/multi_state.rs b/kayak_core/src/multi_state.rs new file mode 100644 index 0000000..e83bd5c --- /dev/null +++ b/kayak_core/src/multi_state.rs @@ -0,0 +1,26 @@ +// Handles storing more than one item in state.. +pub struct MultiState<T> { + pub data: Vec<T>, +} + +impl<T> MultiState<T> { + pub fn new(first_item: T) -> Self { + Self { + data: vec![first_item], + } + } + + pub fn get_or_add(&mut self, initial_value: T, index: &mut usize) -> &T { + if !self.data.get(*index).is_some() { + self.data.push(initial_value); + } + let item = &self.data[*index]; + *index += 1; + item + } + + pub fn get(&self, index: usize) -> &T { + let item = &self.data[index]; + item + } +} diff --git a/kayak_core/src/render_command.rs b/kayak_core/src/render_command.rs index c717a2f..d0c4b51 100644 --- a/kayak_core/src/render_command.rs +++ b/kayak_core/src/render_command.rs @@ -4,6 +4,8 @@ use crate::layout_cache::Space; pub enum RenderCommand { Empty, Window, + /// Represents a node that has no renderable object but contributes to the layout. + Layout, Clip, Quad, Text { diff --git a/kayak_core/src/render_primitive.rs b/kayak_core/src/render_primitive.rs index 54b3224..732a867 100644 --- a/kayak_core/src/render_primitive.rs +++ b/kayak_core/src/render_primitive.rs @@ -60,6 +60,7 @@ impl From<&Style> for RenderPrimitive { match render_command { RenderCommand::Window => Self::Empty, RenderCommand::Empty => Self::Empty, + RenderCommand::Layout => Self::Empty, RenderCommand::Clip => Self::Clip { layout: Rect::default(), }, diff --git a/kayak_core/src/vec.rs b/kayak_core/src/vec.rs index 9cc4b0e..ff8ff99 100644 --- a/kayak_core/src/vec.rs +++ b/kayak_core/src/vec.rs @@ -43,6 +43,10 @@ where self.id } + fn focusable(&self) -> bool { + false + } + fn set_id(&mut self, id: Index) { self.id = id; } diff --git a/kayak_core/src/widget.rs b/kayak_core/src/widget.rs index 265e746..6b08976 100644 --- a/kayak_core/src/widget.rs +++ b/kayak_core/src/widget.rs @@ -3,6 +3,7 @@ use as_any::AsAny; use crate::{context::KayakContext, styles::Style, Event, Index}; pub trait Widget: std::fmt::Debug + AsAny + Send + Sync { + fn focusable(&self) -> bool; fn get_id(&self) -> Index; fn set_id(&mut self, id: Index); fn get_styles(&self) -> Option<Style>; diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs index 1f78f7f..c10c225 100644 --- a/kayak_core/src/widget_manager.rs +++ b/kayak_core/src/widget_manager.rs @@ -199,10 +199,6 @@ impl WidgetManager { .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) diff --git a/kayak_render_macros/examples/main.rs b/kayak_render_macros/examples/main.rs index dd08789..a188574 100644 --- a/kayak_render_macros/examples/main.rs +++ b/kayak_render_macros/examples/main.rs @@ -20,6 +20,10 @@ impl Widget for Test { todo!() } + fn focusable(&self) -> bool { + false + } + fn set_id(&mut self, _id: Index) { todo!() } diff --git a/kayak_render_macros/src/function_component.rs b/kayak_render_macros/src/function_component.rs index fbcdaa0..9e96bec 100644 --- a/kayak_render_macros/src/function_component.rs +++ b/kayak_render_macros/src/function_component.rs @@ -3,7 +3,17 @@ use proc_macro_error::emit_error; use quote::{quote, ToTokens}; use syn::spanned::Spanned; -pub fn create_function_widget(f: syn::ItemFn) -> TokenStream { +pub struct WidgetArguments { + pub focusable: bool, +} + +impl Default for WidgetArguments { + fn default() -> Self { + Self { focusable: false } + } +} + +pub fn create_function_widget(f: syn::ItemFn, widget_arguments: WidgetArguments) -> TokenStream { let struct_name = f.sig.ident; let (impl_generics, ty_generics, where_clause) = f.sig.generics.split_for_impl(); let inputs = f.sig.inputs; @@ -15,6 +25,8 @@ pub fn create_function_widget(f: syn::ItemFn) -> TokenStream { #[cfg(not(feature = "internal"))] let kayak_core = quote! { kayak_ui::core }; + let focusable = widget_arguments.focusable; + let mut input_names: Vec<_> = inputs .iter() .filter_map(|argument| match argument { @@ -147,6 +159,10 @@ pub fn create_function_widget(f: syn::ItemFn) -> TokenStream { self.id } + fn focusable(&self) -> bool { + #focusable + } + fn set_id(&mut self, id: #kayak_core::Index) { self.id = id; } diff --git a/kayak_render_macros/src/lib.rs b/kayak_render_macros/src/lib.rs index 43bd3c9..88d542e 100644 --- a/kayak_render_macros/src/lib.rs +++ b/kayak_render_macros/src/lib.rs @@ -11,6 +11,7 @@ mod partial_eq; mod widget; mod widget_attributes; +use function_component::WidgetArguments; use partial_eq::impl_dyn_partial_eq; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; @@ -60,9 +61,16 @@ pub fn constructor(input: TokenStream) -> TokenStream { #[proc_macro_attribute] #[proc_macro_error] -pub fn widget(_attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn widget(args: TokenStream, item: TokenStream) -> TokenStream { + let mut widget_args = WidgetArguments::default(); + if !args.is_empty() { + // Parse stuff.. + let parsed = args.to_string(); + widget_args.focusable = parsed.contains("focusable"); + } + let f = parse_macro_input!(item as syn::ItemFn); - function_component::create_function_widget(f) + function_component::create_function_widget(f, widget_args) } #[proc_macro_derive(DynPartialEq)] diff --git a/kayak_widgets/src/text_box.rs b/kayak_widgets/src/text_box.rs index 1f0121f..caab514 100644 --- a/kayak_widgets/src/text_box.rs +++ b/kayak_widgets/src/text_box.rs @@ -1,4 +1,5 @@ use kayak_ui::core::{ + render_command::RenderCommand, rsx, styles::{Style, StyleProp, Units}, widget, Bound, Color, EventType, MutableBound, OnEvent, @@ -33,8 +34,16 @@ impl std::fmt::Debug for OnChange { } } -#[widget] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Focus(pub bool); + +#[widget(focusable)] pub fn TextBox(value: String, on_change: Option<OnChange>) { + *styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::Layout), + ..styles.clone().unwrap_or_default() + }); + let background_styles = Style { background_color: StyleProp::Value(Color::new(0.176, 0.196, 0.215, 1.0)), border_radius: StyleProp::Value((5.0, 5.0, 5.0, 5.0)), @@ -45,10 +54,15 @@ pub fn TextBox(value: String, on_change: Option<OnChange>) { }; let internal_value = context.create_state("".to_string()).unwrap(); + let has_focus = context.create_state(Focus(false)).unwrap(); let cloned_on_change = on_change.clone(); + let cloned_has_focus = has_focus.clone(); self.on_event = Some(OnEvent::new(move |_, event| match event.event_type { EventType::CharInput { c } => { + if !cloned_has_focus.get().0 { + return; + } let mut current_value = internal_value.get(); if c == '\u{8}' { if current_value.len() > 0 { @@ -66,9 +80,26 @@ pub fn TextBox(value: String, on_change: Option<OnChange>) { } internal_value.set(current_value); } + EventType::Focus => { + dbg!("Has focus!"); + cloned_has_focus.set(Focus(true)) + } + EventType::Blur => { + dbg!("Lost focus!"); + cloned_has_focus.set(Focus(false)) + } _ => {} })); + // let cloned_has_focus = has_focus.clone(); + // let on_event = Some(OnEvent::new(move |_, event| match event.event_type { + // EventType::Focus => { + // dbg!("Has focus!"); + // cloned_has_focus.set(Focus(true)) + // } + // _ => {} + // })); + let value = value.clone(); rsx! { <Background styles={Some(background_styles)}> -- GitLab