瀏覽代碼

Refactor desktop into App type

Jonathan Kelley 1 年之前
父節點
當前提交
9a6d745339

+ 1 - 1
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",

+ 3 - 3
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(),

+ 7 - 2
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"

+ 569 - 0
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<P> {
+    // 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<Cell<Option<P>>>,
+    pub(crate) cfg: Rc<Cell<Option<Config>>>,
+    pub(crate) root: Component<P>,
+    pub(crate) webviews: HashMap<WindowId, WebviewHandler>,
+    pub(crate) event_handlers: WindowEventHandlers,
+    pub(crate) queue: WebviewQueue,
+    pub(crate) shortcut_manager: ShortcutRegistry,
+    pub(crate) global_hotkey_channel: crossbeam_channel::Receiver<GlobalHotKeyEvent>,
+    pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
+    pub(crate) window_behavior: WindowCloseBehaviour,
+    pub(crate) control_flow: ControlFlow,
+    pub(crate) is_visible_before_start: bool,
+}
+
+impl<P> App<P> {
+    pub fn new(cfg: Config, props: P, root: Component<P>) -> (EventLoop<UserWindowEvent>, Self) {
+        let event_loop = EventLoopBuilder::<UserWindowEvent>::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<UserWindowEvent>,
+    ) {
+        // 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<UserWindowEvent>) {
+        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::<QueryResult>(msg.params()) else {
+            return;
+        };
+
+        let Some(view) = self.webviews.get(&id) else {
+            return;
+        };
+
+        view.dom
+            .base_scope()
+            .consume_context::<DesktopContext>()
+            .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::<HtmlEvent>(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::<DesktopContext>()
+                .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::<file_upload::FileDialogRequest>(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<UserWindowEvent>,
+    proxy: &EventLoopProxy<UserWindowEvent>,
+    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<String, u16>,
+    max_template_count: &AtomicU16,
+) -> Option<Vec<u8>> {
+    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<String, u16>,
+    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::<Object>();
+            let _: () = msg_send![app, hide: nil];
+        });
+    }
+}

+ 0 - 0
packages/desktop/src/asset_handler.rs


+ 4 - 5
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<dyn Fn(&Window, FileDropEvent) -> bool>;
+type DropHandler = Box<dyn Fn(FileDropEvent) -> 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

+ 35 - 41
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},
+};
+
+use wry::{RequestAsyncResponder, WebView};
 
-pub type ProxyType = EventLoopProxy<UserWindowEvent>;
+#[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::<DesktopContext>())
         .as_ref()
@@ -56,7 +46,7 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
 #[derive(Default, Clone)]
 pub(crate) struct EditQueue {
     queue: Arc<Mutex<Vec<Vec<u8>>>>,
-    responder: Arc<Mutex<Option<wry::webview::RequestAsyncResponder>>>,
+    responder: Arc<Mutex<Option<RequestAsyncResponder>>>,
 }
 
 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<RefCell<Vec<WebviewHandler>>>;
 /// ```
 pub struct DesktopService {
     /// The wry/tao proxy to the current window
-    pub webview: Rc<WebView>,
+    pub webview: WebView,
+
+    pub window: Window,
 
     /// The proxy to the event loop
-    pub proxy: ProxyType,
+    pub proxy: EventLoopProxy<UserWindowEvent>,
 
     /// 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<UserWindowEvent>,
         event_loop: EventLoopWindowTarget<UserWindowEvent>,
         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::<Rc<DesktopService>>()
             .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<objc::runtime::Object>) {
-        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,
 

+ 20 - 2
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 {

+ 1 - 0
packages/desktop/src/hooks.rs

@@ -0,0 +1 @@
+

+ 34 - 516
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<P: 'static>(root: Component<P>, props: P, cfg: Config) {
-    let event_loop = EventLoopBuilder::<UserWindowEvent>::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();
-
-    // 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::<WindowId, WebviewHandler>::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();
+        .unwrap()
+        .enter();
 
-    // 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::<HtmlEvent>(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::<DesktopContext>()
-                            .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::<QueryResult>(params) {
-                        let view = webviews.get(&event.1).unwrap();
-                        let query = view
-                            .dom
-                            .base_scope()
-                            .consume_context::<DesktopContext>()
-                            .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::<file_upload::FileDialogRequest>(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);
-                    }
-                }
-
-                _ => {}
             },
             _ => {}
         }
-    })
-}
-
-fn create_new_window(
-    mut cfg: Config,
-    event_loop: &EventLoopWindowTarget<UserWindowEvent>,
-    proxy: &EventLoopProxy<UserWindowEvent>,
-    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<String, u16>,
-    max_template_count: &AtomicU16,
-) -> Option<Vec<u8>> {
-    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<String, u16>,
-    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::<Object>();
-            let _: () = msg_send![app, hide: nil];
-        });
-    }
+        // Update the control flow
+        *control_flow = app.control_flow;
+    })
 }

+ 4 - 6
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),
         }

+ 1 - 1
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};
 

+ 2 - 2
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.
 ///

+ 50 - 69
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<UserWindowEvent>,
     proxy: EventLoopProxy<UserWindowEvent>,
-) -> (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();
-
-//     // 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);
-//     }
-
-//     window_menu.add_native_item(MenuItem::Hide);
-
-//     #[cfg(target_os = "macos")]
-//     {
-//         window_menu.add_native_item(MenuItem::HideOthers);
-//         window_menu.add_native_item(MenuItem::ShowAll);
-//     }
-
-//     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
-// }
+/// 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 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),
+    ]);
+
+    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),
+    ]);
+
+    menu.append_items(&[&window_menu, &edit_menu]);
+
+    menu
+}

+ 1 - 1
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 }