feat(input-server): expand protocol with pointer, scroll, and gamepad events
This commit is contained in:
@@ -8,13 +8,47 @@ pub enum Key {
|
|||||||
Right,
|
Right,
|
||||||
Enter,
|
Enter,
|
||||||
Escape,
|
Escape,
|
||||||
|
Space,
|
||||||
|
Backspace,
|
||||||
|
Tab,
|
||||||
Char(u8),
|
Char(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum GamepadButton {
|
||||||
|
South,
|
||||||
|
East,
|
||||||
|
North,
|
||||||
|
West,
|
||||||
|
DpadUp,
|
||||||
|
DpadDown,
|
||||||
|
DpadLeft,
|
||||||
|
DpadRight,
|
||||||
|
Start,
|
||||||
|
Select,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Axis {
|
||||||
|
LeftX,
|
||||||
|
LeftY,
|
||||||
|
RightX,
|
||||||
|
RightY,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum InputEvent {
|
pub enum InputEvent {
|
||||||
KeyDown(Key),
|
KeyDown(Key),
|
||||||
KeyUp(Key),
|
KeyUp(Key),
|
||||||
|
|
||||||
|
PointerDown { id: u8, x: u16, y: u16 },
|
||||||
|
PointerMove { id: u8, x: u16, y: u16 },
|
||||||
|
PointerUp { id: u8, x: u16, y: u16 },
|
||||||
|
|
||||||
|
Scroll { dx: i16, dy: i16 },
|
||||||
|
|
||||||
|
GamepadButton { button: GamepadButton, pressed: bool },
|
||||||
|
GamepadAxis { axis: Axis, value: i16 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@@ -48,6 +82,16 @@ impl Decode for InputRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Event type tags for encoding
|
||||||
|
const EV_KEY_DOWN: usize = 1;
|
||||||
|
const EV_KEY_UP: usize = 2;
|
||||||
|
const EV_PTR_DOWN: usize = 3;
|
||||||
|
const EV_PTR_MOVE: usize = 4;
|
||||||
|
const EV_PTR_UP: usize = 5;
|
||||||
|
const EV_SCROLL: usize = 6;
|
||||||
|
const EV_GAMEPAD_BTN: usize = 7;
|
||||||
|
const EV_GAMEPAD_AXIS: usize = 8;
|
||||||
|
|
||||||
fn encode_key(key: Key) -> (usize, usize) {
|
fn encode_key(key: Key) -> (usize, usize) {
|
||||||
match key {
|
match key {
|
||||||
Key::Up => (0, 0),
|
Key::Up => (0, 0),
|
||||||
@@ -56,7 +100,10 @@ fn encode_key(key: Key) -> (usize, usize) {
|
|||||||
Key::Right => (3, 0),
|
Key::Right => (3, 0),
|
||||||
Key::Enter => (4, 0),
|
Key::Enter => (4, 0),
|
||||||
Key::Escape => (5, 0),
|
Key::Escape => (5, 0),
|
||||||
Key::Char(c) => (6, c as usize),
|
Key::Space => (6, 0),
|
||||||
|
Key::Backspace => (7, 0),
|
||||||
|
Key::Tab => (8, 0),
|
||||||
|
Key::Char(c) => (9, c as usize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +115,68 @@ fn decode_key(kind: usize, data: usize) -> Option<Key> {
|
|||||||
3 => Some(Key::Right),
|
3 => Some(Key::Right),
|
||||||
4 => Some(Key::Enter),
|
4 => Some(Key::Enter),
|
||||||
5 => Some(Key::Escape),
|
5 => Some(Key::Escape),
|
||||||
6 => Some(Key::Char(data as u8)),
|
6 => Some(Key::Space),
|
||||||
|
7 => Some(Key::Backspace),
|
||||||
|
8 => Some(Key::Tab),
|
||||||
|
9 => Some(Key::Char(data as u8)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_pointer(id: u8, x: u16, y: u16) -> (usize, usize) {
|
||||||
|
(id as usize, (x as usize) | ((y as usize) << 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_pointer(w1: usize, w2: usize) -> (u8, u16, u16) {
|
||||||
|
(w1 as u8, (w2 & 0xFFFF) as u16, ((w2 >> 16) & 0xFFFF) as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_gamepad_button(button: GamepadButton) -> usize {
|
||||||
|
match button {
|
||||||
|
GamepadButton::South => 0,
|
||||||
|
GamepadButton::East => 1,
|
||||||
|
GamepadButton::North => 2,
|
||||||
|
GamepadButton::West => 3,
|
||||||
|
GamepadButton::DpadUp => 4,
|
||||||
|
GamepadButton::DpadDown => 5,
|
||||||
|
GamepadButton::DpadLeft => 6,
|
||||||
|
GamepadButton::DpadRight => 7,
|
||||||
|
GamepadButton::Start => 8,
|
||||||
|
GamepadButton::Select => 9,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_gamepad_button(v: usize) -> Option<GamepadButton> {
|
||||||
|
match v {
|
||||||
|
0 => Some(GamepadButton::South),
|
||||||
|
1 => Some(GamepadButton::East),
|
||||||
|
2 => Some(GamepadButton::North),
|
||||||
|
3 => Some(GamepadButton::West),
|
||||||
|
4 => Some(GamepadButton::DpadUp),
|
||||||
|
5 => Some(GamepadButton::DpadDown),
|
||||||
|
6 => Some(GamepadButton::DpadLeft),
|
||||||
|
7 => Some(GamepadButton::DpadRight),
|
||||||
|
8 => Some(GamepadButton::Start),
|
||||||
|
9 => Some(GamepadButton::Select),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_axis(axis: Axis) -> usize {
|
||||||
|
match axis {
|
||||||
|
Axis::LeftX => 0,
|
||||||
|
Axis::LeftY => 1,
|
||||||
|
Axis::RightX => 2,
|
||||||
|
Axis::RightY => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_axis(v: usize) -> Option<Axis> {
|
||||||
|
match v {
|
||||||
|
0 => Some(Axis::LeftX),
|
||||||
|
1 => Some(Axis::LeftY),
|
||||||
|
2 => Some(Axis::RightX),
|
||||||
|
3 => Some(Axis::RightY),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,14 +185,37 @@ impl Encode for InputResponse {
|
|||||||
fn encode(&self) -> [usize; 4] {
|
fn encode(&self) -> [usize; 4] {
|
||||||
match self {
|
match self {
|
||||||
InputResponse::NoEvent => [0, 0, 0, 0],
|
InputResponse::NoEvent => [0, 0, 0, 0],
|
||||||
InputResponse::Event(InputEvent::KeyDown(key)) => {
|
InputResponse::Event(event) => match event {
|
||||||
let (kind, data) = encode_key(*key);
|
InputEvent::KeyDown(key) => {
|
||||||
[1, kind, data, 0]
|
let (k, d) = encode_key(*key);
|
||||||
|
[EV_KEY_DOWN, k, d, 0]
|
||||||
}
|
}
|
||||||
InputResponse::Event(InputEvent::KeyUp(key)) => {
|
InputEvent::KeyUp(key) => {
|
||||||
let (kind, data) = encode_key(*key);
|
let (k, d) = encode_key(*key);
|
||||||
[2, kind, data, 0]
|
[EV_KEY_UP, k, d, 0]
|
||||||
}
|
}
|
||||||
|
InputEvent::PointerDown { id, x, y } => {
|
||||||
|
let (w1, w2) = encode_pointer(*id, *x, *y);
|
||||||
|
[EV_PTR_DOWN, w1, w2, 0]
|
||||||
|
}
|
||||||
|
InputEvent::PointerMove { id, x, y } => {
|
||||||
|
let (w1, w2) = encode_pointer(*id, *x, *y);
|
||||||
|
[EV_PTR_MOVE, w1, w2, 0]
|
||||||
|
}
|
||||||
|
InputEvent::PointerUp { id, x, y } => {
|
||||||
|
let (w1, w2) = encode_pointer(*id, *x, *y);
|
||||||
|
[EV_PTR_UP, w1, w2, 0]
|
||||||
|
}
|
||||||
|
InputEvent::Scroll { dx, dy } => {
|
||||||
|
[EV_SCROLL, *dx as usize, *dy as usize, 0]
|
||||||
|
}
|
||||||
|
InputEvent::GamepadButton { button, pressed } => {
|
||||||
|
[EV_GAMEPAD_BTN, encode_gamepad_button(*button), *pressed as usize, 0]
|
||||||
|
}
|
||||||
|
InputEvent::GamepadAxis { axis, value } => {
|
||||||
|
[EV_GAMEPAD_AXIS, encode_axis(*axis), *value as usize, 0]
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,14 +224,46 @@ impl Decode for InputResponse {
|
|||||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||||
match words[0] {
|
match words[0] {
|
||||||
0 => Some(InputResponse::NoEvent),
|
0 => Some(InputResponse::NoEvent),
|
||||||
1 => {
|
EV_KEY_DOWN => {
|
||||||
let key = decode_key(words[1], words[2])?;
|
let key = decode_key(words[1], words[2])?;
|
||||||
Some(InputResponse::Event(InputEvent::KeyDown(key)))
|
Some(InputResponse::Event(InputEvent::KeyDown(key)))
|
||||||
}
|
}
|
||||||
2 => {
|
EV_KEY_UP => {
|
||||||
let key = decode_key(words[1], words[2])?;
|
let key = decode_key(words[1], words[2])?;
|
||||||
Some(InputResponse::Event(InputEvent::KeyUp(key)))
|
Some(InputResponse::Event(InputEvent::KeyUp(key)))
|
||||||
}
|
}
|
||||||
|
EV_PTR_DOWN => {
|
||||||
|
let (id, x, y) = decode_pointer(words[1], words[2]);
|
||||||
|
Some(InputResponse::Event(InputEvent::PointerDown { id, x, y }))
|
||||||
|
}
|
||||||
|
EV_PTR_MOVE => {
|
||||||
|
let (id, x, y) = decode_pointer(words[1], words[2]);
|
||||||
|
Some(InputResponse::Event(InputEvent::PointerMove { id, x, y }))
|
||||||
|
}
|
||||||
|
EV_PTR_UP => {
|
||||||
|
let (id, x, y) = decode_pointer(words[1], words[2]);
|
||||||
|
Some(InputResponse::Event(InputEvent::PointerUp { id, x, y }))
|
||||||
|
}
|
||||||
|
EV_SCROLL => {
|
||||||
|
Some(InputResponse::Event(InputEvent::Scroll {
|
||||||
|
dx: words[1] as i16,
|
||||||
|
dy: words[2] as i16,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
EV_GAMEPAD_BTN => {
|
||||||
|
let button = decode_gamepad_button(words[1])?;
|
||||||
|
Some(InputResponse::Event(InputEvent::GamepadButton {
|
||||||
|
button,
|
||||||
|
pressed: words[2] != 0,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
EV_GAMEPAD_AXIS => {
|
||||||
|
let axis = decode_axis(words[1])?;
|
||||||
|
Some(InputResponse::Event(InputEvent::GamepadAxis {
|
||||||
|
axis,
|
||||||
|
value: words[2] as i16,
|
||||||
|
}))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ fn wait_request_roundtrip() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn key_down_response_roundtrip() {
|
fn key_down_roundtrip() {
|
||||||
let resp = InputResponse::Event(InputEvent::KeyDown(Key::Up));
|
let resp = InputResponse::Event(InputEvent::KeyDown(Key::Up));
|
||||||
let words = resp.encode();
|
let words = resp.encode();
|
||||||
assert_eq!(InputResponse::decode(words), Some(resp));
|
assert_eq!(InputResponse::decode(words), Some(resp));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn key_up_response_roundtrip() {
|
fn key_up_roundtrip() {
|
||||||
let resp = InputResponse::Event(InputEvent::KeyUp(Key::Left));
|
let resp = InputResponse::Event(InputEvent::KeyUp(Key::Left));
|
||||||
let words = resp.encode();
|
let words = resp.encode();
|
||||||
assert_eq!(InputResponse::decode(words), Some(resp));
|
assert_eq!(InputResponse::decode(words), Some(resp));
|
||||||
@@ -34,12 +34,84 @@ fn char_key_roundtrip() {
|
|||||||
assert_eq!(InputResponse::decode(words), Some(resp));
|
assert_eq!(InputResponse::decode(words), Some(resp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn space_tab_backspace_roundtrip() {
|
||||||
|
for key in [Key::Space, Key::Tab, Key::Backspace] {
|
||||||
|
let resp = InputResponse::Event(InputEvent::KeyDown(key));
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_event_roundtrip() {
|
fn no_event_roundtrip() {
|
||||||
let words = InputResponse::NoEvent.encode();
|
let words = InputResponse::NoEvent.encode();
|
||||||
assert_eq!(InputResponse::decode(words), Some(InputResponse::NoEvent));
|
assert_eq!(InputResponse::decode(words), Some(InputResponse::NoEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pointer_down_roundtrip() {
|
||||||
|
let resp = InputResponse::Event(InputEvent::PointerDown { id: 0, x: 100, y: 200 });
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pointer_move_roundtrip() {
|
||||||
|
let resp = InputResponse::Event(InputEvent::PointerMove { id: 1, x: 300, y: 400 });
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pointer_up_roundtrip() {
|
||||||
|
let resp = InputResponse::Event(InputEvent::PointerUp { id: 2, x: 50, y: 60 });
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scroll_roundtrip() {
|
||||||
|
let resp = InputResponse::Event(InputEvent::Scroll { dx: -3, dy: 5 });
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gamepad_button_roundtrip() {
|
||||||
|
let resp = InputResponse::Event(InputEvent::GamepadButton {
|
||||||
|
button: GamepadButton::South,
|
||||||
|
pressed: true,
|
||||||
|
});
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gamepad_button_released_roundtrip() {
|
||||||
|
let resp = InputResponse::Event(InputEvent::GamepadButton {
|
||||||
|
button: GamepadButton::Start,
|
||||||
|
pressed: false,
|
||||||
|
});
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gamepad_axis_roundtrip() {
|
||||||
|
let resp = InputResponse::Event(InputEvent::GamepadAxis {
|
||||||
|
axis: Axis::LeftX,
|
||||||
|
value: -128,
|
||||||
|
});
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_gamepad_buttons_roundtrip() {
|
||||||
|
let buttons = [
|
||||||
|
GamepadButton::South, GamepadButton::East, GamepadButton::North, GamepadButton::West,
|
||||||
|
GamepadButton::DpadUp, GamepadButton::DpadDown, GamepadButton::DpadLeft, GamepadButton::DpadRight,
|
||||||
|
GamepadButton::Start, GamepadButton::Select,
|
||||||
|
];
|
||||||
|
for button in buttons {
|
||||||
|
let resp = InputResponse::Event(InputEvent::GamepadButton { button, pressed: true });
|
||||||
|
assert_eq!(InputResponse::decode(resp.encode()), Some(resp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_request_returns_none() {
|
fn invalid_request_returns_none() {
|
||||||
assert!(InputRequest::decode([99, 0, 0, 0]).is_none());
|
assert!(InputRequest::decode([99, 0, 0, 0]).is_none());
|
||||||
|
|||||||
Reference in New Issue
Block a user