use teksilo_canvas::{Point, Size};
use teksilo_tokens::PenKind;
use crate::pen::{PenButtons, PenPacket};
pub mod layout {
pub const POINTER_TYPE: usize = 0;
pub const POINTER_ID: usize = 4;
pub const POINTER_FLAGS: usize = 12;
pub const PIXEL_X: usize = 32;
pub const PIXEL_Y: usize = 36;
pub const TIME: usize = 64;
pub const HISTORY_COUNT: usize = 68;
pub const POINTER_INFO_SIZE: usize = 96;
pub const PEN_FLAGS: usize = 96;
pub const PEN_MASK: usize = 100;
pub const PEN_PRESSURE: usize = 104;
pub const PEN_ROTATION: usize = 108;
pub const PEN_TILT_X: usize = 112;
pub const PEN_TILT_Y: usize = 116;
pub const PEN_INFO_SIZE: usize = 120;
pub const TOUCH_MASK: usize = 100;
pub const TOUCH_CONTACT_LEFT: usize = 104;
pub const TOUCH_CONTACT_TOP: usize = 108;
pub const TOUCH_CONTACT_RIGHT: usize = 112;
pub const TOUCH_CONTACT_BOTTOM: usize = 116;
pub const TOUCH_ORIENTATION: usize = 136;
pub const TOUCH_PRESSURE: usize = 140;
pub const TOUCH_INFO_SIZE: usize = 144;
}
#[cfg(target_os = "windows")]
mod abi_assertions {
use super::layout::*;
use core::mem::{offset_of, size_of};
use windows::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO};
const _: () = assert!(size_of::<POINTER_INFO>() == POINTER_INFO_SIZE);
const _: () = assert!(size_of::<POINTER_PEN_INFO>() == PEN_INFO_SIZE);
const _: () = assert!(size_of::<POINTER_TOUCH_INFO>() == TOUCH_INFO_SIZE);
const _: () = assert!(offset_of!(POINTER_INFO, pointerType) == POINTER_TYPE);
const _: () = assert!(offset_of!(POINTER_INFO, pointerId) == POINTER_ID);
const _: () = assert!(offset_of!(POINTER_INFO, pointerFlags) == POINTER_FLAGS);
const _: () = assert!(offset_of!(POINTER_INFO, ptPixelLocation) == PIXEL_X);
const _: () = assert!(offset_of!(POINTER_INFO, dwTime) == TIME);
const _: () = assert!(offset_of!(POINTER_INFO, historyCount) == HISTORY_COUNT);
const _: () = assert!(offset_of!(POINTER_PEN_INFO, penFlags) == PEN_FLAGS);
const _: () = assert!(offset_of!(POINTER_PEN_INFO, penMask) == PEN_MASK);
const _: () = assert!(offset_of!(POINTER_PEN_INFO, pressure) == PEN_PRESSURE);
const _: () = assert!(offset_of!(POINTER_PEN_INFO, rotation) == PEN_ROTATION);
const _: () = assert!(offset_of!(POINTER_PEN_INFO, tiltX) == PEN_TILT_X);
const _: () = assert!(offset_of!(POINTER_PEN_INFO, tiltY) == PEN_TILT_Y);
const _: () = assert!(offset_of!(POINTER_TOUCH_INFO, touchMask) == TOUCH_MASK);
const _: () = assert!(offset_of!(POINTER_TOUCH_INFO, rcContact) == TOUCH_CONTACT_LEFT);
const _: () = assert!(offset_of!(POINTER_TOUCH_INFO, orientation) == TOUCH_ORIENTATION);
const _: () = assert!(offset_of!(POINTER_TOUCH_INFO, pressure) == TOUCH_PRESSURE);
}
pub const PT_POINTER: u32 = 1;
pub const PT_TOUCH: u32 = 2;
pub const PT_PEN: u32 = 3;
pub const PT_MOUSE: u32 = 4;
pub const PT_TOUCHPAD: u32 = 5;
pub const POINTER_FLAG_INRANGE: u32 = 0x0000_0002;
pub const POINTER_FLAG_INCONTACT: u32 = 0x0000_0004;
pub const POINTER_FLAG_FIRSTBUTTON: u32 = 0x0000_0010;
pub const POINTER_FLAG_SECONDBUTTON: u32 = 0x0000_0020;
pub const POINTER_FLAG_THIRDBUTTON: u32 = 0x0000_0040;
pub const POINTER_FLAG_PRIMARY: u32 = 0x0000_2000;
pub const POINTER_FLAG_CANCELED: u32 = 0x0000_8000;
pub const PEN_FLAG_BARREL: u32 = 0x0000_0001;
pub const PEN_FLAG_INVERTED: u32 = 0x0000_0002;
pub const PEN_FLAG_ERASER: u32 = 0x0000_0004;
pub const PEN_MASK_PRESSURE: u32 = 0x0000_0001;
pub const PEN_MASK_ROTATION: u32 = 0x0000_0002;
pub const PEN_MASK_TILT_X: u32 = 0x0000_0004;
pub const PEN_MASK_TILT_Y: u32 = 0x0000_0008;
pub const TOUCH_MASK_CONTACTAREA: u32 = 0x0000_0001;
pub const TOUCH_MASK_ORIENTATION: u32 = 0x0000_0002;
pub const TOUCH_MASK_PRESSURE: u32 = 0x0000_0004;
pub const PRESSURE_RANGE: f32 = 1024.0;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct RawPenInfo {
pub pointer_type: u32,
pub pointer_id: u32,
pub flags: u32,
pub pen_flags: u32,
pub pen_mask: u32,
pub pressure: u32,
pub rotation: u32,
pub tilt_x: i32,
pub tilt_y: i32,
pub screen: (i32, i32),
pub time_ms: u32,
pub history_count: u32,
}
impl RawPenInfo {
pub const fn in_proximity(&self) -> bool {
self.flags & POINTER_FLAG_INRANGE != 0
}
pub const fn down(&self) -> bool {
self.flags & POINTER_FLAG_INCONTACT != 0
}
pub const fn cancelled(&self) -> bool {
self.flags & POINTER_FLAG_CANCELED != 0
}
pub const fn tool(&self) -> PenKind {
if self.pen_flags & (PEN_FLAG_INVERTED | PEN_FLAG_ERASER) != 0 {
PenKind::Eraser
} else {
PenKind::Pen
}
}
pub fn pressure_normalised(&self) -> Option<f32> {
if self.pen_mask & PEN_MASK_PRESSURE == 0 {
return None;
}
Some((self.pressure as f32 / PRESSURE_RANGE).clamp(0.0, 1.0))
}
pub fn tilt_degrees(&self) -> Option<(f32, f32)> {
if self.pen_mask & (PEN_MASK_TILT_X | PEN_MASK_TILT_Y)
!= (PEN_MASK_TILT_X | PEN_MASK_TILT_Y)
{
return None;
}
Some((
(self.tilt_x as f32).clamp(-90.0, 90.0),
(self.tilt_y as f32).clamp(-90.0, 90.0),
))
}
pub fn twist_degrees(&self) -> Option<f32> {
if self.pen_mask & PEN_MASK_ROTATION == 0 {
return None;
}
Some((self.rotation as f32).clamp(0.0, 359.0))
}
pub const fn buttons(&self) -> PenButtons {
PenButtons::NONE
.with(
PenButtons::BARREL,
self.pen_flags & PEN_FLAG_BARREL != 0
|| self.flags & POINTER_FLAG_SECONDBUTTON != 0,
)
.with(
PenButtons::SECONDARY_BARREL,
self.flags & POINTER_FLAG_THIRDBUTTON != 0,
)
.with(PenButtons::ERASER, self.pen_flags & PEN_FLAG_ERASER != 0)
}
pub fn to_packet(&self, client_origin: (i32, i32), scale: f32) -> PenPacket {
let scale = if scale > 0.0 { scale } else { 1.0 };
let position = Point::new(
(self.screen.0 - client_origin.0) as f32 / scale,
(self.screen.1 - client_origin.1) as f32 / scale,
);
let down = self.down();
PenPacket {
tool: self.tool(),
position,
pressure: self
.pressure_normalised()
.unwrap_or(if down { 1.0 } else { 0.0 }),
tilt: self.tilt_degrees(),
twist: self.twist_degrees(),
buttons: self.buttons(),
in_proximity: self.in_proximity(),
down,
device_time_ms: Some(self.time_ms),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct RawTouchInfo {
pub pointer_id: u32,
pub flags: u32,
pub touch_mask: u32,
pub contact: (i32, i32, i32, i32),
pub orientation: u32,
pub pressure: u32,
}
impl RawTouchInfo {
pub fn contact_size(&self, scale: f32) -> Option<Size> {
if self.touch_mask & TOUCH_MASK_CONTACTAREA == 0 {
return None;
}
let scale = if scale > 0.0 { scale } else { 1.0 };
let (left, top, right, bottom) = self.contact;
if right <= left || bottom <= top {
return None;
}
Some(Size::new(
(right - left) as f32 / scale,
(bottom - top) as f32 / scale,
))
}
pub fn pressure_normalised(&self) -> Option<f32> {
if self.touch_mask & TOUCH_MASK_PRESSURE == 0 {
return None;
}
Some((self.pressure as f32 / PRESSURE_RANGE).clamp(0.0, 1.0))
}
}
fn u32_at(bytes: &[u8], offset: usize) -> Option<u32> {
let slice = bytes.get(offset..offset + 4)?;
Some(u32::from_le_bytes(slice.try_into().ok()?))
}
fn i32_at(bytes: &[u8], offset: usize) -> Option<i32> {
u32_at(bytes, offset).map(|v| v as i32)
}
pub fn decode_pen_info(bytes: &[u8]) -> Option<RawPenInfo> {
if bytes.len() < layout::PEN_INFO_SIZE {
return None;
}
Some(RawPenInfo {
pointer_type: u32_at(bytes, layout::POINTER_TYPE)?,
pointer_id: u32_at(bytes, layout::POINTER_ID)?,
flags: u32_at(bytes, layout::POINTER_FLAGS)?,
pen_flags: u32_at(bytes, layout::PEN_FLAGS)?,
pen_mask: u32_at(bytes, layout::PEN_MASK)?,
pressure: u32_at(bytes, layout::PEN_PRESSURE)?,
rotation: u32_at(bytes, layout::PEN_ROTATION)?,
tilt_x: i32_at(bytes, layout::PEN_TILT_X)?,
tilt_y: i32_at(bytes, layout::PEN_TILT_Y)?,
screen: (
i32_at(bytes, layout::PIXEL_X)?,
i32_at(bytes, layout::PIXEL_Y)?,
),
time_ms: u32_at(bytes, layout::TIME)?,
history_count: u32_at(bytes, layout::HISTORY_COUNT)?,
})
}
pub const MAX_PEN_HISTORY_ENTRIES: usize = 512;
pub const fn history_entries_to_request(history_count: u32) -> Option<usize> {
if history_count <= 1 {
return None;
}
let wanted = history_count as usize;
Some(if wanted > MAX_PEN_HISTORY_ENTRIES {
MAX_PEN_HISTORY_ENTRIES
} else {
wanted
})
}
pub fn decode_pen_history(bytes: &[u8], entries: usize) -> Vec<RawPenInfo> {
let available = bytes.len() / layout::PEN_INFO_SIZE;
let entries = entries.min(available);
let mut decoded = Vec::with_capacity(entries);
for index in 0..entries {
let start = index * layout::PEN_INFO_SIZE;
let Some(info) = decode_pen_info(&bytes[start..start + layout::PEN_INFO_SIZE]) else {
break;
};
decoded.push(info);
}
decoded.reverse();
decoded
}
pub fn decode_touch_info(bytes: &[u8]) -> Option<RawTouchInfo> {
if bytes.len() < layout::TOUCH_INFO_SIZE {
return None;
}
Some(RawTouchInfo {
pointer_id: u32_at(bytes, layout::POINTER_ID)?,
flags: u32_at(bytes, layout::POINTER_FLAGS)?,
touch_mask: u32_at(bytes, layout::TOUCH_MASK)?,
contact: (
i32_at(bytes, layout::TOUCH_CONTACT_LEFT)?,
i32_at(bytes, layout::TOUCH_CONTACT_TOP)?,
i32_at(bytes, layout::TOUCH_CONTACT_RIGHT)?,
i32_at(bytes, layout::TOUCH_CONTACT_BOTTOM)?,
),
orientation: u32_at(bytes, layout::TOUCH_ORIENTATION)?,
pressure: u32_at(bytes, layout::TOUCH_PRESSURE)?,
})
}
#[cfg(test)]
mod tests;