renderer: Mark latched buttons differently than locked

There are some hacks here in the form of an extra field "appears_locked_from", which can be used to hint that the user should see the button as locked. Without it, there's some confusion on user side regarding buttons that change states unprompted.
This commit is contained in:
Dorota Czaplejewicz
2020-12-04 15:27:43 +00:00
parent 676a2b60ac
commit 7b1755a489
6 changed files with 137 additions and 28 deletions

View File

@ -52,6 +52,8 @@ buttons:
locking: locking:
lock_view: "upper_accents" lock_view: "upper_accents"
unlock_view: "accents" unlock_view: "accents"
looks_locked_from:
- "upper"
outline: "altline" outline: "altline"
icon: "key-shift" icon: "key-shift"
BackSpace: BackSpace:
@ -94,6 +96,8 @@ buttons:
locking: locking:
lock_view: "upper_accents" lock_view: "upper_accents"
unlock_view: "upper" unlock_view: "upper"
looks_locked_from:
- "accents"
outline: "altline" outline: "altline"
label: "ĄĘ" label: "ĄĘ"
period: period:

View File

@ -111,8 +111,8 @@ GtkStyleContext *
eek_get_style_context_for_button (EekRenderer *self, eek_get_style_context_for_button (EekRenderer *self,
const char *name, const char *name,
const char *outline_name, const char *outline_name,
uint64_t pressed, const char *locked_class,
uint64_t locked) uint64_t pressed)
{ {
GtkStyleContext *ctx = self->button_context; GtkStyleContext *ctx = self->button_context;
/* Set the name of the button on the widget path, using the name obtained /* 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. */ (pressed) or normal. */
gtk_style_context_set_state(ctx, gtk_style_context_set_state(ctx,
pressed ? GTK_STATE_FLAG_ACTIVE : GTK_STATE_FLAG_NORMAL); pressed ? GTK_STATE_FLAG_ACTIVE : GTK_STATE_FLAG_NORMAL);
if (locked) { if (locked_class) {
gtk_style_context_add_class(ctx, "locked"); gtk_style_context_add_class(ctx, locked_class);
} }
gtk_style_context_add_class(ctx, outline_name); gtk_style_context_add_class(ctx, outline_name);
return ctx; return ctx;
@ -137,12 +137,12 @@ eek_get_style_context_for_button (EekRenderer *self,
/// Interface for Rust. /// Interface for Rust.
void eek_put_style_context_for_button(GtkStyleContext *ctx, void eek_put_style_context_for_button(GtkStyleContext *ctx,
const char *outline_name, 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 // 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_set_state(ctx, GTK_STATE_FLAG_NORMAL);
gtk_style_context_remove_class(ctx, outline_name); gtk_style_context_remove_class(ctx, outline_name);
if (locked) { if (locked_class) {
gtk_style_context_remove_class(ctx, "locked"); gtk_style_context_remove_class(ctx, locked_class);
} }
} }

View File

@ -32,6 +32,8 @@ pub enum Action {
/// Whether key has a latched state /// Whether key has a latched state
/// that pops when another key is pressed. /// that pops when another key is pressed.
latches: bool, latches: bool,
/// Should take on *locked* appearance whenever latch comes back to those views.
looks_locked_from: Vec<View>,
}, },
/// Hold this modifier for as long as the button is pressed /// Hold this modifier for as long as the button is pressed
ApplyModifier(Modifier), ApplyModifier(Modifier),
@ -51,14 +53,24 @@ pub enum Action {
impl Action { impl Action {
pub fn is_locked(&self, view_name: &str) -> bool { pub fn is_locked(&self, view_name: &str) -> bool {
match self { 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, _ => false,
} }
} }
pub fn is_active(&self, view_name: &str) -> bool { pub fn is_active(&self, view_name: &str) -> bool {
match self { match self {
Action::SetView(view) => view == view_name, 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, _ => false,
} }
} }

View File

@ -270,6 +270,8 @@ enum Action {
lock_view: String, lock_view: String,
unlock_view: String, unlock_view: String,
pops: Option<bool>, pops: Option<bool>,
#[serde(default)]
looks_locked_from: Vec<String>,
}, },
#[serde(rename="set_view")] #[serde(rename="set_view")]
SetView(String), SetView(String),
@ -583,6 +585,7 @@ fn create_action<H: logging::Handler>(
SubmitData::Action(Action::Locking { SubmitData::Action(Action::Locking {
lock_view, unlock_view, lock_view, unlock_view,
pops, pops,
looks_locked_from,
}) => ::action::Action::LockView { }) => ::action::Action::LockView {
lock: filter_view_name( lock: filter_view_name(
name, name,
@ -597,6 +600,7 @@ fn create_action<H: logging::Handler>(
warning_handler, warning_handler,
), ),
latches: pops.unwrap_or(true), latches: pops.unwrap_or(true),
looks_locked_from,
}, },
SubmitData::Action( SubmitData::Action(
Action::ShowPrefs Action::ShowPrefs

View File

@ -3,15 +3,16 @@
use cairo; use cairo;
use std::cell::RefCell; use std::cell::RefCell;
use ::action::Action; use ::action::{ Action, Modifier };
use ::keyboard; use ::keyboard;
use ::layout::{ Button, Label, Layout }; use ::layout::{ Button, Label, LatchedState, Layout };
use ::layout::c::{ Bounds, EekGtkKeyboard, Point }; use ::layout::c::{ Bounds, EekGtkKeyboard, Point };
use ::submission::Submission; use ::submission::Submission;
use glib::translate::FromGlibPtrNone; use glib::translate::FromGlibPtrNone;
use gtk::WidgetExt; use gtk::WidgetExt;
use std::collections::HashSet;
use std::ffi::CStr; use std::ffi::CStr;
use std::ptr; use std::ptr;
@ -57,15 +58,15 @@ mod c {
renderer: EekRenderer, renderer: EekRenderer,
name: *const c_char, name: *const c_char,
outline_name: *const c_char, outline_name: *const c_char,
locked_class: *const c_char,
pressed: u64, pressed: u64,
locked: u64,
) -> GtkStyleContext; ) -> GtkStyleContext;
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
pub fn eek_put_style_context_for_button( pub fn eek_put_style_context_for_button(
ctx: GtkStyleContext, ctx: GtkStyleContext,
outline_name: *const c_char, outline_name: *const c_char,
locked: u64, locked_class: *const c_char,
); );
} }
@ -85,13 +86,16 @@ mod c {
layout.foreach_visible_button(|offset, button| { layout.foreach_visible_button(|offset, button| {
let state = RefCell::borrow(&button.state).clone(); let state = RefCell::borrow(&button.state).clone();
let active_mod = match &state.action {
Action::ApplyModifier(m) => active_modifiers.contains(m), let locked = LockedStyle::from_action(
_ => false, &state.action,
}; &active_modifiers,
let locked = state.action.is_active(&layout.current_view) layout.get_view_latched(),
| active_mod; &layout.current_view,
if state.pressed == keyboard::PressType::Pressed || locked { );
if state.pressed == keyboard::PressType::Pressed
|| locked != LockedStyle::Free
{
render_button_at_position( render_button_at_position(
renderer, &cr, renderer, &cr,
offset, offset,
@ -117,20 +121,55 @@ mod c {
renderer, &cr, renderer, &cr,
offset, offset,
button.as_ref(), 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<Modifier>,
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) /// 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, renderer: c::EekRenderer,
cr: &cairo::Context, cr: &cairo::Context,
position: Point, position: Point,
button: &Button, button: &Button,
pressed: keyboard::PressType, pressed: keyboard::PressType,
locked: bool, locked: LockedStyle,
) { ) {
cr.save(); cr.save();
cr.translate(position.x, position.y); cr.translate(position.x, position.y);
@ -182,18 +221,27 @@ fn with_button_context<R, F: FnOnce(&c::GtkStyleContext) -> R>(
renderer: c::EekRenderer, renderer: c::EekRenderer,
button: &Button, button: &Button,
pressed: keyboard::PressType, pressed: keyboard::PressType,
locked: bool, locked: LockedStyle,
operation: F, operation: F,
) -> R { ) -> R {
let outline_name_c = button.outline_name.as_ptr(); 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 { let ctx = unsafe {
c::eek_get_style_context_for_button( c::eek_get_style_context_for_button(
renderer, renderer,
button.name.as_ptr(), button.name.as_ptr(),
outline_name_c, outline_name_c,
locked_class_c,
pressed as u64, pressed as u64,
locked as u64,
) )
}; };
@ -203,7 +251,7 @@ fn with_button_context<R, F: FnOnce(&c::GtkStyleContext) -> R>(
c::eek_put_style_context_for_button( c::eek_put_style_context_for_button(
ctx, ctx,
outline_name_c, 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) }; let widget = unsafe { gtk::Widget::from_glib_none(keyboard.0) };
widget.queue_draw(); 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,
);
}
}

View File

@ -616,12 +616,18 @@ pub struct Margins {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum LatchedState { pub enum LatchedState {
/// Holds view to return to. /// Holds view to return to.
FromView(String), FromView(String),
Not, Not,
} }
impl LatchedState {
pub fn is_latched(&self) -> bool {
self != &LatchedState::Not
}
}
// TODO: split into sth like // TODO: split into sth like
// Arrangement (views) + details (keymap) + State (keys) // Arrangement (views) + details (keymap) + State (keys)
/// State of the UI, contains the backend as well /// 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 /// Calculates size without margins
fn calculate_inner_size(&self) -> Size { fn calculate_inner_size(&self) -> Size {
View::calculate_super_size( View::calculate_super_size(
@ -847,7 +859,7 @@ impl Layout {
ViewTransition::ChangeTo(view), ViewTransition::ChangeTo(view),
LatchedState::Not, LatchedState::Not,
), ),
Action::LockView { lock, unlock, latches } => { Action::LockView { lock, unlock, latches, looks_locked_from: _ } => {
use self::ViewTransition as VT; use self::ViewTransition as VT;
let locked = action.is_locked(current_view); let locked = action.is_locked(current_view);
match (locked, latched, latches) { match (locked, latched, latches) {
@ -1132,6 +1144,7 @@ mod test {
lock: "lock".into(), lock: "lock".into(),
unlock: "unlock".into(), unlock: "unlock".into(),
latches: true, latches: true,
looks_locked_from: vec![],
}; };
assert_eq!( assert_eq!(
@ -1161,6 +1174,7 @@ mod test {
lock: "locked".into(), lock: "locked".into(),
unlock: "base".into(), unlock: "base".into(),
latches: true, latches: true,
looks_locked_from: vec![],
}; };
let submit = Action::Erase; let submit = Action::Erase;
@ -1228,12 +1242,14 @@ mod test {
lock: "locked".into(), lock: "locked".into(),
unlock: "base".into(), unlock: "base".into(),
latches: true, latches: true,
looks_locked_from: vec![],
}; };
let unswitch = Action::LockView { let unswitch = Action::LockView {
lock: "locked".into(), lock: "locked".into(),
unlock: "unlocked".into(), unlock: "unlocked".into(),
latches: false, latches: false,
looks_locked_from: vec![],
}; };
let submit = Action::Erase; let submit = Action::Erase;
@ -1292,12 +1308,14 @@ mod test {
lock: "locked".into(), lock: "locked".into(),
unlock: "base".into(), unlock: "base".into(),
latches: true, latches: true,
looks_locked_from: vec![],
}; };
let switch_again = Action::LockView { let switch_again = Action::LockView {
lock: "ĄĘ".into(), lock: "ĄĘ".into(),
unlock: "locked".into(), unlock: "locked".into(),
latches: true, latches: true,
looks_locked_from: vec![],
}; };
let submit = Action::Erase; let submit = Action::Erase;