rewrite in rust
This commit is contained in:
parent
d43f61ec76
commit
4a6b261be0
51 changed files with 3104 additions and 9319 deletions
528
src/core/button.rs
Normal file
528
src/core/button.rs
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Copyright (c) 2024, Richard Acayan. All rights reserved.
|
||||
*/
|
||||
|
||||
use core::Display;
|
||||
use core::Graphics;
|
||||
use core::Layout;
|
||||
use core::layout::Key;
|
||||
use core::layout::MODIFIERS_MAX;
|
||||
use core::xkeysym::Keysym;
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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 -> Held (or Locked -> HeldLocked) 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,
|
||||
Latched,
|
||||
Held,
|
||||
Locked,
|
||||
HeldLocked,
|
||||
}
|
||||
|
||||
#[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 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(layout: Layout, mut kbd: K,
|
||||
gfx: Arc<Mutex<Graphics<D>>>) -> Button<D, K>
|
||||
{
|
||||
kbd.change_layout(&layout);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn mod_state(&self) -> &[ModState; MODIFIERS_MAX]
|
||||
{
|
||||
&self.modifiers
|
||||
}
|
||||
|
||||
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 || new == ModState::Released {
|
||||
self.layout.update_modifiers(&self.modifiers);
|
||||
if Layout::is_keysym_modifier(modifier) {
|
||||
self.kbd.change_layout(&self.layout);
|
||||
}
|
||||
}
|
||||
|
||||
if old == ModState::Released {
|
||||
self.kbd.press(Layout::modifier_keysym(modifier));
|
||||
}
|
||||
|
||||
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::Held);
|
||||
self.update_modifier(modifier,
|
||||
ModState::Locked,
|
||||
ModState::HeldLocked);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
eprintln!("warn: pressed same ID twice before releasing");
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
6
src/core/expat.rs
Normal file
6
src/core/expat.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#![allow(dead_code,
|
||||
improper_ctypes,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals)]
|
||||
include!(concat!(env!("OUT_DIR"), "/expat.rs"));
|
||||
694
src/core/graphics.rs
Normal file
694
src/core/graphics.rs
Normal file
|
|
@ -0,0 +1,694 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Copyright (c) 2024, Richard Acayan. All rights reserved.
|
||||
*/
|
||||
|
||||
use core::button::ModState;
|
||||
use core::fontconfig;
|
||||
use core::freetype::freetype;
|
||||
use core::imgref::ImgRefMut;
|
||||
use core::imgref::ImgRef;
|
||||
use core::imgref::ImgVec;
|
||||
use core::Layout;
|
||||
use core::layout::Key;
|
||||
use core::layout::Part;
|
||||
use core::rgb::alt::BGR;
|
||||
use core::rgb::alt::BGRA;
|
||||
use core::xkeysym::Keysym;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::iter;
|
||||
use std::ptr;
|
||||
|
||||
fn convert_gray_to_bgrx(mut dest: ImgRefMut<BGRA<u8>>, src: ImgRef<u8>, fg_color: BGR<f32>)
|
||||
{
|
||||
for (dest, src) in iter::zip(dest.rows_mut(), src.rows()) {
|
||||
for (dest, src) in iter::zip(dest, src) {
|
||||
dest.r = ((dest.r as f32 + *src as f32 * fg_color.r) as u16).try_into().unwrap_or(255);
|
||||
dest.g = ((dest.g as f32 + *src as f32 * fg_color.g) as u16).try_into().unwrap_or(255);
|
||||
dest.b = ((dest.b as f32 + *src as f32 * fg_color.b) as u16).try_into().unwrap_or(255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_image(mut dest: ImgRefMut<u8>, src: ImgRef<u8>)
|
||||
{
|
||||
for (dest, src) in iter::zip(dest.pixels_mut(), src.pixels()) {
|
||||
*dest = (src as u16 + *dest as u16).try_into().unwrap_or(255);
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_image(dest: &mut ImgRefMut<BGRA<u8>>, src: BGRA<u8>)
|
||||
{
|
||||
for dest in dest.pixels_mut() {
|
||||
*dest = src;
|
||||
}
|
||||
}
|
||||
|
||||
fn bitmap_to_imgref(bitmap: &freetype::FT_Bitmap) -> ImgRef<u8>
|
||||
{
|
||||
unsafe {
|
||||
let buf = &*ptr::slice_from_raw_parts(bitmap.buffer,
|
||||
bitmap.pitch as usize
|
||||
* bitmap.rows as usize);
|
||||
ImgRef::new_stride(buf,
|
||||
bitmap.width as usize,
|
||||
bitmap.rows as usize,
|
||||
bitmap.pitch as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Anchor {
|
||||
Min,
|
||||
Center,
|
||||
Max,
|
||||
}
|
||||
|
||||
pub trait Display {
|
||||
fn size(&self) -> (u32, u32);
|
||||
|
||||
fn begin(&mut self) -> ImgRefMut<BGRA<u8>>;
|
||||
fn resize(&mut self, width: u32, height: u32);
|
||||
fn end(&mut self, x: u32, y: u32, width: u32, height: u32);
|
||||
}
|
||||
|
||||
pub struct Graphics<D: Display> {
|
||||
disp: D,
|
||||
fonts: Vec<(String, freetype::FT_Long)>,
|
||||
fontbufs: Vec<Vec<u8>>,
|
||||
labels: HashMap<Keysym, ImgVec<u8>>,
|
||||
sublabels: HashMap<Keysym, ImgVec<u8>>,
|
||||
ft: freetype::FT_Library,
|
||||
|
||||
x_scale: f64,
|
||||
y_scale: f64,
|
||||
}
|
||||
|
||||
struct Label<'a> {
|
||||
sym: Keysym,
|
||||
label: &'a str,
|
||||
secondary: bool,
|
||||
small: bool,
|
||||
}
|
||||
|
||||
const LABELS: [Label; 43] = [
|
||||
Label { sym: Keysym::Greek_MU, label: "\u{039C}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::emdash, label: "\u{2014}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::endash, label: "\u{2013}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::ellipsis, label: "\u{2026}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::leftsinglequotemark, label: "\u{2018}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::rightsinglequotemark, label: "\u{2019}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::leftdoublequotemark, label: "\u{201C}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::rightdoublequotemark, label: "\u{201D}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::permille, label: "\u{2030}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::singlelowquotemark, label: "\u{201A}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::doublelowquotemark, label: "\u{201E}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::OE, label: "\u{0152}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::oe, label: "\u{0153}",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::BackSpace, label: "\u{232B}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Tab, label: "\u{2B7E}",
|
||||
secondary: true, small: true, },
|
||||
Label { sym: Keysym::Return, label: "\u{23CE}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Escape, label: "Esc",
|
||||
secondary: true, small: true, },
|
||||
Label { sym: Keysym::Home, label: "\u{21E4}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Left, label: "\u{2190}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Up, label: "\u{2191}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Right, label: "\u{2192}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Down, label: "\u{2193}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Page_Up, label: "\u{21D1}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Page_Down, label: "\u{21D3}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::End, label: "\u{21E5}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Insert, label: "Ins",
|
||||
secondary: true, small: true, },
|
||||
Label { sym: Keysym::F1, label: "F1",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F2, label: "F2",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F3, label: "F3",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F4, label: "F4",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F5, label: "F5",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F6, label: "F6",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F7, label: "F7",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F8, label: "F8",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F9, label: "F9",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F10, label: "F10",
|
||||
secondary: false, small: false, },
|
||||
Label { sym: Keysym::F11, label: "F11",
|
||||
secondary: false, small: true, },
|
||||
Label { sym: Keysym::F12, label: "F12",
|
||||
secondary: false, small: true, },
|
||||
Label { sym: Keysym::Shift_L, label: "\u{21E7}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::Control_L, label: "Ctrl",
|
||||
secondary: true, small: true, },
|
||||
Label { sym: Keysym::Alt_L, label: "Alt",
|
||||
secondary: true, small: true, },
|
||||
Label { sym: Keysym::Delete, label: "\u{2326}",
|
||||
secondary: true, small: false, },
|
||||
Label { sym: Keysym::XF86_Fn, label: "Fn",
|
||||
secondary: true, small: true, },
|
||||
/*
|
||||
Label { sym: Keysym::approxeq, label: "\u{2248}",
|
||||
secondary: false, small: false, },
|
||||
*/
|
||||
];
|
||||
|
||||
const ANCHORS: [(Anchor, Anchor); 8] = [
|
||||
(Anchor::Min, Anchor::Min),
|
||||
(Anchor::Max, Anchor::Min),
|
||||
(Anchor::Min, Anchor::Max),
|
||||
(Anchor::Max, Anchor::Max),
|
||||
(Anchor::Min, Anchor::Center),
|
||||
(Anchor::Max, Anchor::Center),
|
||||
(Anchor::Center, Anchor::Min),
|
||||
(Anchor::Center, Anchor::Max),
|
||||
];
|
||||
|
||||
impl<D: Display> Graphics<D> {
|
||||
pub fn new(disp: D) -> Graphics<D>
|
||||
{
|
||||
let ft = unsafe {
|
||||
let mut ft = std::ptr::null_mut();
|
||||
let err = freetype::FT_Init_FreeType(&mut ft);
|
||||
if err != 0 {
|
||||
panic!("FreeType error {}", err);
|
||||
}
|
||||
|
||||
ft
|
||||
};
|
||||
|
||||
/*
|
||||
* We want a pattern with default and config substitution, so use the
|
||||
* side effect in font_match(). The first font might not provide all
|
||||
* glyphs needed, so the returned font is unused.
|
||||
*/
|
||||
let fc = fontconfig::Fontconfig::new().unwrap();
|
||||
let mut pattern = fontconfig::Pattern::new(&fc);
|
||||
pattern.font_match();
|
||||
|
||||
let fonts = fontconfig::sort_fonts(&pattern, true);
|
||||
|
||||
let mut fontfiles = Vec::new();
|
||||
|
||||
for font in fonts.iter() {
|
||||
let fname = match font.filename() {
|
||||
Some(fname) => String::from(fname),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let idx = match font.face_index() {
|
||||
Some(idx) => idx.into(),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
fontfiles.push((fname, idx));
|
||||
}
|
||||
|
||||
Graphics {
|
||||
disp,
|
||||
ft,
|
||||
fonts: fontfiles,
|
||||
fontbufs: Vec::new(),
|
||||
labels: HashMap::new(),
|
||||
sublabels: HashMap::new(),
|
||||
|
||||
x_scale: 0.0,
|
||||
y_scale: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_label(sym: Keysym) -> String
|
||||
{
|
||||
let res = LABELS.binary_search_by_key(&sym, |&Label { sym, .. }| sym);
|
||||
if let Ok(idx) = res {
|
||||
return String::from(LABELS[idx].label);
|
||||
}
|
||||
|
||||
// The ! symbol is bitwise NOT.
|
||||
match char::from_u32(sym.raw() & !0x01000000) {
|
||||
Some(c) => {
|
||||
let mut label = String::new();
|
||||
label.push(c);
|
||||
label
|
||||
},
|
||||
None => {
|
||||
format!("U+{}", sym.raw())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_keysym_small(sym: Keysym) -> bool
|
||||
{
|
||||
match LABELS.binary_search_by_key(&sym, |&Label { sym, .. }| sym) {
|
||||
Ok(idx) => LABELS[idx].small,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn open_font(&mut self, idx: usize) -> Option<freetype::FT_Face>
|
||||
{
|
||||
// Read the font if it is not already cached.
|
||||
if self.fontbufs.len() <= idx {
|
||||
let mut f = File::open(&self.fonts[idx].0).ok()?;
|
||||
|
||||
let mut fontbuf = Vec::new();
|
||||
f.read_to_end(&mut fontbuf).ok()?;
|
||||
|
||||
self.fontbufs.push(fontbuf);
|
||||
}
|
||||
|
||||
let fontbuf = &self.fontbufs[idx];
|
||||
|
||||
unsafe {
|
||||
let mut ftface = std::ptr::null_mut();
|
||||
freetype::FT_New_Memory_Face(self.ft,
|
||||
fontbuf.as_ptr(),
|
||||
fontbuf.len() as freetype::FT_Long,
|
||||
self.fonts[idx].1 as freetype::FT_Long,
|
||||
&mut ftface);
|
||||
if ptr::eq(ftface, ptr::null_mut()) {
|
||||
None
|
||||
} else {
|
||||
Some(ftface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn can_font_render_text(face: Option<freetype::FT_Face>, text: &str) -> bool
|
||||
{
|
||||
let face = match face {
|
||||
Some(face) => face,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
for c in text.chars() {
|
||||
unsafe {
|
||||
if freetype::FT_Get_Char_Index(face, c.into()) == 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn open_font_for_text(&mut self, text: &str) -> Option<freetype::FT_Face>
|
||||
{
|
||||
let mut ftface = self.open_font(0);
|
||||
let mut i = 1;
|
||||
|
||||
while !Self::can_font_render_text(ftface, text) {
|
||||
if let Some(ftface) = ftface {
|
||||
unsafe {
|
||||
freetype::FT_Done_Face(ftface);
|
||||
}
|
||||
}
|
||||
|
||||
ftface = self.open_font(i);
|
||||
|
||||
i += 1;
|
||||
if i >= self.fonts.len() {
|
||||
// Fall back to the first font with placeholder glyphs
|
||||
return self.open_font(0);
|
||||
}
|
||||
}
|
||||
|
||||
ftface
|
||||
}
|
||||
|
||||
fn rasterize_text(&mut self, size: f64, text: &str) -> ImgVec<u8>
|
||||
{
|
||||
let ftface = self.open_font_for_text(text).unwrap();
|
||||
unsafe {
|
||||
freetype::FT_Set_Char_Size(ftface, (size * 64.0) as i64, 0, 0, 0);
|
||||
};
|
||||
|
||||
let (metrics, units_per_em, bbox) = unsafe {
|
||||
((*(*ftface).size).metrics,
|
||||
(*ftface).units_per_EM,
|
||||
(*ftface).bbox)
|
||||
};
|
||||
|
||||
let mut width = 0;
|
||||
let glyph_width = (bbox.xMax - bbox.xMin) as usize
|
||||
* metrics.x_ppem as usize
|
||||
/ units_per_em as usize;
|
||||
|
||||
let stride = metrics.max_advance as usize / 64 * (text.len() - 1) + glyph_width;
|
||||
let height = (metrics.ascender - metrics.descender) as usize / 64;
|
||||
|
||||
let vec: Vec<u8> = vec![0; stride * height];
|
||||
let mut img = ImgVec::new(vec, stride, height);
|
||||
|
||||
let mut pen: i64 = 0;
|
||||
|
||||
for c in text.chars() {
|
||||
let glyph = unsafe {
|
||||
freetype::FT_Load_Char(ftface, c.into(), 0);
|
||||
freetype::FT_Render_Glyph((*ftface).glyph, freetype::FT_Render_Mode::FT_RENDER_MODE_NORMAL);
|
||||
*(*ftface).glyph
|
||||
};
|
||||
|
||||
if glyph.bitmap.rows != 0 && glyph.bitmap.width != 0 {
|
||||
if pen < -glyph.bitmap_left as i64 {
|
||||
pen -= glyph.bitmap_left as i64;
|
||||
}
|
||||
|
||||
let dest_x = pen + glyph.bitmap_left as i64;
|
||||
let dest_y = metrics.ascender / 64 - glyph.bitmap_top as i64;
|
||||
|
||||
let src = bitmap_to_imgref(&glyph.bitmap);
|
||||
let dest = img.sub_image_mut(dest_x as usize,
|
||||
dest_y as usize,
|
||||
glyph.bitmap.width as usize,
|
||||
glyph.bitmap.rows as usize);
|
||||
copy_image(dest, src);
|
||||
}
|
||||
|
||||
let new_width = (pen + glyph.bitmap_left as i64
|
||||
+ glyph.bitmap.width as i64) as usize;
|
||||
if width < new_width {
|
||||
width = new_width;
|
||||
}
|
||||
|
||||
pen += glyph.advance.x / 64;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
freetype::FT_Done_Face(ftface);
|
||||
};
|
||||
|
||||
let vec = img.into_buf();
|
||||
ImgVec::new_stride(vec, width, height, stride)
|
||||
}
|
||||
|
||||
/*
|
||||
* Rasterize a key symbol and store it in the labels hash table.
|
||||
*
|
||||
* We need two functions, one for labels and one for sublabels, in order to
|
||||
* borrow the hash tables mutably. The text rasterizing operation borrows
|
||||
* the self as mutable to store fonts for later use. Since the self and hash
|
||||
* table cannot be mutable at the same time, this cannot be abstracted to a
|
||||
* mutable self and mutable hash table.
|
||||
*/
|
||||
fn rasterize_label(&mut self, size: f64, sym: Keysym)
|
||||
{
|
||||
if !self.labels.contains_key(&sym) {
|
||||
let label = Self::keysym_to_label(sym);
|
||||
|
||||
let size = if Self::is_keysym_small(sym) {
|
||||
size * 0.75
|
||||
} else {
|
||||
size
|
||||
};
|
||||
|
||||
let img = self.rasterize_text(size, &label);
|
||||
self.labels.insert(sym, img);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rasterize a key symbol and store it in the sublabels hash table.
|
||||
*
|
||||
* We need two functions, one for labels and one for sublabels, in order to
|
||||
* borrow the hash tables mutably. The text rasterizing operation borrows
|
||||
* the self as mutable to store fonts for later use. Since the self and hash
|
||||
* table cannot be mutable at the same time, this cannot be abstracted to a
|
||||
* mutable self and mutable hash table.
|
||||
*/
|
||||
fn rasterize_sublabel(&mut self, size: f64, sym: Keysym)
|
||||
{
|
||||
if !self.sublabels.contains_key(&sym) {
|
||||
let label = Self::keysym_to_label(sym);
|
||||
|
||||
let size = if Self::is_keysym_small(sym) {
|
||||
size * 0.75
|
||||
} else {
|
||||
size
|
||||
};
|
||||
|
||||
let img = self.rasterize_text(size, &label);
|
||||
self.sublabels.insert(sym, img);
|
||||
}
|
||||
}
|
||||
|
||||
fn rasterize_labels(&mut self, layout: &Layout, mod_state: &[ModState])
|
||||
{
|
||||
self.labels = HashMap::new();
|
||||
self.sublabels = HashMap::new();
|
||||
|
||||
let label_size = self.y_scale * 0.33;
|
||||
let sublabel_size = self.y_scale * 0.22;
|
||||
|
||||
for row in layout.rows() {
|
||||
self.labels.reserve(row.len());
|
||||
self.sublabels.reserve(row.len() * 8);
|
||||
|
||||
for key in row {
|
||||
let sym = key.parts[0].display_symbol(mod_state);
|
||||
self.rasterize_label(label_size, sym);
|
||||
|
||||
for part in &key.parts[1..] {
|
||||
let sym = part.display_symbol(mod_state);
|
||||
self.rasterize_sublabel(sublabel_size, sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_label_part(labels: &HashMap<Keysym, ImgVec<u8>>,
|
||||
x_anchor: Anchor, y_anchor: Anchor,
|
||||
img: &mut ImgRefMut<BGRA<u8>>,
|
||||
sym: Keysym, fg_color: BGR<f32>)
|
||||
{
|
||||
let src = labels.get(&sym)
|
||||
.expect("Layout and size should be set before drawing");
|
||||
|
||||
let x = match x_anchor {
|
||||
Anchor::Min => 0,
|
||||
Anchor::Center => (img.width() - src.width()) / 2,
|
||||
Anchor::Max => img.width() - src.width(),
|
||||
};
|
||||
|
||||
let y = match y_anchor {
|
||||
Anchor::Min => 0,
|
||||
Anchor::Center => (img.height() - src.height()) / 2,
|
||||
Anchor::Max => img.height() - src.height(),
|
||||
};
|
||||
|
||||
let width = src.width();
|
||||
let height = src.height();
|
||||
|
||||
let src = src.sub_image(0, 0, width, height);
|
||||
let dest = img.sub_image_mut(x, y, width, height);
|
||||
|
||||
convert_gray_to_bgrx(dest, src, fg_color);
|
||||
}
|
||||
|
||||
fn keypart_to_color(part: &Part, sublabel: bool,
|
||||
mod_state: &[ModState]) -> BGR<f32>
|
||||
{
|
||||
let color_locked = BGR { r: 0.0, g: 1.0, b: 0.1, };
|
||||
let color_pressed = BGR { r: 0.0, g: 0.6, b: 1.0, };
|
||||
let color_label = BGR { r: 1.0, g: 1.0, b: 1.0, };
|
||||
let color_sublabel = BGR { r: 0.7, g: 0.7, b: 0.7, };
|
||||
|
||||
let modifier = part.modifier_id();
|
||||
if modifier != 0 {
|
||||
let modifier = modifier - 1;
|
||||
if mod_state[modifier] == ModState::Locked
|
||||
|| mod_state[modifier] == ModState::HeldLocked {
|
||||
return color_locked;
|
||||
} else if mod_state[modifier] != ModState::Released {
|
||||
return color_pressed;
|
||||
}
|
||||
}
|
||||
|
||||
if part.presses() != 0 {
|
||||
return color_pressed;
|
||||
}
|
||||
|
||||
if sublabel {
|
||||
return color_sublabel;
|
||||
}
|
||||
|
||||
let res = LABELS.binary_search_by_key(&part.sym(), |&Label { sym, .. }| sym);
|
||||
if let Ok(idx) = res {
|
||||
if LABELS[idx].secondary {
|
||||
return color_sublabel;
|
||||
}
|
||||
}
|
||||
|
||||
color_label
|
||||
}
|
||||
|
||||
fn draw_key(labels: &HashMap<Keysym, ImgVec<u8>>,
|
||||
sublabels: &HashMap<Keysym, ImgVec<u8>>,
|
||||
x_scale: f64, y_scale: f64,
|
||||
img: &mut ImgRefMut<BGRA<u8>>,
|
||||
key: &Key, mod_state: &[ModState])
|
||||
{
|
||||
let x1 = (key.x1 * x_scale) as usize;
|
||||
let y1 = (key.y1 * y_scale) as usize;
|
||||
let x2 = (key.x2 * x_scale) as usize;
|
||||
let y2 = (key.y2 * y_scale) as usize;
|
||||
|
||||
let mut keyimg = img.sub_image_mut(x1, y1, x2 - x1, y2 - y1);
|
||||
|
||||
let mut pressed = false;
|
||||
for part in &key.parts {
|
||||
let modifier = part.modifier_id();
|
||||
if modifier != 0 && mod_state[modifier - 1] != ModState::Released {
|
||||
pressed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if part.presses() != 0 {
|
||||
pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if pressed {
|
||||
fill_image(&mut keyimg, BGRA { r: 51, g: 51, b: 51, a: 0, });
|
||||
} else {
|
||||
fill_image(&mut keyimg, BGRA { r: 0, g: 0, b: 0, a: 0, });
|
||||
}
|
||||
|
||||
let color = Self::keypart_to_color(&key.parts[0], false, mod_state);
|
||||
let sym = key.parts[0].display_symbol(mod_state);
|
||||
Self::draw_label_part(labels, Anchor::Center, Anchor::Center,
|
||||
&mut keyimg, sym, color);
|
||||
|
||||
for i in 0..8 {
|
||||
let color = Self::keypart_to_color(&key.parts[i + 1], true, mod_state);
|
||||
let sym = key.parts[i + 1].display_symbol(mod_state);
|
||||
Self::draw_label_part(sublabels, ANCHORS[i].0, ANCHORS[i].1,
|
||||
&mut keyimg, sym, color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_single(&mut self, key: &Key, mod_state: &[ModState])
|
||||
{
|
||||
let mut img = self.disp.begin();
|
||||
|
||||
let x1 = (key.x1 * self.x_scale) as u32;
|
||||
let y1 = (key.y1 * self.y_scale) as u32;
|
||||
let x2 = (key.x2 * self.x_scale) as u32;
|
||||
let y2 = (key.y2 * self.y_scale) as u32;
|
||||
|
||||
Self::draw_key(&self.labels, &self.sublabels,
|
||||
self.x_scale, self.y_scale,
|
||||
&mut img, key, mod_state);
|
||||
|
||||
self.disp.end(x1, y1, x2 - x1, y2 - y1);
|
||||
}
|
||||
|
||||
pub fn draw_modifiers(&mut self,
|
||||
layout: &Layout,
|
||||
mod_state: &[ModState],
|
||||
modifiers: &[bool])
|
||||
{
|
||||
for row in layout.rows() {
|
||||
for key in row {
|
||||
let draw = key.parts.iter().any(|p| p.modifier_id() != 0
|
||||
&& modifiers[p.modifier_id() - 1]);
|
||||
if draw {
|
||||
self.draw_single(key, mod_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, layout: &Layout, mod_state: &[ModState])
|
||||
{
|
||||
let mut img = self.disp.begin();
|
||||
|
||||
fill_image(&mut img, BGRA { r: 0, g: 0, b: 0, a: 0, });
|
||||
|
||||
for row in layout.rows() {
|
||||
for key in row {
|
||||
Self::draw_key(&self.labels, &self.sublabels,
|
||||
self.x_scale, self.y_scale,
|
||||
&mut img, key, mod_state);
|
||||
}
|
||||
}
|
||||
|
||||
let (width, height) = (img.width() as u32, img.height() as u32);
|
||||
self.disp.end(0, 0, width, height);
|
||||
}
|
||||
|
||||
pub fn change_layout(&mut self, layout: &Layout, mod_state: &[ModState])
|
||||
{
|
||||
self.rasterize_labels(layout, mod_state);
|
||||
self.draw(layout, mod_state);
|
||||
}
|
||||
|
||||
pub fn resize(&mut self,
|
||||
layout: &Layout, mod_state: &[ModState],
|
||||
width: u32, height: u32)
|
||||
{
|
||||
self.x_scale = width as f64 / layout.width();
|
||||
self.y_scale = height as f64 / layout.height();
|
||||
|
||||
self.rasterize_labels(layout, mod_state);
|
||||
|
||||
self.disp.resize(width, height);
|
||||
self.draw(layout, mod_state);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn display(&self) -> &D
|
||||
{
|
||||
&self.disp
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn display_mut(&mut self) -> &mut D
|
||||
{
|
||||
&mut self.disp
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Display> Drop for Graphics<D> {
|
||||
fn drop(&mut self)
|
||||
{
|
||||
unsafe {
|
||||
freetype::FT_Done_FreeType(self.ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
540
src/core/layout.rs
Normal file
540
src/core/layout.rs
Normal file
|
|
@ -0,0 +1,540 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Copyright (c) 2024, Richard Acayan. All rights reserved.
|
||||
*/
|
||||
|
||||
use core::button::ModState;
|
||||
use core::expat;
|
||||
use core::xkeysym::Keysym;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io::Error;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Read;
|
||||
use std::os::raw::c_char;
|
||||
use std::os::raw::c_void;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
unsafe extern "C" fn start_elem(data: *mut c_void,
|
||||
c_name: *const expat::XML_Char,
|
||||
c_attrs: *mut *const expat::XML_Char)
|
||||
{
|
||||
let layout = &mut *(data as *mut Layout);
|
||||
let name = CStr::from_ptr(c_name)
|
||||
.to_str().expect("layout: XML element name must be UTF-8");
|
||||
|
||||
let mut attrs = HashMap::with_capacity(10);
|
||||
|
||||
while !ptr::eq(*c_attrs.add(attrs.len() * 2), ptr::null()) {
|
||||
let k = CStr::from_ptr(*c_attrs.add(attrs.len() * 2))
|
||||
.to_str().expect("layout: XML attribute name must be UTF-8");
|
||||
let v = CStr::from_ptr(*c_attrs.add(attrs.len() * 2 + 1))
|
||||
.to_str().expect("layout: XML attribute value must be UTF-8");
|
||||
|
||||
attrs.insert(k, v);
|
||||
}
|
||||
|
||||
layout.start_elem(name, &attrs);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn end_elem(data: *mut c_void, c_name: *const expat::XML_Char)
|
||||
{
|
||||
let layout = &mut *(data as *mut Layout);
|
||||
let name = CStr::from_ptr(c_name)
|
||||
.to_str().expect("layout: XML element name must be UTF-8");
|
||||
|
||||
layout.end_elem(name);
|
||||
}
|
||||
|
||||
pub const MOD_SHIFT: usize = 1;
|
||||
pub const MOD_CTRL: usize = 2;
|
||||
pub const MOD_ALT: usize = 3;
|
||||
pub const MOD_FN: usize = 4;
|
||||
pub const MODIFIERS_MAX: usize = 4;
|
||||
|
||||
pub struct Part {
|
||||
orig_sym: Keysym,
|
||||
sym: Keysym,
|
||||
|
||||
// TODO: use accessors here
|
||||
presses: u32,
|
||||
}
|
||||
|
||||
impl Part {
|
||||
pub fn modify_shift(orig_sym: Keysym) -> Keysym
|
||||
{
|
||||
match orig_sym {
|
||||
Keysym::a => Keysym::A,
|
||||
Keysym::b => Keysym::B,
|
||||
Keysym::c => Keysym::C,
|
||||
Keysym::d => Keysym::D,
|
||||
Keysym::e => Keysym::E,
|
||||
Keysym::f => Keysym::F,
|
||||
Keysym::g => Keysym::G,
|
||||
Keysym::h => Keysym::H,
|
||||
Keysym::i => Keysym::I,
|
||||
Keysym::j => Keysym::J,
|
||||
Keysym::k => Keysym::K,
|
||||
Keysym::l => Keysym::L,
|
||||
Keysym::m => Keysym::M,
|
||||
Keysym::n => Keysym::N,
|
||||
Keysym::o => Keysym::O,
|
||||
Keysym::p => Keysym::P,
|
||||
Keysym::q => Keysym::Q,
|
||||
Keysym::r => Keysym::R,
|
||||
Keysym::s => Keysym::S,
|
||||
Keysym::t => Keysym::T,
|
||||
Keysym::u => Keysym::U,
|
||||
Keysym::v => Keysym::V,
|
||||
Keysym::w => Keysym::W,
|
||||
Keysym::x => Keysym::X,
|
||||
Keysym::y => Keysym::Y,
|
||||
Keysym::z => Keysym::Z,
|
||||
|
||||
Keysym::ae => Keysym::AE,
|
||||
Keysym::oe => Keysym::OE,
|
||||
Keysym::mu => Keysym::Greek_MU,
|
||||
|
||||
_ => orig_sym,
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_fn(orig_sym: Keysym) -> Keysym
|
||||
{
|
||||
match orig_sym {
|
||||
Keysym::Up => Keysym::Prior, // Page Up
|
||||
Keysym::Down => Keysym::Next, // Page Down
|
||||
Keysym::Left => Keysym::Home,
|
||||
Keysym::Right => Keysym::End,
|
||||
Keysym::Escape => Keysym::Insert,
|
||||
|
||||
Keysym::_1 => Keysym::F1,
|
||||
Keysym::_2 => Keysym::F2,
|
||||
Keysym::_3 => Keysym::F3,
|
||||
Keysym::_4 => Keysym::F4,
|
||||
Keysym::_5 => Keysym::F5,
|
||||
Keysym::_6 => Keysym::F6,
|
||||
Keysym::_7 => Keysym::F7,
|
||||
Keysym::_8 => Keysym::F8,
|
||||
Keysym::_9 => Keysym::F9,
|
||||
Keysym::_0 => Keysym::F10,
|
||||
|
||||
Keysym::less => Keysym::guillemetleft,
|
||||
Keysym::greater => Keysym::guillemetright,
|
||||
// Keysym::braceleft => Keysym::from_char('\u{2039}'),
|
||||
// Keysym::braceright => Keysym::from_char('\u{203A}'),
|
||||
Keysym::bracketleft => Keysym::leftsinglequotemark,
|
||||
Keysym::bracketright => Keysym::rightsinglequotemark,
|
||||
Keysym::parenleft => Keysym::leftdoublequotemark,
|
||||
Keysym::parenright => Keysym::rightdoublequotemark,
|
||||
Keysym::apostrophe => Keysym::singlelowquotemark,
|
||||
Keysym::quotedbl => Keysym::doublelowquotemark,
|
||||
Keysym::minus => Keysym::endash,
|
||||
Keysym::underscore => Keysym::emdash,
|
||||
Keysym::asciicircum => Keysym::notsign,
|
||||
Keysym::percent => Keysym::permille,
|
||||
Keysym::equal => Keysym::approxeq,
|
||||
Keysym::u => Keysym::mu,
|
||||
Keysym::a => Keysym::ae,
|
||||
Keysym::o => Keysym::oe,
|
||||
Keysym::asterisk => Keysym::degree,
|
||||
Keysym::period => Keysym::ellipsis,
|
||||
Keysym::comma => Keysym::periodcentered,
|
||||
Keysym::exclam => Keysym::exclamdown,
|
||||
Keysym::question => Keysym::questiondown,
|
||||
Keysym::bar => Keysym::brokenbar,
|
||||
|
||||
Keysym::e => Keysym::EuroSign,
|
||||
Keysym::l => Keysym::sterling,
|
||||
// Keysym::r => Keysym::from_char('\u{20B9}'),
|
||||
Keysym::y => Keysym::yen,
|
||||
Keysym::c => Keysym::cent,
|
||||
// Keysym::p => Keysym::from_char('\u{20BD}'),
|
||||
// Keysym::b => Keysym::from_char('\u{20B1}'),
|
||||
// Keysym::h => Keysym::from_char('\u{20B4}'),
|
||||
// Keysym::z => Keysym::from_char('\u{20BF}'),
|
||||
|
||||
_ => orig_sym,
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_no_fn(orig_sym: Keysym) -> Keysym
|
||||
{
|
||||
match orig_sym {
|
||||
/*
|
||||
Keysym::F11 => Keysym::NoSymbol,
|
||||
Keysym::F12 => Keysym::NoSymbol,
|
||||
*/
|
||||
_ => orig_sym,
|
||||
}
|
||||
}
|
||||
|
||||
fn new(orig_sym: Keysym) -> Part
|
||||
{
|
||||
Part {
|
||||
orig_sym,
|
||||
sym: Self::modify_no_fn(orig_sym),
|
||||
presses: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn sym(&self) -> Keysym
|
||||
{
|
||||
self.sym
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn press(&mut self)
|
||||
{
|
||||
self.presses += 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn release(&mut self)
|
||||
{
|
||||
self.presses -= 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn presses(&self) -> u32
|
||||
{
|
||||
self.presses
|
||||
}
|
||||
|
||||
pub fn modifier_id(&self) -> usize
|
||||
{
|
||||
match self.sym {
|
||||
Keysym::Shift_L => MOD_SHIFT,
|
||||
Keysym::Control_L => MOD_CTRL,
|
||||
Keysym::Alt_L => MOD_ALT,
|
||||
Keysym::XF86_Fn => MOD_FN,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_modifiers(&mut self, mod_state: &[ModState])
|
||||
{
|
||||
let mut sym = self.orig_sym;
|
||||
|
||||
if mod_state[MOD_FN - 1] != ModState::Released {
|
||||
sym = Self::modify_fn(sym);
|
||||
} else {
|
||||
sym = Self::modify_no_fn(sym);
|
||||
}
|
||||
|
||||
self.sym = sym;
|
||||
}
|
||||
|
||||
pub fn display_symbol(&self, mod_state: &[ModState]) -> Keysym
|
||||
{
|
||||
let mut sym = self.sym;
|
||||
|
||||
if mod_state[MOD_SHIFT - 1] != ModState::Released {
|
||||
sym = Self::modify_shift(sym);
|
||||
}
|
||||
|
||||
sym
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Key {
|
||||
// NOPUSH
|
||||
pub parts: [Part; 9],
|
||||
pub x1: f64,
|
||||
pub y1: f64,
|
||||
pub x2: f64,
|
||||
pub y2: f64,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
fn new(x1: f64, y1: f64, x2: f64, y2: f64,
|
||||
center: Keysym,
|
||||
top_left: Keysym,
|
||||
top_right: Keysym,
|
||||
bottom_left: Keysym,
|
||||
bottom_right: Keysym,
|
||||
left: Keysym,
|
||||
right: Keysym,
|
||||
top: Keysym,
|
||||
bottom: Keysym) -> Key
|
||||
{
|
||||
Key {
|
||||
x1, y1, x2, y2,
|
||||
parts: [
|
||||
Part::new(center),
|
||||
Part::new(top_left),
|
||||
Part::new(top_right),
|
||||
Part::new(bottom_left),
|
||||
Part::new(bottom_right),
|
||||
Part::new(left),
|
||||
Part::new(right),
|
||||
Part::new(top),
|
||||
Part::new(bottom),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Layout {
|
||||
rows: Vec<Vec<Key>>,
|
||||
width: f64,
|
||||
height: f64,
|
||||
row_width: f64,
|
||||
in_row: u32,
|
||||
}
|
||||
|
||||
const KEYSYMS: [(&str, Keysym); 20] = [
|
||||
("\\#", Keysym::numbersign),
|
||||
("\\?", Keysym::question),
|
||||
("\\@", Keysym::at),
|
||||
("\\\\", Keysym::backslash),
|
||||
("backspace", Keysym::BackSpace),
|
||||
("ctrl", Keysym::Control_L),
|
||||
("delete", Keysym::Delete),
|
||||
("down", Keysym::Down),
|
||||
("enter", Keysym::Return),
|
||||
("esc", Keysym::Escape),
|
||||
("f11_placeholder", Keysym::F11),
|
||||
("f12_placeholder", Keysym::F12),
|
||||
("fn", Keysym::XF86_Fn),
|
||||
("left", Keysym::Left),
|
||||
("loc alt", Keysym::Alt_L),
|
||||
("right", Keysym::Right),
|
||||
("shift", Keysym::Shift_L),
|
||||
("space", Keysym::space),
|
||||
("tab", Keysym::Tab),
|
||||
("up", Keysym::Up),
|
||||
];
|
||||
|
||||
impl Layout {
|
||||
pub fn is_keysym_modifier(modifier: usize) -> bool
|
||||
{
|
||||
match modifier {
|
||||
MOD_FN => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_label_modifier(modifier: usize) -> bool
|
||||
{
|
||||
// Shift does not change the keysym used for input, but changes the
|
||||
// rendered keysym.
|
||||
Self::is_keysym_modifier(modifier) || modifier == MOD_SHIFT
|
||||
}
|
||||
|
||||
pub fn modifier_keysym(modifier: usize) -> Keysym
|
||||
{
|
||||
match modifier {
|
||||
MOD_SHIFT => Keysym::Shift_L,
|
||||
MOD_CTRL => Keysym::Control_L,
|
||||
MOD_ALT => Keysym::Alt_L,
|
||||
MOD_FN => Keysym::XF86_Fn,
|
||||
_ => Keysym::NoSymbol,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_keysym_by_name(name: &str) -> Keysym
|
||||
{
|
||||
if let Ok(idx) = KEYSYMS.binary_search_by_key(&name, |&(k, _)| k) {
|
||||
return KEYSYMS[idx].1;
|
||||
}
|
||||
|
||||
let mut chars = name.chars();
|
||||
|
||||
let c1 = chars.next();
|
||||
let c2 = chars.next();
|
||||
match (c1, c2) {
|
||||
(Some(c), None) => Keysym::new(c as u32),
|
||||
_ => Keysym::NoSymbol,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_key(&mut self, attrs: &HashMap<&str, &str>)
|
||||
{
|
||||
if self.in_row != 1 {
|
||||
println!("layout: key element not in row element ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
let shift = f64::from_str(attrs.get("shift").unwrap_or(&"")).unwrap_or(0.0);
|
||||
let x1 = self.row_width + shift;
|
||||
|
||||
let width = f64::from_str(attrs.get("width").unwrap_or(&"")).unwrap_or(1.0);
|
||||
let x2 = x1 + width;
|
||||
|
||||
self.row_width = x2;
|
||||
|
||||
let key = Key::new(
|
||||
x1, self.height, x2, self.height + 1.0,
|
||||
Self::get_keysym_by_name(attrs.get("key0").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key1").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key2").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key3").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key4").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key5").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key6").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key7").unwrap_or(&"")),
|
||||
Self::get_keysym_by_name(attrs.get("key8").unwrap_or(&""))
|
||||
);
|
||||
|
||||
let idx = self.rows.len() - 1;
|
||||
self.rows[idx].push(key);
|
||||
}
|
||||
|
||||
fn start_row(&mut self)
|
||||
{
|
||||
self.rows.push(Vec::new());
|
||||
self.in_row += 1;
|
||||
self.row_width = 0.0;
|
||||
}
|
||||
|
||||
fn start_elem(&mut self, name: &str, attrs: &HashMap<&str, &str>)
|
||||
{
|
||||
if name == "key" {
|
||||
self.start_key(attrs);
|
||||
} else if name == "row" {
|
||||
self.start_row();
|
||||
}
|
||||
}
|
||||
|
||||
fn end_row(&mut self)
|
||||
{
|
||||
self.in_row -= 1;
|
||||
|
||||
if self.row_width > self.width {
|
||||
self.width = self.row_width;
|
||||
}
|
||||
|
||||
self.height += 1.0;
|
||||
}
|
||||
|
||||
fn end_elem(&mut self, name: &str)
|
||||
{
|
||||
if name == "row" {
|
||||
self.end_row();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(dir: &Path, filename: &str) -> Result<Self, Error>
|
||||
{
|
||||
let mut layout = Layout {
|
||||
rows: Vec::new(),
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
row_width: 0.0,
|
||||
in_row: 0,
|
||||
};
|
||||
|
||||
let mut vec = Vec::new();
|
||||
let path = dir.join(filename);
|
||||
let mut file = File::open(path)?;
|
||||
file.read_to_end(&mut vec)?;
|
||||
|
||||
let mut vec2 = Vec::new();
|
||||
let path = dir.join("bottom_row.xml");
|
||||
let mut file = File::open(path)?;
|
||||
file.read_to_end(&mut vec2)?;
|
||||
|
||||
unsafe {
|
||||
let parser = expat::XML_ParserCreate(ptr::null());
|
||||
if ptr::eq(parser, ptr::null_mut()) {
|
||||
return Err(Error::new(ErrorKind::OutOfMemory, "Could not parse XML"));
|
||||
}
|
||||
|
||||
expat::XML_SetElementHandler(parser, Some(start_elem), Some(end_elem));
|
||||
expat::XML_SetUserData(parser, &mut layout as *mut Layout as *mut c_void);
|
||||
|
||||
let status = expat::XML_Parse(parser, vec.as_ptr() as *const c_char,
|
||||
vec.len() as i32, 1);
|
||||
if status != expat::XML_Status_XML_STATUS_OK {
|
||||
return Err(Error::new(ErrorKind::InvalidData, "Could not parse XML"));
|
||||
}
|
||||
|
||||
expat::XML_ParserReset(parser, ptr::null());
|
||||
expat::XML_SetElementHandler(parser, Some(start_elem), Some(end_elem));
|
||||
expat::XML_SetUserData(parser, &mut layout as *mut Layout as *mut c_void);
|
||||
|
||||
let status = expat::XML_Parse(parser, vec2.as_ptr() as *const c_char,
|
||||
vec2.len() as i32, 1);
|
||||
if status != expat::XML_Status_XML_STATUS_OK {
|
||||
return Err(Error::new(ErrorKind::InvalidData, "Could not parse XML"));
|
||||
}
|
||||
|
||||
expat::XML_ParserFree(parser);
|
||||
};
|
||||
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn width(&self) -> f64
|
||||
{
|
||||
self.width
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn height(&self) -> f64
|
||||
{
|
||||
self.height
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn rows(&self) -> &Vec<Vec<Key>>
|
||||
{
|
||||
&self.rows
|
||||
}
|
||||
|
||||
pub fn locate_key(&self, x: f64, y: f64) -> Option<&Key>
|
||||
{
|
||||
let row = &self.rows[y as usize];
|
||||
let res = row.binary_search_by(|k| {
|
||||
if k.x2 < x {
|
||||
Ordering::Less
|
||||
} else if k.x1 > x {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(i) => Some(&row[i]),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn locate_key_mut(&mut self, x: f64, y: f64) -> Option<&mut Key>
|
||||
{
|
||||
let row = &mut self.rows[y as usize];
|
||||
let res = row.binary_search_by(|k| {
|
||||
if k.x2 < x {
|
||||
Ordering::Less
|
||||
} else if k.x1 > x {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(i) => Some(&mut row[i]),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_modifiers(&mut self, mod_state: &[ModState])
|
||||
{
|
||||
for row in self.rows.iter_mut() {
|
||||
for key in row.iter_mut() {
|
||||
for part in key.parts.iter_mut() {
|
||||
part.update_modifiers(mod_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/core/mod.rs
Normal file
23
src/core/mod.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Copyright (c) 2024, Richard Acayan. All rights reserved.
|
||||
*/
|
||||
|
||||
pub extern crate fontconfig;
|
||||
pub extern crate freetype;
|
||||
pub extern crate imgref;
|
||||
pub extern crate rgb;
|
||||
pub extern crate xkeysym;
|
||||
|
||||
mod button;
|
||||
mod expat;
|
||||
mod graphics;
|
||||
mod layout;
|
||||
|
||||
pub use self::button::Button;
|
||||
pub use self::button::Keyboard;
|
||||
pub use self::button::ModState;
|
||||
pub use self::graphics::Display;
|
||||
pub use self::graphics::Graphics;
|
||||
pub use self::layout::Layout;
|
||||
pub use self::layout::Part;
|
||||
Loading…
Add table
Add a link
Reference in a new issue