dioxus_desktop/
config.rs

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