diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index ff6296f..5837ec8 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -32,8 +32,9 @@ struct FileExplorer<'a> { impl<'a> FileExplorer<'a> { fn init(apt: &'a Apt, hid: &'a Hid, gfx: &'a Gfx) -> Self { - gfx.top_screen.borrow_mut().set_wide_mode(true); - let console = Console::init(gfx.top_screen.borrow_mut()); + let mut top_screen = gfx.top_screen.borrow_mut(); + top_screen.set_wide_mode(true); + let console = Console::init(top_screen); FileExplorer { apt, diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index 5b7e8ee..53cefcb 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -1,3 +1,4 @@ +use ctru::gfx::Side; use ctru::prelude::*; use std::io::BufWriter; @@ -7,7 +8,7 @@ fn main() { 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 _console = Console::init(gfx.top_screen.borrow_side_mut(Side::Left)); let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!"; let width = 24; diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 6400d97..d9c83d6 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -1,7 +1,7 @@ //! LCD screens manipulation helper use once_cell::sync::Lazy; -use std::cell::RefCell; +use std::cell::{Ref, RefCell, RefMut}; use std::marker::PhantomData; use std::sync::Mutex; @@ -9,11 +9,19 @@ use crate::error::Result; use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; -/// Trait implemented by TopScreen and BottomScreen for common methods +/// This trait is implemented by the screen structs for working with frame buffers and +/// drawing to the screens. Graphics-related code can be made generic over this +/// trait to work with any of the given screens. pub trait Screen { /// Returns the libctru value for the Screen kind fn as_raw(&self) -> ctru_sys::gfxScreen_t; + /// Returns a [`RawFrameBuffer`] for the screen. + /// + /// Note that the pointer of the framebuffer returned by this function can + /// change after each call to this function if double buffering is enabled. + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer; + /// Sets whether to use double buffering. Enabled by default. /// /// Note that even when double buffering is disabled, one should still use the `swap_buffers` @@ -33,8 +41,23 @@ pub trait Screen { } } +// TODO: it might be nice to do `TopScreen` but it requires +// #![feature(adt_const_params)] which is unstable, and doesn't seem worth it +// just for this. +#[non_exhaustive] +pub struct TopLeftScreen; + +#[non_exhaustive] +pub struct TopRightScreen; + +/// A helper container for both sides of the top screen. +pub struct TopScreenInner { + left: TopLeftScreen, + right: TopRightScreen, +} + #[non_exhaustive] -pub struct TopScreen; +pub struct TopScreen(RefCell); #[non_exhaustive] pub struct BottomScreen; @@ -71,7 +94,7 @@ pub enum Side { /// The service exits when this struct is dropped. #[non_exhaustive] pub struct Gfx { - pub top_screen: RefCell, + pub top_screen: TopScreen, pub bottom_screen: RefCell, _service_handler: ServiceReference, } @@ -100,14 +123,15 @@ impl Gfx { )?; Ok(Self { - top_screen: RefCell::new(TopScreen), + top_screen: TopScreen::new(TopLeftScreen, TopRightScreen), bottom_screen: RefCell::new(BottomScreen), _service_handler, }) } - /// Creates a new Gfx instance with default init values - /// It's the same as calling: `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) + /// Creates a new [Gfx] instance with default init values + /// It's the same as calling: + /// `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)` pub fn init() -> Result { Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) } @@ -140,6 +164,36 @@ impl Gfx { } impl TopScreen { + fn new(left: TopLeftScreen, right: TopRightScreen) -> Self { + Self(RefCell::new(TopScreenInner { left, right })) + } + + pub fn borrow(&self) -> Ref<'_, TopScreenInner> { + self.0.borrow() + } + + pub fn borrow_mut(&self) -> RefMut<'_, TopScreenInner> { + self.0.borrow_mut() + } + + pub fn borrow_side(&self, side: Side) -> Ref<'_, dyn Screen> { + let borrow = self.0.borrow(); + match side { + Side::Left => Ref::map(borrow, |top| &top.left), + Side::Right => Ref::map(borrow, |top| &top.right), + } + } + + pub fn borrow_side_mut(&self, side: Side) -> RefMut<'_, dyn Screen> { + let borrow = self.0.borrow_mut(); + match side { + Side::Left => RefMut::map(borrow, |top| &mut top.left), + Side::Right => RefMut::map(borrow, |top| &mut top.right), + } + } +} + +impl TopScreenInner { /// Enable or disable the 3D stereoscopic effect pub fn set_3d_enabled(&mut self, enabled: bool) { unsafe { @@ -159,24 +213,6 @@ impl TopScreen { pub fn get_wide_mode(&self) -> bool { unsafe { ctru_sys::gfxIsWide() } } - - /// Returns a [`RawFrameBuffer`] for the given [`Side`] of the top screen. - /// - /// Note that the pointer of the framebuffer returned by this function can - /// change after each call to this function if double buffering is enabled. - pub fn get_raw_framebuffer(&mut self, side: Side) -> RawFrameBuffer { - RawFrameBuffer::for_screen_side(self, side) - } -} - -impl BottomScreen { - /// Returns a [`RawFrameBuffer`] for the bottom screen. - /// - /// Note that the pointer of the framebuffer returned by this function can - /// change after each call to this function if double buffering is enabled. - pub fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { - RawFrameBuffer::for_screen_side(self, Side::Left) - } } impl<'screen> RawFrameBuffer<'screen> { @@ -195,16 +231,46 @@ impl<'screen> RawFrameBuffer<'screen> { } } -impl Screen for TopScreen { +impl Screen for TopScreenInner { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + self.left.as_raw() + } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + // When dealing with the "whole" top screen, the left framebuffer is the + // one used for writing pixel data. + self.left.get_raw_framebuffer() + } +} + +impl Screen for TopLeftScreen { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_TOP } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + RawFrameBuffer::for_screen_side(self, Side::Left) + } +} + +impl Screen for TopRightScreen { + fn as_raw(&self) -> ctru_sys::gfxScreen_t { + ctru_sys::GFX_TOP + } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + RawFrameBuffer::for_screen_side(self, Side::Right) + } } impl Screen for BottomScreen { fn as_raw(&self) -> ctru_sys::gfxScreen_t { ctru_sys::GFX_BOTTOM } + + fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { + RawFrameBuffer::for_screen_side(self, Side::Left) + } } impl From for ctru_sys::gfx3dSide_t {