From 6cfb52bb792b41325b5dcf4ed8a9cdde9f22f16d Mon Sep 17 00:00:00 2001
From: StarToaster <startoaster23@gmail.com>
Date: Sun, 23 Oct 2022 19:32:36 -0400
Subject: [PATCH] Updates, Props, State, and Context version 2.0!

---
 Cargo.toml                           |   1 +
 examples/context.rs                  | 343 ++++++++++----------
 examples/demo.rs                     |  74 +++++
 examples/quads.rs                    |  63 +++-
 examples/simple.rs                   |  86 -----
 examples/simple_state.rs             |  43 ++-
 examples/tabs/tab.rs                 |  59 ++--
 examples/tabs/tab_button.rs          |  93 +++---
 examples/tabs/tab_context.rs         |  41 +--
 examples/tabs/tabs.rs                |  62 ++--
 examples/text.rs                     |  10 +-
 examples/text_box.rs                 |  51 ++-
 examples/todo/input.rs               |  11 +-
 examples/todo/items.rs               |  17 +-
 examples/todo/todo.rs                |  27 +-
 examples/vec.rs                      |  10 +-
 kayak_ui_macros/src/widget.rs        |   4 +-
 src/calculate_nodes.rs               |  22 +-
 src/children.rs                      |   2 +-
 src/clone_component.rs               |  47 +++
 src/context.rs                       | 229 +++++++++++--
 src/event_dispatcher.rs              |  10 +-
 src/lib.rs                           |   4 +
 src/on_event.rs                      |  20 +-
 src/render/extract.rs                |  11 +-
 src/styles/style.rs                  |   2 +-
 src/widget.rs                        | 144 ++++++++-
 src/widget_context.rs                |  25 +-
 src/widget_state.rs                  |  43 +++
 src/widgets/app.rs                   |  47 +--
 src/widgets/background.rs            |  20 +-
 src/widgets/button.rs                |  15 +-
 src/widgets/clip.rs                  |  14 +-
 src/widgets/element.rs               |  17 +-
 src/widgets/image.rs                 |   5 +-
 src/widgets/mod.rs                   | 103 +++++-
 src/widgets/nine_patch.rs            |  16 +-
 src/widgets/scroll/scroll_bar.rs     | 465 +++++++++++++--------------
 src/widgets/scroll/scroll_box.rs     | 302 +++++++++--------
 src/widgets/scroll/scroll_content.rs | 103 +++---
 src/widgets/scroll/scroll_context.rs |  21 +-
 src/widgets/text.rs                  |  25 +-
 src/widgets/text_box.rs              | 216 ++++++-------
 src/widgets/texture_atlas.rs         |   5 +-
 src/widgets/window.rs                | 220 ++++++-------
 45 files changed, 1849 insertions(+), 1299 deletions(-)
 create mode 100644 examples/demo.rs
 delete mode 100644 examples/simple.rs
 create mode 100644 src/clone_component.rs
 create mode 100644 src/widget_state.rs

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