diff --git a/Cargo.lock b/Cargo.lock index e254674be..af81d8525 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -827,6 +827,7 @@ dependencies = [ "blitz-traits", "dioxus", "dioxus-native", + "dioxus-native-dom", "env_logger", "euclid", "image", @@ -835,6 +836,7 @@ dependencies = [ "reqwest", "tokio", "tracing-subscriber", + "winit", ] [[package]] @@ -2085,6 +2087,7 @@ dependencies = [ "dioxus-native-dom", "dioxus-signals", "dioxus-stores", + "futures-channel", "futures-util", "keyboard-types", "manganis", diff --git a/Cargo.toml b/Cargo.toml index 42529f71e..129dcd447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ dioxus-asset-resolver = { version = "0.7.3" } manganis = { version = "0.7.3" } dioxus-document = { version = "0.7.3" } dioxus-history = { version = "0.7.3" } +futures-channel = "0.3" # Dioxus hot reload dioxus-devtools = { version = "0.7.3" } @@ -231,6 +232,7 @@ blitz-net = { workspace = true } blitz = { workspace = true, features = ["net"] } dioxus = { workspace = true } dioxus-native = { workspace = true, features = ["vello", "floats", "svg", "prelude"] } +dioxus-native-dom = { workspace = true } euclid = { workspace = true } reqwest = { workspace = true } tokio = { workspace = true, features = ["macros"] } @@ -239,6 +241,7 @@ png = { workspace = true } peniko = { workspace = true } env_logger = "0.11" tracing-subscriber = "0.3" +winit = { workspace = true } # [patch.crates-io] # anyrender = { path = "../anyrender/crates/anyrender" } @@ -255,4 +258,4 @@ tracing-subscriber = "0.3" # [patch."https://github.com/nicoburns/parley"] # parley = { path = "../parley/parley" } -# fontique = { path = "../parley/fontique" } \ No newline at end of file +# fontique = { path = "../parley/fontique" } diff --git a/README.md b/README.md index ed7f2a0d9..25251712e 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,11 @@ Check out the [roadmap issue](https://github.com/DioxusLabs/blitz/issues/119) fo cargo run --release --package wgpu_texture ``` + - multi-window demo + ```sh + cargo run --example multi_window + ``` + Other examples are available in the [examples/](./examples/) folder. ## Goals diff --git a/examples/multi_window.rs b/examples/multi_window.rs new file mode 100644 index 000000000..f0d2c7362 --- /dev/null +++ b/examples/multi_window.rs @@ -0,0 +1,83 @@ +//! Demonstrate opening additional windows from a pure Dioxus app. + +use blitz_traits::shell::ShellProvider; +use dioxus::prelude::*; +use dioxus_native::DioxusNativeProvider; +use std::sync::Arc; +use std::sync::Weak; +use winit::window::Window; +use winit::window::WindowId; +use winit::window::{WindowAttributes, WindowButtons}; + +fn main() { + // Demonstrate how to pass custom WindowAttributes (title, size, decorations). + let attrs = WindowAttributes::default() + .with_title("Multi-window Demo") + .with_inner_size(winit::dpi::LogicalSize::new(600.0, 800.0)) + .with_enabled_buttons(WindowButtons::all()); + dioxus_native::launch_cfg(app, vec![], vec![Box::new(attrs)]); +} + +fn app() -> Element { + let provider = use_context::(); + let mut counter = use_signal(|| 0u32); + let spawned_windows = use_signal(Vec::<(WindowId, Weak)>::new); + + rsx! { + main { + h1 { "Blitz multi-window" } + p { "Click the button to open another RSX window." } + div { + button { + onclick: move |_| { + let vdom = VirtualDom::new(secondary_window); + let title = format!("window#{}", counter()); + let attributes = WindowAttributes::default() + .with_title(title) + .with_inner_size(winit::dpi::LogicalSize::new(400.0, 300.0)); + let config = dioxus_native::Config::new().with_window_attributes(attributes); + let pending = provider.new_window(vdom, config); + let mut spawned_windows = spawned_windows; + spawn(async move { + let (window_id, window) = pending.await; + let mut next = spawned_windows(); + next.push((window_id, Arc::downgrade(&window))); + spawned_windows.set(next); + }); + counter += 1; + }, + "Open secondary window" + } + } + + h2 { "Spawned windows" } + ul { + {spawned_windows().into_iter().map(|(id, window)| match window.upgrade() { + Some(window) => { + let title = window.title(); + let title = if title.is_empty() { String::from("") } else { title }; + rsx! { li { "{title} (ID: {id:?})" } } + } + None => rsx! { li { " (ID: {id:?})" } }, + })} + } + } + } +} + +fn secondary_window() -> Element { + let shell_provider = use_context::>(); + + rsx! { + main { + h1 { "Secondary window" } + p { "This content comes from another RSX function." } + button { + onclick: move |_| { + shell_provider.set_window_title(format!("Time: {:?}", std::time::SystemTime::now())) + }, + "click to update title", + } + } + } +} diff --git a/packages/dioxus-native/Cargo.toml b/packages/dioxus-native/Cargo.toml index 91b413412..4b48c90f2 100644 --- a/packages/dioxus-native/Cargo.toml +++ b/packages/dioxus-native/Cargo.toml @@ -38,7 +38,7 @@ vello-cpu-base = ["alt-renderer", "dep:anyrender_vello_cpu"] alt-renderer = [] # Other features -net = ["dep:tokio", "dep:blitz-net"] +net = ["dep:blitz-net", "dep:tokio"] html = ["dep:blitz-html"] # Dev @@ -94,8 +94,9 @@ manganis = { workspace = true, features = ["dioxus"], optional = true } winit = { workspace = true } keyboard-types = { workspace = true } -# IO & Networking +# Runtime + channels tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } +futures-channel = { workspace = true } webbrowser = { workspace = true } # Other dependencies diff --git a/packages/dioxus-native/src/dioxus_application.rs b/packages/dioxus-native/src/dioxus_application.rs index 4622b30a5..f2d3c637a 100644 --- a/packages/dioxus-native/src/dioxus_application.rs +++ b/packages/dioxus-native/src/dioxus_application.rs @@ -1,14 +1,99 @@ +use blitz_shell::WindowConfig; use blitz_shell::{BlitzApplication, View}; +use blitz_traits::{navigation::NavigationProvider, net::NetProvider}; use dioxus_core::{provide_context, ScopeId}; use dioxus_history::{History, MemoryHistory}; +use futures_channel::oneshot; +use std::any::Any; use std::rc::Rc; +use std::sync::Arc; use winit::application::ApplicationHandler; + use winit::event::{StartCause, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; +use winit::window::Window; use winit::window::WindowId; +use blitz_dom::DocumentConfig; +use blitz_dom::HtmlParserProvider; + use crate::DioxusNativeWindowRenderer; -use crate::{contexts::DioxusNativeDocument, BlitzShellEvent, DioxusDocument, WindowConfig}; +use crate::{ + contexts::DioxusNativeDocument, BlitzShellEvent, Config, DioxusDocument, + PendingWindowContext, WindowCreated, +}; + +#[repr(transparent)] +#[doc(hidden)] +pub struct OpaquePtr(*mut T); + +impl Copy for OpaquePtr {} + +impl Clone for OpaquePtr { + fn clone(&self) -> Self { + *self + } +} + +// Safety: this is intentionally used to smuggle single-threaded payloads through an embedder +// event channel that requires `Send + Sync`. Correctness relies on the caller ensuring that the +// payload is only ever created + consumed within the same thread/affinity assumptions as before. +unsafe impl Send for OpaquePtr {} +unsafe impl Sync for OpaquePtr {} + +impl OpaquePtr { + pub fn from_box(value: Box) -> Self { + Self(Box::into_raw(value)) + } +} + +#[doc(hidden)] +pub struct UnsafeBox { + value: Option>, + owner: std::thread::ThreadId, +} + +// Safety: this wrapper is only used to cross an event channel boundary and is guarded by +// runtime thread-affinity checks. Misuse will panic instead of causing UB. +unsafe impl Send for UnsafeBox {} +unsafe impl Sync for UnsafeBox {} + +impl UnsafeBox { + pub fn new(value: Box) -> Self { + Self { + value: Some(value), + owner: std::thread::current().id(), + } + } + + pub fn into_inner(mut self) -> Box { + self.assert_owner(); + self.value + .take() + .expect("UnsafeBox inner value already taken") + } + + fn assert_owner(&self) { + assert_eq!( + self.owner, + std::thread::current().id(), + "UnsafeBox accessed from a different thread", + ); + } +} + +impl Drop for UnsafeBox { + fn drop(&mut self) { + if self.value.is_some() { + self.assert_owner(); + } + } +} + +type ContextProvider = Box Box + Send + Sync>; +type ContextProviders = Arc>; +type CreateWindowReply = UnsafeBox>; +type CreateWindowReplyOpt = Option; /// Dioxus-native specific event type pub enum DioxusNativeEvent { @@ -18,30 +103,111 @@ pub enum DioxusNativeEvent { /// Create a new head element from the Link and Title elements /// - /// todo(jon): these should probabkly be synchronous somehow + /// todo(jon): these should probably be synchronous somehow CreateHeadElement { window: WindowId, name: String, attributes: Vec<(String, String)>, contents: Option, }, + + /// Spawn a pre-constructed window. + /// + /// # Safety + /// The pointers must come from `Box::into_raw` on the same process, and must be sent exactly + /// once; they will be reclaimed by the event loop thread via `Box::from_raw`. + CreateDocumentWindow { + vdom: UnsafeBox, + config: Config, + reply: CreateWindowReplyOpt, + }, + + GetWindow { + window_id: WindowId, + reply: UnsafeBox>>>, + }, } pub struct DioxusNativeApplication { - pending_window: Option>, inner: BlitzApplication, proxy: EventLoopProxy, + + renderer_factory: Arc DioxusNativeWindowRenderer + Send + Sync>, + + contexts: ContextProviders, + net_provider: Arc, + #[cfg(feature = "net")] + inner_net_provider: Option>, + html_parser_provider: Option>, + navigation_provider: Option>, +} + +#[derive(Clone)] +pub struct DioxusNativeProvider { + proxy: EventLoopProxy, +} + +impl DioxusNativeProvider { + pub(crate) fn new(proxy: EventLoopProxy) -> Self { + Self { proxy } + } + + pub fn create_document_window( + &self, + vdom: dioxus_core::VirtualDom, + config: Config, + ) -> PendingWindowContext { + let (sender, receiver) = oneshot::channel(); + let vdom = UnsafeBox::new(Box::new(vdom)); + let reply = Some(UnsafeBox::new(Box::new(sender))); + let _ = self.proxy.send_event(BlitzShellEvent::embedder_event( + DioxusNativeEvent::CreateDocumentWindow { + vdom, + config, + reply, + }, + )); + PendingWindowContext::new(receiver) + } + + pub fn new_window( + &self, + vdom: dioxus_core::VirtualDom, + config: Config, + ) -> PendingWindowContext { + self.create_document_window(vdom, config) + } + + pub fn get_window(&self, window_id: WindowId) -> oneshot::Receiver>> { + let (sender, receiver) = oneshot::channel(); + let reply = UnsafeBox::new(Box::new(sender)); + let _ = self.proxy.send_event(BlitzShellEvent::embedder_event( + DioxusNativeEvent::GetWindow { window_id, reply }, + )); + receiver + } } impl DioxusNativeApplication { - pub fn new( + pub(crate) fn new( proxy: EventLoopProxy, - config: WindowConfig, + renderer_factory: Arc DioxusNativeWindowRenderer + Send + Sync>, + contexts: ContextProviders, + net_provider: Arc, + #[cfg(feature = "net")] inner_net_provider: Option>, + html_parser_provider: Option>, + navigation_provider: Option>, ) -> Self { Self { - pending_window: Some(config), inner: BlitzApplication::new(proxy.clone()), proxy, + renderer_factory, + contexts, + net_provider, + #[cfg(feature = "net")] + inner_net_provider, + html_parser_provider, + navigation_provider, } } @@ -49,11 +215,101 @@ impl DioxusNativeApplication { self.inner.add_window(window_config); } - fn handle_blitz_shell_event( + fn _spawn_window( + &mut self, + config: WindowConfig, + event_loop: &ActiveEventLoop, + ) -> (WindowId, Arc) { + let mut window = View::init(config, event_loop, &self.proxy); + self.inject_window_contexts(&mut window); + + window.resume(); + + let window_id = window.window_id(); + let window_arc = Arc::clone(&window.window); + self.inner.windows.insert(window_id, window); + + (window_id, window_arc) + } + + fn inject_window_contexts(&self, window: &mut View) { + let renderer = window.renderer.clone(); + let window_id = window.window_id(); + let doc = window.downcast_doc_mut::(); + + doc.vdom.in_scope(ScopeId::ROOT, || { + let shared: Rc = + Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id)); + provide_context(shared); + }); + + let shell_provider = doc.inner.borrow().shell_provider.clone(); + doc.vdom + .in_scope(ScopeId::ROOT, move || provide_context(shell_provider)); + + let history_provider: Rc = Rc::new(MemoryHistory::default()); + doc.vdom + .in_scope(ScopeId::ROOT, move || provide_context(history_provider)); + + doc.vdom + .in_scope(ScopeId::ROOT, move || provide_context(renderer)); + + doc.initial_build(); + window.request_redraw(); + } + + fn create_window( &mut self, event_loop: &ActiveEventLoop, - event: &DioxusNativeEvent, + vdom: UnsafeBox, + config: Config, + reply: CreateWindowReplyOpt, ) { + let mut vdom = *vdom.into_inner(); + + // Make the event loop proxy available to user components. + vdom.provide_root_context(self.proxy.clone()); + + // Provide a minimal, stable API for spawning additional windows. + vdom.provide_root_context(DioxusNativeProvider::new(self.proxy.clone())); + + #[cfg(feature = "net")] + if let Some(inner) = &self.inner_net_provider { + vdom.provide_root_context(Arc::clone(inner)); + } + + let contexts = &self.contexts; + for context in contexts.iter() { + vdom.insert_any_root_context(context()); + } + vdom.provide_root_context(self.net_provider.clone()); + if let Some(parser) = self.html_parser_provider.clone() { + vdom.provide_root_context(parser); + } + if let Some(nav) = self.navigation_provider.clone() { + vdom.provide_root_context(nav); + } + let document = DioxusDocument::new( + vdom, + DocumentConfig { + net_provider: Some(self.net_provider.clone()), + html_parser_provider: self.html_parser_provider.clone(), + navigation_provider: self.navigation_provider.clone(), + ..Default::default() + }, + ); + let doc = Box::new(document) as _; + + let renderer = (self.renderer_factory)(); + let config = WindowConfig::with_attributes(doc, renderer, config.into_window_attributes()); + let (window_id, window_arc) = self._spawn_window(config, event_loop); + + if let Some(reply) = reply { + let _ = reply.into_inner().send((window_id, window_arc)); + } + } + + fn handle_blitz_shell_event(&mut self, event_loop: &ActiveEventLoop, event: DioxusNativeEvent) { match event { #[cfg(all(feature = "hot-reload", debug_assertions))] DioxusNativeEvent::DevserverEvent(event) => match event { @@ -62,7 +318,7 @@ impl DioxusNativeApplication { let doc = window.downcast_doc_mut::(); // Apply changes to vdom - dioxus_devtools::apply_changes(&doc.vdom, hotreload_message); + dioxus_devtools::apply_changes(&doc.vdom, &hotreload_message); // Reload changed assets for asset_path in &hotreload_message.assets { @@ -87,19 +343,36 @@ impl DioxusNativeApplication { contents, window, } => { - if let Some(window) = self.inner.windows.get_mut(window) { + if let Some(window) = self.inner.windows.get_mut(&window) { let doc = window.downcast_doc_mut::(); - doc.create_head_element(name, attributes, contents); + doc.create_head_element(&name, &attributes, &contents); window.poll(); } } + DioxusNativeEvent::CreateDocumentWindow { + vdom, + config, + reply, + } => { + self.create_window(event_loop, vdom, config, reply); + } + + DioxusNativeEvent::GetWindow { window_id, reply } => { + let window = self + .inner + .windows + .get(&window_id) + .map(|view| Arc::clone(&view.window)); + let _ = reply.into_inner().send(window); + } + // Suppress unused variable warning #[cfg(not(all(feature = "hot-reload", debug_assertions)))] #[allow(unreachable_patterns)] _ => { let _ = event_loop; - let _ = event; + let _ = &event; } } } @@ -109,43 +382,6 @@ impl ApplicationHandler for DioxusNativeApplication { fn resumed(&mut self, event_loop: &ActiveEventLoop) { #[cfg(feature = "tracing")] tracing::debug!("Injecting document provider into all windows"); - - if let Some(config) = self.pending_window.take() { - let mut window = View::init(config, event_loop, &self.proxy); - let renderer = window.renderer.clone(); - let window_id = window.window_id(); - let doc = window.downcast_doc_mut::(); - - doc.vdom.in_scope(ScopeId::ROOT, || { - let shared: Rc = - Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id)); - provide_context(shared); - }); - - // Add shell provider - let shell_provider = doc.inner.borrow().shell_provider.clone(); - doc.vdom - .in_scope(ScopeId::ROOT, move || provide_context(shell_provider)); - - // Add history - let history_provider: Rc = Rc::new(MemoryHistory::default()); - doc.vdom - .in_scope(ScopeId::ROOT, move || provide_context(history_provider)); - - // Add renderer - doc.vdom - .in_scope(ScopeId::ROOT, move || provide_context(renderer)); - - // Queue rebuild - doc.initial_build(); - - // And then request redraw - window.request_redraw(); - - // todo(jon): we should actually mess with the pending windows instead of passing along the contexts - self.inner.windows.insert(window_id, window); - } - self.inner.resumed(event_loop); } @@ -169,8 +405,17 @@ impl ApplicationHandler for DioxusNativeApplication { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: BlitzShellEvent) { match event { BlitzShellEvent::Embedder(event) => { - if let Some(event) = event.downcast_ref::() { - self.handle_blitz_shell_event(event_loop, event); + match std::sync::Arc::downcast::(event) { + Ok(event) => { + if let Ok(event) = std::sync::Arc::try_unwrap(event) { + self.handle_blitz_shell_event(event_loop, event); + } else { + unreachable!("Dioxus embedder event unexpectedly shared"); + } + } + Err(_event) => { + unreachable!("Unhandled embedder event"); + } } } event => self.inner.user_event(event_loop, event), diff --git a/packages/dioxus-native/src/lib.rs b/packages/dioxus-native/src/lib.rs index cc966a617..383230418 100644 --- a/packages/dioxus-native/src/lib.rs +++ b/packages/dioxus-native/src/lib.rs @@ -14,6 +14,7 @@ mod contexts; mod dioxus_application; mod dioxus_renderer; mod link_handler; +// windowing module removed #[cfg(feature = "prelude")] pub mod prelude; @@ -24,6 +25,11 @@ use blitz_traits::net::NetProvider; pub use dioxus_native_dom::*; use assets::DioxusNativeNetProvider; +pub use dioxus_application::DioxusNativeProvider; +#[doc(hidden)] +pub use dioxus_application::OpaquePtr; +#[doc(hidden)] +pub use dioxus_application::UnsafeBox; pub use dioxus_application::{DioxusNativeApplication, DioxusNativeEvent}; pub use dioxus_renderer::DioxusNativeWindowRenderer; @@ -58,12 +64,91 @@ pub use { dioxus_renderer::{use_wgpu, Features, Limits}, }; -use blitz_shell::{create_default_event_loop, BlitzShellEvent, Config, WindowConfig}; +use blitz_shell::{create_default_event_loop, BlitzShellEvent, Config as BlitzConfig}; use dioxus_core::{ComponentFunction, Element, VirtualDom}; +use futures_channel::oneshot; use link_handler::DioxusNativeNavigationProvider; use std::any::Any; +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; -use winit::window::WindowAttributes; +use winit::window::{Window, WindowAttributes, WindowId}; + +/// Window configuration for Dioxus Native. +#[derive(Clone)] +pub struct Config { + window_attributes: WindowAttributes, +} + +impl Default for Config { + fn default() -> Self { + Self { + window_attributes: WindowAttributes::default(), + } + } +} + +impl Config { + pub fn new() -> Self { + Self::default() + } + + pub fn with_window(mut self, window_attributes: WindowAttributes) -> Self { + self.window_attributes = window_attributes; + self + } + + pub fn with_window_attributes(self, window_attributes: WindowAttributes) -> Self { + self.with_window(window_attributes) + } + + pub fn window_attributes(&self) -> &WindowAttributes { + &self.window_attributes + } + + pub fn into_window_attributes(self) -> WindowAttributes { + self.window_attributes + } +} + +impl From for Config { + fn from(window_attributes: WindowAttributes) -> Self { + Self { window_attributes } + } +} + +pub type WindowCreated = (WindowId, Arc); + +pub struct PendingWindowContext { + receiver: oneshot::Receiver, +} + +impl PendingWindowContext { + pub(crate) fn new(receiver: oneshot::Receiver) -> Self { + Self { receiver } + } + + pub async fn resolve(self) -> WindowCreated { + self.try_resolve() + .await + .expect("Failed to resolve pending window context") + } + + pub async fn try_resolve( + self, + ) -> Result { + self.receiver.await + } +} + +impl std::future::IntoFuture for PendingWindowContext { + type Output = WindowCreated; + type IntoFuture = Pin>>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(self.resolve()) + } +} /// Launch an interactive HTML/CSS renderer driven by the Dioxus virtualdom pub fn launch(app: fn() -> Element) { @@ -126,11 +211,12 @@ pub fn launch_cfg_with_props( cfg = try_read_config!(cfg, limits, Limits); } cfg = try_read_config!(cfg, window_attributes, WindowAttributes); - cfg = try_read_config!(cfg, _config, Config); + cfg = try_read_config!(cfg, _config, BlitzConfig); let _ = cfg; } let event_loop = create_default_event_loop::(); + let proxy = event_loop.create_proxy(); // Turn on the runtime and enter it #[cfg(feature = "net")] @@ -154,38 +240,34 @@ pub fn launch_cfg_with_props( // Spin up the virtualdom // We're going to need to hit it with a special waker // Note that we are delaying the initialization of window-specific contexts (net provider, document, etc) - let mut vdom = VirtualDom::new_with_props(app, props); - - // Add contexts - for context in contexts { - vdom.insert_any_root_context(context()); - } + let contexts = Arc::new(contexts); + let app = app.clone(); #[cfg(feature = "net")] - let net_provider = { + let (net_provider, inner_net_provider) = { use blitz_shell::BlitzShellNetWaker; let proxy = event_loop.create_proxy(); let net_waker = Some(BlitzShellNetWaker::shared(proxy.clone())); let inner_net_provider = Arc::new(blitz_net::Provider::new(net_waker.clone())); - vdom.provide_root_context(Arc::clone(&inner_net_provider)); - Arc::new(DioxusNativeNetProvider::with_inner( + let net_provider = Arc::new(DioxusNativeNetProvider::with_inner( proxy, - inner_net_provider as _, - )) as Arc + Arc::clone(&inner_net_provider) as _, + )) as Arc; + + (net_provider, Some(inner_net_provider)) }; #[cfg(not(feature = "net"))] let net_provider = DioxusNativeNetProvider::shared(event_loop.create_proxy()); - vdom.provide_root_context(Arc::clone(&net_provider)); + // contexts/providers are injected via window runtime #[cfg(feature = "html")] let html_parser_provider = { let html_parser = Arc::new(blitz_html::HtmlProvider) as _; - vdom.provide_root_context(Arc::clone(&html_parser)); Some(html_parser) }; #[cfg(not(feature = "html"))] @@ -193,16 +275,6 @@ pub fn launch_cfg_with_props( let navigation_provider = Some(Arc::new(DioxusNativeNavigationProvider) as _); - // Create document + window from the baked virtualdom - let doc = DioxusDocument::new( - vdom, - DocumentConfig { - net_provider: Some(net_provider), - html_parser_provider, - navigation_provider, - ..Default::default() - }, - ); #[cfg(any( feature = "vello", all( @@ -210,7 +282,13 @@ pub fn launch_cfg_with_props( not(all(target_os = "ios", target_abi = "sim")) ) ))] - let renderer = DioxusNativeWindowRenderer::with_features_and_limits(features, limits); + let renderer_factory: Arc DioxusNativeWindowRenderer + Send + Sync> = { + let features = features; + let limits = limits.clone(); + Arc::new(move || { + DioxusNativeWindowRenderer::with_features_and_limits(features, limits.clone()) + }) + }; #[cfg(not(any( feature = "vello", all( @@ -218,15 +296,34 @@ pub fn launch_cfg_with_props( not(all(target_os = "ios", target_abi = "sim")) ) )))] - let renderer = DioxusNativeWindowRenderer::new(); - let config = WindowConfig::with_attributes( - Box::new(doc) as _, - renderer.clone(), - window_attributes.unwrap_or_default(), + let renderer_factory: Arc DioxusNativeWindowRenderer + Send + Sync> = + Arc::new(DioxusNativeWindowRenderer::new); + + let window_attributes = window_attributes.unwrap_or_default(); + let window_config = Config::from(window_attributes); + + let vdom = VirtualDom::new_with_props(app, props); + let vdom = UnsafeBox::new(Box::new(vdom)); + + let mut application = DioxusNativeApplication::new( + proxy.clone(), + renderer_factory, + Arc::clone(&contexts), + Arc::clone(&net_provider), + #[cfg(feature = "net")] + inner_net_provider, + html_parser_provider.clone(), + navigation_provider.clone(), ); - // Create application - let mut application = DioxusNativeApplication::new(event_loop.create_proxy(), config); + // Queue the initial window creation via an embedder event. + let _ = proxy.send_event(BlitzShellEvent::embedder_event( + DioxusNativeEvent::CreateDocumentWindow { + vdom, + config: window_config, + reply: None, + }, + )); // Run event loop event_loop.run_app(&mut application).unwrap();