ctru/applets/mii_selector.rs
1//! Mii Selector applet.
2//!
3//! This applet opens a window which lets the player/user choose a Mii from the ones present on their console.
4//! The selected Mii is readable as a [`Mii`].
5
6use crate::mii::Mii;
7use crate::services::{apt::Apt, gfx::Gfx};
8
9use bitflags::bitflags;
10use std::{ffi::CString, fmt};
11
12/// Index of a Mii on the [`MiiSelector`] interface.
13///
14/// See [`MiiSelector::allowlist_user_mii()`] and related functions for more information.
15#[derive(Debug, Clone, Copy, Eq, PartialEq)]
16pub enum Index {
17 /// Specific Mii index.
18 ///
19 /// # Notes
20 ///
21 /// Indexes start at 0.
22 Index(u32),
23 /// All Miis.
24 All,
25}
26
27/// The type of a Mii.
28#[derive(Debug, Clone, Eq, PartialEq)]
29pub enum MiiType {
30 /// Guest Mii.
31 Guest {
32 /// Guest Mii index.
33 index: u32,
34 /// Guest Mii name.
35 name: String,
36 },
37 /// User-made Mii.
38 User,
39}
40
41bitflags! {
42 /// Options to configure the [`MiiSelector`].
43 ///
44 /// See [`MiiSelector::set_options()`] to learn how to use them.
45 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
46 pub struct Options: u8 {
47 /// Show the cancel button.
48 const ENABLE_CANCEL = ctru_sys::MIISELECTOR_CANCEL;
49 /// Make guest Miis available to select.
50 const ENABLE_GUESTS = ctru_sys::MIISELECTOR_GUESTS;
51 /// Show the [`MiiSelector`] window on the top screen.
52 const USE_TOP_SCREEN = ctru_sys::MIISELECTOR_TOP;
53 /// Start the [`MiiSelector`] on the guests' page. Requires [`Options::ENABLE_GUESTS`].
54 const START_WITH_GUESTS = ctru_sys::MIISELECTOR_GUESTSTART;
55 }
56}
57
58/// Configuration structure to setup the Mii Selector applet.
59#[doc(alias = "MiiSelectorConf")]
60#[derive(Clone, Debug)]
61pub struct MiiSelector {
62 config: Box<ctru_sys::MiiSelectorConf>,
63}
64
65/// Return value of a successful [`MiiSelector::launch()`].
66#[non_exhaustive]
67#[derive(Clone, Debug)]
68pub struct Selection {
69 /// Data of the selected Mii.
70 pub mii_data: Mii,
71 /// Type of the selected Mii.
72 pub mii_type: MiiType,
73}
74
75/// Error returned by an unsuccessful [`MiiSelector::launch()`].
76#[derive(Copy, Clone, Debug, Eq, PartialEq)]
77pub enum Error {
78 /// The selected Mii's data is corrupt.
79 InvalidChecksum,
80 /// Either the user cancelled the selection (see [`Options::ENABLE_CANCEL`]) or no valid Miis were available to select.
81 NoMiiSelected,
82}
83
84impl MiiSelector {
85 /// Initialize a new configuration for the Mii Selector applet.
86 #[doc(alias = "miiSelectorInit")]
87 pub fn new() -> Self {
88 let mut config = Box::<ctru_sys::MiiSelectorConf>::default();
89 unsafe {
90 ctru_sys::miiSelectorInit(config.as_mut());
91 }
92 Self { config }
93 }
94
95 /// Set the title of the Mii Selector window.
96 ///
97 /// # Panics
98 /// This function will panic if the given `&str` contains NUL bytes.
99 ///
100 /// # Example
101 ///
102 /// ```no_run
103 /// # fn main() {
104 /// use ctru::applets::mii_selector::MiiSelector;
105 ///
106 /// let mut mii_selector = MiiSelector::new();
107 /// mii_selector.set_title("Select a Mii!");
108 /// # }
109 /// ```
110 #[doc(alias = "miiSelectorSetTitle")]
111 pub fn set_title(&mut self, text: &str) {
112 // This can only fail if the text contains NUL bytes in the string... which seems
113 // unlikely and is documented
114 let c_text = CString::new(text).expect("Failed to convert the title text into a CString");
115 unsafe {
116 ctru_sys::miiSelectorSetTitle(self.config.as_mut(), c_text.as_ptr());
117 }
118 }
119
120 /// Set the options of the Mii Selector.
121 ///
122 /// This will overwrite any previously saved options. Use bitwise operations to set all your wanted options at once.
123 ///
124 /// # Example
125 ///
126 /// ```no_run
127 /// # fn main() {
128 /// use ctru::applets::mii_selector::{MiiSelector, Options};
129 /// let mut mii_selector = MiiSelector::new();
130 ///
131 /// // Setup a `MiiSelector` that can be cancelled and that makes Guest Miis available to select.
132 /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS;
133 /// mii_selector.set_options(opts);
134 /// # }
135 /// ```
136 #[doc(alias = "miiSelectorSetOptions")]
137 pub fn set_options(&mut self, options: Options) {
138 unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits().into()) }
139 }
140
141 /// Allowlist a guest Mii based on its index.
142 ///
143 /// # Notes
144 ///
145 /// Guest Mii's won't be available regardless of their allowlist/blocklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
146 /// Look into [`MiiSelector::set_options()`] to see how to work with options.
147 ///
148 /// # Example
149 ///
150 /// ```no_run
151 /// # fn main() {
152 /// #
153 /// use ctru::applets::mii_selector::{Index, MiiSelector};
154 /// let mut mii_selector = MiiSelector::new();
155 ///
156 /// // Allowlist the guest Mii at index 2.
157 /// mii_selector.allowlist_guest_mii(Index::Index(2));
158 /// # }
159 /// ```
160 #[doc(alias = "miiSelectorWhitelistGuestMii")]
161 pub fn allowlist_guest_mii(&mut self, mii_index: Index) {
162 let index = match mii_index {
163 Index::Index(i) => i,
164 Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
165 };
166
167 unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) }
168 }
169
170 /// Blocklist a guest Mii based on its index.
171 ///
172 /// # Notes
173 ///
174 /// Guest Mii's won't be available regardless of their allowlist/blocklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
175 /// Look into [`MiiSelector::set_options()`] to see how to work with options.
176 ///
177 /// # Example
178 ///
179 /// ```no_run
180 /// # fn main() {
181 /// #
182 /// use ctru::applets::mii_selector::{Index, MiiSelector};
183 /// let mut mii_selector = MiiSelector::new();
184 ///
185 /// // Blocklist the guest Mii at index 1 so that it cannot be selected.
186 /// mii_selector.blocklist_guest_mii(Index::Index(1));
187 /// # }
188 /// ```
189 #[doc(alias = "miiSelectorBlacklistGuestMii")]
190 pub fn blocklist_guest_mii(&mut self, mii_index: Index) {
191 let index = match mii_index {
192 Index::Index(i) => i,
193 Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
194 };
195
196 unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) }
197 }
198
199 /// Allowlist a user-created Mii based on its index.
200 ///
201 /// # Example
202 ///
203 /// ```no_run
204 /// # fn main() {
205 /// #
206 /// use ctru::applets::mii_selector::{Index, MiiSelector};
207 /// let mut mii_selector = MiiSelector::new();
208 ///
209 /// // Allowlist the user-created Mii at index 0.
210 /// mii_selector.allowlist_user_mii(Index::Index(0));
211 /// # }
212 /// ```
213 #[doc(alias = "miiSelectorWhitelistUserMii")]
214 pub fn allowlist_user_mii(&mut self, mii_index: Index) {
215 let index = match mii_index {
216 Index::Index(i) => i,
217 Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
218 };
219
220 unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) }
221 }
222
223 /// Blocklist a user-created Mii based on its index.
224 ///
225 /// # Example
226 ///
227 /// ```no_run
228 /// # fn main() {
229 /// #
230 /// use ctru::applets::mii_selector::{Index, MiiSelector};
231 /// let mut mii_selector = MiiSelector::new();
232 ///
233 /// // Blocklist all user-created Miis so that they cannot be selected.
234 /// mii_selector.blocklist_user_mii(Index::All);
235 /// # }
236 /// ```
237 #[doc(alias = "miiSelectorBlacklistUserMii")]
238 pub fn blocklist_user_mii(&mut self, mii_index: Index) {
239 let index = match mii_index {
240 Index::Index(i) => i,
241 Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
242 };
243
244 unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
245 }
246
247 /// Set where the GUI cursor will start at.
248 ///
249 /// If there's no Mii at that index, the cursor will start at the Mii with the index 0.
250 #[doc(alias = "miiSelectorSetInitialIndex")]
251 pub fn set_initial_index(&mut self, index: usize) {
252 unsafe { ctru_sys::miiSelectorSetInitialIndex(self.config.as_mut(), index as u32) };
253 }
254
255 /// Launch the Mii Selector.
256 ///
257 /// Depending on the configuration, the Mii Selector window will appear either
258 /// on the bottom screen (default behaviour) or the top screen (see [`Options::USE_TOP_SCREEN`]).
259 ///
260 /// # Example
261 ///
262 /// ```no_run
263 /// # use std::error::Error;
264 /// # fn main() -> Result<(), Box<dyn Error>> {
265 /// # use ctru::services::{apt::Apt, gfx::Gfx};
266 /// #
267 /// # let gfx = Gfx::new().unwrap();
268 /// # let apt = Apt::new().unwrap();
269 /// #
270 /// use ctru::applets::mii_selector::{MiiSelector, Options};
271 ///
272 /// let mut mii_selector = MiiSelector::new();
273 /// mii_selector.set_title("Select a Mii!");
274 ///
275 /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS;
276 /// mii_selector.set_options(opts);
277 ///
278 /// let result = mii_selector.launch(&apt, &gfx)?;
279 /// #
280 /// # Ok(())
281 /// # }
282 /// ```
283 #[doc(alias = "miiSelectorLaunch")]
284 pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<Selection, Error> {
285 let mut return_val = Box::<ctru_sys::MiiSelectorReturn>::default();
286 unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) }
287
288 if return_val.no_mii_selected != 0 {
289 return Err(Error::NoMiiSelected);
290 }
291
292 if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } {
293 Ok((*return_val).into())
294 } else {
295 Err(Error::InvalidChecksum)
296 }
297 }
298}
299
300impl Default for MiiSelector {
301 fn default() -> Self {
302 Self::new()
303 }
304}
305
306impl fmt::Display for Error {
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 match self {
309 Self::InvalidChecksum => write!(f, "selected mii has invalid checksum"),
310 Self::NoMiiSelected => write!(f, "no mii was selected"),
311 }
312 }
313}
314
315impl std::error::Error for Error {}
316
317impl From<ctru_sys::MiiSelectorReturn> for Selection {
318 fn from(ret: ctru_sys::MiiSelectorReturn) -> Self {
319 let raw_mii_data = ret.mii;
320 let mut guest_mii_name = ret.guest_mii_name;
321
322 Selection {
323 mii_data: raw_mii_data.into(),
324 mii_type: if ret.guest_mii_index != 0xFFFFFFFF {
325 MiiType::Guest {
326 index: ret.guest_mii_index,
327 name: {
328 let utf16_be = &mut guest_mii_name;
329 utf16_be.reverse();
330 String::from_utf16(utf16_be.as_slice()).unwrap()
331 },
332 }
333 } else {
334 MiiType::User
335 },
336 }
337 }
338}
339
340impl From<u32> for Index {
341 fn from(v: u32) -> Self {
342 Self::Index(v)
343 }
344}