From 060490892ac6d3ff7d6dc1362d1583b46e3d817e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 4 Jan 2024 18:19:28 -0800 Subject: [PATCH] Clean up desktop even more --- packages/desktop/src/app.rs | 142 +++++++++--------------- packages/desktop/src/assets.rs | 7 +- packages/desktop/src/desktop_context.rs | 14 +-- packages/desktop/src/eval.rs | 16 ++- packages/desktop/src/file_upload.rs | 125 +++++++++++---------- packages/desktop/src/webview.rs | 11 +- 6 files changed, 137 insertions(+), 178 deletions(-) diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 4e954d496..9a45e6c19 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -1,48 +1,25 @@ -pub use crate::assets::{AssetFuture, AssetHandler, AssetRequest, AssetResponse}; -pub use crate::cfg::{Config, WindowCloseBehaviour}; -pub use crate::desktop_context::DesktopContext; -pub use crate::desktop_context::{window, DesktopService, WryEventHandler, WryEventHandlerId}; -use crate::edits::{EditQueue, WebviewQueue}; -use crate::element::DesktopElement; -use crate::eval::init_eval; -use crate::events::{IpcMessage, IpcMethod}; -use crate::file_upload; -use crate::hooks::*; -use crate::query::QueryResult; -use crate::shortcut::GlobalHotKeyEvent; -use crate::shortcut::ShortcutRegistry; -pub use crate::shortcut::{ShortcutHandle, ShortcutId, ShortcutRegistryError}; use crate::{ + cfg::{Config, WindowCloseBehaviour}, desktop_context::{EventData, UserWindowEvent, WindowEventHandlers}, + edits::WebviewQueue, + element::DesktopElement, + events::IpcMessage, + file_upload::FileDialogRequest, + query::QueryResult, + shortcut::{GlobalHotKeyEvent, ShortcutRegistry}, webview::WebviewHandler, }; -use dioxus_core::*; -use dioxus_html::{event_bubbles, MountedData}; +use dioxus_core::{Component, ElementId, VirtualDom}; +use dioxus_html::MountedData; use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent}; -use dioxus_interpreter_js::binary_protocol::Channel; use futures_util::{pin_mut, FutureExt}; -use global_hotkey::{ - hotkey::{Code, HotKey, Modifiers}, - GlobalHotKeyManager, -}; -use rustc_hash::FxHashMap; +use std::cell::Cell; 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::EventLoopBuilder; 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 tao::{event::Event, event_loop::ControlFlow}; use wry::WebView; pub struct App

{ @@ -221,24 +198,14 @@ impl App

{ return; }; - view.dom - .base_scope() - .consume_context::() - .unwrap() - .query - .send(result); + view.desktop_context.query.send(result); } pub fn handle_user_event_msg(&mut self, msg: IpcMessage, id: WindowId) { - let params = msg.params(); + let parsed_params = serde_json::from_value(msg.params()) + .map_err(|err| tracing::error!("Error parsing user_event: {:?}", err)); - let evt = match serde_json::from_value::(params) { - Ok(value) => value, - Err(err) => { - tracing::error!("Error parsing user_event: {:?}", err); - return; - } - }; + let Ok(evt) = parsed_params else { return }; let HtmlEvent { element, @@ -250,20 +217,13 @@ impl App

{ 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() + let as_any = match data { + dioxus_html::EventData::Mounted => Rc::new(MountedData::new(DesktopElement::new( + element, + view.desktop_context.clone(), + view.desktop_context.query.clone(), + ))), + _ => data.into_any(), }; view.dom.handle_event(&name, as_any, element, bubbles); @@ -278,9 +238,7 @@ impl App

{ webview.dom.replace_template(template); } - let ids = self.webviews.keys().copied().collect::>(); - - for id in ids { + for id in self.webviews.keys().copied().collect::>() { self.poll_vdom(id); } } @@ -291,31 +249,32 @@ impl App

{ } pub fn handle_file_dialog_msg(&mut 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 Ok(file_dialog) = serde_json::from_value::(msg.params()) else { + return; + }; - let view = self.webviews.get_mut(&window).unwrap(); + let id = ElementId(file_dialog.target); + let event_name = &file_dialog.event; + let event_bubbles = file_dialog.bubbles; + let files = file_dialog.get_file_event(); - 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); - } + let data = Rc::new(FormData { + value: Default::default(), + values: Default::default(), + files: Some(Arc::new(NativeFileEngine::new(files))), + }); - view.desktop_context.send_edits(view.dom.render_immediate()); + 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); } + + view.desktop_context.send_edits(view.dom.render_immediate()); } /// Poll the virtualdom until it's pending @@ -324,10 +283,15 @@ impl App

{ /// /// 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 Some(view) = self.webviews.get_mut(&id) else { + return; + }; let mut cx = std::task::Context::from_waker(&view.waker); + // Continously poll the virtualdom until it's pending + // Wait for work will return Ready when it has edits to be sent to the webview + // It will return Pending when it needs to be polled again - nothing is ready loop { { let fut = view.dom.wait_for_work(); @@ -335,7 +299,7 @@ impl App

{ match fut.poll_unpin(&mut cx) { std::task::Poll::Ready(_) => {} - std::task::Poll::Pending => break, + std::task::Poll::Pending => return, } } diff --git a/packages/desktop/src/assets.rs b/packages/desktop/src/assets.rs index f80c7542a..7f86b6184 100644 --- a/packages/desktop/src/assets.rs +++ b/packages/desktop/src/assets.rs @@ -1,6 +1,4 @@ -use crate::edits::EditQueue; use crate::DesktopContext; -use dioxus_core::ScopeState; use slab::Slab; use std::{ borrow::Cow, @@ -15,10 +13,7 @@ use tokio::{ runtime::Handle, sync::{OnceCell, RwLock}, }; -use wry::{ - http::{status::StatusCode, Request, Response}, - Result, -}; +use wry::http::{Request, Response}; /// An arbitrary asset is an HTTP response containing a binary body. pub type AssetResponse = Response>; diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 240f8ec24..ef2f671b2 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -146,25 +146,19 @@ impl DesktopService { self.shortcut_manager.clone(), ); - let desktop_context = window - .dom - .base_scope() - .consume_context::>() - .unwrap(); - - let id = window.desktop_context.window.id(); + let cx = window.desktop_context.clone(); self.proxy - .send_event(UserWindowEvent(EventData::NewWindow, id)) + .send_event(UserWindowEvent(EventData::NewWindow, cx.id())) .unwrap(); self.proxy - .send_event(UserWindowEvent(EventData::Poll, id)) + .send_event(UserWindowEvent(EventData::Poll, cx.id())) .unwrap(); self.pending_windows.borrow_mut().push(window); - Rc::downgrade(&desktop_context) + Rc::downgrade(&cx) } /// trigger the drag-window event diff --git a/packages/desktop/src/eval.rs b/packages/desktop/src/eval.rs index e187be450..e9b0f036b 100644 --- a/packages/desktop/src/eval.rs +++ b/packages/desktop/src/eval.rs @@ -1,21 +1,19 @@ #![allow(clippy::await_holding_refcell_ref)] use async_trait::async_trait; -use dioxus_core::ScopeState; use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator}; use std::{cell::RefCell, rc::Rc}; use crate::{query::Query, DesktopContext}; -/// Provides the DesktopEvalProvider through [`cx.provide_context`]. -pub fn init_eval(cx: &ScopeState) { - let desktop_ctx = cx.consume_context::().unwrap(); - let provider: Rc = Rc::new(DesktopEvalProvider { desktop_ctx }); - cx.provide_context(provider); -} - /// Reprents the desktop-target's provider of evaluators. pub struct DesktopEvalProvider { - desktop_ctx: DesktopContext, + pub(crate) desktop_ctx: DesktopContext, +} + +impl DesktopEvalProvider { + pub fn new(desktop_ctx: DesktopContext) -> Self { + Self { desktop_ctx } + } } impl EvalProvider for DesktopEvalProvider { diff --git a/packages/desktop/src/file_upload.rs b/packages/desktop/src/file_upload.rs index 7b5f08fc3..98dbec437 100644 --- a/packages/desktop/src/file_upload.rs +++ b/packages/desktop/src/file_upload.rs @@ -14,74 +14,77 @@ pub(crate) struct FileDialogRequest { pub bubbles: bool, } -#[cfg(not(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -)))] -pub(crate) fn get_file_event(_request: &FileDialogRequest) -> Vec { - vec![] -} +#[allow(unused)] +impl FileDialogRequest { + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] + pub(crate) fn get_file_event(&self) -> Vec { + vec![] + } -#[cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] -pub(crate) fn get_file_event(request: &FileDialogRequest) -> Vec { - fn get_file_event_for_folder( - request: &FileDialogRequest, - dialog: rfd::FileDialog, - ) -> Vec { - if request.multiple { - dialog.pick_folders().into_iter().flatten().collect() - } else { - dialog.pick_folder().into_iter().collect() + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub(crate) fn get_file_event(&self) -> Vec { + fn get_file_event_for_folder( + request: &FileDialogRequest, + dialog: rfd::FileDialog, + ) -> Vec { + if request.multiple { + dialog.pick_folders().into_iter().flatten().collect() + } else { + dialog.pick_folder().into_iter().collect() + } } - } - fn get_file_event_for_file( - request: &FileDialogRequest, - mut dialog: rfd::FileDialog, - ) -> Vec { - let filters: Vec<_> = request - .accept - .as_deref() - .unwrap_or_default() - .split(',') - .filter_map(|s| Filters::from_str(s).ok()) - .collect(); + fn get_file_event_for_file( + request: &FileDialogRequest, + mut dialog: rfd::FileDialog, + ) -> Vec { + let filters: Vec<_> = request + .accept + .as_deref() + .unwrap_or_default() + .split(',') + .filter_map(|s| Filters::from_str(s).ok()) + .collect(); - let file_extensions: Vec<_> = filters - .iter() - .flat_map(|f| f.as_extensions().into_iter()) - .collect(); + let file_extensions: Vec<_> = filters + .iter() + .flat_map(|f| f.as_extensions().into_iter()) + .collect(); - dialog = dialog.add_filter("name", file_extensions.as_slice()); + dialog = dialog.add_filter("name", file_extensions.as_slice()); - let files: Vec<_> = if request.multiple { - dialog.pick_files().into_iter().flatten().collect() + let files: Vec<_> = if request.multiple { + dialog.pick_files().into_iter().flatten().collect() + } else { + dialog.pick_file().into_iter().collect() + }; + + files + } + + let dialog = rfd::FileDialog::new(); + + if self.directory { + get_file_event_for_folder(self, dialog) } else { - dialog.pick_file().into_iter().collect() - }; - - files - } - - let dialog = rfd::FileDialog::new(); - - if request.directory { - get_file_event_for_folder(request, dialog) - } else { - get_file_event_for_file(request, dialog) + get_file_event_for_file(self, dialog) + } } } diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index cd83362b5..6eac25b62 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -1,15 +1,17 @@ use std::{rc::Rc, task::Waker}; -use crate::edits::{EditQueue, WebviewQueue}; use crate::{ assets::AssetHandlerRegistry, desktop_context::UserWindowEvent, waker::tao_waker, Config, DesktopContext, }; use crate::{ desktop_context::{EventData, WindowEventHandlers}, - eval::init_eval, shortcut::ShortcutRegistry, }; +use crate::{ + edits::{EditQueue, WebviewQueue}, + eval::DesktopEvalProvider, +}; use crate::{ protocol::{self}, DesktopService, @@ -162,9 +164,12 @@ pub fn create_new_window( asset_handlers, )); + // Provide the desktop context to the virtualdom dom.base_scope().provide_context(desktop_context.clone()); - init_eval(dom.base_scope()); + // Also set up its eval provider + dom.base_scope() + .provide_context(Rc::new(DesktopEvalProvider::new(desktop_context.clone()))); WebviewHandler { // We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both