diff --git a/examples/calculator.rs b/examples/calculator.rs index 4f5d7dedd..93e298010 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -25,6 +25,7 @@ fn app(cx: Scope) -> Element { if val.get() == "0" { val.set(String::new()); } + val.make_mut().push_str(num.to_string().as_str()); }; @@ -99,12 +100,8 @@ fn app(cx: Scope) -> Element { } } div { class: "digit-keys", - button { class: "calculator-key key-0", onclick: move |_| input_digit(0), - "0" - } - button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), - "●" - } + button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" } + button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" } (1..10).map(|k| rsx!{ button { class: "calculator-key {k}", @@ -116,22 +113,13 @@ fn app(cx: Scope) -> Element { } } div { class: "operator-keys", - button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), - "÷" - } - button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), - "×" - } - button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), - "−" - } - button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), - "+" - } - button { class: "calculator-key key-equals", - onclick: move |_| { - val.set(format!("{}", calc_val(val.as_str()))); - }, + button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), "÷" } + button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), "×" } + button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), "−" } + button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), "+" } + button { + class: "calculator-key key-equals", + onclick: move |_| val.set(format!("{}", calc_val(val.as_str()))), "=" } } diff --git a/examples/callback.rs b/examples/callback.rs index e7930ef34..bf2e3e3a0 100644 --- a/examples/callback.rs +++ b/examples/callback.rs @@ -5,7 +5,7 @@ fn main() { } fn app(cx: Scope) -> Element { - let login = use_callback!(cx, || move |evt: MouseEvent| async move { + let login = use_callback!(cx, move |_| async move { let res = reqwest::get("https://dog.ceo/api/breeds/list/all") .await .unwrap() @@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element { .await .unwrap(); - println!("{}, ", res); + println!("{:#?}, ", res); }); cx.render(rsx! { diff --git a/examples/nested_listeners.rs b/examples/nested_listeners.rs index 163a827a2..3938959a7 100644 --- a/examples/nested_listeners.rs +++ b/examples/nested_listeners.rs @@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element { button { onclick: move |evt| { println!("clicked! bottom no bubbling"); - evt.cancel_bubble(); + evt.stop_propogation(); }, "Dont propogate" } diff --git a/examples/rsx_compile_fail.rs b/examples/rsx_compile_fail.rs index 75b740df9..6e02c66a3 100644 --- a/examples/rsx_compile_fail.rs +++ b/examples/rsx_compile_fail.rs @@ -6,7 +6,7 @@ use dioxus_ssr::config::SsrConfig; fn main() { let mut vdom = VirtualDom::new(example); - vdom.rebuild(); + _ = vdom.rebuild(); let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true)); println!("{}", out); diff --git a/examples/simple_todo.rs b/examples/simple_todo.rs deleted file mode 100644 index 454e4b508..000000000 --- a/examples/simple_todo.rs +++ /dev/null @@ -1,32 +0,0 @@ -use dioxus::prelude::*; - -fn main() { - dioxus_desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let mut idx = use_state(cx, || 0); - let onhover = |h| println!("go!"); - - cx.render(rsx! { - div { - button { onclick: move |_| idx += 1, "+" } - button { onclick: move |_| idx -= 1, "-" } - ul { - (0..**idx).map(|i| rsx! { - Child { i: i, onhover: onhover } - }) - } - } - }) -} - -#[inline_props] -fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element { - cx.render(rsx! { - li { - onmouseover: move |e| onhover.call(e), - "{i}" - } - }) -} diff --git a/examples/suspense2.rs b/examples/suspense2.rs deleted file mode 100644 index b63867f0a..000000000 --- a/examples/suspense2.rs +++ /dev/null @@ -1,16 +0,0 @@ -use dioxus::prelude::*; -use dioxus_desktop::{Config, LogicalSize, WindowBuilder}; - -fn main() { - dioxus_desktop::launch(|cx| cx.render(rsx! { async_app {} })); -} - -async fn async_app(cx: Scope<'_>) -> Element { - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - - cx.render(rsx! { - div { - "hi!" - } - }) -} diff --git a/examples/window_event.rs b/examples/window_event.rs index 8805bcfdd..cd752fa17 100644 --- a/examples/window_event.rs +++ b/examples/window_event.rs @@ -35,13 +35,13 @@ fn app(cx: Scope) -> Element { nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| window.set_minimized(true), "Minimize" } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| { window.set_fullscreen(!**fullscreen); @@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element { } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| window.close(), "Close" } @@ -66,7 +66,7 @@ fn app(cx: Scope) -> Element { div { button { class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| { window.set_always_on_top(!always_on_top); always_on_top.set(!always_on_top); @@ -77,7 +77,7 @@ fn app(cx: Scope) -> Element { div { button { class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| { window.set_decorations(!decorations); decorations.set(!decorations); @@ -88,7 +88,7 @@ fn app(cx: Scope) -> Element { div { button { class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| window.set_title("Dioxus Application"), "Change Title" } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 6ede9be4d..02c5bed99 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -159,15 +159,24 @@ pub enum TemplateNode<'a> { }, /// This template node is just a piece of static text - Text { text: &'a str }, + Text { + /// The actual text + text: &'a str, + }, /// This template node is unknown, and needs to be created at runtime. - Dynamic { id: usize }, + Dynamic { + /// The index of the dynamic node in the VNode's dynamic_nodes list + id: usize, + }, /// This template node is known to be some text, but needs to be created at runtime /// /// This is separate from the pure Dynamic variant for various optimizations - DynamicText { id: usize }, + DynamicText { + /// The index of the dynamic node in the VNode's dynamic_nodes list + id: usize, + }, } /// A node created at runtime diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 724ec9b94..58e947574 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -33,7 +33,7 @@ webbrowser = "0.8.0" infer = "0.11.0" dunce = "1.0.2" -interprocess = { version = "1.1.1" } +interprocess = { version = "1.1.1", optional = true} futures-util = "0.3.25" [target.'cfg(target_os = "ios")'.dependencies] @@ -44,11 +44,12 @@ objc_id = "0.1.1" core-foundation = "0.9.3" [features] -default = ["tokio_runtime"] +default = ["tokio_runtime", "hot-reload"] tokio_runtime = ["tokio"] fullscreen = ["wry/fullscreen"] transparent = ["wry/transparent"] tray = ["wry/tray"] +hot-reload = ["interprocess"] [dev-dependencies] dioxus-core-macro = { path = "../core-macro" } diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index c97f5acb5..8dadf0c0b 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -1,7 +1,7 @@ use crate::desktop_context::{DesktopContext, UserWindowEvent}; use crate::events::{decode_event, EventMessage}; use dioxus_core::*; -use futures_channel::mpsc::UnboundedReceiver; +use futures_channel::mpsc::{unbounded, UnboundedSender}; use futures_util::StreamExt; #[cfg(target_os = "ios")] use objc::runtime::Object; @@ -22,6 +22,9 @@ pub(super) struct DesktopController { pub(super) pending_edits: Arc>>, pub(super) quit_app_on_close: bool, pub(super) is_ready: Arc, + pub(super) proxy: EventLoopProxy, + pub(super) event_tx: UnboundedSender, + #[cfg(target_os = "ios")] pub(super) views: Vec<*mut Object>, } @@ -33,9 +36,10 @@ impl DesktopController { root: Component

, props: P, proxy: EventLoopProxy, - mut event_rx: UnboundedReceiver, ) -> Self { let edit_queue = Arc::new(Mutex::new(Vec::new())); + let (event_tx, mut event_rx) = unbounded(); + let proxy2 = proxy.clone(); let pending_edits = edit_queue.clone(); let desktop_context_proxy = proxy.clone(); @@ -87,6 +91,8 @@ impl DesktopController { webviews: HashMap::new(), is_ready: Arc::new(AtomicBool::new(false)), quit_app_on_close: true, + proxy: proxy2, + event_tx, #[cfg(target_os = "ios")] views: vec![], } @@ -117,4 +123,8 @@ impl DesktopController { } } } + + pub(crate) fn set_template(&self, serialized_template: String) { + todo!() + } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index e5e184df9..f2bc2b3dc 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -168,6 +168,11 @@ pub enum UserWindowEvent { DragWindow, FocusWindow, + /// Set a new Dioxus template for hot-reloading + /// + /// Is a no-op in release builds. Must fit the right format for templates + SetTemplate(String), + Visible(bool), Minimize(bool), Maximize(bool), @@ -195,97 +200,101 @@ pub enum UserWindowEvent { PopView, } -pub(super) fn handler( - user_event: UserWindowEvent, - desktop: &mut DesktopController, - control_flow: &mut ControlFlow, -) { - // currently dioxus-desktop supports a single window only, - // so we can grab the only webview from the map; - // on wayland it is possible that a user event is emitted - // before the webview is initialized. ignore the event. - let webview = if let Some(webview) = desktop.webviews.values().next() { - webview - } else { - return; - }; - let window = webview.window(); +impl DesktopController { + pub(super) fn handle_event( + &mut self, + user_event: UserWindowEvent, + control_flow: &mut ControlFlow, + ) { + // currently dioxus-desktop supports a single window only, + // so we can grab the only webview from the map; + // on wayland it is possible that a user event is emitted + // before the webview is initialized. ignore the event. + let webview = if let Some(webview) = self.webviews.values().next() { + webview + } else { + return; + }; - match user_event { - Initialize | EditsReady => desktop.try_load_ready_webviews(), - CloseWindow => *control_flow = ControlFlow::Exit, - DragWindow => { - // if the drag_window has any errors, we don't do anything - window.fullscreen().is_none().then(|| window.drag_window()); - } - Visible(state) => window.set_visible(state), - Minimize(state) => window.set_minimized(state), - Maximize(state) => window.set_maximized(state), - MaximizeToggle => window.set_maximized(!window.is_maximized()), - Fullscreen(state) => { - if let Some(handle) = window.current_monitor() { - window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); + let window = webview.window(); + + match user_event { + Initialize | EditsReady => self.try_load_ready_webviews(), + SetTemplate(template) => self.set_template(template), + CloseWindow => *control_flow = ControlFlow::Exit, + DragWindow => { + // if the drag_window has any errors, we don't do anything + window.fullscreen().is_none().then(|| window.drag_window()); } - } - FocusWindow => window.set_focus(), - Resizable(state) => window.set_resizable(state), - AlwaysOnTop(state) => window.set_always_on_top(state), - - CursorVisible(state) => window.set_cursor_visible(state), - CursorGrab(state) => { - let _ = window.set_cursor_grab(state); - } - - SetTitle(content) => window.set_title(&content), - SetDecorations(state) => window.set_decorations(state), - - SetZoomLevel(scale_factor) => webview.zoom(scale_factor), - - Print => { - if let Err(e) = webview.print() { - // we can't panic this error. - log::warn!("Open print modal failed: {e}"); + Visible(state) => window.set_visible(state), + Minimize(state) => window.set_minimized(state), + Maximize(state) => window.set_maximized(state), + MaximizeToggle => window.set_maximized(!window.is_maximized()), + Fullscreen(state) => { + if let Some(handle) = window.current_monitor() { + window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); + } } - } - DevTool => { - #[cfg(debug_assertions)] - webview.open_devtools(); - #[cfg(not(debug_assertions))] - log::warn!("Devtools are disabled in release builds"); - } + FocusWindow => window.set_focus(), + Resizable(state) => window.set_resizable(state), + AlwaysOnTop(state) => window.set_always_on_top(state), - Eval(code) => { - if let Err(e) = webview.evaluate_script(code.as_str()) { - // we can't panic this error. - log::warn!("Eval script error: {e}"); + CursorVisible(state) => window.set_cursor_visible(state), + CursorGrab(state) => { + let _ = window.set_cursor_grab(state); } - } - #[cfg(target_os = "ios")] - PushView(view) => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - let ui_view = window.ui_view() as *mut Object; - let ui_view_frame: *mut Object = msg_send![ui_view, frame]; - let _: () = msg_send![view, setFrame: ui_view_frame]; - let _: () = msg_send![view, setAutoresizingMask: 31]; + SetTitle(content) => window.set_title(&content), + SetDecorations(state) => window.set_decorations(state), - let ui_view_controller = window.ui_view_controller() as *mut Object; - let _: () = msg_send![ui_view_controller, setView: view]; - desktop.views.push(ui_view); - }, + SetZoomLevel(scale_factor) => webview.zoom(scale_factor), + + Print => { + if let Err(e) = webview.print() { + // we can't panic this error. + log::warn!("Open print modal failed: {e}"); + } + } + DevTool => { + #[cfg(debug_assertions)] + webview.open_devtools(); + #[cfg(not(debug_assertions))] + log::warn!("Devtools are disabled in release builds"); + } + + Eval(code) => { + if let Err(e) = webview.evaluate_script(code.as_str()) { + // we can't panic this error. + log::warn!("Eval script error: {e}"); + } + } + + #[cfg(target_os = "ios")] + PushView(view) => unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + let ui_view = window.ui_view() as *mut Object; + let ui_view_frame: *mut Object = msg_send![ui_view, frame]; + let _: () = msg_send![view, setFrame: ui_view_frame]; + let _: () = msg_send![view, setAutoresizingMask: 31]; - #[cfg(target_os = "ios")] - PopView => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - if let Some(view) = desktop.views.pop() { let ui_view_controller = window.ui_view_controller() as *mut Object; let _: () = msg_send![ui_view_controller, setView: view]; - } - }, + desktop.views.push(ui_view); + }, + + #[cfg(target_os = "ios")] + PopView => unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + if let Some(view) = desktop.views.pop() { + let ui_view_controller = window.ui_view_controller() as *mut Object; + let _: () = msg_send![ui_view_controller, setView: view]; + } + }, + } } } diff --git a/packages/desktop/src/hot_reload.rs b/packages/desktop/src/hot_reload.rs index f78df6ca8..6e394f5f0 100644 --- a/packages/desktop/src/hot_reload.rs +++ b/packages/desktop/src/hot_reload.rs @@ -1,28 +1,31 @@ use dioxus_core::VirtualDom; -use interprocess::local_socket::LocalSocketStream; -// use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; + +use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use std::io::{BufRead, BufReader}; use std::time::Duration; use std::{sync::Arc, sync::Mutex}; -fn _handle_error(connection: std::io::Result) -> Option { +fn handle_error(connection: std::io::Result) -> Option { connection .map_err(|error| eprintln!("Incoming connection failed: {}", error)) .ok() } -pub(crate) fn _init(_dom: &VirtualDom) { +pub(crate) fn init(_dom: &VirtualDom) { let latest_in_connection: Arc>>> = Arc::new(Mutex::new(None)); - let _latest_in_connection_handle = latest_in_connection.clone(); + + let latest_in_connection_handle = latest_in_connection.clone(); // connect to processes for incoming data std::thread::spawn(move || { - // if let Ok(listener) = LocalSocketListener::bind("@dioxusin") { - // for conn in listener.incoming().filter_map(handle_error) { - // *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn)); - // } - // } + let temp_file = std::env::temp_dir().join("@dioxusin"); + + if let Ok(listener) = LocalSocketListener::bind(temp_file) { + for conn in listener.incoming().filter_map(handle_error) { + *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn)); + } + } }); std::thread::spawn(move || { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index e8961184f..585a7ff51 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -8,13 +8,17 @@ mod controller; mod desktop_context; mod escape; mod events; -#[cfg(any(feature = "hot-reload", debug_assertions))] -mod hot_reload; mod protocol; +#[cfg(all(feature = "hot-reload", debug_assertions))] +mod hot_reload; + +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + use desktop_context::UserWindowEvent; pub use desktop_context::{use_eval, use_window, DesktopContext}; -use futures_channel::mpsc::unbounded; +use futures_channel::mpsc::UnboundedSender; pub use wry; pub use wry::application as tao; @@ -100,15 +104,68 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// ``` pub fn launch_with_props(root: Component

, props: P, mut cfg: Config) { let event_loop = EventLoop::with_user_event(); + let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy()); - let (event_tx, event_rx) = unbounded(); - let mut desktop = - DesktopController::new_on_tokio(root, props, event_loop.create_proxy(), event_rx); - let proxy = event_loop.create_proxy(); + event_loop.run(move |window_event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; - // We assume that if the icon is None, then the user just didnt set it + match window_event { + Event::NewEvents(StartCause::Init) => desktop.start(&mut cfg, event_loop), + + Event::WindowEvent { + event, window_id, .. + } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow), + _ => {} + }, + + Event::UserEvent(user_event) => desktop.handle_event(user_event, control_flow), + Event::MainEventsCleared => {} + Event::Resumed => {} + Event::Suspended => {} + Event::LoopDestroyed => {} + Event::RedrawRequested(_id) => {} + _ => {} + } + }) +} + +impl DesktopController { + fn start( + &mut self, + cfg: &mut Config, + event_loop: &tao::event_loop::EventLoopWindowTarget, + ) { + let webview = build_webview( + cfg, + event_loop, + self.is_ready.clone(), + self.proxy.clone(), + self.event_tx.clone(), + ); + + self.webviews.insert(webview.window().id(), webview); + } +} + +fn build_webview( + cfg: &mut Config, + event_loop: &tao::event_loop::EventLoopWindowTarget, + is_ready: Arc, + proxy: tao::event_loop::EventLoopProxy, + event_tx: UnboundedSender, +) -> wry::webview::WebView { + let builder = cfg.window.clone(); + let window = builder.build(event_loop).unwrap(); + let file_handler = cfg.file_drop_handler.take(); + let custom_head = cfg.custom_head.clone(); + let resource_dir = cfg.resource_dir.clone(); + let index_file = cfg.custom_index.clone(); + + // We assume that if the icon is None in cfg, then the user just didnt set it if cfg.window.window.window_icon.is_none() { - cfg.window = cfg.window.with_window_icon(Some( + window.set_window_icon(Some( tao::window::Icon::from_rgba( include_bytes!("./assets/default_icon.bin").to_vec(), 460, @@ -118,84 +175,62 @@ pub fn launch_with_props(root: Component

, props: P, mut cf )); } - event_loop.run(move |window_event, event_loop, control_flow| { - *control_flow = ControlFlow::Wait; - - // println!("window event: {:?}", window_event); - match window_event { - Event::NewEvents(StartCause::Init) => { - let builder = cfg.window.clone(); - - let window = builder.build(event_loop).unwrap(); - let window_id = window.id(); - - let (is_ready, _) = (desktop.is_ready.clone(), ()); - // let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone()); - - let proxy = proxy.clone(); - - let file_handler = cfg.file_drop_handler.take(); - let custom_head = cfg.custom_head.clone(); - let resource_dir = cfg.resource_dir.clone(); - let index_file = cfg.custom_index.clone(); - let event_tx = event_tx.clone(); - - let mut webview = WebViewBuilder::new(window) - .unwrap() - .with_transparent(cfg.window.window.transparent) - .with_url("dioxus://index.html/") - .unwrap() - .with_ipc_handler(move |_window: &Window, payload: String| { - parse_ipc_message(&payload) - .map(|message| match message.method() { - "user_event" => { - _ = event_tx.unbounded_send(message.params()); + let mut webview = WebViewBuilder::new(window) + .unwrap() + .with_transparent(cfg.window.window.transparent) + .with_url("dioxus://index.html/") + .unwrap() + .with_ipc_handler(move |_window: &Window, payload: String| { + parse_ipc_message(&payload) + .map(|message| match message.method() { + "user_event" => { + _ = event_tx.unbounded_send(message.params()); + } + "initialize" => { + is_ready.store(true, std::sync::atomic::Ordering::Relaxed); + let _ = proxy.send_event(UserWindowEvent::EditsReady); + } + "browser_open" => { + let data = message.params(); + log::trace!("Open browser: {:?}", data); + if let Some(temp) = data.as_object() { + if temp.contains_key("href") { + let url = temp.get("href").unwrap().as_str().unwrap(); + if let Err(e) = webbrowser::open(url) { + log::error!("Open Browser error: {:?}", e); } - "initialize" => { - is_ready.store(true, std::sync::atomic::Ordering::Relaxed); - let _ = proxy.send_event(UserWindowEvent::EditsReady); - } - "browser_open" => { - let data = message.params(); - log::trace!("Open browser: {:?}", data); - if let Some(temp) = data.as_object() { - if temp.contains_key("href") { - let url = temp.get("href").unwrap().as_str().unwrap(); - if let Err(e) = webbrowser::open(url) { - log::error!("Open Browser error: {:?}", e); - } - } - } - } - _ => (), - }) - .unwrap_or_else(|| { - log::warn!("invalid IPC message received"); - }); - }) - .with_custom_protocol(String::from("dioxus"), move |r| { - protocol::desktop_handler( - r, - resource_dir.clone(), - custom_head.clone(), - index_file.clone(), - ) - }) - .with_file_drop_handler(move |window, evet| { - file_handler - .as_ref() - .map(|handler| handler(window, evet)) - .unwrap_or_default() - }); + } + } + } + _ => (), + }) + .unwrap_or_else(|| { + log::warn!("invalid IPC message received"); + }); + }) + .with_custom_protocol(String::from("dioxus"), move |r| { + protocol::desktop_handler( + r, + resource_dir.clone(), + custom_head.clone(), + index_file.clone(), + ) + }) + .with_file_drop_handler(move |window, evet| { + file_handler + .as_ref() + .map(|handler| handler(window, evet)) + .unwrap_or_default() + }); - for (name, handler) in cfg.protocols.drain(..) { - webview = webview.with_custom_protocol(name, handler) - } + for (name, handler) in cfg.protocols.drain(..) { + webview = webview.with_custom_protocol(name, handler) + } - if cfg.disable_context_menu { - // in release mode, we don't want to show the dev tool or reload menus - webview = webview.with_initialization_script( - r#" + if cfg.disable_context_menu { + // in release mode, we don't want to show the dev tool or reload menus + webview = webview.with_initialization_script( + r#" if (document.addEventListener) { document.addEventListener('contextmenu', function(e) { e.preventDefault(); @@ -206,32 +241,11 @@ pub fn launch_with_props(root: Component

, props: P, mut cf }); } "#, - ) - } else { - // in debug, we are okay with the reload menu showing and dev tool - webview = webview.with_devtools(true); - } + ) + } else { + // in debug, we are okay with the reload menu showing and dev tool + webview = webview.with_devtools(true); + } - desktop.webviews.insert(window_id, webview.build().unwrap()); - } - - Event::WindowEvent { - event, window_id, .. - } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow), - _ => {} - }, - - Event::UserEvent(user_event) => { - desktop_context::handler(user_event, &mut desktop, control_flow) - } - Event::MainEventsCleared => {} - Event::Resumed => {} - Event::Suspended => {} - Event::LoopDestroyed => {} - Event::RedrawRequested(_id) => {} - _ => {} - } - }) + webview.build().unwrap() } diff --git a/packages/hooks/src/usecallback.rs b/packages/hooks/src/usecallback.rs index 180ed2242..0c5661a92 100644 --- a/packages/hooks/src/usecallback.rs +++ b/packages/hooks/src/usecallback.rs @@ -17,6 +17,12 @@ macro_rules! use_callback { move || $($rest)* ) }; + ($cx:ident, $($rest:tt)*) => { + use_callback( + $cx, + move || $($rest)* + ) + }; } pub fn use_callback<'a, T, R, F>(cx: &'a ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + 'a where diff --git a/packages/interpreter/src/bindings.rs b/packages/interpreter/src/bindings.rs index ee7e4f48c..40da394d0 100644 --- a/packages/interpreter/src/bindings.rs +++ b/packages/interpreter/src/bindings.rs @@ -17,6 +17,9 @@ extern "C" { #[wasm_bindgen(method)] pub fn MountToRoot(this: &Interpreter); + #[wasm_bindgen(method)] + pub fn AppendChildren(this: &Interpreter, m: u32); + #[wasm_bindgen(method)] pub fn AssignId(this: &Interpreter, path: &[u8], id: u32); diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 2c0e939bd..93da98b0c 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -210,7 +210,6 @@ export class Interpreter { } } handleEdits(edits) { - for (let template of edits.templates) { this.SaveTemplate(template); } @@ -276,7 +275,16 @@ export class Interpreter { } HydrateText(path, value, id) { let node = this.LoadChild(path); - node.textContent = value; + + if (node.nodeType == Node.TEXT_NODE) { + node.textContent = value; + } else { + // replace with a textnode + let text = document.createTextNode(value); + node.replaceWith(text); + node = text; + } + this.nodes[id] = node; } ReplacePlaceholder(path, m) { diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index ffda78ff7..2e01f5150 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -220,7 +220,12 @@ impl RealDom { let node = self.tree.get_mut(node_id).unwrap(); if let NodeType::Text { text } = &mut node.node_data.node_type { *text = value.to_string(); + } else { + node.node_data.node_type = NodeType::Text { + text: value.to_string(), + }; } + mark_dirty(node_id, NodeMask::new().with_text(), &mut nodes_updated); } LoadTemplate { name, index, id } => { diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 94315886f..d48b6df3b 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -14,15 +14,14 @@ use futures_channel::mpsc; use js_sys::Function; use std::{any::Any, rc::Rc}; use wasm_bindgen::{closure::Closure, JsCast, JsValue}; -use web_sys::{Document, Element, Event, HtmlElement}; +use web_sys::{Document, Element, Event}; use crate::Config; pub struct WebsysDom { - document: Document, interpreter: Interpreter, handler: Closure, - root: Element, + _root: Element, } impl WebsysDom { @@ -36,9 +35,8 @@ impl WebsysDom { }; Self { - document, interpreter: Interpreter::new(root.clone()), - root, + _root: root, handler: Closure::wrap(Box::new(move |event: &web_sys::Event| { let _ = event_channel.unbounded_send(event.clone()); })), @@ -63,7 +61,7 @@ impl WebsysDom { let i = &self.interpreter; for edit in edits.drain(..) { match edit { - AppendChildren { id, m } => i.AppendChildren(id, m), + AppendChildren { id, m } => i.AppendChildren(m as u32), AssignId { path, id } => i.AssignId(path, id.0 as u32), CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32), CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32), @@ -87,8 +85,8 @@ impl WebsysDom { self.interpreter.NewEventListener( name, id.0 as u32, - self.handler.as_ref().unchecked_ref(), event_bubbles(&name[2..]), + self.handler.as_ref().unchecked_ref(), ); } RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32), diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 7b730db79..399dfbcef 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -211,6 +211,7 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop let mut res = { let work = dom.wait_for_work().fuse(); pin_mut!(work); + futures_util::select! { _ = work => None, new_template = hotreload_rx.next() => {