diff --git a/Cargo.toml b/Cargo.toml
index f39ff22ffcda0ef928885a1518e04f3e33359ce7..6413a505c0f78256c7d138a02170f32278cad381 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,6 @@ resolver = "2"
 authors = ["John Mitchell"]
 homepage = "https://github.com/StarArawn/kayak_ui"
 repository = "https://github.com/StarArawn/kayak_ui"
-license = "MIT OR Apache-2.0"
 license-file = "LICENSE"
 exclude = ["assets/*", "screenshots/*", "book"]
 
@@ -17,7 +16,7 @@ members = ["kayak_ui_macros", "kayak_font"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bevy = { version = "0.9", default-features = false, features = ["bevy_ui"] }
+bevy = { version = "0.10", default-features = false, features = ["bevy_render", "bevy_asset", "bevy_winit", "bevy_core_pipeline", "bevy_ui"] }
 bytemuck = "1.12"
 dashmap = "5.4"
 kayak_font = { path = "./kayak_font", version = "0.2" }
@@ -32,7 +31,8 @@ instant = "0.1"
 
 [dev-dependencies]
 fastrand = "1.8"
-bevy-inspector-egui = "0.14"
+bevy-inspector-egui = "0.18"
+bevy = { version = "0.10", default-features = true }
 
 [[example]]
 name = "tabs"
diff --git a/examples/bevy_scene.rs b/examples/bevy_scene.rs
index bf8439b69e0f8639de094acb0df5cb2941d37c06..7750894247a12d778a4bd99787adb15e9e6691c7 100644
--- a/examples/bevy_scene.rs
+++ b/examples/bevy_scene.rs
@@ -1,6 +1,7 @@
 use bevy::{
     math::{Vec3Swizzles, Vec4Swizzles},
     prelude::*,
+    window::PrimaryWindow,
 };
 use kayak_ui::prelude::{widgets::*, *};
 
@@ -40,7 +41,7 @@ fn set_active_tile_target(
     cursor: Res<Input<MouseButton>>,
     event_context: Query<&EventDispatcher, With<GameUI>>,
     camera_transform: Query<&GlobalTransform, With<WorldCamera>>,
-    windows: Res<Windows>,
+    window: Query<&Window, With<PrimaryWindow>>,
 ) {
     if !cursor.just_pressed(MouseButton::Left) {
         // Only run this system when the mouse button is clicked
@@ -62,7 +63,7 @@ fn set_active_tile_target(
     // }
     // ```
 
-    let world_pos = cursor_to_world(&windows, camera_transform.single());
+    let world_pos = cursor_to_world(window.single(), camera_transform.single());
     let tile_pos = world_to_tile(world_pos);
     let mut tile = tile.single_mut();
     tile.target = tile_pos;
@@ -83,11 +84,11 @@ fn move_ghost_tile(
     mut tile: Query<&mut Transform, With<GhostTile>>,
     mut cursor_moved: EventReader<CursorMoved>,
     camera_transform: Query<&GlobalTransform, With<WorldCamera>>,
-    windows: Res<Windows>,
+    window: Query<&Window, With<PrimaryWindow>>,
 ) {
     for _ in cursor_moved.iter() {
         if !event_context.single().contains_cursor() {
-            let world_pos = cursor_to_world(&windows, camera_transform.single());
+            let world_pos = cursor_to_world(window.single(), camera_transform.single());
             let tile_pos = world_to_tile(world_pos);
             let mut ghost = tile.single_mut();
             ghost.translation.x = tile_pos.x;
@@ -115,7 +116,6 @@ fn on_color_change(
 
 /// A system that sets up the world
 fn world_setup(mut commands: Commands, active_color: Res<ActiveColor>) {
-    commands.spawn((Camera2dBundle::default(), WorldCamera));
     commands
         .spawn(SpriteBundle {
             sprite: Sprite {
@@ -139,8 +139,7 @@ fn world_setup(mut commands: Commands, active_color: Res<ActiveColor>) {
 }
 
 /// Get the world position of the cursor in 2D space
-fn cursor_to_world(windows: &Windows, camera_transform: &GlobalTransform) -> Vec2 {
-    let window = windows.get_primary().unwrap();
+fn cursor_to_world(window: &Window, camera_transform: &GlobalTransform) -> Vec2 {
     let size = Vec2::new(window.width(), window.height());
 
     let mut pos = window.cursor_position().unwrap_or_default();
@@ -172,16 +171,22 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    // The UI Camera and the world camera are the same.
+    // CameraUIKayak is used to tell kayak which camera should render UI.
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak, WorldCamera))
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
 
     let handle_change_color = OnEvent::new(
         move |In((event_dispatcher_context, _, event, _)): In<(
             EventDispatcherContext,
             WidgetState,
-            Event,
+            KEvent,
             Entity,
         )>,
               mut active_color: ResMut<ActiveColor>| {
@@ -255,7 +260,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn((UICameraBundle::new(widget_context), GameUI));
+    commands.spawn((widget_context, EventDispatcher::default(), GameUI));
 }
 
 fn main() {
diff --git a/examples/clipping.rs b/examples/clipping.rs
index 9eeb05889354fa863a2d7b63766892ccd9a29d31..36203347a6d4db0e83e6327e7f172fa7f84489c9 100644
--- a/examples/clipping.rs
+++ b/examples/clipping.rs
@@ -6,11 +6,15 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
     let image = asset_server.load("panel.png");
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
@@ -56,7 +60,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sed tellus neque.
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/conditional_widget.rs b/examples/conditional_widget.rs
index 1791f087cf45762443f3032586ee3d1355cb1c98..20d9c341212cfaac5062d668f750e267d59cd7b0 100644
--- a/examples/conditional_widget.rs
+++ b/examples/conditional_widget.rs
@@ -48,7 +48,7 @@ fn my_widget_render(
                         text: "Show Window".into(),
                     }}
                     on_event={OnEvent::new(
-                        move |In((event_dispatcher_context, _, mut event, _entity)): In<(EventDispatcherContext, WidgetState, Event, Entity)>,
+                        move |In((event_dispatcher_context, _, mut event, _entity)): In<(EventDispatcherContext, WidgetState, KEvent, Entity)>,
                             mut query: Query<&mut MyWidgetState>| {
                             event.prevent_default();
                             event.stop_propagation();
@@ -78,7 +78,7 @@ fn my_widget_render(
                             <KButtonBundle
                                 button={KButton { text: "Hide Window".into(), ..Default::default() }}
                                 on_event={OnEvent::new(
-                                    move |In((event_dispatcher_context, _, mut event, _entity)): In<(EventDispatcherContext, WidgetState, Event, Entity)>,
+                                    move |In((event_dispatcher_context, _, mut event, _entity)): In<(EventDispatcherContext, WidgetState, KEvent, Entity)>,
                                         mut query: Query<&mut MyWidgetState>| {
                                         match event.event_type {
                                             EventType::Click(..) => {
@@ -109,13 +109,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("lato-light.kttf"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<MyWidget, MyWidgetState>();
@@ -130,7 +130,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/context.rs b/examples/context.rs
index a712519772efeb7726587817b1727e9256014ac3..9c4603c16fe7908ba60fd7b823e4674537489946 100644
--- a/examples/context.rs
+++ b/examples/context.rs
@@ -104,7 +104,7 @@ fn update_theme_button(
                             move |In((event_dispatcher_context, _, event, _entity)): In<(
                                 EventDispatcherContext,
                                 WidgetState,
-                                Event,
+                                KEvent,
                                 Entity,
                             )>,
                             query: Query<&ThemeButton>,
@@ -325,13 +325,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<ThemeDemo, EmptyState>();
     widget_context.add_widget_data::<ThemeButton, EmptyState>();
@@ -377,7 +377,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/demo.rs b/examples/demo.rs
index 0ba501e2ec1b438fcfab777ade91007f1ae7384e..0d562a49bf41bc761f641ea04122782a6d4da336 100644
--- a/examples/demo.rs
+++ b/examples/demo.rs
@@ -21,7 +21,11 @@ fn my_widget_1_render(
 impl Widget for MyWidget {}
 
 fn startup(mut commands: Commands) {
-    let mut context = KayakRootContext::new();
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
+    let mut context = KayakRootContext::new(camera_entity);
     context.add_plugin(KayakWidgetsContextPlugin);
     context.add_widget_system(
         MyWidget::default().get_name(),
@@ -47,7 +51,7 @@ fn startup(mut commands: Commands) {
     });
     context.add_widget(None, app_entity);
 
-    commands.spawn(UICameraBundle::new(context));
+    commands.spawn((context, EventDispatcher::default()));
 }
 
 // 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 caf0660553c22cf932aa72ebfb63fcb1eafefffc..188504eb9d5bc0316036b79bbd7c15951b9175c8 100644
--- a/examples/hello_world.rs
+++ b/examples/hello_world.rs
@@ -1,14 +1,23 @@
 use bevy::prelude::*;
-use kayak_ui::prelude::{widgets::*, *};
+use kayak_ui::{
+    prelude::{widgets::*, *},
+    CameraUIKayak,
+};
 
 fn startup(
     mut commands: Commands,
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn(Camera2dBundle::default())
+        .insert(CameraUIKayak)
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+    // font_mapping.force_subpixel(&asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
@@ -23,7 +32,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/hello_world_no_macro.rs b/examples/hello_world_no_macro.rs
index 1b472f8eb962ed39ece9044244278655b1d0005b..7216668abb4043f7a24c896ab08fc824fb5149b2 100644
--- a/examples/hello_world_no_macro.rs
+++ b/examples/hello_world_no_macro.rs
@@ -5,8 +5,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn(Camera2dBundle::default())
+        .insert(CameraUIKayak)
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
 
     let app_entity = widget_context.spawn_widget(&mut commands, None);
@@ -39,7 +44,7 @@ fn startup(
 
     // Add widget context as resource.
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 fn main() {
     App::new()
diff --git a/examples/image.rs b/examples/image.rs
index ae984f6ebe19cee77666e543b6127fd89889414d..40c7edba142927b072af1cfeaf7e3963e994ba5b 100644
--- a/examples/image.rs
+++ b/examples/image.rs
@@ -6,11 +6,15 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
     let image = asset_server.load("generic-rpg-vendor.png");
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
@@ -30,7 +34,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/layout.rs b/examples/layout.rs
index 6fdc489262656d4c4e1dc796f472809824ce5572..f2a0e842befff80835b0279686ac37c149352099 100644
--- a/examples/layout.rs
+++ b/examples/layout.rs
@@ -6,13 +6,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
@@ -130,7 +130,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/main_menu.rs b/examples/main_menu.rs
index 40949d683ca0689499637a650580f8fef8e79df8..4f2d649fc3baa96b580a3b332b0b591368ae1996 100644
--- a/examples/main_menu.rs
+++ b/examples/main_menu.rs
@@ -49,7 +49,7 @@ fn menu_button_render(
         move |In((event_dispatcher_context, _, mut event, _entity)): In<(
             EventDispatcherContext,
             WidgetState,
-            Event,
+            KEvent,
             Entity,
         )>,
               mut query: Query<&mut ButtonState>| {
@@ -123,9 +123,13 @@ fn startup(
     asset_server: Res<AssetServer>,
     mut preload_resource: ResMut<PreloadResource>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<MenuButton, ButtonState>();
     widget_context.add_widget_system(
@@ -151,7 +155,7 @@ fn startup(
         move |In((event_dispatcher_context, _, event, _entity)): In<(
             EventDispatcherContext,
             WidgetState,
-            Event,
+            KEvent,
             Entity,
         )>,
               mut exit: EventWriter<AppExit>| {
@@ -218,7 +222,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/multi_context.rs b/examples/multi_context.rs
deleted file mode 100644
index 8b137891791fe96927ad78e64b0aad7bded08bdc..0000000000000000000000000000000000000000
--- a/examples/multi_context.rs
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/examples/nine_patch.rs b/examples/nine_patch.rs
index 1aeeadaa5adaf3abacb88cfa9ebdf8ea3f856847..ea211a725baacca64ccb2df3f49c97496d85a64a 100644
--- a/examples/nine_patch.rs
+++ b/examples/nine_patch.rs
@@ -6,11 +6,15 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
     let image = asset_server.load("panel.png");
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
@@ -51,7 +55,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/quads.rs b/examples/quads.rs
index 33216d627d9568f50e24debf88b7025ce2b9c690..771b6a211c0e59a55c5e2d83180a5e40f54bad0a 100644
--- a/examples/quads.rs
+++ b/examples/quads.rs
@@ -39,7 +39,7 @@ fn my_quad_update(
             move |In((event_dispatcher_context, _, mut event, entity)): In<(
                 EventDispatcherContext,
                 WidgetState,
-                Event,
+                KEvent,
                 Entity,
             )>,
                   mut query: Query<(&mut KStyle, &MyQuad)>| {
@@ -94,13 +94,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_system(
         MyQuad::default().get_name(),
@@ -134,7 +134,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/render_target.rs b/examples/render_target.rs
index d17bb3ba89908ac328ccd59f7c5ae8da88b5e3c3..e6ee73f9e5a4af1955cd62664ed9c68ddfc198c6 100644
--- a/examples/render_target.rs
+++ b/examples/render_target.rs
@@ -41,6 +41,7 @@ fn startup(
             label: None,
             size,
             dimension: TextureDimension::D2,
+            view_formats: &[TextureFormat::Bgra8UnormSrgb],
             format: TextureFormat::Bgra8UnormSrgb,
             mip_level_count: 1,
             sample_count: 1,
@@ -56,7 +57,22 @@ fn startup(
 
     let image_handle = images.add(image);
 
-    let mut widget_context = KayakRootContext::new();
+    let camera_entity = commands
+        .spawn(Camera2dBundle {
+            camera: Camera {
+                priority: -1,
+                target: RenderTarget::Image(image_handle.clone()),
+                ..Camera::default()
+            },
+            camera_2d: Camera2d {
+                clear_color: bevy::core_pipeline::clear_color::ClearColorConfig::Default,
+            },
+            ..Default::default()
+        })
+        .insert(CameraUIKayak)
+        .id();
+
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
@@ -81,18 +97,7 @@ fn startup(
             />
         </KayakAppBundle>
     };
-
-    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)
-    });
+    commands.spawn((widget_context, EventDispatcher::default()));
 
     // Setup 3D scene
     // Light
@@ -127,14 +132,17 @@ fn startup(
         .insert(MainPassCube);
 
     // The main pass camera.
-    commands.spawn(Camera3dBundle {
-        transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0))
-            .looking_at(Vec3::default(), Vec3::Y),
-        ..default()
-    });
+    let camera_entity = commands
+        .spawn(Camera3dBundle {
+            transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0))
+                .looking_at(Vec3::default(), Vec3::Y),
+            ..default()
+        })
+        .insert(CameraUIKayak)
+        .id();
 
     // Spawn another UI in 2D space!
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
@@ -148,7 +156,7 @@ fn startup(
             />
         </KayakAppBundle>
     };
-    commands.spawn((UICameraBundle::new(widget_context), MainUI));
+    commands.spawn((widget_context, EventDispatcher::default(), MainUI));
 }
 
 /// Rotates the outer cube (main pass)
diff --git a/examples/scrolling.rs b/examples/scrolling.rs
index cac9c08d53e7aae9a0a4b3ff770dea080a243241..d77fa08a432ae3675ba93899b6bd621863c8c9b1 100644
--- a/examples/scrolling.rs
+++ b/examples/scrolling.rs
@@ -6,6 +6,10 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     let font_asset = asset_server.load("roboto.kayak_font");
     font_mapping.set_default(font_asset.clone());
 
@@ -14,11 +18,7 @@ fn startup(
     // will be ignored if this setting is used.
     font_mapping.force_subpixel(&font_asset);
 
-    // 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();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
@@ -60,7 +60,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sed tellus neque.
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/simple_state.rs b/examples/simple_state.rs
index 2e27896d14c468df56b2b3aae131db2160cbb8e2..82e2c28ab7b8c922e73582a5045b804527e0f494 100644
--- a/examples/simple_state.rs
+++ b/examples/simple_state.rs
@@ -57,7 +57,7 @@ fn current_count_render(
                         ..Default::default()
                     }}
                     on_event={OnEvent::new(
-                        move |In((event_dispatcher_context, _, mut event, _entity)): In<(EventDispatcherContext, WidgetState, Event, Entity)>,
+                        move |In((event_dispatcher_context, _, mut event, _entity)): In<(EventDispatcherContext, WidgetState, KEvent, Entity)>,
                             mut query: Query<&mut CurrentCountState>| {
                             match event.event_type {
                                 EventType::Click(..) => {
@@ -85,13 +85,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("lato-light.kttf"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("lato-light.kttf"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<CurrentCount, CurrentCountState>();
@@ -129,7 +129,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/tabs/tab_button.rs b/examples/tabs/tab_button.rs
index 2741684da64753d9b4660908fb7a0e5897b47800..811a515f7854e1e15d12a20d120ffbcdd9c57a88 100644
--- a/examples/tabs/tab_button.rs
+++ b/examples/tabs/tab_button.rs
@@ -1,7 +1,7 @@
 use bevy::prelude::{Bundle, Color, Commands, Component, Entity, In, Query};
 use kayak_ui::{
     prelude::{
-        rsx, widgets::KButtonBundle, Corner, Event, EventDispatcherContext, EventType, KStyle,
+        rsx, widgets::KButtonBundle, Corner, EventDispatcherContext, EventType, KEvent, KStyle,
         KayakWidgetContext, OnEvent, StyleProp, Units, Widget, WidgetName, WidgetState,
     },
     widgets::KButton,
@@ -57,7 +57,7 @@ pub fn tab_button_render(
                 move |In((event_dispatcher_context, _, event, _entity)): In<(
                     EventDispatcherContext,
                     WidgetState,
-                    Event,
+                    KEvent,
                     Entity,
                 )>,
                       mut query: Query<&mut TabContext>| {
diff --git a/examples/tabs/tabs.rs b/examples/tabs/tabs.rs
index 2539aecd9720908d149348c467003415284a2210..9ecb4b53a67c422678a65e0c1255a5cd25c1d3b8 100644
--- a/examples/tabs/tabs.rs
+++ b/examples/tabs/tabs.rs
@@ -15,13 +15,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<Tab, EmptyState>();
     widget_context.add_widget_data::<TabContextProvider, EmptyState>();
@@ -87,7 +87,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/test_no_startup.rs b/examples/test_no_startup.rs
index 060081ee9ab9b6337605abc4f220d4ebcf4b9460..503a4ed738ae95249df095724729591ecabeb240 100644
--- a/examples/test_no_startup.rs
+++ b/examples/test_no_startup.rs
@@ -1,15 +1,15 @@
 use bevy::prelude::*;
 use kayak_ui::prelude::{widgets::*, *};
 
-#[derive(Default, Clone, Copy, PartialEq, Hash, Eq, Debug)]
+#[derive(Default, Clone, Copy, PartialEq, Hash, Eq, Debug, States)]
 pub enum GameState {
     #[default]
     First,
     Second,
 }
 
-fn first_sys(mut state: ResMut<State<GameState>>) {
-    state.overwrite_replace(GameState::Second).unwrap();
+fn first_sys(mut state: ResMut<NextState<GameState>>) {
+    state.set(GameState::Second);
 }
 
 fn second_sys(
@@ -17,9 +17,13 @@ fn second_sys(
     asset_server: Res<AssetServer>,
     mut font_mapping: ResMut<FontMapping>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     rsx! {
@@ -34,7 +38,7 @@ fn second_sys(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
@@ -42,8 +46,8 @@ fn main() {
         .add_plugins(DefaultPlugins)
         .add_plugin(KayakContextPlugin)
         .add_plugin(KayakWidgets)
-        .add_state(GameState::First)
-        .add_system_set(SystemSet::on_enter(GameState::First).with_system(first_sys))
-        .add_system_set(SystemSet::on_enter(GameState::Second).with_system(second_sys))
-        .run()
+        .add_state::<GameState>()
+        .add_system(first_sys.in_schedule(OnEnter(GameState::First)))
+        .add_system(second_sys.in_schedule(OnEnter(GameState::Second)))
+        .run();
 }
diff --git a/examples/text.rs b/examples/text.rs
index fa49287c6f47b86904eb8466d09b5a13c6af626d..496023d6d5d3fc8ea978f5c25a687ae311360fe3 100644
--- a/examples/text.rs
+++ b/examples/text.rs
@@ -73,13 +73,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<MyWidgetProps, EmptyState>();
@@ -92,7 +92,7 @@ fn startup(
         <KayakAppBundle><MyWidgetBundle props={MyWidgetProps { foo: 0 }} /></KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 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 72b273156600b77fc3f2ef3436bca5bb6a02d0b0..edb8e9f48deb69bf07f60bd2991ca9fd53a29ff2 100644
--- a/examples/text_box.rs
+++ b/examples/text_box.rs
@@ -88,13 +88,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
 
     widget_context.add_widget_data::<TextBoxExample, TextBoxExampleState>();
@@ -120,7 +120,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/texture_atlas.rs b/examples/texture_atlas.rs
index d4e311ae9fa9825ef10fe6e3659aefc5d7239ff9..69f7d7d99ae1009bb6e87bed46684f40f0612fde 100644
--- a/examples/texture_atlas.rs
+++ b/examples/texture_atlas.rs
@@ -6,6 +6,10 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
     let image_handle = asset_server.load("texture_atlas.png");
@@ -28,7 +32,7 @@ fn startup(
     //The flower is in the 6(-1) row and 15 collumn
     let flower_index = columns * 5 + 15;
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
 
@@ -68,7 +72,7 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/examples/todo/input.rs b/examples/todo/input.rs
index 38b2da700a6496d85b156601cb60c5bb3bebd66f..37017d93eba74b1b6765ae853405db4513cf9f38 100644
--- a/examples/todo/input.rs
+++ b/examples/todo/input.rs
@@ -52,7 +52,7 @@ pub fn render_todo_input(
         move |In((event_dispatcher_context, _, event, _)): In<(
             EventDispatcherContext,
             WidgetState,
-            Event,
+            KEvent,
             Entity,
         )>,
               mut todo_list: ResMut<TodoList>| {
diff --git a/examples/todo/items.rs b/examples/todo/items.rs
index 8892ece69b5ed7c0f03d91931dcb48e62a4477db..d7adac9023e02f21756864ebf565b8191061ad1d 100644
--- a/examples/todo/items.rs
+++ b/examples/todo/items.rs
@@ -50,7 +50,7 @@ pub fn render_todo_items(
                     move |In((event_dispatcher_context, _, event, _)): In<(
                         EventDispatcherContext,
                         WidgetState,
-                        Event,
+                        KEvent,
                         Entity,
                     )>,
                         mut todo_list: ResMut<TodoList>,| {
diff --git a/examples/todo/todo.rs b/examples/todo/todo.rs
index 4f62b8823c5794297a8394d45e1e86dc80e23152..66b12c2b7d34fd2acc04cfdb90424178519f2af1 100644
--- a/examples/todo/todo.rs
+++ b/examples/todo/todo.rs
@@ -1,5 +1,5 @@
 use bevy::prelude::*;
-use bevy_inspector_egui::WorldInspectorPlugin;
+use bevy_inspector_egui::quick::WorldInspectorPlugin;
 use kayak_ui::prelude::{widgets::*, *};
 
 mod input;
@@ -53,13 +53,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
-    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
 
-    // Camera 2D forces a clear pass in bevy.
-    // We do this because our scene is not rendering anything else.
-    commands.spawn(Camera2dBundle::default());
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     widget_context.add_widget_data::<TodoItemsProps, EmptyState>();
     widget_context.add_widget_data::<TodoInputProps, EmptyState>();
@@ -99,17 +99,17 @@ fn startup(
         </KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
     App::new()
         .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
         .add_plugins(DefaultPlugins)
-        .add_plugin(WorldInspectorPlugin::new())
+        .add_plugin(WorldInspectorPlugin::default())
         .add_plugin(KayakContextPlugin)
         .add_plugin(KayakWidgets)
-        .insert_non_send_resource(TodoList::new())
+        .insert_resource(TodoList::new())
         .add_startup_system(startup)
         .run()
 }
diff --git a/examples/vec.rs b/examples/vec.rs
index fa362a71ca9d2e76f780a8cbab998a538b3c030f..459e579371b2246ae5614c8253f969aa401ca12f 100644
--- a/examples/vec.rs
+++ b/examples/vec.rs
@@ -57,9 +57,13 @@ fn startup(
     mut font_mapping: ResMut<FontMapping>,
     asset_server: Res<AssetServer>,
 ) {
+    let camera_entity = commands
+        .spawn((Camera2dBundle::default(), CameraUIKayak))
+        .id();
+
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
-    let mut widget_context = KayakRootContext::new();
+    let mut widget_context = KayakRootContext::new(camera_entity);
     widget_context.add_plugin(KayakWidgetsContextPlugin);
     let parent_id = None;
     widget_context.add_widget_data::<MyWidgetProps, EmptyState>();
@@ -72,7 +76,7 @@ fn startup(
         <KayakAppBundle><MyWidgetBundle /></KayakAppBundle>
     };
 
-    commands.spawn(UICameraBundle::new(widget_context));
+    commands.spawn((widget_context, EventDispatcher::default()));
 }
 
 fn main() {
diff --git a/kayak_font/Cargo.toml b/kayak_font/Cargo.toml
index f219c8f380491b504034586481ca2df3e6b7fdf6..08026172e2d7970691a159ee9303a6cb82aeab79 100644
--- a/kayak_font/Cargo.toml
+++ b/kayak_font/Cargo.toml
@@ -7,7 +7,6 @@ resolver = "2"
 authors = ["John Mitchell"]
 homepage = "https://github.com/StarArawn/kayak_ui"
 repository = "https://github.com/StarArawn/kayak_ui"
-license = "MIT OR Apache-2.0"
 license-file = "../LICENSE"
 exclude = ["assets/*"]
 
@@ -28,8 +27,8 @@ image = "0.24"
 # Provides UAX #14 line break segmentation
 xi-unicode = "0.3"
 
-bevy = { version = "0.9", optional = true, default-features = false, features = ["bevy_asset", "bevy_render", "bevy_core_pipeline"] }
+bevy = { version = "0.10", optional = true, default-features = false, features = ["bevy_asset", "bevy_render", "bevy_core_pipeline"] }
 
 [dev-dependencies]
-bevy = { version = "0.9" }
+bevy = { version = "0.10" }
 bytemuck = "1.12.0"
diff --git a/kayak_font/src/bevy/mod.rs b/kayak_font/src/bevy/mod.rs
index d2140100b608b146a22422944f864ef9ec263c01..97da83234b987dc4c5a330f4a1d4382327641f8a 100644
--- a/kayak_font/src/bevy/mod.rs
+++ b/kayak_font/src/bevy/mod.rs
@@ -11,8 +11,8 @@ mod loader;
 mod renderer;
 
 mod plugin {
-    use bevy::prelude::{AddAsset, Plugin};
-    use bevy::render::{RenderApp, RenderStage};
+    use bevy::prelude::{AddAsset, IntoSystemAppConfig, IntoSystemConfig, Plugin};
+    use bevy::render::{ExtractSchedule, RenderApp, RenderSet};
 
     use crate::bevy::font_texture::init_font_texture;
     use crate::KayakFont;
@@ -32,8 +32,8 @@ mod plugin {
             render_app
                 .init_resource::<FontTextureCache>()
                 .init_resource::<ExtractedFonts>()
-                .add_system_to_stage(RenderStage::Extract, extract_fonts)
-                .add_system_to_stage(RenderStage::Prepare, prepare_fonts);
+                .add_system(extract_fonts.in_schedule(ExtractSchedule))
+                .add_system(prepare_fonts.in_set(RenderSet::Prepare));
         }
     }
 }
diff --git a/kayak_font/src/bevy/renderer/font_texture_cache.rs b/kayak_font/src/bevy/renderer/font_texture_cache.rs
index ec1baecfc02a7489c1f2b4f9e5e67b783ea1f21c..eb7e153e65287f93986dc683719a6354395212e4 100644
--- a/kayak_font/src/bevy/renderer/font_texture_cache.rs
+++ b/kayak_font/src/bevy/renderer/font_texture_cache.rs
@@ -126,6 +126,7 @@ impl FontTextureCache {
             mip_level_count: 1,
             sample_count: 1,
             dimension: TextureDimension::D2,
+            view_formats: &[format],
             format,
             usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
         };
@@ -163,6 +164,7 @@ impl FontTextureCache {
             texture,
             sampler,
             texture_view,
+            mip_level_count: 1,
             size: Vec2 {
                 x: size.0 as f32,
                 y: size.1 as f32,
@@ -184,6 +186,7 @@ impl FontTextureCache {
             mip_level_count: 1,
             sample_count: 1,
             dimension: TextureDimension::D2,
+            view_formats: &[TextureFormat::Rgba8Unorm],
             format: TextureFormat::Rgba8Unorm,
             usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
         };
@@ -208,6 +211,7 @@ impl FontTextureCache {
             texture,
             sampler,
             texture_view,
+            mip_level_count: 1,
             size: Vec2 { x: 1.0, y: 1.0 },
             texture_format: TextureFormat::Rgba8Unorm,
         };
diff --git a/kayak_ui_macros/Cargo.toml b/kayak_ui_macros/Cargo.toml
index 9be77cd90343b44f91567a30049ddfee3119611b..155a45fec4ef267c35f6eaf1c06a44437483f484 100644
--- a/kayak_ui_macros/Cargo.toml
+++ b/kayak_ui_macros/Cargo.toml
@@ -7,7 +7,6 @@ resolver = "2"
 authors = ["John Mitchell"]
 homepage = "https://github.com/StarArawn/kayak_ui"
 repository = "https://github.com/StarArawn/kayak_ui"
-license = "MIT OR Apache-2.0"
 license-file = "../LICENSE"
 
 [lib]
diff --git a/src/camera/camera.rs b/src/camera/camera.rs
index f432acedb4b8a8aa7a0ea9deb165dd981dcb0c70..c9ca4a8ec0bd51ce383bdab54889f6085f217ee2 100644
--- a/src/camera/camera.rs
+++ b/src/camera/camera.rs
@@ -1,9 +1,8 @@
 use bevy::{
     core_pipeline::clear_color::ClearColorConfig,
-    ecs::query::QueryItem,
-    prelude::{Bundle, Component, GlobalTransform, Transform, With},
+    prelude::{Bundle, Component, GlobalTransform, Transform, Vec2},
     render::{
-        camera::{Camera, CameraProjection, CameraRenderGraph, WindowOrigin},
+        camera::{Camera, CameraProjection, CameraRenderGraph},
         extract_component::ExtractComponent,
         primitives::Frustum,
         view::VisibleEntities,
@@ -15,20 +14,11 @@ use crate::{context::KayakRootContext, event_dispatcher::EventDispatcher};
 use super::ortho::UIOrthographicProjection;
 
 /// Kayak UI's default UI camera.
-#[derive(Component, Clone, Default)]
+#[derive(Component, ExtractComponent, Clone, Default)]
 pub struct CameraUIKayak {
     pub clear_color: ClearColorConfig,
 }
 
-impl ExtractComponent for CameraUIKayak {
-    type Query = &'static Self;
-    type Filter = With<Camera>;
-
-    fn extract_component(item: QueryItem<Self::Query>) -> Self {
-        item.clone()
-    }
-}
-
 /// Kayak UI's default UI camera bundle.
 #[derive(Bundle)]
 pub struct UICameraBundle {
@@ -60,7 +50,7 @@ impl UICameraBundle {
 
         let orthographic_projection = UIOrthographicProjection {
             far,
-            window_origin: WindowOrigin::BottomLeft,
+            window_origin: Vec2::new(0.0, 0.0),
             ..Default::default()
         };
 
@@ -68,15 +58,10 @@ impl UICameraBundle {
 
         let view_projection =
             orthographic_projection.get_projection_matrix() * transform.compute_matrix().inverse();
-        let frustum = Frustum::from_view_projection(
-            &view_projection,
-            &transform.translation,
-            &transform.back(),
-            orthographic_projection.far(),
-        );
+        let frustum = Frustum::from_view_projection(&view_projection);
         UICameraBundle {
             camera: Camera {
-                priority: isize::MAX - 1,
+                order: isize::MAX - 1,
                 ..Default::default()
             },
             camera_render_graph: CameraRenderGraph::new(bevy::core_pipeline::core_2d::graph::NAME),
diff --git a/src/camera/mod.rs b/src/camera/mod.rs
index 60e86c38d313c7d30e88d0dc490618246882de3e..f260d649c4ff1ded0f0a3befe5b44e5f7697b1b2 100644
--- a/src/camera/mod.rs
+++ b/src/camera/mod.rs
@@ -1,23 +1,24 @@
-use bevy::{
-    prelude::{CoreStage, Plugin},
-    render::{camera::CameraProjectionPlugin, extract_component::ExtractComponentPlugin},
-};
-
-mod camera;
-mod ortho;
-
-pub use camera::{CameraUIKayak, UICameraBundle};
-pub(crate) use ortho::UIOrthographicProjection;
-
-pub struct KayakUICameraPlugin;
-
-impl Plugin for KayakUICameraPlugin {
-    fn build(&self, app: &mut bevy::prelude::App) {
-        app.add_system_to_stage(
-            CoreStage::PostUpdate,
-            bevy::render::camera::camera_system::<UIOrthographicProjection>,
-        )
-        .add_plugin(CameraProjectionPlugin::<UIOrthographicProjection>::default())
-        .add_plugin(ExtractComponentPlugin::<CameraUIKayak>::default());
-    }
-}
+use bevy::{
+    ecs::query::QueryItem,
+    prelude::*,
+    render::extract_component::{ExtractComponent, ExtractComponentPlugin},
+};
+
+#[derive(Component, Default, Debug, Clone, Copy)]
+pub struct CameraUIKayak;
+impl ExtractComponent for CameraUIKayak {
+    type Query = &'static Self;
+    type Filter = With<Camera>;
+    type Out = CameraUIKayak;
+
+    fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> {
+        Some(item.clone())
+    }
+}
+
+pub struct KayakUICameraPlugin;
+impl Plugin for KayakUICameraPlugin {
+    fn build(&self, app: &mut bevy::prelude::App) {
+        app.add_plugin(ExtractComponentPlugin::<CameraUIKayak>::default());
+    }
+}
diff --git a/src/camera/ortho.rs b/src/camera/ortho.rs
index bb7a800f9c75ac5010c0f9ee193d6fc8dbd578b4..cd6d0244fd5d21d1b4eda58faf16f12e21c8b462 100644
--- a/src/camera/ortho.rs
+++ b/src/camera/ortho.rs
@@ -1,9 +1,9 @@
 use bevy::ecs::reflect::ReflectComponent;
-use bevy::prelude::Component;
+use bevy::prelude::{Component, Vec2};
 use bevy::{
     math::Mat4,
     reflect::Reflect,
-    render::camera::{CameraProjection, ScalingMode, WindowOrigin},
+    render::camera::{CameraProjection, ScalingMode},
 };
 
 /// Kayak UI's default orthographic projection matrix
@@ -19,7 +19,7 @@ pub struct UIOrthographicProjection {
     pub top: f32,
     pub near: f32,
     pub far: f32,
-    pub window_origin: WindowOrigin,
+    pub window_origin: Vec2,
     pub scaling_mode: ScalingMode,
     pub scale: f32,
 }
@@ -39,8 +39,11 @@ impl CameraProjection for UIOrthographicProjection {
     }
 
     fn update(&mut self, width: f32, height: f32) {
-        match (&self.scaling_mode, &self.window_origin) {
-            (ScalingMode::WindowSize, WindowOrigin::BottomLeft) => {
+        match &self.scaling_mode {
+            ScalingMode::WindowSize(scale) => {
+                if *scale != 1.0 || self.window_origin != Vec2::ZERO {
+                    return;
+                }
                 self.left = 0.0;
                 self.right = width;
                 self.top = 0.0;
@@ -64,8 +67,8 @@ impl Default for UIOrthographicProjection {
             top: 1.0,
             near: 0.0,
             far: 1000.0,
-            window_origin: WindowOrigin::Center,
-            scaling_mode: ScalingMode::WindowSize,
+            window_origin: Vec2::new(0.5, 0.5),
+            scaling_mode: ScalingMode::WindowSize(1.0),
             scale: 1.0,
         }
     }
diff --git a/src/context.rs b/src/context.rs
index ac9b8b8e36bdf32896d70b976966ce47c12eb691..3b19a7f35783feee29afd7295e6e9721c6c36403 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -4,6 +4,7 @@ use bevy::{
     ecs::{event::ManualEventReader, system::CommandQueue},
     prelude::*,
     utils::{HashMap, HashSet},
+    window::PrimaryWindow,
 };
 use morphorm::Hierarchy;
 
@@ -18,7 +19,7 @@ use crate::{
     input::query_world,
     layout::{LayoutCache, Rect},
     layout_dispatcher::LayoutEventDispatcher,
-    node::{DirtyNode, WrappedIndex},
+    node::{DirtyNode, Node, WrappedIndex},
     prelude::KayakWidgetContext,
     render_primitive::RenderPrimitive,
     styles::{
@@ -64,7 +65,7 @@ type WidgetSystems = HashMap<
 ///     }).id();
 ///     // Stores the kayak app widget in the widget context's tree.
 ///     widget_context.add_widget(None, app_entity);
-///     commands.spawn(UICameraBundle::new(widget_context));
+///     commands.spawn((widget_context, EventDispatcher::default()));
 /// }
 ///
 /// fn main() {
@@ -90,17 +91,18 @@ pub struct KayakRootContext {
     pub(crate) order_tree: Arc<RwLock<Tree>>,
     pub(crate) index: Arc<RwLock<HashMap<Entity, usize>>>,
     pub(crate) uninitilized_systems: HashSet<String>,
+    pub camera_entity: Entity,
 }
 
 impl Default for KayakRootContext {
     fn default() -> Self {
-        Self::new()
+        Self::new(Entity::from_raw(0))
     }
 }
 
 impl KayakRootContext {
     /// Creates a new widget context.
-    pub fn new() -> Self {
+    pub fn new(camera_entity: Entity) -> Self {
         Self {
             tree: Arc::new(RwLock::new(Tree::default())),
             layout_cache: Arc::new(RwLock::new(LayoutCache::default())),
@@ -115,6 +117,7 @@ impl KayakRootContext {
             index: Default::default(),
             order_tree: Default::default(),
             uninitilized_systems: Default::default(),
+            camera_entity,
         }
     }
 
@@ -483,7 +486,7 @@ fn update_widgets_sys(world: &mut World) {
         world,
     );
 
-    for (camera_entity, mut context) in context_data.drain(..) {
+    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);
@@ -516,7 +519,7 @@ fn update_widgets_sys(world: &mut World) {
 
         // dbg!("Updating widgets!");
         update_widgets(
-            camera_entity,
+            context.camera_entity,
             world,
             &context.tree,
             &context.layout_cache,
@@ -564,7 +567,7 @@ fn update_widgets_sys(world: &mut World) {
             indices.clear();
         }
 
-        world.entity_mut(camera_entity).insert(context);
+        world.entity_mut(entity).insert(context);
     }
 }
 
@@ -586,14 +589,14 @@ fn update_widgets(
 ) {
     for entity in widgets.iter() {
         // A small hack to add parents to widgets
-        let mut command_queue = CommandQueue::default();
-        {
-            let mut commands = Commands::new(&mut command_queue, &world);
-            if let Some(mut entity_commands) = commands.get_entity(entity.0) {
-                entity_commands.set_parent(camera_entity);
-            }
-        }
-        command_queue.apply(world);
+        // let mut command_queue = CommandQueue::default();
+        // {
+        //     let mut commands = Commands::new(&mut command_queue, &world);
+        //     if let Some(mut entity_commands) = commands.get_entity(entity.0) {
+        //         entity_commands.set_parent(camera_entity);
+        //     }
+        // }
+        // command_queue.apply(world);
 
         if let Some(entity_ref) = world.get_entity(entity.0) {
             if let Some(widget_type) = entity_ref.get::<WidgetName>() {
@@ -808,6 +811,7 @@ fn update_widget(
         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);
+        widget_update_system.apply_buffers(world);
 
         if should_rerender {
             if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() {
@@ -912,7 +916,7 @@ fn update_widget(
     // Mark node as needing a recalculation of rendering/layout.
     commands.entity(entity.0).insert(DirtyNode);
 
-    for (_index, changed_entity, _parent, changes) in diff.changes.iter() {
+    for (_index, changed_entity, parent, changes) in diff.changes.iter() {
         if changes.iter().any(|change| *change == Change::Deleted) {
             // commands.entity(changed_entity.0).despawn();
             // commands.entity(changed_entity.0).remove::<DirtyNode>();
@@ -921,6 +925,10 @@ fn update_widget(
         if changes.iter().any(|change| *change == Change::Inserted) {
             if let Some(mut entity_commands) = commands.get_entity(changed_entity.0) {
                 entity_commands.insert(Mounted);
+                entity_commands.set_parent(parent.0);
+            }
+            if let Some(mut parent_commands) = commands.get_entity(parent.0) {
+                parent_commands.add_child(changed_entity.0);
             }
         }
     }
@@ -990,9 +998,13 @@ impl Plugin for KayakContextPlugin {
             .add_plugin(crate::camera::KayakUICameraPlugin)
             .add_plugin(crate::render::BevyKayakUIRenderPlugin)
             .register_type::<Node>()
-            .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())
+            .add_system(crate::input::process_events.in_base_set(CoreSet::Update))
+            .add_system(update_widgets_sys.in_base_set(CoreSet::PostUpdate))
+            .add_system(
+                calculate_ui
+                    .after(update_widgets_sys)
+                    .in_base_set(CoreSet::PostUpdate),
+            )
             .add_system(crate::window_size::update_window_size);
 
         // Register reflection types.
@@ -1061,10 +1073,11 @@ fn calculate_ui(world: &mut World) {
                 }
             }
 
-            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 Ok(mut window) = world
+                .query_filtered::<&mut Window, With<PrimaryWindow>>()
+                .get_single_mut(world)
+            {
+                window.cursor.icon = context.current_cursor;
             }
         }
 
diff --git a/src/event.rs b/src/event.rs
index 7d24367d343a6ff2a5055ac834478d12dfec7101..f8bf74ee3819d856e6542951d57f40419ddaa3e1 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -8,7 +8,7 @@ use crate::{
 
 /// An event type sent to widgets
 #[derive(Clone)]
-pub struct Event {
+pub struct KEvent {
     /// The node targeted by this event
     pub target: Entity,
     /// The current target of this event
@@ -23,7 +23,7 @@ pub struct Event {
     pub(crate) on_change_systems: Vec<OnChange>,
 }
 
-impl PartialEq for Event {
+impl PartialEq for KEvent {
     fn eq(&self, other: &Self) -> bool {
         self.target == other.target
             && self.current_target == other.current_target
@@ -33,7 +33,7 @@ impl PartialEq for Event {
     }
 }
 
-impl Default for Event {
+impl Default for KEvent {
     fn default() -> Self {
         Self {
             target: Entity::from_raw(0),
@@ -46,7 +46,7 @@ impl Default for Event {
     }
 }
 
-impl Event {
+impl KEvent {
     /// Create a new event
     ///
     /// This is the preferred method for creating an event as it automatically sets up
diff --git a/src/event_dispatcher.rs b/src/event_dispatcher.rs
index e0750a80b84928459de9798232ec6761ce6ff972..fe7dc526457d8b711799eab13c30e2fd14ce399a 100644
--- a/src/event_dispatcher.rs
+++ b/src/event_dispatcher.rs
@@ -6,7 +6,7 @@ use bevy::{
 use crate::{
     context::KayakRootContext,
     cursor::{CursorEvent, PointerEvents, ScrollEvent, ScrollUnit},
-    event::{Event, EventType},
+    event::{EventType, KEvent},
     focus_tree::FocusTree,
     input_event::{InputEvent, InputEventCategory},
     keyboard_event::{KeyboardEvent, KeyboardModifiers},
@@ -185,21 +185,21 @@ impl EventDispatcher {
         self.dispatch_events(events, context, world);
     }
 
-    /// Dispatch an [Event](crate::Event)
+    /// Dispatch an [KEvent](crate::KEvent)
     #[allow(dead_code)]
     pub fn dispatch_event(
         &mut self,
-        event: Event,
+        event: KEvent,
         context: &mut KayakRootContext,
         world: &mut World,
     ) {
         self.dispatch_events(vec![event], context, world);
     }
 
-    /// Dispatch a set of [Events](crate::Event)
+    /// Dispatch a set of [KEvents](crate::KEvent)
     pub fn dispatch_events(
         &mut self,
-        events: Vec<Event>,
+        events: Vec<KEvent>,
         context: &mut KayakRootContext,
         world: &mut World,
     ) {
@@ -211,7 +211,7 @@ impl EventDispatcher {
                 // Create a copy of the event, specific for this node
                 // This is to make sure unauthorized changes to the event are not propagated
                 // (e.g., changing the event type, removing the target, etc.)
-                let mut node_event = Event {
+                let mut node_event = KEvent {
                     current_target: index.0,
                     ..event.clone()
                 };
@@ -221,7 +221,7 @@ impl EventDispatcher {
 
                 // --- Call Event --- //
                 if let Some(mut entity) = world.get_entity_mut(index.0) {
-                    if let Some(mut on_event) = entity.remove::<OnEvent>() {
+                    if let Some(mut on_event) = entity.take::<OnEvent>() {
                         let mut event_dispatcher_context = EventDispatcherContext {
                             cursor_capture: self.cursor_capture,
                         };
@@ -304,14 +304,14 @@ impl EventDispatcher {
         self.previous_events = next_events;
     }
 
-    /// Generates a stream of [Events](crate::Event) from a set of [InputEvents](crate::InputEvent)
+    /// Generates a stream of [KEvents](crate::KEvent) from a set of [InputEvents](crate::InputEvent)
     fn build_event_stream(
         &mut self,
         input_events: &[InputEvent],
         context: &mut KayakRootContext,
         world: &mut World,
-    ) -> Vec<Event> {
-        let mut event_stream = Vec::<Event>::new();
+    ) -> Vec<KEvent> {
+        let mut event_stream = Vec::<KEvent>::new();
         let mut states: HashMap<EventType, EventState> = HashMap::new();
 
         if let Ok(node_tree) = context.tree.try_read() {
@@ -451,7 +451,7 @@ impl EventDispatcher {
                 // These events are ones that require a specific target and need the tree to be evaluated before selecting the best match
                 for (event_type, state) in states {
                     if let Some(node) = state.best_match {
-                        event_stream.push(Event::new(node.0, event_type));
+                        event_stream.push(KEvent::new(node.0, event_type));
 
                         match event_type {
                             EventType::Focus => {
@@ -459,7 +459,7 @@ impl EventDispatcher {
                                 if let Some(current_focus) = focus_tree.current() {
                                     if current_focus != node {
                                         event_stream
-                                            .push(Event::new(current_focus.0, EventType::Blur));
+                                            .push(KEvent::new(current_focus.0, EventType::Blur));
                                     }
                                 }
                                 focus_tree.focus(node);
@@ -476,7 +476,7 @@ impl EventDispatcher {
                 if !had_focus_event && input_events.contains(&InputEvent::MouseLeftPress) {
                     // A mouse press didn't contain a focus event -> blur
                     if let Some(current_focus) = focus_tree.current() {
-                        event_stream.push(Event::new(current_focus.0, EventType::Blur));
+                        event_stream.push(KEvent::new(current_focus.0, EventType::Blur));
                         focus_tree.blur();
                     }
                 }
@@ -513,7 +513,7 @@ impl EventDispatcher {
     /// * `widget_manager`: The widget manager
     /// * `ignore_layout`: Whether to ignore layout (useful for handling captured events)
     ///
-    /// returns: Vec<Event>
+    /// returns: Vec<KEvent>
     fn process_pointer_events(
         &mut self,
         input_event: &InputEvent,
@@ -522,8 +522,8 @@ impl EventDispatcher {
         world: &mut World,
         context: &KayakRootContext,
         ignore_layout: bool,
-    ) -> Vec<Event> {
-        let mut event_stream = Vec::<Event>::new();
+    ) -> Vec<KEvent> {
+        let mut event_stream = Vec::<KEvent>::new();
         let (node, depth) = tree_node;
 
         // let widget_name = world.entity(node.0).get::<WidgetName>();
@@ -539,7 +539,7 @@ impl EventDispatcher {
                         if was_contained {
                             // Mouse out should fire even when
                             event_stream
-                                .push(Event::new(node.0, EventType::MouseOut(cursor_event)));
+                                .push(KEvent::new(node.0, EventType::MouseOut(cursor_event)));
                             // Self::update_state(
                             //     states,
                             //     (node, depth),
@@ -688,12 +688,12 @@ impl EventDispatcher {
         input_event: &InputEvent,
         _states: &mut HashMap<EventType, EventState>,
         focus_tree: &FocusTree,
-    ) -> Vec<Event> {
+    ) -> Vec<KEvent> {
         let mut event_stream = Vec::new();
         if let Some(current_focus) = focus_tree.current() {
             match input_event {
                 InputEvent::CharEvent { c } => {
-                    event_stream.push(Event::new(current_focus.0, EventType::CharInput { c: *c }))
+                    event_stream.push(KEvent::new(current_focus.0, EventType::CharInput { c: *c }))
                 }
                 InputEvent::Keyboard { key, is_pressed } => {
                     // === Modifers === //
@@ -715,12 +715,12 @@ impl EventDispatcher {
 
                     // === Event === //
                     if *is_pressed {
-                        event_stream.push(Event::new(
+                        event_stream.push(KEvent::new(
                             current_focus.0,
                             EventType::KeyDown(KeyboardEvent::new(*key, self.keyboard_modifiers)),
                         ))
                     } else {
-                        event_stream.push(Event::new(
+                        event_stream.push(KEvent::new(
                             current_focus.0,
                             EventType::KeyUp(KeyboardEvent::new(*key, self.keyboard_modifiers)),
                         ))
@@ -789,7 +789,12 @@ impl EventDispatcher {
     }
 
     /// Executes default actions for events
-    fn execute_default(&mut self, event: Event, context: &mut KayakRootContext, world: &mut World) {
+    fn execute_default(
+        &mut self,
+        event: KEvent,
+        context: &mut KayakRootContext,
+        world: &mut World,
+    ) {
         match event.event_type {
             EventType::KeyDown(evt) => match evt.key() {
                 KeyCode::Tab => {
@@ -808,10 +813,10 @@ impl EventDispatcher {
                         };
 
                     if let Some(index) = index {
-                        let mut events = vec![Event::new(index.0, EventType::Focus)];
+                        let mut events = vec![KEvent::new(index.0, EventType::Focus)];
                         if let Some(current_focus) = current_focus {
                             if current_focus != index {
-                                events.push(Event::new(current_focus.0, EventType::Blur));
+                                events.push(KEvent::new(current_focus.0, EventType::Blur));
                             }
                         }
                         if let Ok(mut focus_tree) = context.focus_tree.try_write() {
diff --git a/src/input.rs b/src/input.rs
index c798500d9346e9d47337ac71644a160370fd8bee..d7adc3e00e59272644d9fa1fc25f2706ecff668f 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -5,6 +5,7 @@ use bevy::{
         ButtonState,
     },
     prelude::*,
+    window::PrimaryWindow,
 };
 
 use crate::{
@@ -14,13 +15,11 @@ use crate::{
 };
 
 pub(crate) fn process_events(world: &mut World) {
-    let window_size = if let Some(windows) = world.get_resource::<Windows>() {
-        if let Some(window) = windows.get_primary() {
-            Vec2::new(window.width(), window.height())
-        } else {
-            log::warn!("Couldn't find primiary window!");
-            return;
-        }
+    let window_size = if let Ok(window) = world
+        .query_filtered::<&Window, With<PrimaryWindow>>()
+        .get_single(&world)
+    {
+        Vec2::new(window.width(), window.height())
     } else {
         log::warn!("Couldn't find primiary window!");
         return;
@@ -133,7 +132,7 @@ pub(crate) fn query_world<T: bevy::ecs::system::SystemParam + 'static, F, R>(
     world: &mut World,
 ) -> R
 where
-    F: FnOnce(<T::Fetch as bevy::ecs::system::SystemParamFetch<'_, '_>>::Item) -> R,
+    F: FnOnce(<T as bevy::ecs::system::SystemParam>::Item<'_, '_>) -> R,
 {
     let mut system_state = bevy::ecs::system::SystemState::<T>::new(world);
     let r = {
diff --git a/src/layout_dispatcher.rs b/src/layout_dispatcher.rs
index 913c7eab9f0cdccef2e089297996c68621cb07fd..fc4571b6a4aca1652d86f748c8339836150f5392 100644
--- a/src/layout_dispatcher.rs
+++ b/src/layout_dispatcher.rs
@@ -67,7 +67,7 @@ impl LayoutEventDispatcher {
         // We should be able to just get layout from WidgetManager here
         // since the layouts will be calculated by this point
         if let Some(mut entity) = world.get_entity_mut(index.0) {
-            if let Some(mut on_layout) = entity.remove::<OnLayout>() {
+            if let Some(mut on_layout) = entity.take::<OnLayout>() {
                 if let Some(rect) = layout_cache.rect.get(&index) {
                     // dbg!(format!("Processing event for: {:?}", entity.id()));
                     let layout_event = LayoutEvent::new(*rect, flags, index.0);
diff --git a/src/lib.rs b/src/lib.rs
index d6e61f6eab0c82e6bbe55c357cc04a1579d96faf..02ac24264915c4f44ccfe4cbc0ab1f7a9f8ff887 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -38,7 +38,7 @@ pub use camera::*;
 pub const DEFAULT_FONT: &str = "Kayak-Default";
 
 pub mod prelude {
-    pub use crate::camera::UICameraBundle;
+    pub use crate::camera::*;
     pub use crate::children::KChildren;
     pub use crate::clone_component::PreviousWidget;
     pub use crate::context::*;
@@ -53,6 +53,7 @@ pub mod prelude {
     pub use crate::on_change::OnChange;
     pub use crate::on_event::OnEvent;
     pub use crate::on_layout::OnLayout;
+    pub use crate::render::draw_ui_graph;
     pub use crate::render::font::FontMapping;
     pub use crate::styles::*;
     pub use crate::tree::*;
diff --git a/src/on_event.rs b/src/on_event.rs
index efe92c5dc355acbde4bda24d9ec960f1c2f92e4b..0f6ca069f482791abb0d73c1fbc796afcd05a31a 100644
--- a/src/on_event.rs
+++ b/src/on_event.rs
@@ -2,7 +2,7 @@ use bevy::prelude::{Component, Entity, In, IntoSystem, System, World};
 use std::fmt::{Debug, Formatter};
 use std::sync::{Arc, RwLock};
 
-use crate::event::Event;
+use crate::event::KEvent;
 use crate::event_dispatcher::EventDispatcherContext;
 use crate::widget_state::WidgetState;
 
@@ -17,8 +17,8 @@ pub struct OnEvent {
     system: Arc<
         RwLock<
             dyn System<
-                In = (EventDispatcherContext, WidgetState, Event, Entity),
-                Out = (EventDispatcherContext, Event),
+                In = (EventDispatcherContext, WidgetState, KEvent, Entity),
+                Out = (EventDispatcherContext, KEvent),
             >,
         >,
     >,
@@ -42,8 +42,8 @@ impl OnEvent {
     /// 2. The event
     pub fn new<Params>(
         system: impl IntoSystem<
-            (EventDispatcherContext, WidgetState, Event, Entity),
-            (EventDispatcherContext, Event),
+            (EventDispatcherContext, WidgetState, KEvent, Entity),
+            (EventDispatcherContext, KEvent),
             Params,
         >,
     ) -> OnEvent {
@@ -61,9 +61,9 @@ impl OnEvent {
         mut event_dispatcher_context: EventDispatcherContext,
         widget_state: WidgetState,
         entity: Entity,
-        mut event: Event,
+        mut event: KEvent,
         world: &mut World,
-    ) -> (EventDispatcherContext, Event) {
+    ) -> (EventDispatcherContext, KEvent) {
         if let Ok(mut system) = self.system.try_write() {
             if !self.has_initialized {
                 system.initialize(world);
diff --git a/src/render/extract.rs b/src/render/extract.rs
index e0a4ded369621cd31000a147291965289020d94e..ce034cedd4ddddc980666397f51b8d2864c9f376 100644
--- a/src/render/extract.rs
+++ b/src/render/extract.rs
@@ -3,17 +3,27 @@ use crate::{
     node::Node,
     render_primitive::RenderPrimitive,
     styles::Corner,
+    CameraUIKayak,
 };
 use bevy::{
-    prelude::{Assets, Camera, Color, Commands, Entity, Image, Plugin, Query, Rect, Res, Vec2},
-    render::{Extract, RenderApp, RenderStage},
-    window::Windows,
+    prelude::{
+        Assets, Camera, Camera2d, Camera3d, Color, Commands, Component, Entity, GlobalTransform,
+        Image, IntoSystemAppConfig, IntoSystemAppConfigs, Mat4, Plugin, Query, Rect, Res, UVec4,
+        Vec2, With,
+    },
+    render::{
+        render_phase::RenderPhase,
+        view::{ColorGrading, ExtractedView},
+        Extract, ExtractSchedule, RenderApp,
+    },
+    window::{PrimaryWindow, Window, WindowRef},
 };
 use kayak_font::KayakFont;
 
 use super::{
     font::{self, FontMapping},
     image, nine_patch, texture_atlas,
+    ui_pass::TransparentUI,
     unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType},
 };
 
@@ -25,34 +35,53 @@ pub struct BevyKayakUIExtractPlugin;
 impl Plugin for BevyKayakUIExtractPlugin {
     fn build(&self, app: &mut bevy::prelude::App) {
         let render_app = app.sub_app_mut(RenderApp);
-        render_app.add_system_to_stage(RenderStage::Extract, extract);
+        render_app.add_system(extract.in_schedule(ExtractSchedule));
+        render_app.add_systems(
+            (
+                extract_default_ui_camera_view::<Camera2d>,
+                extract_default_ui_camera_view::<Camera3d>,
+            )
+                .in_schedule(ExtractSchedule),
+        );
     }
 }
 
 pub fn extract(
     mut commands: Commands,
-    context_query: Extract<Query<(Entity, &KayakRootContext, &Camera)>>,
+    context_query: Extract<Query<(Entity, &KayakRootContext)>>,
     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>>,
+    primary_window: Extract<Query<&Window, With<PrimaryWindow>>>,
+    cameras: Extract<Query<&Camera>>,
 ) {
     let mut render_primitives = Vec::new();
-    for (entity, 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
-                }
+    for (_entity, context) in context_query.iter() {
+        let dpi = if let Ok(camera) = cameras.get(context.camera_entity) {
+            match &camera.target {
+                bevy::render::camera::RenderTarget::Window(window_ref) => match window_ref {
+                    WindowRef::Primary => {
+                        if let Ok(window) = primary_window.get_single() {
+                            window.scale_factor() as f32
+                        } else {
+                            1.0
+                        }
+                    }
+                    _ => 1.0,
+                },
+                _ => 1.0,
             }
-            _ => 1.0,
+        } else {
+            1.0
         };
         let mut new_render_primitives = context.build_render_primitives(&node_query, &widget_names);
-        render_primitives.extend(new_render_primitives.drain(..).map(|r| (entity, dpi, r)));
+        render_primitives.extend(
+            new_render_primitives
+                .drain(..)
+                .map(|r| (context.camera_entity, dpi, r)),
+        );
     }
 
     let mut extracted_quads = Vec::new();
@@ -120,3 +149,48 @@ pub fn extract(
     // dbg!(&extracted_quads);
     commands.spawn_batch(extracted_quads);
 }
+
+#[derive(Component)]
+pub struct DefaultCameraView(pub Entity);
+
+const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1;
+
+pub fn extract_default_ui_camera_view<T: Component>(
+    mut commands: Commands,
+    query: Extract<Query<(Entity, &Camera, &CameraUIKayak), With<T>>>,
+) {
+    for (entity, camera, _camera_ui) in &query {
+        if let (Some(logical_size), Some((physical_origin, _)), Some(physical_size)) = (
+            camera.logical_viewport_size(),
+            camera.physical_viewport_rect(),
+            camera.physical_viewport_size(),
+        ) {
+            // use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection
+            let projection_matrix =
+                Mat4::orthographic_rh(0.0, logical_size.x, logical_size.y, 0.0, 0.0, 1000.0);
+            let default_camera_view = commands
+                .spawn(ExtractedView {
+                    projection: projection_matrix,
+                    transform: GlobalTransform::from_xyz(
+                        0.0,
+                        0.0,
+                        1000.0 + UI_CAMERA_TRANSFORM_OFFSET,
+                    ),
+                    hdr: camera.hdr,
+                    viewport: UVec4::new(
+                        physical_origin.x,
+                        physical_origin.y,
+                        physical_size.x,
+                        physical_size.y,
+                    ),
+                    view_projection: None,
+                    color_grading: ColorGrading::default(),
+                })
+                .id();
+            commands.get_or_spawn(entity).insert((
+                DefaultCameraView(default_camera_view),
+                RenderPhase::<TransparentUI>::default(),
+            ));
+        }
+    }
+}
diff --git a/src/render/mod.rs b/src/render/mod.rs
index 67dbf1cfd342bc94164963625f34aa1027682ca9..c6b1e80e1865ee9d3c2898706632d9940ff129cb 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -1,9 +1,9 @@
 use bevy::{
-    prelude::{App, Camera, Commands, Entity, Plugin, Query, With},
+    prelude::{App, Camera, Commands, Entity, IntoSystemAppConfig, Plugin, Query, With},
     render::{
         render_graph::{RenderGraph, RunGraphOnViewNode, SlotInfo, SlotType},
         render_phase::{DrawFunctions, RenderPhase},
-        Extract, RenderApp, RenderStage,
+        Extract, ExtractSchedule, RenderApp,
     },
 };
 
@@ -43,7 +43,7 @@ impl Plugin for BevyKayakUIRenderPlugin {
         let render_app = app.sub_app_mut(RenderApp);
         render_app
             .init_resource::<DrawFunctions<TransparentUI>>()
-            .add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases);
+            .add_system(extract_core_pipeline_camera_phases.in_schedule(ExtractSchedule));
         // .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<TransparentUI>);
 
         // let pass_node_ui = MainPassUINode::new(&mut render_app.world);
@@ -78,32 +78,24 @@ impl Plugin for BevyKayakUIRenderPlugin {
                 draw_ui_graph::node::MAIN_PASS,
                 RunGraphOnViewNode::new(draw_ui_graph::NAME),
             );
-            graph_2d
-                .add_node_edge(
-                    bevy::core_pipeline::core_2d::graph::node::MAIN_PASS,
-                    draw_ui_graph::node::MAIN_PASS,
-                )
-                .unwrap();
-            graph_2d
-                .add_slot_edge(
-                    graph_2d.input_node().unwrap().id,
-                    bevy::core_pipeline::core_2d::graph::input::VIEW_ENTITY,
-                    draw_ui_graph::node::MAIN_PASS,
-                    RunGraphOnViewNode::IN_VIEW,
-                )
-                .unwrap();
-            graph_2d
-                .add_node_edge(
-                    bevy::core_pipeline::core_2d::graph::node::TONEMAPPING,
-                    draw_ui_graph::node::MAIN_PASS,
-                )
-                .unwrap();
-            graph_2d
-                .add_node_edge(
-                    draw_ui_graph::node::MAIN_PASS,
-                    bevy::core_pipeline::core_2d::graph::node::UPSCALING,
-                )
-                .unwrap();
+            graph_2d.add_node_edge(
+                bevy::core_pipeline::core_2d::graph::node::MAIN_PASS,
+                draw_ui_graph::node::MAIN_PASS,
+            );
+            graph_2d.add_slot_edge(
+                graph_2d.input_node().id,
+                bevy::core_pipeline::core_2d::graph::input::VIEW_ENTITY,
+                draw_ui_graph::node::MAIN_PASS,
+                RunGraphOnViewNode::IN_VIEW,
+            );
+            graph_2d.add_node_edge(
+                bevy::core_pipeline::core_2d::graph::node::TONEMAPPING,
+                draw_ui_graph::node::MAIN_PASS,
+            );
+            graph_2d.add_node_edge(
+                draw_ui_graph::node::MAIN_PASS,
+                bevy::core_pipeline::core_2d::graph::node::UPSCALING,
+            );
         }
 
         if let Some(graph_3d) = graph.get_sub_graph_mut(bevy::core_pipeline::core_3d::graph::NAME) {
@@ -112,32 +104,24 @@ impl Plugin for BevyKayakUIRenderPlugin {
                 draw_ui_graph::node::MAIN_PASS,
                 RunGraphOnViewNode::new(draw_ui_graph::NAME),
             );
-            graph_3d
-                .add_node_edge(
-                    bevy::core_pipeline::core_3d::graph::node::MAIN_PASS,
-                    draw_ui_graph::node::MAIN_PASS,
-                )
-                .unwrap();
-            graph_3d
-                .add_node_edge(
-                    bevy::core_pipeline::core_3d::graph::node::TONEMAPPING,
-                    draw_ui_graph::node::MAIN_PASS,
-                )
-                .unwrap();
-            graph_3d
-                .add_node_edge(
-                    draw_ui_graph::node::MAIN_PASS,
-                    bevy::core_pipeline::core_3d::graph::node::UPSCALING,
-                )
-                .unwrap();
-            graph_3d
-                .add_slot_edge(
-                    graph_3d.input_node().unwrap().id,
-                    bevy::core_pipeline::core_3d::graph::input::VIEW_ENTITY,
-                    draw_ui_graph::node::MAIN_PASS,
-                    RunGraphOnViewNode::IN_VIEW,
-                )
-                .unwrap();
+            graph_3d.add_node_edge(
+                bevy::core_pipeline::core_3d::graph::node::MAIN_PASS,
+                draw_ui_graph::node::MAIN_PASS,
+            );
+            graph_3d.add_node_edge(
+                bevy::core_pipeline::core_3d::graph::node::TONEMAPPING,
+                draw_ui_graph::node::MAIN_PASS,
+            );
+            graph_3d.add_node_edge(
+                draw_ui_graph::node::MAIN_PASS,
+                bevy::core_pipeline::core_3d::graph::node::UPSCALING,
+            );
+            graph_3d.add_slot_edge(
+                graph_3d.input_node().id,
+                bevy::core_pipeline::core_3d::graph::input::VIEW_ENTITY,
+                draw_ui_graph::node::MAIN_PASS,
+                RunGraphOnViewNode::IN_VIEW,
+            );
         }
 
         app.add_plugin(font::TextRendererPlugin)
@@ -154,14 +138,12 @@ fn get_ui_graph(render_app: &mut App) -> RenderGraph {
         draw_ui_graph::input::VIEW_ENTITY,
         SlotType::Entity,
     )]);
-    ui_graph
-        .add_slot_edge(
-            input_node_id,
-            draw_ui_graph::input::VIEW_ENTITY,
-            draw_ui_graph::node::MAIN_PASS,
-            MainPassUINode::IN_VIEW,
-        )
-        .unwrap();
+    ui_graph.add_slot_edge(
+        input_node_id,
+        draw_ui_graph::input::VIEW_ENTITY,
+        draw_ui_graph::node::MAIN_PASS,
+        MainPassUINode::IN_VIEW,
+    );
     ui_graph
 }
 
diff --git a/src/render/ui_pass.rs b/src/render/ui_pass.rs
index 70ed3337c8b8b7e7967a16bdbfcd4926c5d7d24b..d7c8edf588d5ff6e44ca721783e44b7ce57f9b60 100644
--- a/src/render/ui_pass.rs
+++ b/src/render/ui_pass.rs
@@ -1,116 +1,123 @@
-use bevy::core_pipeline::clear_color::ClearColorConfig;
-use bevy::ecs::prelude::*;
-use bevy::prelude::ClearColor;
-use bevy::render::render_phase::{DrawFunctionId, PhaseItem};
-use bevy::render::render_resource::CachedRenderPipelineId;
-use bevy::render::{
-    render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
-    render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
-    render_resource::{LoadOp, Operations, RenderPassDescriptor},
-    renderer::RenderContext,
-    view::{ExtractedView, ViewTarget},
-};
-use bevy::utils::FloatOrd;
-
-use crate::CameraUIKayak;
-
-pub struct TransparentUI {
-    pub sort_key: FloatOrd,
-    pub entity: Entity,
-    pub pipeline: CachedRenderPipelineId,
-    pub draw_function: DrawFunctionId,
-}
-
-impl PhaseItem for TransparentUI {
-    type SortKey = FloatOrd;
-
-    #[inline]
-    fn sort_key(&self) -> Self::SortKey {
-        self.sort_key
-    }
-
-    #[inline]
-    fn draw_function(&self) -> DrawFunctionId {
-        self.draw_function
-    }
-}
-
-pub struct MainPassUINode {
-    query: QueryState<
-        (
-            &'static RenderPhase<TransparentUI>,
-            &'static ViewTarget,
-            &'static CameraUIKayak,
-        ),
-        With<ExtractedView>,
-    >,
-}
-
-impl MainPassUINode {
-    pub const IN_VIEW: &'static str = "view";
-
-    pub fn new(world: &mut World) -> Self {
-        Self {
-            query: world.query_filtered(),
-        }
-    }
-}
-
-impl Node for MainPassUINode {
-    fn input(&self) -> Vec<SlotInfo> {
-        vec![SlotInfo::new(MainPassUINode::IN_VIEW, SlotType::Entity)]
-    }
-
-    fn update(&mut self, world: &mut World) {
-        self.query.update_archetypes(world);
-    }
-
-    fn run(
-        &self,
-        graph: &mut RenderGraphContext,
-        render_context: &mut RenderContext,
-        world: &World,
-    ) -> Result<(), NodeRunError> {
-        let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
-        // adapted from bevy itself;
-        // see: <https://github.com/bevyengine/bevy/commit/09a3d8abe062984479bf0e99fcc1508bb722baf6>
-        let (transparent_phase, target, camera_ui) = match self.query.get_manual(world, view_entity)
-        {
-            Ok(it) => it,
-            _ => return Ok(()),
-        };
-        // let clear_color = world.get_resource::<ClearColor>().unwrap();
-        {
-            let pass_descriptor = RenderPassDescriptor {
-                label: Some("main_transparent_pass_UI"),
-                color_attachments: &[Some(target.get_unsampled_color_attachment(Operations {
-                    load: match camera_ui.clear_color {
-                        ClearColorConfig::Default => {
-                            LoadOp::Clear(world.resource::<ClearColor>().0.into())
-                        }
-                        ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
-                        ClearColorConfig::None => LoadOp::Load,
-                    },
-                    store: true,
-                }))],
-                depth_stencil_attachment: None,
-            };
-
-            let draw_functions = world
-                .get_resource::<DrawFunctions<TransparentUI>>()
-                .unwrap();
-
-            let render_pass = render_context
-                .command_encoder
-                .begin_render_pass(&pass_descriptor);
-            let mut draw_functions = draw_functions.write();
-            let mut tracked_pass = TrackedRenderPass::new(render_pass);
-            for item in transparent_phase.items.iter() {
-                let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
-                draw_function.draw(world, &mut tracked_pass, view_entity, item);
-            }
-        }
-
-        Ok(())
-    }
-}
+use bevy::ecs::prelude::*;
+use bevy::render::render_phase::{DrawFunctionId, PhaseItem};
+use bevy::render::render_resource::CachedRenderPipelineId;
+use bevy::render::{
+    render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
+    render_phase::{DrawFunctions, RenderPhase},
+    render_resource::{LoadOp, Operations, RenderPassDescriptor},
+    renderer::RenderContext,
+    view::{ExtractedView, ViewTarget},
+};
+use bevy::utils::FloatOrd;
+
+use crate::CameraUIKayak;
+
+use super::extract::DefaultCameraView;
+
+pub struct TransparentUI {
+    pub sort_key: FloatOrd,
+    pub entity: Entity,
+    pub pipeline: CachedRenderPipelineId,
+    pub draw_function: DrawFunctionId,
+}
+
+impl PhaseItem for TransparentUI {
+    type SortKey = FloatOrd;
+
+    #[inline]
+    fn sort_key(&self) -> Self::SortKey {
+        self.sort_key
+    }
+
+    #[inline]
+    fn draw_function(&self) -> DrawFunctionId {
+        self.draw_function
+    }
+
+    fn entity(&self) -> Entity {
+        self.entity
+    }
+}
+
+pub struct MainPassUINode {
+    query: QueryState<
+        (
+            &'static RenderPhase<TransparentUI>,
+            &'static ViewTarget,
+            &'static CameraUIKayak,
+        ),
+        With<ExtractedView>,
+    >,
+    default_camera_view_query: QueryState<&'static DefaultCameraView>,
+}
+
+impl MainPassUINode {
+    pub const IN_VIEW: &'static str = "view";
+
+    pub fn new(world: &mut World) -> Self {
+        Self {
+            query: world.query_filtered(),
+            default_camera_view_query: world.query(),
+        }
+    }
+}
+
+impl Node for MainPassUINode {
+    fn input(&self) -> Vec<SlotInfo> {
+        vec![SlotInfo::new(MainPassUINode::IN_VIEW, SlotType::Entity)]
+    }
+
+    fn update(&mut self, world: &mut World) {
+        self.query.update_archetypes(world);
+        self.default_camera_view_query.update_archetypes(world);
+    }
+
+    fn run(
+        &self,
+        graph: &mut RenderGraphContext,
+        render_context: &mut RenderContext,
+        world: &World,
+    ) -> Result<(), NodeRunError> {
+        let input_view_entity = graph.get_input_entity(Self::IN_VIEW)?;
+        // adapted from bevy itself;
+        // see: <https://github.com/bevyengine/bevy/commit/09a3d8abe062984479bf0e99fcc1508bb722baf6>
+        let (transparent_phase, target, _camera_ui) =
+            match self.query.get_manual(world, input_view_entity) {
+                Ok(it) => it,
+                _ => return Ok(()),
+            };
+
+        let view_entity = if let Ok(default_view) = self
+            .default_camera_view_query
+            .get_manual(world, input_view_entity)
+        {
+            default_view.0
+        } else {
+            input_view_entity
+        };
+        // let clear_color = world.get_resource::<ClearColor>().unwrap();
+        {
+            let pass_descriptor = RenderPassDescriptor {
+                label: Some("main_transparent_pass_UI"),
+                color_attachments: &[Some(target.get_unsampled_color_attachment(Operations {
+                    load: LoadOp::Load,
+                    store: true,
+                }))],
+                depth_stencil_attachment: None,
+            };
+
+            let draw_functions = world
+                .get_resource::<DrawFunctions<TransparentUI>>()
+                .unwrap();
+
+            let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
+            let mut draw_functions = draw_functions.write();
+            for item in transparent_phase.items.iter() {
+                let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
+                draw_function.draw(world, &mut tracked_pass, view_entity, item);
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/render/unified/mod.rs b/src/render/unified/mod.rs
index 7efdec3f2b7e5ddfe12abe1457659a73539a3846..53a801a4e8d0209bf5a65e9664c288d03d606a11 100644
--- a/src/render/unified/mod.rs
+++ b/src/render/unified/mod.rs
@@ -1,12 +1,15 @@
 use bevy::{
-    prelude::{Assets, Commands, HandleUntyped, Plugin, Res, Resource},
+    prelude::{
+        Assets, Commands, HandleUntyped, IntoSystemAppConfig, IntoSystemConfig, Plugin, Query, Res,
+        Resource, With,
+    },
     reflect::TypeUuid,
     render::{
         render_phase::DrawFunctions,
         render_resource::{Shader, SpecializedRenderPipelines},
-        Extract, RenderApp, RenderStage,
+        Extract, ExtractSchedule, RenderApp, RenderSet,
     },
-    window::Windows,
+    window::{PrimaryWindow, Window},
 };
 
 use crate::{
@@ -41,9 +44,9 @@ impl Plugin for UnifiedRenderPlugin {
             .init_resource::<UnifiedPipeline>()
             .init_resource::<SpecializedRenderPipelines<UnifiedPipeline>>()
             .init_resource::<QuadMeta>()
-            .add_system_to_stage(RenderStage::Extract, extract_baseline)
-            .add_system_to_stage(RenderStage::Prepare, pipeline::prepare_quads)
-            .add_system_to_stage(RenderStage::Queue, pipeline::queue_quads);
+            .add_system(extract_baseline.in_schedule(ExtractSchedule))
+            .add_system(pipeline::prepare_quads.in_set(RenderSet::Prepare))
+            .add_system(pipeline::queue_quads.in_set(RenderSet::Queue));
 
         let draw_quad = DrawUI::new(&mut render_app.world);
 
@@ -61,10 +64,10 @@ pub struct Dpi(f32);
 
 pub fn extract_baseline(
     mut commands: Commands,
-    windows: Extract<Res<Windows>>,
+    windows: Extract<Query<&Window, With<PrimaryWindow>>>,
     window_size: Extract<Res<WindowSize>>,
 ) {
-    let dpi = if let Some(window) = windows.get_primary() {
+    let dpi = if let Ok(window) = windows.get_single() {
         window.scale_factor() as f32
     } else {
         1.0
diff --git a/src/render/unified/pipeline.rs b/src/render/unified/pipeline.rs
index 4d5aa330e4da2f9191d55a252257e925a26ee7c8..96bf6f80e5047c780d1a8e8937c5c3c61f0d12a7 100644
--- a/src/render/unified/pipeline.rs
+++ b/src/render/unified/pipeline.rs
@@ -1,7 +1,8 @@
-use bevy::prelude::{Msaa, Rect, Resource};
+use bevy::prelude::{Rect, Resource};
 use bevy::render::render_resource::{
     DynamicUniformBuffer, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines,
 };
+use bevy::render::view::{ExtractedView, ViewTarget};
 use bevy::utils::FloatOrd;
 use bevy::{
     ecs::system::{
@@ -67,27 +68,10 @@ impl FontRenderingPipeline for UnifiedPipeline {
     }
 }
 
-bitflags::bitflags! {
-    #[repr(transparent)]
-    pub struct UnifiedPipelineKey: u32 {
-        const NONE                        = 0;
-        const MSAA_RESERVED_BITS          = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;
-    }
-}
-
-impl UnifiedPipelineKey {
-    const MSAA_MASK_BITS: u32 = 0b111;
-    const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
-
-    pub fn from_msaa_samples(msaa_samples: u32) -> Self {
-        let msaa_bits =
-            (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
-        Self::from_bits(msaa_bits).unwrap()
-    }
-
-    pub fn msaa_samples(&self) -> u32 {
-        1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS)
-    }
+#[derive(Debug, Component, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct UnifiedPipelineKey {
+    pub msaa: u32,
+    pub hdr: bool,
 }
 
 impl FromWorld for UnifiedPipeline {
@@ -185,6 +169,7 @@ impl FromWorld for UnifiedPipeline {
             mip_level_count: 1,
             sample_count: 1,
             dimension: TextureDimension::D2,
+            view_formats: &[TextureFormat::Rgba8UnormSrgb],
             format: TextureFormat::Rgba8UnormSrgb,
             usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
         };
@@ -209,6 +194,7 @@ impl FromWorld for UnifiedPipeline {
             texture,
             sampler,
             texture_view,
+            mip_level_count: 1,
             size: Vec2::new(1.0, 1.0),
             texture_format: TextureFormat::Rgba8UnormSrgb,
         };
@@ -242,7 +228,7 @@ impl FromWorld for UnifiedPipeline {
 impl SpecializedRenderPipeline for UnifiedPipeline {
     type Key = UnifiedPipelineKey;
 
-    fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor {
+    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
         let vertex_buffer_layout = VertexBufferLayout {
             array_stride: 60,
             step_mode: VertexStepMode::Vertex,
@@ -282,7 +268,11 @@ impl SpecializedRenderPipeline for UnifiedPipeline {
                 shader_defs: vec![],
                 entry_point: "fragment".into(),
                 targets: vec![Some(ColorTargetState {
-                    format: TextureFormat::bevy_default(),
+                    format: if key.hdr {
+                        ViewTarget::TEXTURE_FORMAT_HDR
+                    } else {
+                        TextureFormat::bevy_default()
+                    },
                     blend: Some(BlendState {
                         color: BlendComponent {
                             src_factor: BlendFactor::SrcAlpha,
@@ -298,12 +288,12 @@ impl SpecializedRenderPipeline for UnifiedPipeline {
                     write_mask: ColorWrites::ALL,
                 })],
             }),
-            layout: Some(vec![
+            layout: vec![
                 self.view_layout.clone(),
                 self.font_image_layout.clone(),
                 self.types_layout.clone(),
                 self.image_layout.clone(),
-            ]),
+            ],
             primitive: PrimitiveState {
                 front_face: FrontFace::Ccw,
                 cull_mode: None,
@@ -315,11 +305,12 @@ impl SpecializedRenderPipeline for UnifiedPipeline {
             },
             depth_stencil: None,
             multisample: MultisampleState {
-                count: 1,
+                count: key.msaa,
                 mask: !0,
                 alpha_to_coverage_enabled: false,
             },
             label: Some("unified_pipeline".into()),
+            push_constant_ranges: vec![],
         }
     }
 }
@@ -356,7 +347,7 @@ pub struct ExtractedQuad {
 }
 
 #[repr(C)]
-#[derive(Copy, Clone, Pod, Zeroable)]
+#[derive(Copy, Clone)]
 struct QuadVertex {
     pub position: [f32; 3],
     pub color: [f32; 4],
@@ -364,6 +355,9 @@ struct QuadVertex {
     pub pos_size: [f32; 4],
 }
 
+unsafe impl Zeroable for QuadVertex {}
+unsafe impl Pod for QuadVertex {}
+
 #[repr(C)]
 #[derive(Copy, Clone, ShaderType)]
 struct QuadType {
@@ -534,11 +528,10 @@ pub fn queue_quads(
     mut pipelines: ResMut<SpecializedRenderPipelines<UnifiedPipeline>>,
     mut pipeline_cache: ResMut<PipelineCache>,
     mut extracted_sprites: Query<(Entity, &ExtractedQuad)>,
-    mut views: Query<(Entity, &mut RenderPhase<TransparentUI>)>,
+    mut views: Query<(Entity, &mut RenderPhase<TransparentUI>, &ExtractedView)>,
     mut image_bind_groups: ResMut<ImageBindGroups>,
     unified_pipeline: Res<UnifiedPipeline>,
     gpu_images: Res<RenderAssets<Image>>,
-    msaa: Res<Msaa>,
 ) {
     if let Some(type_binding) = sprite_meta.types_buffer.binding() {
         sprite_meta.types_bind_group =
@@ -562,13 +555,16 @@ pub fn queue_quads(
             layout: &quad_pipeline.view_layout,
         }));
 
-        let key = UnifiedPipelineKey::from_msaa_samples(msaa.samples);
-        let spec_pipeline = pipelines.specialize(&mut pipeline_cache, &quad_pipeline, key);
-
         let draw_quad = draw_functions.read().get_id::<DrawUI>().unwrap();
-        for (camera_entity, mut transparent_phase) in views.iter_mut() {
+        for (camera_entity, mut transparent_phase, view) in views.iter_mut() {
+            let key = UnifiedPipelineKey {
+                msaa: 1,
+                hdr: view.hdr,
+            };
+            let spec_pipeline = pipelines.specialize(&mut pipeline_cache, &quad_pipeline, key);
+
             for (entity, quad) in extracted_sprites.iter_mut() {
-                if quad.camera_entity != camera_entity {
+                if camera_entity != quad.camera_entity {
                     continue;
                 }
                 if let Some(image_handle) = quad.image.as_ref() {
@@ -596,6 +592,7 @@ pub fn queue_quads(
                             });
                     }
                 }
+
                 transparent_phase.add(TransparentUI {
                     draw_function: draw_quad,
                     pipeline: spec_pipeline,
diff --git a/src/render/unified/shader.wgsl b/src/render/unified/shader.wgsl
index cc6061d4ceb11fffbbac17adbff3e94f22de3a0b..122cfb872ee9a1025487f9500e67e9032db2f1bf 100644
--- a/src/render/unified/shader.wgsl
+++ b/src/render/unified/shader.wgsl
@@ -53,7 +53,7 @@ var image_texture: texture_2d<f32>;
 @group(3) @binding(1)
 var image_sampler: sampler;
 
-let RADIUS: f32 = 0.1;
+const RADIUS: f32 = 0.1;
 
 // Where P is the position in pixel space, B is the size of the box adn R is the radius of the current corner.
 fn sdRoundBox(p: vec2<f32>, b: vec2<f32>, r: f32) -> f32 {
@@ -71,7 +71,7 @@ fn sample_sdf(coords: vec2<f32>, arr: i32, scale: f32) -> f32 {
 }
 
 fn range_curve(font_size: f32) -> f32 {
-    return (8.528 - 9.428 * font_size + 3.428 * pow(font_size, 2.0)) + 1.0;
+    return (5.128 - 6.428 * font_size + 3.428 * pow(font_size, 2.0)) + 1.0;
 }
 
 @fragment
diff --git a/src/render/unified/text.rs b/src/render/unified/text.rs
index 400917b415b30fa03654660d8d50548935a1fe76..c9fdb936706fc046f8b86939347076b3995df693 100644
--- a/src/render/unified/text.rs
+++ b/src/render/unified/text.rs
@@ -1,10 +1,10 @@
 use bevy::{
-    prelude::{Plugin, Res, ResMut},
+    prelude::{IntoSystemConfig, Plugin, Res, ResMut},
     render::{
         render_asset::RenderAssets,
         renderer::{RenderDevice, RenderQueue},
         texture::Image,
-        RenderApp, RenderStage,
+        RenderApp, RenderSet,
     },
 };
 use kayak_font::bevy::{FontTextureCache, KayakFontPlugin};
@@ -19,7 +19,7 @@ impl Plugin for TextRendererPlugin {
         app.add_plugin(KayakFontPlugin);
 
         let render_app = app.sub_app_mut(RenderApp);
-        render_app.add_system_to_stage(RenderStage::Queue, create_and_update_font_cache_texture);
+        render_app.add_system(create_and_update_font_cache_texture.in_set(RenderSet::Queue));
     }
 }
 fn create_and_update_font_cache_texture(
diff --git a/src/widget_context.rs b/src/widget_context.rs
index c6e64c770389ec4737fb9c45fd6fd9ada62f9f69..5f5b95ed41c68cc876f0abd06ff53e345c68d4b0 100644
--- a/src/widget_context.rs
+++ b/src/widget_context.rs
@@ -225,6 +225,21 @@ impl KayakWidgetContext {
         }
     }
 
+    // Despawns a widget entity and it's decedents. This is done in a safe way by keeping entity id's around.
+    pub fn despawn_safe(&self, commands: &mut Commands, entity: Entity) {
+        if let Ok(mut tree) = self.old_tree.write() {
+            let mut down_iter = tree.down_iter();
+            down_iter.current_node = Some(WrappedIndex(entity));
+            for child in down_iter {
+                commands.entity(child.0).despawn();
+                commands.get_or_spawn(child.0);
+            }
+            commands.entity(entity).despawn();
+            commands.get_or_spawn(entity);
+            tree.remove(WrappedIndex(entity));
+        }
+    }
+
     /// Attempts to get the layout rect for the widget with the given ID
     ///
     /// # Arguments
diff --git a/src/widgets/app.rs b/src/widgets/app.rs
index 6a0ba1106e844fc6ecfd24b885c86fadb0020837..4b4aa9620f48e675b5cfed4cb1fe2a9fa7d7fd9e 100644
--- a/src/widgets/app.rs
+++ b/src/widgets/app.rs
@@ -1,4 +1,4 @@
-use bevy::prelude::*;
+use bevy::{prelude::*, window::PrimaryWindow};
 use kayak_ui_macros::rsx;
 
 use crate::{
@@ -45,7 +45,7 @@ pub fn app_update(
     In((widget_context, entity, previous_props_entity)): In<(KayakWidgetContext, Entity, Entity)>,
     widget_param: WidgetParam<KayakApp, EmptyState>,
     camera: Query<&Camera, With<CameraUIKayak>>,
-    windows: Res<Windows>,
+    windows: Query<&Window, With<PrimaryWindow>>,
 ) -> bool {
     let mut window_change = false;
 
@@ -60,7 +60,7 @@ pub fn app_update(
                         window_change = true;
                     }
                 } else {
-                    let primary_window = windows.get_primary().unwrap();
+                    let primary_window = windows.single();
                     if app_style.0.width != StyleProp::Value(Units::Pixels(primary_window.width()))
                     {
                         window_change = true;
@@ -84,8 +84,6 @@ pub fn app_render(
     mut commands: Commands,
     mut query: Query<(&KStyle, &mut ComputedStyles, &KChildren)>,
     camera: Query<&Camera, With<CameraUIKayak>>,
-    windows: Res<Windows>,
-    images: Res<Assets<Image>>,
 ) -> bool {
     let (mut width, mut height) = (0.0, 0.0);
 
@@ -94,17 +92,9 @@ pub fn app_render(
             if let Some(size) = camera.logical_viewport_size() {
                 width = size.x;
                 height = size.y;
-            } else if let Some(viewport) = camera
-                .target
-                .get_render_target_info(&windows, &images)
-                .as_ref()
-                .map(|target_info| {
-                    let scale = target_info.scale_factor;
-                    (target_info.physical_size.as_dvec2() / scale).as_vec2()
-                })
-            {
-                width = viewport.x;
-                height = viewport.y;
+            } else if let Some(viewport) = &camera.viewport {
+                width = viewport.physical_size.x as f32;
+                height = viewport.physical_size.y as f32;
             }
         }
     }
diff --git a/src/widgets/button.rs b/src/widgets/button.rs
index 96dee97f28e194aa1a34ccb1fd04b245c7fa5a81..571e74da1c7411f3e3927d1a02086a4b73c8461f 100644
--- a/src/widgets/button.rs
+++ b/src/widgets/button.rs
@@ -7,7 +7,7 @@ use kayak_ui_macros::rsx;
 
 use crate::{
     context::WidgetName,
-    event::{Event, EventType},
+    event::{EventType, KEvent},
     event_dispatcher::EventDispatcherContext,
     on_event::OnEvent,
     prelude::{KChildren, KayakWidgetContext, Units},
@@ -92,7 +92,7 @@ pub fn button_render(
                 move |In((event_dispatcher_context, _, mut event, _entity)): In<(
                     EventDispatcherContext,
                     WidgetState,
-                    Event,
+                    KEvent,
                     Entity,
                 )>,
                       mut query: Query<&mut ButtonState>| {
diff --git a/src/widgets/scroll/scroll_bar.rs b/src/widgets/scroll/scroll_bar.rs
index e45f3a17c2c1ab1aa6dcaa664a56e095055e8b6e..f554548da664371634e73b4a6d86ddc4ff913d6e 100644
--- a/src/widgets/scroll/scroll_bar.rs
+++ b/src/widgets/scroll/scroll_bar.rs
@@ -3,7 +3,7 @@ use kayak_ui_macros::rsx;
 
 use crate::{
     context::WidgetName,
-    event::{Event, EventType},
+    event::{EventType, KEvent},
     event_dispatcher::EventDispatcherContext,
     on_event::OnEvent,
     prelude::{KChildren, KayakWidgetContext},
@@ -206,7 +206,7 @@ pub fn scroll_bar_render(
                     move |In((mut event_dispatcher_context, _, mut event, _entity)): In<(
                         EventDispatcherContext,
                         WidgetState,
-                        Event,
+                        KEvent,
                         Entity,
                     )>,
                           mut query: Query<&mut ScrollContext>| {
diff --git a/src/widgets/scroll/scroll_box.rs b/src/widgets/scroll/scroll_box.rs
index 5d4643c537ba9268827b8c62c25e14ea778a6f08..c59d7c21f6f3c2d437879529c37f8f67b9cd7275 100644
--- a/src/widgets/scroll/scroll_box.rs
+++ b/src/widgets/scroll/scroll_box.rs
@@ -4,7 +4,7 @@ use crate::{
     children::KChildren,
     context::WidgetName,
     cursor::ScrollUnit,
-    event::{Event, EventType},
+    event::{EventType, KEvent},
     event_dispatcher::EventDispatcherContext,
     layout::{GeometryChanged, LayoutEvent},
     on_event::OnEvent,
@@ -185,7 +185,7 @@ pub fn scroll_box_render(
                     move |In((event_dispatcher_context, _, mut event, _entity)): In<(
                         EventDispatcherContext,
                         WidgetState,
-                        Event,
+                        KEvent,
                         Entity,
                     )>,
                           mut query: Query<&mut ScrollContext>| {
diff --git a/src/widgets/text_box.rs b/src/widgets/text_box.rs
index c0426e4634fdee43312e63d9b2fd1e272a34bc14..67cf244e99545786fe1b338a634bdf0d0d8e65c2 100644
--- a/src/widgets/text_box.rs
+++ b/src/widgets/text_box.rs
@@ -6,7 +6,7 @@ use kayak_ui_macros::{constructor, rsx};
 
 use crate::{
     context::WidgetName,
-    event::{Event, EventType},
+    event::{EventType, KEvent},
     event_dispatcher::EventDispatcherContext,
     on_event::OnEvent,
     on_layout::OnLayout,
@@ -185,7 +185,7 @@ pub fn text_box_render(
                 move |In((event_dispatcher_context, _, mut event, _entity)): In<(
                     EventDispatcherContext,
                     WidgetState,
-                    Event,
+                    KEvent,
                     Entity,
                 )>,
                       font_assets: Res<Assets<KayakFont>>,
diff --git a/src/widgets/window.rs b/src/widgets/window.rs
index dd00fd48e1ce22be161077d48279697377c33274..b362deb29e1251a21d7be0ab7dd985507e57a065 100644
--- a/src/widgets/window.rs
+++ b/src/widgets/window.rs
@@ -7,7 +7,7 @@ use kayak_ui_macros::rsx;
 use crate::{
     children::KChildren,
     context::WidgetName,
-    event::{Event, EventType},
+    event::{EventType, KEvent},
     event_dispatcher::EventDispatcherContext,
     on_event::OnEvent,
     prelude::KayakWidgetContext,
@@ -132,7 +132,7 @@ pub fn window_render(
                 move |In((event_dispatcher_context, _, mut event, _entity)): In<(
                     EventDispatcherContext,
                     WidgetState,
-                    Event,
+                    KEvent,
                     Entity,
                 )>,
                       mut query: Query<&mut KWindowState>,
@@ -226,7 +226,7 @@ pub fn window_render(
                                     move |In((mut event_dispatcher_context, _, mut event, entity)): In<(
                                         EventDispatcherContext,
                                         WidgetState,
-                                        Event,
+                                        KEvent,
                                         Entity,
                                     )>,
                                         mut query: Query<&mut KWindowState>| {
diff --git a/src/window_size.rs b/src/window_size.rs
index 4b17d24245df8e39d9d3ef4236618f6b5e518b58..dd274d5aee0c6ff946a18e7e923310895f2370a5 100644
--- a/src/window_size.rs
+++ b/src/window_size.rs
@@ -10,32 +10,42 @@ pub struct WindowSize(pub f32, pub f32);
 pub fn update_window_size(
     mut window_resized_events: EventReader<WindowResized>,
     mut window_created_events: EventReader<WindowCreated>,
-    windows: Res<Windows>,
+    windows: Query<&Window>,
     mut window_size: ResMut<WindowSize>,
 ) {
-    let mut changed_window_ids = Vec::new();
+    let mut changed_windows = Vec::new();
     // handle resize events. latest events are handled first because we only want to resize each
     // window once
-    for event in window_resized_events.iter().rev() {
-        if changed_window_ids.contains(&event.id) {
+    for event in window_resized_events
+        .iter()
+        .collect::<Vec<_>>()
+        .iter()
+        .rev()
+    {
+        if changed_windows.contains(&event.window) {
             continue;
         }
 
-        changed_window_ids.push(event.id);
+        changed_windows.push(event.window);
     }
 
     // handle resize events. latest events are handled first because we only want to resize each
     // window once
-    for event in window_created_events.iter().rev() {
-        if changed_window_ids.contains(&event.id) {
+    for event in window_created_events
+        .iter()
+        .collect::<Vec<_>>()
+        .iter()
+        .rev()
+    {
+        if changed_windows.contains(&event.window) {
             continue;
         }
 
-        changed_window_ids.push(event.id);
+        changed_windows.push(event.window);
     }
 
-    for window_id in changed_window_ids {
-        if let Some(window) = windows.get(window_id) {
+    for window_entity in changed_windows {
+        if let Ok(window) = windows.get(window_entity) {
             let width = window.width();
             let height = window.height();
             *window_size = WindowSize(width, height);