Skip to content

Implement Mii Selector applet #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
32db2c6
Initial mii selector implementation
Techie-Pi Oct 20, 2022
b33411f
Fix bug with author and impl ``From`` for ``MiiConfigIndex``
Techie-Pi Oct 20, 2022
4b43561
Add ``mii_selector`` example
Techie-Pi Oct 20, 2022
de51d97
Cargo fmt
Techie-Pi Oct 20, 2022
3c9bd8f
Implement MiiData parsing
Techie-Pi Oct 21, 2022
e6cc659
Rename Mii Selector example
Techie-Pi Oct 21, 2022
19ebd49
Add MiiData example
Techie-Pi Oct 21, 2022
0a6654a
Clippy
Techie-Pi Oct 21, 2022
1dbb576
Remove comment
Techie-Pi Oct 21, 2022
4b99e92
Cargo fmt & clippy
Techie-Pi Oct 21, 2022
1d92f0f
Apply suggestions
Techie-Pi Oct 23, 2022
fe0d03c
Fix Mii Selector example
Techie-Pi Oct 23, 2022
be87134
Fix ``RegionLock::None`` refactor
Techie-Pi Oct 23, 2022
7ca152a
Apply some suggestions
Techie-Pi Oct 24, 2022
a9569b5
Improve ``mii-selector`` example and a few fixes
Techie-Pi Oct 24, 2022
d7a6843
Clippy & cargo fmt
Techie-Pi Oct 24, 2022
dd5b9f7
Fix issue related to endianness and improve wording
Techie-Pi Oct 24, 2022
91f65a0
Add docs
Techie-Pi Oct 24, 2022
441ab93
Document values sometimes not being straight forward
Techie-Pi Oct 24, 2022
f8baff0
Cargo fmt
Techie-Pi Oct 24, 2022
1310507
Document ``set_title`` input and NUL bytes
Techie-Pi Oct 24, 2022
4d88ba9
Fix rustdoc link
Techie-Pi Oct 24, 2022
7c24404
Move some styles inside the their details struct for consistency
Techie-Pi Oct 24, 2022
16f8de4
Address comments
Techie-Pi Oct 26, 2022
accf99b
Apply cargo check suggestions
Techie-Pi Oct 26, 2022
01e9c70
Address comments
Techie-Pi Nov 2, 2022
42345d1
Rename a few variables and document skipped bits
Techie-Pi Nov 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ctru-rs/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ fn main() {
println!("cargo:rustc-cfg=romfs_exists");
}

println!("cargo:rerun-if-changed={}", manifest_dir);
println!("cargo:rerun-if-changed={manifest_dir}");
}
14 changes: 7 additions & 7 deletions ctru-rs/examples/file-explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl<'a> FileExplorer<'a> {
}
}
Err(e) => {
println!("Failed to read {}: {}", self.path.display(), e)
println!("Failed to read {}: {e}", self.path.display())
}
};

Expand All @@ -110,15 +110,15 @@ impl<'a> FileExplorer<'a> {
for (i, entry) in dir_listing.enumerate() {
match entry {
Ok(entry) => {
println!("{:2} - {}", i, entry.file_name().to_string_lossy());
println!("{i:2} - {}", entry.file_name().to_string_lossy());
self.entries.push(entry);

if (i + 1) % 20 == 0 {
self.wait_for_page_down();
}
}
Err(e) => {
println!("{} - Error: {}", i, e);
println!("{i} - Error: {e}");
}
}
}
Expand All @@ -132,7 +132,7 @@ impl<'a> FileExplorer<'a> {
println!("{0:->80}", "");
}
Err(err) => {
println!("Error reading file: {}", err);
println!("Error reading file: {err}");
}
}
}
Expand Down Expand Up @@ -175,7 +175,7 @@ impl<'a> FileExplorer<'a> {
unreachable!()
}
Err(e) => {
panic!("Error: {:?}", e)
panic!("Error: {e:?}")
}
}
}
Expand All @@ -184,7 +184,7 @@ impl<'a> FileExplorer<'a> {
let next_path_index: usize = match next_path_index.parse() {
Ok(index) => index,
Err(e) => {
println!("Number parsing error: {}", e);
println!("Number parsing error: {e}");
return;
}
};
Expand All @@ -205,7 +205,7 @@ impl<'a> FileExplorer<'a> {
fn set_exact_path(&mut self, new_path_str: String) {
let new_path = Path::new(&new_path_str);
if !new_path.is_dir() {
println!("Not a directory: {}", new_path_str);
println!("Not a directory: {new_path_str}");
return;
}

Expand Down
2 changes: 1 addition & 1 deletion ctru-rs/examples/hashmaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn main() {
map.insert("Another key?", 543);
map.remove("A Key!");

println!("{:#?}", map);
println!("{map:#?}");

while apt.main_loop() {
gfx.flush_buffers();
Expand Down
43 changes: 43 additions & 0 deletions ctru-rs/examples/mii-selector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use ctru::applets::mii_selector::MiiSelector;
use ctru::prelude::*;

fn main() {
ctru::init();

let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller");
let _console = Console::init(gfx.top_screen.borrow_mut());

let mut mii_selector = MiiSelector::init();
mii_selector.set_initial_index(3);
mii_selector.blacklist_user_mii(0.into());
mii_selector.set_title("Great Mii Selector!");

let result = mii_selector.launch().unwrap();

println!("Is Mii selected?: {:?}", result.is_mii_selected);
println!("Mii type: {:?}", result.mii_type);
println!("Name: {:?}", result.mii_data.name);
println!("Author: {:?}", result.mii_data.author_name);
println!(
"Does the Mii have moles?: {:?}",
result.mii_data.mole_details.is_enabled
);

// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();

if hid.keys_down().contains(KeyPad::KEY_START) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();

//Wait for VBlank
gfx.wait_for_vblank();
}
}
10 changes: 5 additions & 5 deletions ctru-rs/examples/network-sockets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,35 @@ fn main() {

match server.accept() {
Ok((mut stream, socket_addr)) => {
println!("Got connection from {}", socket_addr);
println!("Got connection from {socket_addr}");

let mut buf = [0u8; 4096];
match stream.read(&mut buf) {
Ok(_) => {
let req_str = String::from_utf8_lossy(&buf);
println!("{}", req_str);
println!("{req_str}");
}
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
println!("Note: Reading the connection returned ErrorKind::WouldBlock.")
} else {
println!("Unable to read stream: {}", e)
println!("Unable to read stream: {e}")
}
}
}

let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n";

if let Err(e) = stream.write(response) {
println!("Error writing http response: {}", e);
println!("Error writing http response: {e}");
}

stream.shutdown(Shutdown::Both).unwrap();
}
Err(e) => match e.kind() {
std::io::ErrorKind::WouldBlock => {}
_ => {
println!("Error accepting connection: {}", e);
println!("Error accepting connection: {e}");
std::thread::sleep(Duration::from_secs(2));
}
},
Expand Down
2 changes: 1 addition & 1 deletion ctru-rs/examples/software-keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn main() {
// Raise the software keyboard. You can perform different actions depending on which
// software button the user pressed
match keyboard.get_utf8(&mut text) {
Ok(Button::Right) => println!("You entered: {}", text),
Ok(Button::Right) => println!("You entered: {text}"),
Ok(Button::Left) => println!("Cancelled"),
Ok(Button::Middle) => println!("How did you even press this?"),
Err(_) => println!("Oh noes, an error happened!"),
Expand Down
4 changes: 2 additions & 2 deletions ctru-rs/examples/time-rtc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ fn main() {
let day = cur_time.day();
let year = cur_time.year();

println!("\x1b[1;1H{:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds);
println!("{} {} {} {}", weekday, month, day, year);
println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}");
println!("{weekday} {month} {day} {year}");

// Flush and swap framebuffers
gfx.flush_buffers();
Expand Down
186 changes: 186 additions & 0 deletions ctru-rs/src/applets/mii_selector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! Mii Selector applet
//!
//! This module contains the methods to launch the Mii Selector.

use crate::mii::MiiData;
use bitflags::bitflags;
use std::ffi::CString;

/// Index of a Mii used to configure some parameters of the Mii Selector
/// Can be either a single index, or _all_ Miis
#[derive(Debug, Clone)]
pub enum MiiConfigIndex {
Index(u32),
All,
}

/// The type of a Mii with their respective data
#[derive(Debug, Clone)]
pub enum MiiType {
Guest { index: u32, name: String },
User,
}

bitflags! {
/// Options for the Mii Selector
pub struct Options: u32 {
/// Show the cancel button
const MII_SELECTOR_CANCEL = ctru_sys::MIISELECTOR_CANCEL;
/// Make guest Miis selectable
const MII_SELECTOR_GUESTS = ctru_sys::MIISELECTOR_GUESTS;
/// Show on the top screen
const MII_SELECTOR_TOP = ctru_sys::MIISELECTOR_TOP;
/// Start on the guest's page
const MII_SELECTOR_GUEST_START = ctru_sys::MIISELECTOR_GUESTSTART;
}
}

/// An instance of the Mii Selector
///
/// # Example
/// ```
/// use ctru::applets::mii_selector::MiiSelector;
///
/// let mut mii_selector = MiiSelector::init();
/// mii_selector.set_title("Example Mii selector");
///
/// let result = mii_selector.launch().unwrap();
/// ```
#[derive(Clone, Debug)]
pub struct MiiSelector {
config: Box<ctru_sys::MiiSelectorConf>,
}

/// Return value from a MiiSelector's launch
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct MiiSelectorReturn {
pub mii_data: MiiData,
pub is_mii_selected: bool,
pub mii_type: MiiType,
}

/// Error type for the Mii selector
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MiiLaunchError {
InvalidChecksum,
}

impl MiiSelector {
/// Initializes a Mii Selector
pub fn init() -> Self {
let mut config = Box::<ctru_sys::MiiSelectorConf>::default();
unsafe {
ctru_sys::miiSelectorInit(config.as_mut());
}
Self { config }
}

/// Set the title of the Mii Selector.
///
/// This function would panic if the given ``&str`` contains NUL bytes.
pub fn set_title(&mut self, text: &str) {
// This can only fail if the text contains NUL bytes in the string... which seems
// unlikely and is documented
let c_text = CString::new(text).expect("Failed to convert the title text into a CString");
unsafe {
ctru_sys::miiSelectorSetTitle(self.config.as_mut(), c_text.as_ptr());
}
}

/// Set the options of the Mii Selector
pub fn set_options(&mut self, options: Options) {
unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits) }
}

/// Whitelist a guest Mii
pub fn whitelist_guest_mii(&mut self, mii_index: MiiConfigIndex) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
};

unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) }
}

/// Blacklist a guest Mii
pub fn blacklist_guest_mii(&mut self, mii_index: MiiConfigIndex) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
};

unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) }
}

/// Whitelist a user Mii
pub fn whitelist_user_mii(&mut self, mii_index: MiiConfigIndex) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
};

unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) }
}

/// Blacklist a user Mii
pub fn blacklist_user_mii(&mut self, mii_index: MiiConfigIndex) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
};

unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
}

/// Set where the cursor will be.
/// If there's no Mii at that index, the cursor will start at the Mii with the index 0
pub fn set_initial_index(&mut self, index: u32) {
// This function is static inline in libctru
// https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155
self.config.initial_index = index
}

/// Launch the Mii Selector.
/// Returns an error when the checksum of the Mii is invalid.
pub fn launch(&mut self) -> Result<MiiSelectorReturn, MiiLaunchError> {
let mut return_val = Box::<ctru_sys::MiiSelectorReturn>::default();
unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) }

if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } {
Ok((*return_val).into())
} else {
Err(MiiLaunchError::InvalidChecksum)
}
}
}

impl From<ctru_sys::MiiSelectorReturn> for MiiSelectorReturn {
fn from(ret: ctru_sys::MiiSelectorReturn) -> Self {
let raw_mii_data = ret.mii;
let mut guest_mii_name = ret.guest_mii_name;

MiiSelectorReturn {
mii_data: raw_mii_data.into(),
is_mii_selected: ret.no_mii_selected == 0,
mii_type: if ret.guest_mii_index != 0xFFFFFFFF {
MiiType::Guest {
index: ret.guest_mii_index,
name: {
let utf16_be = &mut guest_mii_name;
utf16_be.reverse();
String::from_utf16(utf16_be.as_slice()).unwrap()
},
}
} else {
MiiType::User
},
}
}
}

impl From<u32> for MiiConfigIndex {
fn from(v: u32) -> Self {
Self::Index(v)
}
}
1 change: 1 addition & 0 deletions ctru-rs/src/applets/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod mii_selector;
pub mod swkbd;
6 changes: 3 additions & 3 deletions ctru-rs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl fmt::Debug for Error {
match self {
&Self::Os(err) => f
.debug_struct("Error")
.field("raw", &format_args!("{:#08X}", err))
.field("raw", &format_args!("{err:#08X}"))
.field("description", &R_DESCRIPTION(err))
.field("module", &R_MODULE(err))
.field("summary", &R_SUMMARY(err))
Expand All @@ -106,8 +106,8 @@ impl fmt::Debug for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Self::Os(err) => write!(f, "libctru result code: 0x{:08X}", err),
Self::Libc(err) => write!(f, "{}", err),
&Self::Os(err) => write!(f, "libctru result code: 0x{err:08X}"),
Self::Libc(err) => write!(f, "{err}"),
Self::ServiceAlreadyActive => write!(f, "Service already active"),
Self::OutputAlreadyRedirected => {
write!(f, "output streams are already redirected to 3dslink")
Expand Down
Loading