diff --git a/examples/bevy_scene.rs b/examples/bevy_scene.rs
new file mode 100644
index 0000000000000000000000000000000000000000..78046451a4622977cc6467c5e26b89e684b90d56
--- /dev/null
+++ b/examples/bevy_scene.rs
@@ -0,0 +1,272 @@
+use bevy::{
+    math::{Vec3Swizzles, Vec4Swizzles},
+    prelude::*,
+};
+use kayak_ui::prelude::{widgets::*, *};
+
+const TILE_SIZE: Vec2 = Vec2::from_array([50.0, 50.0]);
+const COLORS: &[Color] = &[Color::TEAL, Color::MAROON, Color::INDIGO];
+
+// ! === Unnecessary Details Below === ! //
+// Below this point are mainly implementation details. The main purpose of this example is to show how to know
+// when to allow or disallow world interaction through `BevyContext` (see the `set_active_tile_target` function)
+
+/// A resource used to control the color of the tiles
+#[derive(Resource)]
+struct ActiveColor {
+    index: usize,
+}
+
+/// A component used to control the "Active Tile" that moves to the clicked positions
+#[derive(Default, Component)]
+struct ActiveTile {
+    target: Vec2,
+}
+
+/// A component used to control the "Ghost Tile" that follows the user's cursor
+#[derive(Component)]
+struct GhostTile;
+
+/// A component used to mark the "world camera" (differentiating it from other cameras possibly in the scene)
+#[derive(Component)]
+struct WorldCamera;
+
+/// This is the system that sets the active tile's target position
+///
+/// To prevent the tile from being moved to a position under our UI, we can use the `BevyContext` resource
+/// to filter out clicks that occur over the UI
+fn set_active_tile_target(
+    mut tile: Query<&mut ActiveTile>,
+    cursor: Res<Input<MouseButton>>,
+    event_context: Res<EventDispatcher>,
+    camera_transform: Query<&GlobalTransform, With<WorldCamera>>,
+    windows: Res<Windows>,
+) {
+    if !cursor.just_pressed(MouseButton::Left) {
+        // Only run this system when the mouse button is clicked
+        return;
+    }
+
+    if event_context.contains_cursor() {
+        // This is the important bit:
+        // If the cursor is over a part of the UI, then we should not allow clicks to pass through to the world
+        return;
+    }
+
+    // If you wanted to allow clicks through the UI as long as the cursor is not on a focusable widget (such as Buttons),
+    // you could use `context.wants_cursor()` instead:
+    //
+    // ```
+    // if context.wants_cursor() {
+    //     return;
+    // }
+    // ```
+
+    let world_pos = cursor_to_world(&windows, &camera_transform.single());
+    let tile_pos = world_to_tile(world_pos);
+    let mut tile = tile.single_mut();
+    tile.target = tile_pos;
+}
+
+/// A system that moves the active tile to its target position
+fn move_active_tile(mut tile: Query<(&mut Transform, &ActiveTile)>) {
+    let (mut transform, tile) = tile.single_mut();
+    let curr_pos = transform.translation.xy();
+    let next_pos = curr_pos.lerp(tile.target, 0.1);
+    transform.translation.x = next_pos.x;
+    transform.translation.y = next_pos.y;
+}
+
+/// A system that moves the ghost tile to the cursor's position
+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>,
+) {
+    for _ in cursor_moved.iter() {
+        let world_pos = cursor_to_world(&windows, &camera_transform.single());
+        let tile_pos = world_to_tile(world_pos);
+        let mut ghost = tile.single_mut();
+        ghost.translation.x = tile_pos.x;
+        ghost.translation.y = tile_pos.y;
+    }
+}
+
+/// A system that updates the tiles' color
+fn on_color_change(
+    mut active_tile: Query<&mut Sprite, (With<ActiveTile>, Without<GhostTile>)>,
+    mut ghost_tile: Query<&mut Sprite, (With<GhostTile>, Without<ActiveTile>)>,
+    active_color: Res<ActiveColor>,
+) {
+    if !active_color.is_changed() {
+        return;
+    }
+
+    let mut active_tile = active_tile.single_mut();
+    active_tile.color = COLORS[active_color.index];
+
+    let mut ghost_tile = ghost_tile.single_mut();
+    ghost_tile.color = ghost_color(COLORS[active_color.index]);
+}
+
+/// 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 {
+                color: COLORS[active_color.index],
+                custom_size: Some(TILE_SIZE),
+                ..Default::default()
+            },
+            ..Default::default()
+        })
+        .insert(ActiveTile::default());
+    commands
+        .spawn(SpriteBundle {
+            sprite: Sprite {
+                color: ghost_color(COLORS[active_color.index]),
+                custom_size: Some(TILE_SIZE),
+                ..Default::default()
+            },
+            ..Default::default()
+        })
+        .insert(GhostTile);
+}
+
+/// 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();
+    let size = Vec2::new(window.width(), window.height());
+
+    let mut pos = window.cursor_position().unwrap_or_default();
+    pos.y = size.y - pos.y;
+    pos -= size / 2.0;
+
+    let point = camera_transform.compute_matrix() * pos.extend(0.0).extend(1.0);
+    point.xy()
+}
+
+/// Converts a world coordinate to a rounded tile coordinate
+fn world_to_tile(world_pos: Vec2) -> Vec2 {
+    let extents = TILE_SIZE / 2.0;
+    let world_pos = world_pos - extents;
+    (world_pos / TILE_SIZE).ceil() * TILE_SIZE
+}
+
+/// Get the ghost tile color for a given color
+fn ghost_color(color: Color) -> Color {
+    let mut c = color;
+    c.set_a(0.35);
+    c
+}
+
+fn startup(
+    mut commands: Commands,
+    mut font_mapping: ResMut<FontMapping>,
+    asset_server: Res<AssetServer>,
+) {
+    font_mapping.set_default(asset_server.load("roboto.kayak_font"));
+
+    commands.spawn(UICameraBundle::new());
+
+    let mut widget_context = KayakRootContext::new();
+
+    let handle_change_color = OnEvent::new(
+        move |In((event_dispatcher_context, _, event, _)): In<(
+            EventDispatcherContext,
+            WidgetState,
+            Event,
+            Entity,
+        )>,
+              mut active_color: ResMut<ActiveColor>| {
+            match event.event_type {
+                EventType::Click(..) => {
+                    active_color.index = (active_color.index + 1) % COLORS.len();
+                }
+                _ => {}
+            }
+            (event_dispatcher_context, event)
+        },
+    );
+
+    let text_styles = KStyle {
+        left: StyleProp::Value(Units::Stretch(1.0)),
+        right: StyleProp::Value(Units::Stretch(1.0)),
+        ..Default::default()
+    };
+    let button_styles = KStyle {
+        min_width: StyleProp::Value(Units::Pixels(150.0)),
+        width: StyleProp::Value(Units::Auto),
+        height: StyleProp::Value(Units::Auto),
+        left: StyleProp::Value(Units::Stretch(1.0)),
+        right: StyleProp::Value(Units::Stretch(1.0)),
+        top: StyleProp::Value(Units::Pixels(16.0)),
+        bottom: StyleProp::Value(Units::Pixels(8.0)),
+        padding: StyleProp::Value(Edge::axis(Units::Pixels(8.0), Units::Pixels(48.0))),
+        ..Default::default()
+    };
+
+    let parent_id = None;
+    rsx! {
+        <KayakAppBundle>
+            <WindowBundle
+                window={KWindow {
+                    draggable: true,
+                    initial_position: Vec2::new(50.0, 50.0),
+                    size: Vec2::new(300.0, 200.0),
+                    title: "Square Mover: The Game".to_string(),
+                    ..Default::default()
+                }}
+            >
+                <TextWidgetBundle
+                    text={TextProps {
+                        size: 13.0,
+                        content: "You can check if the cursor is over the UI or on a focusable widget using the BevyContext resource.".to_string(),
+                        ..Default::default()
+                    }}
+                    styles={text_styles.clone()}
+                />
+                <KButtonBundle
+                    on_event={handle_change_color}
+                    styles={button_styles}
+                >
+                    <TextWidgetBundle
+                        text={TextProps {
+                            size: 16.0,
+                            content: "Change Tile Color".to_string(),
+                            ..Default::default()
+                        }}
+                    />
+                </KButtonBundle>
+                <TextWidgetBundle
+                    text={TextProps {
+                        size: 11.0,
+                        content: "Go ahead and click the button! The tile won't move.".to_string(),
+                        ..Default::default()
+                    }}
+                    styles={text_styles}
+                />
+            </WindowBundle>
+        </KayakAppBundle>
+    }
+
+    commands.insert_resource(widget_context);
+}
+
+fn main() {
+    App::new()
+        .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
+        .insert_resource(ActiveColor { index: 0 })
+        .add_plugins(DefaultPlugins)
+        .add_plugin(KayakContextPlugin)
+        .add_plugin(KayakWidgets)
+        .add_startup_system(startup)
+        .add_startup_system(world_setup)
+        .add_system(move_ghost_tile)
+        .add_system(set_active_tile_target)
+        .add_system(move_active_tile)
+        .add_system(on_color_change)
+        .run()
+}
diff --git a/examples/context.rs b/examples/context.rs
index 3a2e6547dc94a6998085709256861a67205bcd87..5630b1fb4ee9938a5b2cac6c7ae4acfd18ec3634 100644
--- a/examples/context.rs
+++ b/examples/context.rs
@@ -339,6 +339,9 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
+    // Camera 2D forces a clear pass in bevy.
+    // We do this because our scene is not rendering anything else.
+    commands.spawn(Camera2dBundle::default());
     commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
diff --git a/examples/quads.rs b/examples/quads.rs
index 168141653585e02bad14362dfa8ab9f3cf529da5..514c9a9b88a442164460ef2ed64d81807959a48d 100644
--- a/examples/quads.rs
+++ b/examples/quads.rs
@@ -83,6 +83,9 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
+    // Camera 2D forces a clear pass in bevy.
+    // We do this because our scene is not rendering anything else.
+    commands.spawn(Camera2dBundle::default());
     commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
diff --git a/examples/simple_state.rs b/examples/simple_state.rs
index 00fe63cfae480e374bc3d1b6824e8fc1aee52201..eee326a6c2d91654447d0982dcee3d283d9721f3 100644
--- a/examples/simple_state.rs
+++ b/examples/simple_state.rs
@@ -63,6 +63,9 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
+    // Camera 2D forces a clear pass in bevy.
+    // We do this because our scene is not rendering anything else.
+    commands.spawn(Camera2dBundle::default());
     commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
diff --git a/examples/text_box.rs b/examples/text_box.rs
index 0e3f4096df13a6f75dad816c2e113cb9b9599016..723f96ef2f80bc03e43acd956a2e71cf7eb988e9 100644
--- a/examples/text_box.rs
+++ b/examples/text_box.rs
@@ -90,6 +90,9 @@ fn startup(
 ) {
     font_mapping.set_default(asset_server.load("roboto.kayak_font"));
 
+    // Camera 2D forces a clear pass in bevy.
+    // We do this because our scene is not rendering anything else.
+    commands.spawn(Camera2dBundle::default());
     commands.spawn(UICameraBundle::new());
 
     let mut widget_context = KayakRootContext::new();
diff --git a/src/camera/camera.rs b/src/camera/camera.rs
index f04d3ab9d966fc4e25c072132e7b31c9a42f0ede..edd4761b7852df77b75de35581082c6e7560ded3 100644
--- a/src/camera/camera.rs
+++ b/src/camera/camera.rs
@@ -1,6 +1,6 @@
 use bevy::{
     ecs::query::QueryItem,
-    prelude::{Bundle, Camera2d, Component, GlobalTransform, Transform, With},
+    prelude::{Bundle, Component, GlobalTransform, Transform, With},
     render::{
         camera::{Camera, CameraProjection, CameraRenderGraph, WindowOrigin},
         extract_component::ExtractComponent,
@@ -28,7 +28,7 @@ impl ExtractComponent for CameraUiKayak {
 #[derive(Bundle)]
 pub struct UICameraBundle {
     pub camera: Camera,
-    pub camera_2d: Camera2d,
+    // pub camera_2d: Camera2d,
     pub camera_render_graph: CameraRenderGraph,
     pub orthographic_projection: UIOrthographicProjection,
     pub visible_entities: VisibleEntities,
@@ -71,7 +71,7 @@ impl UICameraBundle {
             frustum,
             visible_entities: VisibleEntities::default(),
             transform,
-            camera_2d: Camera2d::default(),
+            // camera_2d: Camera2d::default(),
             global_transform: Default::default(),
             marker: CameraUiKayak,
         }
diff --git a/src/event_dispatcher.rs b/src/event_dispatcher.rs
index cb70a75c2137c6b7d26dfd2baae84965e0f90805..53a6340bd5c337afac1fce29181221593e1a87de 100644
--- a/src/event_dispatcher.rs
+++ b/src/event_dispatcher.rs
@@ -44,7 +44,7 @@ impl Default for EventState {
 }
 
 #[derive(Resource, Debug, Clone)]
-pub(crate) struct EventDispatcher {
+pub struct EventDispatcher {
     is_mouse_pressed: bool,
     next_mouse_pressed: bool,
     current_mouse_position: (f32, f32),
@@ -55,12 +55,12 @@ pub(crate) struct EventDispatcher {
     contains_cursor: Option<bool>,
     wants_cursor: Option<bool>,
     has_cursor: Option<WrappedIndex>,
-    pub cursor_capture: Option<WrappedIndex>,
-    pub hovered: Option<WrappedIndex>,
+    pub(crate) cursor_capture: Option<WrappedIndex>,
+    pub(crate) hovered: Option<WrappedIndex>,
 }
 
 impl EventDispatcher {
-    pub fn new() -> Self {
+    pub(crate) fn new() -> Self {
         Self {
             // last_clicked: Binding::new(WrappedIndex(Entity::from_raw(0))),
             is_mouse_pressed: Default::default(),
@@ -175,7 +175,7 @@ impl EventDispatcher {
     // }
 
     /// Process and dispatch a set of [InputEvents](crate::InputEvent)
-    pub fn process_events(
+    pub(crate) fn process_events(
         &mut self,
         input_events: Vec<InputEvent>,
         context: &mut KayakRootContext,
diff --git a/src/lib.rs b/src/lib.rs
index 2c931209809809f5e744e4444707b98cc854a619..541553089de76b2996129c687039bcf45ebc1c1e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -42,7 +42,7 @@ pub mod prelude {
     pub use crate::clone_component::PreviousWidget;
     pub use crate::context::*;
     pub use crate::event::*;
-    pub use crate::event_dispatcher::EventDispatcherContext;
+    pub use crate::event_dispatcher::{EventDispatcher, EventDispatcherContext};
     pub use crate::focus_tree::Focusable;
     pub use crate::input_event::*;
     pub use crate::keyboard_event::*;