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