desktop_context.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. use std::cell::RefCell;
  2. use std::rc::Rc;
  3. use std::rc::Weak;
  4. use crate::create_new_window;
  5. use crate::eval::EvalResult;
  6. use crate::events::IpcMessage;
  7. use crate::Config;
  8. use crate::WebviewHandler;
  9. use dioxus_core::ScopeState;
  10. use dioxus_core::VirtualDom;
  11. use serde_json::Value;
  12. use slab::Slab;
  13. use wry::application::event::Event;
  14. use wry::application::event_loop::EventLoopProxy;
  15. use wry::application::event_loop::EventLoopWindowTarget;
  16. #[cfg(target_os = "ios")]
  17. use wry::application::platform::ios::WindowExtIOS;
  18. use wry::application::window::Fullscreen as WryFullscreen;
  19. use wry::application::window::Window;
  20. use wry::application::window::WindowId;
  21. use wry::webview::WebView;
  22. pub type ProxyType = EventLoopProxy<UserWindowEvent>;
  23. /// Get an imperative handle to the current window
  24. pub fn use_window(cx: &ScopeState) -> &DesktopContext {
  25. cx.use_hook(|| cx.consume_context::<DesktopContext>())
  26. .as_ref()
  27. .unwrap()
  28. }
  29. pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
  30. /// An imperative interface to the current window.
  31. ///
  32. /// To get a handle to the current window, use the [`use_window`] hook.
  33. ///
  34. ///
  35. /// # Example
  36. ///
  37. /// you can use `cx.consume_context::<DesktopContext>` to get this context
  38. ///
  39. /// ```rust, ignore
  40. /// let desktop = cx.consume_context::<DesktopContext>().unwrap();
  41. /// ```
  42. #[derive(Clone)]
  43. pub struct DesktopContext {
  44. /// The wry/tao proxy to the current window
  45. pub webview: Rc<WebView>,
  46. /// The proxy to the event loop
  47. pub proxy: ProxyType,
  48. /// The receiver for eval results since eval is async
  49. pub(super) eval: tokio::sync::broadcast::Sender<Value>,
  50. pub(super) pending_windows: WebviewQueue,
  51. pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
  52. pub(crate) event_handlers: WindowEventHandlers,
  53. #[cfg(target_os = "ios")]
  54. pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
  55. }
  56. /// A smart pointer to the current window.
  57. impl std::ops::Deref for DesktopContext {
  58. type Target = Window;
  59. fn deref(&self) -> &Self::Target {
  60. self.webview.window()
  61. }
  62. }
  63. impl DesktopContext {
  64. pub(crate) fn new(
  65. webview: Rc<WebView>,
  66. proxy: ProxyType,
  67. event_loop: EventLoopWindowTarget<UserWindowEvent>,
  68. webviews: WebviewQueue,
  69. event_handlers: WindowEventHandlers,
  70. ) -> Self {
  71. Self {
  72. webview,
  73. proxy,
  74. event_loop,
  75. eval: tokio::sync::broadcast::channel(8).0,
  76. pending_windows: webviews,
  77. event_handlers,
  78. #[cfg(target_os = "ios")]
  79. views: Default::default(),
  80. }
  81. }
  82. /// Create a new window using the props and window builder
  83. ///
  84. /// Returns the webview handle for the new window.
  85. ///
  86. /// You can use this to control other windows from the current window.
  87. ///
  88. /// Be careful to not create a cycle of windows, or you might leak memory.
  89. pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> Weak<WebView> {
  90. let window = create_new_window(
  91. cfg,
  92. &self.event_loop,
  93. &self.proxy,
  94. dom,
  95. &self.pending_windows,
  96. &self.event_handlers,
  97. );
  98. let id = window.webview.window().id();
  99. self.proxy
  100. .send_event(UserWindowEvent(EventData::NewWindow, id))
  101. .unwrap();
  102. self.proxy
  103. .send_event(UserWindowEvent(EventData::Poll, id))
  104. .unwrap();
  105. let webview = window.webview.clone();
  106. self.pending_windows.borrow_mut().push(window);
  107. Rc::downgrade(&webview)
  108. }
  109. /// trigger the drag-window event
  110. ///
  111. /// Moves the window with the left mouse button until the button is released.
  112. ///
  113. /// you need use it in `onmousedown` event:
  114. /// ```rust, ignore
  115. /// onmousedown: move |_| { desktop.drag_window(); }
  116. /// ```
  117. pub fn drag(&self) {
  118. let window = self.webview.window();
  119. // if the drag_window has any errors, we don't do anything
  120. if window.fullscreen().is_none() {
  121. window.drag_window().unwrap();
  122. }
  123. }
  124. /// Toggle whether the window is maximized or not
  125. pub fn toggle_maximized(&self) {
  126. let window = self.webview.window();
  127. window.set_maximized(!window.is_maximized())
  128. }
  129. /// close window
  130. pub fn close(&self) {
  131. let _ = self
  132. .proxy
  133. .send_event(UserWindowEvent(EventData::CloseWindow, self.id()));
  134. }
  135. /// close window
  136. pub fn close_window(&self, id: WindowId) {
  137. let _ = self
  138. .proxy
  139. .send_event(UserWindowEvent(EventData::CloseWindow, id));
  140. }
  141. /// change window to fullscreen
  142. pub fn set_fullscreen(&self, fullscreen: bool) {
  143. if let Some(handle) = self.webview.window().current_monitor() {
  144. self.webview
  145. .window()
  146. .set_fullscreen(fullscreen.then_some(WryFullscreen::Borderless(Some(handle))));
  147. }
  148. }
  149. /// launch print modal
  150. pub fn print(&self) {
  151. if let Err(e) = self.webview.print() {
  152. log::warn!("Open print modal failed: {e}");
  153. }
  154. }
  155. /// Set the zoom level of the webview
  156. pub fn set_zoom_level(&self, level: f64) {
  157. self.webview.zoom(level);
  158. }
  159. /// opens DevTool window
  160. pub fn devtool(&self) {
  161. #[cfg(debug_assertions)]
  162. self.webview.open_devtools();
  163. #[cfg(not(debug_assertions))]
  164. log::warn!("Devtools are disabled in release builds");
  165. }
  166. /// Evaluate a javascript expression
  167. pub fn eval(&self, code: &str) -> EvalResult {
  168. // Embed the return of the eval in a function so we can send it back to the main thread
  169. let script = format!(
  170. r#"
  171. window.ipc.postMessage(
  172. JSON.stringify({{
  173. "method":"eval_result",
  174. "params": (
  175. function(){{
  176. {}
  177. }}
  178. )()
  179. }})
  180. );
  181. "#,
  182. code
  183. );
  184. if let Err(e) = self.webview.evaluate_script(&script) {
  185. // send an error to the eval receiver
  186. log::warn!("Eval script error: {e}");
  187. }
  188. EvalResult::new(self.eval.clone())
  189. }
  190. /// Create a wry event handler that listens for wry events.
  191. /// This event handler is scoped to the currently active window and will only recieve events that are either global or related to the current window.
  192. ///
  193. /// The id this function returns can be used to remove the event handler with [`DesktopContext::remove_wry_event_handler`]
  194. pub fn create_wry_event_handler(
  195. &self,
  196. handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
  197. ) -> WryEventHandlerId {
  198. self.event_handlers.add(self.id(), handler)
  199. }
  200. /// Remove a wry event handler created with [`DesktopContext::create_wry_event_handler`]
  201. pub fn remove_wry_event_handler(&self, id: WryEventHandlerId) {
  202. self.event_handlers.remove(id)
  203. }
  204. /// Push an objc view to the window
  205. #[cfg(target_os = "ios")]
  206. pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
  207. let window = self.webview.window();
  208. unsafe {
  209. use objc::runtime::Object;
  210. use objc::*;
  211. assert!(is_main_thread());
  212. let ui_view = window.ui_view() as *mut Object;
  213. let ui_view_frame: *mut Object = msg_send![ui_view, frame];
  214. let _: () = msg_send![view, setFrame: ui_view_frame];
  215. let _: () = msg_send![view, setAutoresizingMask: 31];
  216. let ui_view_controller = window.ui_view_controller() as *mut Object;
  217. let _: () = msg_send![ui_view_controller, setView: view];
  218. self.views.borrow_mut().push(ui_view);
  219. }
  220. }
  221. /// Pop an objc view from the window
  222. #[cfg(target_os = "ios")]
  223. pub fn pop_view(&self) {
  224. let window = self.webview.window();
  225. unsafe {
  226. use objc::runtime::Object;
  227. use objc::*;
  228. assert!(is_main_thread());
  229. if let Some(view) = self.views.borrow_mut().pop() {
  230. let ui_view_controller = window.ui_view_controller() as *mut Object;
  231. let _: () = msg_send![ui_view_controller, setView: view];
  232. }
  233. }
  234. }
  235. }
  236. #[derive(Debug, Clone)]
  237. pub struct UserWindowEvent(pub EventData, pub WindowId);
  238. #[derive(Debug, Clone)]
  239. pub enum EventData {
  240. Poll,
  241. Ipc(IpcMessage),
  242. NewWindow,
  243. CloseWindow,
  244. }
  245. #[cfg(target_os = "ios")]
  246. fn is_main_thread() -> bool {
  247. use objc::runtime::{Class, BOOL, NO};
  248. use objc::*;
  249. let cls = Class::get("NSThread").unwrap();
  250. let result: BOOL = unsafe { msg_send![cls, isMainThread] };
  251. result != NO
  252. }
  253. /// The unique identifier of a window event handler. This can be used to later remove the handler.
  254. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  255. pub struct WryEventHandlerId(usize);
  256. #[derive(Clone, Default)]
  257. pub(crate) struct WindowEventHandlers {
  258. handlers: Rc<RefCell<Slab<WryWindowEventHandlerInner>>>,
  259. }
  260. impl WindowEventHandlers {
  261. pub(crate) fn add(
  262. &self,
  263. window_id: WindowId,
  264. handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
  265. ) -> WryEventHandlerId {
  266. WryEventHandlerId(
  267. self.handlers
  268. .borrow_mut()
  269. .insert(WryWindowEventHandlerInner {
  270. window_id,
  271. handler: Box::new(handler),
  272. }),
  273. )
  274. }
  275. pub(crate) fn remove(&self, id: WryEventHandlerId) {
  276. self.handlers.borrow_mut().try_remove(id.0);
  277. }
  278. pub(crate) fn apply_event(
  279. &self,
  280. event: &Event<UserWindowEvent>,
  281. target: &EventLoopWindowTarget<UserWindowEvent>,
  282. ) {
  283. for (_, handler) in self.handlers.borrow_mut().iter_mut() {
  284. handler.apply_event(event, target);
  285. }
  286. }
  287. }
  288. struct WryWindowEventHandlerInner {
  289. window_id: WindowId,
  290. handler: WryEventHandlerCallback,
  291. }
  292. type WryEventHandlerCallback =
  293. Box<dyn FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static>;
  294. impl WryWindowEventHandlerInner {
  295. fn apply_event(
  296. &mut self,
  297. event: &Event<UserWindowEvent>,
  298. target: &EventLoopWindowTarget<UserWindowEvent>,
  299. ) {
  300. // if this event does not apply to the window this listener cares about, return
  301. match event {
  302. Event::WindowEvent { window_id, .. }
  303. | Event::MenuEvent {
  304. window_id: Some(window_id),
  305. ..
  306. } => {
  307. if *window_id != self.window_id {
  308. return;
  309. }
  310. }
  311. _ => (),
  312. }
  313. (self.handler)(event, target)
  314. }
  315. }
  316. /// Get a closure that executes any JavaScript in the WebView context.
  317. pub fn use_wry_event_handler(
  318. cx: &ScopeState,
  319. handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
  320. ) -> &WryEventHandler {
  321. let desktop = use_window(cx);
  322. cx.use_hook(move || {
  323. let desktop = desktop.clone();
  324. let id = desktop.create_wry_event_handler(handler);
  325. WryEventHandler {
  326. handlers: desktop.event_handlers,
  327. id,
  328. }
  329. })
  330. }
  331. /// A wry event handler that is scoped to the current component and window. The event handler will only receive events for the window it was created for and global events.
  332. ///
  333. /// This will automatically be removed when the component is unmounted.
  334. pub struct WryEventHandler {
  335. handlers: WindowEventHandlers,
  336. /// The unique identifier of the event handler.
  337. pub id: WryEventHandlerId,
  338. }
  339. impl WryEventHandler {
  340. /// Remove the event handler.
  341. pub fn remove(&self) {
  342. self.handlers.remove(self.id);
  343. }
  344. }
  345. impl Drop for WryEventHandler {
  346. fn drop(&mut self) {
  347. self.handlers.remove(self.id);
  348. }
  349. }