#![cfg_attr(
not(all(unix, not(target_os = "macos"))),
allow(rustdoc::broken_intra_doc_links)
)]
pub mod null;
#[cfg(all(unix, not(target_os = "macos")))]
pub mod wayland;
pub mod windows;
use teksilo_canvas::{Point, Size};
use teksilo_core::pointer::EventTime;
use teksilo_core::raw_handle::ParentHandle;
use teksilo_tokens::PenKind;
use crate::pointer_backend::BackendCaps;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct PenButtons(u8);
impl PenButtons {
pub const NONE: Self = Self(0);
pub const BARREL: Self = Self(1 << 0);
pub const SECONDARY_BARREL: Self = Self(1 << 1);
pub const ERASER: Self = Self(1 << 2);
pub const fn bits(self) -> u8 {
self.0
}
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn without(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
pub const fn with(self, other: Self, held: bool) -> Self {
if held {
self.union(other)
} else {
self.without(other)
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct PenCaps {
pub tool_kind: bool,
pub pressure: bool,
pub tilt: bool,
pub twist: bool,
pub touch_contact: bool,
}
impl PenCaps {
pub const NONE: Self = Self {
tool_kind: false,
pressure: false,
tilt: false,
twist: false,
touch_contact: false,
};
pub const FULL_PEN: Self = Self {
tool_kind: true,
pressure: true,
tilt: true,
twist: true,
touch_contact: false,
};
pub fn apply_to(self, caps: &mut BackendCaps) {
caps.reports_pen_kind |= self.tool_kind;
caps.reports_pressure |= self.pressure;
caps.reports_tilt |= self.tilt;
caps.reports_twist |= self.twist;
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct PenPacket {
pub tool: PenKind,
pub position: Point,
pub pressure: f32,
pub tilt: Option<(f32, f32)>,
pub twist: Option<f32>,
pub buttons: PenButtons,
pub in_proximity: bool,
pub down: bool,
pub time: EventTime,
}
impl PenPacket {
pub fn hovering(tool: PenKind, position: Point) -> Self {
Self {
tool,
position,
pressure: 0.0,
tilt: None,
twist: None,
buttons: PenButtons::NONE,
in_proximity: true,
down: false,
time: EventTime::ZERO,
}
}
pub fn out_of_proximity(tool: PenKind, position: Point) -> Self {
Self {
in_proximity: false,
..Self::hovering(tool, position)
}
}
pub fn down_at(mut self, pressure: f32) -> Self {
self.down = true;
self.pressure = pressure.clamp(0.0, 1.0);
self
}
}
pub trait PenSource: std::fmt::Debug {
fn poll(&mut self, out: &mut Vec<PenPacket>);
fn capabilities(&self) -> PenCaps {
PenCaps::NONE
}
fn polls_off_thread(&self) -> bool {
false
}
fn touch_contact(&self, _os_contact_id: u64) -> Option<Size> {
None
}
}
pub fn create_pen_source(parent: &ParentHandle) -> Box<dyn PenSource> {
#[cfg(all(unix, not(target_os = "macos")))]
{
if let Some(source) = wayland::WaylandPenSource::attach(parent) {
return Box::new(source);
}
}
#[cfg(target_os = "windows")]
{
if let Some(source) = windows::WindowsPenSource::attach(parent) {
return Box::new(source);
}
}
let _ = parent;
Box::new(null::NullPenSource::new())
}
pub const PEN_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(4);
#[cfg(test)]
mod tests {
use super::*;
use crate::pointer_backend::{PlatformKind, PointerBackend};
use crate::window_system::WindowSystem;
#[test]
fn buttons_are_a_set() {
let held = PenButtons::NONE.with(PenButtons::BARREL, true);
assert!(held.contains(PenButtons::BARREL));
assert!(!held.contains(PenButtons::SECONDARY_BARREL));
assert!(!held.is_empty());
assert!(held.without(PenButtons::BARREL).is_empty());
assert_eq!(held.without(PenButtons::ERASER), held);
}
#[test]
fn caps_only_raise_never_lower() {
let mut caps = BackendCaps::for_platform(PlatformKind::Windows, WindowSystem::Unknown);
assert!(caps.reports_pressure);
PenCaps::NONE.apply_to(&mut caps);
assert!(caps.reports_pressure, "NONE must not clear a set flag");
assert!(!caps.reports_tilt);
PenCaps::FULL_PEN.apply_to(&mut caps);
assert!(caps.reports_tilt && caps.reports_twist && caps.reports_pen_kind);
}
#[test]
fn the_poll_interval_stays_under_the_velocity_stop_gap() {
use teksilo_core::kinetic::velocity::STOP_GAP;
assert!(
PEN_POLL_INTERVAL < STOP_GAP,
"a poll interval at or past the {STOP_GAP:?} stop gap clears the \
velocity history between samples"
);
}
#[test]
fn the_null_source_reports_no_pen_and_yields_nothing() {
let mut source = null::NullPenSource::new();
let mut out = Vec::new();
source.poll(&mut out);
assert!(out.is_empty(), "the null source must yield no packets");
assert_eq!(source.capabilities(), PenCaps::NONE);
assert_eq!(source.touch_contact(1), None);
let mut state = crate::event_translation::TranslationState::new();
state.set_pen_source(Box::new(null::NullPenSource::new()));
let caps = state.capabilities();
assert!(!caps.reports_pen_kind);
assert!(!caps.reports_tilt);
assert!(!caps.reports_twist);
assert!(
state.poll_pen(EventTime::from_millis(10)).is_empty(),
"no packets in, no samples out"
);
}
}