From ab8a440e281b3891b9cfba54e790a40b0c47cf08 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 19 Jul 2023 23:41:11 +0200 Subject: [PATCH] Finalize applets --- ctru-rs/examples/file-explorer.rs | 4 +- ctru-rs/examples/mii-selector.rs | 6 +- ctru-rs/examples/software-keyboard.rs | 6 +- ctru-rs/src/applets/mii_selector.rs | 187 +++++++++++----- ctru-rs/src/applets/mod.rs | 4 +- ctru-rs/src/applets/swkbd.rs | 311 +++++++++++++++++++++----- 6 files changed, 397 insertions(+), 121 deletions(-) diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 6d30590..748a937 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -1,7 +1,7 @@ //! A file explorer which shows off using standard library file system APIs to //! read the SD card. -use ctru::applets::swkbd::{Button, Swkbd}; +use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; use std::fs::DirEntry; @@ -159,7 +159,7 @@ impl<'a> FileExplorer<'a> { } fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) { - let mut keyboard = Swkbd::default(); + let mut keyboard = SoftwareKeyboard::default(); match keyboard.get_string(2048) { Ok((path, Button::Right)) => { diff --git a/ctru-rs/examples/mii-selector.rs b/ctru-rs/examples/mii-selector.rs index f6d3aa9..b12f217 100644 --- a/ctru-rs/examples/mii-selector.rs +++ b/ctru-rs/examples/mii-selector.rs @@ -1,4 +1,4 @@ -use ctru::applets::mii_selector::{LaunchError, MiiSelector, Options}; +use ctru::applets::mii_selector::{Error, MiiSelector, Options}; use ctru::prelude::*; fn main() { @@ -25,8 +25,8 @@ fn main() { result.mii_data.mole_details.is_enabled ); } - Err(LaunchError::InvalidChecksum) => println!("Corrupt Mii selected"), - Err(LaunchError::NoMiiSelected) => println!("No Mii selected"), + Err(Error::InvalidChecksum) => println!("Corrupt Mii selected"), + Err(Error::NoMiiSelected) => println!("No Mii selected"), } // Main loop diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 39b5549..4d7c4f2 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -1,4 +1,4 @@ -use ctru::applets::swkbd::{Button, Swkbd}; +use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; fn main() { @@ -18,9 +18,9 @@ fn main() { if hid.keys_down().contains(KeyPad::A) { // Prepares a software keyboard with two buttons: One to cancel input and one - // to accept it. You can also use `Swkbd::new()` to launch the keyboard in different + // to accept it. You can also use `SoftwareKeyboard::new()` to launch the keyboard in different // configurations. - let mut keyboard = Swkbd::default(); + let mut keyboard = SoftwareKeyboard::default(); // Raise the software keyboard. You can perform different actions depending on which // software button the user pressed diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index 2ccc42c..16e885c 100644 --- a/ctru-rs/src/applets/mii_selector.rs +++ b/ctru-rs/src/applets/mii_selector.rs @@ -1,16 +1,22 @@ //! Mii Selector applet. //! -//! This applet opens a window on the console's bottom screen which lets the player/user choose a Mii from the ones present on their console. +//! This applet opens a window which lets the player/user choose a Mii from the ones present on their console. //! The selected Mii is readable as a [`MiiData`](crate::mii::MiiData). use crate::mii::MiiData; use bitflags::bitflags; -use std::{error::Error, ffi::CString, fmt}; +use std::{ffi::CString, fmt}; -/// Index of a Mii on the Mii Selector interface. +/// Index of a Mii on the [`MiiSelector`] interface. +/// +/// See [`MiiSelector::whitelist_user_mii()`] and related functions for more information. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Index { /// Specific Mii index. + /// + /// # Notes + /// + /// Indexes start at 0. Index(u32), /// All Miis. All, @@ -31,64 +37,30 @@ pub enum MiiType { } bitflags! { - /// Options to configure the [MiiSelector]. - /// - ///

Example

- /// - /// ```no_run - /// # use std::error::Error; - /// # fn main() -> Result<(), Box> { - /// # - /// use ctru::applets::mii_selector::{MiiSelector, Options}; + /// Options to configure the [`MiiSelector`]. /// - /// // Setup a `MiiSelector` that can be cancelled and that makes Guest Miis available to select. - /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS; - /// - /// let mut mii_selector = MiiSelector::new(); - /// mii_selector.set_options(opts); - /// - /// let result = mii_selector.launch()?; - /// # - /// # Ok(()) - /// # } - /// ``` + /// See [`MiiSelector::set_options()`] to learn how to use them. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct Options: u32 { /// Show the cancel button. const ENABLE_CANCEL = ctru_sys::MIISELECTOR_CANCEL; /// Make guest Miis available to select. const ENABLE_GUESTS = ctru_sys::MIISELECTOR_GUESTS; - /// Show the Mii Selector window on the top screen. + /// Show the [`MiiSelector`] window on the top screen. const USE_TOP_SCREEN = ctru_sys::MIISELECTOR_TOP; - /// Start the Mii Selector on the guests' page. Requires [Options::ENABLE_GUESTS]. + /// Start the [`MiiSelector`] on the guests' page. Requires [`Options::ENABLE_GUESTS`]. const START_WITH_GUESTS = ctru_sys::MIISELECTOR_GUESTSTART; } } /// Configuration structure to setup the Mii Selector applet. -/// -/// # Example -/// ```no_run -/// # use std::error::Error; -/// # fn main() -> Result<(), Box> { -/// # -/// use ctru::applets::mii_selector::MiiSelector; -/// -/// let mut mii_selector = MiiSelector::new(); -/// mii_selector.set_title("Example Mii Selector"); -/// -/// let result = mii_selector.launch()?; -/// # -/// # Ok(()) -/// # } -/// ``` #[doc(alias = "MiiSelectorConf")] #[derive(Clone, Debug)] pub struct MiiSelector { config: Box, } -/// Return value of a successful [MiiSelector::launch()]. +/// Return value of a successful [`MiiSelector::launch()`]. #[non_exhaustive] #[derive(Clone, Debug)] pub struct Selection { @@ -98,12 +70,12 @@ pub struct Selection { pub mii_type: MiiType, } -/// Error returned by an unsuccessful [MiiSelector::launch()]. +/// Error returned by an unsuccessful [`MiiSelector::launch()`]. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum LaunchError { - /// The selected Mii's data is corrupt in some way. +pub enum Error { + /// The selected Mii's data is corrupt. InvalidChecksum, - /// Either the user cancelled the selection (see [Options::ENABLE_CANCEL]), or no valid Miis were available to select. + /// Either the user cancelled the selection (see [`Options::ENABLE_CANCEL`]) or no valid Miis were available to select. NoMiiSelected, } @@ -120,7 +92,18 @@ impl MiiSelector { /// Set the title of the Mii Selector window. /// + /// # Panics /// This function will panic if the given `&str` contains NUL bytes. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// use ctru::applets::mii_selector::MiiSelector; + /// + /// let mut mii_selector = MiiSelector::new(); + /// mii_selector.set_title("Select a Mii!"); + /// # } + /// ``` #[doc(alias = "miiSelectorSetTitle")] pub fn set_title(&mut self, text: &str) { // This can only fail if the text contains NUL bytes in the string... which seems @@ -133,13 +116,42 @@ impl MiiSelector { /// Set the options of the Mii Selector. /// - /// This will overwrite any previously saved options. + /// This will overwrite any previously saved options. Use bitwise operations to set all your wanted options at once. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// use ctru::applets::mii_selector::{MiiSelector, Options}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Setup a `MiiSelector` that can be cancelled and that makes Guest Miis available to select. + /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS; + /// mii_selector.set_options(opts); + /// # } + /// ``` #[doc(alias = "miiSelectorSetOptions")] pub fn set_options(&mut self, options: Options) { unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits()) } } /// Whitelist a guest Mii based on its index. + /// + /// # Notes + /// + /// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`]. + /// Look into [`MiiSelector::set_options()`] to see how to work with options. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Whitelist the guest Mii at index 2. + /// mii_selector.whitelist_guest_mii(Index::Index(2)); + /// # } + /// ``` #[doc(alias = "miiSelectorWhitelistGuestMii")] pub fn whitelist_guest_mii(&mut self, mii_index: Index) { let index = match mii_index { @@ -151,6 +163,23 @@ impl MiiSelector { } /// Blacklist a guest Mii based on its index. + /// + /// # Notes + /// + /// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`]. + /// Look into [`MiiSelector::set_options()`] to see how to work with options. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Blacklist the guest Mii at index 1 so that it cannot be selected. + /// mii_selector.blacklist_guest_mii(Index::Index(1)); + /// # } + /// ``` #[doc(alias = "miiSelectorBlacklistGuestMii")] pub fn blacklist_guest_mii(&mut self, mii_index: Index) { let index = match mii_index { @@ -161,7 +190,19 @@ impl MiiSelector { unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) } } - /// Whitelist a user Mii based on its index. + /// Whitelist a user-created Mii based on its index. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Whitelist the user-created Mii at index 0. + /// mii_selector.whitelist_user_mii(Index::Index(0)); + /// # } + /// ``` #[doc(alias = "miiSelectorWhitelistUserMii")] pub fn whitelist_user_mii(&mut self, mii_index: Index) { let index = match mii_index { @@ -172,7 +213,19 @@ impl MiiSelector { unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) } } - /// Blacklist a user Mii based on its index. + /// Blacklist a user-created Mii based on its index. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Blacklist all user-created Miis so that they cannot be selected. + /// mii_selector.blacklist_user_mii(Index::All); + /// # } + /// ``` #[doc(alias = "miiSelectorBlacklistUserMii")] pub fn blacklist_user_mii(&mut self, mii_index: Index) { let index = match mii_index { @@ -183,9 +236,10 @@ impl MiiSelector { unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) } } - /// Set where the cursor will start at. + /// Set where the GUI cursor will start at. /// /// If there's no Mii at that index, the cursor will start at the Mii with the index 0. + #[doc(alias = "miiSelectorSetInitialIndex")] pub fn set_initial_index(&mut self, index: usize) { // This function is static inline in libctru // https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155 @@ -194,20 +248,41 @@ impl MiiSelector { /// Launch the Mii Selector. /// - /// Depending on the configuration, the Mii Selector window will appear either on the bottom screen (default behaviour) or the top screen (see [Options::USE_TOP_SCREEN]). + /// Depending on the configuration, the Mii Selector window will appear either on the bottom screen (default behaviour) or the top screen (see [`Options::USE_TOP_SCREEN`]). + /// + /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// + /// # Example + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::applets::mii_selector::{MiiSelector, Options}; + /// + /// let mut mii_selector = MiiSelector::new(); + /// mii_selector.set_title("Select a Mii!"); + /// + /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS; + /// mii_selector.set_options(opts); + /// + /// let result = mii_selector.launch()?; + /// # + /// # Ok(()) + /// # } + /// ``` #[doc(alias = "miiSelectorLaunch")] - pub fn launch(&mut self) -> Result { + pub fn launch(&mut self) -> Result { let mut return_val = Box::::default(); unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) } if return_val.no_mii_selected != 0 { - return Err(LaunchError::NoMiiSelected); + return Err(Error::NoMiiSelected); } if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } { Ok((*return_val).into()) } else { - Err(LaunchError::InvalidChecksum) + Err(Error::InvalidChecksum) } } } @@ -218,7 +293,7 @@ impl Default for MiiSelector { } } -impl fmt::Display for LaunchError { +impl fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidChecksum => write!(f, "selected mii has invalid checksum"), @@ -227,7 +302,7 @@ impl fmt::Display for LaunchError { } } -impl Error for LaunchError {} +impl std::error::Error for Error {} impl From for Selection { fn from(ret: ctru_sys::MiiSelectorReturn) -> Self { diff --git a/ctru-rs/src/applets/mod.rs b/ctru-rs/src/applets/mod.rs index 15216fa..244fb74 100644 --- a/ctru-rs/src/applets/mod.rs +++ b/ctru-rs/src/applets/mod.rs @@ -4,7 +4,9 @@ //! Thanks to these integrations the developer can avoid wasting time re-implementing common features and instead use a more reliable base for their application. //! //! Unlike [services](crate::services), applets aren't accessed via a system subprocess (which would require obtaining a special handle at runtime). -//! Instead, the user builds a configuration storing the various parameters which is then used to "launch" the applet. +//! Instead, the application builds a configuration storing the various parameters which is then used to "launch" the applet. +//! +//! Applets block execution of the thread that launches them as long as the user doesn't close the applet. pub mod mii_selector; pub mod swkbd; diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 86f6357..b399fb0 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -1,23 +1,28 @@ //! Software Keyboard applet. //! -//! This applet opens a virtual keyboard on the console's bottom screen which lets the player/user write UTF-16 valid text. +//! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text. +// TODO: Implement remaining functionality (password mode, filter callbacks, etc.). Also improve "max text length" API. Improve `number of buttons` API when creating a new SoftwareKeyboard. +// TODO: Split the Parental PIN lock operations into a different type. use bitflags::bitflags; use ctru_sys::{ self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState, }; use libc; +use std::fmt::Display; use std::iter::once; use std::str; -/// An instance of the software keyboard. +/// Configuration structure to setup the Software Keyboard applet. #[doc(alias = "SwkbdState")] #[derive(Clone)] -pub struct Swkbd { +pub struct SoftwareKeyboard { state: Box, } -/// The kind of keyboard to be initialized. +/// The type of keyboard used by the [`SoftwareKeyboard`]. +/// +/// Can be set with [`SoftwareKeyboard::new()`] #[doc(alias = "SwkbdType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] @@ -28,13 +33,15 @@ pub enum Kind { Qwerty = ctru_sys::SWKBD_TYPE_QWERTY, /// Only number pad. Numpad = ctru_sys::SWKBD_TYPE_NUMPAD, - /// On JPN systems: a keyboard without japanese input capablities. + /// On JPN systems: a keyboard without japanese input capabilities. /// /// On any other region: same as [`Normal`](Kind::Normal). Western = ctru_sys::SWKBD_TYPE_WESTERN, } -/// Represents which button the user pressed to close the software keyboard. +/// Represents which button the user pressed to close the [`SoftwareKeyboard`]. +/// +/// Button text and behaviour can be customized with [`SoftwareKeyboard::configure_button()`]. #[doc(alias = "SwkbdButton")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] @@ -47,30 +54,37 @@ pub enum Button { Right = ctru_sys::SWKBD_BUTTON_RIGHT, } -/// Error type for the software keyboard. +/// Error returned by an unsuccessful [`SoftwareKeyboard::get_string()`]. #[doc(alias = "SwkbdResult")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(i32)] pub enum Error { - /// Invalid parameters inputted in the Software Keyboard. - InvalidInput = ctru_sys::SWKBD_INVALID_INPUT, - /// Out of memory. + /// Invalid parameters given to the [`SoftwareKeyboard`] configuration. + InvalidParameters = ctru_sys::SWKBD_INVALID_INPUT, + /// [`SoftwareKeyboard`] ran out of memory. OutOfMem = ctru_sys::SWKBD_OUTOFMEM, - /// Home button was pressed during execution. + /// Home button was pressed while [`SoftwareKeyboard`] was running. HomePressed = ctru_sys::SWKBD_HOMEPRESSED, - /// Reset button was pressed during execution. + /// Reset button was pressed while [`SoftwareKeyboard`] was running. ResetPressed = ctru_sys::SWKBD_RESETPRESSED, - /// Power button was pressed during execution. + /// Power button was pressed while [`SoftwareKeyboard`] was running. PowerPressed = ctru_sys::SWKBD_POWERPRESSED, - /// The parental PIN was correct. + /// The parental lock PIN was correct. + /// + /// While this variant isn't *technically* considerable an error + /// the result of a Parental PIN operation won't return a string to the program, thus it's still exceptional behaviour. ParentalOk = ctru_sys::SWKBD_PARENTAL_OK, - /// The parental PIN was incorrect. + /// The parental lock PIN was incorrect. ParentalFail = ctru_sys::SWKBD_PARENTAL_FAIL, /// Input triggered the filter. + /// + /// You can have a look at [`Filters`] to activate custom filters. BannedInput = ctru_sys::SWKBD_BANNED_INPUT, } -/// Restrictions on keyboard input. +/// Restrictions to enforce rules on the keyboard input. +/// +/// See [`SoftwareKeyboard::set_validation()`] #[doc(alias = "SwkbdValidInput")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] @@ -79,71 +93,113 @@ pub enum ValidInput { Anything = ctru_sys::SWKBD_ANYTHING, /// Empty inputs are not accepted. NotEmpty = ctru_sys::SWKBD_NOTEMPTY, - /// Blank (consisting only of whitespaces) inputs are not accepted. + /// Blank inputs (consisting only of whitespaces) are not accepted. NotBlank = ctru_sys::SWKBD_NOTBLANK, /// Neither empty inputs nor blank inputs are accepted. NotEmptyNotBlank = ctru_sys::SWKBD_NOTEMPTY_NOTBLANK, - /// Input must have a fixed length. Maximum length can be specified with [`Swkbd::set_max_text_len`]; + /// Input must have a fixed length. Maximum length can be specified with [`SoftwareKeyboard::set_max_text_len()`]; FixedLen = ctru_sys::SWKBD_FIXEDLEN, } bitflags! { - /// Keyboard feature flags. + /// Special features that can be activated via [`SoftwareKeyboard::set_features()`]. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct Features: u32 { /// Parental PIN mode. + /// + /// # Notes + /// + /// Refer to [`Error::ParentalOk`] and [`Error::ParentalFail`] to check whether the Parental PIN lock was successfully opened. const PARENTAL_PIN = ctru_sys::SWKBD_PARENTAL; - /// Darken top screen while the Software Keyboard is active. - const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN; + /// Darken top screen while the [`SoftwareKeyboard`] is active. + const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN; /// Enable predictive input (necessary for Kanji on JPN consoles). const PREDICTIVE_INPUT = ctru_sys::SWKBD_PREDICTIVE_INPUT; /// Enable multiline input. - const MULTILINE = ctru_sys::SWKBD_MULTILINE; + const MULTILINE = ctru_sys::SWKBD_MULTILINE; /// Enable fixed-width mode. const FIXED_WIDTH = ctru_sys::SWKBD_FIXED_WIDTH; - /// Allow the usage of the Home Button while the Software Keyboard is active. + /// Allow the usage of the Home Button while the [`SoftwareKeyboard`] is running. const ALLOW_HOME = ctru_sys::SWKBD_ALLOW_HOME; - /// Allow the usage of the Reset Button while the Software Keyboard is active. + /// Allow the usage of the Reset Button while the [`SoftwareKeyboard`] is running. const ALLOW_RESET = ctru_sys::SWKBD_ALLOW_RESET; - /// Allow the usage of the Power Button while the Software Keyboard is active. + /// Allow the usage of the Power Button while the [`SoftwareKeyboard`] is running. const ALLOW_POWER = ctru_sys::SWKBD_ALLOW_POWER; - /// Default to the QWERTY page when the Software Keyboard is shown. + /// Default to the QWERTY page when the [`SoftwareKeyboard`] is shown. const DEFAULT_QWERTY = ctru_sys::SWKBD_DEFAULT_QWERTY; } - /// Keyboard input filtering flags + /// Availble filters to disallow some types of input for the [`SoftwareKeyboard`]. + /// + /// See [`SoftwareKeyboard::set_validation()`] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct Filters: u32 { - /// Disallows the usage of numerical digits. + /// Disallow the usage of numerical digits. + /// + /// The maximum number of digits that are allowed to be used while this filter is active + /// can be configured with [`SoftwareKeyboard::set_max_digits()`] (default is 0). const DIGITS = ctru_sys::SWKBD_FILTER_DIGITS; - /// Disallows the usage of the "at" (@) sign. + /// Disallow the usage of the "at" (@) sign. const AT = ctru_sys::SWKBD_FILTER_AT; - /// Disallows the usage of the "percent" (%) sign. + /// Disallow the usage of the "percent" (%) sign. const PERCENT = ctru_sys::SWKBD_FILTER_PERCENT; - /// Disallows the usage of the "backslash" (\) sign. + /// Disallow the usage of the "backslash" (\) sign. const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH; - /// Disallows the use of profanity via Nintendo's profanity filter. + /// Disallow the use of profanity via Nintendo's profanity filter. const PROFANITY = ctru_sys::SWKBD_FILTER_PROFANITY; /// Use a custom callback in order to filter the input. + /// + /// TODO: It's currently impossible to setup a custom filter callback. const CALLBACK = ctru_sys::SWKBD_FILTER_CALLBACK; } } -impl Swkbd { - /// Initializes a software keyboard of the specified type and the chosen number of buttons - /// (from 1-3). +impl SoftwareKeyboard { + /// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3). + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Kind}; + /// + /// // Standard keyboard. + /// let keyboard = SoftwareKeyboard::new(Kind::Normal, 2); + /// + /// // Numpad (with only the "confirm" button). + /// let keyboard = SoftwareKeyboard::new(Kind::Numpad, 1); + /// # + /// # } #[doc(alias = "swkbdInit")] pub fn new(keyboard_type: Kind, num_buttons: i32) -> Self { unsafe { let mut state = Box::::default(); swkbdInit(state.as_mut(), keyboard_type.into(), num_buttons, -1); - Swkbd { state } + SoftwareKeyboard { state } } } - /// Gets input from this keyboard and appends it to the provided string. + /// Launches the applet based on the given configuration and returns a string containing the text input. + /// + /// # Notes /// /// The text received from the keyboard will be truncated if it is longer than `max_bytes`. + /// + /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// + /// # Example + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::applets::swkbd::SoftwareKeyboard; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// let (text, button) = keyboard.get_string(2048)?; + /// # + /// # Ok(()) + /// # } + /// ``` #[doc(alias = "swkbdInputText")] pub fn get_string(&mut self, max_bytes: usize) -> Result<(String, Button), Error> { // Unfortunately the libctru API doesn't really provide a way to get the exact length @@ -167,8 +223,28 @@ impl Swkbd { /// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from /// this software keyboard. /// + /// # Notes + /// /// If the buffer is too small to contain the entire sequence received from the keyboard, - /// the output will be truncated but should still be well-formed UTF-8. + /// the output will be truncated. + /// + /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// + /// # Example + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::applets::swkbd::SoftwareKeyboard; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// let mut buffer = vec![0; 100]; + /// + /// let button = keyboard.write_exact(&mut buffer)?; + /// # + /// # Ok(()) + /// # } + /// ``` #[doc(alias = "swkbdInputText")] pub fn write_exact(&mut self, buf: &mut [u8]) -> Result { unsafe { @@ -182,26 +258,85 @@ impl Swkbd { } } - /// Sets special features for this keyboard + /// Set special features for this keyboard. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Features}; + /// + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// let features = Features::DARKEN_TOP_SCREEN & Features::MULTILINE; + /// keyboard.set_features(features); + /// # + /// # } #[doc(alias = "swkbdSetFeatures")] pub fn set_features(&mut self, features: Features) { unsafe { swkbdSetFeatures(self.state.as_mut(), features.bits()) } } - /// Configures input validation for this keyboard + /// Configure input validation for this keyboard. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters}; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// // Disallow empty or blank input. + /// let validation = ValidInput::NotEmptyNotBlank; + /// + /// // Disallow the use of numerical digits and profanity. + /// let filters = Filters::DIGITS & Filters::PROFANITY; + /// keyboard.set_validation(validation, filters); + /// # + /// # } pub fn set_validation(&mut self, validation: ValidInput, filters: Filters) { self.state.valid_input = validation.into(); self.state.filter_flags = filters.bits(); } - /// Configures the maximum number of digits that can be entered in the keyboard when the - /// `Filters::DIGITS` flag is enabled + /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters}; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// // Disallow empty or blank input. + /// let validation = ValidInput::NotEmptyNotBlank; + /// + /// // Disallow the use of numerical digits. This filter is customizable thanks to `set_max_digits`. + /// let filters = Filters::DIGITS; + /// keyboard.set_validation(validation, filters); + /// + /// // No more than 3 numbers are accepted. + /// keyboard.set_max_digits(3); + /// # + /// # } pub fn set_max_digits(&mut self, digits: u16) { self.state.max_digits = digits; } - /// Sets the hint text for this software keyboard (that is, the help text that is displayed - /// when the textbox is empty) + /// Set the hint text for this software keyboard. + /// + /// The hint text is the text shown in gray before any text gets written in the input box. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::SoftwareKeyboard; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// keyboard.set_hint_text("Write here what you like!"); + /// # + /// # } #[doc(alias = "swkbdSetHintText")] pub fn set_hint_text(&mut self, text: &str) { unsafe { @@ -210,12 +345,30 @@ impl Swkbd { } } - /// Configures the look and behavior of a button for this keyboard. + /// Configure the look and behavior of a button for this keyboard. + /// + /// # Arguments + /// + /// - `button` - the [`Button`] to be configured based on the position. + /// - `text` - the text displayed in the button. + /// - `submit` - whether pressing the button will accept the keyboard's input or discard it. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind}; + /// + /// // We create a `SoftwareKeyboard` with left and right buttons. + /// let mut keyboard = SoftwareKeyboard::new(Kind::Normal, 2); /// - /// `button` is the `Button` to be configured - /// `text` configures the display text for the button - /// `submit` configures whether pressing the button will accept the keyboard's input or - /// discard it. + /// // Set the left button text to "Cancel" and pressing it will NOT return the user's input. + /// keyboard.configure_button(Button::Left, "Cancel", false); + /// + /// // Set the right button text to "Ok" and pressing it will return the user's input. + /// keyboard.configure_button(Button::Right, "Ok", true); + /// # + /// # } #[doc(alias = "swkbdSetButton")] pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) { unsafe { @@ -229,19 +382,33 @@ impl Swkbd { } } - /// Configures the maximum number of UTF-16 code units that can be entered into the software - /// keyboard. By default the limit is 0xFDE8 code units. + /// Configure the maximum number of UTF-16 code units that can be entered into the software + /// keyboard. By default the limit is `0xFDE8` code units. + /// + /// # Notes /// - /// Note that keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust, + /// Keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust, /// so this code point limit does not necessarily equal the max number of UTF-8 code points - /// receivable by the `get_utf8` and `get_bytes` functions. + /// receivable by [`SoftwareKeyboard::get_string()`] and [`SoftwareKeyboard::write_exact()`]. + /// + /// # Example + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind}; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// // Set the maximum text length to 18 UTF-16 code units. + /// keyboard.set_max_text_len(18); + /// # + /// # } pub fn set_max_text_len(&mut self, len: u16) { self.state.max_text_len = len; } fn parse_swkbd_error(&self) -> Error { match self.state.result { - ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidInput, + ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidParameters, ctru_sys::SWKBD_OUTOFMEM => Error::OutOfMem, ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed, ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed, @@ -254,12 +421,44 @@ impl Swkbd { } } -impl Default for Swkbd { +/// Creates a new [`SoftwareKeyboard`] configuration set to using a [`Kind::Normal`] keyboard and 2 [`Button`]s. +impl Default for SoftwareKeyboard { fn default() -> Self { - Swkbd::new(Kind::Normal, 2) + SoftwareKeyboard::new(Kind::Normal, 2) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidParameters => write!( + f, + "software keyboard was configured with invalid parameters" + ), + Self::OutOfMem => write!(f, "software keyboard ran out of memory"), + Self::HomePressed => { + write!(f, "home button pressed while software keyboard was running") + } + Self::ResetPressed => write!( + f, + "reset button pressed while software keyboard was running" + ), + Self::PowerPressed => write!( + f, + "power button pressed while software keyboard was running" + ), + Self::ParentalOk => write!(f, "parental lock pin was correct"), + Self::ParentalFail => write!(f, "parental lock pin was incorrect"), + Self::BannedInput => write!( + f, + "input given to the software keyboard triggered the active filters" + ), + } } } +impl std::error::Error for Error {} + from_impl!(Kind, ctru_sys::SwkbdType); from_impl!(Button, ctru_sys::SwkbdButton); from_impl!(Error, ctru_sys::SwkbdResult);