Native: implement hot-reloading of stylesheets (#4830)
* Make `tracing` feature of dioxus-native enable `tracing` feature of dioxus-native-dom * Native: implement hotreloading of stylesheet assets * Add asset support for native-headless-in-bevy example Signed-off-by: Nico Burns <nico@nicoburns.com> * Remove println statements Signed-off-by: Nico Burns <nico@nicoburns.com> --------- Signed-off-by: Nico Burns <nico@nicoburns.com>
This commit is contained in:
1 parent
c55a29e930
commit
2bbfccfd04
7 files changed
+227
-6
No files matched your search
Generated
+6
-2
@@ -2977,9 +2977,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blitz-dom"
|
name = "blitz-dom"
|
||||||
version = "0.2.2"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "961605e9c4cc60eb93bc54172650d3f7b868d15a6e3345419f5e76d1df367b45"
|
checksum = "c81d0f520bad6798a54cc4360bcabd245dc0aba2ce5917217d1d82ac5ee6bfed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit 0.17.1",
|
"accesskit 0.17.1",
|
||||||
"app_units",
|
"app_units",
|
||||||
@@ -11195,8 +11195,12 @@ dependencies = [
|
|||||||
"blitz-dom",
|
"blitz-dom",
|
||||||
"blitz-paint",
|
"blitz-paint",
|
||||||
"blitz-traits",
|
"blitz-traits",
|
||||||
|
"bytes",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
"data-url 0.3.2",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
|
"dioxus-asset-resolver",
|
||||||
|
"dioxus-devtools",
|
||||||
"dioxus-native-dom",
|
"dioxus-native-dom",
|
||||||
"paste",
|
"paste",
|
||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
|
|||||||
+2
-1
@@ -216,7 +216,7 @@ depinfo = { path = "packages/depinfo", version = "=0.7.0-rc.3" }
|
|||||||
warnings = { version = "0.2.1" }
|
warnings = { version = "0.2.1" }
|
||||||
|
|
||||||
# blitz
|
# blitz
|
||||||
blitz-dom = { version = "0.2", default-features = false }
|
blitz-dom = { version = "0.2.4", default-features = false }
|
||||||
blitz-net = { version = "0.2" }
|
blitz-net = { version = "0.2" }
|
||||||
blitz-html = { version = "0.2" }
|
blitz-html = { version = "0.2" }
|
||||||
blitz-paint = { version = "0.2" }
|
blitz-paint = { version = "0.2" }
|
||||||
@@ -336,6 +336,7 @@ inventory = { version = "0.3" }
|
|||||||
macro-string = "0.1.4"
|
macro-string = "0.1.4"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
url = "2"
|
url = "2"
|
||||||
|
data-url = "0.3.2"
|
||||||
separator = "0.4.1"
|
separator = "0.4.1"
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
|
|||||||
@@ -9,19 +9,25 @@ license = "MIT"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["hot-reload"]
|
||||||
tracing = ["dep:tracing-subscriber", "dioxus-native-dom/tracing"]
|
tracing = ["dep:tracing-subscriber", "dioxus-native-dom/tracing"]
|
||||||
|
hot-reload = ["dep:dioxus-devtools"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus = { workspace = true }
|
dioxus = { workspace = true, features = ["document"]}
|
||||||
dioxus-native-dom = { workspace = true }
|
dioxus-native-dom = { workspace = true }
|
||||||
|
dioxus-asset-resolver = { workspace = true, features = ["native"] }
|
||||||
|
dioxus-devtools = { workspace = true, optional = true }
|
||||||
blitz-dom = { workspace = true, default-features = true }
|
blitz-dom = { workspace = true, default-features = true }
|
||||||
blitz-paint = { workspace = true, default-features = true }
|
blitz-paint = { workspace = true, default-features = true }
|
||||||
blitz-traits = { workspace = true, default-features = true }
|
blitz-traits = { workspace = true, default-features = true }
|
||||||
|
data-url = { workspace = true, default-features = true }
|
||||||
anyrender_vello = { workspace = true }
|
anyrender_vello = { workspace = true }
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true, optional = true }
|
tracing-subscriber = { workspace = true, optional = true }
|
||||||
bevy = { workspace = true }
|
bevy = { workspace = true }
|
||||||
vello = { workspace = true }
|
vello = { workspace = true }
|
||||||
|
bytes = { workspace = true }
|
||||||
async-std = "1.13"
|
async-std = "1.13"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use bevy::asset::RenderAssetUsages;
|
use bevy::asset::RenderAssetUsages;
|
||||||
@@ -25,9 +27,13 @@ use blitz_paint::paint_scene;
|
|||||||
use blitz_traits::events::{
|
use blitz_traits::events::{
|
||||||
BlitzKeyEvent, BlitzMouseButtonEvent, KeyState, MouseEventButton, MouseEventButtons, UiEvent,
|
BlitzKeyEvent, BlitzMouseButtonEvent, KeyState, MouseEventButton, MouseEventButtons, UiEvent,
|
||||||
};
|
};
|
||||||
|
use blitz_traits::net::{NetCallback, NetProvider};
|
||||||
use blitz_traits::shell::{ColorScheme, Viewport};
|
use blitz_traits::shell::{ColorScheme, Viewport};
|
||||||
|
use bytes::Bytes;
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
use data_url::DataUrl;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_devtools::DevserverMsg;
|
||||||
use dioxus_native_dom::DioxusDocument;
|
use dioxus_native_dom::DioxusDocument;
|
||||||
use vello::{
|
use vello::{
|
||||||
peniko::color::AlphaColor, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene,
|
peniko::color::AlphaColor, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene,
|
||||||
@@ -51,12 +57,24 @@ impl<UIProps: std::marker::Send + std::marker::Sync + std::clone::Clone + 'stati
|
|||||||
{
|
{
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
let epoch = AnimationTime(Instant::now());
|
let epoch = AnimationTime(Instant::now());
|
||||||
|
let (s, r) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
// Create the dioxus virtual dom and the dioxus-native document
|
// Create the dioxus virtual dom and the dioxus-native document
|
||||||
// The viewport will be set in setup_ui after we get the window size
|
// The viewport will be set in setup_ui after we get the window size
|
||||||
let vdom = VirtualDom::new_with_props(self.ui, self.props.clone());
|
let vdom = VirtualDom::new_with_props(self.ui, self.props.clone());
|
||||||
// FIXME add a NetProvider
|
// FIXME add a NetProvider
|
||||||
let mut dioxus_doc = DioxusDocument::new(vdom, DocumentConfig::default());
|
let mut dioxus_doc = DioxusDocument::new(vdom, DocumentConfig::default());
|
||||||
|
|
||||||
|
// Setup NetProvider
|
||||||
|
let net_provider = BevyNetProvider::shared(s.clone());
|
||||||
|
dioxus_doc.set_net_provider(net_provider);
|
||||||
|
|
||||||
|
// Setup DocumentProxy to process CreateHeadElement messages
|
||||||
|
let proxy = Rc::new(DioxusDocumentProxy::new(s.clone()));
|
||||||
|
dioxus_doc.vdom.in_scope(ScopeId::ROOT, move || {
|
||||||
|
provide_context(proxy as Rc<dyn dioxus::document::Document>);
|
||||||
|
});
|
||||||
|
|
||||||
dioxus_doc.initial_build();
|
dioxus_doc.initial_build();
|
||||||
dioxus_doc.resolve(0.0);
|
dioxus_doc.resolve(0.0);
|
||||||
|
|
||||||
@@ -67,9 +85,14 @@ impl<UIProps: std::marker::Send + std::marker::Sync + std::clone::Clone + 'stati
|
|||||||
}
|
}
|
||||||
let waker = std::task::Waker::from(std::sync::Arc::new(NullWake));
|
let waker = std::task::Waker::from(std::sync::Arc::new(NullWake));
|
||||||
|
|
||||||
|
// Setup devtools listener for hot-reloading
|
||||||
|
dioxus_devtools::connect(move |msg| s.send(DioxusMessage::Devserver(msg)).unwrap());
|
||||||
|
app.insert_resource(DioxusMessages(r));
|
||||||
|
|
||||||
app.insert_non_send_resource(dioxus_doc);
|
app.insert_non_send_resource(dioxus_doc);
|
||||||
app.insert_non_send_resource(waker);
|
app.insert_non_send_resource(waker);
|
||||||
app.insert_resource(epoch);
|
app.insert_resource(epoch);
|
||||||
|
|
||||||
app.add_systems(Startup, setup_ui);
|
app.add_systems(Startup, setup_ui);
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
@@ -201,6 +224,21 @@ fn extract_texture_image(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HeadElement {
|
||||||
|
name: String,
|
||||||
|
attributes: Vec<(String, String)>,
|
||||||
|
contents: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DioxusMessage {
|
||||||
|
Devserver(DevserverMsg),
|
||||||
|
CreateHeadElement(HeadElement),
|
||||||
|
ResourceLoad(blitz_dom::net::Resource),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
struct DioxusMessages(Receiver<DioxusMessage>);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct DioxusUiQuad;
|
pub struct DioxusUiQuad;
|
||||||
|
|
||||||
@@ -255,6 +293,7 @@ fn setup_ui(
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn update_ui(
|
fn update_ui(
|
||||||
mut dioxus_doc: NonSendMut<DioxusDocument>,
|
mut dioxus_doc: NonSendMut<DioxusDocument>,
|
||||||
|
dioxus_messages: Res<DioxusMessages>,
|
||||||
waker: NonSendMut<std::task::Waker>,
|
waker: NonSendMut<std::task::Waker>,
|
||||||
vello_renderer: Option<NonSendMut<VelloRenderer>>,
|
vello_renderer: Option<NonSendMut<VelloRenderer>>,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
@@ -263,6 +302,33 @@ fn update_ui(
|
|||||||
animation_epoch: Res<AnimationTime>,
|
animation_epoch: Res<AnimationTime>,
|
||||||
mut cached_texture: Local<Option<RenderTexture>>,
|
mut cached_texture: Local<Option<RenderTexture>>,
|
||||||
) {
|
) {
|
||||||
|
while let Ok(msg) = dioxus_messages.0.try_recv() {
|
||||||
|
match msg {
|
||||||
|
DioxusMessage::Devserver(devserver_msg) => match devserver_msg {
|
||||||
|
dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {
|
||||||
|
// Apply changes to vdom
|
||||||
|
dioxus_devtools::apply_changes(&dioxus_doc.vdom, &hotreload_message);
|
||||||
|
|
||||||
|
// Reload changed assets
|
||||||
|
for asset_path in &hotreload_message.assets {
|
||||||
|
if let Some(url) = asset_path.to_str() {
|
||||||
|
dioxus_doc.reload_resource_by_href(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dioxus_devtools::DevserverMsg::FullReloadStart => {}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
DioxusMessage::CreateHeadElement(el) => {
|
||||||
|
dioxus_doc.create_head_element(&el.name, &el.attributes, &el.contents);
|
||||||
|
dioxus_doc.poll(Some(std::task::Context::from_waker(&waker)));
|
||||||
|
}
|
||||||
|
DioxusMessage::ResourceLoad(resource) => {
|
||||||
|
dioxus_doc.load_resource(resource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
while let Ok(texture) = receiver.try_recv() {
|
while let Ok(texture) = receiver.try_recv() {
|
||||||
*cached_texture = Some(texture);
|
*cached_texture = Some(texture);
|
||||||
}
|
}
|
||||||
@@ -519,6 +585,140 @@ fn handle_keyboard_events(
|
|||||||
// dioxus_doc.resolve();
|
// dioxus_doc.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DioxusDocumentProxy {
|
||||||
|
sender: Sender<DioxusMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DioxusDocumentProxy {
|
||||||
|
fn new(sender: Sender<DioxusMessage>) -> Self {
|
||||||
|
Self { sender }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl dioxus::document::Document for DioxusDocumentProxy {
|
||||||
|
fn eval(&self, _js: String) -> dioxus::document::Eval {
|
||||||
|
dioxus::document::NoOpDocument.eval(_js)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_head_element(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
attributes: &[(&str, String)],
|
||||||
|
contents: Option<String>,
|
||||||
|
) {
|
||||||
|
self.sender
|
||||||
|
.send(DioxusMessage::CreateHeadElement(HeadElement {
|
||||||
|
name: name.to_string(),
|
||||||
|
attributes: attributes
|
||||||
|
.iter()
|
||||||
|
.map(|(name, value)| (name.to_string(), value.clone()))
|
||||||
|
.collect(),
|
||||||
|
contents,
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_title(&self, title: String) {
|
||||||
|
self.create_head_element("title", &[], Some(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_meta(&self, props: dioxus::document::MetaProps) {
|
||||||
|
let attributes = props.attributes();
|
||||||
|
self.create_head_element("meta", &attributes, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_script(&self, props: dioxus::document::ScriptProps) {
|
||||||
|
let attributes = props.attributes();
|
||||||
|
self.create_head_element("script", &attributes, props.script_contents().ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_style(&self, props: dioxus::document::StyleProps) {
|
||||||
|
let attributes = props.attributes();
|
||||||
|
self.create_head_element("style", &attributes, props.style_contents().ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_link(&self, props: dioxus::document::LinkProps) {
|
||||||
|
let attributes = props.attributes();
|
||||||
|
self.create_head_element("link", &attributes, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_head_component(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BevyNetCallback {
|
||||||
|
sender: Sender<DioxusMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
use blitz_dom::net::Resource as BlitzResource;
|
||||||
|
use blitz_traits::net::NetHandler;
|
||||||
|
|
||||||
|
impl NetCallback<BlitzResource> for BevyNetCallback {
|
||||||
|
fn call(&self, _doc_id: usize, result: core::result::Result<BlitzResource, Option<String>>) {
|
||||||
|
if let Ok(res) = result {
|
||||||
|
self.sender.send(DioxusMessage::ResourceLoad(res)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BevyNetProvider {
|
||||||
|
callback: Arc<dyn NetCallback<BlitzResource> + 'static>,
|
||||||
|
}
|
||||||
|
impl BevyNetProvider {
|
||||||
|
fn shared(sender: Sender<DioxusMessage>) -> Arc<dyn NetProvider<BlitzResource>> {
|
||||||
|
Arc::new(Self::new(sender)) as _
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(sender: Sender<DioxusMessage>) -> Self {
|
||||||
|
Self {
|
||||||
|
callback: Arc::new(BevyNetCallback { sender }) as _,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetProvider<BlitzResource> for BevyNetProvider {
|
||||||
|
fn fetch(
|
||||||
|
&self,
|
||||||
|
doc_id: usize,
|
||||||
|
request: blitz_traits::net::Request,
|
||||||
|
handler: Box<dyn NetHandler<BlitzResource>>,
|
||||||
|
) {
|
||||||
|
match request.url.scheme() {
|
||||||
|
// Load Dioxus assets
|
||||||
|
"dioxus" => match dioxus_asset_resolver::native::serve_asset(request.url.path()) {
|
||||||
|
Ok(res) => handler.bytes(doc_id, res.into_body().into(), self.callback.clone()),
|
||||||
|
Err(_) => {
|
||||||
|
self.callback.call(
|
||||||
|
doc_id,
|
||||||
|
Err(Some(String::from("Error loading Dioxus asset"))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Decode data URIs
|
||||||
|
"data" => {
|
||||||
|
let Ok(data_url) = DataUrl::process(request.url.as_str()) else {
|
||||||
|
self.callback
|
||||||
|
.call(doc_id, Err(Some(String::from("Failed to parse data uri"))));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Ok(decoded) = data_url.decode_to_vec() else {
|
||||||
|
self.callback
|
||||||
|
.call(doc_id, Err(Some(String::from("Failed to decode data uri"))));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let bytes = Bytes::from(decoded.0);
|
||||||
|
handler.bytes(doc_id, bytes, Arc::clone(&self.callback));
|
||||||
|
}
|
||||||
|
// TODO: support http requests
|
||||||
|
_ => {
|
||||||
|
self.callback
|
||||||
|
.call(doc_id, Err(Some(String::from("UnsupportedScheme"))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn bevy_key_to_blitz_key(key: &BevyKey) -> Key {
|
fn bevy_key_to_blitz_key(key: &BevyKey) -> Key {
|
||||||
match key {
|
match key {
|
||||||
BevyKey::Character(c) => Key::Character(c.to_string()),
|
BevyKey::Character(c) => Key::Character(c.to_string()),
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ pub fn ui(props: UIProps) -> Element {
|
|||||||
println!("rgba({r}, {g}, {b}, {a})");
|
println!("rgba({r}, {g}, {b}, {a})");
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
style { {include_str!("./ui.css")} }
|
document::Stylesheet { href: asset!("/src/ui.css") }
|
||||||
div {
|
div {
|
||||||
id: "panel",
|
id: "panel",
|
||||||
class: "catch-events",
|
class: "catch-events",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ svg = ["blitz-dom/svg", "blitz-paint/svg"]
|
|||||||
net = ["dep:tokio", "dep:blitz-net"]
|
net = ["dep:tokio", "dep:blitz-net"]
|
||||||
html = ["dep:blitz-html"]
|
html = ["dep:blitz-html"]
|
||||||
accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"]
|
accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"]
|
||||||
tracing = ["dep:tracing", "blitz-shell/tracing", "blitz-dom/tracing"]
|
tracing = ["dep:tracing", "dioxus-native-dom/tracing", "blitz-shell/tracing", "blitz-dom/tracing"]
|
||||||
hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-devtools"]
|
hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-devtools"]
|
||||||
system-fonts = ["blitz-dom/system_fonts"]
|
system-fonts = ["blitz-dom/system_fonts"]
|
||||||
autofocus = []
|
autofocus = []
|
||||||
|
|||||||
@@ -70,7 +70,17 @@ impl DioxusNativeApplication {
|
|||||||
dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {
|
dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {
|
||||||
for window in self.inner.windows.values_mut() {
|
for window in self.inner.windows.values_mut() {
|
||||||
let doc = window.downcast_doc_mut::<DioxusDocument>();
|
let doc = window.downcast_doc_mut::<DioxusDocument>();
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if let Some(url) = asset_path.to_str() {
|
||||||
|
doc.reload_resource_by_href(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.poll();
|
window.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user