diff --git a/CHANGELOG.md b/CHANGELOG.md index b57a929..c8e5b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,19 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- ... - ### Removed -- ... - ### Changed -- ... - ### Fixed -- ... + +## [0.2.0] - 2019-05-12 +### Added +- Joystick / controller support with API for emulation (not complete yet). ## [0.1.0] - 2019-05-01 ### Added -- Getting user information (Linux, Windows, MacOS) -- Playing / recording audio (Linux) -- Filesystem loading / saving ZIP files (Linux, Windows) +- Getting user information (Linux, Windows, MacOS). +- Playing / recording audio (Linux). +- Filesystem loading / saving ZIP files (Linux, Windows). diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5990d23..f6e7c56 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,3 +1,2 @@ # Contributors - -* [OxyDeadbeef](https://github.com/OxyDeadbeef) +- [JeronAldaron](https://github.com/JeronAldaron) diff --git a/Cargo.toml b/Cargo.toml index 9391758..1052f88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ [package] name = "cala" -version = "0.1.0" +version = "0.2.0" authors = ["Jeron Aldaron Lau "] edition = "2018" @@ -25,3 +25,4 @@ whoami = "0.5" # user wavy = "0.1" # audio stronghold = "0.2" # file serde = "1.0" +stick = "0.5" # joystick / controller diff --git a/README.md b/README.md index 5335f22..8cee6f9 100644 --- a/README.md +++ b/README.md @@ -11,24 +11,25 @@ Easily create cross-platform applications. Some common tasks are not easily por Cala is a platform-agnostic system interface for hardware IO. This means that eventually, Cala should support all of the different hardware that's connected to your computer. Cala is designed so that it talks to the operating system to interface with the hardware, so no special permissions are needed for your application. ## Features -* Targeted Platforms: Linux (includes Raspberry Pi), MacOS, Redox, Android, Windows, iOS, Web (WASM), Nintendo Switch, XBox, PlayStation, FreeBSD, others (Maybe FreeDOS for fun 😉️). -* Getting user information (Linux, Windows, MacOS) -* Playing / recording audio (Linux) -* Filesystem loading / saving ZIP files (Linux, Windows) -* Hardware acceleration - SIMD, GPU (NOT IMPLEMENTED YET) -* Clock - Date, Time of day, Timer (NOT IMPLEMENTED YET) -* GUI - Render, Mouse & Keyboard (NOT IMPLEMENTED YET) -* Game Controller - JoyStick (NOT IMPLEMENTED YET) -* Camera - Webcam (NOT IMPLEMENTED YET) -* Network - Bluetooth & Wifi Direct (NOT IMPLEMENTED YET) +- Targeted Platforms: Linux (includes Raspberry Pi), MacOS, Redox, Android, Windows, iOS, Web (WASM), Nintendo Switch, XBox, PlayStation, FreeBSD, others (Maybe FreeDOS for fun 😉️). +- Getting user information (Linux, Windows, MacOS) +- Playing / recording audio (Linux) +- Filesystem loading / saving ZIP files (Linux, Windows) +- Game Controller - JoyStick (Linux) +- Hardware acceleration - SIMD, GPU (NOT IMPLEMENTED YET) +- Clock - Date, Time of day, Timer (NOT IMPLEMENTED YET) +- GUI - Render, Mouse & Keyboard (NOT IMPLEMENTED YET) +- Camera - Webcam (NOT IMPLEMENTED YET) +- Network - Bluetooth & Wifi Direct (NOT IMPLEMENTED YET) ## Getting Started -* TODO +- TODO ## Links -* [Website](https://jeronaldaron.github.io/cala/) -* [Cargo](https://crates.io/crates/cala) -* [Documentation](https://docs.rs/cala) -* [Change Log](https://jeronaldaron.github.io/cala/CHANGELOG) -* [Contributors](https://jeronaldaron.github.io/cala/CONTRIBUTORS) -* [Code of Conduct](https://jeronaldaron.github.io/cala/CODEOFCONDUCT) +- [Website](https://jeronaldaron.github.io/cala/) +- [Cargo](https://crates.io/crates/cala) +- [Documentation](https://docs.rs/cala) +- [Change Log](https://jeronaldaron.github.io/cala/CHANGELOG) +- [Contributors](https://jeronaldaron.github.io/cala/CONTRIBUTORS) +- [Code of Conduct](https://jeronaldaron.github.io/cala/CODEOFCONDUCT) +- [Join Zulip Chat](https://plopgrizzly.zulipchat.com/join/pp13s6clnexk03tvlnrtjvi1/) diff --git a/examples/controller.rs b/examples/controller.rs new file mode 100644 index 0000000..00e658c --- /dev/null +++ b/examples/controller.rs @@ -0,0 +1,17 @@ +//! Get joystick input. + +use cala::*; + +fn main() { + let mut app = App::new(()); + + let layout = cala::ControllerLayout::new().joy(false).abxy(false); + + loop { + for id in 0..app.controller_update() { + let state = app.controller_get(id, &layout); + println!("{}: {:?}", id, state); + } + std::thread::sleep(std::time::Duration::from_millis(16)); + } +} diff --git a/icon.svg b/icon.svg index 71f5c40..9243018 100644 --- a/icon.svg +++ b/icon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/res/controller.png b/res/controller.png index 50bf279..43fe40f 100644 Binary files a/res/controller.png and b/res/controller.png differ diff --git a/src/controller.rs b/src/controller.rs new file mode 100644 index 0000000..cd63f9b --- /dev/null +++ b/src/controller.rs @@ -0,0 +1,177 @@ +pub(crate) use stick::Port as ControllerPort; +pub(crate) use stick::Btn; + +pub(crate) enum Axis { + JoyXY, + CamXY, + Lrt, + Pitch, + Yaw, +} + +pub(crate) enum Btns { + Abxy, + Dpad, + Quit, + Menu, + Wz, + Lr, + Dc, +} + +/// Select which buttons and axis you want on your controller. +/// +/// Button names are from this contoller layout: +/// +/// +pub struct ControllerLayout { + pub(crate) joy: Option, + pub(crate) cam: Option, + pub(crate) lrt: Option, + pub(crate) pitch: Option, + pub(crate) yaw: Option, + pub(crate) abxy: Option, + pub(crate) dpad: Option, + pub(crate) quit: Option, + pub(crate) menu: Option, + pub(crate) wz: Option, + pub(crate) cd: Option, + pub(crate) lrb: Option, + pub(crate) axis: Vec, + pub(crate) btns: Vec, +} + +impl ControllerLayout { + /// Create a new `ControllerLayout`. + pub fn new() -> Self { + ControllerLayout { + joy: None, + cam: None, + lrt: None, + pitch: None, + yaw: None, + abxy: None, + dpad: None, + quit: None, + menu: None, + wz: None, + cd: None, + lrb: None, + axis: vec![], + btns: vec![], + } + } + + /// Request an x & y axis for main joystick. + pub fn joy(mut self, optional: bool) -> Self { + // Don't do twice! + if self.joy.is_some() { return self; } + + self.joy = Some(optional); + self.axis.push(Axis::JoyXY); + self + } + + /// Request an x & y axis for camera (secondary) joystick. + pub fn cam(mut self, optional: bool) -> Self { + // Don't do twice! + if self.cam.is_some() { return self; } + + self.cam = Some(optional); + self.axis.push(Axis::CamXY); + self + } + + /// Request an x & y axis for camera (secondary) joystick. + pub fn lrt(mut self, optional: bool) -> Self { + // Don't do twice! + if self.lrt.is_some() { return self; } + + self.cam = Some(optional); + self.axis.push(Axis::Lrt); + self + } + + /// Request an axis for pitch (stationary throttle). + pub fn pitch(mut self, optional: bool) -> Self { + // Don't do twice! + if self.pitch.is_some() { return self; } + + self.pitch = Some(optional); + self.axis.push(Axis::Pitch); + self + } + + /// Request an axis for yaw (stationary throttle). + pub fn yaw(mut self, optional: bool) -> Self { + // Don't do twice! + if self.yaw.is_some() { return self; } + + self.yaw = Some(optional); + self.axis.push(Axis::Yaw); + self + } + + /// Request ABXY buttons. + pub fn abxy(mut self, optional: bool) -> Self { + if self.abxy.is_some() { return self; } + + self.abxy = Some(optional); + self.btns.push(Btns::Abxy); + self + } + + /// Request arrow buttons. + pub fn arrow(mut self, optional: bool) -> Self { + if self.dpad.is_some() { return self; } + + self.dpad = Some(optional); + self.btns.push(Btns::Dpad); + self + } + + /// Request Back button. + pub fn back(mut self, optional: bool) -> Self { + if self.quit.is_some() { return self; } + + self.quit = Some(optional); + self.btns.push(Btns::Quit); + self + } + + /// Request Menu button. + pub fn menu(mut self, optional: bool) -> Self { + if self.menu.is_some() { return self; } + + self.menu = Some(optional); + self.btns.push(Btns::Menu); + self + } + + /// Request W & Z buttons. + pub fn wz(mut self, optional: bool) -> Self { + if self.wz.is_some() { return self; } + + self.wz = Some(optional); + self.btns.push(Btns::Wz); + self + } + + /// Request D & C buttons (Push in joystick). + pub fn dc(mut self, optional: bool) -> Self { + if self.cd.is_some() { return self; } + + self.cd = Some(optional); + self.btns.push(Btns::Dc); + self + } + + /// Request L & R buttons. + pub fn lrb(mut self, optional: bool) -> Self { + if self.lrb.is_some() { return self; } + + self.lrb = Some(optional); + self.btns.push(Btns::Lr); + self + } +} diff --git a/src/dive.rs b/src/dive.rs index 33f1f34..5f6aa55 100644 --- a/src/dive.rs +++ b/src/dive.rs @@ -1,13 +1,21 @@ use crate::user::*; use crate::audio::*; +use crate::controller::*; /// The Dive Application Context. pub struct App { + // User user: User, + // Audio mic: MicrophoneSystem, speaker: SpeakerSystem, + // Store file: T, changed: bool, + // Controller + controller_port: ControllerPort, + axis: [f32; 8], + btns: [bool; 16], } impl App { @@ -19,6 +27,9 @@ impl App { speaker: SpeakerSystem::new(SampleRate::Normal).unwrap(), file, changed: false, + controller_port: ControllerPort::new(), + axis: [0.0; 8], + btns: [false; 16], } } @@ -71,4 +82,112 @@ impl App { pub fn play(&mut self, callback: &mut FnMut() -> AudioSample) { self.speaker.play(callback); } + + /// Return the number of controllers. + pub fn controller_update(&mut self) -> u16 { + self.controller_port.update() + } + + /// Get the state of a controller from the requested controller layout. + pub fn controller_get(&mut self, id: u16, layout: &ControllerLayout) -> (&[f32], &[bool]) { + let mut state = self.controller_port.get(id); + let mut i_axis = 0; + let mut i_btns = 0; + + for axis in 0..layout.axis.len() { + match layout.axis[axis] { + Axis::JoyXY => { + // TODO: Fallback. + let (x, y) = state.joy().unwrap_or((0.0, 0.0)); + self.axis[i_axis] = x; + i_axis += 1; + self.axis[i_axis] = y; + i_axis += 1; + }, + Axis::CamXY => { + // TODO: Fallback. + let (x, y) = state.cam().unwrap_or((0.0, 0.0)); + self.axis[i_axis] = x; + i_axis += 1; + self.axis[i_axis] = y; + i_axis += 1; + }, + Axis::Lrt => { + // TODO: Fallback. +// let (x, y) = state.lr().unwrap_or((0.0, 0.0)); + // TODO: Not supported yet. + let (x, y) = (0.0, 0.0); + self.axis[i_axis] = x; + i_axis += 1; + self.axis[i_axis] = y; + i_axis += 1; + }, + Axis::Pitch => { + // TODO: Fallback. + let x = state.pitch().unwrap_or(0.0); + self.axis[i_axis] = x; + i_axis += 1; + }, + Axis::Yaw => { +// let x = state.yaw().unwrap_or(0.0); + // TODO: Not supported yet. + self.axis[i_axis] = 0.0; + i_axis += 1; + }, + } + } + + for btn in 0..layout.btns.len() { + match layout.btns[btn] { + Btns::Abxy => { + self.btns[i_btns] = state.btn(Btn::A).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::B).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::X).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::Y).unwrap_or(false); + i_btns += 1; + }, + Btns::Dpad => { + self.btns[i_btns] = state.btn(Btn::Up).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::Down).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::Left).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::Right).unwrap_or(false); + i_btns += 1; + }, + Btns::Quit => { + self.btns[i_btns] = state.btn(Btn::E).unwrap_or(false); + i_btns += 1; + }, + Btns::Menu => { + self.btns[i_btns] = state.btn(Btn::F).unwrap_or(false); + i_btns += 1; + }, + Btns::Wz => { + self.btns[i_btns] = state.btn(Btn::W).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::Z).unwrap_or(false); + i_btns += 1; + }, + Btns::Lr => { + self.btns[i_btns] = state.btn(Btn::L).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::R).unwrap_or(false); + i_btns += 1; + }, + Btns::Dc => { + self.btns[i_btns] = state.btn(Btn::D).unwrap_or(false); + i_btns += 1; + self.btns[i_btns] = state.btn(Btn::C).unwrap_or(false); + i_btns += 1; + } + } + } + + (&self.axis[..i_axis], &self.btns[..i_btns]) + } } diff --git a/src/lib.rs b/src/lib.rs index c18430d..e36ff82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ //!

-//! Dive +//! Cala //!

//! //! ### Note -//! Dive is a complete redesign of previous library [ADI](https://crates.io/crates/adi). It is still in it's early stages. +//! Cala is a complete redesign of previous library [ADI](https://crates.io/crates/adi). It is still in it's early stages. //! //! # About //! Easily create cross-platform applications. Some common tasks are not easily portable across different platforms, and this crate hopes to fix that. That way you don't have to worry about how to port your GUI, audio, or bluetooth interface, etc. and can dive right in to building your application's content! @@ -15,10 +15,10 @@ //! * Getting user information (Linux, Windows, MacOS) //! * Playing / recording audio (Linux) //! * Filesystem loading / saving ZIP files (Linux, Windows) +//! * Game Controller - JoyStick (Linux) //! * Hardware acceleration - SIMD, GPU (NOT IMPLEMENTED YET) //! * Clock - Date, Time of day, Timer (NOT IMPLEMENTED YET) //! * GUI - Render, Mouse & Keyboard (NOT IMPLEMENTED YET) -//! * Game Controller - JoyStick (NOT IMPLEMENTED YET) //! * Camera - Webcam (NOT IMPLEMENTED YET) //! * Network - Bluetooth & Wifi Direct (NOT IMPLEMENTED YET) //! @@ -27,16 +27,18 @@ #![warn(missing_docs)] #![doc( - html_logo_url = "https://dive.ga/dive/icon.svg", - html_favicon_url = "https://dive.ga/dive/icon.svg" + html_logo_url = "https://jeronaldaron.github.io/cala/icon.svg", + html_favicon_url = "https://jeronaldaron.github.io/cala/icon.svg" )] mod user; mod dive; mod audio; +mod controller; pub use dive::App; pub use audio::AudioSample; +pub use controller::ControllerLayout; #[cfg(test)] mod tests {