diff --git a/packages/native/Cargo.toml b/packages/native/Cargo.toml index 9fb5d048a8..a617a40747 100644 --- a/packages/native/Cargo.toml +++ b/packages/native/Cargo.toml @@ -10,9 +10,12 @@ homepage = "https://dioxuslabs.com/learn/0.7/getting_started" keywords = ["dom", "ui", "gui", "react"] [features] -default = ["accessibility", "hot-reload", "net", "html", "svg", "system-fonts", "clipboard", "file_dialog", "vello-hybrid", "incremental", "woff", "apple-font-embolden"] +default = ["shell", "accessibility", "hot-reload", "net", "html", "svg", "system-fonts", "clipboard", "file_dialog", "vello-hybrid", "incremental", "woff", "apple-font-embolden"] prelude = ["dep:dioxus-core-macro", "dep:dioxus-hooks", "dep:dioxus-signals", "dep:dioxus-stores", "dep:manganis"] +# Blitz-shell/Winit integration. +shell = ["dep:blitz-shell", "dep:winit"] + # DOM features svg = ["blitz-dom/svg", "blitz-paint/svg"] floats = ["blitz-dom/floats"] @@ -24,11 +27,11 @@ system-fonts = ["blitz-dom/system_fonts"] woff = ["blitz-dom/woff"] # Shell Features -clipboard = ["blitz-shell/clipboard"] -file_dialog = ["file-dialog"] # Backwards compat. Remove in next breaking version -file-dialog = ["blitz-shell/file_dialog"] -accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"] -data-uri = ["blitz-shell/data-uri"] +clipboard = ["shell", "blitz-shell/clipboard"] +file_dialog = ["shell", "file-dialog"] # Backwards compat. Remove in next breaking version +file-dialog = ["shell", "blitz-shell/file_dialog"] +accessibility = ["shell", "blitz-shell/accessibility", "blitz-dom/accessibility"] +data-uri = ["shell", "blitz-shell/data-uri"] # Renderer features vello = ["dep:anyrender_vello", "dep:wgpu_context"] @@ -60,7 +63,7 @@ log-frame-times = [ "anyrender_vello_hybrid?/log_frame_times", "anyrender_skia?/log_frame_times", ] -tracing = ["dep:tracing", "dioxus-native-dom/tracing", "blitz-shell/tracing", "blitz-dom/tracing", "blitz-html/tracing", "blitz-net/tracing"] +tracing = ["dep:tracing", "dioxus-native-dom/tracing", "blitz-shell?/tracing", "blitz-dom/tracing", "blitz-html/tracing", "blitz-net/tracing"] [dependencies] # Blitz dependencies @@ -69,7 +72,7 @@ blitz-html = { workspace = true, optional = true } blitz-net = { workspace = true, optional = true } blitz-paint = { workspace = true, optional = true, features = ["custom-widget"] } blitz-traits = { workspace = true } -blitz-shell = { workspace = true } +blitz-shell = { workspace = true, optional = true } # AnyRender dependencies anyrender = { workspace = true } @@ -99,7 +102,7 @@ dioxus-core-macro = { workspace = true, optional = true } manganis = { workspace = true, features = ["dioxus"], optional = true } # Windowing & Input -winit = { workspace = true } +winit = { workspace = true, optional = true } keyboard-types = { workspace = true } # IO & Networking @@ -115,8 +118,8 @@ cfg-if = "1.0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } -[target.'cfg(target_os = "android")'.dependencies] -android-activity = { version = "0.6", default-features = false } +[target.'cfg(all(target_os = "android", feature = "shell"))'.dependencies] +android-activity = { version = "0.6", default-features = false, optional = true } [target.'cfg(all(target_os = "ios", target_abi = "sim"))'.dependencies] anyrender_vello_cpu = { workspace = true, features = ["pixels_window_renderer"]} diff --git a/packages/native/src/config.rs b/packages/native/src/config.rs index 34194e8181..8beeef4d35 100644 --- a/packages/native/src/config.rs +++ b/packages/native/src/config.rs @@ -1,9 +1,11 @@ use blitz_dom::FontContext; use dioxus_core::LaunchConfig; +#[cfg(feature = "shell")] use winit::window::WindowAttributes; /// Launch-time configuration for a dioxus-native application. pub struct Config { + #[cfg(feature = "shell")] pub(crate) window_attributes: WindowAttributes, pub(crate) font_ctx: Option, } @@ -12,6 +14,7 @@ impl LaunchConfig for Config {} impl Default for Config { fn default() -> Self { + #[cfg(feature = "shell")] let window_attributes = WindowAttributes::default() .with_title(dioxus_cli_config::app_title().unwrap_or_else(|| "Dioxus App".to_string())); @@ -21,7 +24,7 @@ impl Default for Config { // the canvas's CSS layout box when winit reports a 0×0 initial size. // To target an existing ``, replace these attributes via // [`Config::with_window_attributes`]. - #[cfg(target_arch = "wasm32")] + #[cfg(all(feature = "shell", target_arch = "wasm32"))] let window_attributes = { use winit::platform::web::WindowAttributesWeb; window_attributes.with_platform_attributes(Box::new( @@ -30,6 +33,7 @@ impl Default for Config { }; Self { + #[cfg(feature = "shell")] window_attributes, font_ctx: None, } @@ -42,6 +46,7 @@ impl Config { } /// Set the configuration for the window. + #[cfg(feature = "shell")] pub fn with_window_attributes(mut self, attrs: WindowAttributes) -> Self { self.window_attributes = attrs; self diff --git a/packages/native/src/lib.rs b/packages/native/src/lib.rs index 4bd8a31d40..ec53ddb62e 100644 --- a/packages/native/src/lib.rs +++ b/packages/native/src/lib.rs @@ -8,45 +8,21 @@ //! - `hot-reload`: Enables hot-reloading of Dioxus RSX. //! - `menu`: Enables the [`muda`](https://docs.rs/muda/latest/muda/) menubar. //! - `tracing`: Enables tracing support. +//! - `shell`: Enables window and event-loop integration with blitz-shell and winit. -mod assets; mod config; -mod contexts; -mod dioxus_application; mod dioxus_renderer; -mod link_handler; +#[cfg(feature = "shell")] +mod shell; #[cfg(feature = "prelude")] pub mod prelude; -#[cfg(all(feature = "net", not(target_arch = "wasm32")))] -use blitz_traits::net::NetProvider; #[doc(inline)] pub use dioxus_native_dom::*; -use assets::DioxusNativeNetProvider; -pub use dioxus_application::{DioxusNativeApplication, DioxusNativeEvent}; pub use dioxus_renderer::DioxusNativeWindowRenderer; -#[cfg(target_os = "android")] -#[cfg_attr(docsrs, doc(cfg(target_os = "android")))] -/// Set the current [`AndroidApp`](android_activity::AndroidApp). -pub fn set_android_app(app: android_activity::AndroidApp) { - blitz_shell::set_android_app(app); -} - -#[cfg(target_os = "android")] -#[cfg_attr(docsrs, doc(cfg(target_os = "android")))] -/// Get the current [`AndroidApp`](android_activity::AndroidApp). -/// This will panic if the android activity has not been setup with [`set_android_app`]. -pub fn current_android_app() -> android_activity::AndroidApp { - blitz_shell::current_android_app() -} - -#[cfg(target_os = "android")] -#[cfg_attr(docsrs, doc(cfg(target_os = "android")))] -pub use android_activity::AndroidApp; - #[cfg(any(feature = "vello", feature = "vello-hybrid"))] pub use { dioxus_renderer::{Features, Limits}, @@ -55,175 +31,6 @@ pub use { pub use blitz_dom::{FontContext, Widget, build_single_font_ctx}; pub use config::Config; -pub use winit::dpi::{LogicalSize, PhysicalSize}; -pub use winit::window::WindowAttributes; - -use blitz_shell::{BlitzShellEvent, BlitzShellProxy, WindowConfig, create_default_event_loop}; -use dioxus_core::{ComponentFunction, Element, VirtualDom, consume_context, use_hook}; -use link_handler::DioxusNativeNavigationProvider; -use std::any::Any; -use std::sync::Arc; -use winit::{ - raw_window_handle::{HasWindowHandle as _, RawWindowHandle}, - window::Window, -}; - -pub fn use_window() -> Arc { - use_hook(consume_context::>) -} - -pub fn use_raw_window_handle() -> RawWindowHandle { - use_hook(|| { - consume_context::>() - .window_handle() - .unwrap() - .as_raw() - }) -} - -/// Launch an interactive HTML/CSS renderer driven by the Dioxus virtualdom -pub fn launch(app: fn() -> Element) { - launch_cfg(app, vec![], vec![]) -} - -pub fn launch_cfg( - app: fn() -> Element, - contexts: Vec Box + Send + Sync>>, - cfg: Vec>, -) { - launch_cfg_with_props(app, (), contexts, cfg) -} - -// todo: props shouldn't have the clone bound - should try and match dioxus-desktop behavior -pub fn launch_cfg_with_props( - app: impl ComponentFunction, - props: P, - contexts: Vec Box + Send + Sync>>, - configs: Vec>, -) { - // Macro to attempt to downcast a type out of a Box - macro_rules! try_read_config { - ($input:ident, $store:ident, $kind:ty) => { - // Try to downcast the Box to type $kind - match $input.downcast::<$kind>() { - // If the type matches then write downcast value to variable $store - Ok(value) => { - $store = Some(*value); - continue; - } - // Else extract the original Box value out of the error type - // and return it so that we can try again with a different type. - Err(cfg) => cfg, - } - }; - } - - // Read config values - #[cfg(any(feature = "vello", feature = "vello-hybrid"))] - let (mut features, mut limits) = (None, None); - let mut window_attributes = None; - let mut config = None; - for mut cfg in configs { - #[cfg(any(feature = "vello", feature = "vello-hybrid"))] - { - cfg = try_read_config!(cfg, features, Features); - cfg = try_read_config!(cfg, limits, Limits); - } - cfg = try_read_config!(cfg, window_attributes, WindowAttributes); - cfg = try_read_config!(cfg, config, Config); - let _ = cfg; - } - - let mut config = config.unwrap_or_default(); - if let Some(window_attributes) = window_attributes { - config.window_attributes = window_attributes; - } - let event_loop = create_default_event_loop(); - let winit_proxy = event_loop.create_proxy(); - let (proxy, event_queue) = BlitzShellProxy::new(winit_proxy); - - // Turn on the runtime and enter it - #[cfg(feature = "net")] - #[cfg(not(target_arch = "wasm32"))] - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - #[cfg(feature = "net")] - #[cfg(not(target_arch = "wasm32"))] - let _guard = rt.enter(); - - // Setup hot-reloading if enabled. - #[cfg(all(feature = "hot-reload", debug_assertions))] - #[cfg(not(target_arch = "wasm32"))] - { - let proxy = proxy.clone(); - dioxus_devtools::connect(move |event| { - let dxn_event = DioxusNativeEvent::DevserverEvent(event); - proxy.send_event(BlitzShellEvent::embedder_event(dxn_event)); - }) - } - - // Build the vdom first; the net provider, document, and other window-bound - // contexts are attached below once the event-loop proxy exists. - let mut vdom = VirtualDom::new_with_props(app, props); - - for context in contexts { - vdom.insert_any_root_context(context()); - } - - #[cfg(all(feature = "net", not(target_arch = "wasm32")))] - let net_provider = { - let net_waker = Some(Arc::new(proxy.clone()) as _); - let inner_net_provider = Arc::new(blitz_net::Provider::new(net_waker)); - vdom.provide_root_context(Arc::clone(&inner_net_provider)); - - Arc::new(DioxusNativeNetProvider::with_inner( - proxy.clone(), - inner_net_provider as _, - )) as Arc - }; - - #[cfg(any(not(feature = "net"), target_arch = "wasm32"))] - let net_provider = DioxusNativeNetProvider::shared(proxy.clone()); - - vdom.provide_root_context(Arc::clone(&net_provider)); - - #[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"))] - let html_parser_provider = None; - - 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, - font_ctx: config.font_ctx, - ..Default::default() - }, - ); - #[cfg(any(feature = "vello", feature = "vello-hybrid"))] - let renderer = DioxusNativeWindowRenderer::with_features_and_limits(features, limits); - #[cfg(not(any(feature = "vello", feature = "vello-hybrid")))] - let renderer = DioxusNativeWindowRenderer::new(); - let config = WindowConfig::with_attributes( - Box::new(doc) as _, - renderer.clone(), - config.window_attributes, - ); - - // Create application - let application = DioxusNativeApplication::new(proxy, event_queue, config); - // Run event loop - event_loop.run_app(application).unwrap(); -} +#[cfg(feature = "shell")] +pub use shell::*; diff --git a/packages/native/src/assets.rs b/packages/native/src/shell/assets.rs similarity index 100% rename from packages/native/src/assets.rs rename to packages/native/src/shell/assets.rs diff --git a/packages/native/src/contexts.rs b/packages/native/src/shell/contexts.rs similarity index 100% rename from packages/native/src/contexts.rs rename to packages/native/src/shell/contexts.rs diff --git a/packages/native/src/dioxus_application.rs b/packages/native/src/shell/dioxus_application.rs similarity index 98% rename from packages/native/src/dioxus_application.rs rename to packages/native/src/shell/dioxus_application.rs index cfb277f923..16df030327 100644 --- a/packages/native/src/dioxus_application.rs +++ b/packages/native/src/shell/dioxus_application.rs @@ -11,8 +11,10 @@ use winit::window::WindowId; #[cfg(target_os = "macos")] use winit::platform::macos::ApplicationHandlerExtMacOS; +use crate::DioxusDocument; use crate::DioxusNativeWindowRenderer; -use crate::{BlitzShellEvent, DioxusDocument, WindowConfig, contexts::DioxusNativeDocument}; +use crate::shell::contexts::DioxusNativeDocument; +use blitz_shell::{BlitzShellEvent, WindowConfig}; /// Dioxus-native specific event type pub enum DioxusNativeEvent { diff --git a/packages/native/src/link_handler.rs b/packages/native/src/shell/link_handler.rs similarity index 100% rename from packages/native/src/link_handler.rs rename to packages/native/src/shell/link_handler.rs diff --git a/packages/native/src/shell/mod.rs b/packages/native/src/shell/mod.rs new file mode 100644 index 0000000000..4e29fed58f --- /dev/null +++ b/packages/native/src/shell/mod.rs @@ -0,0 +1,208 @@ +mod assets; +mod contexts; +mod dioxus_application; +mod link_handler; + +use std::any::Any; +use std::sync::Arc; + +use blitz_shell::{BlitzShellEvent, BlitzShellProxy, WindowConfig, create_default_event_loop}; +use dioxus_core::{ComponentFunction, Element, VirtualDom, consume_context, use_hook}; +use winit::{ + raw_window_handle::{HasWindowHandle as _, RawWindowHandle}, + window::Window, +}; + +#[cfg(all(feature = "net", not(target_arch = "wasm32")))] +use blitz_traits::net::NetProvider; + +use self::assets::DioxusNativeNetProvider; +use self::link_handler::DioxusNativeNavigationProvider; +use crate::Config; +use crate::DioxusNativeWindowRenderer; +#[cfg(any(feature = "vello", feature = "vello-hybrid"))] +use crate::dioxus_renderer::{Features, Limits}; +use dioxus_native_dom::{DioxusDocument, DocumentConfig}; + +pub use self::dioxus_application::{DioxusNativeApplication, DioxusNativeEvent}; +pub use winit::dpi::{LogicalSize, PhysicalSize}; +pub use winit::window::WindowAttributes; + +#[cfg(target_os = "android")] +#[cfg_attr(docsrs, doc(cfg(all(feature = "shell", target_os = "android"))))] +/// Set the current [`AndroidApp`](android_activity::AndroidApp). +pub fn set_android_app(app: android_activity::AndroidApp) { + blitz_shell::set_android_app(app); +} + +#[cfg(target_os = "android")] +#[cfg_attr(docsrs, doc(cfg(all(feature = "shell", target_os = "android"))))] +/// Get the current [`AndroidApp`](android_activity::AndroidApp). +/// This will panic if the android activity has not been setup with [`set_android_app`]. +pub fn current_android_app() -> android_activity::AndroidApp { + blitz_shell::current_android_app() +} + +#[cfg(target_os = "android")] +#[cfg_attr(docsrs, doc(cfg(all(feature = "shell", target_os = "android"))))] +pub use android_activity::AndroidApp; + +pub fn use_window() -> Arc { + use_hook(consume_context::>) +} + +pub fn use_raw_window_handle() -> RawWindowHandle { + use_hook(|| { + consume_context::>() + .window_handle() + .unwrap() + .as_raw() + }) +} + +/// Launch an interactive HTML/CSS renderer driven by the Dioxus virtualdom +pub fn launch(app: fn() -> Element) { + launch_cfg(app, vec![], vec![]) +} + +pub fn launch_cfg( + app: fn() -> Element, + contexts: Vec Box + Send + Sync>>, + cfg: Vec>, +) { + launch_cfg_with_props(app, (), contexts, cfg) +} + +// todo: props shouldn't have the clone bound - should try and match dioxus-desktop behavior +pub fn launch_cfg_with_props( + app: impl ComponentFunction, + props: P, + contexts: Vec Box + Send + Sync>>, + configs: Vec>, +) { + // Macro to attempt to downcast a type out of a Box + macro_rules! try_read_config { + ($input:ident, $store:ident, $kind:ty) => { + // Try to downcast the Box to type $kind + match $input.downcast::<$kind>() { + // If the type matches then write downcast value to variable $store + Ok(value) => { + $store = Some(*value); + continue; + } + // Else extract the original Box value out of the error type + // and return it so that we can try again with a different type. + Err(cfg) => cfg, + } + }; + } + + // Read config values + #[cfg(any(feature = "vello", feature = "vello-hybrid"))] + let (mut features, mut limits) = (None, None); + let mut window_attributes = None; + let mut config = None; + for mut cfg in configs { + #[cfg(any(feature = "vello", feature = "vello-hybrid"))] + { + cfg = try_read_config!(cfg, features, Features); + cfg = try_read_config!(cfg, limits, Limits); + } + cfg = try_read_config!(cfg, window_attributes, WindowAttributes); + cfg = try_read_config!(cfg, config, Config); + let _ = cfg; + } + + let mut config = config.unwrap_or_default(); + if let Some(window_attributes) = window_attributes { + config.window_attributes = window_attributes; + } + let event_loop = create_default_event_loop(); + let winit_proxy = event_loop.create_proxy(); + let (proxy, event_queue) = BlitzShellProxy::new(winit_proxy); + + // Turn on the runtime and enter it + #[cfg(feature = "net")] + #[cfg(not(target_arch = "wasm32"))] + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + #[cfg(feature = "net")] + #[cfg(not(target_arch = "wasm32"))] + let _guard = rt.enter(); + + // Setup hot-reloading if enabled. + #[cfg(all(feature = "hot-reload", debug_assertions))] + #[cfg(not(target_arch = "wasm32"))] + { + let proxy = proxy.clone(); + dioxus_devtools::connect(move |event| { + let dxn_event = DioxusNativeEvent::DevserverEvent(event); + proxy.send_event(BlitzShellEvent::embedder_event(dxn_event)); + }) + } + + // Build the vdom first; the net provider, document, and other window-bound + // contexts are attached below once the event-loop proxy exists. + let mut vdom = VirtualDom::new_with_props(app, props); + + for context in contexts { + vdom.insert_any_root_context(context()); + } + + #[cfg(all(feature = "net", not(target_arch = "wasm32")))] + let net_provider = { + let net_waker = Some(Arc::new(proxy.clone()) as _); + let inner_net_provider = Arc::new(blitz_net::Provider::new(net_waker)); + vdom.provide_root_context(Arc::clone(&inner_net_provider)); + + Arc::new(DioxusNativeNetProvider::with_inner( + proxy.clone(), + inner_net_provider as _, + )) as Arc + }; + + #[cfg(any(not(feature = "net"), target_arch = "wasm32"))] + let net_provider = DioxusNativeNetProvider::shared(proxy.clone()); + + vdom.provide_root_context(Arc::clone(&net_provider)); + + #[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"))] + let html_parser_provider = None; + + 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, + font_ctx: config.font_ctx, + ..Default::default() + }, + ); + #[cfg(any(feature = "vello", feature = "vello-hybrid"))] + let renderer = DioxusNativeWindowRenderer::with_features_and_limits(features, limits); + #[cfg(not(any(feature = "vello", feature = "vello-hybrid")))] + let renderer = DioxusNativeWindowRenderer::new(); + let config = WindowConfig::with_attributes( + Box::new(doc) as _, + renderer.clone(), + config.window_attributes, + ); + + // Create application + let application = DioxusNativeApplication::new(proxy, event_queue, config); + + // Run event loop + event_loop.run_app(application).unwrap(); +}