desktop_context.rs 8.7 KB

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