|
@@ -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, 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::{CustomPaintSource, 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::<u64, Box<dyn CustomPaintSource + 'static>>::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<dyn Fn() + 'static + Send + Sync>) -> std::task::Waker {
|
|
|
+ struct DomHandle {
|
|
|
+ callback: Box<dyn Fn() + 'static + Send + Sync>,
|
|
|
+ }
|
|
|
+
|
|
|
+ impl futures_util::task::ArcWake for DomHandle {
|
|
|
+ fn wake_by_ref(arc_self: &Arc<Self>) {
|
|
|
+ (arc_self.callback)()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ futures_util::task::waker(Arc::new(DomHandle { callback }))
|
|
|
+}
|