lib.rs 8.4 KB

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