relm4_components/
open_dialog.rs1use gtk::prelude::{Cast, FileChooserExt, FileExt, ListModelExt, NativeDialogExt};
5use relm4::{ComponentParts, ComponentSender, SimpleComponent, gtk};
6
7use std::{fmt::Debug, marker::PhantomData, path::PathBuf};
8
9pub type OpenDialog = OpenDialogInner<SingleSelection>;
13
14pub type OpenDialogMulti = OpenDialogInner<MultiSelection>;
18
19pub trait Select: Debug {
21 type Selection: Debug;
23 const SELECT_MULTIPLE: bool;
25 fn select(dialog: >k::FileChooserNative) -> Self::Selection;
27}
28
29#[derive(Debug)]
31pub struct SingleSelection;
32
33impl Select for SingleSelection {
34 type Selection = PathBuf;
35 const SELECT_MULTIPLE: bool = false;
36 fn select(dialog: >k::FileChooserNative) -> Self::Selection {
37 dialog
38 .file()
39 .expect("No file selected")
40 .path()
41 .expect("No path")
42 }
43}
44
45#[derive(Debug)]
47pub struct MultiSelection;
48impl Select for MultiSelection {
49 type Selection = Vec<PathBuf>;
50 const SELECT_MULTIPLE: bool = true;
51 fn select(dialog: >k::FileChooserNative) -> Self::Selection {
52 let list_model = dialog.files();
53 (0..list_model.n_items())
54 .filter_map(|index| list_model.item(index))
55 .filter_map(|obj| obj.downcast::<gtk::gio::File>().ok())
56 .filter_map(|file| file.path())
57 .collect()
58 }
59}
60
61#[derive(Clone, Debug)]
62pub struct OpenDialogSettings {
64 pub folder_mode: bool,
66 pub cancel_label: String,
68 pub accept_label: String,
70 pub create_folders: bool,
72 pub is_modal: bool,
74 pub filters: Vec<gtk::FileFilter>,
76}
77
78impl Default for OpenDialogSettings {
79 fn default() -> Self {
80 OpenDialogSettings {
81 folder_mode: false,
82 accept_label: String::from("Open"),
83 cancel_label: String::from("Cancel"),
84 create_folders: true,
85 is_modal: true,
86 filters: Vec::new(),
87 }
88 }
89}
90
91#[derive(Debug)]
92pub struct OpenDialogInner<S: Select> {
94 visible: bool,
95 _phantom: PhantomData<S>,
96}
97
98#[derive(Debug, Clone)]
100pub enum OpenDialogMsg {
101 Open,
103 #[doc(hidden)]
104 Hide,
105}
106
107#[derive(Debug, Clone)]
109pub enum OpenDialogResponse<S: Select> {
110 Accept(S::Selection),
112 Cancel,
114}
115
116#[relm4::component(pub)]
118impl<S: Select + 'static> SimpleComponent for OpenDialogInner<S> {
119 type Init = OpenDialogSettings;
120 type Input = OpenDialogMsg;
121 type Output = OpenDialogResponse<S>;
122
123 view! {
124 gtk::FileChooserNative {
125 set_action: if settings.folder_mode {
126 gtk::FileChooserAction::SelectFolder
127 } else {
128 gtk::FileChooserAction::Open
129 },
130
131 set_select_multiple: S::SELECT_MULTIPLE,
132 set_create_folders: settings.create_folders,
133 set_modal: settings.is_modal,
134 set_accept_label: Some(&settings.accept_label),
135 set_cancel_label: Some(&settings.cancel_label),
136 #[iterate]
137 add_filter: &settings.filters,
138
139 #[watch]
140 set_visible: model.visible,
141
142 connect_response[sender] => move |dialog, res_ty| {
143 match res_ty {
144 gtk::ResponseType::Accept => {
145 let selection = S::select(dialog);
146 sender.output(OpenDialogResponse::Accept(selection)).unwrap();
147 }
148 _ => sender.output(OpenDialogResponse::Cancel).unwrap(),
149 }
150
151 sender.input(OpenDialogMsg::Hide);
152 }
153 }
154 }
155
156 fn init(
157 settings: Self::Init,
158 root: Self::Root,
159 sender: ComponentSender<Self>,
160 ) -> ComponentParts<Self> {
161 let model = OpenDialogInner {
162 visible: false,
163 _phantom: PhantomData,
164 };
165
166 let widgets = view_output!();
167
168 ComponentParts { model, widgets }
169 }
170
171 fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
172 match message {
173 OpenDialogMsg::Open => {
174 self.visible = true;
175 }
176 OpenDialogMsg::Hide => {
177 self.visible = false;
178 }
179 }
180 }
181}