lib.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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. #[cfg(feature = "hot-reload")]
  11. mod hot_reload;
  12. mod protocol;
  13. use desktop_context::UserWindowEvent;
  14. pub use desktop_context::{use_eval, use_window, DesktopContext};
  15. pub use wry;
  16. pub use wry::application as tao;
  17. use crate::events::trigger_from_serialized;
  18. use cfg::DesktopConfig;
  19. use controller::DesktopController;
  20. use dioxus_core::*;
  21. use events::parse_ipc_message;
  22. use tao::{
  23. event::{Event, StartCause, WindowEvent},
  24. event_loop::{ControlFlow, EventLoop},
  25. window::Window,
  26. };
  27. use wry::webview::WebViewBuilder;
  28. /// Launch the WebView and run the event loop.
  29. ///
  30. /// This function will start a multithreaded Tokio runtime as well the WebView event loop.
  31. ///
  32. /// ```rust
  33. /// use dioxus::prelude::*;
  34. ///
  35. /// fn main() {
  36. /// dioxus::desktop::launch(app);
  37. /// }
  38. ///
  39. /// fn app(cx: Scope) -> Element {
  40. /// cx.render(rsx!{
  41. /// h1 {"hello world!"}
  42. /// })
  43. /// }
  44. /// ```
  45. pub fn launch(root: Component) {
  46. launch_with_props(root, (), |c| c)
  47. }
  48. /// Launch the WebView and run the event loop, with configuration.
  49. ///
  50. /// This function will start a multithreaded Tokio runtime as well the WebView event loop.
  51. ///
  52. /// You can configure the WebView window with a configuration closure
  53. ///
  54. /// ```rust
  55. /// use dioxus::prelude::*;
  56. ///
  57. /// fn main() {
  58. /// dioxus::desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App")));
  59. /// }
  60. ///
  61. /// fn app(cx: Scope) -> Element {
  62. /// cx.render(rsx!{
  63. /// h1 {"hello world!"}
  64. /// })
  65. /// }
  66. /// ```
  67. pub fn launch_cfg(
  68. root: Component,
  69. config_builder: impl FnOnce(&mut DesktopConfig) -> &mut DesktopConfig,
  70. ) {
  71. launch_with_props(root, (), config_builder)
  72. }
  73. /// Launch the WebView and run the event loop, with configuration and root props.
  74. ///
  75. /// This function will start a multithreaded Tokio runtime as well the WebView event loop.
  76. ///
  77. /// You can configure the WebView window with a configuration closure
  78. ///
  79. /// ```rust
  80. /// use dioxus::prelude::*;
  81. ///
  82. /// fn main() {
  83. /// dioxus::desktop::launch_cfg(app, AppProps { name: "asd" }, |c| c);
  84. /// }
  85. ///
  86. /// struct AppProps {
  87. /// name: &'static str
  88. /// }
  89. ///
  90. /// fn app(cx: Scope<AppProps>) -> Element {
  91. /// cx.render(rsx!{
  92. /// h1 {"hello {cx.props.name}!"}
  93. /// })
  94. /// }
  95. /// ```
  96. pub fn launch_with_props<P: 'static + Send>(
  97. root: Component<P>,
  98. props: P,
  99. builder: impl FnOnce(&mut DesktopConfig) -> &mut DesktopConfig,
  100. ) {
  101. let mut cfg = DesktopConfig::default().with_default_icon();
  102. builder(&mut cfg);
  103. let event_loop = EventLoop::with_user_event();
  104. let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
  105. let proxy = event_loop.create_proxy();
  106. event_loop.run(move |window_event, event_loop, control_flow| {
  107. *control_flow = ControlFlow::Wait;
  108. match window_event {
  109. Event::NewEvents(StartCause::Init) => {
  110. let builder = cfg.window.clone();
  111. let window = builder.build(event_loop).unwrap();
  112. let window_id = window.id();
  113. let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone());
  114. let proxy = proxy.clone();
  115. let file_handler = cfg.file_drop_handler.take();
  116. let custom_head = cfg.custom_head.clone();
  117. let resource_dir = cfg.resource_dir.clone();
  118. let index_file = cfg.custom_index.clone();
  119. let mut webview = WebViewBuilder::new(window)
  120. .unwrap()
  121. .with_transparent(cfg.window.window.transparent)
  122. .with_url("dioxus://index.html/")
  123. .unwrap()
  124. .with_ipc_handler(move |_window: &Window, payload: String| {
  125. parse_ipc_message(&payload)
  126. .map(|message| match message.method() {
  127. "user_event" => {
  128. let event = trigger_from_serialized(message.params());
  129. log::trace!("User event: {:?}", event);
  130. sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
  131. }
  132. "initialize" => {
  133. is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
  134. let _ = proxy.send_event(UserWindowEvent::Update);
  135. }
  136. "browser_open" => {
  137. let data = message.params();
  138. log::trace!("Open browser: {:?}", data);
  139. if let Some(temp) = data.as_object() {
  140. if temp.contains_key("href") {
  141. let url = temp.get("href").unwrap().as_str().unwrap();
  142. if let Err(e) = webbrowser::open(url) {
  143. log::error!("Open Browser error: {:?}", e);
  144. }
  145. }
  146. }
  147. }
  148. _ => (),
  149. })
  150. .unwrap_or_else(|| {
  151. log::warn!("invalid IPC message received");
  152. });
  153. })
  154. .with_custom_protocol(String::from("dioxus"), move |r| {
  155. protocol::desktop_handler(
  156. r,
  157. resource_dir.clone(),
  158. custom_head.clone(),
  159. index_file.clone(),
  160. )
  161. })
  162. .with_file_drop_handler(move |window, evet| {
  163. file_handler
  164. .as_ref()
  165. .map(|handler| handler(window, evet))
  166. .unwrap_or_default()
  167. });
  168. for (name, handler) in cfg.protocols.drain(..) {
  169. webview = webview.with_custom_protocol(name, handler)
  170. }
  171. if cfg.disable_context_menu {
  172. // in release mode, we don't want to show the dev tool or reload menus
  173. webview = webview.with_initialization_script(
  174. r#"
  175. if (document.addEventListener) {
  176. document.addEventListener('contextmenu', function(e) {
  177. e.preventDefault();
  178. }, false);
  179. } else {
  180. document.attachEvent('oncontextmenu', function() {
  181. window.event.returnValue = false;
  182. });
  183. }
  184. "#,
  185. )
  186. } else {
  187. // in debug, we are okay with the reload menu showing and dev tool
  188. webview = webview.with_devtools(true);
  189. }
  190. desktop.webviews.insert(window_id, webview.build().unwrap());
  191. }
  192. Event::WindowEvent {
  193. event, window_id, ..
  194. } => match event {
  195. WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
  196. WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
  197. WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
  198. if let Some(view) = desktop.webviews.get_mut(&window_id) {
  199. let _ = view.resize();
  200. }
  201. }
  202. _ => {}
  203. },
  204. Event::UserEvent(user_event) => {
  205. desktop_context::handler(user_event, &mut desktop, control_flow)
  206. }
  207. Event::MainEventsCleared => {}
  208. Event::Resumed => {}
  209. Event::Suspended => {}
  210. Event::LoopDestroyed => {}
  211. Event::RedrawRequested(_id) => {}
  212. _ => {}
  213. }
  214. })
  215. }