608 lines
18 KiB
Rust
608 lines
18 KiB
Rust
// SPDX-License-Identifier: GPL-3.0-only
|
|
/*
|
|
* Copyright (c) 2024, Richard Acayan. All rights reserved.
|
|
*/
|
|
|
|
use crate::core::Display;
|
|
use crate::core::Graphics;
|
|
use crate::core::Layout;
|
|
use crate::core::layout::Key;
|
|
use crate::core::layout::MODIFIERS_MAX;
|
|
use std::collections::VecDeque;
|
|
use std::convert::TryInto;
|
|
use std::iter;
|
|
use std::ops::Add;
|
|
use std::sync::Arc;
|
|
use std::sync::Mutex;
|
|
use std::time::Duration;
|
|
use std::time::Instant;
|
|
use xkeysym::Keysym;
|
|
|
|
/*
|
|
* The available states for the modifier keys.
|
|
*
|
|
* The state goes Released -> Pressed when the modifier key is pressed.
|
|
*
|
|
* When pressed, the modifier can be released, going Pressed -> Latched so that
|
|
* the modifier will apply to the next key press.
|
|
* It could be held down for long enough that it goes Pressed -> Locked, and the
|
|
* modifier would apply to any other key press until it is released, pressed and
|
|
* released again.
|
|
* Finally, it could be held down while another key is being pressed, going
|
|
* Pressed -> HeldPressed (or Locked -> HeldLockedPressed) to apply until it is
|
|
* released.
|
|
*
|
|
* When latched, it could go Latched -> Released when any key
|
|
* (including itself) is released.
|
|
*
|
|
* When held, it goes Held -> Released (or HeldLocked -> Released) when the
|
|
* modifier key is released.
|
|
*
|
|
* When locked, the modifier key goes Locked -> HeldLocked when it is pressed
|
|
* again.
|
|
*/
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum ModState {
|
|
Released,
|
|
Pressed,
|
|
HeldPressed,
|
|
Latched,
|
|
Held,
|
|
Locked,
|
|
HeldLocked,
|
|
HeldLockedPressed,
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
enum DrawOperation {
|
|
Key,
|
|
Modifiers([bool; MODIFIERS_MAX]),
|
|
Labels,
|
|
}
|
|
|
|
impl DrawOperation {
|
|
fn from_modifier(modifier: usize) -> DrawOperation
|
|
{
|
|
let mut modifiers = [false; MODIFIERS_MAX];
|
|
modifiers[modifier - 1] = true;
|
|
DrawOperation::Modifiers(modifiers)
|
|
}
|
|
|
|
fn from_modifier_edge(modifier: usize) -> DrawOperation
|
|
{
|
|
if Layout::is_label_modifier(modifier) {
|
|
DrawOperation::Labels
|
|
} else {
|
|
DrawOperation::from_modifier(modifier)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Add for DrawOperation {
|
|
type Output = Self;
|
|
|
|
fn add(self, other: Self) -> Self
|
|
{
|
|
match (&self, &other) {
|
|
(&DrawOperation::Labels, _) => self,
|
|
(_, &DrawOperation::Labels) => other,
|
|
(&DrawOperation::Key, &DrawOperation::Modifiers(_)) => other,
|
|
(&DrawOperation::Modifiers(_), &DrawOperation::Key) => self,
|
|
(&DrawOperation::Modifiers(a), &DrawOperation::Modifiers(b)) => {
|
|
let mods = iter::zip(&a, &b).map(|(a, b)| *a || *b);
|
|
DrawOperation::Modifiers(mods.collect::<Vec<_>>().try_into().unwrap())
|
|
},
|
|
(&DrawOperation::Key, &DrawOperation::Key) => self,
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Press {
|
|
x1: f64,
|
|
y1: f64,
|
|
part: usize,
|
|
timer: Instant,
|
|
}
|
|
|
|
const PRESSES_MAX: usize = 64;
|
|
|
|
pub trait Keyboard {
|
|
fn key_supported(&self, sym: Keysym) -> bool;
|
|
|
|
fn press(&mut self, sym: Keysym);
|
|
fn release(&mut self, sym: Keysym);
|
|
fn change_layout(&mut self, layout: &Layout);
|
|
}
|
|
|
|
pub struct Button<D: Display, K: Keyboard> {
|
|
layout: Layout,
|
|
kbd: K,
|
|
gfx: Arc<Mutex<Graphics<D>>>,
|
|
|
|
presses: [Option<Press>; PRESSES_MAX],
|
|
timers: VecDeque<usize>,
|
|
modifiers: [ModState; MODIFIERS_MAX],
|
|
|
|
longpress: Duration,
|
|
repeat: Duration,
|
|
r_square: f64,
|
|
}
|
|
|
|
const PART_TL: usize = 1;
|
|
const PART_TR: usize = 2;
|
|
const PART_BL: usize = 3;
|
|
const PART_BR: usize = 4;
|
|
const PART_LEFT: usize = 5;
|
|
const PART_RIGHT: usize = 6;
|
|
const PART_TOP: usize = 7;
|
|
const PART_BOTTOM: usize = 8;
|
|
|
|
const ANGLE_PARTS: [[usize; 4]; 16] = [
|
|
[PART_LEFT, PART_TL, PART_BL, PART_TOP],
|
|
[PART_TL, PART_LEFT, PART_TOP, PART_BL],
|
|
[PART_TL, PART_TOP, PART_LEFT, PART_TR],
|
|
[PART_TOP, PART_TL, PART_TR, PART_LEFT],
|
|
[PART_TOP, PART_TR, PART_TL, PART_RIGHT],
|
|
[PART_TR, PART_TOP, PART_RIGHT, PART_TL],
|
|
[PART_TR, PART_RIGHT, PART_TOP, PART_BR],
|
|
[PART_RIGHT, PART_TR, PART_BR, PART_TOP],
|
|
[PART_RIGHT, PART_BR, PART_TR, PART_BOTTOM],
|
|
[PART_BR, PART_RIGHT, PART_BOTTOM, PART_TR],
|
|
[PART_BR, PART_BOTTOM, PART_RIGHT, PART_BL],
|
|
[PART_BOTTOM, PART_BR, PART_BL, PART_RIGHT],
|
|
[PART_BOTTOM, PART_BL, PART_BR, PART_LEFT],
|
|
[PART_BL, PART_BOTTOM, PART_LEFT, PART_BR],
|
|
[PART_BL, PART_LEFT, PART_BOTTOM, PART_TL],
|
|
[PART_LEFT, PART_BL, PART_TL, PART_BOTTOM],
|
|
];
|
|
|
|
// The None option cannot be copied, but it works if it is a constant. Rust is insane.
|
|
const NO_PRESS: Option<Press> = None;
|
|
|
|
impl<D: Display, K: Keyboard> Button<D, K> {
|
|
pub fn new(mut layout: Layout, mut kbd: K,
|
|
gfx: Arc<Mutex<Graphics<D>>>) -> Button<D, K>
|
|
{
|
|
kbd.change_layout(&layout);
|
|
layout.update_keys_supported(&kbd);
|
|
|
|
Button {
|
|
layout,
|
|
kbd,
|
|
gfx,
|
|
|
|
presses: [NO_PRESS; PRESSES_MAX],
|
|
modifiers: [ModState::Released; MODIFIERS_MAX],
|
|
timers: VecDeque::with_capacity(PRESSES_MAX),
|
|
|
|
longpress: Duration::from_millis(600),
|
|
repeat: Duration::from_millis(25),
|
|
r_square: 0.25,
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn layout(&self) -> &Layout
|
|
{
|
|
&self.layout
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn keyboard(&self) -> &K
|
|
{
|
|
&self.kbd
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn keyboard_mut(&mut self) -> &mut K
|
|
{
|
|
&mut self.kbd
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn mod_state(&self) -> &[ModState; MODIFIERS_MAX]
|
|
{
|
|
&self.modifiers
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn set_text_supported(&mut self, text_supp: bool)
|
|
{
|
|
self.layout.set_text_supported(text_supp)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn update_keys_supported(&mut self)
|
|
{
|
|
self.layout.update_keys_supported(&self.kbd)
|
|
}
|
|
|
|
pub fn next_time(&self) -> Option<Instant>
|
|
{
|
|
let id = *self.timers.front()?;
|
|
if id == PRESSES_MAX {
|
|
return None;
|
|
}
|
|
|
|
// The next timer must always be held by an active key press.
|
|
Some(self.presses[id].as_ref().unwrap().timer)
|
|
}
|
|
|
|
fn update_modifier(&mut self, modifier: usize, old: ModState, new: ModState) -> bool
|
|
{
|
|
if self.modifiers[modifier - 1] == old {
|
|
self.modifiers[modifier - 1] = new;
|
|
|
|
if new == ModState::Released {
|
|
self.kbd.release(Layout::modifier_keysym(modifier));
|
|
}
|
|
|
|
if old == ModState::Released {
|
|
self.kbd.press(Layout::modifier_keysym(modifier));
|
|
}
|
|
|
|
if old == ModState::Released || new == ModState::Released {
|
|
self.layout.update_modifiers(&self.kbd, &self.modifiers);
|
|
if Layout::is_keysym_modifier(modifier) {
|
|
self.kbd.change_layout(&self.layout);
|
|
}
|
|
}
|
|
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn try_press_mod(&mut self, modifier: usize) -> DrawOperation
|
|
{
|
|
let mut draw = DrawOperation::Key;
|
|
|
|
if modifier != 0 {
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::Released,
|
|
ModState::Pressed);
|
|
if changed {
|
|
draw = draw + DrawOperation::from_modifier_edge(modifier);
|
|
}
|
|
|
|
self.update_modifier(modifier,
|
|
ModState::Latched,
|
|
ModState::Held);
|
|
self.update_modifier(modifier,
|
|
ModState::Locked,
|
|
ModState::HeldLocked);
|
|
} else {
|
|
for modifier in 1..=MODIFIERS_MAX {
|
|
self.update_modifier(modifier,
|
|
ModState::Pressed,
|
|
ModState::HeldPressed);
|
|
self.update_modifier(modifier,
|
|
ModState::Locked,
|
|
ModState::HeldLockedPressed);
|
|
}
|
|
}
|
|
|
|
draw
|
|
}
|
|
|
|
fn draw(op: DrawOperation,
|
|
gfx: &Arc<Mutex<Graphics<D>>>,
|
|
layout: &Layout,
|
|
key: Option<&Key>,
|
|
mod_state: &[ModState])
|
|
{
|
|
let mut gfx = gfx.lock().unwrap();
|
|
|
|
match op {
|
|
DrawOperation::Key => {
|
|
if let Some(key) = key {
|
|
gfx.draw_single(key, mod_state);
|
|
}
|
|
},
|
|
DrawOperation::Modifiers(m) => {
|
|
gfx.draw_modifiers(layout, mod_state, &m);
|
|
|
|
if let Some(key) = key {
|
|
gfx.draw_single(key, mod_state);
|
|
}
|
|
},
|
|
DrawOperation::Labels => {
|
|
gfx.change_layout(layout, mod_state);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn press(&mut self, id: usize, x: f64, y: f64)
|
|
{
|
|
if self.presses[id].is_some() {
|
|
return;
|
|
}
|
|
|
|
let key = match self.layout.locate_key_mut(x, y) {
|
|
Some(k) => k,
|
|
// Ignore the press if it is not for a key.
|
|
None => return,
|
|
};
|
|
|
|
key.parts[0].press();
|
|
|
|
let modifier = key.parts[0].modifier_id();
|
|
let draw = self.try_press_mod(modifier);
|
|
|
|
let timer = Instant::now() + self.longpress;
|
|
self.presses[id] = Some(Press {
|
|
x1: x,
|
|
y1: y,
|
|
part: 0,
|
|
timer,
|
|
});
|
|
self.timers.push_back(id);
|
|
|
|
let key = self.layout.locate_key(x, y).unwrap();
|
|
Self::draw(draw, &self.gfx, &self.layout, Some(key), &self.modifiers);
|
|
}
|
|
|
|
fn update_pressed_part(press: &mut Press, key: &Key, angle: usize)
|
|
{
|
|
if key.parts[ANGLE_PARTS[angle][0]].sym() != Keysym::NoSymbol {
|
|
press.part = ANGLE_PARTS[angle][0];
|
|
} else if key.parts[ANGLE_PARTS[angle][1]].sym() != Keysym::NoSymbol {
|
|
press.part = ANGLE_PARTS[angle][1];
|
|
} else if key.parts[ANGLE_PARTS[angle][2]].sym() != Keysym::NoSymbol {
|
|
press.part = ANGLE_PARTS[angle][2];
|
|
} else if key.parts[ANGLE_PARTS[angle][3]].sym() != Keysym::NoSymbol {
|
|
press.part = ANGLE_PARTS[angle][3];
|
|
}
|
|
}
|
|
|
|
fn remove_timer(&mut self, id: usize)
|
|
{
|
|
for evt in self.timers.iter_mut() {
|
|
if *evt == id {
|
|
*evt = PRESSES_MAX;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while self.timers.front() == Some(&PRESSES_MAX) {
|
|
self.timers.pop_front();
|
|
}
|
|
}
|
|
|
|
fn try_cancel_mod(&mut self, modifier: usize) -> DrawOperation
|
|
{
|
|
let mut draw = DrawOperation::Key;
|
|
|
|
if modifier != 0 {
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::Pressed,
|
|
ModState::Released);
|
|
if changed && Layout::is_label_modifier(modifier) {
|
|
draw = draw + DrawOperation::Labels;
|
|
}
|
|
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::Locked,
|
|
ModState::Released);
|
|
if changed && Layout::is_label_modifier(modifier) {
|
|
draw = draw + DrawOperation::Labels;
|
|
}
|
|
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::HeldPressed,
|
|
ModState::Released);
|
|
if changed && Layout::is_label_modifier(modifier) {
|
|
draw = draw + DrawOperation::Labels;
|
|
}
|
|
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::HeldLockedPressed,
|
|
ModState::Released);
|
|
if changed && Layout::is_label_modifier(modifier) {
|
|
draw = draw + DrawOperation::Labels;
|
|
}
|
|
|
|
self.update_modifier(modifier,
|
|
ModState::Held,
|
|
ModState::Latched);
|
|
self.update_modifier(modifier,
|
|
ModState::HeldLocked,
|
|
ModState::Locked);
|
|
}
|
|
|
|
draw
|
|
}
|
|
|
|
pub fn pos(&mut self, id: usize, x: f64, y: f64)
|
|
{
|
|
let press = match &mut self.presses[id] {
|
|
Some(p) => p,
|
|
None => return,
|
|
};
|
|
|
|
let (dx, dy) = (x - press.x1, y - press.y1);
|
|
if dx * dx + dy * dy < self.r_square {
|
|
// We only need to make changes when the key is being dragged.
|
|
return;
|
|
}
|
|
|
|
let angle = dy.atan2(dx) * 8.0 / std::f64::consts::PI + 8.0;
|
|
let angle = angle.clamp(0.0, 15.0) as usize;
|
|
|
|
let key = self.layout.locate_key_mut(press.x1, press.y1).unwrap();
|
|
|
|
let old_part = press.part;
|
|
Self::update_pressed_part(press, key, angle);
|
|
let new_part = press.part;
|
|
|
|
if new_part == old_part {
|
|
return;
|
|
}
|
|
|
|
key.parts[old_part].release();
|
|
key.parts[new_part].press();
|
|
|
|
let modifier1 = key.parts[old_part].modifier_id();
|
|
let modifier2 = key.parts[new_part].modifier_id();
|
|
|
|
press.timer = Instant::now() + self.longpress;
|
|
self.remove_timer(id);
|
|
self.timers.push_back(id);
|
|
|
|
let mut draw = self.try_cancel_mod(modifier1);
|
|
draw = draw + self.try_press_mod(modifier2);
|
|
|
|
let press = self.presses[id].as_ref().unwrap();
|
|
let key = self.layout.locate_key(press.x1, press.y1).unwrap();
|
|
Self::draw(draw, &self.gfx, &self.layout, Some(key), &self.modifiers);
|
|
}
|
|
|
|
fn is_normal_key_pressed(&self) -> bool
|
|
{
|
|
for id in &self.timers {
|
|
if *id == PRESSES_MAX {
|
|
continue;
|
|
}
|
|
|
|
let press = self.presses[*id].as_ref().unwrap();
|
|
let key = self.layout.locate_key(press.x1, press.y1).unwrap();
|
|
|
|
if key.parts[press.part].modifier_id() == 0 {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/*
|
|
* Release the given modifier key. If the key being pressed is not a
|
|
* modifier key (i.e. modifier = 0), release all latched modifier keys.
|
|
*/
|
|
fn release_mod(&mut self, modifier: usize) -> DrawOperation
|
|
{
|
|
let mut draw = DrawOperation::Key;
|
|
|
|
if modifier != 0 {
|
|
self.update_modifier(modifier,
|
|
ModState::Pressed,
|
|
ModState::Latched);
|
|
|
|
self.update_modifier(modifier,
|
|
ModState::HeldPressed,
|
|
ModState::Held);
|
|
|
|
self.update_modifier(modifier,
|
|
ModState::HeldLockedPressed,
|
|
ModState::HeldLocked);
|
|
|
|
if self.is_normal_key_pressed() {
|
|
self.update_modifier(modifier,
|
|
ModState::Held,
|
|
ModState::Latched);
|
|
|
|
self.update_modifier(modifier,
|
|
ModState::HeldLocked,
|
|
ModState::Locked);
|
|
}
|
|
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::Held,
|
|
ModState::Released);
|
|
if changed && Layout::is_label_modifier(modifier) {
|
|
draw = draw + DrawOperation::Labels;
|
|
}
|
|
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::HeldLocked,
|
|
ModState::Released);
|
|
if changed && Layout::is_label_modifier(modifier) {
|
|
draw = draw + DrawOperation::Labels;
|
|
}
|
|
} else {
|
|
for modifier in 1..=MODIFIERS_MAX {
|
|
let changed = self.update_modifier(modifier,
|
|
ModState::Latched,
|
|
ModState::Released);
|
|
if changed {
|
|
draw = draw + DrawOperation::from_modifier_edge(modifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
draw
|
|
}
|
|
|
|
pub fn release(&mut self, id: usize)
|
|
{
|
|
let press = match &mut self.presses[id] {
|
|
Some(p) => p,
|
|
None => return,
|
|
};
|
|
|
|
let key = self.layout.locate_key_mut(press.x1, press.y1).unwrap();
|
|
|
|
key.parts[press.part].release();
|
|
|
|
let modifier = key.parts[press.part].modifier_id();
|
|
if modifier == 0 {
|
|
self.kbd.press(key.parts[press.part].sym());
|
|
self.kbd.release(key.parts[press.part].sym());
|
|
}
|
|
|
|
let draw = self.release_mod(modifier);
|
|
|
|
let press = self.presses[id].as_ref().unwrap();
|
|
let key = self.layout.locate_key(press.x1, press.y1).unwrap();
|
|
Self::draw(draw, &self.gfx, &self.layout, Some(key), &self.modifiers);
|
|
|
|
self.remove_timer(id);
|
|
self.presses[id] = None;
|
|
}
|
|
|
|
fn lock_mod(&mut self, modifier: usize) -> DrawOperation
|
|
{
|
|
let changed = self.update_modifier(modifier, ModState::Pressed, ModState::Locked);
|
|
if changed {
|
|
DrawOperation::from_modifier(modifier)
|
|
} else {
|
|
DrawOperation::Key
|
|
}
|
|
}
|
|
|
|
pub fn dispatch_timers(&mut self)
|
|
{
|
|
let mut draw = DrawOperation::Key;
|
|
|
|
while !self.timers.is_empty() {
|
|
let id = *self.timers.front().unwrap();
|
|
if id == PRESSES_MAX {
|
|
self.timers.pop_front();
|
|
continue;
|
|
}
|
|
|
|
let press = self.presses[id].as_mut().unwrap();
|
|
if Instant::now() < press.timer {
|
|
break;
|
|
}
|
|
|
|
self.timers.pop_front();
|
|
|
|
let key = self.layout.locate_key(press.x1, press.y1).unwrap();
|
|
|
|
let modifier = key.parts[press.part].modifier_id();
|
|
if modifier != 0 {
|
|
draw = draw + self.lock_mod(modifier);
|
|
} else {
|
|
self.kbd.press(key.parts[press.part].sym());
|
|
self.kbd.release(key.parts[press.part].sym());
|
|
|
|
press.timer += self.repeat;
|
|
self.timers.push_back(id);
|
|
}
|
|
}
|
|
|
|
Self::draw(draw, &self.gfx, &self.layout, None, &self.modifiers);
|
|
}
|
|
}
|