layout: Make press handling more transparent

In adition to that, an attempt at multiple locked levels of keys was added.
This commit is contained in:
Dorota Czaplejewicz
2019-12-01 13:36:19 +00:00
parent dcbd55c31e
commit e31ef19d03
5 changed files with 270 additions and 122 deletions

View File

@ -6,8 +6,8 @@ use std::ffi::CString;
#[derive(Debug, Clone, PartialEq)]
pub struct KeySym(pub String);
/// Use to switch layouts
type Level = String;
/// Name of a set of buttons displayed at one time within the layout
type View = String;
/// Use to send modified keypresses
#[derive(Debug, Clone, PartialEq)]
@ -20,12 +20,12 @@ pub enum Modifier {
#[derive(Debug, Clone, PartialEq)]
pub enum Action {
/// Switch to this view
SetLevel(Level),
SetView(View),
/// Switch to a view and latch
LockLevel {
lock: Level,
LockView {
lock: View,
/// When unlocked by pressing it or emitting a key
unlock: Level,
unlock: View,
},
/// Set this modifier TODO: release?
SetModifier(Modifier),

View File

@ -504,7 +504,7 @@ fn create_action<H: WarningHandler>(
match submission {
SubmitData::Action(
Action::SetView(view_name)
) => ::action::Action::SetLevel(
) => ::action::Action::SetView(
filter_view_name(
name, view_name.clone(), &view_names,
warning_handler,
@ -512,7 +512,7 @@ fn create_action<H: WarningHandler>(
),
SubmitData::Action(Action::Locking {
lock_view, unlock_view
}) => ::action::Action::LockLevel {
}) => ::action::Action::LockView {
lock: filter_view_name(
name,
lock_view.clone(),

View File

@ -55,15 +55,27 @@ pub struct KeyState {
impl KeyState {
#[must_use]
pub fn activate(self) -> KeyState {
pub fn into_activated(self) -> KeyState {
match self.action {
Action::LockLevel { lock: _, unlock: _ } => KeyState {
Action::LockView { lock: _, unlock: _ } => KeyState {
locked: self.locked ^ true,
..self
},
_ => self,
}
}
#[must_use]
pub fn into_switched(self) -> KeyState {
use self::PressType::*;
KeyState {
pressed: match self.pressed {
Released => Pressed,
Pressed => Released,
},
..self
}
}
}
/// Generates a mapping where each key gets a keycode, starting from ~~8~~

View File

@ -20,6 +20,7 @@
use std::cell::RefCell;
use std::collections::{ HashMap, HashSet };
use std::ffi::CString;
use std::fmt;
use std::rc::Rc;
use std::vec::Vec;
@ -28,6 +29,7 @@ use ::float_ord::FloatOrd;
use ::keyboard::{ KeyState, PressType };
use ::submission::{ Timestamp, VirtualKeyboard };
use ::util;
use ::util::Stack;
use std::borrow::Borrow;
use std::iter::FromIterator;
@ -193,10 +195,7 @@ pub mod c {
pub extern "C"
fn squeek_layout_get_current_view(layout: *const Layout) -> *const View {
let layout = unsafe { &*layout };
let view_name = layout.current_view.clone();
layout.views.get(&view_name)
.expect("Current view doesn't exist")
.as_ref() as *const View
layout.get_current_view().as_ref() as *const View
}
#[no_mangle]
@ -356,21 +355,25 @@ pub mod c {
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
// The list must be copied,
// because it will be mutated in the loop
let ui_backend = UIBackend {
widget_to_layout,
keyboard: ui_keyboard,
};
let keys = layout.get_pressed_keys();
for key in keys {
let key: &Rc<RefCell<KeyState>> = key.borrow();
ui::release_key(
seat::release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
Some(&ui_backend),
time,
ui_keyboard,
key
key,
);
}
}
/// Release all buittons but don't redraw
/// Release all buttons but don't redraw.
/// Use for situations where the UI is gone, e.g. unmapped.
#[no_mangle]
pub extern "C"
fn squeek_layout_release_all_only(
@ -378,15 +381,18 @@ pub mod c {
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
time: u32,
) {
let layout = unsafe { &mut *layout };
let mut layout = unsafe { &mut *layout };
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
let time = Timestamp(time);
for key in layout.get_pressed_keys() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
layout.release_key(
seat::release_key(
&mut layout,
&virtual_keyboard,
None, // don't update anything UI related
time,
&mut key.clone(),
Timestamp(time)
);
}
}
@ -450,6 +456,11 @@ pub mod c {
Point { x: x_widget, y: y_widget }
);
let ui_backend = UIBackend {
keyboard: ui_keyboard,
widget_to_layout,
};
let pressed = layout.get_pressed_keys();
let state_place = {
let view = layout.get_current_view();
@ -466,12 +477,11 @@ pub mod c {
if Rc::ptr_eq(&state, &key) {
found = true;
} else {
ui::release_key(
seat::release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
Some(&ui_backend),
time,
ui_keyboard,
&key,
);
}
@ -482,12 +492,11 @@ pub mod c {
}
} else {
for key in pressed {
ui::release_key(
seat::release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
Some(&ui_backend),
time,
ui_keyboard,
&key,
);
}
@ -740,12 +749,32 @@ pub enum ArrangementKind {
Wide = 1,
}
enum ViewLockKind {
/// Lock gets released the next time something is submitted
OneShot,
/// Lock persists until the lock button is clicked again
Persisting,
}
pub struct ViewLock {
/// Name of the view
view: String,
/// ID of the lock, for unlocking.
/// Ideally unique within the layout
id: String,
kind: ViewLockKind,
}
// TODO: split into sth like
// Arrangement (views) + details (keymap) + State (keys)
/// State of the UI, contains the backend as well
pub struct Layout {
pub kind: ArrangementKind,
pub current_view: String,
/// The view after unlocking all locking view locks.
pub base_view: String,
/// A stack of views that are locked, later locks with higher indices.
/// Setting a view directly discards all.
pub locked_views: Stack<ViewLock>,
// Views own the actual buttons which have state
// Maybe they should own UI only,
// and keys should be owned by a dedicated non-UI-State?
@ -764,8 +793,23 @@ pub struct LayoutData {
pub keymap_str: CString,
}
#[derive(Debug)]
struct NoSuchView;
impl fmt::Display for NoSuchView {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "No such view")
}
}
struct NoSuchLock;
impl fmt::Display for NoSuchLock {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "No such lock")
}
}
// Unfortunately, changes are not atomic due to mutability :(
// An error will not be recoverable
// The usage of &mut on Rc<RefCell<KeyState>> doesn't mean anything special.
@ -774,13 +818,18 @@ impl Layout {
pub fn new(data: LayoutData, kind: ArrangementKind) -> Layout {
Layout {
kind,
current_view: "base".to_owned(),
base_view: "base".to_owned(),
locked_views: Vec::new(),
views: data.views,
keymap_str: data.keymap_str,
}
}
fn get_current_view(&self) -> &Box<View> {
self.views.get(&self.current_view).expect("Selected nonexistent view")
let view_name = self.locked_views.iter().next_back()
.map(|lock| &lock.view)
.unwrap_or(&self.base_view);
self.views.get(view_name).expect("Selected nonexistent view")
}
/// Returns all keys matching filter, without duplicates
@ -824,33 +873,54 @@ impl Layout {
})
}
/// Top oneshot locks can be all removed when a button is pressed
fn get_top_oneshot_locks_idx(&self) -> usize {
let last_persisting_idx = self.locked_views.iter()
.rposition(|lock| match lock.kind {
ViewLockKind::Persisting => true,
ViewLockKind::OneShot => false,
});
match last_persisting_idx {
// don't remove the last persisting item
Some(idx) => idx + 1,
None => 0,
}
}
fn lock_view(&mut self, view: String, id: String)
-> Result<(), NoSuchView>
{
if self.views.contains_key(&view) {
self.locked_views.push(ViewLock {
view,
id,
kind: ViewLockKind::OneShot,
});
Ok(())
} else {
Err(NoSuchView)
}
}
fn unlock_view(&mut self, id: String) -> Result<(), NoSuchLock> {
let lock_idx = self.locked_views.iter()
// find the first occurrence: locking the same key multiple times
// sounds like not something we want to do
.position(|lock| lock.id == id);
lock_idx.map(|idx| self.locked_views.truncate(idx))
.ok_or(NoSuchLock)
}
fn set_view(&mut self, view: String) -> Result<(), NoSuchView> {
if self.views.contains_key(&view) {
self.current_view = view;
self.base_view = view;
self.locked_views = Vec::new();
Ok(())
} else {
Err(NoSuchView)
}
}
fn release_key(
&mut self,
virtual_keyboard: &VirtualKeyboard,
mut key: &mut Rc<RefCell<KeyState>>,
time: Timestamp,
) {
{
let mut bkey = key.borrow_mut();
virtual_keyboard.switch(
&bkey.keycodes,
PressType::Released,
time,
);
bkey.pressed = PressType::Released;
}
self.set_level_from_press(&mut key);
}
fn press_key(
&mut self,
virtual_keyboard: &VirtualKeyboard,
@ -865,47 +935,6 @@ impl Layout {
);
bkey.pressed = PressType::Pressed;
}
fn set_level_from_press(&mut self, key: &Rc<RefCell<KeyState>>) {
fn activate(layout: &mut Layout, key: &Rc<RefCell<KeyState>>) {
let updated = {
let keyref = RefCell::borrow(key);
keyref.clone().activate()
};
RefCell::replace(key, updated.clone());
layout.set_state_from_press(updated.action.clone(), updated.locked);
};
// unlock all
let keys = self.get_locked_keys();
for key in &keys {
activate(self, key);
}
// Don't handle the same key twice, but handle it at least once,
// because its press is the reason we're here
if let None = keys.iter().find(|k| Rc::ptr_eq(k, &key)) {
activate(self, key);
}
}
fn set_state_from_press(&mut self, action: Action, locked: bool) {
let view_name = match action {
Action::SetLevel(name) => {
Some(name.clone())
},
Action::LockLevel { lock, unlock } => {
Some(if locked { lock } else { unlock }.clone())
},
_ => None,
};
if let Some(view_name) = view_name {
if let Err(_e) = self.set_view(view_name.clone()) {
eprintln!("No such view: {}, ignoring switch", view_name)
};
};
}
}
mod procedures {
@ -949,8 +978,7 @@ mod procedures {
key: &Rc<RefCell<KeyState>>,
ui_keyboard: c::EekGtkKeyboard,
) {
let paths = ::layout::procedures::find_key_paths(&view, key);
for (_row, button) in paths {
for (_row, button) in find_key_paths(&view, key) {
unsafe {
c::procedures::eek_gtk_on_button_released(
button.as_ref() as *const Button,
@ -1037,45 +1065,150 @@ mod procedures {
}
}
pub struct UIBackend {
widget_to_layout: c::procedures::Transformation,
keyboard: c::EekGtkKeyboard,
}
/// Top level UI procedures
mod ui {
mod seat {
use super::*;
/// Only use from release_key for when the view switch is ignored
fn set_all_unlocked(layout: &mut Layout) {
for key in layout.get_locked_keys() {
let mut key = RefCell::borrow_mut(&key);
match &key.action {
Action::LockView { lock: _, unlock: _ } => {},
a => eprintln!(
"BUG: action {:?} was found inside locked keys",
a
),
};
key.locked = false;
}
}
/// Only use from release_key. Changes state of other keys only.
fn handle_set_view(layout: &mut Layout, view_name: String) {
set_all_unlocked(layout);
layout.set_view(view_name.clone())
.map_err(|e|
eprintln!("Bad view {} ({}), ignoring", view_name, e)
).ok();
}
fn handle_unstick_locks(layout: &mut Layout) {
let oneshot_idx = layout.get_top_oneshot_locks_idx();
let (_permalocks, onetimes) = layout.locked_views.split_at(oneshot_idx);
// Convert into a hashmap for easier finding of elements
let onetime_ids = HashSet::<&String>::from_iter(
onetimes.into_iter().map(|lock| &lock.id)
);
for key in layout.get_locked_keys() {
let mut key = RefCell::borrow_mut(&key);
match &key.action {
Action::LockView { lock: _, unlock: id } => {
if onetime_ids.contains(id) {
key.locked = false;
}
},
a => eprintln!(
"BUG: action {:?} was found inside locked keys",
a
),
};
}
layout.locked_views.truncate(oneshot_idx);
}
// TODO: turn into release_button
pub fn release_key(
layout: &mut Layout,
virtual_keyboard: &VirtualKeyboard,
widget_to_layout: &c::procedures::Transformation,
ui: Option<&UIBackend>,
time: Timestamp,
ui_keyboard: c::EekGtkKeyboard,
key: &Rc<RefCell<KeyState>>,
rckey: &Rc<RefCell<KeyState>>,
) {
layout.release_key(virtual_keyboard, &mut key.clone(), time);
let key: KeyState = {
RefCell::borrow(rckey).clone()
};
let action = key.action.clone();
let view = layout.get_current_view();
let action = RefCell::borrow(key).action.clone();
if let Action::ShowPreferences = action {
let paths = ::layout::procedures::find_key_paths(
view, key
);
// getting first item will cause mispositioning
// with more than one button with the same key
// on the keyboard
if let Some((row, button)) = paths.get(0) {
let bounds = ::layout::procedures::get_button_bounds(
view, row, button
).unwrap_or_else(|| {
eprintln!("BUG: Clicked button has no position?");
c::Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }
});
::popover::show(
ui_keyboard,
widget_to_layout.reverse_bounds(bounds)
// update
let key = key.into_switched();
let key = match action {
Action::LockView { lock: _, unlock: _ } => key.into_activated(),
_ => key,
};
// process changes
match action {
Action::Submit { text: _, keys: _ } => {
handle_unstick_locks(layout);
virtual_keyboard.switch(
&key.keycodes,
PressType::Released,
time,
);
}
}
procedures::release_ui_buttons(view, key, ui_keyboard);
},
Action::SetView(view) => {
handle_set_view(layout, view)
},
Action::LockView { lock, unlock } => {
// The button that triggered this will be in the right state
// due to commit at the end.
set_all_unlocked(layout);
match key.locked {
true => {
layout.lock_view(lock.clone(), unlock).map_err(|e|
eprintln!("Bad view {} ({}), ignoring", lock, e)
).ok();
},
false => {
layout.unlock_view(unlock.clone()).map_err(|e| {
eprintln!(
"BUG: Bad id {} ({}), resetting locks",
unlock, e
);
layout.set_view("base".into()).unwrap()
}).ok();
},
// TODO: stuck => stick_view(lock, unlock)
}
},
Action::ShowPreferences => if let Some(ui) = &ui {
let view = layout.get_current_view();
let paths = ::layout::procedures::find_key_paths(
view, &rckey
);
// Getting first item will cause mispositioning
// with more than one button with the same key
// on the keyboard.
if let Some((row, button)) = paths.get(0) {
let bounds = ::layout::procedures::get_button_bounds(
view, row, button
).unwrap_or_else(|| {
eprintln!("BUG: Clicked button has no position?");
c::Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }
});
::popover::show(
ui.keyboard,
ui.widget_to_layout.reverse_bounds(bounds)
);
}
},
Action::SetModifier(_) => eprintln!("Modifiers unsupported"),
};
//layout.release_key(virtual_keyboard, &mut key.clone(), time);
if let Some(ui) = ui {
let view = layout.get_current_view();
procedures::release_ui_buttons(view, &rckey, ui.keyboard);
};
// Commits activated button state changes
RefCell::replace(rckey, key);
}
}
@ -1090,7 +1223,7 @@ mod test {
pressed: PressType::Released,
locked: false,
keycodes: Vec::new(),
action: Action::SetLevel("default".into()),
action: Action::SetView("default".into()),
}))
}

View File

@ -198,6 +198,9 @@ pub trait WarningHandler {
fn handle(&mut self, warning: &str);
}
/// TODO: only allow indexing, push, peek, and pop
pub type Stack<T> = Vec<T>;
#[cfg(test)]
mod tests {
use super::*;