diff --git a/examples/mobile_demo/Cargo.toml b/examples/mobile_demo/Cargo.toml
index a90ff4f36..e980774e7 100644
--- a/examples/mobile_demo/Cargo.toml
+++ b/examples/mobile_demo/Cargo.toml
@@ -35,7 +35,7 @@ frameworks = ["WebKit"]
[dependencies]
anyhow = "1.0.56"
log = "0.4.11"
-wry = "0.34.0"
+wry = "0.35.0"
dioxus = { path = "../../packages/dioxus" }
dioxus-desktop = { path = "../../packages/desktop", features = [
"tokio_runtime",
diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs
index d42f59395..d4fe7c1ba 100644
--- a/packages/core/src/error_boundary.rs
+++ b/packages/core/src/error_boundary.rs
@@ -6,7 +6,7 @@ use crate::{
use std::{
any::{Any, TypeId},
backtrace::Backtrace,
- cell::RefCell,
+ cell::{Cell, RefCell},
error::Error,
fmt::{Debug, Display},
rc::Rc,
@@ -472,8 +472,8 @@ pub fn ErrorBoundary<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element {
attr_paths: &[],
};
VNode {
- parent: Default::default(),
- stable_id: Default::default(),
+ parent: Cell::new(None),
+ stable_id: Cell::new(None),
key: None,
template: std::cell::Cell::new(TEMPLATE),
root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml
index 1064d2c1e..4eed74b84 100644
--- a/packages/desktop/Cargo.toml
+++ b/packages/desktop/Cargo.toml
@@ -19,7 +19,10 @@ serde = "1.0.136"
serde_json = "1.0.79"
thiserror = { workspace = true }
tracing = { workspace = true }
-wry = { version = "0.34.0", default-features = false, features = ["tao", "protocol", "file-drop"] }
+wry = { version = "0.35.0", default-features = false, features = [
+ "protocol",
+ "file-drop",
+] }
futures-channel = { workspace = true }
tokio = { workspace = true, features = [
"sync",
@@ -39,11 +42,13 @@ futures-util = { workspace = true }
urlencoding = "2.1.2"
async-trait = "0.1.68"
crossbeam-channel = "0.5.8"
+tao = { version = "0.24.0", features = ["rwh_05"] }
+muda = "0.11.3"
[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
rfd = "0.12"
-global-hotkey = { git = "https://github.com/tauri-apps/global-hotkey" }
+global-hotkey = "0.4.1"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.7"
diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs
new file mode 100644
index 000000000..f88970f03
--- /dev/null
+++ b/packages/desktop/src/app.rs
@@ -0,0 +1,569 @@
+pub use crate::cfg::{Config, WindowCloseBehaviour};
+pub use crate::desktop_context::DesktopContext;
+pub use crate::desktop_context::{
+ use_window, use_wry_event_handler, window, DesktopService, WryEventHandler, WryEventHandlerId,
+};
+use crate::desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
+use crate::events::{IpcMessage, KnownIpcMethod};
+use crate::file_upload;
+use crate::query::QueryResult;
+use crate::shortcut::GlobalHotKeyEvent;
+use dioxus_core::*;
+use dioxus_html::{event_bubbles, MountedData};
+use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
+use global_hotkey::{
+ hotkey::{Code, HotKey, Modifiers},
+ GlobalHotKeyManager,
+};
+// use dioxus_interpreter_js::binary_protocol::Channel;
+use crate::element::DesktopElement;
+use crate::eval::init_eval;
+pub use crate::protocol::{
+ use_asset_handler, AssetFuture, AssetHandler, AssetRequest, AssetResponse,
+};
+use crate::shortcut::ShortcutRegistry;
+pub use crate::shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
+use futures_util::{pin_mut, FutureExt};
+use rustc_hash::FxHashMap;
+use std::rc::Rc;
+use std::sync::atomic::AtomicU16;
+use std::task::Waker;
+use std::{borrow::Borrow, cell::Cell};
+use std::{collections::HashMap, sync::Arc};
+pub use tao::dpi::{LogicalSize, PhysicalSize};
+use tao::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
+pub use tao::window::WindowBuilder;
+use tao::window::WindowId;
+use tao::{
+ event::{Event, StartCause, WindowEvent},
+ event_loop::ControlFlow,
+};
+use tao::{event_loop::EventLoopBuilder, window::Window};
+use tokio::runtime::Builder;
+pub use wry;
+use wry::WebContext;
+use wry::WebView;
+
+pub struct App
{
+ // move the props into a cell so we can pop it out later to create the first window
+ // iOS panics if we create a window before the event loop is started
+ pub(crate) props: Rc>>,
+ pub(crate) cfg: Rc| >>,
+ pub(crate) root: Component ,
+ pub(crate) webviews: HashMap,
+ pub(crate) event_handlers: WindowEventHandlers,
+ pub(crate) queue: WebviewQueue,
+ pub(crate) shortcut_manager: ShortcutRegistry,
+ pub(crate) global_hotkey_channel: crossbeam_channel::Receiver,
+ pub(crate) proxy: EventLoopProxy,
+ pub(crate) window_behavior: WindowCloseBehaviour,
+ pub(crate) control_flow: ControlFlow,
+ pub(crate) is_visible_before_start: bool,
+}
+
+impl App {
+ pub fn new(cfg: Config, props: P, root: Component ) -> (EventLoop, Self) {
+ let event_loop = EventLoopBuilder::::with_user_event().build();
+
+ let mut app = Self {
+ root,
+ window_behavior: cfg.last_window_close_behaviour,
+ is_visible_before_start: true,
+ webviews: HashMap::new(),
+ event_handlers: WindowEventHandlers::default(),
+ queue: WebviewQueue::default(),
+ shortcut_manager: ShortcutRegistry::new(),
+ global_hotkey_channel: GlobalHotKeyEvent::receiver().clone(),
+ proxy: event_loop.create_proxy(),
+ props: Rc::new(Cell::new(Some(props))),
+ cfg: Rc::new(Cell::new(Some(cfg))),
+ control_flow: ControlFlow::Wait,
+ };
+
+ #[cfg(all(feature = "hot-reload", debug_assertions))]
+ app.connect_hotreload();
+
+ (event_loop, app)
+ }
+
+ pub fn tick(
+ &mut self,
+ window_event: &Event<'_, UserWindowEvent>,
+ event_loop: &EventLoopWindowTarget,
+ ) {
+ // Set the control flow here, but make sure to update it at the end of the match
+ self.control_flow = ControlFlow::Wait;
+
+ self.event_handlers.apply_event(window_event, event_loop);
+
+ if let Ok(event) = self.global_hotkey_channel.try_recv() {
+ self.shortcut_manager.call_handlers(event);
+ }
+ }
+
+ pub fn connect_hotreload(&mut self) {
+ let proxy = self.proxy.clone();
+ dioxus_hot_reload::connect({
+ move |template| {
+ let _ = proxy.send_event(UserWindowEvent(
+ EventData::HotReloadEvent(template),
+ unsafe { WindowId::dummy() },
+ ));
+ }
+ });
+ }
+
+ //
+ pub fn handle_new_window(&mut self) {
+ for handler in self.queue.borrow_mut().drain(..) {
+ let id = handler.desktop_context.window.id();
+ self.webviews.insert(id, handler);
+ _ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id));
+ }
+ }
+
+ pub fn handle_close_requested(&mut self, id: WindowId) {
+ use WindowCloseBehaviour::*;
+
+ match self.window_behavior {
+ LastWindowExitsApp => {
+ self.webviews.remove(&id);
+ if self.webviews.is_empty() {
+ self.control_flow = ControlFlow::Exit
+ }
+ }
+
+ LastWindowHides => {
+ let Some(webview) = self.webviews.get(&id) else {
+ return;
+ };
+
+ hide_app_window(&webview.desktop_context.webview);
+ }
+
+ CloseWindow => {
+ self.webviews.remove(&id);
+ }
+ }
+ }
+
+ pub fn window_destroyed(&mut self, id: WindowId) {
+ self.webviews.remove(&id);
+
+ if matches!(
+ self.window_behavior,
+ WindowCloseBehaviour::LastWindowExitsApp
+ ) && self.webviews.is_empty()
+ {
+ self.control_flow = ControlFlow::Exit
+ }
+ }
+
+ pub fn handle_start_cause_init(&mut self, target: &EventLoopWindowTarget) {
+ let props = self.props.take().unwrap();
+ let cfg = self.cfg.take().unwrap();
+
+ let dom = VirtualDom::new_with_props(self.root, props);
+
+ self.is_visible_before_start = cfg.window.window.visible;
+
+ let handler = create_new_window(
+ cfg,
+ target,
+ &self.proxy,
+ dom,
+ &self.queue,
+ &self.event_handlers,
+ self.shortcut_manager.clone(),
+ );
+
+ let id = handler.desktop_context.window.id();
+ self.webviews.insert(id, handler);
+
+ _ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id));
+ }
+
+ pub fn handle_browser_open(&mut self, msg: IpcMessage) {
+ if let Some(temp) = msg.params().as_object() {
+ if temp.contains_key("href") {
+ let open = webbrowser::open(temp["href"].as_str().unwrap());
+ if let Err(e) = open {
+ tracing::error!("Open Browser error: {:?}", e);
+ }
+ }
+ }
+ }
+
+ pub fn handle_initialize_msg(&mut self, id: WindowId) {
+ let view = self.webviews.get_mut(&id).unwrap();
+ send_edits(view.dom.rebuild(), &view.desktop_context);
+ view.desktop_context
+ .window
+ .set_visible(self.is_visible_before_start);
+ }
+
+ pub fn handle_close_msg(&mut self, id: WindowId) {
+ self.webviews.remove(&id);
+
+ if self.webviews.is_empty() {
+ self.control_flow = ControlFlow::Exit
+ }
+ }
+
+ pub fn handle_poll_msg(&mut self, id: WindowId) {
+ self.poll_vdom(id);
+ }
+
+ pub fn handle_query_msg(&mut self, msg: IpcMessage, id: WindowId) {
+ let Ok(result) = serde_json::from_value::(msg.params()) else {
+ return;
+ };
+
+ let Some(view) = self.webviews.get(&id) else {
+ return;
+ };
+
+ view.dom
+ .base_scope()
+ .consume_context::()
+ .unwrap()
+ .query
+ .send(result);
+ }
+
+ pub fn handle_user_event_msg(&mut self, msg: IpcMessage, id: WindowId) {
+ let params = msg.params();
+
+ let evt = match serde_json::from_value::(params) {
+ Ok(value) => value,
+ Err(err) => {
+ tracing::error!("Error parsing user_event: {:?}", err);
+ return;
+ }
+ };
+
+ let HtmlEvent {
+ element,
+ name,
+ bubbles,
+ data,
+ } = evt;
+
+ let view = self.webviews.get_mut(&id).unwrap();
+
+ // check for a mounted event placeholder and replace it with a desktop specific element
+ let as_any = if let dioxus_html::EventData::Mounted = &data {
+ let query = view
+ .dom
+ .base_scope()
+ .consume_context::()
+ .unwrap()
+ .query
+ .clone();
+
+ let element = DesktopElement::new(element, view.desktop_context.clone(), query);
+
+ Rc::new(MountedData::new(element))
+ } else {
+ data.into_any()
+ };
+
+ view.dom.handle_event(&name, as_any, element, bubbles);
+
+ send_edits(view.dom.render_immediate(), &view.desktop_context);
+ }
+
+ #[cfg(all(feature = "hot-reload", debug_assertions))]
+ pub fn handle_hot_reload_msg(&mut self, msg: dioxus_hot_reload::HotReloadMsg) {
+ match msg {
+ dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
+ for webview in self.webviews.values_mut() {
+ webview.dom.replace_template(template);
+
+ // poll_vdom(webview);
+ todo!()
+ }
+ }
+ dioxus_hot_reload::HotReloadMsg::Shutdown => {
+ self.control_flow = ControlFlow::Exit;
+ }
+ }
+ }
+
+ pub fn handle_file_dialog_msg(&self, msg: IpcMessage, window: WindowId) {
+ if let Ok(file_diolog) =
+ serde_json::from_value::(msg.params())
+ {
+ let id = ElementId(file_diolog.target);
+ let event_name = &file_diolog.event;
+ let event_bubbles = file_diolog.bubbles;
+ let files = file_upload::get_file_event(&file_diolog);
+ let data = Rc::new(FormData {
+ value: Default::default(),
+ values: Default::default(),
+ files: Some(Arc::new(NativeFileEngine::new(files))),
+ });
+
+ let view = self.webviews.get_mut(&window).unwrap();
+
+ if event_name == "change&input" {
+ view.dom
+ .handle_event("input", data.clone(), id, event_bubbles);
+ view.dom.handle_event("change", data, id, event_bubbles);
+ } else {
+ view.dom.handle_event(event_name, data, id, event_bubbles);
+ }
+
+ todo!()
+ // send_edits(view.dom.render_immediate(), &view.desktop_context);
+ }
+ }
+
+ /// Poll the virtualdom until it's pending
+ ///
+ /// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
+ ///
+ /// All IO is done on the tokio runtime we started earlier
+ pub fn poll_vdom(&mut self, id: WindowId) {
+ let view = self.webviews.get_mut(&id).unwrap();
+
+ let mut cx = std::task::Context::from_waker(&view.waker);
+
+ loop {
+ {
+ let fut = view.dom.wait_for_work();
+ pin_mut!(fut);
+
+ match fut.poll_unpin(&mut cx) {
+ std::task::Poll::Ready(_) => {}
+ std::task::Poll::Pending => break,
+ }
+ }
+
+ send_edits(view.dom.render_immediate(), &view.desktop_context);
+ }
+ }
+}
+
+pub fn create_new_window(
+ mut cfg: Config,
+ event_loop: &EventLoopWindowTarget,
+ proxy: &EventLoopProxy,
+ dom: VirtualDom,
+ queue: &WebviewQueue,
+ event_handlers: &WindowEventHandlers,
+ shortcut_manager: ShortcutRegistry,
+) -> WebviewHandler {
+ let (webview, web_context, asset_handlers, edit_queue, window) =
+ crate::webview::build(&mut cfg, event_loop, proxy.clone());
+
+ let desktop_context = Rc::from(DesktopService::new(
+ window,
+ webview,
+ proxy.clone(),
+ event_loop.clone(),
+ queue.clone(),
+ event_handlers.clone(),
+ shortcut_manager,
+ edit_queue,
+ asset_handlers,
+ ));
+
+ let cx = dom.base_scope();
+ cx.provide_context(desktop_context.clone());
+
+ // Init eval
+ init_eval(cx);
+
+ WebviewHandler {
+ // We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
+ waker: crate::waker::tao_waker(proxy, desktop_context.window.id()),
+ desktop_context,
+ dom,
+ _web_context: web_context,
+ }
+}
+
+pub struct WebviewHandler {
+ dom: VirtualDom,
+ desktop_context: DesktopContext,
+ waker: Waker,
+
+ // Wry assumes the webcontext is alive for the lifetime of the webview.
+ // We need to keep the webcontext alive, otherwise the webview will crash
+ _web_context: WebContext,
+}
+
+/// Send a list of mutations to the webview
+pub fn send_edits(edits: Mutations, desktop_context: &DesktopContext) {
+ let mut channel = desktop_context.channel.borrow_mut();
+ let mut templates = desktop_context.templates.borrow_mut();
+ if let Some(bytes) = apply_edits(
+ edits,
+ &mut channel,
+ &mut templates,
+ &desktop_context.max_template_count,
+ ) {
+ desktop_context.edit_queue.add_edits(bytes)
+ }
+}
+
+pub struct Channel {}
+
+pub fn apply_edits(
+ mutations: Mutations,
+ channel: &mut Channel,
+ templates: &mut FxHashMap,
+ max_template_count: &AtomicU16,
+) -> Option> {
+ use dioxus_core::Mutation::*;
+ if mutations.templates.is_empty() && mutations.edits.is_empty() {
+ return None;
+ }
+ for template in mutations.templates {
+ add_template(&template, channel, templates, max_template_count);
+ }
+ for edit in mutations.edits {
+ match edit {
+ AppendChildren { id, m } => channel.append_children(id.0 as u32, m as u16),
+ AssignId { path, id } => channel.assign_id(path, id.0 as u32),
+ CreatePlaceholder { id } => channel.create_placeholder(id.0 as u32),
+ CreateTextNode { value, id } => channel.create_text_node(value, id.0 as u32),
+ HydrateText { path, value, id } => channel.hydrate_text(path, value, id.0 as u32),
+ LoadTemplate { name, index, id } => {
+ if let Some(tmpl_id) = templates.get(name) {
+ channel.load_template(*tmpl_id, index as u16, id.0 as u32)
+ }
+ }
+ ReplaceWith { id, m } => channel.replace_with(id.0 as u32, m as u16),
+ ReplacePlaceholder { path, m } => channel.replace_placeholder(path, m as u16),
+ InsertAfter { id, m } => channel.insert_after(id.0 as u32, m as u16),
+ InsertBefore { id, m } => channel.insert_before(id.0 as u32, m as u16),
+ SetAttribute {
+ name,
+ value,
+ id,
+ ns,
+ } => match value {
+ BorrowedAttributeValue::Text(txt) => {
+ channel.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
+ }
+ BorrowedAttributeValue::Float(f) => {
+ channel.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
+ }
+ BorrowedAttributeValue::Int(n) => {
+ channel.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
+ }
+ BorrowedAttributeValue::Bool(b) => channel.set_attribute(
+ id.0 as u32,
+ name,
+ if b { "true" } else { "false" },
+ ns.unwrap_or_default(),
+ ),
+ BorrowedAttributeValue::None => {
+ channel.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
+ }
+ _ => unreachable!(),
+ },
+ SetText { value, id } => channel.set_text(id.0 as u32, value),
+ NewEventListener { name, id, .. } => {
+ channel.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
+ }
+ RemoveEventListener { name, id } => {
+ channel.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
+ }
+ Remove { id } => channel.remove(id.0 as u32),
+ PushRoot { id } => channel.push_root(id.0 as u32),
+ }
+ }
+
+ let bytes: Vec<_> = channel.export_memory().collect();
+ channel.reset();
+ Some(bytes)
+}
+
+pub fn add_template(
+ template: &Template<'static>,
+ channel: &mut Channel,
+ templates: &mut FxHashMap,
+ max_template_count: &AtomicU16,
+) {
+ let current_max_template_count = max_template_count.load(std::sync::atomic::Ordering::Relaxed);
+ for root in template.roots.iter() {
+ create_template_node(channel, root);
+ templates.insert(template.name.to_owned(), current_max_template_count);
+ }
+ channel.add_templates(current_max_template_count, template.roots.len() as u16);
+
+ max_template_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
+}
+
+pub fn create_template_node(channel: &mut Channel, v: &'static TemplateNode<'static>) {
+ use TemplateNode::*;
+ match v {
+ Element {
+ tag,
+ namespace,
+ attrs,
+ children,
+ ..
+ } => {
+ // Push the current node onto the stack
+ match namespace {
+ Some(ns) => channel.create_element_ns(tag, ns),
+ None => channel.create_element(tag),
+ }
+ // Set attributes on the current node
+ for attr in *attrs {
+ if let TemplateAttribute::Static {
+ name,
+ value,
+ namespace,
+ } = attr
+ {
+ channel.set_top_attribute(name, value, namespace.unwrap_or_default())
+ }
+ }
+ // Add each child to the stack
+ for child in *children {
+ create_template_node(channel, child);
+ }
+ // Add all children to the parent
+ channel.append_children_to_top(children.len() as u16);
+ }
+ Text { text } => channel.create_raw_text(text),
+ DynamicText { .. } => channel.create_raw_text("p"),
+ Dynamic { .. } => channel.add_placeholder(),
+ }
+}
+
+/// Different hide implementations per platform
+#[allow(unused)]
+pub fn hide_app_window(webview: &WebView) {
+ #[cfg(target_os = "windows")]
+ {
+ use wry::application::platform::windows::WindowExtWindows;
+ window.set_visible(false);
+ window.set_skip_taskbar(true);
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ use wry::application::platform::unix::WindowExtUnix;
+ window.set_visible(false);
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ // window.set_visible(false); has the wrong behaviour on macOS
+ // It will hide the window but not show it again when the user switches
+ // back to the app. `NSApplication::hide:` has the correct behaviour
+ use objc::runtime::Object;
+ use objc::{msg_send, sel, sel_impl};
+ objc::rc::autoreleasepool(|| unsafe {
+ let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication];
+ let nil = std::ptr::null_mut::();
+ let _: () = msg_send![app, hide: nil];
+ });
+ }
+}
diff --git a/packages/desktop/src/asset_handler.rs b/packages/desktop/src/asset_handler.rs
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs
index 596b21938..9d1854bdf 100644
--- a/packages/desktop/src/cfg.rs
+++ b/packages/desktop/src/cfg.rs
@@ -1,11 +1,10 @@
use std::borrow::Cow;
use std::path::PathBuf;
-use wry::application::window::Icon;
+use tao::window::{Icon, Window, WindowBuilder};
use wry::{
- application::window::{Window, WindowBuilder},
http::{Request as HttpRequest, Response as HttpResponse},
- webview::FileDropEvent,
+ FileDropEvent,
};
// pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView);
@@ -38,7 +37,7 @@ pub struct Config {
pub(crate) enable_default_menu_bar: bool,
}
-type DropHandler = Box bool>;
+type DropHandler = Box bool>;
pub(crate) type WryProtocol = (
String,
@@ -120,7 +119,7 @@ impl Config {
/// Set a file drop handler
pub fn with_file_drop_handler(
mut self,
- handler: impl Fn(&Window, FileDropEvent) -> bool + 'static,
+ handler: impl Fn(FileDropEvent) -> bool + 'static,
) -> Self {
self.file_drop_handler = Some(Box::new(handler));
self
diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs
index 62a574d9b..763c00c39 100644
--- a/packages/desktop/src/desktop_context.rs
+++ b/packages/desktop/src/desktop_context.rs
@@ -1,38 +1,29 @@
-use crate::create_new_window;
-use crate::events::IpcMessage;
use crate::protocol::AssetFuture;
use crate::protocol::AssetHandlerRegistry;
use crate::query::QueryEngine;
use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError};
use crate::AssetHandler;
use crate::Config;
-use crate::WebviewHandler;
+use crate::{app::WebviewHandler, events::IpcMessage};
use dioxus_core::ScopeState;
use dioxus_core::VirtualDom;
-#[cfg(all(feature = "hot-reload", debug_assertions))]
-use dioxus_hot_reload::HotReloadMsg;
use dioxus_interpreter_js::binary_protocol::Channel;
use rustc_hash::FxHashMap;
use slab::Slab;
-use std::cell::RefCell;
-use std::fmt::Debug;
-use std::fmt::Formatter;
-use std::rc::Rc;
-use std::rc::Weak;
-use std::sync::atomic::AtomicU16;
-use std::sync::Arc;
-use std::sync::Mutex;
-use wry::application::event::Event;
-use wry::application::event_loop::EventLoopProxy;
-use wry::application::event_loop::EventLoopWindowTarget;
-#[cfg(target_os = "ios")]
-use wry::application::platform::ios::WindowExtIOS;
-use wry::application::window::Fullscreen as WryFullscreen;
-use wry::application::window::Window;
-use wry::application::window::WindowId;
-use wry::webview::WebView;
+use std::{
+ cell::RefCell, fmt::Debug, fmt::Formatter, rc::Rc, rc::Weak, sync::atomic::AtomicU16,
+ sync::Arc, sync::Mutex,
+};
+use tao::{
+ event::Event,
+ event_loop::{EventLoopProxy, EventLoopWindowTarget},
+ window::{Fullscreen as WryFullscreen, Window, WindowId},
+};
-pub type ProxyType = EventLoopProxy;
+use wry::{RequestAsyncResponder, WebView};
+
+#[cfg(target_os = "ios")]
+use tao::platform::ios::WindowExtIOS;
/// Get an imperative handle to the current window without using a hook
///
@@ -44,7 +35,6 @@ pub fn window() -> DesktopContext {
}
/// Get an imperative handle to the current window
-#[deprecated = "Prefer the using the `window` function directly for cleaner code"]
pub fn use_window(cx: &ScopeState) -> &DesktopContext {
cx.use_hook(|| cx.consume_context::())
.as_ref()
@@ -56,7 +46,7 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
#[derive(Default, Clone)]
pub(crate) struct EditQueue {
queue: Arc>>>,
- responder: Arc>>,
+ responder: Arc>>,
}
impl Debug for EditQueue {
@@ -71,7 +61,7 @@ impl Debug for EditQueue {
}
impl EditQueue {
- pub fn handle_request(&self, responder: wry::webview::RequestAsyncResponder) {
+ pub fn handle_request(&self, responder: RequestAsyncResponder) {
let mut queue = self.queue.lock().unwrap();
if let Some(bytes) = queue.pop() {
responder.respond(wry::http::Response::new(bytes));
@@ -106,10 +96,12 @@ pub(crate) type WebviewQueue = Rc>>;
/// ```
pub struct DesktopService {
/// The wry/tao proxy to the current window
- pub webview: Rc,
+ pub webview: WebView,
+
+ pub window: Window,
/// The proxy to the event loop
- pub proxy: ProxyType,
+ pub proxy: EventLoopProxy,
/// The receiver for queries about the current window
pub(super) query: QueryEngine,
@@ -141,14 +133,15 @@ impl std::ops::Deref for DesktopService {
type Target = Window;
fn deref(&self) -> &Self::Target {
- self.webview.window()
+ &self.window
}
}
impl DesktopService {
pub(crate) fn new(
+ window: Window,
webview: WebView,
- proxy: ProxyType,
+ proxy: EventLoopProxy,
event_loop: EventLoopWindowTarget,
webviews: WebviewQueue,
event_handlers: WindowEventHandlers,
@@ -157,7 +150,8 @@ impl DesktopService {
asset_handlers: AssetHandlerRegistry,
) -> Self {
Self {
- webview: Rc::new(webview),
+ window,
+ webview,
proxy,
event_loop,
query: Default::default(),
@@ -198,7 +192,7 @@ impl DesktopService {
.consume_context::>()
.unwrap();
- let id = window.desktop_context.webview.window().id();
+ let id = window.desktop_context.window.id();
self.proxy
.send_event(UserWindowEvent(EventData::NewWindow, id))
@@ -222,7 +216,7 @@ impl DesktopService {
/// onmousedown: move |_| { desktop.drag_window(); }
/// ```
pub fn drag(&self) {
- let window = self.webview.window();
+ let window = &self.window;
// if the drag_window has any errors, we don't do anything
if window.fullscreen().is_none() {
@@ -232,7 +226,7 @@ impl DesktopService {
/// Toggle whether the window is maximized or not
pub fn toggle_maximized(&self) {
- let window = self.webview.window();
+ let window = &self.window;
window.set_maximized(!window.is_maximized())
}
@@ -253,10 +247,10 @@ impl DesktopService {
/// change window to fullscreen
pub fn set_fullscreen(&self, fullscreen: bool) {
- if let Some(handle) = self.webview.window().current_monitor() {
- self.webview
- .window()
- .set_fullscreen(fullscreen.then_some(WryFullscreen::Borderless(Some(handle))));
+ if let Some(handle) = &self.window.current_monitor() {
+ self.window.set_fullscreen(
+ fullscreen.then_some(WryFullscreen::Borderless(Some(handle.clone()))),
+ );
}
}
@@ -336,7 +330,7 @@ impl DesktopService {
/// Push an objc view to the window
#[cfg(target_os = "ios")]
pub fn push_view(&self, view: objc_id::ShareId) {
- let window = self.webview.window();
+ let window = &self.window;
unsafe {
use objc::runtime::Object;
@@ -356,7 +350,7 @@ impl DesktopService {
/// Pop an objc view from the window
#[cfg(target_os = "ios")]
pub fn pop_view(&self) {
- let window = self.webview.window();
+ let window = &self.window;
unsafe {
use objc::runtime::Object;
@@ -380,7 +374,7 @@ pub enum EventData {
Ipc(IpcMessage),
#[cfg(all(feature = "hot-reload", debug_assertions))]
- HotReloadEvent(HotReloadMsg),
+ HotReloadEvent(dioxus_hot_reload::HotReloadMsg),
NewWindow,
diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs
index 7059a7c85..a877b52d6 100644
--- a/packages/desktop/src/events.rs
+++ b/packages/desktop/src/events.rs
@@ -8,9 +8,27 @@ pub struct IpcMessage {
params: serde_json::Value,
}
+#[derive(Deserialize, Serialize, Debug, Clone)]
+pub enum KnownIpcMethod<'a> {
+ FileDialog,
+ UserEvent,
+ Query,
+ BrowserOpen,
+ Initialize,
+ Other(&'a str),
+}
+
impl IpcMessage {
- pub(crate) fn method(&self) -> &str {
- self.method.as_str()
+ pub(crate) fn method(&self) -> KnownIpcMethod {
+ match self.method.as_str() {
+ // todo: this is a misspelling
+ "file_diolog" => KnownIpcMethod::FileDialog,
+ "user_event" => KnownIpcMethod::UserEvent,
+ "query" => KnownIpcMethod::Query,
+ "browser_open" => KnownIpcMethod::BrowserOpen,
+ "initialize" => KnownIpcMethod::Initialize,
+ _ => KnownIpcMethod::Other(&self.method),
+ }
}
pub(crate) fn params(self) -> serde_json::Value {
diff --git a/packages/desktop/src/hooks.rs b/packages/desktop/src/hooks.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/packages/desktop/src/hooks.rs
@@ -0,0 +1 @@
+
diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs
index 9460ca87d..1fc3e99c0 100644
--- a/packages/desktop/src/lib.rs
+++ b/packages/desktop/src/lib.rs
@@ -3,6 +3,8 @@
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
#![deny(missing_docs)]
+mod app;
+mod asset_handler;
mod cfg;
mod desktop_context;
mod element;
@@ -10,6 +12,7 @@ mod escape;
mod eval;
mod events;
mod file_upload;
+mod hooks;
#[cfg(any(target_os = "ios", target_os = "android"))]
mod mobile_shortcut;
mod protocol;
@@ -18,44 +21,24 @@ mod shortcut;
mod waker;
mod webview;
-use crate::query::QueryResult;
-use crate::shortcut::GlobalHotKeyEvent;
pub use cfg::{Config, WindowCloseBehaviour};
+pub use desktop_context::use_window;
pub use desktop_context::DesktopContext;
#[allow(deprecated)]
pub use desktop_context::{
- use_window, use_wry_event_handler, window, DesktopService, WryEventHandler, WryEventHandlerId,
+ use_wry_event_handler, window, DesktopService, WryEventHandler, WryEventHandlerId,
};
-use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
+use desktop_context::{EventData, UserWindowEvent};
use dioxus_core::*;
-use dioxus_html::{event_bubbles, MountedData};
-use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
-use dioxus_interpreter_js::binary_protocol::Channel;
-use element::DesktopElement;
-use eval::init_eval;
-use futures_util::{pin_mut, FutureExt};
+use events::KnownIpcMethod;
pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetRequest, AssetResponse};
-use rustc_hash::FxHashMap;
-use shortcut::ShortcutRegistry;
pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
-use std::cell::Cell;
-use std::rc::Rc;
-use std::sync::atomic::AtomicU16;
-use std::task::Waker;
-use std::{collections::HashMap, sync::Arc};
pub use tao::dpi::{LogicalSize, PhysicalSize};
-use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
+use tao::event::{Event, StartCause, WindowEvent};
pub use tao::window::WindowBuilder;
-use tao::{
- event::{Event, StartCause, WindowEvent},
- event_loop::ControlFlow,
-};
-// pub use webview::build_default_menu_bar;
+use tokio::runtime::Builder;
+pub use webview::build_default_menu_bar;
pub use wry;
-pub use wry::application as tao;
-use wry::application::event_loop::EventLoopBuilder;
-use wry::webview::WebView;
-use wry::{application::window::WindowId, webview::WebContext};
/// Launch the WebView and run the event loop.
///
@@ -127,512 +110,47 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
/// }
/// ```
pub fn launch_with_props(root: Component, props: P, cfg: Config) {
- let event_loop = EventLoopBuilder::::with_user_event().build();
-
- let proxy = event_loop.create_proxy();
-
- let window_behaviour = cfg.last_window_close_behaviour;
-
- // Intialize hot reloading if it is enabled
- #[cfg(all(feature = "hot-reload", debug_assertions))]
- dioxus_hot_reload::connect({
- let proxy = proxy.clone();
- move |template| {
- let _ = proxy.send_event(UserWindowEvent(
- EventData::HotReloadEvent(template),
- unsafe { WindowId::dummy() },
- ));
- }
- });
-
// We start the tokio runtime *on this thread*
// Any future we poll later will use this runtime to spawn tasks and for IO
- let rt = tokio::runtime::Builder::new_multi_thread()
+ // I would love to just allow dioxus to work with any runtime... but tokio is weird
+ let _guard = Builder::new_multi_thread()
.enable_all()
.build()
- .unwrap();
+ .unwrap()
+ .enter();
- // We enter the runtime but we poll futures manually, circumventing the per-task runtime budget
- let _guard = rt.enter();
-
- // We only have one webview right now, but we'll have more later
- // Store them in a hashmap so we can remove them when they're closed
- let mut webviews = HashMap::::new();
-
- // We use this to allow dynamically adding and removing window event handlers
- let event_handlers = WindowEventHandlers::default();
-
- let queue = WebviewQueue::default();
-
- let shortcut_manager = ShortcutRegistry::new();
- let global_hotkey_channel = GlobalHotKeyEvent::receiver();
-
- // move the props into a cell so we can pop it out later to create the first window
- // iOS panics if we create a window before the event loop is started
- let props = Rc::new(Cell::new(Some(props)));
- let cfg = Rc::new(Cell::new(Some(cfg)));
- let mut is_visible_before_start = true;
+ let (event_loop, mut app) = app::App::new(cfg, props, root);
event_loop.run(move |window_event, event_loop, control_flow| {
- *control_flow = ControlFlow::Poll;
-
- event_handlers.apply_event(&window_event, event_loop);
-
- if let Ok(event) = global_hotkey_channel.try_recv() {
- shortcut_manager.call_handlers(event);
- }
+ app.tick(&window_event, event_loop);
match window_event {
+ Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(event_loop),
Event::WindowEvent {
event, window_id, ..
} => match event {
- WindowEvent::CloseRequested => match window_behaviour {
- cfg::WindowCloseBehaviour::LastWindowExitsApp => {
- webviews.remove(&window_id);
-
- if webviews.is_empty() {
- *control_flow = ControlFlow::Exit
- }
- }
- cfg::WindowCloseBehaviour::LastWindowHides => {
- let Some(webview) = webviews.get(&window_id) else {
- return;
- };
- hide_app_window(&webview.desktop_context.webview);
- }
- cfg::WindowCloseBehaviour::CloseWindow => {
- webviews.remove(&window_id);
- }
- },
- WindowEvent::Destroyed { .. } => {
- webviews.remove(&window_id);
-
- if matches!(
- window_behaviour,
- cfg::WindowCloseBehaviour::LastWindowExitsApp
- ) && webviews.is_empty()
- {
- *control_flow = ControlFlow::Exit
- }
- }
+ WindowEvent::CloseRequested => app.handle_close_requested(window_id),
+ WindowEvent::Destroyed { .. } => app.window_destroyed(window_id),
_ => {}
},
-
- Event::NewEvents(StartCause::Init) => {
- let props = props.take().unwrap();
- let cfg = cfg.take().unwrap();
-
- // Create a dom
- let dom = VirtualDom::new_with_props(root, props);
-
- is_visible_before_start = cfg.window.window.visible;
-
- let handler = create_new_window(
- cfg,
- event_loop,
- &proxy,
- dom,
- &queue,
- &event_handlers,
- shortcut_manager.clone(),
- );
-
- let id = handler.desktop_context.webview.window().id();
- webviews.insert(id, handler);
- _ = proxy.send_event(UserWindowEvent(EventData::Poll, id));
- }
-
- Event::UserEvent(UserWindowEvent(EventData::NewWindow, _)) => {
- for handler in queue.borrow_mut().drain(..) {
- let id = handler.desktop_context.webview.window().id();
- webviews.insert(id, handler);
- _ = proxy.send_event(UserWindowEvent(EventData::Poll, id));
- }
- }
-
- Event::UserEvent(event) => match event.0 {
- #[cfg(all(feature = "hot-reload", debug_assertions))]
- EventData::HotReloadEvent(msg) => match msg {
- dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
- for webview in webviews.values_mut() {
- webview.dom.replace_template(template);
-
- poll_vdom(webview);
- }
- }
- dioxus_hot_reload::HotReloadMsg::Shutdown => {
- *control_flow = ControlFlow::Exit;
- }
+ Event::UserEvent(UserWindowEvent(event, id)) => match event {
+ EventData::Poll => app.handle_poll_msg(id),
+ EventData::NewWindow => app.handle_new_window(),
+ EventData::CloseWindow => app.handle_close_msg(id),
+ EventData::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg),
+ EventData::Ipc(msg) => match msg.method() {
+ KnownIpcMethod::FileDialog => app.handle_file_dialog_msg(msg, id),
+ KnownIpcMethod::UserEvent => app.handle_user_event_msg(msg, id),
+ KnownIpcMethod::Query => app.handle_query_msg(msg, id),
+ KnownIpcMethod::BrowserOpen => app.handle_browser_open(msg),
+ KnownIpcMethod::Initialize => app.handle_initialize_msg(id),
+ KnownIpcMethod::Other(_) => {}
},
-
- EventData::CloseWindow => {
- webviews.remove(&event.1);
-
- if webviews.is_empty() {
- *control_flow = ControlFlow::Exit
- }
- }
-
- EventData::Poll => {
- if let Some(view) = webviews.get_mut(&event.1) {
- poll_vdom(view);
- }
- }
-
- EventData::Ipc(msg) if msg.method() == "user_event" => {
- let params = msg.params();
-
- let evt = match serde_json::from_value::(params) {
- Ok(value) => value,
- Err(err) => {
- tracing::error!("Error parsing user_event: {:?}", err);
- return;
- }
- };
-
- let HtmlEvent {
- element,
- name,
- bubbles,
- data,
- } = evt;
-
- let view = webviews.get_mut(&event.1).unwrap();
-
- // check for a mounted event placeholder and replace it with a desktop specific element
- let as_any = if let dioxus_html::EventData::Mounted = &data {
- let query = view
- .dom
- .base_scope()
- .consume_context::()
- .unwrap()
- .query
- .clone();
-
- let element =
- DesktopElement::new(element, view.desktop_context.clone(), query);
-
- Rc::new(MountedData::new(element))
- } else {
- data.into_any()
- };
-
- view.dom.handle_event(&name, as_any, element, bubbles);
-
- send_edits(view.dom.render_immediate(), &view.desktop_context);
- }
-
- // When the webview sends a query, we need to send it to the query manager which handles dispatching the data to the correct pending query
- EventData::Ipc(msg) if msg.method() == "query" => {
- let params = msg.params();
-
- if let Ok(result) = serde_json::from_value::(params) {
- let view = webviews.get(&event.1).unwrap();
- let query = view
- .dom
- .base_scope()
- .consume_context::()
- .unwrap()
- .query
- .clone();
-
- query.send(result);
- }
- }
-
- EventData::Ipc(msg) if msg.method() == "initialize" => {
- let view = webviews.get_mut(&event.1).unwrap();
- send_edits(view.dom.rebuild(), &view.desktop_context);
- view.desktop_context
- .webview
- .window()
- .set_visible(is_visible_before_start);
- }
-
- EventData::Ipc(msg) if msg.method() == "browser_open" => {
- if let Some(temp) = msg.params().as_object() {
- if temp.contains_key("href") {
- let open = webbrowser::open(temp["href"].as_str().unwrap());
- if let Err(e) = open {
- tracing::error!("Open Browser error: {:?}", e);
- }
- }
- }
- }
-
- EventData::Ipc(msg) if msg.method() == "file_diolog" => {
- if let Ok(file_diolog) =
- serde_json::from_value::(msg.params())
- {
- let id = ElementId(file_diolog.target);
- let event_name = &file_diolog.event;
- let event_bubbles = file_diolog.bubbles;
- let files = file_upload::get_file_event(&file_diolog);
- let data = Rc::new(FormData {
- value: Default::default(),
- values: Default::default(),
- files: Some(Arc::new(NativeFileEngine::new(files))),
- });
-
- let view = webviews.get_mut(&event.1).unwrap();
-
- if event_name == "change&input" {
- view.dom
- .handle_event("input", data.clone(), id, event_bubbles);
- view.dom.handle_event("change", data, id, event_bubbles);
- } else {
- view.dom.handle_event(event_name, data, id, event_bubbles);
- }
-
- send_edits(view.dom.render_immediate(), &view.desktop_context);
- }
- }
-
- _ => {}
},
_ => {}
}
+
+ // Update the control flow
+ *control_flow = app.control_flow;
})
}
-
-fn create_new_window(
- mut cfg: Config,
- event_loop: &EventLoopWindowTarget,
- proxy: &EventLoopProxy,
- dom: VirtualDom,
- queue: &WebviewQueue,
- event_handlers: &WindowEventHandlers,
- shortcut_manager: ShortcutRegistry,
-) -> WebviewHandler {
- let (webview, web_context, asset_handlers, edit_queue) =
- webview::build(&mut cfg, event_loop, proxy.clone());
- let desktop_context = Rc::from(DesktopService::new(
- webview,
- proxy.clone(),
- event_loop.clone(),
- queue.clone(),
- event_handlers.clone(),
- shortcut_manager,
- edit_queue,
- asset_handlers,
- ));
-
- let cx = dom.base_scope();
- cx.provide_context(desktop_context.clone());
-
- // Init eval
- init_eval(cx);
-
- WebviewHandler {
- // We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
- waker: waker::tao_waker(proxy, desktop_context.webview.window().id()),
- desktop_context,
- dom,
- _web_context: web_context,
- }
-}
-
-struct WebviewHandler {
- dom: VirtualDom,
- desktop_context: DesktopContext,
- waker: Waker,
-
- // Wry assumes the webcontext is alive for the lifetime of the webview.
- // We need to keep the webcontext alive, otherwise the webview will crash
- _web_context: WebContext,
-}
-
-/// Poll the virtualdom until it's pending
-///
-/// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
-///
-/// All IO is done on the tokio runtime we started earlier
-fn poll_vdom(view: &mut WebviewHandler) {
- let mut cx = std::task::Context::from_waker(&view.waker);
-
- loop {
- {
- let fut = view.dom.wait_for_work();
- pin_mut!(fut);
-
- match fut.poll_unpin(&mut cx) {
- std::task::Poll::Ready(_) => {}
- std::task::Poll::Pending => break,
- }
- }
-
- send_edits(view.dom.render_immediate(), &view.desktop_context);
- }
-}
-
-/// Send a list of mutations to the webview
-fn send_edits(edits: Mutations, desktop_context: &DesktopContext) {
- let mut channel = desktop_context.channel.borrow_mut();
- let mut templates = desktop_context.templates.borrow_mut();
- if let Some(bytes) = apply_edits(
- edits,
- &mut channel,
- &mut templates,
- &desktop_context.max_template_count,
- ) {
- desktop_context.edit_queue.add_edits(bytes)
- }
-}
-
-fn apply_edits(
- mutations: Mutations,
- channel: &mut Channel,
- templates: &mut FxHashMap,
- max_template_count: &AtomicU16,
-) -> Option> {
- use dioxus_core::Mutation::*;
- if mutations.templates.is_empty() && mutations.edits.is_empty() {
- return None;
- }
- for template in mutations.templates {
- add_template(&template, channel, templates, max_template_count);
- }
- for edit in mutations.edits {
- match edit {
- AppendChildren { id, m } => channel.append_children(id.0 as u32, m as u16),
- AssignId { path, id } => channel.assign_id(path, id.0 as u32),
- CreatePlaceholder { id } => channel.create_placeholder(id.0 as u32),
- CreateTextNode { value, id } => channel.create_text_node(value, id.0 as u32),
- HydrateText { path, value, id } => channel.hydrate_text(path, value, id.0 as u32),
- LoadTemplate { name, index, id } => {
- if let Some(tmpl_id) = templates.get(name) {
- channel.load_template(*tmpl_id, index as u16, id.0 as u32)
- }
- }
- ReplaceWith { id, m } => channel.replace_with(id.0 as u32, m as u16),
- ReplacePlaceholder { path, m } => channel.replace_placeholder(path, m as u16),
- InsertAfter { id, m } => channel.insert_after(id.0 as u32, m as u16),
- InsertBefore { id, m } => channel.insert_before(id.0 as u32, m as u16),
- SetAttribute {
- name,
- value,
- id,
- ns,
- } => match value {
- BorrowedAttributeValue::Text(txt) => {
- channel.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
- }
- BorrowedAttributeValue::Float(f) => {
- channel.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
- }
- BorrowedAttributeValue::Int(n) => {
- channel.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
- }
- BorrowedAttributeValue::Bool(b) => channel.set_attribute(
- id.0 as u32,
- name,
- if b { "true" } else { "false" },
- ns.unwrap_or_default(),
- ),
- BorrowedAttributeValue::None => {
- channel.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
- }
- _ => unreachable!(),
- },
- SetText { value, id } => channel.set_text(id.0 as u32, value),
- NewEventListener { name, id, .. } => {
- channel.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
- }
- RemoveEventListener { name, id } => {
- channel.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
- }
- Remove { id } => channel.remove(id.0 as u32),
- PushRoot { id } => channel.push_root(id.0 as u32),
- }
- }
-
- let bytes: Vec<_> = channel.export_memory().collect();
- channel.reset();
- Some(bytes)
-}
-
-fn add_template(
- template: &Template<'static>,
- channel: &mut Channel,
- templates: &mut FxHashMap,
- max_template_count: &AtomicU16,
-) {
- let current_max_template_count = max_template_count.load(std::sync::atomic::Ordering::Relaxed);
- for root in template.roots.iter() {
- create_template_node(channel, root);
- templates.insert(template.name.to_owned(), current_max_template_count);
- }
- channel.add_templates(current_max_template_count, template.roots.len() as u16);
-
- max_template_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
-}
-
-fn create_template_node(channel: &mut Channel, v: &'static TemplateNode<'static>) {
- use TemplateNode::*;
- match v {
- Element {
- tag,
- namespace,
- attrs,
- children,
- ..
- } => {
- // Push the current node onto the stack
- match namespace {
- Some(ns) => channel.create_element_ns(tag, ns),
- None => channel.create_element(tag),
- }
- // Set attributes on the current node
- for attr in *attrs {
- if let TemplateAttribute::Static {
- name,
- value,
- namespace,
- } = attr
- {
- channel.set_top_attribute(name, value, namespace.unwrap_or_default())
- }
- }
- // Add each child to the stack
- for child in *children {
- create_template_node(channel, child);
- }
- // Add all children to the parent
- channel.append_children_to_top(children.len() as u16);
- }
- Text { text } => channel.create_raw_text(text),
- DynamicText { .. } => channel.create_raw_text("p"),
- Dynamic { .. } => channel.add_placeholder(),
- }
-}
-
-/// Different hide implementations per platform
-#[allow(unused)]
-fn hide_app_window(webview: &WebView) {
- #[cfg(target_os = "windows")]
- {
- use wry::application::platform::windows::WindowExtWindows;
- webview.window().set_visible(false);
- webview.window().set_skip_taskbar(true);
- }
-
- #[cfg(target_os = "linux")]
- {
- use wry::application::platform::unix::WindowExtUnix;
- webview.window().set_visible(false);
- }
-
- #[cfg(target_os = "macos")]
- {
- // webview.window().set_visible(false); has the wrong behaviour on macOS
- // It will hide the window but not show it again when the user switches
- // back to the app. `NSApplication::hide:` has the correct behaviour
- use objc::runtime::Object;
- use objc::{msg_send, sel, sel_impl};
- objc::rc::autoreleasepool(|| unsafe {
- let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication];
- let nil = std::ptr::null_mut::();
- let _: () = msg_send![app, hide: nil];
- });
- }
-}
diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs
index 61c6af661..36400ce95 100644
--- a/packages/desktop/src/protocol.rs
+++ b/packages/desktop/src/protocol.rs
@@ -1,4 +1,4 @@
-use crate::{window, DesktopContext};
+use crate::{use_window, DesktopContext};
use dioxus_core::ScopeState;
use dioxus_interpreter_js::INTERPRETER_JS;
use slab::Slab;
@@ -17,7 +17,7 @@ use tokio::{
};
use wry::{
http::{status::StatusCode, Request, Response},
- Result,
+ RequestAsyncResponder, Result,
};
use crate::desktop_context::EditQueue;
@@ -259,8 +259,7 @@ pub(super) async fn desktop_handler(
.body(Cow::from(body))
{
Ok(response) => {
- responder.respond(response);
- return;
+ return Ok(response);
}
Err(err) => tracing::error!("error building response: {}", err),
}
@@ -307,8 +306,7 @@ pub(super) async fn desktop_handler(
.body(Cow::from(asset))
{
Ok(response) => {
- responder.respond(response);
- return;
+ return response;
}
Err(err) => tracing::error!("error building response: {}", err),
}
diff --git a/packages/desktop/src/shortcut.rs b/packages/desktop/src/shortcut.rs
index bf8ed1382..55d49b405 100644
--- a/packages/desktop/src/shortcut.rs
+++ b/packages/desktop/src/shortcut.rs
@@ -3,7 +3,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, str::FromStr};
use dioxus_core::ScopeState;
use dioxus_html::input_data::keyboard_types::Modifiers;
use slab::Slab;
-use wry::application::keyboard::ModifiersState;
+use tao::keyboard::ModifiersState;
use crate::{desktop_context::DesktopContext, window};
diff --git a/packages/desktop/src/waker.rs b/packages/desktop/src/waker.rs
index 1e34baffc..11f23d8d8 100644
--- a/packages/desktop/src/waker.rs
+++ b/packages/desktop/src/waker.rs
@@ -1,7 +1,7 @@
use crate::desktop_context::{EventData, UserWindowEvent};
-use futures_util::task::ArcWake;
+use futures_util::{pin_mut, task::ArcWake, FutureExt};
use std::sync::Arc;
-use wry::application::{event_loop::EventLoopProxy, window::WindowId};
+use tao::{event_loop::EventLoopProxy, window::WindowId};
/// Create a waker that will send a poll event to the event loop.
///
diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs
index de5ff121c..d98e60099 100644
--- a/packages/desktop/src/webview.rs
+++ b/packages/desktop/src/webview.rs
@@ -1,20 +1,18 @@
use crate::desktop_context::{EditQueue, EventData};
use crate::protocol::{self, AssetHandlerRegistry};
use crate::{desktop_context::UserWindowEvent, Config};
+use muda::{Menu, MenuItem, PredefinedMenuItem, Submenu};
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
-pub use wry;
-pub use wry::application as tao;
-use wry::application::window::Window;
+use tao::window::Window;
use wry::http::Response;
-use wry::webview::{WebContext, WebView, WebViewBuilder};
+use wry::{WebContext, WebView, WebViewBuilder};
pub(crate) fn build(
cfg: &mut Config,
event_loop: &EventLoopWindowTarget,
proxy: EventLoopProxy,
-) -> (WebView, WebContext, AssetHandlerRegistry, EditQueue) {
+) -> (WebView, WebContext, AssetHandlerRegistry, EditQueue, Window) {
let builder = cfg.window.clone();
- let window = builder.with_visible(false).build(event_loop).unwrap();
let file_handler = cfg.file_drop_handler.take();
let custom_head = cfg.custom_head.clone();
let index_file = cfg.custom_index.clone();
@@ -43,15 +41,14 @@ pub(crate) fn build(
let asset_handlers = AssetHandlerRegistry::new();
let asset_handlers_ref = asset_handlers.clone();
- let mut webview = WebViewBuilder::new(window)
- .unwrap()
+ let mut webview = WebViewBuilder::new(&window)
.with_transparent(cfg.window.window.transparent)
.with_url("dioxus://index.html/")
.unwrap()
- .with_ipc_handler(move |window: &Window, payload: String| {
+ .with_ipc_handler(move |payload: String| {
// defer the event to the main thread
if let Ok(message) = serde_json::from_str(&payload) {
- _ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id()));
+ _ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window_id));
}
})
.with_asynchronous_custom_protocol(String::from("dioxus"), {
@@ -77,10 +74,10 @@ pub(crate) fn build(
});
}
})
- .with_file_drop_handler(move |window, evet| {
+ .with_file_drop_handler(move |event| {
file_handler
.as_ref()
- .map(|handler| handler(window, evet))
+ .map(|handler| handler(event))
.unwrap_or_default()
})
.with_web_context(&mut web_context);
@@ -129,63 +126,47 @@ pub(crate) fn build(
web_context,
asset_handlers,
edit_queue,
+ window,
)
}
-// /// Builds a standard menu bar depending on the users platform. It may be used as a starting point
-// /// to further customize the menu bar and pass it to a [`WindowBuilder`](tao::window::WindowBuilder).
-// /// > Note: The default menu bar enables macOS shortcuts like cut/copy/paste.
-// /// > The menu bar differs per platform because of constraints introduced
-// /// > by [`MenuItem`](tao::menu::MenuItem).
-// pub fn build_default_menu_bar() -> MenuBar {
-// let mut menu_bar = MenuBar::new();
+/// Builds a standard menu bar depending on the users platform. It may be used as a starting point
+/// to further customize the menu bar and pass it to a [`WindowBuilder`](tao::window::WindowBuilder).
+/// > Note: The default menu bar enables macOS shortcuts like cut/copy/paste.
+/// > The menu bar differs per platform because of constraints introduced
+/// > by [`MenuItem`](tao::menu::MenuItem).
+pub fn build_default_menu_bar() -> Menu {
+ let menu = Menu::new();
-// // since it is uncommon on windows to have an "application menu"
-// // we add a "window" menu to be more consistent across platforms with the standard menu
-// let mut window_menu = MenuBar::new();
-// #[cfg(target_os = "macos")]
-// {
-// window_menu.add_native_item(MenuItem::EnterFullScreen);
-// window_menu.add_native_item(MenuItem::Zoom);
-// window_menu.add_native_item(MenuItem::Separator);
-// }
+ // since it is uncommon on windows to have an "application menu"
+ // we add a "window" menu to be more consistent across platforms with the standard menu
+ let window_menu = Submenu::new("Window", true);
+ window_menu.append_items(&[
+ &PredefinedMenuItem::fullscreen(None),
+ &PredefinedMenuItem::separator(),
+ &PredefinedMenuItem::hide(None),
+ &PredefinedMenuItem::hide_others(None),
+ &PredefinedMenuItem::show_all(None),
+ &PredefinedMenuItem::maximize(None),
+ &PredefinedMenuItem::minimize(None),
+ &PredefinedMenuItem::close_window(None),
+ &PredefinedMenuItem::separator(),
+ &PredefinedMenuItem::quit(None),
+ ]);
-// window_menu.add_native_item(MenuItem::Hide);
+ let edit_menu = Submenu::new("Window", true);
+ edit_menu.append_items(&[
+ &PredefinedMenuItem::undo(None),
+ &PredefinedMenuItem::redo(None),
+ &PredefinedMenuItem::separator(),
+ &PredefinedMenuItem::cut(None),
+ &PredefinedMenuItem::copy(None),
+ &PredefinedMenuItem::paste(None),
+ &PredefinedMenuItem::separator(),
+ &PredefinedMenuItem::select_all(None),
+ ]);
-// #[cfg(target_os = "macos")]
-// {
-// window_menu.add_native_item(MenuItem::HideOthers);
-// window_menu.add_native_item(MenuItem::ShowAll);
-// }
+ menu.append_items(&[&window_menu, &edit_menu]);
-// window_menu.add_native_item(MenuItem::Minimize);
-// window_menu.add_native_item(MenuItem::CloseWindow);
-// window_menu.add_native_item(MenuItem::Separator);
-// window_menu.add_native_item(MenuItem::Quit);
-// menu_bar.add_submenu("Window", true, window_menu);
-
-// // since tao supports none of the below items on linux we should only add them on macos/windows
-// #[cfg(not(target_os = "linux"))]
-// {
-// let mut edit_menu = MenuBar::new();
-// #[cfg(target_os = "macos")]
-// {
-// edit_menu.add_native_item(MenuItem::Undo);
-// edit_menu.add_native_item(MenuItem::Redo);
-// edit_menu.add_native_item(MenuItem::Separator);
-// }
-
-// edit_menu.add_native_item(MenuItem::Cut);
-// edit_menu.add_native_item(MenuItem::Copy);
-// edit_menu.add_native_item(MenuItem::Paste);
-
-// #[cfg(target_os = "macos")]
-// {
-// edit_menu.add_native_item(MenuItem::Separator);
-// edit_menu.add_native_item(MenuItem::SelectAll);
-// }
-// menu_bar.add_submenu("Edit", true, edit_menu);
-// }
-
-// menu_bar
-// }
+ menu
+}
diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml
index 7d046fbc8..153b69ba6 100644
--- a/packages/interpreter/Cargo.toml
+++ b/packages/interpreter/Cargo.toml
@@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
wasm-bindgen = { workspace = true, optional = true }
js-sys = { version = "0.3.56", optional = true }
web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
-sledgehammer_bindgen = { git = "https://github.com/ealmloff/sledgehammer_bindgen", default-features = false, optional = true }
+sledgehammer_bindgen = { version = "0.3.0", default-features = false, optional = true }
sledgehammer_utils = { version = "0.2", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
| |