Browse Source

Merge branch 'improve/api' into feat/flush-swap-screen-traits

pull/118/head
Andrea Ciliberti 2 years ago
parent
commit
a3fc356d6a
  1. 1
      .github/CODEOWNERS
  2. 46
      .github/actions/setup/action.yml
  3. 56
      .github/workflows/ci.yml
  4. 57
      ctru-rs/examples/graphics-bitmap.rs
  5. 49
      ctru-rs/examples/touch-screen.rs
  6. 28
      ctru-rs/src/applets/mii_selector.rs
  7. 24
      ctru-rs/src/services/cfgu.rs
  8. 57
      ctru-rs/src/services/fs.rs
  9. 58
      ctru-rs/src/services/hid.rs

1
.github/CODEOWNERS

@ -0,0 +1 @@ @@ -0,0 +1 @@
* @rust3ds/active

46
.github/actions/setup/action.yml

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
name: Setup
description: Set up CI environment for Rust + 3DS development
inputs:
toolchain:
description: The Rust toolchain to use for the steps
required: true
default: nightly
runs:
using: composite
steps:
# https://github.com/nektos/act/issues/917#issuecomment-1074421318
- if: ${{ env.ACT }}
shell: bash
name: Hack container for local development
run: |
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
- name: Setup default Rust toolchain
# Use this helper action so we get matcher support
# https://github.com/actions-rust-lang/setup-rust-toolchain/pull/15
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy, rustfmt, rust-src
toolchain: ${{ inputs.toolchain }}
- name: Install build tools for host
shell: bash
run: sudo apt-get update && sudo apt-get install -y build-essential
- name: Install cargo-3ds
uses: actions-rs/cargo@v1
with:
command: install
# TODO: this should probably just be a released version from crates.io
# once cargo-3ds gets published somewhere...
args: >-
--git https://github.com/rust3ds/cargo-3ds
--rev 78a652fdfb01e2614a792d1a56b10c980ee1dae9
- name: Set PATH to include devkitARM
shell: bash
# For some reason devkitARM/bin is not part of the default PATH in the container
run: echo "${DEVKITARM}/bin" >> $GITHUB_PATH

56
.github/workflows/ci.yml

@ -12,6 +12,8 @@ on: @@ -12,6 +12,8 @@ on:
env:
# https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html
CARGO_UNSTABLE_SPARSE_REGISTRY: "true"
# actions-rust-lang/setup-rust-toolchain sets some default RUSTFLAGS
RUSTFLAGS: ""
jobs:
lint:
@ -22,41 +24,24 @@ jobs: @@ -22,41 +24,24 @@ jobs:
- nightly-2023-01-13
# Check for breakage on latest nightly
- nightly
# But if latest nightly fails, allow the workflow to continue
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
runs-on: ubuntu-latest
container: devkitpro/devkitarm
steps:
# https://github.com/nektos/act/issues/917#issuecomment-1074421318
- if: ${{ env.ACT }}
name: Hack container for local development
run: |
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
- name: Checkout branch
uses: actions/checkout@v2
- name: Setup default Rust toolchain
uses: actions-rs/toolchain@v1
- uses: ./.github/actions/setup
with:
components: clippy, rustfmt, rust-src
profile: minimal
toolchain: ${{ matrix.toolchain }}
default: true
- name: Install build tools for host
run: sudo apt-get update && sudo apt-get install -y build-essential
- name: Install cargo-3ds
uses: actions-rs/cargo@v1
with:
command: install
# TODO: this should probably just be a released version from crates.io
# once cargo-3ds gets published somewhere...
args: >-
--git https://github.com/rust3ds/cargo-3ds
--rev 7b70b6b26c4740b9a10ab85b832ee73c41142bbb
- name: Hide duplicate warnings from lint job
if: ${{ matrix.toolchain == 'nightly' }}
run: |
echo "::remove-matcher owner=clippy::"
echo "::remove-matcher owner=rustfmt::"
- name: Check formatting
run: cargo fmt --all --verbose -- --check
@ -68,5 +53,28 @@ jobs: @@ -68,5 +53,28 @@ jobs:
# feature, but https://github.com/actions/runner/issues/2341 means we
# can't have both that *and* colored output.
doctests:
strategy:
matrix:
toolchain:
- nightly-2023-01-13
- nightly
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
runs-on: ubuntu-latest
container: devkitpro/devkitarm
steps:
- name: Checkout branch
uses: actions/checkout@v2
- uses: ./.github/actions/setup
with:
toolchain: ${{ matrix.toolchain }}
- name: Hide duplicated warnings from lint job
run: echo "::remove-matcher owner=clippy::"
- name: Build doc tests
run: cargo 3ds test --doc --verbose
# TODO: it would be nice to actually build 3dsx for examples/tests, etc.
# and run it somehow, but exactly how remains to be seen.

57
ctru-rs/examples/graphics-bitmap.rs

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
use ctru::prelude::*;
use ctru::services::gfx::Screen;
/// Ferris image taken from <https://rustacean.net> 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_buffer();
bottom_screen.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();
}
}

49
ctru-rs/examples/touch-screen.rs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
use ctru::prelude::*;
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());
// We'll hold the previous touch position for comparison.
let mut old_touch: (u16, u16) = (0, 0);
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Get X and Y coordinates of the touch point.
// The touch screen is 320x240.
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
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
println!("\x1b[29;16HPress Start to exit");
}
// 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
old_touch = touch;
gfx.wait_for_vblank();
}
}

28
ctru-rs/src/applets/mii_selector.rs

@ -9,7 +9,7 @@ use std::ffi::CString; @@ -9,7 +9,7 @@ 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, Copy, Eq, PartialEq)]
pub enum MiiIndex {
pub enum Index {
Index(u32),
All,
}
@ -94,40 +94,40 @@ impl MiiSelector { @@ -94,40 +94,40 @@ impl MiiSelector {
}
/// Whitelist a guest Mii
pub fn whitelist_guest_mii(&mut self, mii_index: MiiIndex) {
pub fn whitelist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index {
MiiIndex::Index(i) => i,
MiiIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
Index::Index(i) => i,
Index::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: MiiIndex) {
pub fn blacklist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index {
MiiIndex::Index(i) => i,
MiiIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
Index::Index(i) => i,
Index::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: MiiIndex) {
pub fn whitelist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index {
MiiIndex::Index(i) => i,
MiiIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
Index::Index(i) => i,
Index::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: MiiIndex) {
pub fn blacklist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index {
MiiIndex::Index(i) => i,
MiiIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
};
unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
@ -185,7 +185,7 @@ impl From<ctru_sys::MiiSelectorReturn> for SelectionResult { @@ -185,7 +185,7 @@ impl From<ctru_sys::MiiSelectorReturn> for SelectionResult {
}
}
impl From<u32> for MiiIndex {
impl From<u32> for Index {
fn from(v: u32) -> Self {
Self::Index(v)
}

24
ctru-rs/src/services/cfgu.rs

@ -36,12 +36,12 @@ pub enum Language { @@ -36,12 +36,12 @@ pub enum Language {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum SystemModel {
N3DS = ctru_sys::CFG_MODEL_3DS,
N3DSXL = ctru_sys::CFG_MODEL_3DSXL,
NewN3DS = ctru_sys::CFG_MODEL_N3DS,
N2DS = ctru_sys::CFG_MODEL_2DS,
NewN3DSXL = ctru_sys::CFG_MODEL_N3DSXL,
NewN2DSXL = ctru_sys::CFG_MODEL_N2DSXL,
Old3DS = ctru_sys::CFG_MODEL_3DS,
Old3DSXL = ctru_sys::CFG_MODEL_3DSXL,
New3DS = ctru_sys::CFG_MODEL_N3DS,
Old2DS = ctru_sys::CFG_MODEL_2DS,
New3DSXL = ctru_sys::CFG_MODEL_N3DSXL,
New2DSXL = ctru_sys::CFG_MODEL_N2DSXL,
}
/// Represents the configuration service. No actions can be performed
@ -163,12 +163,12 @@ impl TryFrom<u8> for SystemModel { @@ -163,12 +163,12 @@ impl TryFrom<u8> for SystemModel {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value as u32 {
ctru_sys::CFG_MODEL_3DS => Ok(SystemModel::N3DS),
ctru_sys::CFG_MODEL_3DSXL => Ok(SystemModel::N3DSXL),
ctru_sys::CFG_MODEL_N3DS => Ok(SystemModel::NewN3DS),
ctru_sys::CFG_MODEL_2DS => Ok(SystemModel::N2DS),
ctru_sys::CFG_MODEL_N3DSXL => Ok(SystemModel::NewN3DSXL),
ctru_sys::CFG_MODEL_N2DSXL => Ok(SystemModel::NewN2DSXL),
ctru_sys::CFG_MODEL_3DS => Ok(SystemModel::Old3DS),
ctru_sys::CFG_MODEL_3DSXL => Ok(SystemModel::Old3DSXL),
ctru_sys::CFG_MODEL_N3DS => Ok(SystemModel::New3DS),
ctru_sys::CFG_MODEL_2DS => Ok(SystemModel::Old2DS),
ctru_sys::CFG_MODEL_N3DSXL => Ok(SystemModel::New3DSXL),
ctru_sys::CFG_MODEL_N2DSXL => Ok(SystemModel::New2DSXL),
_ => Err(()),
}
}

57
ctru-rs/src/services/fs.rs

@ -99,7 +99,7 @@ pub struct Fs(()); @@ -99,7 +99,7 @@ pub struct Fs(());
/// ```no_run
/// use ctru::services::fs::Fs;
///
/// let fs = Fs::new().unwrap();
/// let mut fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// ```
pub struct Archive {
@ -119,47 +119,62 @@ pub struct Archive { @@ -119,47 +119,62 @@ pub struct Archive {
/// Create a new file and write bytes to it:
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::new()?;
/// let sdmc = fs.sdmc()?;
///
/// let mut file = File::create(&sdmc, "/foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// let mut fs = Fs::new()?;
/// let mut sdmc = fs.sdmc()?;
/// #
/// # Ok(())
/// # }
/// ```
///
/// Read the contents of a file into a `String`::
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::new()?;
/// let sdmc = fs.sdmc()?;
/// let mut fs = Fs::new()?;
/// let mut sdmc = fs.sdmc()?;
///
/// let mut file = File::open(&sdmc, "/foo.txt")?;
/// let mut contents = String::new();
/// file.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!");
/// #
/// # Ok(())
/// # }
/// ```
///
/// It can be more efficient to read the contents of a file with a buffered
/// `Read`er. This can be accomplished with `BufReader<R>`:
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::BufReader;
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::new()?;
/// let sdmc = fs.sdmc()?;
/// let mut fs = Fs::new()?;
/// let mut sdmc = fs.sdmc()?;
///
/// let file = File::open(&sdmc, "/foo.txt")?;
/// let mut buf_reader = BufReader::new(file);
/// let mut contents = String::new();
/// buf_reader.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!");
/// #
/// # Ok(())
/// # }
/// ```
pub struct File {
handle: u32,
@ -205,13 +220,13 @@ pub struct Metadata { @@ -205,13 +220,13 @@ pub struct Metadata {
/// ```no_run
/// use ctru::services::fs::{Fs, OpenOptions};
///
<<<<<<< HEAD
/// let fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// let file = OpenOptions::new()
/// .read(true)
=======
/// let mut fs = Fs::new().unwrap();
/// .archive(&sdmc_archive)
/// .open("foo.txt")
/// .unwrap();
/// ```
///
/// Opening a file for both reading and writing, as well as creating it if it
@ -220,8 +235,13 @@ pub struct Metadata { @@ -220,8 +235,13 @@ pub struct Metadata {
/// ```no_run
/// use ctru::services::fs::{Fs, OpenOptions};
///
<<<<<<< HEAD
/// let fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
=======
/// let mut fs = Fs::new().unwrap();
/// let mut sdmc_archive = fs.sdmc().unwrap();
>>>>>>> improve/api
/// let file = OpenOptions::new()
/// .read(true)
/// .write(true)
@ -347,8 +367,13 @@ impl File { @@ -347,8 +367,13 @@ impl File {
/// ```no_run
/// use ctru::services::fs::{Fs, File};
///
<<<<<<< HEAD
/// let fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
=======
/// let mut fs = Fs::new().unwrap();
/// let mut sdmc_archive = fs.sdmc().unwrap();
>>>>>>> improve/api
/// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap();
/// ```
pub fn open<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
@ -376,9 +401,15 @@ impl File { @@ -376,9 +401,15 @@ impl File {
/// ```no_run
/// use ctru::services::fs::{Fs, File};
///
<<<<<<< HEAD
/// let fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap();
=======
/// let mut fs = Fs::new().unwrap();
/// let mut sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::create(&mut sdmc_archive, "/foo.txt").unwrap();
>>>>>>> improve/api
/// ```
pub fn create<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<File> {
OpenOptions::new()

58
ctru-rs/src/services/hid.rs

@ -46,14 +46,6 @@ bitflags::bitflags! { @@ -46,14 +46,6 @@ bitflags::bitflags! {
/// This service requires no special permissions to use.
pub struct Hid(());
/// Represents user input to the touchscreen.
#[derive(Debug, Clone, Copy)]
pub struct TouchPosition(ctru_sys::touchPosition);
/// Represents the current position of the 3DS circle pad.
#[derive(Debug, Clone, Copy)]
pub struct CirclePosition(ctru_sys::circlePosition);
/// Initializes the HID service.
///
/// # Errors
@ -102,47 +94,33 @@ impl Hid { @@ -102,47 +94,33 @@ impl Hid {
KeyPad::from_bits_truncate(keys)
}
}
}
impl Default for TouchPosition {
fn default() -> Self {
TouchPosition(ctru_sys::touchPosition { px: 0, py: 0 })
}
}
impl TouchPosition {
/// Create a new TouchPosition instance.
pub fn new() -> Self {
Self::default()
}
/// Returns the current touch position in pixels (x, y).
///
/// # Notes
///
/// (0, 0) represents the top left corner of the screen.
pub fn touch_position(&mut self) -> (u16, u16) {
let mut res = ctru_sys::touchPosition { px: 0, py: 0 };
/// Returns the current touch position in pixels.
pub fn get(&mut self) -> (u16, u16) {
unsafe {
ctru_sys::hidTouchRead(&mut self.0);
}
(self.0.px, self.0.py)
ctru_sys::hidTouchRead(&mut res);
}
}
impl Default for CirclePosition {
fn default() -> Self {
CirclePosition(ctru_sys::circlePosition { dx: 0, dy: 0 })
(res.px, res.py)
}
}
impl CirclePosition {
/// Create a new CirclePosition instance.
pub fn new() -> Self {
Self::default()
}
/// Returns the current circle pad position in relative (x, y).
///
/// # Notes
///
/// (0, 0) represents the center of the circle pad.
pub fn circlepad_position(&mut self) -> (i16, i16) {
let mut res = ctru_sys::circlePosition { dx: 0, dy: 0 };
/// Returns the current circle pad position in (x, y) form.
pub fn get(&mut self) -> (i16, i16) {
unsafe {
ctru_sys::hidCircleRead(&mut self.0);
ctru_sys::hidCircleRead(&mut res);
}
(self.0.dx, self.0.dy)
(res.dx, res.dy)
}
}

Loading…
Cancel
Save