relm4_components/
save_dialog.rs

1//! Reusable and easily configurable save dialog component.
2//!
3//! **[Example implementation](https://github.com/Relm4/Relm4/blob/main/relm4-components/examples/file_dialogs.rs)**
4use gtk::prelude::{FileChooserExt, FileExt, NativeDialogExt};
5use relm4::{ComponentParts, ComponentSender, SimpleComponent, gtk};
6
7use std::path::PathBuf;
8
9#[derive(Clone, Debug)]
10/// Configuration for the save dialog component
11pub struct SaveDialogSettings {
12    /// Label for cancel button
13    pub cancel_label: String,
14    /// Label for accept button
15    pub accept_label: String,
16    /// Allow or disallow creating folders
17    pub create_folders: bool,
18    /// Freeze other windows while the dialog is open
19    pub is_modal: bool,
20    /// Filter for MIME types or other patterns
21    pub filters: Vec<gtk::FileFilter>,
22}
23
24impl Default for SaveDialogSettings {
25    fn default() -> Self {
26        SaveDialogSettings {
27            accept_label: String::from("Save"),
28            cancel_label: String::from("Cancel"),
29            create_folders: true,
30            is_modal: true,
31            filters: Vec::new(),
32        }
33    }
34}
35
36#[derive(Debug)]
37/// A model for the save dialog component
38pub struct SaveDialog {
39    current_name: String,
40    visible: bool,
41}
42
43/// Messages that can be sent to the save dialog component
44#[derive(Debug, Clone)]
45pub enum SaveDialogMsg {
46    /// Show the dialog
47    Save,
48    /// Show the dialog, with a suggested file name
49    SaveAs(String),
50    #[doc(hidden)]
51    Hide,
52}
53
54/// Messages that can be sent from the save dialog component
55#[derive(Debug, Clone)]
56pub enum SaveDialogResponse {
57    /// User clicked accept button.
58    Accept(PathBuf),
59    /// User clicked cancel button.
60    Cancel,
61}
62
63/// Widgets of the save dialog component.
64#[relm4::component(pub)]
65impl SimpleComponent for SaveDialog {
66    type Init = SaveDialogSettings;
67
68    type Input = SaveDialogMsg;
69    type Output = SaveDialogResponse;
70
71    view! {
72        gtk::FileChooserNative {
73            set_action: gtk::FileChooserAction::Save,
74
75            set_create_folders: settings.create_folders,
76            set_modal: settings.is_modal,
77            set_accept_label: Some(&settings.accept_label),
78            set_cancel_label: Some(&settings.cancel_label),
79            #[iterate]
80            add_filter: &settings.filters,
81
82            #[watch]
83            set_current_name: &model.current_name,
84            #[watch]
85            set_visible: model.visible,
86
87            connect_response[sender] => move |dialog, res_ty| {
88                match res_ty {
89                    gtk::ResponseType::Accept => {
90                        if let Some(file) = dialog.file()
91                            && let Some(path) = file.path() {
92                                sender.output(SaveDialogResponse::Accept(path)).unwrap();
93                                sender.input(SaveDialogMsg::Hide);
94                                return;
95                            }
96                        sender.output(SaveDialogResponse::Cancel).unwrap();
97                    }
98                    _ => sender.output(SaveDialogResponse::Cancel).unwrap(),
99                }
100                sender.input(SaveDialogMsg::Hide);
101            }
102        }
103    }
104
105    fn init(
106        settings: Self::Init,
107        root: Self::Root,
108        sender: ComponentSender<Self>,
109    ) -> ComponentParts<Self> {
110        let model = SaveDialog {
111            current_name: String::new(),
112            visible: false,
113        };
114
115        let widgets = view_output!();
116
117        ComponentParts { model, widgets }
118    }
119
120    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
121        match message {
122            SaveDialogMsg::Save => {
123                self.current_name = String::new();
124                self.visible = true;
125            }
126            SaveDialogMsg::SaveAs(file_name) => {
127                self.current_name = file_name;
128                self.visible = true;
129            }
130            SaveDialogMsg::Hide => {
131                self.visible = false;
132            }
133        }
134    }
135}