use crate::input::{KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use crate::screen::CellPixelSize;
#[derive(Clone, Copy, Debug)]
pub(crate) enum CoordEncoding {
X10,
Sgr,
SgrPixel(CellPixelSize),
}
const MOUSE_BUTTON_LEFT: u32 = 0;
const MOUSE_BUTTON_MIDDLE: u32 = 1;
const MOUSE_BUTTON_RIGHT: u32 = 2;
const MOUSE_BUTTON_RELEASE: u32 = 3;
const MOUSE_DRAG_OFFSET: u32 = 32;
const MOUSE_MOTION: u32 = 35;
const MOUSE_SCROLL_UP: u32 = 64;
const MOUSE_SCROLL_DOWN: u32 = 65;
const MOUSE_SCROLL_LEFT: u32 = 66;
const MOUSE_SCROLL_RIGHT: u32 = 67;
const MOUSE_MOD_SHIFT: u32 = 4;
const MOUSE_MOD_ALT: u32 = 8;
const MOUSE_MOD_CTRL: u32 = 16;
const X10_OFFSET: u32 = 32;
pub(crate) fn mouse_to_bytes(event: &MouseEvent, encoding: CoordEncoding) -> Option<Vec<u8>> {
let button = match event.kind {
MouseEventKind::Down(MouseButton::Left) => MOUSE_BUTTON_LEFT,
MouseEventKind::Down(MouseButton::Middle) => MOUSE_BUTTON_MIDDLE,
MouseEventKind::Down(MouseButton::Right) => MOUSE_BUTTON_RIGHT,
MouseEventKind::Up(MouseButton::Left) => MOUSE_BUTTON_LEFT,
MouseEventKind::Up(MouseButton::Middle) => MOUSE_BUTTON_MIDDLE,
MouseEventKind::Up(MouseButton::Right) => MOUSE_BUTTON_RIGHT,
MouseEventKind::Drag(MouseButton::Left) => MOUSE_DRAG_OFFSET + MOUSE_BUTTON_LEFT,
MouseEventKind::Drag(MouseButton::Middle) => MOUSE_DRAG_OFFSET + MOUSE_BUTTON_MIDDLE,
MouseEventKind::Drag(MouseButton::Right) => MOUSE_DRAG_OFFSET + MOUSE_BUTTON_RIGHT,
MouseEventKind::Moved => MOUSE_MOTION,
MouseEventKind::ScrollUp => MOUSE_SCROLL_UP,
MouseEventKind::ScrollDown => MOUSE_SCROLL_DOWN,
MouseEventKind::ScrollLeft => MOUSE_SCROLL_LEFT,
MouseEventKind::ScrollRight => MOUSE_SCROLL_RIGHT,
};
let mut cb: u32 = button;
if event.modifiers.contains(KeyModifiers::SHIFT) {
cb |= MOUSE_MOD_SHIFT;
}
if event.modifiers.contains(KeyModifiers::ALT) {
cb |= MOUSE_MOD_ALT;
}
if event.modifiers.contains(KeyModifiers::CONTROL) {
cb |= MOUSE_MOD_CTRL;
}
let final_char = match event.kind {
MouseEventKind::Up(_) => 'm',
_ => 'M',
};
match encoding {
CoordEncoding::Sgr => {
let col = u32::from(event.col) + 1;
let row = u32::from(event.row) + 1;
Some(format!("\x1b[<{cb};{col};{row}{final_char}").into_bytes())
}
CoordEncoding::SgrPixel(cell) => {
let x = u32::from(event.col) * u32::from(cell.width) + 1;
let y = u32::from(event.row) * u32::from(cell.height) + 1;
Some(format!("\x1b[<{cb};{x};{y}{final_char}").into_bytes())
}
CoordEncoding::X10 => {
let col = u32::from(event.col) + 1;
let row = u32::from(event.row) + 1;
let cx = u8::try_from(col.saturating_add(X10_OFFSET)).ok()?;
let cy = u8::try_from(row.saturating_add(X10_OFFSET)).ok()?;
let cb_byte = u8::try_from(cb.saturating_add(X10_OFFSET)).ok()?;
let cb_byte = match event.kind {
MouseEventKind::Up(_) => (X10_OFFSET + MOUSE_BUTTON_RELEASE) as u8,
_ => cb_byte,
};
Some(vec![0x1b, b'[', b'M', cb_byte, cx, cy])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mouse_sgr_left_click() {
let event = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
row: 4,
col: 9,
modifiers: KeyModifiers::NONE,
};
assert_eq!(
mouse_to_bytes(&event, CoordEncoding::Sgr),
Some(b"\x1b[<0;10;5M".to_vec())
);
}
#[test]
fn mouse_sgr_left_release() {
let event = MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
row: 4,
col: 9,
modifiers: KeyModifiers::NONE,
};
assert_eq!(
mouse_to_bytes(&event, CoordEncoding::Sgr),
Some(b"\x1b[<0;10;5m".to_vec())
);
}
#[test]
fn mouse_sgr_scroll_up() {
let event = MouseEvent {
kind: MouseEventKind::ScrollUp,
row: 0,
col: 0,
modifiers: KeyModifiers::NONE,
};
assert_eq!(
mouse_to_bytes(&event, CoordEncoding::Sgr),
Some(b"\x1b[<64;1;1M".to_vec())
);
}
#[test]
fn mouse_sgr_shift_click() {
let event = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
row: 0,
col: 0,
modifiers: KeyModifiers::SHIFT,
};
assert_eq!(
mouse_to_bytes(&event, CoordEncoding::Sgr),
Some(b"\x1b[<4;1;1M".to_vec())
);
}
#[test]
fn mouse_x10_left_click() {
let event = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
row: 0,
col: 0,
modifiers: KeyModifiers::NONE,
};
assert_eq!(
mouse_to_bytes(&event, CoordEncoding::X10),
Some(vec![0x1b, b'[', b'M', 32, 33, 33])
);
}
#[test]
fn mouse_x10_release() {
let event = MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
row: 0,
col: 0,
modifiers: KeyModifiers::NONE,
};
assert_eq!(
mouse_to_bytes(&event, CoordEncoding::X10),
Some(vec![0x1b, b'[', b'M', 35, 33, 33])
);
}
#[test]
fn mouse_drag() {
let event = MouseEvent {
kind: MouseEventKind::Drag(MouseButton::Left),
row: 10,
col: 5,
modifiers: KeyModifiers::NONE,
};
assert_eq!(
mouse_to_bytes(&event, CoordEncoding::Sgr),
Some(b"\x1b[<32;6;11M".to_vec())
);
}
}