From 9c955441dc6d5ce6b96b37dbe2520a4295e0abd3 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Thu, 27 Jul 2023 19:55:11 +0200 Subject: [PATCH] Finalize examples --- ctru-rs/examples/audio-filters.rs | 35 +++++++++++---- ctru-rs/examples/buttons.rs | 24 +++++----- ctru-rs/examples/camera-image.rs | 22 ++++++--- ctru-rs/examples/file-explorer.rs | 20 ++++++--- ctru-rs/examples/gfx-3d-mode.rs | 26 ++++++----- ctru-rs/examples/gfx-bitmap.rs | 29 ++++++------ ctru-rs/examples/gfx-wide-mode.rs | 12 +++++ ctru-rs/examples/graphics-bitmap.rs | 57 ------------------------ ctru-rs/examples/hashmaps.rs | 11 ++++- ctru-rs/examples/hello-both-screens.rs | 11 +++-- ctru-rs/examples/hello-world.rs | 20 +++++++-- ctru-rs/examples/linear-memory.rs | 15 ++++--- ctru-rs/examples/mii-selector.rs | 13 ++++-- ctru-rs/examples/network-sockets.rs | 21 ++++++--- ctru-rs/examples/output-3dslink.rs | 9 ++-- ctru-rs/examples/romfs.rs | 20 +++++---- ctru-rs/examples/software-keyboard.rs | 20 ++++++--- ctru-rs/examples/system-configuration.rs | 14 ++++-- ctru-rs/examples/thread-basic.rs | 2 + ctru-rs/examples/thread-info.rs | 2 +- ctru-rs/examples/thread-locals.rs | 2 +- ctru-rs/examples/time-rtc.rs | 25 ++++------- ctru-rs/examples/title-info.rs | 30 +++++++++---- ctru-rs/examples/touch-screen.rs | 14 +++--- ctru-rs/src/services/gfx.rs | 4 ++ 25 files changed, 267 insertions(+), 191 deletions(-) delete mode 100644 ctru-rs/examples/graphics-bitmap.rs diff --git a/ctru-rs/examples/audio-filters.rs b/ctru-rs/examples/audio-filters.rs index 34fa470..75941b7 100644 --- a/ctru-rs/examples/audio-filters.rs +++ b/ctru-rs/examples/audio-filters.rs @@ -1,3 +1,7 @@ +//! Audio Filters example. +//! +//! This example showcases basic audio functionality using [`Ndsp`]. + #![feature(allocator_api)] use std::f32::consts::PI; @@ -9,17 +13,18 @@ use ctru::services::ndsp::{ AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode, }; +// Configuration for the NDSP process and channels. const SAMPLE_RATE: usize = 22050; const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205 const BYTES_PER_SAMPLE: usize = AudioFormat::PCM16Stereo.size(); const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE; -// Note Frequencies +// Note frequencies. const NOTEFREQ: [f32; 7] = [220., 440., 880., 1760., 3520., 7040., 14080.]; -// The audio format is Stereo PCM16 -// As such, a sample is made up of 2 "Mono" samples (2 * i16 = u32), one for each channel (left and right) fn fill_buffer(audio_data: &mut [u8], frequency: f32) { + // The audio format is Stereo PCM16. + // As such, a sample is made up of 2 "Mono" samples (2 * i16), one for each channel (left and right). let formatted_data = bytemuck::cast_slice_mut::<_, [i16; 2]>(audio_data); for (i, chunk) in formatted_data.iter_mut().enumerate() { @@ -44,8 +49,7 @@ fn main() { let mut note: usize = 4; - // Filters - + // Filter names to display. let filter_names = [ "None", "Low-Pass", @@ -60,19 +64,26 @@ fn main() { // We set up two wave buffers and alternate between the two, // effectively streaming an infinitely long sine wave. + // We create a buffer on the LINEAR memory that will hold our audio data. + // It's necessary for the buffer to live on the LINEAR memory sector since it needs to be accessed by the DSP processor. let mut audio_data1 = Box::new_in([0u8; AUDIO_WAVE_LENGTH], LinearAllocator); + + // Fill the buffer with the first set of data. This simply writes a sine wave into the buffer. fill_buffer(audio_data1.as_mut_slice(), NOTEFREQ[4]); + // Clone the original buffer to obtain an equal buffer on the LINEAR memory used for double buffering. let audio_data2 = audio_data1.clone(); + // Setup two wave info objects with the correct configuration and ownership of the audio data. let mut wave_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false); let mut wave_info2 = Wave::new(audio_data2, AudioFormat::PCM16Stereo, false); - let mut ndsp = Ndsp::new().expect("Couldn't obtain NDSP controller"); + // Setup the NDSP service and its configuration. - // This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo` + let mut ndsp = Ndsp::new().expect("Couldn't obtain NDSP controller"); ndsp.set_output_mode(OutputMode::Stereo); + // Channel configuration. We use channel zero but any channel would do just fine. let mut channel_zero = ndsp.channel(0).unwrap(); channel_zero.set_interpolation(InterpolationType::Linear); channel_zero.set_sample_rate(SAMPLE_RATE as f32); @@ -82,6 +93,7 @@ fn main() { let mix = AudioMix::default(); channel_zero.set_mix(&mix); + // First set of queueing for the two buffers. The second one will only play after the first one has ended. channel_zero.queue_wave(&mut wave_info1).unwrap(); channel_zero.queue_wave(&mut wave_info2).unwrap(); @@ -93,6 +105,8 @@ fn main() { filter_names[filter as usize] ); + println!("\x1b[29;16HPress Start to exit"); + let mut altern = true; // true is wave_info1, false is wave_info2 while apt.main_loop() { @@ -101,14 +115,16 @@ fn main() { if keys_down.contains(KeyPad::START) { break; - } // break in order to return to hbmenu + } + // Note frequency controller using the buttons. if keys_down.intersects(KeyPad::DOWN) { note = note.saturating_sub(1); } else if keys_down.intersects(KeyPad::UP) { note = std::cmp::min(note + 1, NOTEFREQ.len() - 1); } + // Filter controller using the buttons. let mut update_params = false; if keys_down.intersects(KeyPad::LEFT) { filter -= 1; @@ -139,12 +155,14 @@ fn main() { } } + // Double buffer alternation depending on the one used. let current: &mut Wave = if altern { &mut wave_info1 } else { &mut wave_info2 }; + // If the current buffer has finished playing, we can refill it with new data and re-queue it. let status = current.status(); if let Status::Done = status { fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); @@ -154,7 +172,6 @@ fn main() { altern = !altern; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 8bba6c7..0de9e7c 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -1,3 +1,7 @@ +//! Buttons example. +//! +//! This example showcases how to retrieve button inputs from the console's HID. + use ctru::prelude::*; fn main() { @@ -18,28 +22,28 @@ fn main() { // Scan for user input on the current frame. hid.scan_input(); - // Get information about which keys were held down on this frame + // Get information about which keys were held down on this frame. let keys = hid.keys_held(); // We only want to print when the keys we're holding now are different - // from what they were on the previous frame + // from what they were on the previous frame. if keys != old_keys { - // Clear the screen + // Clear the screen. console.clear(); - // We print these again because we just cleared the screen above + // We print these again because we just cleared the screen above. println!("Hi there! Try pressing a button"); println!("\x1b[29;16HPress Start to exit"); - // Move the cursor back to the top of the screen + // Move the cursor back to the top of the screen. println!("\x1b[3;0H"); // Print to the screen depending on which keys were held. // - // The .contains() method checks for all of the provided keys, - // and the .intersects() method checks for any of the provided keys. + // The `.contains()` method checks for all of the provided keys, + // and the `.intersects()` method checks for any of the provided keys. // - // You can also use the .bits() method to do direct comparisons on + // You can also use the `.bits()` method to do direct comparisons on // the underlying bits if keys.contains(KeyPad::A) { @@ -54,13 +58,13 @@ fn main() { if keys.intersects(KeyPad::L | KeyPad::R | KeyPad::ZL | KeyPad::ZR) { println!("You held a shoulder button!"); } - if keys.intersects(KeyPad::START) { + if keys.contains(KeyPad::START) { println!("See ya!"); break; } } - // Save our current key presses for the next frame + // Save our current key presses for the next frame. old_keys = keys; gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 0042599..40c5f75 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -1,3 +1,7 @@ +//! Camera image example. +//! +//! This example demonstrates how to use the built-in cameras to take a picture and display it to the screen. + use ctru::prelude::*; use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize}; use ctru::services::gfx::{Flush, Screen, Swap}; @@ -8,7 +12,7 @@ use std::time::Duration; const WIDTH: usize = 400; const HEIGHT: usize = 240; -// The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes) +// The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes). const BUF_SIZE: usize = WIDTH * HEIGHT * 2; const WAIT_TIMEOUT: Duration = Duration::from_millis(300); @@ -26,12 +30,11 @@ fn main() { let _console = Console::new(gfx.bottom_screen.borrow_mut()); - let mut keys_down; - println!("Initializing camera"); let mut cam = Cam::new().expect("Failed to initialize CAM service."); + // Camera setup. { let camera = &mut cam.outer_right_cam; @@ -58,21 +61,23 @@ fn main() { let mut buf = vec![0u8; BUF_SIZE]; println!("\nPress R to take a new picture"); - println!("Press Start to exit to Homebrew Launcher"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { hid.scan_input(); - keys_down = hid.keys_down(); + let keys_down = hid.keys_down(); if keys_down.contains(KeyPad::START) { break; } + // If the user presses the R button. if keys_down.contains(KeyPad::R) { println!("Capturing new image"); let camera = &mut cam.outer_right_cam; + // Take a picture and write it to the buffer. camera .take_picture( &mut buf, @@ -82,12 +87,14 @@ fn main() { ) .expect("Failed to take picture"); + // Play the normal shutter sound. cam.play_shutter_sound(ShutterSound::Normal) .expect("Failed to play shutter sound"); + // Rotate the image and correctly display it on the screen. rotate_image_to_screen(&buf, top_screen.raw_framebuffer().ptr, WIDTH, HEIGHT); - // We will only flush the "camera" screen, since the other screen is handled by `Console` + // We will only flush and swap the "camera" screen, since the other screen is handled by the `Console`. top_screen.flush_buffers(); top_screen.swap_buffers(); @@ -99,6 +106,7 @@ fn main() { // The 3DS' screens are 2 vertical LCD panels rotated by 90 degrees. // As such, we'll need to write a "vertical" image to the framebuffer to have it displayed properly. // This functions rotates an horizontal image by 90 degrees to the right. +// This function is only supposed to be used in this example. In a real world application, the program should use the GPU to draw to the screen. fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: usize) { for j in 0..height { for i in 0..width { @@ -115,7 +123,7 @@ fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: u let draw_index = (draw_x * height + draw_y) * 2; // This 2 stands for the number of bytes per pixel (16 bits) unsafe { - // We'll work with pointers since the frambuffer is a raw pointer regardless. + // We'll work with pointers since the framebuffer is a raw pointer regardless. // The offsets are completely safe as long as the width and height are correct. let pixel_pointer = framebuf.offset(draw_index as isize); pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2); diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 748a937..4a7fbda 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -1,5 +1,7 @@ -//! A file explorer which shows off using standard library file system APIs to -//! read the SD card. +//! File Explorer example. +//! +//! This (rather complex) example creates a working text-based file explorer which shows off using standard library file system APIs to +//! read the SD card and RomFS (if properly read via the `romfs:/` prefix). use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; @@ -15,6 +17,7 @@ fn main() { let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); + // Mount the RomFS if available. #[cfg(all(feature = "romfs", romfs_exists))] let _romfs = ctru::services::romfs::RomFS::new().unwrap(); @@ -50,6 +53,7 @@ impl<'a> FileExplorer<'a> { fn run(&mut self) { self.running = true; + // Print the file explorer commands. self.print_menu(); while self.running && self.apt.main_loop() { @@ -62,8 +66,10 @@ impl<'a> FileExplorer<'a> { self.path.pop(); self.console.clear(); self.print_menu(); + // Open a directory/file to read. } else if input.contains(KeyPad::A) { self.get_input_and_run(Self::set_next_path); + // Open a specific path using the `SoftwareKeyboard` applet. } else if input.contains(KeyPad::X) { self.get_input_and_run(Self::set_exact_path); } @@ -100,7 +106,7 @@ impl<'a> FileExplorer<'a> { } }; - println!("Start to exit, A to select an entry by number, B to go up a directory, X to set the path."); + println!("Press Start to exit, A to select an entry by number, B to go up a directory, X to set the path."); } fn print_dir_entries(&mut self) { @@ -137,7 +143,7 @@ impl<'a> FileExplorer<'a> { } } - /// Paginate output + /// Paginate output.' fn wait_for_page_down(&mut self) { println!("Press A to go to next page, or Start to exit"); @@ -163,14 +169,14 @@ impl<'a> FileExplorer<'a> { match keyboard.get_string(2048) { Ok((path, Button::Right)) => { - // Clicked "OK" + // Clicked "OK". action(self, path); } Ok((_, Button::Left)) => { - // Clicked "Cancel" + // Clicked "Cancel". } Ok((_, Button::Middle)) => { - // This button wasn't shown + // This button wasn't shown. unreachable!() } Err(e) => { diff --git a/ctru-rs/examples/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs index 582994e..c5b9de8 100644 --- a/ctru-rs/examples/gfx-3d-mode.rs +++ b/ctru-rs/examples/gfx-3d-mode.rs @@ -1,10 +1,15 @@ +//! 3D Graphics example. +//! +//! This example showcases 3D mode rendering (using the CPU). +//! In a normal application, all rendering should be hanlded via the GPU. + use ctru::prelude::*; use ctru::services::gfx::{Flush, Screen, Side, Swap, TopScreen3D}; -/// See `graphics-bitmap.rs` for details on how the image is generated. -/// -/// WARNING: this example uses 3D mode in a rather unnatural way, and should -/// probably not be viewed for too long or at all if you are photosensitive. +// See `graphics-bitmap.rs` for details on how the image is generated. +// +// WARNING: this example uses 3D mode in a rather unnatural way, and should +// probably not be viewed for too long or at all if you are photosensitive. const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); static ZERO: &[u8] = &[0; IMAGE.len()]; @@ -17,7 +22,8 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.bottom_screen.borrow_mut()); - println!("Press Start to exit.\nPress A to switch sides (be sure to have 3D mode enabled)."); + println!("Press A to switch sides (be sure to have set the 3D slider correctly)."); + println!("\x1b[29;16HPress Start to exit"); gfx.top_screen.borrow_mut().set_double_buffering(true); @@ -25,39 +31,40 @@ fn main() { let mut current_side = Side::Left; - // 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::START) { break; } + // Split the TopScreen3D to get references to the two render surfaces. let (mut left, mut right) = top_screen.split_mut(); let left_buf = left.raw_framebuffer(); let right_buf = right.raw_framebuffer(); - // Clear both buffers every time, in case the user switches sides this loop + // Clear both buffers every time, in case the user switches sides this loop. unsafe { left_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); right_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); } if hid.keys_down().contains(KeyPad::A) { - // flip which buffer we're writing to + // Switch which buffer we're writing to. current_side = match current_side { Side::Left => Side::Right, Side::Right => Side::Left, }; } + // Obtain the framebuffer of the currently rendered side. let buf = match current_side { Side::Left => left_buf.ptr, Side::Right => right_buf.ptr, }; + // Render the image to the surface's buffer. unsafe { buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); } @@ -67,7 +74,6 @@ fn main() { top_screen.flush_buffers(); top_screen.swap_buffers(); - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/gfx-bitmap.rs b/ctru-rs/examples/gfx-bitmap.rs index f680ff2..39634c2 100644 --- a/ctru-rs/examples/gfx-bitmap.rs +++ b/ctru-rs/examples/gfx-bitmap.rs @@ -1,3 +1,6 @@ +/// Bitmap Graphics example. +/// +/// This example uses the CPU to render a simple bitmap image to the screen. use ctru::prelude::*; use ctru::services::gfx::{Flush, Screen, Swap}; @@ -22,7 +25,8 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); - println!("\x1b[21;4HPress Start to exit, or A to flip the image."); + println!("\x1b[21;4HPress A to flip the image."); + println!("\x1b[29;16HPress Start to exit"); let mut bottom_screen = gfx.bottom_screen.borrow_mut(); @@ -37,18 +41,23 @@ fn main() { let mut image_bytes = IMAGE; - // Main loop + // We assume the image is the correct size already, so we drop width + height. + let frame_buffer = bottom_screen.raw_framebuffer(); + + // We copy the image to the framebuffer. + unsafe { + frame_buffer + .ptr + .copy_from(image_bytes.as_ptr(), image_bytes.len()); + } + 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::START) { break; } - // We assume the image is the correct size already, so we drop width + height. - let frame_buffer = bottom_screen.raw_framebuffer(); - if hid.keys_down().contains(KeyPad::A) { image_bytes = if std::ptr::eq(image_bytes, IMAGE) { &flipped_image[..] @@ -57,18 +66,10 @@ fn main() { }; } - // this copies more than necessary (once per frame) but it's fine... - unsafe { - frame_buffer - .ptr - .copy_from(image_bytes.as_ptr(), image_bytes.len()); - } - // Flush framebuffers. Since we're not using double buffering, // this will render the pixels immediately bottom_screen.flush_buffers(); - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/gfx-wide-mode.rs b/ctru-rs/examples/gfx-wide-mode.rs index 8036538..109169d 100644 --- a/ctru-rs/examples/gfx-wide-mode.rs +++ b/ctru-rs/examples/gfx-wide-mode.rs @@ -1,3 +1,10 @@ +//! Wide-Mode Graphics example. +//! +//! This example demonstrates the wide-mode capability of the top screen +//! which doubles the horizontal resolution of the screen by merging the 2 stereoscopic 3D sides. +//! +//! Beware, wide-mode doesn't work on Old 2DS consoles. + use ctru::prelude::*; fn main() { @@ -9,6 +16,7 @@ fn main() { let mut console = Console::new(gfx.top_screen.borrow_mut()); println!("Press A to enable/disable wide screen mode."); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { hid.scan_input(); @@ -17,14 +25,18 @@ fn main() { break; } + // Since we can't set wide-mode while running the console (since that would break the currently displayed text), + // we need to rebuild the console each time we want to make the switch. if hid.keys_down().contains(KeyPad::A) { drop(console); + // Switch the state of the wide-mode. let wide_mode = gfx.top_screen.borrow().is_wide(); gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode); console = Console::new(gfx.top_screen.borrow_mut()); println!("Press A to enable/disable wide screen mode."); + println!("\x1b[29;16HPress Start to exit"); } gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs deleted file mode 100644 index df9ce11..0000000 --- a/ctru-rs/examples/graphics-bitmap.rs +++ /dev/null @@ -1,57 +0,0 @@ -use ctru::prelude::*; -use ctru::services::gfx::{Flush, Screen, Swap}; - -/// Ferris image taken from and scaled down to 320x240px. -/// To regenerate the data, you will need to install `imagemagick` and run this -/// command from the `examples` directory: -/// -/// ```sh -/// magick assets/ferris.png -channel-fx "red<=>blue" -rotate 90 assets/ferris.rgb -/// ``` -/// -/// This creates an image appropriate for the default frame buffer format of -/// [`Bgr8`](ctru::services::gspgpu::FramebufferFormat::Bgr8) -/// and rotates the image 90° to account for the portrait mode screen. -static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); - -fn main() { - ctru::use_panic_handler(); - - let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); - let mut hid = Hid::new().expect("Couldn't obtain HID controller"); - let apt = Apt::new().expect("Couldn't obtain APT controller"); - let _console = Console::new(gfx.top_screen.borrow_mut()); - - println!("\x1b[21;16HPress Start to exit."); - - let mut bottom_screen = gfx.bottom_screen.borrow_mut(); - - // We don't need double buffering in this example. - // In this way we can draw our image only once on screen. - bottom_screen.set_double_buffering(false); - - // We assume the image is the correct size already, so we drop width + height. - let frame_buffer = bottom_screen.raw_framebuffer(); - - // Copy the image into the frame buffer - unsafe { - frame_buffer.ptr.copy_from(IMAGE.as_ptr(), IMAGE.len()); - } - - // 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::START) { - break; - } - - // Flush and swap framebuffers - bottom_screen.flush_buffers(); - bottom_screen.swap_buffers(); - - //Wait for VBlank - gfx.wait_for_vblank(); - } -} diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs index 7d4d8c4..e563068 100644 --- a/ctru-rs/examples/hashmaps.rs +++ b/ctru-rs/examples/hashmaps.rs @@ -1,10 +1,16 @@ +//! Hashmap example. +//! +//! This example showcases using Hashmaps on the 3DS console using the functionality implemented by the standard library. +//! While it may seem inappropriate for such a simple (and somewhat out-of-scope) example to be included here, it's important to note how +//! normally Hashmaps wouldn't work on the console, and are only capable to because of the internal implementations made by `ctru-rs`. +//! +//! As such, this example functions more closely to a test than a demonstration. + use ctru::prelude::*; fn main() { ctru::use_panic_handler(); - // Initialize services - // // HashMaps generate hashes thanks to the 3DS' cryptografically secure generator. // This generator is only active when activating the `PS` service. // This service is automatically initialized. @@ -19,6 +25,7 @@ fn main() { map.remove("A Key!"); println!("{map:#?}"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/hello-both-screens.rs b/ctru-rs/examples/hello-both-screens.rs index f2007f1..1f2b383 100644 --- a/ctru-rs/examples/hello-both-screens.rs +++ b/ctru-rs/examples/hello-both-screens.rs @@ -1,3 +1,7 @@ +//! Hello World example using both screens. +//! +//! This is similar to the `hello-world` example, with the main difference of using 2 virtual `Console`s that can be alternated to print on both screens. + use ctru::prelude::*; fn main() { @@ -11,14 +15,15 @@ fn main() { let top_screen = Console::new(gfx.top_screen.borrow_mut()); // Start a console on the bottom screen. - // The most recently initialized console will be active by default + // The most recently initialized console will be active by default. let bottom_screen = Console::new(gfx.bottom_screen.borrow_mut()); - // Let's print on the top screen first + // Let's print on the top screen first. + // Since the bottom screen is currently selected (being created afterwards), it is required to select the top screen console first. top_screen.select(); println!("This is the top screen! We have a lot of space up here!"); - // Now let's print something on the bottom screen + // Now let's print something on the bottom screen. bottom_screen.select(); println!("\x1b[14;00HThis is the bottom screen."); println!("There's not as much space down here, but that's okay."); diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index a92cb90..9210484 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -1,15 +1,25 @@ -use ctru::prelude::*; +//! Hello World example. +//! +//! Simple "Hello World" application to showcase the basic setup needed for any user-oriented app to work. +use ctru::prelude::*; use std::io::BufWriter; fn main() { + // Setup the custom panic handler in case any errors arise. + // Thanks to it the user will get promptly notified of any panics. ctru::use_panic_handler(); + // Setup Graphics, Controller Inputs, Application runtime. + // These is standard setup any app would need. let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); + + // Create a Console to print our "Hello, World!" to. let _console = Console::new(gfx.top_screen.borrow_mut()); + // Snazzy message created via `ferris_says`. let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!"; let width = 24; @@ -21,16 +31,18 @@ fn main() { String::from_utf8_lossy(&writer.into_inner().unwrap()) ); - // Main loop + println!("\x1b[29;16HPress Start to exit"); + + // Main application loop. This checks whether the app is normally running in the foreground. while apt.main_loop() { - //Scan all the inputs. This should be done once for each frame + // Scan all the controller inputs. hid.scan_input(); if hid.keys_down().contains(KeyPad::START) { break; } - //Wait for VBlank + // Use VSync to cap the framerate at the same speed as the LCD screen's refresh rate (60 fps). gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/linear-memory.rs b/ctru-rs/examples/linear-memory.rs index e6c18dc..de653e6 100644 --- a/ctru-rs/examples/linear-memory.rs +++ b/ctru-rs/examples/linear-memory.rs @@ -1,3 +1,9 @@ +//! LINEAR memory example. +//! +//! This example showcases simple allocation on the LINEAR memory sector. +//! Using LINEAR memory is required when sending data to the GPU or DSP processor. + +// You will need to activate this unstable feature to use custom allocators. #![feature(allocator_api)] use ctru::linear::LinearAllocator; @@ -11,11 +17,13 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); + // The `LinearAllocator` is always available for use. + // Luckily, we can always read how much memory is available to be allocated on it. let linear_space_before = LinearAllocator::free_space(); - // Normal `Box` on the heap + // Normal `Box` on the heap. let heap_box = Box::new(1492); - // `Box` living on the linear memory sector + // `Box` living on the LINEAR memory. let linear_box: Box = Box::new_in(2022, LinearAllocator); println!("This value is from the heap: {heap_box}"); @@ -29,16 +37,13 @@ fn main() { println!("\x1b[29;16HPress Start to exit"); - // 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/mii-selector.rs b/ctru-rs/examples/mii-selector.rs index b12f217..db3a9ea 100644 --- a/ctru-rs/examples/mii-selector.rs +++ b/ctru-rs/examples/mii-selector.rs @@ -1,3 +1,7 @@ +//! Mii Selector example. +//! +//! This example showcases the use of the MiiSelector applet to obtain Mii data from the user's input. + use ctru::applets::mii_selector::{Error, MiiSelector, Options}; use ctru::prelude::*; @@ -9,12 +13,16 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); + // Setup the Mii Selector configuration. let mut mii_selector = MiiSelector::new(); + // The Mii Selector window can be closed without selecting a Mii. mii_selector.set_options(Options::ENABLE_CANCEL); mii_selector.set_initial_index(3); + // The first user-made Mii cannot be used. mii_selector.blacklist_user_mii(0.into()); mii_selector.set_title("Great Mii Selector!"); + // Launch the Mii Selector and use its result to print the selected Mii's information. match mii_selector.launch() { Ok(result) => { println!("Mii type: {:?}", result.mii_type); @@ -29,16 +37,15 @@ fn main() { Err(Error::NoMiiSelected) => println!("No Mii selected"), } - // Main loop + println!("\x1b[29;16HPress Start to exit"); + 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/network-sockets.rs b/ctru-rs/examples/network-sockets.rs index bbea510..0cc70ad 100644 --- a/ctru-rs/examples/network-sockets.rs +++ b/ctru-rs/examples/network-sockets.rs @@ -1,3 +1,7 @@ +//! Network Sockets example. +//! +//! This example showcases the use of network sockets via the `Soc` service and the standard library's implementations. + use ctru::prelude::*; use std::io::{self, Read, Write}; @@ -14,20 +18,27 @@ fn main() { println!("\nlibctru sockets demo\n"); + // Owning a living handle to the `Soc` service is required to use network functionalities. let soc = Soc::new().unwrap(); + // Listen on the standard HTTP port (80). let server = TcpListener::bind("0.0.0.0:80").unwrap(); server.set_nonblocking(true).unwrap(); println!("Point your browser to http://{}/\n", soc.host_address()); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { - gfx.wait_for_vblank(); + hid.scan_input(); + if hid.keys_down().contains(KeyPad::START) { + break; + }; match server.accept() { Ok((mut stream, socket_addr)) => { println!("Got connection from {socket_addr}"); + // Print the HTTP request sent by the client (most likely, a web browser). let mut buf = [0u8; 4096]; match stream.read(&mut buf) { Ok(_) => { @@ -43,15 +54,18 @@ fn main() { } } + // Return a HTML page with a simple "Hello World!". let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nHello world\r\n"; if let Err(e) = stream.write(response) { println!("Error writing http response: {e}"); } + // Shutdown the stream (depending on the web browser used to view the page, this might cause some issues). stream.shutdown(Shutdown::Both).unwrap(); } Err(e) => match e.kind() { + // If the TCP socket would block execution, just try again. std::io::ErrorKind::WouldBlock => {} _ => { println!("Error accepting connection: {e}"); @@ -60,9 +74,6 @@ fn main() { }, } - hid.scan_input(); - if hid.keys_down().contains(KeyPad::START) { - break; - }; + gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/output-3dslink.rs b/ctru-rs/examples/output-3dslink.rs index 8314efa..9df3f3d 100644 --- a/ctru-rs/examples/output-3dslink.rs +++ b/ctru-rs/examples/output-3dslink.rs @@ -1,4 +1,6 @@ -//! Use the `3dslink --server` option for redirecting output from the 3DS back +//! Output redirection example. +//! +//! This example uses the `3dslink --server` option for redirecting output from the 3DS back //! to the device that sent the executable. //! //! For now, `cargo 3ds run` does not support this flag, so to run this example @@ -17,24 +19,23 @@ fn main() { let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); + // We need to use network sockets to send the data stream back. let mut soc = Soc::new().expect("Couldn't obtain SOC controller"); + // Set the output to be redirected to the `3dslink` server. soc.redirect_to_3dslink(true, true) .expect("unable to redirect stdout/err to 3dslink server"); println!("Hello 3dslink!"); eprintln!("Press Start on the device to disconnect and exit."); - // 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/romfs.rs b/ctru-rs/examples/romfs.rs index 1138203..490a785 100644 --- a/ctru-rs/examples/romfs.rs +++ b/ctru-rs/examples/romfs.rs @@ -1,3 +1,7 @@ +//! RomFS example. +//! +//! This example showcases the RomFS service and how to mount it to include a read-only filesystem within the application bundle. + use ctru::prelude::*; fn main() { @@ -9,35 +13,35 @@ fn main() { let _console = Console::new(gfx.top_screen.borrow_mut()); cfg_if::cfg_if! { - // Run this code if RomFS are wanted and available - // This never fails as `ctru-rs` examples inherit all of the `ctru` features, - // but it might if a normal user application wasn't setup correctly + // Run this code if RomFS are wanted and available. + // This never fails as `ctru-rs` examples inherit all of the `ctru-rs` features, + // but it might if a normal user application wasn't setup correctly. if #[cfg(all(feature = "romfs", romfs_exists))] { + // Mount the romfs volume. let _romfs = ctru::services::romfs::RomFS::new().unwrap(); + // Open a simple text file present in the RomFS volume. + // Remember to use the `romfs:/` prefix when working with `RomFS`. let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap(); println!("Contents of test-file.txt: \n{f}\n"); let f = std::fs::read_to_string("romfs:/ファイル.txt").unwrap(); - // While RomFS supports UTF-16 file paths, `Console` doesn't... + // While `RomFS` supports UTF-16 file paths, `Console` does not, so we'll use a placeholder for the text. println!("Contents of [UTF-16 File]: \n{f}\n"); } else { println!("No RomFS was found, are you sure you included it?") } } - println!("\nPress START to exit"); + println!("\x1b[29;16HPress Start to exit"); - // 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 4d7c4f2..269ccf0 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -1,3 +1,7 @@ +//! Software Keyboard example. +//! +//! 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::prelude::*; @@ -9,13 +13,17 @@ fn main() { let gfx = Gfx::new().unwrap(); let _console = Console::new(gfx.top_screen.borrow_mut()); - println!("Press A to enter some text or press Start to quit"); + println!("Press A to enter some text."); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { - gfx.wait_for_vblank(); - hid.scan_input(); + if hid.keys_down().contains(KeyPad::START) { + break; + } + + // If the user request to write some input. 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 `SoftwareKeyboard::new()` to launch the keyboard in different @@ -23,7 +31,7 @@ fn main() { let mut keyboard = SoftwareKeyboard::default(); // Raise the software keyboard. You can perform different actions depending on which - // software button the user pressed + // software button the user pressed. match keyboard.get_string(2048) { Ok((text, Button::Right)) => println!("You entered: {text}"), Ok((_, Button::Left)) => println!("Cancelled"), @@ -32,8 +40,6 @@ fn main() { } } - if hid.keys_down().contains(KeyPad::START) { - break; - } + gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/system-configuration.rs b/ctru-rs/examples/system-configuration.rs index 001233d..f1748f7 100644 --- a/ctru-rs/examples/system-configuration.rs +++ b/ctru-rs/examples/system-configuration.rs @@ -1,3 +1,8 @@ +//! System Configuration example. +//! +//! This example showcases the CFGU service to retrieve information about the console that the application is running on, +//! such as the model, region and used language. + use ctru::prelude::*; use ctru::services::cfgu::Cfgu; @@ -7,23 +12,24 @@ fn main() { let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); - let cfgu = Cfgu::new().expect("Couldn't obtain CFGU controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); + // Initialize the CFGU service to retrieve all wanted information. + let cfgu = Cfgu::new().expect("Couldn't obtain CFGU controller"); + println!("\x1b[0;0HRegion: {:?}", cfgu.region().unwrap()); println!("\x1b[10;0HLanguage: {:?}", cfgu.language().unwrap()); println!("\x1b[20;0HModel: {:?}", cfgu.model().unwrap()); - // Main loop + println!("\x1b[29;16HPress Start to exit"); + 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/thread-basic.rs b/ctru-rs/examples/thread-basic.rs index 33a8ea9..3e4604b 100644 --- a/ctru-rs/examples/thread-basic.rs +++ b/ctru-rs/examples/thread-basic.rs @@ -33,6 +33,8 @@ fn main() { println!("Created thread {ix}"); } + println!("\x1b[29;16HPress Start to exit"); + while apt.main_loop() { gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/thread-info.rs b/ctru-rs/examples/thread-info.rs index 28297ac..46d34d3 100644 --- a/ctru-rs/examples/thread-info.rs +++ b/ctru-rs/examples/thread-info.rs @@ -36,7 +36,7 @@ fn main() { .unwrap(); println!("sys thread exited"); - println!("\nPress Start to exit"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { hid.scan_input(); diff --git a/ctru-rs/examples/thread-locals.rs b/ctru-rs/examples/thread-locals.rs index 80d8508..72458c9 100644 --- a/ctru-rs/examples/thread-locals.rs +++ b/ctru-rs/examples/thread-locals.rs @@ -52,7 +52,7 @@ fn main() { ); }); - println!("Press Start to exit"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { hid.scan_input(); diff --git a/ctru-rs/examples/time-rtc.rs b/ctru-rs/examples/time-rtc.rs index 377fd5c..e8c3a95 100644 --- a/ctru-rs/examples/time-rtc.rs +++ b/ctru-rs/examples/time-rtc.rs @@ -1,3 +1,8 @@ +//! Time Clock example. +//! +//! This example showcases how to retrieve the local time set in the console's configuration +//! using the implementations of the standard library. + use ctru::prelude::*; fn main() { @@ -9,11 +14,9 @@ fn main() { let _console = Console::new(gfx.top_screen.borrow_mut()); - print!("\x1b[30;16HPress Start to exit."); + println!("\x1b[29;16HPress Start to exit"); - // 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::START) { @@ -21,22 +24,12 @@ fn main() { } // Technically, this actually just gets local time and assumes it's UTC, - // since the 3DS doesn't seem to support timezones... + // since the 3DS doesn't seem to support timezones. let cur_time = time::OffsetDateTime::now_utc(); - let hours = cur_time.hour(); - let minutes = cur_time.minute(); - let seconds = cur_time.second(); - - let weekday = cur_time.weekday().to_string(); - let month = cur_time.month().to_string(); - let day = cur_time.day(); - let year = cur_time.year(); - - println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}"); - println!("{weekday} {month} {day} {year}"); + // Display the retrieved information. + println!("\x1b[1;1H{cur_time}"); - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index 25b5c75..9cb4db8 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -1,3 +1,8 @@ +//! Title Info example. +//! +//! This example showcases how to retrieve information about the titles installed on the console running the application +//! via the Application Manager (Am) service. + use ctru::prelude::*; use ctru::services::am::Am; use ctru::services::fs::FsMediaType; @@ -8,20 +13,27 @@ fn main() { let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); - let am = Am::new().expect("Couldn't obtain AM controller"); + let top_screen = Console::new(gfx.top_screen.borrow_mut()); let bottom_screen = Console::new(gfx.bottom_screen.borrow_mut()); + // Setup the AM service to retrieve the wanted information. + let am = Am::new().expect("Couldn't obtain AM controller"); + + // Amount of titles installed on the SD card. let sd_count = am .title_count(FsMediaType::Sd) .expect("Failed to get sd title count"); + // List of titles installed on the SD card. let sd_list = am .title_list(FsMediaType::Sd) .expect("Failed to get sd title list"); + // Amount of titles installed on the NAND storage. let nand_count = am .title_count(FsMediaType::Nand) .expect("Failed to get nand title count"); + // List of titles installed on the NAND storage. let nand_list = am .title_list(FsMediaType::Nand) .expect("Failed to get nand title list"); @@ -30,9 +42,7 @@ fn main() { let mut refresh = true; let mut use_nand = false; - // 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::START) { @@ -58,13 +68,15 @@ fn main() { } } + // Render the title list via a scrollable text UI. if refresh { let mut selected_title = cur_list.iter().skip(offset).next().unwrap(); - // Clear top screen and write title ids to it + + // Clear the top screen and write title IDs to it. top_screen.select(); print!("\x1b[2J"); - // Top screen seems to have only 30 rows + // Top screen has 30 rows. for (i, title) in cur_list.iter().skip(offset).take(29).enumerate() { if i == 0 { selected_title = title; @@ -74,17 +86,18 @@ fn main() { } } - // Clear bottom screen and write properties of selected title to it + // Clear the bottom screen and write the properties of selected title to it. bottom_screen.select(); println!("\x1b[2J"); - // Move cursor to top left + + // Move cursor to top left. println!("\x1b[1;1"); println!("Size: {} kB", selected_title.size() / 1024); println!("Version: 0x{:x}", selected_title.version()); println!("Product code: \"{}\"", selected_title.product_code()); - println!("\x1b[26;0HPress START to exit"); + println!("\x1b[29;16HPress Start to exit"); if use_nand { println!("Press SELECT to choose SD Card"); println!("Current medium: NAND"); @@ -98,7 +111,6 @@ fn main() { refresh = false; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/touch-screen.rs b/ctru-rs/examples/touch-screen.rs index 204f324..a785055 100644 --- a/ctru-rs/examples/touch-screen.rs +++ b/ctru-rs/examples/touch-screen.rs @@ -1,3 +1,7 @@ +//! Touch Screen example. +//! +//! This example showcases how to retrieve the touch screen's touch information via the HID service. + use ctru::prelude::*; fn main() { @@ -9,7 +13,7 @@ fn main() { let console = Console::new(gfx.top_screen.borrow_mut()); - // We'll hold the previous touch position for comparison. + // We'll save the previous touch position for comparison. let mut old_touch: (u16, u16) = (0, 0); println!("\x1b[29;16HPress Start to exit"); @@ -26,22 +30,22 @@ fn main() { let touch: (u16, u16) = hid.touch_position(); // We only want to print the position when it's different - // from what it was on the previous frame + // from what it was on the previous frame. if touch != old_touch { // Special case for when the user lifts the stylus/finger from the screen. // This is done to avoid some screen tearing. if touch == (0, 0) { console.clear(); - // Print again because we just cleared the screen + // Print again because we just cleared the screen. println!("\x1b[29;16HPress Start to exit"); } - // Move the cursor back to the top of the screen and print the coordinates + // Move the cursor back to the top of the screen and print the coordinates. print!("\x1b[1;1HTouch Screen position: {:#?}", touch); } - // Save our current touch position for the next frame + // Save our current touch position for the next frame. old_touch = touch; gfx.wait_for_vblank(); diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 5a2fc62..0769b81 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -414,8 +414,12 @@ impl TopScreen { /// Enable or disable wide mode on the top screen. /// + /// # Notes + /// /// [`Swap::swap_buffers`] must be called after this method for the configuration /// to take effect. + /// + /// Wide mode does NOT work on Old 2DS models (but still does on New 2DS XL models). #[doc(alias = "gfxSetWide")] pub fn set_wide_mode(&mut self, enable: bool) { unsafe {