diff --git a/data/keyboards/pl.yaml b/data/keyboards/pl.yaml index f6c45411..348ed856 100644 --- a/data/keyboards/pl.yaml +++ b/data/keyboards/pl.yaml @@ -52,6 +52,8 @@ buttons: locking: lock_view: "upper_accents" unlock_view: "accents" + looks_locked_from: + - "upper" outline: "altline" icon: "key-shift" BackSpace: @@ -94,6 +96,8 @@ buttons: locking: lock_view: "upper_accents" unlock_view: "upper" + looks_locked_from: + - "accents" outline: "altline" label: "ĄĘ" period: diff --git a/eek/eek-renderer.c b/eek/eek-renderer.c index e5f1cd52..4f64b6e8 100644 --- a/eek/eek-renderer.c +++ b/eek/eek-renderer.c @@ -111,8 +111,8 @@ GtkStyleContext * eek_get_style_context_for_button (EekRenderer *self, const char *name, const char *outline_name, - uint64_t pressed, - uint64_t locked) + const char *locked_class, + uint64_t pressed) { GtkStyleContext *ctx = self->button_context; /* Set the name of the button on the widget path, using the name obtained @@ -127,8 +127,8 @@ eek_get_style_context_for_button (EekRenderer *self, (pressed) or normal. */ gtk_style_context_set_state(ctx, pressed ? GTK_STATE_FLAG_ACTIVE : GTK_STATE_FLAG_NORMAL); - if (locked) { - gtk_style_context_add_class(ctx, "locked"); + if (locked_class) { + gtk_style_context_add_class(ctx, locked_class); } gtk_style_context_add_class(ctx, outline_name); return ctx; @@ -137,12 +137,12 @@ eek_get_style_context_for_button (EekRenderer *self, /// Interface for Rust. void eek_put_style_context_for_button(GtkStyleContext *ctx, const char *outline_name, - uint64_t locked) { + const char *locked_class) { // Save and restore functions don't work if gtk_render_* was used in between gtk_style_context_set_state(ctx, GTK_STATE_FLAG_NORMAL); gtk_style_context_remove_class(ctx, outline_name); - if (locked) { - gtk_style_context_remove_class(ctx, "locked"); + if (locked_class) { + gtk_style_context_remove_class(ctx, locked_class); } } diff --git a/src/action.rs b/src/action.rs index 862dcbf1..46d34940 100644 --- a/src/action.rs +++ b/src/action.rs @@ -32,6 +32,8 @@ pub enum Action { /// Whether key has a latched state /// that pops when another key is pressed. latches: bool, + /// Should take on *locked* appearance whenever latch comes back to those views. + looks_locked_from: Vec, }, /// Hold this modifier for as long as the button is pressed ApplyModifier(Modifier), @@ -51,14 +53,24 @@ pub enum Action { impl Action { pub fn is_locked(&self, view_name: &str) -> bool { match self { - Action::LockView { lock, unlock: _, latches: _ } => lock == view_name, + Action::LockView { lock, unlock: _, latches: _, looks_locked_from: _ } => lock == view_name, + _ => false, + } + } + pub fn has_locked_appearance_from(&self, locked_view_name: &str) -> bool { + match self { + Action::LockView { lock: _, unlock: _, latches: _, looks_locked_from } => { + looks_locked_from.iter() + .find(|view| locked_view_name == view.as_str()) + .is_some() + }, _ => false, } } pub fn is_active(&self, view_name: &str) -> bool { match self { Action::SetView(view) => view == view_name, - Action::LockView { lock, unlock: _, latches: _ } => lock == view_name, + Action::LockView { lock, unlock: _, latches: _, looks_locked_from: _ } => lock == view_name, _ => false, } } diff --git a/src/data.rs b/src/data.rs index 60e86db6..1fc8939c 100644 --- a/src/data.rs +++ b/src/data.rs @@ -270,6 +270,8 @@ enum Action { lock_view: String, unlock_view: String, pops: Option, + #[serde(default)] + looks_locked_from: Vec, }, #[serde(rename="set_view")] SetView(String), @@ -583,6 +585,7 @@ fn create_action( SubmitData::Action(Action::Locking { lock_view, unlock_view, pops, + looks_locked_from, }) => ::action::Action::LockView { lock: filter_view_name( name, @@ -597,6 +600,7 @@ fn create_action( warning_handler, ), latches: pops.unwrap_or(true), + looks_locked_from, }, SubmitData::Action( Action::ShowPrefs diff --git a/src/drawing.rs b/src/drawing.rs index 432ff938..7dfc3cd7 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -3,15 +3,16 @@ use cairo; use std::cell::RefCell; -use ::action::Action; +use ::action::{ Action, Modifier }; use ::keyboard; -use ::layout::{ Button, Label, Layout }; +use ::layout::{ Button, Label, LatchedState, Layout }; use ::layout::c::{ Bounds, EekGtkKeyboard, Point }; use ::submission::Submission; use glib::translate::FromGlibPtrNone; use gtk::WidgetExt; +use std::collections::HashSet; use std::ffi::CStr; use std::ptr; @@ -57,15 +58,15 @@ mod c { renderer: EekRenderer, name: *const c_char, outline_name: *const c_char, + locked_class: *const c_char, pressed: u64, - locked: u64, ) -> GtkStyleContext; #[allow(improper_ctypes)] pub fn eek_put_style_context_for_button( ctx: GtkStyleContext, outline_name: *const c_char, - locked: u64, + locked_class: *const c_char, ); } @@ -85,13 +86,16 @@ mod c { layout.foreach_visible_button(|offset, button| { let state = RefCell::borrow(&button.state).clone(); - let active_mod = match &state.action { - Action::ApplyModifier(m) => active_modifiers.contains(m), - _ => false, - }; - let locked = state.action.is_active(&layout.current_view) - | active_mod; - if state.pressed == keyboard::PressType::Pressed || locked { + + let locked = LockedStyle::from_action( + &state.action, + &active_modifiers, + layout.get_view_latched(), + &layout.current_view, + ); + if state.pressed == keyboard::PressType::Pressed + || locked != LockedStyle::Free + { render_button_at_position( renderer, &cr, offset, @@ -117,20 +121,55 @@ mod c { renderer, &cr, offset, button.as_ref(), - keyboard::PressType::Released, false, + keyboard::PressType::Released, + LockedStyle::Free, ); }) } } +#[derive(Clone, Copy, PartialEq, Debug)] +enum LockedStyle { + Free, + Latched, + Locked, +} + +impl LockedStyle { + fn from_action( + action: &Action, + mods: &HashSet, + latched_view: &LatchedState, + current_view: &str, + ) -> LockedStyle { + let active_mod = match action { + Action::ApplyModifier(m) => mods.contains(m), + _ => false, + }; + + let active_view = action.is_active(current_view); + let latched_button = match latched_view { + LatchedState::Not => false, + LatchedState::FromView(view) => !action.has_locked_appearance_from(view), + }; + match (active_mod, active_view, latched_button) { + // Modifiers don't latch. + (true, _, _) => LockedStyle::Locked, + (false, true, false) => LockedStyle::Locked, + (false, true, true) => LockedStyle::Latched, + _ => LockedStyle::Free, + } + } +} + /// Renders a button at a position (button's own bounds ignored) -pub fn render_button_at_position( +fn render_button_at_position( renderer: c::EekRenderer, cr: &cairo::Context, position: Point, button: &Button, pressed: keyboard::PressType, - locked: bool, + locked: LockedStyle, ) { cr.save(); cr.translate(position.x, position.y); @@ -182,18 +221,27 @@ fn with_button_context R>( renderer: c::EekRenderer, button: &Button, pressed: keyboard::PressType, - locked: bool, + locked: LockedStyle, operation: F, ) -> R { let outline_name_c = button.outline_name.as_ptr(); + let locked_class_c = match locked { + LockedStyle::Free => ptr::null(), + LockedStyle::Locked => unsafe { + CStr::from_bytes_with_nul_unchecked(b"locked\0").as_ptr() + }, + LockedStyle::Latched => unsafe { + CStr::from_bytes_with_nul_unchecked(b"latched\0").as_ptr() + }, + }; let ctx = unsafe { c::eek_get_style_context_for_button( renderer, button.name.as_ptr(), outline_name_c, + locked_class_c, pressed as u64, - locked as u64, ) }; @@ -203,7 +251,7 @@ fn with_button_context R>( c::eek_put_style_context_for_button( ctx, outline_name_c, - locked as u64, + locked_class_c, ) }; @@ -214,3 +262,26 @@ pub fn queue_redraw(keyboard: EekGtkKeyboard) { let widget = unsafe { gtk::Widget::from_glib_none(keyboard.0) }; widget.queue_draw(); } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_exit_only() { + assert_eq!( + LockedStyle::from_action( + &Action::LockView { + lock: "ab".into(), + unlock: "a".into(), + latches: true, + looks_locked_from: vec!["b".into()], + }, + &HashSet::new(), + &LatchedState::FromView("b".into()), + "ab", + ), + LockedStyle::Locked, + ); + } +} diff --git a/src/layout.rs b/src/layout.rs index 7ca0adb5..8d7c1f21 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -616,12 +616,18 @@ pub struct Margins { } #[derive(Clone, Debug, PartialEq)] -enum LatchedState { +pub enum LatchedState { /// Holds view to return to. FromView(String), Not, } +impl LatchedState { + pub fn is_latched(&self) -> bool { + self != &LatchedState::Not + } +} + // TODO: split into sth like // Arrangement (views) + details (keymap) + State (keys) /// State of the UI, contains the backend as well @@ -707,6 +713,12 @@ impl Layout { } } + // Layout is passed around mutably, + // so better keep the field away from direct access. + pub fn get_view_latched(&self) -> &LatchedState { + &self.view_latched + } + /// Calculates size without margins fn calculate_inner_size(&self) -> Size { View::calculate_super_size( @@ -847,7 +859,7 @@ impl Layout { ViewTransition::ChangeTo(view), LatchedState::Not, ), - Action::LockView { lock, unlock, latches } => { + Action::LockView { lock, unlock, latches, looks_locked_from: _ } => { use self::ViewTransition as VT; let locked = action.is_locked(current_view); match (locked, latched, latches) { @@ -1132,6 +1144,7 @@ mod test { lock: "lock".into(), unlock: "unlock".into(), latches: true, + looks_locked_from: vec![], }; assert_eq!( @@ -1161,6 +1174,7 @@ mod test { lock: "locked".into(), unlock: "base".into(), latches: true, + looks_locked_from: vec![], }; let submit = Action::Erase; @@ -1228,12 +1242,14 @@ mod test { lock: "locked".into(), unlock: "base".into(), latches: true, + looks_locked_from: vec![], }; let unswitch = Action::LockView { lock: "locked".into(), unlock: "unlocked".into(), latches: false, + looks_locked_from: vec![], }; let submit = Action::Erase; @@ -1292,12 +1308,14 @@ mod test { lock: "locked".into(), unlock: "base".into(), latches: true, + looks_locked_from: vec![], }; let switch_again = Action::LockView { lock: "ĄĘ".into(), unlock: "locked".into(), latches: true, + looks_locked_from: vec![], }; let submit = Action::Erase;