diff --git a/projects/README.md b/projects/README.md
index ea9f176ce..8032ecdbe 100644
--- a/projects/README.md
+++ b/projects/README.md
@@ -23,3 +23,6 @@ This example walks you through in explicit detail how to use [Tauri](https://tau
### counter_dwarf_debug
This example shows how to add breakpoints within the browser or visual studio code for debugging.
+
+### bevy3d_ui
+This example uses the bevy 3d game engine with leptos within webassembly.
diff --git a/projects/bevy3d_ui/Cargo.toml b/projects/bevy3d_ui/Cargo.toml
new file mode 100644
index 000000000..7a3343774
--- /dev/null
+++ b/projects/bevy3d_ui/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "bevy3d_ui"
+version = "0.1.0"
+edition = "2021"
+
+[profile.release]
+codegen-units = 1
+lto = true
+
+[dependencies]
+leptos = { version = "0.6.11", features = ["csr"] }
+leptos_meta = { version = "0.6.11", features = ["csr"] }
+leptos_router = { version = "0.6.11", features = ["csr"] }
+console_log = "1"
+log = "0.4"
+console_error_panic_hook = "0.1.7"
+bevy = "0.13.2"
+crossbeam-channel = "0.5.12"
+
+[dev-dependencies]
+wasm-bindgen = "0.2"
+wasm-bindgen-test = "0.3.0"
+web-sys = "0.3"
+
+[workspace]
+# The empty workspace here is to keep rust-analyzer satisfied
diff --git a/projects/bevy3d_ui/README.md b/projects/bevy3d_ui/README.md
new file mode 100644
index 000000000..c71465df3
--- /dev/null
+++ b/projects/bevy3d_ui/README.md
@@ -0,0 +1,15 @@
+# Bevy 3D UI Example
+
+This example combines a leptos UI with a bevy 3D view.
+Bevy is a 3D game engine written in rust that can be compiled to web assembly by using the wgpu library.
+The wgpu library in turn can target the newer webgpu standard or the older webgl for web browsers.
+
+In the case of a desktop application, if you wanted to use a styled ui via leptos and a 3d view via bevy
+you could also combine this with tauri.
+
+## Quick Start
+
+ * Run `trunk serve to run the example.
+ * Browse to http://127.0.0.1:8080/
+
+It's best to use a web browser with webgpu capability for best results such as Chrome or Opera.
diff --git a/projects/bevy3d_ui/index.html b/projects/bevy3d_ui/index.html
new file mode 100644
index 000000000..75fa1f12a
--- /dev/null
+++ b/projects/bevy3d_ui/index.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/projects/bevy3d_ui/public/favicon.ico b/projects/bevy3d_ui/public/favicon.ico
new file mode 100644
index 000000000..2ba8527cb
Binary files /dev/null and b/projects/bevy3d_ui/public/favicon.ico differ
diff --git a/projects/bevy3d_ui/rust-toolchain.toml b/projects/bevy3d_ui/rust-toolchain.toml
new file mode 100644
index 000000000..ff2a4ff10
--- /dev/null
+++ b/projects/bevy3d_ui/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "stable" # test change
diff --git a/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/events.rs b/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/events.rs
new file mode 100644
index 000000000..03513e010
--- /dev/null
+++ b/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/events.rs
@@ -0,0 +1,38 @@
+use bevy::prelude::*;
+
+/// Event Processor
+#[derive(Resource)]
+pub struct EventProcessor {
+ pub sender: crossbeam_channel::Sender,
+ pub receiver: crossbeam_channel::Receiver,
+}
+
+impl Clone for EventProcessor {
+ fn clone(&self) -> Self {
+ Self {
+ sender: self.sender.clone(),
+ receiver: self.receiver.clone(),
+ }
+ }
+}
+
+/// Events sent from the client to bevy
+#[derive(Debug)]
+pub enum ClientInEvents {
+ /// Update the 3d model position from the client
+ CounterEvt(CounterEvtData),
+}
+
+/// Events sent out from bevy to the client
+#[derive(Debug)]
+pub enum PluginOutEvents {
+ /// TODO Feed back to the client an event from bevy
+ Click,
+}
+
+/// Input event to update the bevy view from the client
+#[derive(Clone, Debug, Event)]
+pub struct CounterEvtData {
+ /// Amount to move on the Y Axis
+ pub value: f32,
+}
diff --git a/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/mod.rs b/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/mod.rs
new file mode 100644
index 000000000..d6e89b83c
--- /dev/null
+++ b/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/mod.rs
@@ -0,0 +1,2 @@
+pub mod events;
+pub mod plugin;
diff --git a/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/plugin.rs b/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/plugin.rs
new file mode 100644
index 000000000..bf061f2fb
--- /dev/null
+++ b/projects/bevy3d_ui/src/demos/bevydemo1/eventqueue/plugin.rs
@@ -0,0 +1,63 @@
+use super::events::*;
+use bevy::prelude::*;
+
+/// Events plugin for bevy
+#[derive(Clone)]
+pub struct DuplexEventsPlugin {
+ /// Client processor for sending ClientInEvents, receiving PluginOutEvents
+ client_processor: EventProcessor,
+ /// Internal processor for sending PluginOutEvents, receiving ClientInEvents
+ plugin_processor: EventProcessor,
+}
+
+impl DuplexEventsPlugin {
+ /// Create a new instance
+ pub fn new() -> DuplexEventsPlugin {
+ // For sending messages from bevy to the client
+ let (bevy_sender, client_receiver) = crossbeam_channel::bounded(50);
+ // For sending message from the client to bevy
+ let (client_sender, bevy_receiver) = crossbeam_channel::bounded(50);
+ let instance = DuplexEventsPlugin {
+ client_processor: EventProcessor {
+ sender: client_sender,
+ receiver: client_receiver,
+ },
+ plugin_processor: EventProcessor {
+ sender: bevy_sender,
+ receiver: bevy_receiver,
+ },
+ };
+ instance
+ }
+
+ /// Get the client event processor
+ pub fn get_processor(
+ &self,
+ ) -> EventProcessor {
+ self.client_processor.clone()
+ }
+}
+
+/// Build the bevy plugin and attach
+impl Plugin for DuplexEventsPlugin {
+ fn build(&self, app: &mut App) {
+ app.insert_resource(self.plugin_processor.clone())
+ .init_resource::>()
+ .add_systems(PreUpdate, input_events_system);
+ }
+}
+
+/// Send the event to bevy using EventWriter
+fn input_events_system(
+ int_processor: Res>,
+ mut counter_event_writer: EventWriter,
+) {
+ for input_event in int_processor.receiver.try_iter() {
+ match input_event {
+ ClientInEvents::CounterEvt(event) => {
+ // Send event through Bevy's event system
+ counter_event_writer.send(event);
+ }
+ }
+ }
+}
diff --git a/projects/bevy3d_ui/src/demos/bevydemo1/mod.rs b/projects/bevy3d_ui/src/demos/bevydemo1/mod.rs
new file mode 100644
index 000000000..933b4f2d8
--- /dev/null
+++ b/projects/bevy3d_ui/src/demos/bevydemo1/mod.rs
@@ -0,0 +1,3 @@
+pub mod eventqueue;
+pub mod scene;
+pub mod state;
diff --git a/projects/bevy3d_ui/src/demos/bevydemo1/scene.rs b/projects/bevy3d_ui/src/demos/bevydemo1/scene.rs
new file mode 100644
index 000000000..58401d041
--- /dev/null
+++ b/projects/bevy3d_ui/src/demos/bevydemo1/scene.rs
@@ -0,0 +1,124 @@
+use super::eventqueue::events::{
+ ClientInEvents, CounterEvtData, EventProcessor, PluginOutEvents,
+};
+use super::eventqueue::plugin::DuplexEventsPlugin;
+use super::state::{Shared, SharedResource, SharedState};
+use bevy::prelude::*;
+
+/// Represents the Cube in the scene
+#[derive(Component, Copy, Clone)]
+pub struct Cube;
+
+/// Represents the 3D Scene
+#[derive(Clone)]
+pub struct Scene {
+ is_setup: bool,
+ canvas_id: String,
+ evt_plugin: DuplexEventsPlugin,
+ shared_state: Shared,
+ processor: EventProcessor,
+}
+
+impl Scene {
+ /// Create a new instance
+ pub fn new(canvas_id: String) -> Scene {
+ let plugin = DuplexEventsPlugin::new();
+ let instance = Scene {
+ is_setup: false,
+ canvas_id: canvas_id,
+ evt_plugin: plugin.clone(),
+ shared_state: SharedState::new(),
+ processor: plugin.get_processor(),
+ };
+ instance
+ }
+
+ /// Get the shared state
+ pub fn get_state(&self) -> Shared {
+ self.shared_state.clone()
+ }
+
+ /// Get the event processor
+ pub fn get_processor(
+ &self,
+ ) -> EventProcessor {
+ self.processor.clone()
+ }
+
+ /// Setup and attach the bevy instance to the html canvas element
+ pub fn setup(&mut self) {
+ if self.is_setup == true {
+ return;
+ };
+ App::new()
+ .add_plugins(DefaultPlugins.set(WindowPlugin {
+ primary_window: Some(Window {
+ canvas: Some(self.canvas_id.clone()),
+ ..default()
+ }),
+ ..default()
+ }))
+ .add_plugins(self.evt_plugin.clone())
+ .insert_resource(SharedResource(self.shared_state.clone()))
+ .add_systems(Startup, setup_scene)
+ .add_systems(Update, handle_bevy_event)
+ .run();
+ self.is_setup = true;
+ }
+}
+
+/// Setup the scene
+fn setup_scene(
+ mut commands: Commands,
+ mut meshes: ResMut>,
+ mut materials: ResMut>,
+ resource: Res,
+) {
+ let name = resource.0.lock().unwrap().name.clone();
+ // circular base
+ commands.spawn(PbrBundle {
+ mesh: meshes.add(Circle::new(4.0)),
+ material: materials.add(Color::WHITE),
+ transform: Transform::from_rotation(Quat::from_rotation_x(
+ -std::f32::consts::FRAC_PI_2,
+ )),
+ ..default()
+ });
+ // cube
+ commands.spawn((
+ PbrBundle {
+ mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
+ material: materials.add(Color::rgb_u8(124, 144, 255)),
+ transform: Transform::from_xyz(0.0, 0.5, 0.0),
+ ..default()
+ },
+ Cube,
+ ));
+ // light
+ commands.spawn(PointLightBundle {
+ point_light: PointLight {
+ shadows_enabled: true,
+ ..default()
+ },
+ transform: Transform::from_xyz(4.0, 8.0, 4.0),
+ ..default()
+ });
+ // camera
+ commands.spawn(Camera3dBundle {
+ transform: Transform::from_xyz(-2.5, 4.5, 9.0)
+ .looking_at(Vec3::ZERO, Vec3::Y),
+ ..default()
+ });
+ commands.spawn(TextBundle::from_section(name, TextStyle::default()));
+}
+
+/// Move the Cube on event
+fn handle_bevy_event(
+ mut counter_event_reader: EventReader,
+ mut cube_query: Query<&mut Transform, With>,
+) {
+ let mut cube_transform = cube_query.get_single_mut().expect("no cube :(");
+ for _ev in counter_event_reader.read() {
+ cube_transform.translation += Vec3::new(0.0, _ev.value, 0.0);
+ }
+}
diff --git a/projects/bevy3d_ui/src/demos/bevydemo1/state.rs b/projects/bevy3d_ui/src/demos/bevydemo1/state.rs
new file mode 100644
index 000000000..b4aed2ab5
--- /dev/null
+++ b/projects/bevy3d_ui/src/demos/bevydemo1/state.rs
@@ -0,0 +1,24 @@
+use bevy::ecs::system::Resource;
+use std::sync::{Arc, Mutex};
+
+pub type Shared = Arc>;
+
+/// Shared Resource used for Bevy
+#[derive(Resource)]
+pub struct SharedResource(pub Shared);
+
+/// Shared State
+pub struct SharedState {
+ pub name: String,
+}
+
+impl SharedState {
+ /// Get a new shared state
+ pub fn new() -> Arc> {
+ let state = SharedState {
+ name: "This can be used for shared state".to_string(),
+ };
+ let shared = Arc::new(Mutex::new(state));
+ shared
+ }
+}
diff --git a/projects/bevy3d_ui/src/demos/mod.rs b/projects/bevy3d_ui/src/demos/mod.rs
new file mode 100644
index 000000000..d9d3a23a2
--- /dev/null
+++ b/projects/bevy3d_ui/src/demos/mod.rs
@@ -0,0 +1 @@
+pub mod bevydemo1;
diff --git a/projects/bevy3d_ui/src/main.rs b/projects/bevy3d_ui/src/main.rs
new file mode 100644
index 000000000..d35c07344
--- /dev/null
+++ b/projects/bevy3d_ui/src/main.rs
@@ -0,0 +1,11 @@
+mod demos;
+mod routes;
+use leptos::*;
+use routes::RootPage;
+
+pub fn main() {
+ // Bevy will output a lot of debug info to the console when this is enabled.
+ //_ = console_log::init_with_level(log::Level::Debug);
+ console_error_panic_hook::set_once();
+ mount_to_body(|| view! { })
+}
diff --git a/projects/bevy3d_ui/src/routes/demo1.rs b/projects/bevy3d_ui/src/routes/demo1.rs
new file mode 100644
index 000000000..6c49894e0
--- /dev/null
+++ b/projects/bevy3d_ui/src/routes/demo1.rs
@@ -0,0 +1,52 @@
+use crate::demos::bevydemo1::eventqueue::events::{
+ ClientInEvents, CounterEvtData,
+};
+use crate::demos::bevydemo1::scene::Scene;
+use leptos::*;
+
+/// 3d view component
+#[component]
+pub fn Demo1() -> impl IntoView {
+ // Setup a Counter
+ let initial_value: i32 = 0;
+ let step: i32 = 1;
+ let (value, set_value) = create_signal(initial_value);
+
+ // Setup a bevy 3d scene
+ let scene = Scene::new("#bevy".to_string());
+ let sender = scene.get_processor().sender;
+ let (sender_sig, _set_sender_sig) = create_signal(sender);
+ let (scene_sig, _set_scene_sig) = create_signal(scene);
+
+ // We need to add the 3D view onto the canvas post render.
+ create_effect(move |_| {
+ request_animation_frame(move || {
+ scene_sig.get().setup();
+ });
+ });
+
+ view! {
+