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}