dioxus_desktop/
config.rs

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