config.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. use dioxus_core::LaunchConfig;
  2. use std::borrow::Cow;
  3. use std::path::PathBuf;
  4. use tao::event_loop::{EventLoop, EventLoopWindowTarget};
  5. use tao::window::{Icon, WindowBuilder};
  6. use wry::http::{Request as HttpRequest, Response as HttpResponse};
  7. use wry::RequestAsyncResponder;
  8. use crate::ipc::UserWindowEvent;
  9. use crate::menubar::{default_menu_bar, DioxusMenu};
  10. type CustomEventHandler = Box<
  11. dyn 'static
  12. + for<'a> FnMut(
  13. &tao::event::Event<'a, UserWindowEvent>,
  14. &EventLoopWindowTarget<UserWindowEvent>,
  15. ),
  16. >;
  17. /// The behaviour of the application when the last window is closed.
  18. #[derive(Copy, Clone, Eq, PartialEq)]
  19. #[non_exhaustive]
  20. pub enum WindowCloseBehaviour {
  21. /// Default behaviour, closing the last window exits the app
  22. LastWindowExitsApp,
  23. /// Closing the last window will not actually close it, just hide it
  24. LastWindowHides,
  25. /// Closing the last window will close it but the app will keep running so that new windows can be opened
  26. CloseWindow,
  27. }
  28. /// The state of the menu builder. We need to keep track of if the state is default
  29. /// so we only swap out the default menu bar when decorations are disabled
  30. pub(crate) enum MenuBuilderState {
  31. Unset,
  32. Set(Option<DioxusMenu>),
  33. }
  34. impl From<MenuBuilderState> for Option<DioxusMenu> {
  35. fn from(val: MenuBuilderState) -> Self {
  36. match val {
  37. MenuBuilderState::Unset => Some(default_menu_bar()),
  38. MenuBuilderState::Set(menu) => menu,
  39. }
  40. }
  41. }
  42. /// The configuration for the desktop application.
  43. pub struct Config {
  44. pub(crate) event_loop: Option<EventLoop<UserWindowEvent>>,
  45. pub(crate) window: WindowBuilder,
  46. pub(crate) as_child_window: bool,
  47. pub(crate) menu: MenuBuilderState,
  48. pub(crate) protocols: Vec<WryProtocol>,
  49. pub(crate) asynchronous_protocols: Vec<AsyncWryProtocol>,
  50. pub(crate) pre_rendered: Option<String>,
  51. pub(crate) disable_context_menu: bool,
  52. pub(crate) resource_dir: Option<PathBuf>,
  53. pub(crate) data_dir: Option<PathBuf>,
  54. pub(crate) custom_head: Option<String>,
  55. pub(crate) custom_index: Option<String>,
  56. pub(crate) root_name: String,
  57. pub(crate) background_color: Option<(u8, u8, u8, u8)>,
  58. pub(crate) last_window_close_behavior: WindowCloseBehaviour,
  59. pub(crate) custom_event_handler: Option<CustomEventHandler>,
  60. pub(crate) disable_file_drop_handler: bool,
  61. }
  62. impl LaunchConfig for Config {}
  63. pub(crate) type WryProtocol = (
  64. String,
  65. Box<dyn Fn(&str, HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static>,
  66. );
  67. pub(crate) type AsyncWryProtocol = (
  68. String,
  69. Box<dyn Fn(&str, HttpRequest<Vec<u8>>, RequestAsyncResponder) + 'static>,
  70. );
  71. impl Config {
  72. /// Initializes a new `WindowBuilder` with default values.
  73. #[inline]
  74. pub fn new() -> Self {
  75. let mut window: WindowBuilder = WindowBuilder::new()
  76. .with_title(dioxus_cli_config::app_title().unwrap_or_else(|| "Dioxus App".to_string()));
  77. // During development we want the window to be on top so we can see it while we work
  78. let always_on_top = dioxus_cli_config::always_on_top().unwrap_or(true);
  79. if cfg!(debug_assertions) {
  80. window = window.with_always_on_top(always_on_top);
  81. }
  82. Self {
  83. window,
  84. as_child_window: false,
  85. event_loop: None,
  86. menu: MenuBuilderState::Unset,
  87. protocols: Vec::new(),
  88. asynchronous_protocols: Vec::new(),
  89. pre_rendered: None,
  90. disable_context_menu: !cfg!(debug_assertions),
  91. resource_dir: None,
  92. data_dir: None,
  93. custom_head: None,
  94. custom_index: None,
  95. root_name: "main".to_string(),
  96. background_color: None,
  97. last_window_close_behavior: WindowCloseBehaviour::LastWindowExitsApp,
  98. custom_event_handler: None,
  99. disable_file_drop_handler: false,
  100. }
  101. }
  102. /// set the directory from which assets will be searched in release mode
  103. pub fn with_resource_directory(mut self, path: impl Into<PathBuf>) -> Self {
  104. self.resource_dir = Some(path.into());
  105. self
  106. }
  107. /// set the directory where data will be stored in release mode.
  108. ///
  109. /// > Note: This **must** be set when bundling on Windows.
  110. pub fn with_data_directory(mut self, path: impl Into<PathBuf>) -> Self {
  111. self.data_dir = Some(path.into());
  112. self
  113. }
  114. /// Set whether or not the right-click context menu should be disabled.
  115. pub fn with_disable_context_menu(mut self, disable: bool) -> Self {
  116. self.disable_context_menu = disable;
  117. self
  118. }
  119. /// Set whether or not the file drop handler should be disabled.
  120. /// On Windows the drop handler must be disabled for HTML drag and drop APIs to work.
  121. pub fn with_disable_drag_drop_handler(mut self, disable: bool) -> Self {
  122. self.disable_file_drop_handler = disable;
  123. self
  124. }
  125. /// Set the pre-rendered HTML content
  126. pub fn with_prerendered(mut self, content: String) -> Self {
  127. self.pre_rendered = Some(content);
  128. self
  129. }
  130. /// Set the event loop to be used
  131. pub fn with_event_loop(mut self, event_loop: EventLoop<UserWindowEvent>) -> Self {
  132. self.event_loop = Some(event_loop);
  133. self
  134. }
  135. /// Set the configuration for the window.
  136. pub fn with_window(mut self, window: WindowBuilder) -> Self {
  137. // We need to do a swap because the window builder only takes itself as muy self
  138. self.window = window;
  139. // If the decorations are off for the window, remove the menu as well
  140. if !self.window.window.decorations && matches!(self.menu, MenuBuilderState::Unset) {
  141. self.menu = MenuBuilderState::Set(None);
  142. }
  143. self
  144. }
  145. /// Set the window as child
  146. pub fn with_as_child_window(mut self) -> Self {
  147. self.as_child_window = true;
  148. self
  149. }
  150. /// Sets the behaviour of the application when the last window is closed.
  151. pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self {
  152. self.last_window_close_behavior = behaviour;
  153. self
  154. }
  155. /// Sets a custom callback to run whenever the event pool receives an event.
  156. pub fn with_custom_event_handler(
  157. mut self,
  158. f: impl FnMut(&tao::event::Event<'_, UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>)
  159. + 'static,
  160. ) -> Self {
  161. self.custom_event_handler = Some(Box::new(f));
  162. self
  163. }
  164. /// Set a custom protocol
  165. pub fn with_custom_protocol<F>(mut self, name: impl ToString, handler: F) -> Self
  166. where
  167. F: Fn(&str, HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static,
  168. {
  169. self.protocols.push((name.to_string(), Box::new(handler)));
  170. self
  171. }
  172. /// Set an asynchronous custom protocol
  173. ///
  174. /// **Example Usage**
  175. /// ```rust
  176. /// # use wry::http::response::Response as HTTPResponse;
  177. /// # use std::borrow::Cow;
  178. /// # use dioxus_desktop::Config;
  179. /// #
  180. /// # fn main() {
  181. /// let cfg = Config::new()
  182. /// .with_asynchronous_custom_protocol("asset", |request, responder| {
  183. /// tokio::spawn(async move {
  184. /// responder.respond(
  185. /// HTTPResponse::builder()
  186. /// .status(404)
  187. /// .body(Cow::Borrowed("404 - Not Found".as_bytes()))
  188. /// .unwrap()
  189. /// );
  190. /// });
  191. /// });
  192. /// # }
  193. /// ```
  194. /// note a key difference between Dioxus and Wry, the protocol name doesn't explicitly need to be a
  195. /// [`String`], but needs to implement [`ToString`].
  196. ///
  197. /// See [`wry`](wry::WebViewBuilder::with_asynchronous_custom_protocol) for more details on implementation
  198. pub fn with_asynchronous_custom_protocol<F>(mut self, name: impl ToString, handler: F) -> Self
  199. where
  200. F: Fn(&str, HttpRequest<Vec<u8>>, RequestAsyncResponder) + 'static,
  201. {
  202. self.asynchronous_protocols
  203. .push((name.to_string(), Box::new(handler)));
  204. self
  205. }
  206. /// Set a custom icon for this application
  207. pub fn with_icon(mut self, icon: Icon) -> Self {
  208. self.window.window.window_icon = Some(icon);
  209. self
  210. }
  211. /// Inject additional content into the document's HEAD.
  212. ///
  213. /// This is useful for loading CSS libraries, JS libraries, etc.
  214. pub fn with_custom_head(mut self, head: String) -> Self {
  215. self.custom_head = Some(head);
  216. self
  217. }
  218. /// Use a custom index.html instead of the default Dioxus one.
  219. ///
  220. /// Make sure your index.html is valid HTML.
  221. ///
  222. /// Dioxus injects some loader code into the closing body tag. Your document
  223. /// must include a body element!
  224. pub fn with_custom_index(mut self, index: String) -> Self {
  225. self.custom_index = Some(index);
  226. self
  227. }
  228. /// Set the name of the element that Dioxus will use as the root.
  229. ///
  230. /// This is akin to calling React.render() on the element with the specified name.
  231. pub fn with_root_name(mut self, name: impl Into<String>) -> Self {
  232. self.root_name = name.into();
  233. self
  234. }
  235. /// Sets the background color of the WebView.
  236. /// This will be set before the HTML is rendered and can be used to prevent flashing when the page loads.
  237. /// Accepts a color in RGBA format
  238. pub fn with_background_color(mut self, color: (u8, u8, u8, u8)) -> Self {
  239. self.background_color = Some(color);
  240. self
  241. }
  242. /// Sets the menu the window will use. This will override the default menu bar.
  243. ///
  244. /// > Note: Menu will be hidden if
  245. /// > [`with_decorations`](tao::window::WindowBuilder::with_decorations)
  246. /// > is set to false and passed into [`with_window`](Config::with_window)
  247. #[allow(unused)]
  248. pub fn with_menu(mut self, menu: impl Into<Option<DioxusMenu>>) -> Self {
  249. #[cfg(not(any(target_os = "ios", target_os = "android")))]
  250. {
  251. if self.window.window.decorations {
  252. self.menu = MenuBuilderState::Set(menu.into())
  253. }
  254. }
  255. self
  256. }
  257. }
  258. impl Default for Config {
  259. fn default() -> Self {
  260. Self::new()
  261. }
  262. }
  263. // dirty trick, avoid introducing `image` at runtime
  264. // TODO: use serde when `Icon` impl serde
  265. //
  266. // This function should only be enabled when generating new icons.
  267. //
  268. // #[test]
  269. // #[ignore]
  270. // fn prepare_default_icon() {
  271. // use image::io::Reader as ImageReader;
  272. // use image::ImageFormat;
  273. // use std::fs::File;
  274. // use std::io::Cursor;
  275. // use std::io::Write;
  276. // use std::path::PathBuf;
  277. // let png: &[u8] = include_bytes!("default_icon.png");
  278. // let mut reader = ImageReader::new(Cursor::new(png));
  279. // reader.set_format(ImageFormat::Png);
  280. // let icon = reader.decode().unwrap();
  281. // let bin = PathBuf::from(file!())
  282. // .parent()
  283. // .unwrap()
  284. // .join("default_icon.bin");
  285. // println!("{:?}", bin);
  286. // let mut file = File::create(bin).unwrap();
  287. // file.write_all(icon.as_bytes()).unwrap();
  288. // println!("({}, {})", icon.width(), icon.height())
  289. // }