From d6263f153510d22c1e10393d3556956ede28a3af Mon Sep 17 00:00:00 2001
From: MrGVSV <gino.valente.code@gmail.com>
Date: Fri, 14 Jan 2022 17:54:30 -0800
Subject: [PATCH] Added tabs example

---
 Cargo.toml                         |   4 +
 examples/tabs/tab.rs               | 171 +++++++++++++++++++++++++++++
 examples/tabs/tab_bar.rs           | 103 +++++++++++++++++
 examples/tabs/tab_box.rs           |  48 ++++++++
 examples/tabs/tab_content.rs       |  33 ++++++
 examples/tabs/tabs.rs              | 170 ++++++++++++++++++++++++++++
 examples/tabs/theming.rs           |  27 +++++
 kayak_core/src/event_dispatcher.rs |   6 -
 kayak_core/src/focus_tree.rs       |  13 +--
 kayak_core/src/lib.rs              |   2 +-
 kayak_core/src/widget_manager.rs   |  29 +++--
 src/widgets/button.rs              |   2 +-
 12 files changed, 581 insertions(+), 27 deletions(-)
 create mode 100644 examples/tabs/tab.rs
 create mode 100644 examples/tabs/tab_bar.rs
 create mode 100644 examples/tabs/tab_box.rs
 create mode 100644 examples/tabs/tab_content.rs
 create mode 100644 examples/tabs/tabs.rs
 create mode 100644 examples/tabs/theming.rs

diff --git a/Cargo.toml b/Cargo.toml
index 1aadade..e6cddfc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,3 +29,7 @@ bevy = { version = "0.6.0" }
 [[example]]
 name = "todo"
 path = "examples/todo/todo.rs"
+
+[[example]]
+name = "tabs"
+path = "examples/tabs/tabs.rs"
diff --git a/examples/tabs/tab.rs b/examples/tabs/tab.rs
new file mode 100644
index 0000000..63b34d9
--- /dev/null
+++ b/examples/tabs/tab.rs
@@ -0,0 +1,171 @@
+use kayak_ui::{
+    core::{
+        render_command::RenderCommand,
+        styles::{LayoutType, Style, StyleProp, Units},
+        Bound, EventType, Handler, KeyCode, OnEvent, rsx, use_state, widget,
+    },
+    widgets::{Background, Text},
+};
+
+use crate::TabTheme;
+
+#[derive(Clone, PartialEq)]
+enum TabHoverState {
+    None,
+    Inactive,
+    Active,
+}
+
+#[widget]
+pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_request_remove: Handler) {
+    let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
+    let (hover_state, set_hover_state, ..) = use_state!(TabHoverState::None);
+    match hover_state {
+        TabHoverState::Inactive if selected => set_hover_state(TabHoverState::Active),
+        TabHoverState::Active if !selected => set_hover_state(TabHoverState::Inactive),
+        _ => {}
+    };
+    let (focus_state, set_focus_state, ..) = use_state!(false);
+    let (is_exit_hovered, set_is_exit_hovered, ..) = use_state!(false);
+
+    let event_handler = OnEvent::new(move |_, event| {
+        match event.event_type {
+            EventType::Hover => {
+                if selected {
+                    set_hover_state(TabHoverState::Active);
+                } else {
+                    set_hover_state(TabHoverState::Inactive);
+                }
+            }
+            EventType::MouseOut => {
+                set_hover_state(TabHoverState::None);
+            }
+            EventType::Focus => {
+                set_focus_state(true);
+            }
+            EventType::Blur => {
+                set_focus_state(false);
+            }
+            _ => {}
+        }
+    });
+
+    let exit_btn_event_handler = OnEvent::new(move |_, event| {
+        match event.event_type {
+            EventType::Hover => {
+                set_is_exit_hovered(true);
+            }
+            EventType::MouseOut => {
+                set_is_exit_hovered(false);
+            }
+            EventType::Focus => {
+                set_is_exit_hovered(true);
+            }
+            EventType::Blur => {
+                set_is_exit_hovered(false);
+            }
+            EventType::Click => {
+                // Stop propagation so we don't select a deleted tab!
+                event.stop_propagation();
+                on_request_remove.call(());
+            }
+            EventType::KeyDown(evt) => {
+                if evt.key() == KeyCode::Return || evt.key() == KeyCode::Space {
+                    // Stop propagation so we don't select a deleted tab!
+                    event.stop_propagation();
+                    on_request_remove.call(());
+                }
+            }
+            _ => {}
+        }
+    });
+
+    let tab_color = match hover_state {
+        TabHoverState::None if selected => theme.get().active_tab.normal,
+        TabHoverState::None => theme.get().inactive_tab.normal,
+        TabHoverState::Inactive => theme.get().inactive_tab.hovered,
+        TabHoverState::Active => theme.get().active_tab.hovered,
+    };
+
+    let pad_x = Units::Pixels(2.0);
+    let bg_styles = Style {
+        background_color: StyleProp::Value(tab_color),
+        layout_type: StyleProp::Value(LayoutType::Row),
+        padding_left: StyleProp::Value(pad_x),
+        padding_right: StyleProp::Value(pad_x),
+        ..Default::default()
+    };
+
+
+    let border_width = Units::Pixels(2.0);
+    let border_styles = Style {
+        background_color: if focus_state {
+            StyleProp::Value(theme.get().focus)
+        } else {
+            StyleProp::Value(tab_color)
+        },
+        padding_left: StyleProp::Value(border_width),
+        padding_right: StyleProp::Value(border_width),
+        padding_top: StyleProp::Value(border_width),
+        padding_bottom: StyleProp::Value(border_width),
+        layout_type: StyleProp::Value(LayoutType::Row),
+        ..Default::default()
+    };
+
+    let text_styles = Style {
+        background_color: if focus_state {
+            StyleProp::Value(theme.get().focus)
+        } else {
+            StyleProp::Value(tab_color)
+        },
+        color: StyleProp::Value(theme.get().text.normal),
+        top: StyleProp::Value(Units::Stretch(0.1)),
+        bottom: StyleProp::Value(Units::Stretch(1.0)),
+        width: StyleProp::Value(Units::Stretch(1.0)),
+        ..Default::default()
+    };
+
+    let exit_styles = Style {
+        background_color: if is_exit_hovered {
+            let mut darkened = theme.get().inactive_tab.hovered;
+            darkened.r -= 0.025;
+            darkened.g -= 0.025;
+            darkened.b -= 0.025;
+            StyleProp::Value(darkened)
+        } else {
+            StyleProp::Value(tab_color)
+        },
+        width: StyleProp::Value(Units::Pixels(theme.get().tab_height - 4.0)),
+        height: StyleProp::Value(Units::Pixels(theme.get().tab_height - 4.0)),
+        top: StyleProp::Value(Units::Stretch(0.175)),
+        bottom: StyleProp::Value(Units::Stretch(1.0)),
+        left: StyleProp::Value(Units::Stretch(1.0)),
+        ..Default::default()
+    };
+
+    let exit_text_styles = Style {
+        left: StyleProp::Value(Units::Stretch(1.0)),
+        right: StyleProp::Value(Units::Stretch(1.0)),
+        top: StyleProp::Value(Units::Stretch(0.35)),
+        bottom: StyleProp::Value(Units::Stretch(1.0)),
+        ..Default::default()
+    };
+
+    self.styles = Some(Style {
+        render_command: StyleProp::Value(RenderCommand::Layout),
+        height: StyleProp::Value(Units::Pixels(theme.get().tab_height)),
+        max_width: StyleProp::Value(Units::Pixels(100.0)),
+        ..styles.clone().unwrap_or_default()
+    });
+
+    rsx! {
+        <Background focusable={Some(true)} on_event={Some(event_handler)} styles={Some(border_styles)}>
+            <Background styles={Some(bg_styles)}>
+                <Text content={content} size={12.0} styles={Some(text_styles)} />
+                <Background focusable={Some(true)} on_event={Some(exit_btn_event_handler)} styles={Some(exit_styles)}>
+                    <Text content={"X".to_string()} size={8.0} styles={Some(exit_text_styles)} />
+                </Background>
+            </Background>
+        </Background>
+    }
+}
\ No newline at end of file
diff --git a/examples/tabs/tab_bar.rs b/examples/tabs/tab_bar.rs
new file mode 100644
index 0000000..2d9cece
--- /dev/null
+++ b/examples/tabs/tab_bar.rs
@@ -0,0 +1,103 @@
+use kayak_ui::{
+    core::{
+        styles::{LayoutType, Style, StyleProp, Units},
+        Bound, constructor, EventType, Handler, KeyCode, OnEvent,
+        rsx, use_state, VecTracker, widget,
+    },
+    widgets::{Background, Element, Text},
+};
+
+use crate::tab::Tab;
+use crate::TabTheme;
+
+#[widget]
+pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on_select_tab: Handler<usize>, on_add_tab: Handler, on_remove_tab: Handler<usize>) {
+    let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
+    let (is_add_hovered, set_is_add_hovered, ..) = use_state!(false);
+
+    let tabs = tabs.iter().enumerate().map(|(index, tab)| {
+        let on_select = on_select_tab.clone();
+        let tab_event_handler = OnEvent::new(move |_, event| {
+            match event.event_type {
+                EventType::Click => {
+                    on_select.call(index);
+                }
+                EventType::KeyDown(evt) => {
+                    if evt.key() == KeyCode::Return || evt.key() == KeyCode::Space {
+                        on_select.call(index);
+                    }
+                }
+                _ => {}
+            }
+        });
+
+        let on_remove = on_remove_tab.clone();
+        let on_request_remove = Handler::new(move |_| {
+            on_remove.call(index);
+        });
+
+        constructor! {
+            <Tab content={tab.clone()} on_event={Some(tab_event_handler.clone())} selected={selected == index} on_request_remove={on_request_remove} />
+        }
+    }).collect::<Vec<_>>();
+
+    let add_btn_event_handler = OnEvent::new(move |_, event| match event.event_type {
+        EventType::Hover => {
+            set_is_add_hovered(true);
+        }
+        EventType::MouseOut => {
+            set_is_add_hovered(false);
+        }
+        EventType::Focus => {
+            set_is_add_hovered(true);
+        }
+        EventType::Blur => {
+            set_is_add_hovered(false);
+        }
+        EventType::Click => {
+            on_add_tab.call(());
+        }
+        EventType::KeyDown(evt) => {
+            if evt.key() == KeyCode::Return || evt.key() == KeyCode::Space {
+                on_add_tab.call(());
+            }
+        }
+        _ => {}
+    });
+
+    let add_btn_styles = Style {
+        height: StyleProp::Value(Units::Pixels(theme.get().tab_height)),
+        width: StyleProp::Value(Units::Pixels(theme.get().tab_height)),
+        border_radius: StyleProp::Value((10.0, 10.0, 10.0, 10.0)),
+        ..Default::default()
+    };
+    let add_btn_text_styles = Style {
+        color: if is_add_hovered {
+            StyleProp::Value(theme.get().text.hovered)
+        } else {
+            StyleProp::Value(theme.get().text.normal)
+        },
+        left: StyleProp::Value(Units::Stretch(1.0)),
+        right: StyleProp::Value(Units::Stretch(1.0)),
+        height: StyleProp::Value(Units::Percentage(100.0)),
+        width: StyleProp::Value(Units::Percentage(100.0)),
+        ..Default::default()
+    };
+
+    let background_styles = Style {
+        layout_type: StyleProp::Value(LayoutType::Row),
+        background_color: StyleProp::Value(theme.get().bg),
+        height: StyleProp::Value(Units::Auto),
+        width: StyleProp::Value(Units::Stretch(1.0)),
+        ..styles.clone().unwrap_or_default()
+    };
+
+    rsx! {
+        <Background styles={Some(background_styles)}>
+            <VecTracker data={tabs} />
+            <Element focusable={Some(true)} on_event={Some(add_btn_event_handler)} styles={Some(add_btn_styles)}>
+                <Text content={"+".to_string()} size={16.0} styles={Some(add_btn_text_styles)} />
+            </Element>
+        </Background>
+    }
+}
\ No newline at end of file
diff --git a/examples/tabs/tab_box.rs b/examples/tabs/tab_box.rs
new file mode 100644
index 0000000..0f7353f
--- /dev/null
+++ b/examples/tabs/tab_box.rs
@@ -0,0 +1,48 @@
+use std::fmt::Debug;
+use kayak_ui::{
+    core::{
+        render_command::RenderCommand,
+        styles::{Style, StyleProp},
+        Bound, Fragment, Handler, rsx, use_state, widget,
+    }
+};
+
+use crate::tab_bar::TabBar;
+use crate::TabTheme;
+use crate::tab_content::TabContent;
+
+#[derive(Debug, Default, Clone, PartialEq)]
+pub struct TabData {
+    pub name: String,
+    pub content: Fragment,
+}
+
+#[widget]
+pub fn TabBox(context: &mut KayakContext, tabs: Vec<TabData>, initial_tab: usize, on_add_tab: Handler, on_remove_tab: Handler<usize>) {
+    let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
+    let (selected, set_selected, ..) = use_state!(initial_tab);
+
+    let tab_names = tabs.iter().map(|tab| {
+        tab.name.clone()
+    }).collect::<Vec<String>>();
+    let tab_content = tabs.iter().map(|tab| {
+        tab.content.clone()
+    }).collect::<Vec<_>>();
+
+    let on_select_tab = Handler::<usize>::new(move |index| {
+        set_selected(index);
+    });
+
+    self.styles = Some(Style {
+        render_command: StyleProp::Value(RenderCommand::Quad),
+        background_color: StyleProp::Value(theme.get().fg),
+        ..Default::default()
+    });
+
+    rsx! {
+        <>
+            <TabBar tabs={tab_names} selected={selected} on_select_tab={on_select_tab} on_add_tab={on_add_tab} on_remove_tab={on_remove_tab} />
+            <TabContent tabs={tab_content} selected={selected}  />
+        </>
+    }
+}
\ No newline at end of file
diff --git a/examples/tabs/tab_content.rs b/examples/tabs/tab_content.rs
new file mode 100644
index 0000000..1b5b024
--- /dev/null
+++ b/examples/tabs/tab_content.rs
@@ -0,0 +1,33 @@
+use std::ops::Index;
+use kayak_ui::{
+    core::{
+        render_command::RenderCommand,
+        styles::{Style, StyleProp},
+        Bound, Fragment, rsx, VecTracker, widget,
+    }
+};
+
+use crate::TabTheme;
+
+#[widget]
+pub fn TabContent(context: &mut KayakContext, tabs: Vec<Fragment>, selected: usize) {
+    let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
+
+    if selected >= tabs.len() {
+        return;
+    }
+
+    self.styles = Some(Style {
+        render_command: StyleProp::Value(RenderCommand::Quad),
+        background_color: StyleProp::Value(theme.get().fg),
+        ..Default::default()
+    });
+
+    let tab = tabs.index(selected).clone();
+
+    rsx! {
+        <>
+            <VecTracker data={vec![tab.clone()]} />
+        </>
+    }
+}
\ No newline at end of file
diff --git a/examples/tabs/tabs.rs b/examples/tabs/tabs.rs
new file mode 100644
index 0000000..926ab29
--- /dev/null
+++ b/examples/tabs/tabs.rs
@@ -0,0 +1,170 @@
+use bevy::{
+    prelude::{App as BevyApp, AssetServer, Commands, Res, ResMut},
+    window::WindowDescriptor,
+    DefaultPlugins,
+};
+
+use kayak_ui::{
+    bevy::{BevyContext, BevyKayakUIPlugin, FontMapping, UICameraBundle},
+    core::{
+        styles::{Style, StyleProp, Units},
+        Children, Color, constructor, Handler, Index, render, rsx, use_state, widget
+    },
+    widgets::{App, Text, Window},
+};
+
+use tab_box::TabBox;
+use tab_box::TabData;
+use crate::theming::{ColorState, TabTheme, TabThemeProvider};
+
+mod tab_bar;
+mod tab_box;
+mod tab_content;
+mod tab;
+mod theming;
+
+#[widget]
+fn TabDemo() {
+    let text_style = Style {
+        width: StyleProp::Value(Units::Percentage(75.0)),
+        top: StyleProp::Value(Units::Stretch(0.5)),
+        left: StyleProp::Value(Units::Stretch(1.0)),
+        bottom: StyleProp::Value(Units::Stretch(1.0)),
+        right: StyleProp::Value(Units::Stretch(1.0)),
+        ..Default::default()
+    };
+
+    let (count, set_count, ..) = use_state!(0);
+    let (tabs, set_tabs, ..) = use_state!(vec![
+        TabData {
+            name: "Tab 1".to_string(),
+            content: {
+                let children = Children::None;
+                let text_style = text_style.clone();
+                constructor! {
+                    <>
+                        <Text content={"Welcome to Tab 1!".to_string()} size={48.0} styles={Some(text_style)} />
+                    </>
+                }
+            },
+        },
+        TabData {
+            name: "Tab 2".to_string(),
+            content: {
+                let children = Children::None;
+                let text_style = text_style.clone();
+                constructor! {
+                    <>
+                        <Text content={"Welcome to Tab 2!".to_string()} size={48.0} styles={Some(text_style)} />
+                    </>
+                }
+            },
+        },
+        TabData {
+            name: "Tab 3".to_string(),
+            content: {
+                let children = Children::None;
+                let text_style = text_style.clone();
+                constructor! {
+                    <>
+                        <Text content={"Welcome to Tab 3!".to_string()} size={48.0} styles={Some(text_style)} />
+                    </>
+                }
+            },
+        },
+    ]);
+
+    let tab_clone = tabs.clone();
+    let set_added_tabs = set_tabs.clone();
+    let on_add_tab = Handler::new(move |_| {
+        let mut tab_clone = (&tab_clone).clone();
+        tab_clone.push(TabData {
+            name: format!("Tab {}", count),
+            content: {
+                let children = Children::None;
+                constructor! {
+                    <>
+                        <Text content={"Hello".to_string()} size={12.0} />
+                    </>
+                }
+            },
+        }, );
+        set_count(count + 1);
+        set_added_tabs(tab_clone);
+    });
+
+    let tab_clone = tabs.clone();
+    let on_remove_tab = Handler::new(move |index: usize| {
+        let mut tab_clone = (&tab_clone).clone();
+        tab_clone.remove(index);
+        set_tabs(tab_clone);
+    });
+
+    rsx! {
+        <TabBox tabs={tabs} on_add_tab={on_add_tab} on_remove_tab={on_remove_tab} />
+    }
+}
+
+fn startup(
+    mut commands: Commands,
+    mut font_mapping: ResMut<FontMapping>,
+    asset_server: Res<AssetServer>,
+) {
+    commands.spawn_bundle(UICameraBundle::new());
+
+    font_mapping.add(asset_server.load("roboto.kayak_font"));
+
+    let theme = TabTheme {
+        primary: Default::default(),
+        bg: Color::new(0.176, 0.227, 0.255, 1.0),
+        fg: Color::new(0.286, 0.353, 0.392, 1.0),
+        focus: Color::new(0.388, 0.474, 0.678, 0.5),
+        text: ColorState {
+            normal: Color::new(0.949, 0.956, 0.968, 1.0),
+            hovered: Color::new(0.650, 0.574, 0.669, 1.0),
+            active: Color::new(0.949, 0.956, 0.968, 1.0),
+            disabled: Color::new(0.662, 0.678, 0.694, 1.0),
+        },
+        active_tab: ColorState {
+            normal: Color::new(0.286, 0.353, 0.392, 1.0),
+            hovered: Color::new(0.246, 0.323, 0.352, 1.0),
+            active: Default::default(),
+            disabled: Color::new(0.474, 0.486, 0.505, 1.0),
+        },
+        inactive_tab: ColorState {
+            normal: Color::new(0.176, 0.227, 0.255, 1.0),
+            hovered: Color::new(0.16, 0.21, 0.23, 1.0),
+            active: Default::default(),
+            disabled: Color::new(0.474, 0.486, 0.505, 1.0),
+        },
+        tab_height: 22.0,
+    };
+
+    let context = BevyContext::new(|context| {
+        render! {
+            <App>
+                <Window position={(50.0, 50.0)} size={(600.0, 300.0)} title={"Tabs Example".to_string()}>
+                    <TabThemeProvider initial_theme={theme}>
+                        <TabDemo />
+                    </TabThemeProvider>
+                </Window>
+            </App>
+        }
+    });
+
+    commands.insert_resource(context);
+}
+
+fn main() {
+    BevyApp::new()
+        .insert_resource(WindowDescriptor {
+            width: 1270.0,
+            height: 720.0,
+            title: String::from("UI Example"),
+            ..Default::default()
+        })
+        .add_plugins(DefaultPlugins)
+        .add_plugin(BevyKayakUIPlugin)
+        .add_startup_system(startup)
+        .run();
+}
\ No newline at end of file
diff --git a/examples/tabs/theming.rs b/examples/tabs/theming.rs
new file mode 100644
index 0000000..cbcc1b3
--- /dev/null
+++ b/examples/tabs/theming.rs
@@ -0,0 +1,27 @@
+use kayak_ui::{core::{Color, rsx, widget}};
+
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct TabTheme {
+    pub primary: Color,
+    pub bg: Color,
+    pub fg: Color,
+    pub focus: Color,
+    pub text: ColorState,
+    pub active_tab: ColorState,
+    pub inactive_tab: ColorState,
+    pub tab_height: f32
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct ColorState {
+    pub normal: Color,
+    pub hovered: Color,
+    pub active: Color,
+    pub disabled: Color,
+}
+
+#[widget]
+pub fn TabThemeProvider(initial_theme: TabTheme) {
+    context.create_provider(initial_theme);
+    rsx! { <>{children}</> }
+}
\ No newline at end of file
diff --git a/kayak_core/src/event_dispatcher.rs b/kayak_core/src/event_dispatcher.rs
index 848e5f0..c1c242a 100644
--- a/kayak_core/src/event_dispatcher.rs
+++ b/kayak_core/src/event_dispatcher.rs
@@ -94,12 +94,6 @@ impl EventDispatcher {
                 // --- Call Event --- //
                 let mut target_widget = context.widget_manager.take(index);
                 target_widget.on_event(context, &mut node_event);
-                // if target_widget.get_name() == String::from("TextBox") {
-                //     println!("Event: {:#?}", node_event);
-                //     println!("Widget Focus: {} - {:?}", target_widget.focusable(), index);
-                //     println!("Context Focus: {} - {:?}", context.get_focusable(index), index);
-                // }
-                // target_widget.set_focusable(false);
                 context.widget_manager.repossess(target_widget);
 
                 event.default_prevented |= node_event.default_prevented;
diff --git a/kayak_core/src/focus_tree.rs b/kayak_core/src/focus_tree.rs
index eb5edf2..2535a1f 100644
--- a/kayak_core/src/focus_tree.rs
+++ b/kayak_core/src/focus_tree.rs
@@ -36,14 +36,7 @@ impl FocusTree {
             }
         }
 
-        if let Some(root) = self.tree.root_node {
-            // Replace root node
-            self.tree.replace(root, index);
-            if widget_tree.is_descendant(root, index) {
-                // If old root is child -> add it back in
-                self.add(root, &widget_tree);
-            }
-        } else {
+        if self.tree.root_node.is_none() {
             // Set root node
             self.tree.add(index, None);
             self.focus(index);
@@ -158,6 +151,10 @@ impl FocusTree {
 
         self.tree.root_node
     }
+
+    pub fn tree(&self) -> &Tree {
+        &self.tree
+    }
 }
 
 
diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs
index 2e5693f..ec41180 100644
--- a/kayak_core/src/lib.rs
+++ b/kayak_core/src/lib.rs
@@ -63,7 +63,7 @@ impl OnEvent {
 }
 
 #[derive(Clone)]
-pub struct Handler<T>(pub Arc<RwLock<dyn FnMut(T) + Send + Sync + 'static>>);
+pub struct Handler<T = ()>(pub Arc<RwLock<dyn FnMut(T) + Send + Sync + 'static>>);
 
 impl<T> Default for Handler<T> {
     fn default() -> Self {
diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs
index c406f47..a570757 100644
--- a/kayak_core/src/widget_manager.rs
+++ b/kayak_core/src/widget_manager.rs
@@ -360,7 +360,7 @@ impl WidgetManager {
         )
     }
 
-    fn build_nodes_tree(&self) -> Tree {
+    fn build_nodes_tree(&mut self) -> Tree {
         let mut tree = Tree::default();
         let (root_node_id, _) = self.current_widgets.iter().next().unwrap();
         tree.root_node = Some(root_node_id);
@@ -368,6 +368,11 @@ impl WidgetManager {
             tree.root_node.unwrap(),
             self.get_valid_node_children(tree.root_node.unwrap()),
         );
+
+        let old_focus = self.focus_tree.current();
+        self.focus_tree.clear();
+        self.focus_tree.add(root_node_id, &self.tree);
+
         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 {
@@ -381,7 +386,19 @@ impl WidgetManager {
                     }
                 }
             }
+
+            let focusable = self.get_focusable(widget_id).unwrap_or_default();
+            if focusable {
+                self.focus_tree.add(widget_id, &self.tree);
+            }
         }
+
+        if let Some(old_focus) = old_focus {
+            if self.focus_tree.contains(old_focus) {
+                self.focus_tree.focus(old_focus);
+            }
+        }
+
         tree
     }
 
@@ -426,16 +443,6 @@ impl WidgetManager {
     }
 
     pub fn set_focusable(&mut self, focusable: Option<bool>, index: Index, is_parent: bool) {
-        let is_focusable = self.get_focusable(index).unwrap_or_default();
         self.focus_tracker.set_focusability(index, focusable, is_parent);
-        let focusable = self.get_focusable(index).unwrap_or_default();
-
-        if focusable {
-            if !is_focusable {
-                self.focus_tree.add(index, &self.tree);
-            }
-        } else if is_focusable {
-            self.focus_tree.remove(index);
-        }
     }
 }
diff --git a/src/widgets/button.rs b/src/widgets/button.rs
index 5c05b4d..cfe2050 100644
--- a/src/widgets/button.rs
+++ b/src/widgets/button.rs
@@ -6,7 +6,7 @@ use crate::core::{
     widget, Children, Fragment,
 };
 
-#[widget]
+#[widget(focusable)]
 pub fn Button(children: Children, styles: Option<Style>) {
     let base_styles = styles.clone().unwrap_or_default();
     *styles = Some(Style {
-- 
GitLab