Skip to main content

gb/
joypad.rs

1//! The eight buttons.
2//!
3//! [`read`] returns a [`Buttons`], a bit per button and set where held. The
4//! hardware is the other way round, clearing the bit of a button that is down.
5//! See <https://gbdev.io/pandocs/Joypad_Input.html>.
6//!
7//! [`Pad`] is the usual way in. It keeps the previous reading, which turns
8//! "what is held" into "what changed".
9//!
10//! ```ignore
11//! let vblank = unsafe { ppu::Vblank::listen() };
12//! let mut pad = Pad::new();
13//! loop {
14//!     vblank.wait();
15//!     pad.poll();
16//!
17//!     if pad.just_pressed.a() {
18//!         jump();
19//!     }
20//!     x = x.wrapping_add_signed(pad.pressed.x() as i8);
21//! }
22//! ```
23//!
24//! # Reading from a handler
25//!
26//! [`read`] selects a row, reads it, then selects the other and reads that. A
27//! handler reading the joypad in between finishes with both rows off, and the
28//! half of the interrupted read that has not happened yet comes back as nothing
29//! pressed. Nothing reports that, so poll in one place.
30
31use bitfield_struct::bitfield;
32
33use crate::mmio::{JOYP, Joypad};
34
35/// The eight buttons, set where held.
36///
37/// The d-pad occupies the low nibble and the action buttons the high one, which
38/// is the layout GBDK's `J_` constants use.
39#[bitfield(u8)]
40#[derive(PartialEq, Eq)]
41pub struct Buttons {
42    /// Right on the d-pad.
43    pub right: bool,
44    /// Left on the d-pad.
45    pub left: bool,
46    /// Up on the d-pad.
47    pub up: bool,
48    /// Down on the d-pad.
49    pub down: bool,
50    /// The A button.
51    pub a: bool,
52    /// The B button.
53    pub b: bool,
54    /// The Select button.
55    pub select: bool,
56    /// The Start button.
57    pub start: bool,
58}
59
60/// Where the d-pad points left or right, as a number to add to a coordinate.
61///
62/// ```ignore
63/// x = x.wrapping_add_signed(pad.pressed.x() as i8 * SPEED);
64/// ```
65#[repr(i8)]
66#[derive(Clone, Copy, PartialEq, Eq, Debug)]
67pub enum DPadX {
68    /// Left, negative because screen coordinates grow to the right.
69    Left = -1,
70    /// Neither, or both at once.
71    Neutral = 0,
72    /// Right.
73    Right = 1,
74}
75
76/// Where the d-pad points up or down, as a number to add to a coordinate.
77#[repr(i8)]
78#[derive(Clone, Copy, PartialEq, Eq, Debug)]
79pub enum DPadY {
80    /// Up, negative because screen coordinates grow downwards.
81    Up = -1,
82    /// Neither, or both at once.
83    Neutral = 0,
84    /// Down.
85    Down = 1,
86}
87
88impl Buttons {
89    /// Where the d-pad points left or right.
90    #[inline]
91    pub const fn x(self) -> DPadX {
92        match (self.left(), self.right()) {
93            (true, false) => DPadX::Left,
94            (false, true) => DPadX::Right,
95            _ => DPadX::Neutral,
96        }
97    }
98
99    /// Where the d-pad points up or down.
100    #[inline]
101    pub const fn y(self) -> DPadY {
102        match (self.up(), self.down()) {
103            (true, false) => DPadY::Up,
104            (false, true) => DPadY::Down,
105            _ => DPadY::Neutral,
106        }
107    }
108}
109
110// Active low, so clearing a select bit is what turns that row on.
111const DPAD: Joypad = Joypad::new().with_buttons(true);
112const BUTTONS: Joypad = Joypad::new().with_dpad(true);
113const NEITHER: Joypad = Joypad::new().with_dpad(true).with_buttons(true);
114
115/// Read the buttons.
116pub fn read() -> Buttons {
117    // A select line takes time to settle, so a row is read more than once and
118    // only the last read counts. The counts are GBDK's. The second row gets more
119    // because both select lines move for it where only one moves for the first.
120    JOYP.write(DPAD);
121    JOYP.read();
122    let dpad = JOYP.read().into_bits() & 0x0F;
123
124    JOYP.write(BUTTONS);
125    JOYP.read();
126    JOYP.read();
127    JOYP.read();
128    JOYP.read();
129    JOYP.read();
130    let buttons = JOYP.read().into_bits() & 0x0F;
131
132    // With both rows off no input line can fall, so a press between calls
133    // cannot reach the joypad interrupt.
134    JOYP.write(NEITHER);
135
136    Buttons::from_bits(!((buttons << 4) | dpad))
137}
138
139/// The buttons, and what changed at the last [`poll`](Self::poll).
140pub struct Pad {
141    /// Held now.
142    pub pressed: Buttons,
143    /// Went down at the last poll.
144    pub just_pressed: Buttons,
145    /// Came up at the last poll.
146    pub just_released: Buttons,
147}
148
149impl Pad {
150    /// A pad with nothing held.
151    ///
152    /// A button already down when the first poll runs counts as newly pressed.
153    pub const fn new() -> Self {
154        Pad {
155            pressed: Buttons::new(),
156            just_pressed: Buttons::new(),
157            just_released: Buttons::new(),
158        }
159    }
160
161    /// Read the buttons and work out what changed.
162    #[inline]
163    pub fn poll(&mut self) {
164        let now = read().into_bits();
165        let was = self.pressed.into_bits();
166        self.just_pressed = Buttons::from_bits(now & !was);
167        self.just_released = Buttons::from_bits(!now & was);
168        self.pressed = Buttons::from_bits(now);
169    }
170}
171
172impl Default for Pad {
173    fn default() -> Self {
174        Self::new()
175    }
176}
177
178const _: () = {
179    assert!(DPAD.into_bits() == 0b0010_0000);
180    assert!(BUTTONS.into_bits() == 0b0001_0000);
181    assert!(NEITHER.into_bits() == 0b0011_0000);
182    assert!(Buttons::new().with_right(true).into_bits() == 0b0000_0001);
183    assert!(Buttons::new().with_a(true).into_bits() == 0b0001_0000);
184    assert!(Buttons::new().with_start(true).into_bits() == 0b1000_0000);
185};