From 9228e05f5f0b4c04b32259f1b13d280304a14ee5 Mon Sep 17 00:00:00 2001
From: John Mitchell <startoaster23@gmail.com>
Date: Sun, 13 Nov 2022 21:22:48 -0500
Subject: [PATCH] Fixed a few issues. Changed root context to be a component.

---
 book/src/chapter_1.md            |   6 +-
 examples/bevy_scene.rs           |  17 +-
 examples/clipping.rs             |   6 +-
 examples/conditional_widget.rs   |   5 +-
 examples/context.rs              |   5 +-
 examples/demo.rs                 |   3 +-
 examples/hello_world.rs          |   6 +-
 examples/hello_world_no_macro.rs |   7 +-
 examples/image.rs                |   6 +-
 examples/main_menu.rs            |   5 +-
 examples/nine_patch.rs           |   5 +-
 examples/quads.rs                |   4 +-
 examples/render_target.rs        |  26 ++--
 examples/scrolling.rs            | 135 ++++++++--------
 examples/simple_state.rs         |   5 +-
 examples/tabs/tabs.rs            |   4 +-
 examples/text.rs                 |   6 +-
 examples/text_box.rs             |   5 +-
 examples/texture_atlas.rs        |   5 +-
 examples/todo/todo.rs            |   5 +-
 examples/vec.rs                  |   6 +-
 src/calculate_nodes.rs           |  21 ++-
 src/camera/camera.rs             |  10 +-
 src/context.rs                   | 260 ++++++++++++++++++-------------
 src/event_dispatcher.rs          |   8 +-
 src/input.rs                     |  33 +++-
 src/lib.rs                       |   5 +
 src/render/extract.rs            |  32 ++--
 src/render/font/mod.rs           |  11 +-
 src/widgets/app.rs               |   9 +-
 src/widgets/mod.rs               | 194 ++++++++++++-----------
 31 files changed, 467 insertions(+), 388 deletions(-)

diff --git a/book/src/chapter_1.md b/book/src/chapter_1.md
index 419a831..1f820f0 100644
--- a/book/src/chapter_1.md
+++ b/book/src/chapter_1.md
@@ -39,7 +39,8 @@ fn startup(
             />
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+    
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
@@ -98,7 +99,8 @@ fn startup(
     widget_context.add_widget(None, app_entity);
 
     // Add widget context as resource.
-    commands.insert_resource(widget_context);
+    
+ commands.spawn(UICameraBundle::new(widget_context));
 }
 fn main() {
     App::new()
diff --git a/examples/bevy_scene.rs b/examples/bevy_scene.rs
index e57e0db..096ee9f 100644
--- a/examples/bevy_scene.rs
+++ b/examples/bevy_scene.rs
@@ -38,7 +38,7 @@ struct WorldCamera;
 fn set_active_tile_target(
     mut tile: Query<&mut ActiveTile>,
     cursor: Res<Input<MouseButton>>,
-    event_context: Res<EventDispatcher>,
+    event_context: Query<&EventDispatcher, With<GameUI>>,
     camera_transform: Query<&GlobalTransform, With<WorldCamera>>,
     windows: Res<Windows>,
 ) {
@@ -47,7 +47,7 @@ fn set_active_tile_target(
         return;
     }
 
-    if event_context.contains_cursor() {
+    if event_context.single().contains_cursor() {
         // This is the important bit:
         // If the cursor is over a part of the UI, then we should not allow clicks to pass through to the world
         return;
@@ -79,14 +79,14 @@ fn move_active_tile(mut tile: Query<(&mut Transform, &ActiveTile)>) {
 
 /// A system that moves the ghost tile to the cursor's position
 fn move_ghost_tile(
-    event_context: Res<EventDispatcher>,
+    event_context: Query<&EventDispatcher, With<GameUI>>,
     mut tile: Query<&mut Transform, With<GhostTile>>,
     mut cursor_moved: EventReader<CursorMoved>,
     camera_transform: Query<&GlobalTransform, With<WorldCamera>>,
     windows: Res<Windows>,
 ) {
     for _ in cursor_moved.iter() {
-        if !event_context.contains_cursor() {
+        if !event_context.single().contains_cursor() {
             let world_pos = cursor_to_world(&windows, camera_transform.single());
             let tile_pos = world_to_tile(world_pos);
             let mut ghost = tile.single_mut();
@@ -144,7 +144,6 @@ fn cursor_to_world(windows: &Windows, camera_transform: &GlobalTransform) -> Vec
     let size = Vec2::new(window.width(), window.height());
 
     let mut pos = window.cursor_position().unwrap_or_default();
-    pos.y = size.y - pos.y;
     pos -= size / 2.0;
 
     let point = camera_transform.compute_matrix() * pos.extend(0.0).extend(1.0);
@@ -165,6 +164,9 @@ fn ghost_color(color: Color) -> Color {
     c
 }
 
+#[derive(Component)]
+pub struct GameUI;
+
 fn startup(
     mut commands: Commands,
     mut font_mapping: ResMut<FontMapping>,
@@ -172,9 +174,8 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
 
     let handle_change_color = OnEvent::new(
         move |In((event_dispatcher_context, _, event, _)): In<(
@@ -254,7 +255,7 @@ fn startup(
         </KayakAppBundle>
     }
 
-    commands.insert_resource(widget_context);
+    commands.spawn((UICameraBundle::new(widget_context), GameUI));
 }
 
 fn main() {
diff --git a/examples/clipping.rs b/examples/clipping.rs
index b583137..ee5a303 100644
--- a/examples/clipping.rs
+++ b/examples/clipping.rs
@@ -8,11 +8,10 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("lato-light.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let image = asset_server.load("panel.png");
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
     let nine_patch_styles = KStyle {
@@ -56,7 +55,8 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sed tellus neque.
             </NinePatchBundle>
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/conditional_widget.rs b/examples/conditional_widget.rs
index a6de4d6..b668f17 100644
--- a/examples/conditional_widget.rs
+++ b/examples/conditional_widget.rs
@@ -114,9 +114,9 @@ fn startup(
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
     commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<MyWidget, MyWidgetState>();
     widget_context.add_widget_system(
@@ -129,7 +129,8 @@ fn startup(
             <MyWidgetBundle />
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/context.rs b/examples/context.rs
index 8147520..aa3696f 100644
--- a/examples/context.rs
+++ b/examples/context.rs
@@ -330,9 +330,9 @@ fn startup(
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
     commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<ThemeDemo, EmptyState>();
     widget_context.add_widget_data::<ThemeButton, EmptyState>();
     widget_context.add_widget_data::<ThemeSelector, EmptyState>();
@@ -376,7 +376,8 @@ fn startup(
             </WindowBundle>
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/demo.rs b/examples/demo.rs
index 2d63f72..0ba501e 100644
--- a/examples/demo.rs
+++ b/examples/demo.rs
@@ -22,6 +22,7 @@ impl Widget for MyWidget {}
 
 fn startup(mut commands: Commands) {
     let mut context = KayakRootContext::new();
+    context.add_plugin(KayakWidgetsContextPlugin);
     context.add_widget_system(
         MyWidget::default().get_name(),
         widget_update::<MyWidget, EmptyState>,
@@ -46,7 +47,7 @@ fn startup(mut commands: Commands) {
     });
     context.add_widget(None, app_entity);
 
-    commands.insert_resource(context);
+    commands.spawn(UICameraBundle::new(context));
 }
 
 // Note this example shows prop changing not state changing which is quite different.
diff --git a/examples/hello_world.rs b/examples/hello_world.rs
index 87cc694..e57e6f3 100644
--- a/examples/hello_world.rs
+++ b/examples/hello_world.rs
@@ -8,9 +8,8 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
         <KayakAppBundle>
@@ -23,7 +22,8 @@ fn startup(
             />
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/hello_world_no_macro.rs b/examples/hello_world_no_macro.rs
index 647e31d..1b472f8 100644
--- a/examples/hello_world_no_macro.rs
+++ b/examples/hello_world_no_macro.rs
@@ -6,8 +6,8 @@ fn startup(
     asset_server: Res<AssetServer>,
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
-    commands.spawn(UICameraBundle::new());
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
 
     let app_entity = widget_context.spawn_widget(&mut commands, None);
     // Create default app bundle
@@ -19,7 +19,7 @@ fn startup(
     let mut children = KChildren::new();
 
     // Create the text child
-    let text_entity = widget_context.spawn_widget(&mut commands, Some(app_entity));
+    let text_entity = widget_context.spawn_widget(&mut commands, None);
     commands.entity(text_entity).insert(TextWidgetBundle {
         text: TextProps {
             content: "Hello World".into(),
@@ -38,7 +38,8 @@ fn startup(
     widget_context.add_widget(None, app_entity);
 
     // Add widget context as resource.
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 fn main() {
     App::new()
diff --git a/examples/image.rs b/examples/image.rs
index 03cac2b..1b490ee 100644
--- a/examples/image.rs
+++ b/examples/image.rs
@@ -8,11 +8,10 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let image = asset_server.load("generic-rpg-vendor.png");
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
         <KayakAppBundle>
@@ -30,7 +29,8 @@ fn startup(
             />
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/main_menu.rs b/examples/main_menu.rs
index a67d67b..a1ec90c 100644
--- a/examples/main_menu.rs
+++ b/examples/main_menu.rs
@@ -122,9 +122,8 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("lato-light.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<MenuButton, ButtonState>();
     widget_context.add_widget_system(
         MenuButton::default().get_name(),
@@ -214,7 +213,7 @@ fn startup(
         </KayakAppBundle>
     }
 
-    commands.insert_resource(widget_context);
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/nine_patch.rs b/examples/nine_patch.rs
index 2ddf562..5ff1e0d 100644
--- a/examples/nine_patch.rs
+++ b/examples/nine_patch.rs
@@ -8,11 +8,10 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let image = asset_server.load("panel.png");
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
     // The border prop splits up the image into 9 quadrants like so:
@@ -52,7 +51,7 @@ fn startup(
         </KayakAppBundle>
     }
 
-    commands.insert_resource(widget_context);
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/quads.rs b/examples/quads.rs
index 5e65231..e0afabe 100644
--- a/examples/quads.rs
+++ b/examples/quads.rs
@@ -90,9 +90,9 @@ fn startup(
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
     commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_system(
         MyQuad::default().get_name(),
         widget_update::<MyQuad, EmptyState>,
@@ -125,7 +125,7 @@ fn startup(
         </KayakAppBundle>
     }
 
-    commands.insert_resource(widget_context);
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/render_target.rs b/examples/render_target.rs
index 763d5c6..3df1195 100644
--- a/examples/render_target.rs
+++ b/examples/render_target.rs
@@ -53,19 +53,8 @@ fn startup(
 
     let image_handle = images.add(image);
 
-    commands.spawn(UICameraBundle {
-        camera: Camera {
-            priority: -1,
-            target: RenderTarget::Image(image_handle.clone()),
-            ..Camera::default()
-        },
-        camera_ui: CameraUIKayak {
-            clear_color: bevy::core_pipeline::clear_color::ClearColorConfig::Default,
-        },
-        ..UICameraBundle::new()
-    });
-
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
         <KayakAppBundle
@@ -83,7 +72,18 @@ fn startup(
             />
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle {
+        camera: Camera {
+            priority: -1,
+            target: RenderTarget::Image(image_handle.clone()),
+            ..Camera::default()
+        },
+        camera_ui: CameraUIKayak {
+            clear_color: bevy::core_pipeline::clear_color::ClearColorConfig::Default,
+        },
+        ..UICameraBundle::new(widget_context)
+    });
 
     // Setup 3D scene
     // Light
diff --git a/examples/scrolling.rs b/examples/scrolling.rs
index baef12e..c4c2387 100644
--- a/examples/scrolling.rs
+++ b/examples/scrolling.rs
@@ -1,67 +1,68 @@
-use bevy::prelude::*;
-use kayak_ui::prelude::{widgets::*, *};
-
-fn startup(
-    mut commands: Commands,
-    mut font_mapping: ResMut<FontMapping>,
-    asset_server: Res<AssetServer>,
-) {
-    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
-
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
-
-    let mut widget_context = KayakRootContext::new();
-    let parent_id = None;
-
-    let lorem_ipsum = r#"
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sed tellus neque. Proin tempus ligula a mi molestie aliquam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam venenatis consequat ultricies. Sed ac orci purus. Nullam velit nisl, dapibus vel mauris id, dignissim elementum sapien. Vestibulum faucibus sapien ut erat bibendum, id lobortis nisi luctus. Mauris feugiat at lectus at pretium. Pellentesque vitae finibus ante. Nulla non ex neque. Cras varius, lorem facilisis consequat blandit, lorem mauris mollis massa, eget consectetur magna sem vel enim. Nam aliquam risus pulvinar, volutpat leo eget, eleifend urna. Suspendisse in magna sed ligula vehicula volutpat non vitae augue. Phasellus aliquam viverra consequat. Nam rhoncus molestie purus, sed laoreet neque imperdiet eget. Sed egestas metus eget sodales congue.
-                                    
- Sed vel ante placerat, posuere lacus sit amet, tempus enim. Cras ullamcorper ex vitae metus consequat, a blandit leo semper. Nunc lacinia porta massa, a tempus leo laoreet nec. Sed vel metus tincidunt, scelerisque ex sit amet, lacinia dui. In sollicitudin pulvinar odio vitae hendrerit. Maecenas mollis tempor egestas. Nulla facilisi. Praesent nisi turpis, accumsan eu lobortis vestibulum, ultrices id nibh. Suspendisse sed dui porta, mollis elit sed, ornare sem. Cras molestie est libero, quis faucibus leo semper at.
-                                    
- Nulla vel nisl rutrum, fringilla elit non, mollis odio. Donec convallis arcu neque, eget venenatis sem mattis nec. Nulla facilisi. Phasellus risus elit, vehicula sit amet risus et, sodales ultrices est. Quisque vulputate felis orci, non tristique leo faucibus in. Duis quis velit urna. Sed rhoncus dolor vel commodo aliquet. In sed tempor quam. Nunc non tempus ipsum. Praesent mi lacus, vehicula eu dolor eu, condimentum venenatis diam. In tristique ligula a ligula dictum, eu dictum lacus consectetur. Proin elementum egestas pharetra. Nunc suscipit dui ac nisl maximus, id congue velit volutpat. Etiam condimentum, mauris ac sodales tristique, est augue accumsan elit, ut luctus est mi ut urna. Mauris commodo, tortor eget gravida lacinia, leo est imperdiet arcu, a ullamcorper dui sapien eget erat.
-                                
- Vivamus pulvinar dui et elit volutpat hendrerit. Praesent luctus dolor ut rutrum finibus. Fusce ut odio ultrices, laoreet est at, condimentum turpis. Morbi at ultricies nibh. Mauris tempus imperdiet porta. Proin sit amet tincidunt eros. Quisque rutrum lacus ac est vehicula dictum. Pellentesque nec augue mi.
-                                
- Vestibulum rutrum imperdiet nisl, et consequat massa porttitor vel. Ut velit justo, vehicula a nulla eu, auctor eleifend metus. Ut egestas malesuada metus, sit amet pretium nunc commodo ac. Pellentesque gravida, nisl in faucibus volutpat, libero turpis mattis orci, vitae tincidunt ligula ligula ut tortor. Maecenas vehicula lobortis odio in molestie. Curabitur dictum elit sed arcu dictum, ut semper nunc cursus. Donec semper felis non nisl tincidunt elementum.
-    "#.to_string();
-
-    rsx! {
-        <KayakAppBundle>
-            <WindowBundle
-                window={KWindow {
-                    title: "Simple scrolling example".into(),
-                    draggable: true,
-                    initial_position: Vec2::new(10.0, 10.0),
-                    size: Vec2::new(512.0, 512.0),
-                    ..KWindow::default()
-                }}
-            >
-                <ScrollContextProviderBundle>
-                    <ScrollBoxBundle>
-                        <TextWidgetBundle
-                            text={TextProps {
-                                content: lorem_ipsum,
-                                size: 14.0,
-                                ..Default::default()
-                            }}
-                        />
-                    </ScrollBoxBundle>
-                </ScrollContextProviderBundle>
-            </WindowBundle>
-        </KayakAppBundle>
-    }
-    commands.insert_resource(widget_context);
-}
-
-fn main() {
-    App::new()
-        .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
-        .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
-        .add_plugin(KayakContextPlugin)
-        .add_plugin(KayakWidgets)
-        .add_startup_system(startup)
-        .run()
-}
+use bevy::prelude::*;
+use kayak_ui::prelude::{widgets::*, *};
+
+fn startup(
+    mut commands: Commands,
+    mut font_mapping: ResMut<FontMapping>,
+    asset_server: Res<AssetServer>,
+) {
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+
+    // Camera 2D forces a clear pass in bevy.
+    // We do this because our scene is not rendering anything else.
+    commands.spawn(Camera2dBundle::default());
+
+    let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
+    let parent_id = None;
+
+    let lorem_ipsum = r#"
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sed tellus neque. Proin tempus ligula a mi molestie aliquam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam venenatis consequat ultricies. Sed ac orci purus. Nullam velit nisl, dapibus vel mauris id, dignissim elementum sapien. Vestibulum faucibus sapien ut erat bibendum, id lobortis nisi luctus. Mauris feugiat at lectus at pretium. Pellentesque vitae finibus ante. Nulla non ex neque. Cras varius, lorem facilisis consequat blandit, lorem mauris mollis massa, eget consectetur magna sem vel enim. Nam aliquam risus pulvinar, volutpat leo eget, eleifend urna. Suspendisse in magna sed ligula vehicula volutpat non vitae augue. Phasellus aliquam viverra consequat. Nam rhoncus molestie purus, sed laoreet neque imperdiet eget. Sed egestas metus eget sodales congue.
+                                    
+ Sed vel ante placerat, posuere lacus sit amet, tempus enim. Cras ullamcorper ex vitae metus consequat, a blandit leo semper. Nunc lacinia porta massa, a tempus leo laoreet nec. Sed vel metus tincidunt, scelerisque ex sit amet, lacinia dui. In sollicitudin pulvinar odio vitae hendrerit. Maecenas mollis tempor egestas. Nulla facilisi. Praesent nisi turpis, accumsan eu lobortis vestibulum, ultrices id nibh. Suspendisse sed dui porta, mollis elit sed, ornare sem. Cras molestie est libero, quis faucibus leo semper at.
+                                    
+ Nulla vel nisl rutrum, fringilla elit non, mollis odio. Donec convallis arcu neque, eget venenatis sem mattis nec. Nulla facilisi. Phasellus risus elit, vehicula sit amet risus et, sodales ultrices est. Quisque vulputate felis orci, non tristique leo faucibus in. Duis quis velit urna. Sed rhoncus dolor vel commodo aliquet. In sed tempor quam. Nunc non tempus ipsum. Praesent mi lacus, vehicula eu dolor eu, condimentum venenatis diam. In tristique ligula a ligula dictum, eu dictum lacus consectetur. Proin elementum egestas pharetra. Nunc suscipit dui ac nisl maximus, id congue velit volutpat. Etiam condimentum, mauris ac sodales tristique, est augue accumsan elit, ut luctus est mi ut urna. Mauris commodo, tortor eget gravida lacinia, leo est imperdiet arcu, a ullamcorper dui sapien eget erat.
+                                
+ Vivamus pulvinar dui et elit volutpat hendrerit. Praesent luctus dolor ut rutrum finibus. Fusce ut odio ultrices, laoreet est at, condimentum turpis. Morbi at ultricies nibh. Mauris tempus imperdiet porta. Proin sit amet tincidunt eros. Quisque rutrum lacus ac est vehicula dictum. Pellentesque nec augue mi.
+                                
+ Vestibulum rutrum imperdiet nisl, et consequat massa porttitor vel. Ut velit justo, vehicula a nulla eu, auctor eleifend metus. Ut egestas malesuada metus, sit amet pretium nunc commodo ac. Pellentesque gravida, nisl in faucibus volutpat, libero turpis mattis orci, vitae tincidunt ligula ligula ut tortor. Maecenas vehicula lobortis odio in molestie. Curabitur dictum elit sed arcu dictum, ut semper nunc cursus. Donec semper felis non nisl tincidunt elementum.
+    "#.to_string();
+
+    rsx! {
+        <KayakAppBundle>
+            <WindowBundle
+                window={KWindow {
+                    title: "Simple scrolling example".into(),
+                    draggable: true,
+                    initial_position: Vec2::new(10.0, 10.0),
+                    size: Vec2::new(512.0, 512.0),
+                    ..KWindow::default()
+                }}
+            >
+                <ScrollContextProviderBundle>
+                    <ScrollBoxBundle>
+                        <TextWidgetBundle
+                            text={TextProps {
+                                content: lorem_ipsum,
+                                size: 14.0,
+                                ..Default::default()
+                            }}
+                        />
+                    </ScrollBoxBundle>
+                </ScrollContextProviderBundle>
+            </WindowBundle>
+        </KayakAppBundle>
+    }
+
+    commands.spawn(UICameraBundle::new(widget_context));
+}
+
+fn main() {
+    App::new()
+        .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
+        .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
+        .add_plugin(KayakContextPlugin)
+        .add_plugin(KayakWidgets)
+        .add_startup_system(startup)
+        .run()
+}
diff --git a/examples/simple_state.rs b/examples/simple_state.rs
index 8f5be82..0e2e8e2 100644
--- a/examples/simple_state.rs
+++ b/examples/simple_state.rs
@@ -88,9 +88,9 @@ fn startup(
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
     commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<CurrentCount, CurrentCountState>();
     widget_context.add_widget_system(
@@ -126,7 +126,8 @@ fn startup(
             </WindowContextProviderBundle>
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/tabs/tabs.rs b/examples/tabs/tabs.rs
index 8df527c..677c1e3 100644
--- a/examples/tabs/tabs.rs
+++ b/examples/tabs/tabs.rs
@@ -20,9 +20,9 @@ fn startup(
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
     commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<Tab, EmptyState>();
     widget_context.add_widget_data::<TabContextProvider, EmptyState>();
     widget_context.add_widget_data::<TabButton, EmptyState>();
@@ -87,7 +87,7 @@ fn startup(
         </KayakAppBundle>
     }
 
-    commands.insert_resource(widget_context);
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/text.rs b/examples/text.rs
index 3d6ae7e..8b3a414 100644
--- a/examples/text.rs
+++ b/examples/text.rs
@@ -67,9 +67,8 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<MyWidgetProps, EmptyState>();
     widget_context.add_widget_system(
@@ -80,7 +79,8 @@ fn startup(
     rsx! {
         <KayakAppBundle><MyWidgetBundle props={MyWidgetProps { foo: 0 }} /></KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn update_resource(keyboard_input: Res<Input<KeyCode>>, mut my_resource: ResMut<MyResource>) {
diff --git a/examples/text_box.rs b/examples/text_box.rs
index 812b046..4894d90 100644
--- a/examples/text_box.rs
+++ b/examples/text_box.rs
@@ -93,9 +93,9 @@ fn startup(
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
     commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
 
     widget_context.add_widget_data::<TextBoxExample, TextBoxExampleState>();
     widget_context.add_widget_system(
@@ -119,7 +119,8 @@ fn startup(
             </WindowBundle>
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/texture_atlas.rs b/examples/texture_atlas.rs
index 8d78df6..87298e0 100644
--- a/examples/texture_atlas.rs
+++ b/examples/texture_atlas.rs
@@ -8,8 +8,6 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let image_handle = asset_server.load("texture_atlas.png");
 
     //texture_atlas.png uses 16 pixel sprites and is 272x128 pixels
@@ -31,6 +29,7 @@ fn startup(
     let flower_index = columns * 5 + 15;
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
     let atlas_styles = KStyle {
@@ -69,7 +68,7 @@ fn startup(
         </KayakAppBundle>
     }
 
-    commands.insert_resource(widget_context);
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/todo/todo.rs b/examples/todo/todo.rs
index 36687d9..3072de0 100644
--- a/examples/todo/todo.rs
+++ b/examples/todo/todo.rs
@@ -57,9 +57,9 @@ fn startup(
     // Camera 2D forces a clear pass in bevy.
     // We do this because our scene is not rendering anything else.
     commands.spawn(Camera2dBundle::default());
-    commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<TodoItemsProps, EmptyState>();
     widget_context.add_widget_data::<TodoInputProps, EmptyState>();
 
@@ -97,7 +97,8 @@ fn startup(
             </WindowBundle>
         </KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/examples/vec.rs b/examples/vec.rs
index 9e412a8..4d9daf3 100644
--- a/examples/vec.rs
+++ b/examples/vec.rs
@@ -61,9 +61,8 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    commands.spawn(UICameraBundle::new());
-
     let mut widget_context = KayakRootContext::new();
+    widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<MyWidgetProps, EmptyState>();
     widget_context.add_widget_system(
@@ -74,7 +73,8 @@ fn startup(
     rsx! {
         <KayakAppBundle><MyWidgetBundle /></KayakAppBundle>
     }
-    commands.insert_resource(widget_context);
+
+    commands.spawn(UICameraBundle::new(widget_context));
 }
 
 fn main() {
diff --git a/src/calculate_nodes.rs b/src/calculate_nodes.rs
index 5f37f78..569ebfe 100644
--- a/src/calculate_nodes.rs
+++ b/src/calculate_nodes.rs
@@ -1,5 +1,5 @@
 use bevy::{
-    prelude::{Assets, Commands, Entity, Query, Res, ResMut, With},
+    prelude::{Assets, Commands, Entity, In, Query, Res, With},
     utils::HashMap,
 };
 use kayak_font::KayakFont;
@@ -15,14 +15,14 @@ use crate::{
 };
 
 pub fn calculate_nodes(
+    In(mut context): In<KayakRootContext>,
     mut commands: Commands,
-    mut context: ResMut<KayakRootContext>,
     fonts: Res<Assets<KayakFont>>,
     font_mapping: Res<FontMapping>,
     query: Query<Entity, With<DirtyNode>>,
     all_styles_query: Query<&KStyle>,
     node_query: Query<(Entity, &Node)>,
-) {
+) -> KayakRootContext {
     let mut new_nodes = HashMap::<Entity, (Node, bool)>::default();
     // This is the maximum recursion depth for this method.
     // Recursion involves recalculating layout which should be done sparingly.
@@ -39,11 +39,15 @@ pub fn calculate_nodes(
     // }
     if let Ok(tree) = context.tree.clone().try_read() {
         if tree.root_node.is_none() {
-            return;
+            return context;
         }
 
         for dirty_entity in query.iter() {
             let dirty_entity = WrappedIndex(dirty_entity);
+            if !tree.contains(dirty_entity) {
+                continue;
+            }
+
             let styles = all_styles_query
                 .get(dirty_entity.0)
                 .unwrap_or(&default_styles);
@@ -170,14 +174,15 @@ pub fn calculate_nodes(
             commands.entity(entity).insert(node);
         }
     }
+
+    context
 }
 
 pub fn calculate_layout(
+    In(context): In<KayakRootContext>,
     mut commands: Commands,
-    mut context: ResMut<KayakRootContext>,
     nodes_no_entity_query: Query<&'static Node>,
-) {
-    let context = context.as_mut();
+) -> KayakRootContext {
     if let Ok(tree) = context.tree.try_read() {
         // tree.dump();
         let node_tree = &*tree;
@@ -200,6 +205,8 @@ pub fn calculate_layout(
             }
         }
     }
+
+    context
 }
 
 fn create_primitive(
diff --git a/src/camera/camera.rs b/src/camera/camera.rs
index 4f509ab..f432ace 100644
--- a/src/camera/camera.rs
+++ b/src/camera/camera.rs
@@ -10,6 +10,8 @@ use bevy::{
     },
 };
 
+use crate::{context::KayakRootContext, event_dispatcher::EventDispatcher};
+
 use super::ortho::UIOrthographicProjection;
 
 /// Kayak UI's default UI camera.
@@ -39,17 +41,19 @@ pub struct UICameraBundle {
     pub transform: Transform,
     pub global_transform: GlobalTransform,
     pub camera_ui: CameraUIKayak,
+    pub context: KayakRootContext,
+    pub event_disaptcher: EventDispatcher,
 }
 
 impl Default for UICameraBundle {
     fn default() -> Self {
-        Self::new()
+        Self::new(KayakRootContext::default())
     }
 }
 
 impl UICameraBundle {
     pub const UI_CAMERA: &'static str = "KAYAK_UI_CAMERA";
-    pub fn new() -> Self {
+    pub fn new(kayak_root_context: KayakRootContext) -> Self {
         // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset
         // the camera's translation by far and use a right handed coordinate system
         let far = 1000.0;
@@ -85,6 +89,8 @@ impl UICameraBundle {
             camera_ui: CameraUIKayak {
                 clear_color: ClearColorConfig::None,
             },
+            context: kayak_root_context,
+            event_disaptcher: EventDispatcher::new(),
         }
     }
 }
diff --git a/src/context.rs b/src/context.rs
index f1bba5e..cada031 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock};
 use bevy::{
     ecs::{event::ManualEventReader, system::CommandQueue},
     prelude::*,
-    utils::HashMap,
+    utils::{HashMap, HashSet},
 };
 use morphorm::Hierarchy;
 
@@ -14,6 +14,7 @@ use crate::{
     context_entities::ContextEntities,
     event_dispatcher::EventDispatcher,
     focus_tree::FocusTree,
+    input::query_world,
     layout::{LayoutCache, Rect},
     layout_dispatcher::LayoutEventDispatcher,
     node::{DirtyNode, WrappedIndex},
@@ -22,7 +23,7 @@ use crate::{
     styles::KStyle,
     tree::{Change, Tree},
     widget_state::WidgetState,
-    Focusable, WindowSize,
+    Focusable, KayakUIPlugin, WindowSize,
 };
 
 /// A tag component representing when a widget has been mounted(added to the tree).
@@ -58,7 +59,7 @@ type WidgetSystems = HashMap<
 ///     }).id();
 ///     // Stores the kayak app widget in the widget context's tree.
 ///     widget_context.add_widget(None, app_entity);
-///     commands.insert_resource(widget_context);
+///     commands.spawn(UICameraBundle::new(widget_context));
 /// }
 ///
 /// fn main() {
@@ -69,7 +70,7 @@ type WidgetSystems = HashMap<
 ///     .add_startup_system(setup);
 /// }
 /// ```
-#[derive(Resource)]
+#[derive(Component)]
 pub struct KayakRootContext {
     pub tree: Arc<RwLock<Tree>>,
     pub(crate) layout_cache: Arc<RwLock<LayoutCache>>,
@@ -83,6 +84,7 @@ pub struct KayakRootContext {
     pub(crate) widget_state: WidgetState,
     pub(crate) order_tree: Arc<RwLock<Tree>>,
     pub(crate) index: Arc<RwLock<HashMap<Entity, usize>>>,
+    pub(crate) uninitilized_systems: HashSet<String>,
 }
 
 impl Default for KayakRootContext {
@@ -107,9 +109,14 @@ impl KayakRootContext {
             widget_state: Default::default(),
             index: Default::default(),
             order_tree: Default::default(),
+            uninitilized_systems: Default::default(),
         }
     }
 
+    pub fn add_plugin(&mut self, plugin: impl KayakUIPlugin) {
+        plugin.build(self)
+    }
+
     /// Get's the layout for th given widget index.
     pub(crate) fn get_layout(&self, id: &WrappedIndex) -> Option<Rect> {
         if let Ok(cache) = self.layout_cache.try_read() {
@@ -141,10 +148,12 @@ impl KayakRootContext {
         update: impl IntoSystem<(KayakWidgetContext, Entity, Entity), bool, Params>,
         render: impl IntoSystem<(KayakWidgetContext, Entity), bool, Params2>,
     ) {
+        let type_name = type_name.into();
         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));
+            .insert(type_name.clone(), (update_system, render_system));
+        self.uninitilized_systems.insert(type_name);
     }
 
     /// Let's the widget context know what data types are used for a given widget.
@@ -278,8 +287,6 @@ impl KayakRootContext {
             return vec![];
         }
 
-        // self.node_tree.dump();
-
         let render_primitives = if let Ok(mut layout_cache) = self.layout_cache.try_write() {
             recurse_node_tree_to_build_primitives(
                 &node_tree,
@@ -346,12 +353,23 @@ fn recurse_node_tree_to_build_primitives(
         };
 
         match &render_primitive {
-            RenderPrimitive::Text { content, .. } => {
+            RenderPrimitive::Text {
+                content, layout, ..
+            } => {
                 log::trace!(
-                    "Text node: {}-{} is equal to: {}",
+                    "Text node: {}-{} is equal to: {}, {:?}",
                     widget_names.get(current_node.0).unwrap().0,
                     current_node.0.index(),
                     content,
+                    layout,
+                );
+            }
+            RenderPrimitive::Clip { layout } => {
+                log::trace!(
+                    "Clip node: {}-{} is equal to: {:?}",
+                    widget_names.get(current_node.0).unwrap().0,
+                    current_node.0.index(),
+                    layout,
                 );
             }
             RenderPrimitive::Empty => {
@@ -439,84 +457,99 @@ fn recurse_node_tree_to_build_primitives(
 }
 
 fn update_widgets_sys(world: &mut World) {
-    let mut context = world.remove_resource::<KayakRootContext>().unwrap();
-    let tree_iterator = if let Ok(tree) = context.tree.read() {
-        tree.down_iter().collect::<Vec<_>>()
-    } else {
-        panic!("Failed to acquire read lock.");
-    };
-
-    // let change_tick = world.increment_change_tick();
+    let mut context_data = Vec::new();
 
-    let old_focus = if let Ok(mut focus_tree) = context.focus_tree.try_write() {
-        let current = focus_tree.current();
-        focus_tree.clear();
-        if let Ok(tree) = context.tree.read() {
-            if let Some(root_node) = tree.root_node {
-                focus_tree.add(root_node, &tree);
+    query_world::<Query<(Entity, &mut KayakRootContext)>, _, _>(
+        |mut query| {
+            for (entity, mut kayak_root_context) in query.iter_mut() {
+                context_data.push((entity, std::mem::take(&mut *kayak_root_context)));
             }
-        }
-        current
-    } else {
-        None
-    };
-
-    let mut new_ticks = HashMap::new();
-
-    // dbg!("Updating widgets!");
-    update_widgets(
+        },
         world,
-        &context.tree,
-        &context.layout_cache,
-        &mut context.systems,
-        tree_iterator,
-        &context.context_entities,
-        &context.focus_tree,
-        &context.clone_systems,
-        &context.cloned_widget_entities,
-        &context.widget_state,
-        &mut new_ticks,
-        &context.order_tree,
-        &context.index,
     );
 
-    if let Some(old_focus) = old_focus {
-        if let Ok(mut focus_tree) = context.focus_tree.try_write() {
-            if focus_tree.contains(old_focus) {
-                focus_tree.focus(old_focus);
+    for (entity, mut context) in context_data.drain(..) {
+        for system_id in context.uninitilized_systems.drain() {
+            if let Some(system) = context.systems.get_mut(&system_id) {
+                system.0.initialize(world);
+                system.1.initialize(world);
             }
         }
-    }
 
-    // dbg!("Finished updating widgets!");
-    let tick = world.read_change_tick();
+        let tree_iterator = if let Ok(tree) = context.tree.read() {
+            tree.down_iter().collect::<Vec<_>>()
+        } else {
+            panic!("Failed to acquire read lock.");
+        };
+
+        // let change_tick = world.increment_change_tick();
 
-    for (key, system) in context.systems.iter_mut() {
-        if let Some(new_tick) = new_ticks.get(key) {
-            system.0.set_last_change_tick(*new_tick);
-            system.1.set_last_change_tick(*new_tick);
+        let old_focus = if let Ok(mut focus_tree) = context.focus_tree.try_write() {
+            let current = focus_tree.current();
+            focus_tree.clear();
+            if let Ok(tree) = context.tree.read() {
+                if let Some(root_node) = tree.root_node {
+                    focus_tree.add(root_node, &tree);
+                }
+            }
+            current
         } else {
-            system.0.set_last_change_tick(tick);
-            system.1.set_last_change_tick(tick);
+            None
+        };
+
+        let mut new_ticks = HashMap::new();
+
+        // dbg!("Updating widgets!");
+        update_widgets(
+            world,
+            &context.tree,
+            &context.layout_cache,
+            &mut context.systems,
+            tree_iterator,
+            &context.context_entities,
+            &context.focus_tree,
+            &context.clone_systems,
+            &context.cloned_widget_entities,
+            &context.widget_state,
+            &mut new_ticks,
+            &context.order_tree,
+            &context.index,
+        );
+
+        if let Some(old_focus) = old_focus {
+            if let Ok(mut focus_tree) = context.focus_tree.try_write() {
+                if focus_tree.contains(old_focus) {
+                    focus_tree.focus(old_focus);
+                }
+            }
         }
-        // system.apply_buffers(world);
-    }
 
-    // Clear out indices
-    if let Ok(mut indices) = context.index.try_write() {
-        // for (entity, value) in indices.iter_mut() {
-        //     if tree.root_node.unwrap().0.id() != entity.id() {
-        //         *value = 0;
-        //     }
-        // }
-        indices.clear();
-    }
+        // dbg!("Finished updating widgets!");
+        let tick = world.read_change_tick();
 
-    // if let Ok(order_tree) = context.order_tree.try_read() {
-    //     order_tree.dump();
-    // }
+        for (key, system) in context.systems.iter_mut() {
+            if let Some(new_tick) = new_ticks.get(key) {
+                system.0.set_last_change_tick(*new_tick);
+                system.1.set_last_change_tick(*new_tick);
+            } else {
+                system.0.set_last_change_tick(tick);
+                system.1.set_last_change_tick(tick);
+            }
+            // system.apply_buffers(world);
+        }
+
+        // Clear out indices
+        if let Ok(mut indices) = context.index.try_write() {
+            // for (entity, value) in indices.iter_mut() {
+            //     if tree.root_node.unwrap().0.id() != entity.id() {
+            //         *value = 0;
+            //     }
+            // }
+            indices.clear();
+        }
 
-    world.insert_resource(context);
+        world.entity_mut(entity).insert(context);
+    }
 }
 
 fn update_widgets(
@@ -623,7 +656,7 @@ fn update_widgets(
                         tree.merge(&widget_context, *entity, diff, UPDATE_DEPTH);
 
                         // if had_removal {
-                        //     tree.dump();
+                        // tree.dump();
                         // }
 
                         for child in widget_context.child_iter(*entity) {
@@ -708,7 +741,13 @@ fn update_widget(
                 panic!("Couldn't get write lock!")
             };
 
-        let widget_update_system = &mut systems.get_mut(&widget_type).unwrap().0;
+        let widget_update_system = &mut systems
+            .get_mut(&widget_type)
+            .expect(&format!(
+                "Wasn't able to find render/update systems for widget: {}!",
+                widget_type
+            ))
+            .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);
@@ -857,16 +896,6 @@ fn update_widget(
     (widget_context, should_update_children)
 }
 
-fn init_systems(world: &mut World) {
-    let mut context = world.remove_resource::<KayakRootContext>().unwrap();
-    for system in context.systems.values_mut() {
-        system.0.initialize(world);
-        system.1.initialize(world);
-    }
-
-    world.insert_resource(context);
-}
-
 /// The default Kayak Context plugin
 /// Creates systems and resources for kayak.
 pub struct KayakContextPlugin;
@@ -877,7 +906,6 @@ pub struct CustomEventReader<T: bevy::ecs::event::Event>(pub ManualEventReader<T
 impl Plugin for KayakContextPlugin {
     fn build(&self, app: &mut App) {
         app.insert_resource(WindowSize::default())
-            .insert_resource(EventDispatcher::new())
             .insert_resource(CustomEventReader(ManualEventReader::<
                 bevy::window::CursorMoved,
             >::default()))
@@ -896,7 +924,6 @@ impl Plugin for KayakContextPlugin {
             .add_plugin(crate::camera::KayakUICameraPlugin)
             .add_plugin(crate::render::BevyKayakUIRenderPlugin)
             .register_type::<Node>()
-            .add_startup_system_to_stage(StartupStage::PostStartup, init_systems.at_end())
             .add_system_to_stage(CoreStage::Update, crate::input::process_events)
             .add_system_to_stage(CoreStage::PostUpdate, update_widgets_sys.at_start())
             .add_system_to_stage(CoreStage::PostUpdate, calculate_ui.at_end())
@@ -906,30 +933,40 @@ impl Plugin for KayakContextPlugin {
 
 fn calculate_ui(world: &mut World) {
     // dbg!("Calculating nodes!");
-    let mut node_system = IntoSystem::into_system(calculate_nodes);
-    node_system.initialize(world);
 
-    let mut layout_system = IntoSystem::into_system(calculate_layout);
-    layout_system.initialize(world);
+    let mut context_data = Vec::new();
 
-    for _ in 0..3 {
-        node_system.run((), world);
-        node_system.apply_buffers(world);
+    query_world::<Query<(Entity, &mut EventDispatcher, &mut KayakRootContext)>, _, _>(
+        |mut query| {
+            for (entity, mut event_dispatcher, mut kayak_root_context) in query.iter_mut() {
+                context_data.push((
+                    entity,
+                    std::mem::take(&mut *event_dispatcher),
+                    std::mem::take(&mut *kayak_root_context),
+                ));
+            }
+        },
+        world,
+    );
 
-        layout_system.run((), world);
-        layout_system.apply_buffers(world);
-        world.resource_scope::<KayakRootContext, _>(|world, mut context| {
-            LayoutEventDispatcher::dispatch(&mut context, world);
-        });
-    }
+    for (entity, event_dispatcher, mut context) in context_data.drain(..) {
+        let mut node_system = IntoSystem::into_system(calculate_nodes);
+        node_system.initialize(world);
+        let mut layout_system = IntoSystem::into_system(calculate_layout);
+        layout_system.initialize(world);
 
-    world.resource_scope::<KayakRootContext, _>(|world, mut context| {
-        world.resource_scope::<EventDispatcher, _>(|world, event_dispatcher| {
-            if event_dispatcher.hovered.is_none() {
-                context.current_cursor = CursorIcon::Default;
-                return;
-            }
+        for _ in 0..3 {
+            context = node_system.run(context, world);
+            node_system.apply_buffers(world);
+
+            context = layout_system.run(context, world);
+            layout_system.apply_buffers(world);
+            LayoutEventDispatcher::dispatch(&mut context, world);
+        }
 
+        if event_dispatcher.hovered.is_none() {
+            context.current_cursor = CursorIcon::Default;
+        } else {
             let hovered = event_dispatcher.hovered.unwrap();
             if let Some(entity) = world.get_entity(hovered.0) {
                 if let Some(node) = entity.get::<crate::node::Node>() {
@@ -937,15 +974,16 @@ fn calculate_ui(world: &mut World) {
                     context.current_cursor = icon.0;
                 }
             }
-        });
 
-        if let Some(ref mut windows) = world.get_resource_mut::<Windows>() {
-            if let Some(window) = windows.get_primary_mut() {
-                window.set_cursor_icon(context.current_cursor);
+            if let Some(ref mut windows) = world.get_resource_mut::<Windows>() {
+                if let Some(window) = windows.get_primary_mut() {
+                    window.set_cursor_icon(context.current_cursor);
+                }
             }
         }
-    });
 
+        world.entity_mut(entity).insert((event_dispatcher, context));
+    }
     // dbg!("Finished calculating nodes!");
 
     // dbg!("Dispatching layout events!");
diff --git a/src/event_dispatcher.rs b/src/event_dispatcher.rs
index 41d9227..92679cd 100644
--- a/src/event_dispatcher.rs
+++ b/src/event_dispatcher.rs
@@ -1,5 +1,5 @@
 use bevy::{
-    prelude::{Entity, KeyCode, Resource, World},
+    prelude::{Component, Entity, KeyCode, World},
     utils::{HashMap, HashSet},
 };
 
@@ -43,7 +43,7 @@ impl Default for EventState {
     }
 }
 
-#[derive(Resource, Debug, Clone)]
+#[derive(Component, Debug, Clone, Default)]
 pub struct EventDispatcher {
     is_mouse_pressed: bool,
     next_mouse_pressed: bool,
@@ -177,11 +177,11 @@ impl EventDispatcher {
     /// Process and dispatch a set of [InputEvents](crate::InputEvent)
     pub(crate) fn process_events(
         &mut self,
-        input_events: Vec<InputEvent>,
+        input_events: &Vec<InputEvent>,
         context: &mut KayakRootContext,
         world: &mut World,
     ) {
-        let events = { self.build_event_stream(&input_events, context, world) };
+        let events = { self.build_event_stream(input_events, context, world) };
         self.dispatch_events(events, context, world);
     }
 
diff --git a/src/input.rs b/src/input.rs
index cf6f5ee..c798500 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -105,16 +105,35 @@ pub(crate) fn process_events(world: &mut World) {
         world,
     );
 
-    world.resource_scope::<EventDispatcher, _>(|world, mut event_dispatcher| {
-        world.resource_scope::<KayakRootContext, _>(|world, mut context| {
-            event_dispatcher.process_events(input_events, &mut context, world);
-        });
-    });
+    // TODO: find a faster way of doing this.
+    let mut context_data = Vec::new();
+
+    query_world::<Query<(Entity, &mut EventDispatcher, &mut KayakRootContext)>, _, _>(
+        |mut query| {
+            for (entity, mut event_dispatcher, mut kayak_root_context) in query.iter_mut() {
+                context_data.push((
+                    entity,
+                    std::mem::take(&mut *event_dispatcher),
+                    std::mem::take(&mut *kayak_root_context),
+                ));
+            }
+        },
+        world,
+    );
+
+    for (entity, mut event_dispatcher, mut context) in context_data.drain(..) {
+        event_dispatcher.process_events(&input_events, &mut context, world);
+
+        world.entity_mut(entity).insert((event_dispatcher, context));
+    }
 }
 
-fn query_world<T: bevy::ecs::system::SystemParam + 'static, F, R>(mut f: F, world: &mut World) -> R
+pub(crate) fn query_world<T: bevy::ecs::system::SystemParam + 'static, F, R>(
+    f: F,
+    world: &mut World,
+) -> R
 where
-    F: FnMut(<T::Fetch as bevy::ecs::system::SystemParamFetch<'_, '_>>::Item) -> R,
+    F: FnOnce(<T::Fetch as bevy::ecs::system::SystemParamFetch<'_, '_>>::Item) -> R,
 {
     let mut system_state = bevy::ecs::system::SystemState::<T>::new(world);
     let r = {
diff --git a/src/lib.rs b/src/lib.rs
index 5415530..3af6415 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,6 +29,7 @@ mod widget_state;
 pub mod widgets;
 mod window_size;
 
+use context::KayakRootContext;
 pub use window_size::WindowSize;
 
 pub use camera::*;
@@ -63,3 +64,7 @@ pub mod prelude {
 }
 
 pub use focus_tree::Focusable;
+
+pub trait KayakUIPlugin {
+    fn build(&self, context: &mut KayakRootContext);
+}
diff --git a/src/render/extract.rs b/src/render/extract.rs
index 8312995..5f85576 100644
--- a/src/render/extract.rs
+++ b/src/render/extract.rs
@@ -5,7 +5,7 @@ use crate::{
     styles::Corner,
 };
 use bevy::{
-    prelude::{Assets, Color, Commands, Image, Plugin, Query, Rect, Res, Vec2},
+    prelude::{Assets, Camera, Color, Commands, Image, Plugin, Query, Rect, Res, Vec2},
     render::{Extract, RenderApp, RenderStage},
     window::Windows,
 };
@@ -31,7 +31,7 @@ impl Plugin for BevyKayakUIExtractPlugin {
 
 pub fn extract(
     mut commands: Commands,
-    context: Extract<Res<KayakRootContext>>,
+    context_query: Extract<Query<(&KayakRootContext, &Camera)>>,
     fonts: Extract<Res<Assets<KayakFont>>>,
     font_mapping: Extract<Res<FontMapping>>,
     node_query: Extract<Query<&Node>>,
@@ -39,20 +39,24 @@ pub fn extract(
     images: Extract<Res<Assets<Image>>>,
     windows: Extract<Res<Windows>>,
 ) {
-    // dbg!("STARTED");
-    let render_primitives = context.build_render_primitives(&node_query, &widget_names);
-    // dbg!("FINISHED");
-
-    let dpi = if let Some(window) = windows.get_primary() {
-        window.scale_factor() as f32
-    } else {
-        1.0
-    };
-
-    // dbg!(&render_primitives);
+    let mut render_primitives = Vec::new();
+    for (context, camera) in context_query.iter() {
+        let dpi = match &camera.target {
+            bevy::render::camera::RenderTarget::Window(window_id) => {
+                if let Some(window) = windows.get(*window_id) {
+                    window.scale_factor() as f32
+                } else {
+                    1.0
+                }
+            }
+            _ => 1.0,
+        };
+        let mut new_render_primitives = context.build_render_primitives(&node_query, &widget_names);
+        render_primitives.extend(new_render_primitives.drain(..).map(|r| (dpi, r)));
+    }
 
     let mut extracted_quads = Vec::new();
-    for render_primitive in render_primitives {
+    for (dpi, render_primitive) in render_primitives {
         match render_primitive {
             RenderPrimitive::Text { .. } => {
                 let text_quads = font::extract_texts(&render_primitive, &fonts, &font_mapping, dpi);
diff --git a/src/render/font/mod.rs b/src/render/font/mod.rs
index 39515cb..86e02cb 100644
--- a/src/render/font/mod.rs
+++ b/src/render/font/mod.rs
@@ -1,5 +1,4 @@
-use bevy::prelude::{Assets, Plugin, Res, ResMut};
-use kayak_font::KayakFont;
+use bevy::prelude::{Added, Entity, Plugin, Query, ResMut};
 
 mod extract;
 mod font_mapping;
@@ -21,13 +20,9 @@ impl Plugin for TextRendererPlugin {
 
 fn process_loaded_fonts(
     mut font_mapping: ResMut<FontMapping>,
-    _fonts: Res<Assets<KayakFont>>,
-    context_resource: Res<KayakRootContext>,
+    context_query: Query<Entity, Added<KayakRootContext>>,
 ) {
-    // if let Some(context = context_resource.as_ref() {
-    if context_resource.is_added() {
+    for _ in context_query.iter() {
         font_mapping.mark_all_as_new();
     }
-    // font_mapping.add_loaded_to_kayak(&fonts, &context);
-    // }
 }
diff --git a/src/widgets/app.rs b/src/widgets/app.rs
index 7227042..4626c2a 100644
--- a/src/widgets/app.rs
+++ b/src/widgets/app.rs
@@ -64,7 +64,6 @@ pub fn app_update(
 pub fn app_render(
     In((widget_context, entity)): In<(KayakWidgetContext, Entity)>,
     mut commands: Commands,
-    windows: Res<Windows>,
     mut query: Query<(&mut KStyle, &KChildren)>,
     camera: Query<&Camera, With<CameraUIKayak>>,
 ) -> bool {
@@ -77,12 +76,6 @@ pub fn app_render(
         }
     }
 
-    if width == 0.0 {
-        let primary_window = windows.get_primary().unwrap();
-        width = primary_window.width();
-        height = primary_window.height();
-    }
-
     if let Ok((mut app_style, children)) = query.get_mut(entity) {
         if app_style.width != StyleProp::Value(Units::Pixels(width)) {
             app_style.width = StyleProp::Value(Units::Pixels(width));
@@ -90,8 +83,8 @@ pub fn app_render(
         if app_style.height != StyleProp::Value(Units::Pixels(height)) {
             app_style.height = StyleProp::Value(Units::Pixels(height));
         }
+
         app_style.render_command = StyleProp::Value(RenderCommand::Layout);
-        // children.process(&widget_context, Some(entity));
         let parent_id = Some(entity);
         rsx! {
             <ClipBundle
diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs
index 2ab6866..96825c5 100644
--- a/src/widgets/mod.rs
+++ b/src/widgets/mod.rs
@@ -78,6 +78,7 @@ use window::window_render;
 use crate::{
     context::KayakRootContext,
     widget::{widget_update, widget_update_with_context, EmptyState, Widget},
+    KayakUIPlugin,
 };
 
 use self::window_context_provider::window_context_render;
@@ -86,103 +87,106 @@ pub struct KayakWidgets;
 
 impl Plugin for KayakWidgets {
     fn build(&self, app: &mut bevy::prelude::App) {
-        app.add_startup_system_to_stage(StartupStage::PostStartup, add_widget_systems)
-            .add_system(text_box::cursor_animation_system);
+        app.add_system(text_box::cursor_animation_system);
     }
 }
 
-fn add_widget_systems(mut context: ResMut<KayakRootContext>) {
-    context.add_widget_data::<KayakApp, EmptyState>();
-    context.add_widget_data::<KButton, ButtonState>();
-    context.add_widget_data::<TextProps, EmptyState>();
-    context.add_widget_data::<KWindow, KWindowState>();
-    context.add_widget_data::<WindowContextProvider, EmptyState>();
-    context.add_widget_data::<Background, EmptyState>();
-    context.add_widget_data::<Clip, EmptyState>();
-    context.add_widget_data::<KImage, EmptyState>();
-    context.add_widget_data::<TextureAtlasProps, 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>();
+pub struct KayakWidgetsContextPlugin;
 
-    context.add_widget_system(KayakApp::default().get_name(), app_update, app_render);
-    context.add_widget_system(
-        KButton::default().get_name(),
-        widget_update::<KButton, ButtonState>,
-        button_render,
-    );
-    context.add_widget_system(
-        TextProps::default().get_name(),
-        widget_update::<TextProps, EmptyState>,
-        text_render,
-    );
-    context.add_widget_system(
-        WindowContextProvider::default().get_name(),
-        widget_update::<WindowContextProvider, EmptyState>,
-        window_context_render,
-    );
-    context.add_widget_system(
-        KWindow::default().get_name(),
-        widget_update_with_context::<KWindow, KWindowState, WindowContext>,
-        window_render,
-    );
-    context.add_widget_system(
-        Background::default().get_name(),
-        widget_update::<Background, EmptyState>,
-        background_render,
-    );
-    context.add_widget_system(
-        Clip::default().get_name(),
-        widget_update::<Clip, EmptyState>,
-        clip_render,
-    );
-    context.add_widget_system(
-        KImage::default().get_name(),
-        widget_update::<KImage, EmptyState>,
-        image_render,
-    );
-    context.add_widget_system(
-        TextureAtlasProps::default().get_name(),
-        widget_update::<TextureAtlasProps, EmptyState>,
-        texture_atlas_render,
-    );
-    context.add_widget_system(
-        NinePatch::default().get_name(),
-        widget_update::<NinePatch, EmptyState>,
-        nine_patch_render,
-    );
-    context.add_widget_system(
-        Element::default().get_name(),
-        widget_update::<Element, EmptyState>,
-        element_render,
-    );
-    context.add_widget_system(
-        ScrollBarProps::default().get_name(),
-        widget_update_with_context::<ScrollBarProps, EmptyState, ScrollContext>,
-        scroll_bar_render,
-    );
-    context.add_widget_system(
-        ScrollContentProps::default().get_name(),
-        widget_update_with_context::<ScrollContentProps, EmptyState, ScrollContext>,
-        scroll_content_render,
-    );
-    context.add_widget_system(
-        ScrollBoxProps::default().get_name(),
-        widget_update_with_context::<ScrollBoxProps, EmptyState, ScrollContext>,
-        scroll_box_render,
-    );
-    context.add_widget_system(
-        ScrollContextProvider::default().get_name(),
-        widget_update::<ScrollContextProvider, EmptyState>,
-        scroll_context_render,
-    );
-    context.add_widget_system(
-        TextBoxProps::default().get_name(),
-        widget_update::<TextBoxProps, TextBoxState>,
-        text_box_render,
-    );
+impl KayakUIPlugin for KayakWidgetsContextPlugin {
+    fn build(&self, context: &mut KayakRootContext) {
+        context.add_widget_data::<KayakApp, EmptyState>();
+        context.add_widget_data::<KButton, ButtonState>();
+        context.add_widget_data::<TextProps, EmptyState>();
+        context.add_widget_data::<KWindow, KWindowState>();
+        context.add_widget_data::<WindowContextProvider, EmptyState>();
+        context.add_widget_data::<Background, EmptyState>();
+        context.add_widget_data::<Clip, EmptyState>();
+        context.add_widget_data::<KImage, EmptyState>();
+        context.add_widget_data::<TextureAtlasProps, 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, ButtonState>,
+            button_render,
+        );
+        context.add_widget_system(
+            TextProps::default().get_name(),
+            widget_update::<TextProps, EmptyState>,
+            text_render,
+        );
+        context.add_widget_system(
+            WindowContextProvider::default().get_name(),
+            widget_update::<WindowContextProvider, EmptyState>,
+            window_context_render,
+        );
+        context.add_widget_system(
+            KWindow::default().get_name(),
+            widget_update_with_context::<KWindow, KWindowState, WindowContext>,
+            window_render,
+        );
+        context.add_widget_system(
+            Background::default().get_name(),
+            widget_update::<Background, EmptyState>,
+            background_render,
+        );
+        context.add_widget_system(
+            Clip::default().get_name(),
+            widget_update::<Clip, EmptyState>,
+            clip_render,
+        );
+        context.add_widget_system(
+            KImage::default().get_name(),
+            widget_update::<KImage, EmptyState>,
+            image_render,
+        );
+        context.add_widget_system(
+            TextureAtlasProps::default().get_name(),
+            widget_update::<TextureAtlasProps, EmptyState>,
+            texture_atlas_render,
+        );
+        context.add_widget_system(
+            NinePatch::default().get_name(),
+            widget_update::<NinePatch, EmptyState>,
+            nine_patch_render,
+        );
+        context.add_widget_system(
+            Element::default().get_name(),
+            widget_update::<Element, EmptyState>,
+            element_render,
+        );
+        context.add_widget_system(
+            ScrollBarProps::default().get_name(),
+            widget_update_with_context::<ScrollBarProps, EmptyState, ScrollContext>,
+            scroll_bar_render,
+        );
+        context.add_widget_system(
+            ScrollContentProps::default().get_name(),
+            widget_update_with_context::<ScrollContentProps, EmptyState, ScrollContext>,
+            scroll_content_render,
+        );
+        context.add_widget_system(
+            ScrollBoxProps::default().get_name(),
+            widget_update_with_context::<ScrollBoxProps, EmptyState, ScrollContext>,
+            scroll_box_render,
+        );
+        context.add_widget_system(
+            ScrollContextProvider::default().get_name(),
+            widget_update::<ScrollContextProvider, EmptyState>,
+            scroll_context_render,
+        );
+        context.add_widget_system(
+            TextBoxProps::default().get_name(),
+            widget_update::<TextBoxProps, TextBoxState>,
+            text_box_render,
+        );
+    }
 }
-- 
GitLab