Browse Source

New filter callback

pull/149/head
Andrea Ciliberti 12 months ago
parent
commit
1413bfa201
  1. 20
      ctru-rs/examples/software-keyboard.rs
  2. 98
      ctru-rs/src/applets/swkbd.rs

20
ctru-rs/examples/software-keyboard.rs

@ -2,9 +2,12 @@
//! //!
//! This example showcases the use of the Software Keyboard applet to receive text input from the user. //! This example showcases the use of the Software Keyboard applet to receive text input from the user.
use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::applets::swkbd::{Button, CallbackResult, SoftwareKeyboard};
use ctru::prelude::*; use ctru::prelude::*;
use std::borrow::Cow;
use std::ffi::CString;
fn main() { fn main() {
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
@ -16,6 +19,21 @@ fn main() {
// with different configurations. // with different configurations.
let mut keyboard = SoftwareKeyboard::default(); let mut keyboard = SoftwareKeyboard::default();
// Custom filter callback to handle the given input.
// Using this callback it's possible to integrate the applet
// with custom error messages when the input is incorrect.
keyboard.set_filter_callback(Some(Box::new(|str| {
// The string is guaranteed to contain valid Unicode text, so we can safely unwrap and use it as a normal `&str`.
if str.to_str().unwrap().contains("boo") {
return (
CallbackResult::Retry,
Some(Cow::Owned(CString::new("Ah, you scared me!").unwrap())),
);
}
(CallbackResult::Ok, None)
})));
println!("Press A to enter some text or press Start to exit."); println!("Press A to enter some text or press Start to exit.");
while apt.main_loop() { while apt.main_loop() {

98
ctru-rs/src/applets/swkbd.rs

@ -8,16 +8,21 @@ use ctru_sys::{self, SwkbdState};
use bitflags::bitflags; use bitflags::bitflags;
use libc; use libc;
use std::ffi::{CStr, CString};
use std::borrow::Cow;
use std::ffi::CStr;
use std::fmt::Display; use std::fmt::Display;
use std::iter::once; use std::iter::once;
use std::str; use std::str;
type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option<Cow<CStr>>);
/// Configuration structure to setup the Software Keyboard applet. /// Configuration structure to setup the Software Keyboard applet.
#[doc(alias = "SwkbdState")] #[doc(alias = "SwkbdState")]
#[derive(Clone)] pub struct SoftwareKeyboard<'a> {
pub struct SoftwareKeyboard {
state: Box<SwkbdState>, state: Box<SwkbdState>,
callback: Option<Box<CallbackFunction>>,
error_message: Option<Cow<'a, CStr>>,
} }
/// Configuration structure to setup the Parental Lock applet. /// Configuration structure to setup the Parental Lock applet.
@ -202,7 +207,7 @@ bitflags! {
} }
} }
impl SoftwareKeyboard { 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). /// 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 /// # Example
@ -225,7 +230,11 @@ impl SoftwareKeyboard {
unsafe { unsafe {
let mut state = Box::<SwkbdState>::default(); let mut state = Box::<SwkbdState>::default();
ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1); ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1);
Self { state } Self {
state,
callback: None,
error_message: None,
}
} }
} }
@ -312,6 +321,13 @@ impl SoftwareKeyboard {
#[doc(alias = "swkbdInputText")] #[doc(alias = "swkbdInputText")]
pub fn write_exact(&mut self, buf: &mut [u8], _apt: &Apt, _gfx: &Gfx) -> Result<Button, Error> { pub fn write_exact(&mut self, buf: &mut [u8], _apt: &Apt, _gfx: &Gfx) -> Result<Button, Error> {
unsafe { unsafe {
// The filter callback gets reset every time the SoftwareKeyboard is used.
ctru_sys::swkbdSetFilterCallback(
self.state.as_mut(),
Some(Self::internal_callback),
(self as *mut Self).cast(),
);
match ctru_sys::swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) { match ctru_sys::swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) {
ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()), ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()),
ctru_sys::SWKBD_BUTTON_LEFT => Ok(Button::Left), ctru_sys::SWKBD_BUTTON_LEFT => Ok(Button::Left),
@ -369,11 +385,13 @@ impl SoftwareKeyboard {
/// Configure a custom filtering function to validate the input. /// Configure a custom filtering function to validate the input.
/// ///
/// The callback function will return a callback result and the error message to display when the input is invalid. /// The callback function must return a [`CallbackResult`] and the error message to display when the input is invalid.
/// ///
/// # Notes /// # Notes
/// ///
/// The filter callback will work only for the next time the keyboard is used. After using it once, it must be set again. /// Passing [`None`] will unbind the custom filter callback.
///
/// The error message returned by the callback should be shorter than `256` characters, otherwise it will be truncated.
/// ///
/// # Example /// # Example
/// ///
@ -384,52 +402,56 @@ impl SoftwareKeyboard {
/// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult}; /// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult};
/// let mut keyboard = SoftwareKeyboard::default(); /// let mut keyboard = SoftwareKeyboard::default();
/// ///
/// keyboard.set_filter_callback(|text| { /// keyboard.set_filter_callback(Some(Box::new(|str| {
/// if text.contains("boo") { /// if str.to_str().unwrap().contains("boo") {
/// println!("Ah, you scared me!"); /// return (
/// CallbackResult::Retry,
/// Some(Cow::Owned(CString::new("Ah, you scared me!").unwrap())),
/// );
/// } /// }
/// ///
/// (CallbackResult::Ok, None) /// (CallbackResult::Ok, None)
/// }); /// })));
/// # /// #
/// # } /// # }
pub fn set_filter_callback<F>(&mut self, callback: F) pub fn set_filter_callback(&mut self, callback: Option<Box<CallbackFunction>>) {
where self.callback = callback;
F: FnOnce(&str) -> (CallbackResult, Option<CString>), }
{
unsafe extern "C" fn internal_callback<F>( /// Internal function called by the filter callback.
extern "C" fn internal_callback(
user: *mut libc::c_void, user: *mut libc::c_void,
pp_message: *mut *const libc::c_char, pp_message: *mut *const libc::c_char,
text: *const libc::c_char, text: *const libc::c_char,
_text_size: libc::size_t, _text_size: libc::size_t,
) -> ctru_sys::SwkbdCallbackResult ) -> ctru_sys::SwkbdCallbackResult {
where let this: *mut SoftwareKeyboard = user.cast();
F: FnOnce(&str) -> (CallbackResult, Option<CString>),
{ unsafe {
let closure: Box<Box<F>> = Box::from_raw(user.cast()); // Reset any leftover error message.
(*this).error_message = None;
let text = CStr::from_ptr(text); let text = CStr::from_ptr(text);
let text_slice: &str = text.to_str().unwrap();
let result = closure(text_slice); let result = {
// Run the callback if still available.
if let Some(callback) = &mut (*this).callback {
let (res, cstr) = callback(text);
if let Some(cstr) = result.1 { (*this).error_message = cstr;
*pp_message = cstr.as_ptr();
Box::leak(Box::new(cstr)); // Definitely SHOULD NOT do this, but as far as design goes, it's clean.
}
result.0.into() if let Some(newstr) = &(*this).error_message {
*pp_message = newstr.as_ptr();
} }
let boxed_callback = Box::new(Box::new(callback)); res
} else {
unsafe { CallbackResult::Ok
ctru_sys::swkbdSetFilterCallback( }
self.state.as_mut(),
Some(internal_callback::<F>),
Box::into_raw(boxed_callback).cast(),
)
}; };
result.into()
}
} }
/// Configure 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.
@ -535,7 +557,7 @@ impl SoftwareKeyboard {
/// ///
/// # Notes /// # Notes
/// ///
/// If `None` is passed as either key, that button will not be shown to the user. /// If [`None`] is passed as either key, that button will not be shown to the user.
/// ///
/// # Example /// # Example
/// ///
@ -703,7 +725,7 @@ impl ParentalLock {
} }
/// Creates a new [`SoftwareKeyboard`] configuration set to using a [`Kind::Normal`] keyboard and 2 [`Button`]s. /// Creates a new [`SoftwareKeyboard`] configuration set to using a [`Kind::Normal`] keyboard and 2 [`Button`]s.
impl Default for SoftwareKeyboard { impl Default for SoftwareKeyboard<'_> {
fn default() -> Self { fn default() -> Self {
SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight) SoftwareKeyboard::new(Kind::Normal, ButtonConfig::LeftRight)
} }

Loading…
Cancel
Save