desktop_context.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. use std::rc::Rc;
  2. use crate::controller::DesktopController;
  3. use dioxus_core::ScopeState;
  4. use serde::de::Error;
  5. use serde_json::Value;
  6. use std::future::Future;
  7. use std::future::IntoFuture;
  8. use std::pin::Pin;
  9. use wry::application::dpi::LogicalSize;
  10. use wry::application::event_loop::ControlFlow;
  11. use wry::application::event_loop::EventLoopProxy;
  12. #[cfg(target_os = "ios")]
  13. use wry::application::platform::ios::WindowExtIOS;
  14. use wry::application::window::Fullscreen as WryFullscreen;
  15. use UserWindowEvent::*;
  16. pub type ProxyType = EventLoopProxy<UserWindowEvent>;
  17. /// Get an imperative handle to the current window
  18. pub fn use_window(cx: &ScopeState) -> &DesktopContext {
  19. cx.use_hook(|| cx.consume_context::<DesktopContext>())
  20. .as_ref()
  21. .unwrap()
  22. }
  23. /// An imperative interface to the current window.
  24. ///
  25. /// To get a handle to the current window, use the [`use_window`] hook.
  26. ///
  27. ///
  28. /// # Example
  29. ///
  30. /// you can use `cx.consume_context::<DesktopContext>` to get this context
  31. ///
  32. /// ```rust, ignore
  33. /// let desktop = cx.consume_context::<DesktopContext>().unwrap();
  34. /// ```
  35. #[derive(Clone)]
  36. pub struct DesktopContext {
  37. /// The wry/tao proxy to the current window
  38. pub proxy: ProxyType,
  39. pub(super) eval_reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Value>>>,
  40. }
  41. impl DesktopContext {
  42. pub(crate) fn new(
  43. proxy: ProxyType,
  44. eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>,
  45. ) -> Self {
  46. Self {
  47. proxy,
  48. eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)),
  49. }
  50. }
  51. /// trigger the drag-window event
  52. ///
  53. /// Moves the window with the left mouse button until the button is released.
  54. ///
  55. /// you need use it in `onmousedown` event:
  56. /// ```rust, ignore
  57. /// onmousedown: move |_| { desktop.drag_window(); }
  58. /// ```
  59. pub fn drag(&self) {
  60. let _ = self.proxy.send_event(DragWindow);
  61. }
  62. /// set window minimize state
  63. pub fn set_minimized(&self, minimized: bool) {
  64. let _ = self.proxy.send_event(Minimize(minimized));
  65. }
  66. /// set window maximize state
  67. pub fn set_maximized(&self, maximized: bool) {
  68. let _ = self.proxy.send_event(Maximize(maximized));
  69. }
  70. /// toggle window maximize state
  71. pub fn toggle_maximized(&self) {
  72. let _ = self.proxy.send_event(MaximizeToggle);
  73. }
  74. /// set window visible or not
  75. pub fn set_visible(&self, visible: bool) {
  76. let _ = self.proxy.send_event(Visible(visible));
  77. }
  78. /// close window
  79. pub fn close(&self) {
  80. let _ = self.proxy.send_event(CloseWindow);
  81. }
  82. /// set window to focus
  83. pub fn focus(&self) {
  84. let _ = self.proxy.send_event(FocusWindow);
  85. }
  86. /// change window to fullscreen
  87. pub fn set_fullscreen(&self, fullscreen: bool) {
  88. let _ = self.proxy.send_event(Fullscreen(fullscreen));
  89. }
  90. /// set resizable state
  91. pub fn set_resizable(&self, resizable: bool) {
  92. let _ = self.proxy.send_event(Resizable(resizable));
  93. }
  94. /// set the window always on top
  95. pub fn set_always_on_top(&self, top: bool) {
  96. let _ = self.proxy.send_event(AlwaysOnTop(top));
  97. }
  98. /// set cursor visible or not
  99. pub fn set_cursor_visible(&self, visible: bool) {
  100. let _ = self.proxy.send_event(CursorVisible(visible));
  101. }
  102. /// set cursor grab
  103. pub fn set_cursor_grab(&self, grab: bool) {
  104. let _ = self.proxy.send_event(CursorGrab(grab));
  105. }
  106. /// set window title
  107. pub fn set_title(&self, title: &str) {
  108. let _ = self.proxy.send_event(SetTitle(String::from(title)));
  109. }
  110. /// change window to borderless
  111. pub fn set_decorations(&self, decoration: bool) {
  112. let _ = self.proxy.send_event(SetDecorations(decoration));
  113. }
  114. /// set window zoom level
  115. pub fn set_zoom_level(&self, scale_factor: f64) {
  116. let _ = self.proxy.send_event(SetZoomLevel(scale_factor));
  117. }
  118. /// modifies the inner size of the window
  119. pub fn set_inner_size(&self, logical_size: LogicalSize<f64>) {
  120. let _ = self.proxy.send_event(SetInnerSize(logical_size));
  121. }
  122. /// launch print modal
  123. pub fn print(&self) {
  124. let _ = self.proxy.send_event(Print);
  125. }
  126. /// opens DevTool window
  127. pub fn devtool(&self) {
  128. let _ = self.proxy.send_event(DevTool);
  129. }
  130. /// run (evaluate) a script in the WebView context
  131. pub fn eval(&self, script: impl std::string::ToString) {
  132. let _ = self.proxy.send_event(Eval(script.to_string()));
  133. }
  134. /// Push view
  135. #[cfg(target_os = "ios")]
  136. pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
  137. let _ = self.proxy.send_event(PushView(view));
  138. }
  139. /// Push view
  140. #[cfg(target_os = "ios")]
  141. pub fn pop_view(&self) {
  142. let _ = self.proxy.send_event(PopView);
  143. }
  144. }
  145. #[derive(Debug)]
  146. pub enum UserWindowEvent {
  147. EditsReady,
  148. Initialize,
  149. CloseWindow,
  150. DragWindow,
  151. FocusWindow,
  152. /// Set a new Dioxus template for hot-reloading
  153. ///
  154. /// Is a no-op in release builds. Must fit the right format for templates
  155. SetTemplate(String),
  156. Visible(bool),
  157. Minimize(bool),
  158. Maximize(bool),
  159. MaximizeToggle,
  160. Resizable(bool),
  161. AlwaysOnTop(bool),
  162. Fullscreen(bool),
  163. CursorVisible(bool),
  164. CursorGrab(bool),
  165. SetTitle(String),
  166. SetDecorations(bool),
  167. SetZoomLevel(f64),
  168. SetInnerSize(LogicalSize<f64>),
  169. Print,
  170. DevTool,
  171. Eval(String),
  172. #[cfg(target_os = "ios")]
  173. PushView(objc_id::ShareId<objc::runtime::Object>),
  174. #[cfg(target_os = "ios")]
  175. PopView,
  176. }
  177. impl DesktopController {
  178. pub(super) fn handle_event(
  179. &mut self,
  180. user_event: UserWindowEvent,
  181. control_flow: &mut ControlFlow,
  182. ) {
  183. // currently dioxus-desktop supports a single window only,
  184. // so we can grab the only webview from the map;
  185. // on wayland it is possible that a user event is emitted
  186. // before the webview is initialized. ignore the event.
  187. let webview = if let Some(webview) = self.webviews.values().next() {
  188. webview
  189. } else {
  190. return;
  191. };
  192. let window = webview.window();
  193. match user_event {
  194. Initialize | EditsReady => self.try_load_ready_webviews(),
  195. SetTemplate(template) => self.set_template(template),
  196. CloseWindow => *control_flow = ControlFlow::Exit,
  197. DragWindow => {
  198. // if the drag_window has any errors, we don't do anything
  199. window.fullscreen().is_none().then(|| window.drag_window());
  200. }
  201. Visible(state) => window.set_visible(state),
  202. Minimize(state) => window.set_minimized(state),
  203. Maximize(state) => window.set_maximized(state),
  204. MaximizeToggle => window.set_maximized(!window.is_maximized()),
  205. Fullscreen(state) => {
  206. if let Some(handle) = window.current_monitor() {
  207. window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
  208. }
  209. }
  210. FocusWindow => window.set_focus(),
  211. Resizable(state) => window.set_resizable(state),
  212. AlwaysOnTop(state) => window.set_always_on_top(state),
  213. Eval(code) => {
  214. let script = format!(
  215. r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{
  216. {}
  217. }})()}}));"#,
  218. code
  219. );
  220. if let Err(e) = webview.evaluate_script(&script) {
  221. // we can't panic this error.
  222. log::warn!("Eval script error: {e}");
  223. }
  224. }
  225. CursorVisible(state) => window.set_cursor_visible(state),
  226. CursorGrab(state) => {
  227. let _ = window.set_cursor_grab(state);
  228. }
  229. SetTitle(content) => window.set_title(&content),
  230. SetDecorations(state) => window.set_decorations(state),
  231. SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
  232. SetInnerSize(logical_size) => window.set_inner_size(logical_size),
  233. Print => {
  234. if let Err(e) = webview.print() {
  235. // we can't panic this error.
  236. log::warn!("Open print modal failed: {e}");
  237. }
  238. }
  239. DevTool => {
  240. #[cfg(debug_assertions)]
  241. webview.open_devtools();
  242. #[cfg(not(debug_assertions))]
  243. log::warn!("Devtools are disabled in release builds");
  244. }
  245. #[cfg(target_os = "ios")]
  246. PushView(view) => unsafe {
  247. use objc::runtime::Object;
  248. use objc::*;
  249. assert!(is_main_thread());
  250. let ui_view = window.ui_view() as *mut Object;
  251. let ui_view_frame: *mut Object = msg_send![ui_view, frame];
  252. let _: () = msg_send![view, setFrame: ui_view_frame];
  253. let _: () = msg_send![view, setAutoresizingMask: 31];
  254. let ui_view_controller = window.ui_view_controller() as *mut Object;
  255. let _: () = msg_send![ui_view_controller, setView: view];
  256. self.views.push(ui_view);
  257. },
  258. #[cfg(target_os = "ios")]
  259. PopView => unsafe {
  260. use objc::runtime::Object;
  261. use objc::*;
  262. assert!(is_main_thread());
  263. if let Some(view) = self.views.pop() {
  264. let ui_view_controller = window.ui_view_controller() as *mut Object;
  265. let _: () = msg_send![ui_view_controller, setView: view];
  266. }
  267. },
  268. }
  269. }
  270. }
  271. /// Get a closure that executes any JavaScript in the WebView context.
  272. pub fn use_eval<S: std::string::ToString>(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult {
  273. let desktop = use_window(cx).clone();
  274. cx.use_hook(|| {
  275. move |script| {
  276. desktop.eval(script);
  277. let recv = desktop.eval_reciever.clone();
  278. EvalResult { reciever: recv }
  279. }
  280. })
  281. }
  282. /// A future that resolves to the result of a JavaScript evaluation.
  283. pub struct EvalResult {
  284. reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<serde_json::Value>>>,
  285. }
  286. impl IntoFuture for EvalResult {
  287. type Output = Result<serde_json::Value, serde_json::Error>;
  288. type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>;
  289. fn into_future(self) -> Self::IntoFuture {
  290. Box::pin(async move {
  291. let mut reciever = self.reciever.lock().await;
  292. match reciever.recv().await {
  293. Some(result) => Ok(result),
  294. None => Err(serde_json::Error::custom("No result returned")),
  295. }
  296. }) as Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>
  297. }
  298. }
  299. #[cfg(target_os = "ios")]
  300. fn is_main_thread() -> bool {
  301. use objc::runtime::{Class, BOOL, NO};
  302. use objc::*;
  303. let cls = Class::get("NSThread").unwrap();
  304. let result: BOOL = unsafe { msg_send![cls, isMainThread] };
  305. result != NO
  306. }