diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index c8ca8a92a..c40029b34 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -7,8 +7,9 @@ use crate::{ ipc::IpcMessage, query::QueryResult, shortcut::{GlobalHotKeyEvent, ShortcutRegistry}, - webview::WebviewHandler, + webview::WebviewInstance, }; +use crossbeam_channel::Receiver; use dioxus_core::{Component, ElementId, VirtualDom}; use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent, MountedData}; use futures_util::{pin_mut, FutureExt}; @@ -21,20 +22,28 @@ use tao::{ pub(crate) 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>>, + // iOS panics if we create a window before the event loop is started, so we toss them into a cell + pub(crate) props: Cell>, + pub(crate) cfg: Cell>, - 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, + // Stuff we need mutable access to pub(crate) control_flow: ControlFlow, pub(crate) is_visible_before_start: bool, + pub(crate) root: Component

, + pub(crate) webviews: HashMap, + pub(crate) window_behavior: WindowCloseBehaviour, + + pub(crate) shared: SharedContext, +} + +#[derive(Clone)] +pub struct SharedContext { + pub(crate) event_handlers: WindowEventHandlers, + pub(crate) pending_webviews: WebviewQueue, + pub(crate) shortcut_manager: ShortcutRegistry, + pub(crate) global_hotkey_channel: Receiver, + pub(crate) proxy: EventLoopProxy, + pub(crate) target: EventLoopWindowTarget, } impl App

{ @@ -46,14 +55,17 @@ impl App

{ 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, + props: Cell::new(Some(props)), + cfg: Cell::new(Some(cfg)), + shared: SharedContext { + event_handlers: WindowEventHandlers::default(), + pending_webviews: WebviewQueue::default(), + shortcut_manager: ShortcutRegistry::new(), + global_hotkey_channel: GlobalHotKeyEvent::receiver().clone(), + proxy: event_loop.create_proxy(), + target: event_loop.clone(), + }, }; #[cfg(all(feature = "hot-reload", debug_assertions))] @@ -69,18 +81,21 @@ impl App

{ ) { self.control_flow = ControlFlow::Wait; - self.event_handlers.apply_event(window_event, event_loop); + self.shared + .event_handlers + .apply_event(window_event, event_loop); _ = self + .shared .global_hotkey_channel .try_recv() - .map(|event| self.shortcut_manager.call_handlers(event)); + .map(|event| self.shared.shortcut_manager.call_handlers(event)); } #[cfg(all(feature = "hot-reload", debug_assertions))] pub fn connect_hotreload(&mut self) { - let proxy = self.proxy.clone(); dioxus_hot_reload::connect({ + let proxy = self.shared.proxy.clone(); move |template| { let _ = proxy.send_event(UserWindowEvent( EventData::HotReloadEvent(template), @@ -92,10 +107,13 @@ impl App

{ // pub fn handle_new_window(&mut self) { - for handler in self.queue.borrow_mut().drain(..) { + for handler in self.shared.pending_webviews.borrow_mut().drain(..) { let id = handler.desktop_context.window.id(); self.webviews.insert(id, handler); - _ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id)); + _ = self + .shared + .proxy + .send_event(UserWindowEvent(EventData::Poll, id)); } } @@ -136,28 +154,25 @@ impl App

{ } } - pub fn handle_start_cause_init(&mut self, target: &EventLoopWindowTarget) { + pub fn handle_start_cause_init(&mut self) { 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 = crate::webview::create_new_window( + let handler = WebviewInstance::new( cfg, - dom, - target, - &self.proxy, - &self.queue, - &self.event_handlers, - self.shortcut_manager.clone(), + VirtualDom::new_with_props(self.root, props), + self.shared.clone(), ); let id = handler.desktop_context.window.id(); self.webviews.insert(id, handler); - _ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id)); + _ = self + .shared + .proxy + .send_event(UserWindowEvent(EventData::Poll, id)); } pub fn handle_browser_open(&mut self, msg: IpcMessage) { diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index e1ed1f07a..3988bbbce 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,10 +1,10 @@ -use crate::ipc::IpcMessage; -use crate::query::QueryEngine; use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError}; use crate::AssetHandler; use crate::Config; +use crate::{app::SharedContext, ipc::IpcMessage}; use crate::{assets::AssetFuture, edits::WebviewQueue}; use crate::{assets::AssetHandlerRegistry, edits::EditQueue}; +use crate::{query::QueryEngine, webview::WebviewInstance}; use dioxus_core::Mutations; use dioxus_core::VirtualDom; use dioxus_interpreter_js::binary_protocol::Channel; @@ -52,21 +52,18 @@ pub struct DesktopService { /// The tao window itself pub window: Window, - /// The proxy to the event loop - pub proxy: EventLoopProxy, - /// The receiver for queries about the current window pub(super) query: QueryEngine, - pub(super) pending_windows: WebviewQueue, + pub(crate) shared: SharedContext, - pub(crate) event_loop: EventLoopWindowTarget, - - pub(crate) event_handlers: WindowEventHandlers, - - pub(crate) shortcut_manager: ShortcutRegistry, + // pub(crate) event_handlers: WindowEventHandlers, + // pub(super) pending_windows: WebviewQueue, + // pub(crate) shortcut_manager: ShortcutRegistry, + // pub(crate) event_loop: EventLoopWindowTarget, pub(crate) edit_queue: EditQueue, + pub(crate) templates: RefCell>, pub(crate) max_template_count: AtomicU16, @@ -90,28 +87,20 @@ impl DesktopService { pub(crate) fn new( window: Window, webview: WebView, - proxy: EventLoopProxy, - event_loop: EventLoopWindowTarget, - webviews: WebviewQueue, - event_handlers: WindowEventHandlers, - shortcut_manager: ShortcutRegistry, + shared: SharedContext, edit_queue: EditQueue, asset_handlers: AssetHandlerRegistry, ) -> Self { Self { window, webview, - proxy, - event_loop, - query: Default::default(), - pending_windows: webviews, - event_handlers, - shortcut_manager, + shared, edit_queue, + asset_handlers, + query: Default::default(), templates: Default::default(), max_template_count: Default::default(), channel: Default::default(), - asset_handlers, #[cfg(target_os = "ios")] views: Default::default(), } @@ -136,27 +125,21 @@ impl DesktopService { /// /// Be careful to not create a cycle of windows, or you might leak memory. pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> Weak { - let window = crate::webview::create_new_window( - cfg, - dom, - &self.event_loop, - &self.proxy, - &self.pending_windows, - &self.event_handlers, - self.shortcut_manager.clone(), - ); + let window = WebviewInstance::new(cfg, dom, self.shared.clone()); let cx = window.desktop_context.clone(); - self.proxy + self.shared + .proxy .send_event(UserWindowEvent(EventData::NewWindow, cx.id())) .unwrap(); - self.proxy + self.shared + .proxy .send_event(UserWindowEvent(EventData::Poll, cx.id())) .unwrap(); - self.pending_windows.borrow_mut().push(window); + self.shared.pending_webviews.borrow_mut().push(window); Rc::downgrade(&cx) } @@ -188,6 +171,7 @@ impl DesktopService { /// close window pub fn close(&self) { let _ = self + .shared .proxy .send_event(UserWindowEvent(EventData::CloseWindow, self.id())); } @@ -195,6 +179,7 @@ impl DesktopService { /// close window pub fn close_window(&self, id: WindowId) { let _ = self + .shared .proxy .send_event(UserWindowEvent(EventData::CloseWindow, id)); } @@ -237,12 +222,12 @@ impl DesktopService { &self, handler: impl FnMut(&Event, &EventLoopWindowTarget) + 'static, ) -> WryEventHandlerId { - self.event_handlers.add(self.id(), handler) + self.shared.event_handlers.add(self.window.id(), handler) } /// Remove a wry event handler created with [`DesktopContext::create_wry_event_handler`] pub fn remove_wry_event_handler(&self, id: WryEventHandlerId) { - self.event_handlers.remove(id) + self.shared.event_handlers.remove(id) } /// Create a global shortcut @@ -253,18 +238,19 @@ impl DesktopService { hotkey: HotKey, callback: impl FnMut() + 'static, ) -> Result { - self.shortcut_manager + self.shared + .shortcut_manager .add_shortcut(hotkey, Box::new(callback)) } /// Remove a global shortcut pub fn remove_shortcut(&self, id: ShortcutId) { - self.shortcut_manager.remove_shortcut(id) + self.shared.shortcut_manager.remove_shortcut(id) } /// Remove all global shortcuts pub fn remove_all_shortcuts(&self) { - self.shortcut_manager.remove_all() + self.shared.shortcut_manager.remove_all() } /// Provide a callback to handle asset loading yourself. diff --git a/packages/desktop/src/edits.rs b/packages/desktop/src/edits.rs index 4eeb48a9b..6fd4e6759 100644 --- a/packages/desktop/src/edits.rs +++ b/packages/desktop/src/edits.rs @@ -1,4 +1,4 @@ -use crate::webview::WebviewHandler; +use crate::webview::WebviewInstance; use dioxus_core::Mutations; use dioxus_core::{BorrowedAttributeValue, Template, TemplateAttribute, TemplateNode}; use dioxus_html::event_bubbles; @@ -14,7 +14,7 @@ use std::{ use wry::RequestAsyncResponder; -pub(crate) type WebviewQueue = Rc>>; +pub(crate) type WebviewQueue = Rc>>; /// This handles communication between the requests that the webview makes and the interpreter. The interpreter /// constantly makes long running requests to the webview to get any edits that should be made to the DOM almost like diff --git a/packages/desktop/src/hooks.rs b/packages/desktop/src/hooks.rs index 54d114abb..0f53f7ee6 100644 --- a/packages/desktop/src/hooks.rs +++ b/packages/desktop/src/hooks.rs @@ -23,7 +23,7 @@ pub fn use_wry_event_handler( let id = desktop.create_wry_event_handler(handler); WryEventHandler { - handlers: desktop.event_handlers.clone(), + handlers: desktop.shared.event_handlers.clone(), id, } }) diff --git a/packages/desktop/src/ipc.rs b/packages/desktop/src/ipc.rs index e0a5a4319..54c6d0eb0 100644 --- a/packages/desktop/src/ipc.rs +++ b/packages/desktop/src/ipc.rs @@ -1,13 +1,15 @@ -//! Convert a serialized event to an event trigger - use serde::{Deserialize, Serialize}; +/// A message struct that manages the communication between the webview and the eventloop code +/// +/// This needs to be serializable across the JS boundary, so the method names and structs are sensitive. #[derive(Deserialize, Serialize, Debug, Clone)] pub struct IpcMessage { method: String, params: serde_json::Value, } +/// A set of known messages that we need to respond to #[derive(Deserialize, Serialize, Debug, Clone)] pub enum IpcMethod<'a> { FileDialog, @@ -21,7 +23,7 @@ pub enum IpcMethod<'a> { impl IpcMessage { pub(crate) fn method(&self) -> IpcMethod { match self.method.as_str() { - // todo: this is a misspelling + // todo: this is a misspelling, needs to be fixed "file_diolog" => IpcMethod::FileDialog, "user_event" => IpcMethod::UserEvent, "query" => IpcMethod::Query, diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 8ee939494..8cdcec9b4 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -102,7 +102,7 @@ pub fn launch_with_props_blocking(root: Component

, props: P, cfg: app.tick(&window_event, event_loop); match window_event { - Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(event_loop), + Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(), Event::WindowEvent { event, window_id, .. } => match event { diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 6eac25b62..3ffee1a3a 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -1,26 +1,18 @@ -use std::{rc::Rc, task::Waker}; - use crate::{ - assets::AssetHandlerRegistry, desktop_context::UserWindowEvent, waker::tao_waker, Config, - DesktopContext, -}; -use crate::{ - desktop_context::{EventData, WindowEventHandlers}, - shortcut::ShortcutRegistry, -}; -use crate::{ - edits::{EditQueue, WebviewQueue}, + app::SharedContext, + assets::AssetHandlerRegistry, + desktop_context::{EventData, UserWindowEvent}, + edits::EditQueue, eval::DesktopEvalProvider, -}; -use crate::{ protocol::{self}, - DesktopService, + waker::tao_waker, + Config, DesktopContext, DesktopService, }; use dioxus_core::VirtualDom; -use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget}; +use std::{rc::Rc, task::Waker}; use wry::{WebContext, WebViewBuilder}; -pub struct WebviewHandler { +pub struct WebviewInstance { pub dom: VirtualDom, pub desktop_context: DesktopContext, pub waker: Waker, @@ -30,107 +22,100 @@ pub struct WebviewHandler { _web_context: WebContext, } -pub fn create_new_window( - mut cfg: Config, - dom: VirtualDom, - event_loop: &EventLoopWindowTarget, - proxy: &EventLoopProxy, - queue: &WebviewQueue, - event_handlers: &WindowEventHandlers, - shortcut_manager: ShortcutRegistry, -) -> WebviewHandler { - let window = cfg.window.clone().build(event_loop).unwrap(); +impl WebviewInstance { + pub fn new(mut cfg: Config, dom: VirtualDom, shared: SharedContext) -> WebviewInstance { + let window = cfg.window.clone().build(&shared.target).unwrap(); - // TODO: allow users to specify their own menubars, again :/ - if cfg.enable_default_menu_bar { - use crate::menubar::*; - build_menu_bar(build_default_menu_bar(), &window); - } + // TODO: allow users to specify their own menubars, again :/ + if cfg.enable_default_menu_bar { + use crate::menubar::*; + build_menu_bar(build_default_menu_bar(), &window); + } - let window_id = window.id(); - let file_handler = cfg.file_drop_handler.take(); - let custom_head = cfg.custom_head.clone(); - let index_file = cfg.custom_index.clone(); - let root_name = cfg.root_name.clone(); + let window_id = window.id(); + let file_handler = cfg.file_drop_handler.take(); + let custom_head = cfg.custom_head.clone(); + let index_file = cfg.custom_index.clone(); + let root_name = cfg.root_name.clone(); - // We assume that if the icon is None in cfg, then the user just didnt set it - if cfg.window.window.window_icon.is_none() { - window.set_window_icon(Some( - tao::window::Icon::from_rgba( - include_bytes!("./assets/default_icon.bin").to_vec(), - 460, - 460, - ) - .expect("image parse failed"), - )); - } + // We assume that if the icon is None in cfg, then the user just didnt set it + if cfg.window.window.window_icon.is_none() { + window.set_window_icon(Some( + tao::window::Icon::from_rgba( + include_bytes!("./assets/default_icon.bin").to_vec(), + 460, + 460, + ) + .expect("image parse failed"), + )); + } - let mut web_context = WebContext::new(cfg.data_dir.clone()); - let edit_queue = EditQueue::default(); - let headless = !cfg.window.window.visible; - let asset_handlers = AssetHandlerRegistry::new(); - let asset_handlers_ref = asset_handlers.clone(); + let mut web_context = WebContext::new(cfg.data_dir.clone()); + let edit_queue = EditQueue::default(); + let headless = !cfg.window.window.visible; + let asset_handlers = AssetHandlerRegistry::new(); + let asset_handlers_ref = asset_handlers.clone(); - let mut webview = WebViewBuilder::new(&window) - .with_transparent(cfg.window.window.transparent) - .with_url("dioxus://index.html/") - .unwrap() - .with_ipc_handler({ - let proxy = proxy.clone(); - 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)); + let mut webview = WebViewBuilder::new(&window) + .with_transparent(cfg.window.window.transparent) + .with_url("dioxus://index.html/") + .unwrap() + .with_ipc_handler({ + let proxy = shared.proxy.clone(); + 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)); + } } - } - }) - .with_asynchronous_custom_protocol(String::from("dioxus"), { - let edit_queue = edit_queue.clone(); - move |request, responder| { - let custom_head = custom_head.clone(); - let index_file = index_file.clone(); - let root_name = root_name.clone(); - let asset_handlers_ref = asset_handlers_ref.clone(); + }) + .with_asynchronous_custom_protocol(String::from("dioxus"), { let edit_queue = edit_queue.clone(); - tokio::spawn(async move { - protocol::desktop_handler( - request, - custom_head.clone(), - index_file.clone(), - &root_name, - &asset_handlers_ref, - &edit_queue, - headless, - responder, - ) - .await; - }); - } - }) - .with_file_drop_handler(move |event| { - file_handler - .as_ref() - .map(|handler| handler(window_id, event)) - .unwrap_or_default() - }) - .with_web_context(&mut web_context); + move |request, responder| { + let custom_head = custom_head.clone(); + let index_file = index_file.clone(); + let root_name = root_name.clone(); + let asset_handlers_ref = asset_handlers_ref.clone(); + let edit_queue = edit_queue.clone(); + tokio::spawn(async move { + protocol::desktop_handler( + request, + custom_head.clone(), + index_file.clone(), + &root_name, + &asset_handlers_ref, + &edit_queue, + headless, + responder, + ) + .await; + }); + } + }) + .with_file_drop_handler(move |event| { + file_handler + .as_ref() + .map(|handler| handler(window_id, event)) + .unwrap_or_default() + }) + .with_web_context(&mut web_context); - #[cfg(windows)] - { - // Windows has a platform specific settings to disable the browser shortcut keys - use wry::webview::WebViewBuilderExtWindows; - webview = webview.with_browser_accelerator_keys(false); - } + #[cfg(windows)] + { + // Windows has a platform specific settings to disable the browser shortcut keys + use wry::webview::WebViewBuilderExtWindows; + webview = webview.with_browser_accelerator_keys(false); + } - if let Some(color) = cfg.background_color { - webview = webview.with_background_color(color); - } + if let Some(color) = cfg.background_color { + webview = webview.with_background_color(color); + } - for (name, handler) in cfg.protocols.drain(..) { - webview = webview.with_custom_protocol(name, move |r| handler(r)) - } + for (name, handler) in cfg.protocols.drain(..) { + webview = webview.with_custom_protocol(name, move |r| handler(r)) + } - const INITIALIZATION_SCRIPT: &str = r#" + const INITIALIZATION_SCRIPT: &str = r#" if (document.addEventListener) { document.addEventListener('contextmenu', function(e) { e.preventDefault(); @@ -142,40 +127,37 @@ pub fn create_new_window( } "#; - if cfg.disable_context_menu { - // in release mode, we don't want to show the dev tool or reload menus - webview = webview.with_initialization_script(INITIALIZATION_SCRIPT) - } else { - // in debug, we are okay with the reload menu showing and dev tool - webview = webview.with_devtools(true); - } + if cfg.disable_context_menu { + // in release mode, we don't want to show the dev tool or reload menus + webview = webview.with_initialization_script(INITIALIZATION_SCRIPT) + } else { + // in debug, we are okay with the reload menu showing and dev tool + webview = webview.with_devtools(true); + } - let webview = webview.build().unwrap(); + let webview = webview.build().unwrap(); - 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 desktop_context = Rc::from(DesktopService::new( + window, + webview, + shared.clone(), + edit_queue, + asset_handlers, + )); - // Provide the desktop context to the virtualdom - dom.base_scope().provide_context(desktop_context.clone()); + // Provide the desktop context to the virtualdom + dom.base_scope().provide_context(desktop_context.clone()); - // Also set up its eval provider - dom.base_scope() - .provide_context(Rc::new(DesktopEvalProvider::new(desktop_context.clone()))); + // 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 - waker: tao_waker(proxy.clone(), desktop_context.window.id()), - desktop_context, - dom, - _web_context: web_context, + WebviewInstance { + // We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both + waker: tao_waker(shared.proxy.clone(), desktop_context.window.id()), + desktop_context, + dom, + _web_context: web_context, + } } }