diff --git a/Cargo.lock b/Cargo.lock index 685cc5f3a..98655e449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2822,6 +2822,7 @@ dependencies = [ "tracing", "url", "usvg", + "woff", ] [[package]] @@ -10632,6 +10633,25 @@ dependencies = [ "jobserver", ] +[[package]] +name = "native-headless" +version = "0.0.0" +dependencies = [ + "anyrender_vello", + "blitz-dom", + "blitz-paint", + "blitz-traits", + "bytemuck", + "dioxus", + "dioxus-native-dom", + "futures-util", + "pollster", + "rustc-hash 1.1.0", + "tracing-subscriber", + "vello", + "wgpu", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -19177,6 +19197,16 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "woff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e229335e006d138b1c318e83b99eaa3e1ab03e08c4ced4967e46705e6e9d1cf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "writeable" version = "0.5.5" diff --git a/Cargo.toml b/Cargo.toml index 358eda229..3fa8a1ec1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,7 @@ members = [ "examples/fullstack-auth", "examples/fullstack-websockets", "examples/wgpu-texture", + "examples/native-headless", "examples/bevy", # Playwright tests @@ -201,6 +202,14 @@ wasm-used = { path = "packages/wasm-split/wasm-used", version = "0.7.0-alpha.2" depinfo = { path = "packages/depinfo", version = "0.7.0-alpha.2" } warnings = { version = "0.2.1" } +# blitz +blitz-dom = { version = "=0.1.0-alpha.5", default-features = false } +blitz-net = { version = "=0.1.0-alpha.5" } +blitz-paint = { version = "=0.1.0-alpha.5" } +blitz-traits = { version = "=0.1.0-alpha.5" } +blitz-shell = { version = "=0.1.0-alpha.5", default-features = false } +wgpu = { version = "24.0" } + # a fork of pretty please for tests - let's get off of this if we can! prettier-please = { version = "0.3.0", features = ["verbatim"] } anyhow = "1.0.98" @@ -412,7 +421,7 @@ reqwest = { workspace = true, features = ["json"], optional = true } ciborium = { workspace = true, optional = true } base64 = { workspace = true, optional = true } http-range = { version = "0.1.5", optional = true } -wgpu = { version = "24.0", optional = true } +wgpu = { workspace = true, optional = true } ouroboros = { version = "*", optional = true } wasm-splitter = { workspace = true, package = "wasm-split" } diff --git a/examples/bevy/Cargo.toml b/examples/bevy/Cargo.toml index 2e5034199..e23a697f5 100644 --- a/examples/bevy/Cargo.toml +++ b/examples/bevy/Cargo.toml @@ -15,6 +15,6 @@ tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"] bevy = { version = "0.16" } dioxus-native = { path = "../../packages/native" } dioxus = { workspace = true } -wgpu = "24" +wgpu = { workspace = true } color = "0.3" tracing-subscriber = { workspace = true, optional = true } diff --git a/examples/native-headless/Cargo.toml b/examples/native-headless/Cargo.toml new file mode 100644 index 000000000..eff60ecbf --- /dev/null +++ b/examples/native-headless/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "native-headless" +version = "0.0.0" +edition = "2021" +license = "MIT" +publish = false + +[features] +tracing = ["dep:tracing-subscriber", "dioxus-native-dom/tracing"] + +[dependencies] +dioxus = { workspace = true, default-features = false, features = ["html", "hooks", "signals"] } +dioxus-native-dom = { path = "../../packages/native-dom" } +anyrender_vello = "0.4.1" +vello = "0.5" +rustc-hash = "1" +futures-util = { workspace = true } +tracing-subscriber = { workspace = true, optional = true } +wgpu = { workspace = true } +pollster = "0.4" +bytemuck = "1" +blitz-paint = { workspace = true, default-features = true } +blitz-traits = { workspace = true, default-features = true } +blitz-dom = { workspace = true, default-features = true } diff --git a/examples/native-headless/src/main.rs b/examples/native-headless/src/main.rs new file mode 100644 index 000000000..a1fd0c46b --- /dev/null +++ b/examples/native-headless/src/main.rs @@ -0,0 +1,172 @@ +//! A "sketch" of how to integrate a Dioxus Native app to into a wider application +//! by rendering the UI to a texture and driving it with your own event loop +//! +//! (this example is not really intended to be run as-is, and requires you to fill +//! in the missing pieces) +use anyrender_vello::{wgpu_context::WGPUContext, CustomPaintSource, VelloScenePainter}; +use blitz_dom::Document as _; +use blitz_paint::paint_scene; +use blitz_traits::{ + events::{BlitzMouseButtonEvent, MouseEventButton, MouseEventButtons, UiEvent}, + shell::{ColorScheme, Viewport}, +}; +use dioxus::prelude::*; +use dioxus_native_dom::DioxusDocument; +use pollster::FutureExt as _; +use rustc_hash::FxHashMap; +use std::sync::Arc; +use std::task::Context; +use vello::{ + peniko::color::AlphaColor, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene, +}; +use wgpu::TextureFormat; + +// Constant width, height, scale factor and color schemefor example purposes +const SCALE_FACTOR: f32 = 1.0; +const WIDTH: u32 = 800; +const HEIGHT: u32 = 600; +const COLOR_SCHEME: ColorScheme = ColorScheme::Light; + +// Example Dioxus app. +fn app() -> Element { + rsx! { + div { "Hello, world!" } + } +} + +fn main() { + #[cfg(feature = "tracing")] + tracing_subscriber::fmt::init(); + + // ============= + // INITIAL SETUP + // ============= + + let waker = create_waker(Box::new(|| { + // This should wake up and "poll" your event loop + })); + + // Create the dioxus virtual dom and the dioxus-native document + // It is important to set the width, height, and scale factor on the document as these are used for layout. + let vdom = VirtualDom::new(app); + let mut dioxus_doc = DioxusDocument::new(vdom, None); + dioxus_doc.set_viewport(Viewport::new(WIDTH, HEIGHT, SCALE_FACTOR, COLOR_SCHEME)); + + // Setup a WGPU Device and Queue + // + // There is nothing special about WGPUContext. It is just used to + // reduce the amount of boilerplate associated with setting up WGPU + let mut wgpu_context = WGPUContext::new(); + let device_id = wgpu_context + .find_or_create_device(None) + .block_on() + .expect("Failed to create WGPU device"); + let device_handle = &wgpu_context.device_pool[device_id]; + let device = device_handle.device.clone(); + let queue = device_handle.queue.clone(); + + // Create Vello renderer + // Note: creating a VelloRenderer is expensive, so it should be done once per Device. + let mut vello_renderer = VelloRenderer::new(&device, RendererOptions::default()).unwrap(); + + // ============= + // CREATE TEXTURE (RECREATE ON RESIZE) + // ============= + + // Create texture and texture view + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: WIDTH, + height: HEIGHT, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, + format: TextureFormat::Rgba8Unorm, + view_formats: &[], + }); + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + // ============= + // EACH FRAME + // ============= + + // Poll the vdom + dioxus_doc.poll(Context::from_waker(&waker)); + + // Create a `VelloScenePainter` to paint into + let mut custom_paint_sources = + FxHashMap::>::default(); + let mut scene_painter = VelloScenePainter { + inner: Scene::new(), + renderer: &mut vello_renderer, + custom_paint_sources: &mut custom_paint_sources, + }; + + // Paint the document using `blitz_paint::paint_scene` + // + // Note: the `paint_scene` will work with any implementation of `anyrender::PaintScene` + // so you could also write your own implementation if you want more control over rendering + // (i.e. to render a custom renderer instead of Vello) + paint_scene( + &mut scene_painter, + &dioxus_doc, + SCALE_FACTOR as f64, + WIDTH, + HEIGHT, + ); + + // Extract the `vello::Scene` from the `VelloScenePainter` + let scene = scene_painter.finish(); + + // Render the `vello::Scene` to the Texture using the `VelloRenderer` + vello_renderer + .render_to_texture( + &device, + &queue, + &scene, + &texture_view, + &RenderParams { + base_color: AlphaColor::TRANSPARENT, + width: WIDTH, + height: HEIGHT, + antialiasing_method: vello::AaConfig::Msaa16, + }, + ) + .expect("failed to render to texture"); + + // `texture` will now contain the rendered Scene + + // ============= + // EVENT HANDLING + // ============= + + let event = UiEvent::MouseDown(BlitzMouseButtonEvent { + x: 30.0, + y: 40.0, + button: MouseEventButton::Main, + buttons: MouseEventButtons::Primary, // keep track of all pressed buttons + mods: Modifiers::empty(), // ctrl, alt, shift, etc + }); + dioxus_doc.handle_event(event); + + // Trigger a poll via your event loop (or wait for next frame) +} + +/// Create a waker that will call an arbitrary callback +pub fn create_waker(callback: Box) -> std::task::Waker { + struct DomHandle { + callback: Box, + } + + impl futures_util::task::ArcWake for DomHandle { + fn wake_by_ref(arc_self: &Arc) { + (arc_self.callback)() + } + } + + futures_util::task::waker(Arc::new(DomHandle { callback })) +} diff --git a/examples/wgpu-texture/Cargo.toml b/examples/wgpu-texture/Cargo.toml index 7a2542eb1..aafdd1b58 100644 --- a/examples/wgpu-texture/Cargo.toml +++ b/examples/wgpu-texture/Cargo.toml @@ -1,6 +1,3 @@ -# Copyright © SixtyFPS GmbH -# SPDX-License-Identifier: MIT - [package] name = "wgpu-texture" version = "0.0.0" @@ -17,7 +14,7 @@ tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"] [dependencies] dioxus-native = { path = "../../packages/native" } dioxus = { workspace = true } -wgpu = "24" +wgpu = { workspace = true } winit = "0.30" bytemuck = "1" color = "0.3" diff --git a/packages/native/Cargo.toml b/packages/native/Cargo.toml index a818148b1..615ccabf0 100644 --- a/packages/native/Cargo.toml +++ b/packages/native/Cargo.toml @@ -23,11 +23,11 @@ autofocus = [] # Blitz dependencies anyrender = { version = "0.4", default-features = false } anyrender_vello = { version = "0.4", default-features = false } -blitz-dom = { version = "=0.1.0-alpha.5", default-features = false } -blitz-net = { version = "=0.1.0-alpha.5", optional = true } -blitz-paint = { version = "=0.1.0-alpha.5", optional = true } -blitz-traits = { version = "=0.1.0-alpha.5" } -blitz-shell = { version = "=0.1.0-alpha.5", default-features = false } +blitz-dom = { workspace = true, default-features = false } +blitz-net = { workspace = true, optional = true } +blitz-paint = { workspace = true, optional = true } +blitz-traits = { workspace = true } +blitz-shell = { workspace = true, default-features = false } # DioxusLabs dependencies dioxus-core = { workspace = true }