diff --git a/src/action.rs b/src/action.rs index 61660653..d928c0e7 100644 --- a/src/action.rs +++ b/src/action.rs @@ -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), diff --git a/src/data.rs b/src/data.rs index 503716ca..da498718 100644 --- a/src/data.rs +++ b/src/data.rs @@ -504,7 +504,7 @@ fn create_action( 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( ), SubmitData::Action(Action::Locking { lock_view, unlock_view - }) => ::action::Action::LockLevel { + }) => ::action::Action::LockView { lock: filter_view_name( name, lock_view.clone(), diff --git a/src/keyboard.rs b/src/keyboard.rs index 411e47c2..242c59e2 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -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~~ diff --git a/src/layout.rs b/src/layout.rs index c465fec8..2e1d842e 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -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> = 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> = 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, // 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> 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 { - 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>, - 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>) { - fn activate(layout: &mut Layout, key: &Rc>) { - 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>, 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>, + rckey: &Rc>, ) { - 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()), })) } diff --git a/src/util.rs b/src/util.rs index 891e9d28..7003412b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -198,6 +198,9 @@ pub trait WarningHandler { fn handle(&mut self, warning: &str); } +/// TODO: only allow indexing, push, peek, and pop +pub type Stack = Vec; + #[cfg(test)] mod tests { use super::*;