gui_server_api/navigation/
filepicker.rs

1// SPDX-FileCopyrightText: 2024-2025 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! File picker (select file) navigation request and response formats.
5
6#[derive(Debug, Copy, Clone, Eq, PartialEq, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
7pub enum Location {
8    Internal,
9    Airlock,
10    External,
11}
12
13#[derive(Debug, Clone, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
14pub enum AllowedLocations {
15    All,
16    Specific(Vec<Location>),
17}
18
19impl AllowedLocations {
20    pub fn specific<T: IntoIterator<Item = Location>>(locations: T) -> Self {
21        Self::Specific(locations.into_iter().collect::<Vec<_>>())
22    }
23
24    pub fn contains(&self, location: Location) -> bool {
25        match self {
26            Self::All => true,
27            Self::Specific(locations) => locations.contains(&location),
28        }
29    }
30
31    pub fn len(&self) -> Option<usize> {
32        match self {
33            Self::All => None,
34            Self::Specific(locations) => Some(locations.len()),
35        }
36    }
37
38    pub fn is_empty(&self) -> bool { self.len().map(|len| len == 0).unwrap_or(false) }
39}
40
41#[derive(Debug, Clone, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
42pub enum AllowedExtensions {
43    All,
44    Specific(Vec<String>),
45}
46
47impl AllowedExtensions {
48    pub fn specific<S: AsRef<str>, T: IntoIterator<Item = S>>(extensions: T) -> Self {
49        Self::Specific(extensions.into_iter().map(|s| s.as_ref().to_string()).collect::<Vec<_>>())
50    }
51
52    pub fn contains<S: AsRef<str>>(&self, extension: S) -> bool {
53        match self {
54            Self::All => true,
55            Self::Specific(extensions) => extensions.contains(&extension.as_ref().to_string()),
56        }
57    }
58}
59
60/// Options for the file picker navigation request.
61///
62/// Example to only allow accessing `.bin` files in `External` location:
63///
64/// ```rust,ignore
65/// # use navigation::api::filepicker::{SelectFileOptions, AllowedLocations, AllowedExtensions, Location};
66/// let options = SelectFileOptions::default()
67///     .with_start_location(Location::External)
68///     .with_allowed_locations(AllowedLocations::specific(&[Location::External]))
69///     .with_allowed_extensions(AllowedExtensions::specific(&["bin"]));
70/// ```
71#[derive(Debug, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
72pub struct SelectFileOptions {
73    start_location: Location,
74    locations: AllowedLocations,
75    extensions: AllowedExtensions,
76    hidden_allowed: bool,
77    dirs_allowed: bool,
78    search_allowed: bool,
79    dir_selection_mode: bool,
80    multiple_selection_mode: bool,
81}
82
83impl Default for SelectFileOptions {
84    fn default() -> Self {
85        Self {
86            start_location: Location::Internal,
87            locations: AllowedLocations::All,
88            extensions: AllowedExtensions::All,
89            hidden_allowed: true,
90            search_allowed: true,
91            dirs_allowed: true,
92            dir_selection_mode: false,
93            multiple_selection_mode: false,
94        }
95    }
96}
97
98impl SelectFileOptions {
99    /// Enable directory selection mode.
100    /// Doesn't show the files, only directories and allows navigating into them.
101    /// This is disabled by default.
102    /// Enabling this also enables `dirs_allowed`.
103    pub fn with_dir_selection_mode(self, dir_selection_mode: bool) -> Self {
104        Self { dir_selection_mode, dirs_allowed: self.dirs_allowed || dir_selection_mode, ..self }
105    }
106
107    /// Allow viewing and navigating directories.
108    /// This is enabled by default.
109    /// Disabling this has no effect if `dir_selection_mode` is enabled.
110    pub fn with_dirs_allowed(self, allow_directories: bool) -> Self {
111        Self { dirs_allowed: allow_directories || self.dir_selection_mode, ..self }
112    }
113
114    /// Display the search bar and activates search menu.
115    pub fn with_search_allowed(self, allow_search: bool) -> Self {
116        Self { search_allowed: allow_search, ..self }
117    }
118
119    /// Display the search bar and activates search menu.
120    /// This is disabled by default.
121    /// Enabling this will disable `dir_selection_mode`.
122    pub fn with_multiple_selection_mode(self, multiple_selection_mode: bool) -> Self {
123        let dir_selection_mode = self.dir_selection_mode && !multiple_selection_mode;
124        Self { multiple_selection_mode, dir_selection_mode, ..self }
125    }
126
127    /// Sets whether hidden files are allowed to be shown.
128    pub fn with_hidden_allowed(self, hidden_allowed: bool) -> Self { Self { hidden_allowed, ..self } }
129
130    pub fn with_start_location(self, start_location: Location) -> Self { Self { start_location, ..self } }
131
132    pub fn with_allowed_locations(self, locations: AllowedLocations) -> Self { Self { locations, ..self } }
133
134    pub fn with_allowed_extensions(self, extensions: AllowedExtensions) -> Self {
135        Self { extensions, ..self }
136    }
137
138    pub fn search_allowed(&self) -> bool { self.search_allowed }
139
140    pub fn dirs_allowed(&self) -> bool { self.dirs_allowed }
141
142    pub fn hidden_allowed(&self) -> bool { self.hidden_allowed }
143
144    pub fn dir_selection_mode(&self) -> bool { self.dir_selection_mode }
145
146    pub fn multiple_selection_mode(&self) -> bool { self.multiple_selection_mode }
147
148    pub fn start_location(&self) -> Location { self.start_location }
149
150    pub fn allowed_locations(&self) -> &AllowedLocations { &self.locations }
151
152    pub fn allowed_extensions(&self) -> &AllowedExtensions { &self.extensions }
153
154    pub fn from_slice(data: &[u8]) -> Option<Self> {
155        let Ok(archived) = rkyv::access::<ArchivedSelectFileOptions, rkyv::rancor::Error>(data) else {
156            return None;
157        };
158        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
159    }
160
161    pub fn serialize(&self) -> Vec<u8> { rkyv::to_bytes::<rkyv::rancor::Error>(self).unwrap().to_vec() }
162}
163
164#[derive(Debug, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
165pub struct SelectFileResult {
166    pub files: Vec<(String, Location)>,
167}
168
169impl SelectFileResult {
170    pub fn new(files: Vec<(String, Location)>) -> Self { Self { files } }
171
172    pub fn files(&self) -> &[(String, Location)] { &self.files }
173
174    pub fn from_slice(data: &[u8]) -> Option<Self> {
175        let Ok(archived) = rkyv::access::<ArchivedSelectFileResult, rkyv::rancor::Error>(data) else {
176            return None;
177        };
178        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
179    }
180
181    pub fn serialize(&self) -> Vec<u8> { rkyv::to_bytes::<rkyv::rancor::Error>(self).unwrap().to_vec() }
182}