1+ use std:: rc:: Rc ;
2+ use std:: sync:: Arc ;
13use std:: time:: Instant ;
24
35use bevy:: asset:: RenderAssetUsages ;
@@ -25,9 +27,13 @@ use blitz_paint::paint_scene;
2527use blitz_traits:: events:: {
2628 BlitzKeyEvent , BlitzMouseButtonEvent , KeyState , MouseEventButton , MouseEventButtons , UiEvent ,
2729} ;
30+ use blitz_traits:: net:: { NetCallback , NetProvider } ;
2831use blitz_traits:: shell:: { ColorScheme , Viewport } ;
32+ use bytes:: Bytes ;
2933use crossbeam_channel:: { Receiver , Sender } ;
34+ use data_url:: DataUrl ;
3035use dioxus:: prelude:: * ;
36+ use dioxus_devtools:: DevserverMsg ;
3137use dioxus_native_dom:: DioxusDocument ;
3238use vello:: {
3339 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
5157{
5258 fn build ( & self , app : & mut App ) {
5359 let epoch = AnimationTime ( Instant :: now ( ) ) ;
60+ let ( s, r) = crossbeam_channel:: unbounded ( ) ;
5461
5562 // Create the dioxus virtual dom and the dioxus-native document
5663 // The viewport will be set in setup_ui after we get the window size
5764 let vdom = VirtualDom :: new_with_props ( self . ui , self . props . clone ( ) ) ;
5865 // FIXME add a NetProvider
5966 let mut dioxus_doc = DioxusDocument :: new ( vdom, DocumentConfig :: default ( ) ) ;
67+
68+ // Setup NetProvider
69+ let net_provider = BevyNetProvider :: shared ( s. clone ( ) ) ;
70+ dioxus_doc. set_net_provider ( net_provider) ;
71+
72+ // Setup DocumentProxy to process CreateHeadElement messages
73+ let proxy = Rc :: new ( DioxusDocumentProxy :: new ( s. clone ( ) ) ) ;
74+ dioxus_doc. vdom . in_scope ( ScopeId :: ROOT , move || {
75+ provide_context ( proxy as Rc < dyn dioxus:: document:: Document > ) ;
76+ } ) ;
77+
6078 dioxus_doc. initial_build ( ) ;
6179 dioxus_doc. resolve ( 0.0 ) ;
6280
@@ -67,9 +85,14 @@ impl<UIProps: std::marker::Send + std::marker::Sync + std::clone::Clone + 'stati
6785 }
6886 let waker = std:: task:: Waker :: from ( std:: sync:: Arc :: new ( NullWake ) ) ;
6987
88+ // Setup devtools listener for hot-reloading
89+ dioxus_devtools:: connect ( move |msg| s. send ( DioxusMessage :: Devserver ( msg) ) . unwrap ( ) ) ;
90+ app. insert_resource ( DioxusMessages ( r) ) ;
91+
7092 app. insert_non_send_resource ( dioxus_doc) ;
7193 app. insert_non_send_resource ( waker) ;
7294 app. insert_resource ( epoch) ;
95+
7396 app. add_systems ( Startup , setup_ui) ;
7497 app. add_systems (
7598 PreUpdate ,
@@ -201,6 +224,21 @@ fn extract_texture_image(
201224 }
202225}
203226
227+ struct HeadElement {
228+ name : String ,
229+ attributes : Vec < ( String , String ) > ,
230+ contents : Option < String > ,
231+ }
232+
233+ enum DioxusMessage {
234+ Devserver ( DevserverMsg ) ,
235+ CreateHeadElement ( HeadElement ) ,
236+ ResourceLoad ( blitz_dom:: net:: Resource ) ,
237+ }
238+
239+ #[ derive( Resource , Deref ) ]
240+ struct DioxusMessages ( Receiver < DioxusMessage > ) ;
241+
204242#[ derive( Component ) ]
205243pub struct DioxusUiQuad ;
206244
@@ -255,6 +293,7 @@ fn setup_ui(
255293#[ allow( clippy:: too_many_arguments) ]
256294fn update_ui (
257295 mut dioxus_doc : NonSendMut < DioxusDocument > ,
296+ dioxus_messages : Res < DioxusMessages > ,
258297 waker : NonSendMut < std:: task:: Waker > ,
259298 vello_renderer : Option < NonSendMut < VelloRenderer > > ,
260299 render_device : Res < RenderDevice > ,
@@ -263,6 +302,33 @@ fn update_ui(
263302 animation_epoch : Res < AnimationTime > ,
264303 mut cached_texture : Local < Option < RenderTexture > > ,
265304) {
305+ while let Ok ( msg) = dioxus_messages. 0 . try_recv ( ) {
306+ match msg {
307+ DioxusMessage :: Devserver ( devserver_msg) => match devserver_msg {
308+ dioxus_devtools:: DevserverMsg :: HotReload ( hotreload_message) => {
309+ // Apply changes to vdom
310+ dioxus_devtools:: apply_changes ( & dioxus_doc. vdom , & hotreload_message) ;
311+
312+ // Reload changed assets
313+ for asset_path in & hotreload_message. assets {
314+ if let Some ( url) = asset_path. to_str ( ) {
315+ dioxus_doc. reload_resource_by_href ( url) ;
316+ }
317+ }
318+ }
319+ dioxus_devtools:: DevserverMsg :: FullReloadStart => { }
320+ _ => { }
321+ } ,
322+ DioxusMessage :: CreateHeadElement ( el) => {
323+ dioxus_doc. create_head_element ( & el. name , & el. attributes , & el. contents ) ;
324+ dioxus_doc. poll ( Some ( std:: task:: Context :: from_waker ( & waker) ) ) ;
325+ }
326+ DioxusMessage :: ResourceLoad ( resource) => {
327+ dioxus_doc. load_resource ( resource) ;
328+ }
329+ } ;
330+ }
331+
266332 while let Ok ( texture) = receiver. try_recv ( ) {
267333 * cached_texture = Some ( texture) ;
268334 }
@@ -519,6 +585,140 @@ fn handle_keyboard_events(
519585 // dioxus_doc.resolve();
520586}
521587
588+ pub struct DioxusDocumentProxy {
589+ sender : Sender < DioxusMessage > ,
590+ }
591+
592+ impl DioxusDocumentProxy {
593+ fn new ( sender : Sender < DioxusMessage > ) -> Self {
594+ Self { sender }
595+ }
596+ }
597+
598+ impl dioxus:: document:: Document for DioxusDocumentProxy {
599+ fn eval ( & self , _js : String ) -> dioxus:: document:: Eval {
600+ dioxus:: document:: NoOpDocument . eval ( _js)
601+ }
602+
603+ fn create_head_element (
604+ & self ,
605+ name : & str ,
606+ attributes : & [ ( & str , String ) ] ,
607+ contents : Option < String > ,
608+ ) {
609+ self . sender
610+ . send ( DioxusMessage :: CreateHeadElement ( HeadElement {
611+ name : name. to_string ( ) ,
612+ attributes : attributes
613+ . iter ( )
614+ . map ( |( name, value) | ( name. to_string ( ) , value. clone ( ) ) )
615+ . collect ( ) ,
616+ contents,
617+ } ) )
618+ . unwrap ( ) ;
619+ }
620+
621+ fn set_title ( & self , title : String ) {
622+ self . create_head_element ( "title" , & [ ] , Some ( title) ) ;
623+ }
624+
625+ fn create_meta ( & self , props : dioxus:: document:: MetaProps ) {
626+ let attributes = props. attributes ( ) ;
627+ self . create_head_element ( "meta" , & attributes, None ) ;
628+ }
629+
630+ fn create_script ( & self , props : dioxus:: document:: ScriptProps ) {
631+ let attributes = props. attributes ( ) ;
632+ self . create_head_element ( "script" , & attributes, props. script_contents ( ) . ok ( ) ) ;
633+ }
634+
635+ fn create_style ( & self , props : dioxus:: document:: StyleProps ) {
636+ let attributes = props. attributes ( ) ;
637+ self . create_head_element ( "style" , & attributes, props. style_contents ( ) . ok ( ) ) ;
638+ }
639+
640+ fn create_link ( & self , props : dioxus:: document:: LinkProps ) {
641+ let attributes = props. attributes ( ) ;
642+ self . create_head_element ( "link" , & attributes, None ) ;
643+ }
644+
645+ fn create_head_component ( & self ) -> bool {
646+ true
647+ }
648+ }
649+
650+ struct BevyNetCallback {
651+ sender : Sender < DioxusMessage > ,
652+ }
653+
654+ use blitz_dom:: net:: Resource as BlitzResource ;
655+ use blitz_traits:: net:: NetHandler ;
656+
657+ impl NetCallback < BlitzResource > for BevyNetCallback {
658+ fn call ( & self , _doc_id : usize , result : core:: result:: Result < BlitzResource , Option < String > > ) {
659+ if let Ok ( res) = result {
660+ self . sender . send ( DioxusMessage :: ResourceLoad ( res) ) . unwrap ( ) ;
661+ }
662+ }
663+ }
664+
665+ pub struct BevyNetProvider {
666+ callback : Arc < dyn NetCallback < BlitzResource > + ' static > ,
667+ }
668+ impl BevyNetProvider {
669+ fn shared ( sender : Sender < DioxusMessage > ) -> Arc < dyn NetProvider < BlitzResource > > {
670+ Arc :: new ( Self :: new ( sender) ) as _
671+ }
672+
673+ fn new ( sender : Sender < DioxusMessage > ) -> Self {
674+ Self {
675+ callback : Arc :: new ( BevyNetCallback { sender } ) as _ ,
676+ }
677+ }
678+ }
679+
680+ impl NetProvider < BlitzResource > for BevyNetProvider {
681+ fn fetch (
682+ & self ,
683+ doc_id : usize ,
684+ request : blitz_traits:: net:: Request ,
685+ handler : Box < dyn NetHandler < BlitzResource > > ,
686+ ) {
687+ match request. url . scheme ( ) {
688+ // Load Dioxus assets
689+ "dioxus" => match dioxus_asset_resolver:: native:: serve_asset ( request. url . path ( ) ) {
690+ Ok ( res) => handler. bytes ( doc_id, res. into_body ( ) . into ( ) , self . callback . clone ( ) ) ,
691+ Err ( _) => {
692+ self . callback . call (
693+ doc_id,
694+ Err ( Some ( String :: from ( "Error loading Dioxus asset" ) ) ) ,
695+ ) ;
696+ }
697+ } ,
698+ // Decode data URIs
699+ "data" => {
700+ let Ok ( data_url) = DataUrl :: process ( request. url . as_str ( ) ) else {
701+ self . callback
702+ . call ( doc_id, Err ( Some ( String :: from ( "Failed to parse data uri" ) ) ) ) ;
703+ return ;
704+ } ;
705+ let Ok ( decoded) = data_url. decode_to_vec ( ) else {
706+ self . callback
707+ . call ( doc_id, Err ( Some ( String :: from ( "Failed to decode data uri" ) ) ) ) ;
708+ return ;
709+ } ;
710+ let bytes = Bytes :: from ( decoded. 0 ) ;
711+ handler. bytes ( doc_id, bytes, Arc :: clone ( & self . callback ) ) ;
712+ }
713+ // TODO: support http requests
714+ _ => {
715+ self . callback
716+ . call ( doc_id, Err ( Some ( String :: from ( "UnsupportedScheme" ) ) ) ) ;
717+ }
718+ }
719+ }
720+ }
721+
522722fn bevy_key_to_blitz_key ( key : & BevyKey ) -> Key {
523723 match key {
524724 BevyKey :: Character ( c) => Key :: Character ( c. to_string ( ) ) ,
0 commit comments