lib.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #![doc = include_str!("readme.md")]
  2. #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
  3. #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
  4. #![deny(missing_docs)]
  5. mod cfg;
  6. mod controller;
  7. mod desktop_context;
  8. mod escape;
  9. mod events;
  10. mod protocol;
  11. #[cfg(all(feature = "hot-reload", debug_assertions))]
  12. mod hot_reload;
  13. use std::sync::atomic::AtomicBool;
  14. use std::sync::Arc;
  15. use desktop_context::UserWindowEvent;
  16. pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult};
  17. use futures_channel::mpsc::UnboundedSender;
  18. pub use wry;
  19. pub use wry::application as tao;
  20. pub use cfg::Config;
  21. use controller::DesktopController;
  22. use dioxus_core::*;
  23. use events::parse_ipc_message;
  24. pub use tao::dpi::{LogicalSize, PhysicalSize};
  25. pub use tao::window::WindowBuilder;
  26. use tao::{
  27. event::{Event, StartCause, WindowEvent},
  28. event_loop::{ControlFlow, EventLoop},
  29. window::Window,
  30. };
  31. use wry::webview::WebViewBuilder;
  32. /// Launch the WebView and run the event loop.
  33. ///
  34. /// This function will start a multithreaded Tokio runtime as well the WebView event loop.
  35. ///
  36. /// ```rust, ignore
  37. /// use dioxus::prelude::*;
  38. ///
  39. /// fn main() {
  40. /// dioxus_desktop::launch(app);
  41. /// }
  42. ///
  43. /// fn app(cx: Scope) -> Element {
  44. /// cx.render(rsx!{
  45. /// h1 {"hello world!"}
  46. /// })
  47. /// }
  48. /// ```
  49. pub fn launch(root: Component) {
  50. launch_with_props(root, (), Config::default())
  51. }
  52. /// Launch the WebView and run the event loop, with configuration.
  53. ///
  54. /// This function will start a multithreaded Tokio runtime as well the WebView event loop.
  55. ///
  56. /// You can configure the WebView window with a configuration closure
  57. ///
  58. /// ```rust, ignore
  59. /// use dioxus::prelude::*;
  60. ///
  61. /// fn main() {
  62. /// dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App")));
  63. /// }
  64. ///
  65. /// fn app(cx: Scope) -> Element {
  66. /// cx.render(rsx!{
  67. /// h1 {"hello world!"}
  68. /// })
  69. /// }
  70. /// ```
  71. pub fn launch_cfg(root: Component, config_builder: Config) {
  72. launch_with_props(root, (), config_builder)
  73. }
  74. /// Launch the WebView and run the event loop, with configuration and root props.
  75. ///
  76. /// This function will start a multithreaded Tokio runtime as well the WebView event loop.
  77. ///
  78. /// You can configure the WebView window with a configuration closure
  79. ///
  80. /// ```rust, ignore
  81. /// use dioxus::prelude::*;
  82. ///
  83. /// fn main() {
  84. /// dioxus_desktop::launch_cfg(app, AppProps { name: "asd" }, |c| c);
  85. /// }
  86. ///
  87. /// struct AppProps {
  88. /// name: &'static str
  89. /// }
  90. ///
  91. /// fn app(cx: Scope<AppProps>) -> Element {
  92. /// cx.render(rsx!{
  93. /// h1 {"hello {cx.props.name}!"}
  94. /// })
  95. /// }
  96. /// ```
  97. pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
  98. let event_loop = EventLoop::with_user_event();
  99. let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
  100. event_loop.run(move |window_event, event_loop, control_flow| {
  101. *control_flow = ControlFlow::Wait;
  102. match window_event {
  103. Event::NewEvents(StartCause::Init) => desktop.start(&mut cfg, event_loop),
  104. Event::WindowEvent {
  105. event, window_id, ..
  106. } => match event {
  107. WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
  108. WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
  109. _ => {}
  110. },
  111. Event::UserEvent(user_event) => desktop.handle_event(user_event, control_flow),
  112. Event::MainEventsCleared => {}
  113. Event::Resumed => {}
  114. Event::Suspended => {}
  115. Event::LoopDestroyed => {}
  116. Event::RedrawRequested(_id) => {}
  117. _ => {}
  118. }
  119. })
  120. }
  121. impl DesktopController {
  122. fn start(
  123. &mut self,
  124. cfg: &mut Config,
  125. event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
  126. ) {
  127. let webview = build_webview(
  128. cfg,
  129. event_loop,
  130. self.is_ready.clone(),
  131. self.proxy.clone(),
  132. self.eval_sender.clone(),
  133. self.event_tx.clone(),
  134. );
  135. self.webviews.insert(webview.window().id(), webview);
  136. }
  137. }
  138. fn build_webview(
  139. cfg: &mut Config,
  140. event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
  141. is_ready: Arc<AtomicBool>,
  142. proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
  143. eval_sender: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
  144. event_tx: UnboundedSender<serde_json::Value>,
  145. ) -> wry::webview::WebView {
  146. let builder = cfg.window.clone();
  147. let window = builder.build(event_loop).unwrap();
  148. let file_handler = cfg.file_drop_handler.take();
  149. let custom_head = cfg.custom_head.clone();
  150. let resource_dir = cfg.resource_dir.clone();
  151. let index_file = cfg.custom_index.clone();
  152. let root_name = cfg.root_name.clone();
  153. // We assume that if the icon is None in cfg, then the user just didnt set it
  154. if cfg.window.window.window_icon.is_none() {
  155. window.set_window_icon(Some(
  156. tao::window::Icon::from_rgba(
  157. include_bytes!("./assets/default_icon.bin").to_vec(),
  158. 460,
  159. 460,
  160. )
  161. .expect("image parse failed"),
  162. ));
  163. }
  164. let mut webview = WebViewBuilder::new(window)
  165. .unwrap()
  166. .with_transparent(cfg.window.window.transparent)
  167. .with_url("dioxus://index.html/")
  168. .unwrap()
  169. .with_ipc_handler(move |_window: &Window, payload: String| {
  170. let message = match parse_ipc_message(&payload) {
  171. Some(message) => message,
  172. None => {
  173. log::error!("Failed to parse IPC message: {}", payload);
  174. return;
  175. }
  176. };
  177. match message.method() {
  178. "eval_result" => {
  179. let result = message.params();
  180. eval_sender.send(result).unwrap();
  181. }
  182. "user_event" => {
  183. _ = event_tx.unbounded_send(message.params());
  184. }
  185. "initialize" => {
  186. is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
  187. let _ = proxy.send_event(UserWindowEvent::EditsReady);
  188. }
  189. "browser_open" => {
  190. let data = message.params();
  191. log::trace!("Open browser: {:?}", data);
  192. if let Some(temp) = data.as_object() {
  193. if temp.contains_key("href") {
  194. let url = temp.get("href").unwrap().as_str().unwrap();
  195. if let Err(e) = webbrowser::open(url) {
  196. log::error!("Open Browser error: {:?}", e);
  197. }
  198. }
  199. }
  200. }
  201. _ => (),
  202. }
  203. })
  204. .with_custom_protocol(String::from("dioxus"), move |r| {
  205. protocol::desktop_handler(
  206. r,
  207. resource_dir.clone(),
  208. custom_head.clone(),
  209. index_file.clone(),
  210. &root_name,
  211. )
  212. })
  213. .with_file_drop_handler(move |window, evet| {
  214. file_handler
  215. .as_ref()
  216. .map(|handler| handler(window, evet))
  217. .unwrap_or_default()
  218. });
  219. for (name, handler) in cfg.protocols.drain(..) {
  220. webview = webview.with_custom_protocol(name, handler)
  221. }
  222. if cfg.disable_context_menu {
  223. // in release mode, we don't want to show the dev tool or reload menus
  224. webview = webview.with_initialization_script(
  225. r#"
  226. if (document.addEventListener) {
  227. document.addEventListener('contextmenu', function(e) {
  228. e.preventDefault();
  229. }, false);
  230. } else {
  231. document.attachEvent('oncontextmenu', function() {
  232. window.event.returnValue = false;
  233. });
  234. }
  235. "#,
  236. )
  237. } else {
  238. // in debug, we are okay with the reload menu showing and dev tool
  239. webview = webview.with_devtools(true);
  240. }
  241. webview.build().unwrap()
  242. }