From 9fb2764331dfd6e97a9b08b418de388ede65e0a1 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 18 Aug 2022 18:30:19 -0700 Subject: [PATCH 01/13] feat: Extras, tap-hold, and modifier keys Initial implementation of https://github.com/pop-os/keyboard-configurator/issues/146. Backend representation for mod-tap, layer-tap, and modifiers+basic keycode, with conversion to and from text format and numeric keycode. Add shift-select for modifiers with a non-modifier key, extras tab, and tap-hold section. Sensitivity needs to be set based on whether shift is held and what is already selected. Getting this to work properly is awkward with how GTK modifiers are handled. More things need to be added to extras, the layout for basic keys needs to be updated for the latest design, and various style details need to be addressed. And it needs to not show these things for internal keycodes. --- Cargo.lock | 1 + Cargo.toml | 3 +- backend/Cargo.toml | 1 + backend/src/board.rs | 7 +- backend/src/key.rs | 13 +- backend/src/keycode.rs | 250 +++++++++++++++++++++++++++++++++ backend/src/layout/mod.rs | 81 ++++++----- backend/src/lib.rs | 5 +- layouts/picker.json | 152 ++++++++++++++++++++ src/configurator_app.rs | 10 ++ src/keyboard.rs | 28 ++-- src/keyboard_layer.rs | 2 +- src/page.rs | 62 +++++++- src/picker/mod.rs | 209 ++++++++++++++++++++++++--- src/picker/picker_group.rs | 14 +- src/picker/picker_group_box.rs | 180 ++++++++++++------------ src/picker/picker_json.rs | 1 + src/picker/picker_key.rs | 111 ++++++++++++--- src/picker/tap_hold.rs | 231 ++++++++++++++++++++++++++++++ src/style.css | 9 ++ 20 files changed, 1170 insertions(+), 200 deletions(-) create mode 100644 backend/src/keycode.rs create mode 100644 src/picker/tap_hold.rs create mode 100644 src/style.css diff --git a/Cargo.lock b/Cargo.lock index 8427f24c..8962cf72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1530,6 +1530,7 @@ dependencies = [ name = "system76-keyboard-configurator-backend" version = "0.1.0" dependencies = [ + "bitflags", "cascade", "futures", "futures-timer", diff --git a/Cargo.toml b/Cargo.toml index 03e57595..1eb06e53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ members = [ "tools", "ffi", "backend", "widgets" ] [dependencies] cascade = "1" futures = "0.3.13" -gtk = { version = "0.15.0", features = ["v3_22"] } +# 3.24 is packaged by focal +gtk = { version = "0.15.0", features = ["v3_24"] } libc = "0.2" once_cell = "1.4" pangocairo = "0.15.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 92693a9a..09b1585c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -6,6 +6,7 @@ license = "GPL-3.0-or-later" edition = "2018" [dependencies] +bitflags = "1.3.2" cascade = "1" futures = "0.3.13" futures-timer = "3.0.2" diff --git a/backend/src/board.rs b/backend/src/board.rs index 18d9f632..5acbcd0e 100644 --- a/backend/src/board.rs +++ b/backend/src/board.rs @@ -259,7 +259,12 @@ impl Board { let mut key_leds = HashMap::new(); for key in self.keys().iter() { let scancodes = (0..self.layout().meta.num_layers as usize) - .map(|layer| key.get_scancode(layer).unwrap().1) + .map(|layer| { + key.get_scancode(layer) + .unwrap() + .1 + .map_or_else(String::new, |x| x.to_string()) + }) .collect(); map.insert(key.logical_name.clone(), scancodes); if !key.leds.is_empty() { diff --git a/backend/src/key.rs b/backend/src/key.rs index 3e19e253..42d8081e 100644 --- a/backend/src/key.rs +++ b/backend/src/key.rs @@ -1,7 +1,7 @@ use glib::prelude::*; use std::cell::Cell; -use crate::{Board, Daemon, Hs, PhysicalLayoutKey, Rect, Rgb}; +use crate::{Board, Daemon, Hs, Keycode, PhysicalLayoutKey, Rect, Rgb}; #[derive(Debug)] pub struct Key { @@ -143,17 +143,16 @@ impl Key { Ok(()) } - pub fn get_scancode(&self, layer: usize) -> Option<(u16, String)> { + // TODO: operate on keycode enum + pub fn get_scancode(&self, layer: usize) -> Option<(u16, Option)> { let board = self.board(); let scancode = self.scancodes.get(layer)?.get(); - let scancode_name = match board.layout().scancode_to_name(scancode) { - Some(some) => some, - None => String::new(), - }; + let scancode_name = board.layout().scancode_to_name(scancode); Some((scancode, scancode_name)) } - pub async fn set_scancode(&self, layer: usize, scancode_name: &str) -> Result<(), String> { + // TODO: operate on keycode enum + pub async fn set_scancode(&self, layer: usize, scancode_name: &Keycode) -> Result<(), String> { let board = self.board(); let scancode = board .layout() diff --git a/backend/src/keycode.rs b/backend/src/keycode.rs new file mode 100644 index 00000000..001fe57f --- /dev/null +++ b/backend/src/keycode.rs @@ -0,0 +1,250 @@ +// has_keycode logic; convert to/from int +// serialize (Display?)/format +// serde: serialize/deserialize as string + +use bitflags::bitflags; + +bitflags! { + pub struct Mods: u16 { + const CTRL = 0x1; + const SHIFT = 0x2; + const ALT = 0x4; + const SUPER = 0x8; + + const RIGHT = 0x10; + + const RIGHT_CTRL = Self::RIGHT.bits | Self::CTRL.bits; + const RIGHT_SHIFT = Self::RIGHT.bits | Self::SHIFT.bits; + const RIGHT_ALT = Self::RIGHT.bits | Self::ALT.bits; + const RIGHT_SUPER = Self::RIGHT.bits | Self::SUPER.bits; + } +} + +impl Default for Mods { + fn default() -> Self { + Self::empty() + } +} + +impl Mods { + // Convert single modifier from name + pub fn from_mod_str(s: &str) -> Option { + match s { + "LEFT_CTRL" => Some(Self::CTRL), + "LEFT_SHIFT" => Some(Self::SHIFT), + "LEFT_ALT" => Some(Self::ALT), + "LEFT_SUPER" => Some(Self::SUPER), + "RIGHT_CTRL" => Some(Self::RIGHT_CTRL), + "RIGHT_SHIFT" => Some(Self::RIGHT_SHIFT), + "RIGHT_ALT" => Some(Self::RIGHT_ALT), + "RIGHT_SUPER" => Some(Self::RIGHT_SUPER), + _ => None, + } + } + + // Convert to single modifier + pub(crate) fn as_mod_str(self) -> Option<&'static str> { + match self { + Self::CTRL => Some("LEFT_CTRL"), + Self::SHIFT => Some("LEFT_SHIFT"), + Self::ALT => Some("LEFT_ALT"), + Self::SUPER => Some("LEFT_SUPER"), + Self::RIGHT_CTRL => Some("RIGHT_CTRL"), + Self::RIGHT_SHIFT => Some("RIGHT_SHIFT"), + Self::RIGHT_ALT => Some("RIGHT_ALT"), + Self::RIGHT_SUPER => Some("RIGHT_SUPER"), + _ => None, + } + } + + pub fn mod_names(self) -> impl Iterator { + [Self::CTRL, Self::SHIFT, Self::ALT, Self::SUPER] + .iter() + .filter_map(move |i| (self & (*i | Self::RIGHT)).as_mod_str()) + } + + pub fn toggle_mod(self, other: Self) -> Self { + let other_key = other & !Self::RIGHT; + if !self.contains(other_key) { + self | other + } else { + let key = self & !other_key; + if key == Self::RIGHT { + Self::empty() + } else { + key + } + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone, glib::Boxed)] +#[boxed_type(name = "S76Keycode")] +pub enum Keycode { + Basic(Mods, String), + MT(Mods, String), + LT(u8, String), +} + +impl Keycode { + pub fn parse(s: &str) -> Option { + let mut tokens = tokenize(s); + match tokens.next()? { + "MT" => parse_mt(tokens), + "LT" => parse_lt(tokens), + keycode => parse_basic(tokenize(s)), + } + } + + pub fn none() -> Self { + Self::Basic(Mods::empty(), "NONE".to_string()) + } + + pub fn is_none(&self) -> bool { + if let Keycode::Basic(mode, keycode) = self { + mode.is_empty() && keycode.as_str() == "NONE" + } else { + false + } + } + + pub fn is_roll_over(&self) -> bool { + if let Keycode::Basic(mode, keycode) = self { + mode.is_empty() && keycode.as_str() == "ROLL_OVER" + } else { + false + } + } +} + +impl std::fmt::Display for Keycode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Basic(mods, scancode_name) => { + let mut has_mod = false; + for mod_name in mods.mod_names() { + if has_mod { + write!(f, " | ")?; + } + write!(f, "{}", mod_name)?; + } + if !(scancode_name == "NONE" && has_mod) { + write!(f, "{}", scancode_name)?; + } + } + Self::MT(mods, scancode_name) => { + write!(f, "MT(")?; + let mut has_mod = false; + for mod_name in mods.mod_names() { + if has_mod { + write!(f, " | ")?; + } + write!(f, "{}", mod_name)?; + } + write!(f, ", {})", scancode_name)?; + } + Self::LT(layer, scancode_name) => { + write!(f, "LT({}, {})", layer, scancode_name)?; + } + } + Ok(()) + } +} + +const SEPARATORS: &[char] = &[',', '|', '(', ')']; + +// Tokenize into iterator of &str, splitting on whitespace and putting +// separators in their own tokens. +fn tokenize(mut s: &str) -> impl Iterator { + std::iter::from_fn(move || { + s = s.trim_start_matches(' '); + let idx = if SEPARATORS.contains(&s.chars().next()?) { + 1 + } else { + s.find(|c| c == ' ' || SEPARATORS.contains(&c)) + .unwrap_or(s.len()) + }; + let tok = &s[..idx]; + s = &s[idx..]; + Some(tok) + }) +} + +fn parse_mt<'a>(mut tokens: impl Iterator) -> Option { + if tokens.next() != Some("(") { + return None; + } + + let mut mods = Mods::empty(); + loop { + mods |= Mods::from_mod_str(tokens.next()?)?; + match tokens.next()? { + "|" => {} + "," => { + break; + } + _ => { + return None; + } + } + } + + let keycode = tokens.next()?.to_string(); + + if (tokens.next(), tokens.next()) != (Some(")"), None) { + return None; + } + + Some(Keycode::MT(mods, keycode)) +} + +fn parse_lt<'a>(mut tokens: impl Iterator) -> Option { + if tokens.next() != Some("(") { + return None; + } + + let layer = tokens.next()?.parse().ok()?; + + if tokens.next() != Some(",") { + return None; + } + + let keycode = tokens.next()?.to_string(); + + if (tokens.next(), tokens.next()) != (Some(")"), None) { + return None; + } + + Some(Keycode::LT(layer, keycode)) +} + +// XXX limit to basic if there are mods? +fn parse_basic<'a>(mut tokens: impl Iterator) -> Option { + let mut mods = Mods::empty(); + let mut keycode = None; + + loop { + let token = tokens.next()?; + if let Some(mod_) = Mods::from_mod_str(token) { + mods |= mod_; + } else if keycode.is_none() && token.chars().next()?.is_alphanumeric() { + keycode = Some(token.to_string()); + } else { + return None; + } + match tokens.next() { + Some("|") => {} + Some(_) => { + return None; + } + None => { + break; + } + } + } + + Some(Keycode::Basic( + mods, + keycode.unwrap_or_else(|| "NONE".to_string()), + )) +} diff --git a/backend/src/layout/mod.rs b/backend/src/layout/mod.rs index 49920649..b73f4b6d 100644 --- a/backend/src/layout/mod.rs +++ b/backend/src/layout/mod.rs @@ -1,31 +1,18 @@ -use cascade::cascade; -use regex::Regex; use std::{collections::HashMap, fs, path::Path}; mod meta; -use once_cell::sync::Lazy; mod physical_layout; pub use self::meta::Meta; pub(crate) use physical_layout::{PhysicalLayout, PhysicalLayoutKey}; -use crate::KeyMap; +use crate::{KeyMap, Keycode, Mods}; const QK_MOD_TAP: u16 = 0x6000; const QK_MOD_TAP_MAX: u16 = 0x7FFF; - -pub static MOD_TAP_MODS: Lazy> = Lazy::new(|| { - cascade! { - HashMap::new(); - ..insert("LEFT_CTRL", 0x01); - ..insert("LEFT_SHIFT", 0x02); - ..insert("LEFT_ALT", 0x04); - ..insert("LEFT_SUPER", 0x08); - ..insert("RIGHT_CTRL", 0x11); - ..insert("RIGHT_SHIFT", 0x12); - ..insert("RIGHT_ALT", 0x14); - ..insert("RIGHT_SUPER", 0x18); - } -}); +const QK_LAYER_TAP: u16 = 0x4000; +const QK_LAYER_TAP_MAX: u16 = 0x4FFF; +const QK_MODS: u16 = 0x0100; +const QK_MODS_MAX: u16 = 0x1FFF; pub struct Layout { /// Metadata for keyboard @@ -143,28 +130,58 @@ impl Layout { } /// Get the scancode number corresponding to a name - pub fn scancode_to_name(&self, scancode: u16) -> Option { + pub fn scancode_to_name(&self, scancode: u16) -> Option { + // XXX only on QMK? if scancode >= QK_MOD_TAP && scancode <= QK_MOD_TAP_MAX { - let mod_ = (scancode >> 8) & 0x1f; + let mods = Mods::from_bits((scancode >> 8) & 0x1f)?; + let kc = scancode & 0xff; + let kc_name = self.scancode_names.get(&kc)?; + Some(Keycode::MT(mods, kc_name.clone())) + } else if scancode >= QK_LAYER_TAP && scancode <= QK_LAYER_TAP_MAX { + let layer = ((scancode >> 8) & 0xf) as u8; let kc = scancode & 0xff; - let mod_name = MOD_TAP_MODS.iter().find(|(_, v)| **v == mod_)?.0; let kc_name = self.scancode_names.get(&kc)?; - Some(format!("MT({}, {})", mod_name, kc_name)) + Some(Keycode::LT(layer, kc_name.clone())) + } else if scancode >= QK_MODS && scancode <= QK_MODS_MAX { + let mods = Mods::from_bits((scancode >> 8) & 0x1f)?; + let kc = scancode & 0xff; + let kc_name = self.scancode_names.get(&kc)?; + Some(Keycode::Basic(mods, kc_name.clone())) } else { - self.scancode_names.get(&scancode).cloned() + let kc_name = self.scancode_names.get(&scancode)?; + if let Some(mods) = Mods::from_mod_str(kc_name) { + Some(Keycode::Basic(mods, "NONE".to_string())) + } else { + Some(Keycode::Basic(Mods::empty(), kc_name.clone())) + } } } /// Get the name corresponding to a scancode number - pub fn scancode_from_name(&self, name: &str) -> Option { - // Check if mod-tap - let mt_re = Regex::new("MT\\(([^()]+), ([^()]+)\\)").unwrap(); - if let Some(captures) = mt_re.captures(name) { - let mod_ = *MOD_TAP_MODS.get(&captures.get(1).unwrap().as_str())?; - let kc = *self.keymap.get(captures.get(2).unwrap().as_str())?; - Some(QK_MOD_TAP | ((mod_ & 0x1f) << 8) | (kc & 0xff)) - } else { - self.keymap.get(name).copied() + pub fn scancode_from_name(&self, name: &Keycode) -> Option { + match name { + Keycode::MT(mods, keycode_name) => { + let kc = *self.keymap.get(keycode_name)?; + Some(QK_MOD_TAP | (mods.bits() << 8) | (kc & 0xff)) + } + Keycode::LT(layer, keycode_name) => { + let kc = *self.keymap.get(keycode_name)?; + if *layer < 8 { + Some(QK_LAYER_TAP | (u16::from(*layer) << 8) | (kc & 0xFF)) + } else { + None + } + } + Keycode::Basic(mods, keycode_name) => { + if mods.is_empty() { + self.keymap.get(keycode_name).copied() + } else if let Some(mod_name) = mods.as_mod_str() { + self.keymap.get(mod_name).copied() + } else { + let kc = *self.keymap.get(keycode_name)?; + Some((mods.bits() << 8) | (kc & 0xff)) + } + } } } diff --git a/backend/src/lib.rs b/backend/src/lib.rs index e5524bab..1a66c62a 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -21,6 +21,7 @@ mod color; mod daemon; mod deref_cell; mod key; +mod keycode; mod keymap; mod layer; mod layout; @@ -32,6 +33,6 @@ mod rect; use crate::daemon::*; pub use crate::{ - backend::*, benchmark::*, board::*, color::*, deref_cell::*, key::*, keymap::*, layer::*, - layout::*, localize::*, matrix::*, mode::*, nelson::*, rect::*, + backend::*, benchmark::*, board::*, color::*, deref_cell::*, key::*, keycode::*, keymap::*, + layer::*, layout::*, localize::*, matrix::*, mode::*, nelson::*, rect::*, }; diff --git a/layouts/picker.json b/layouts/picker.json index f790e52c..4c40d630 100644 --- a/layouts/picker.json +++ b/layouts/picker.json @@ -1,6 +1,7 @@ [ { "label": "Alphabet keys", + "section": "basics", "cols": 9, "width": 1, "keys": [ @@ -112,6 +113,7 @@ }, { "label": "Number keys", + "section": "basics", "cols": 4, "width": 1, "keys": [ @@ -159,6 +161,7 @@ }, { "label": "Modifier keys", + "section": "basics", "cols": 4, "width": 1, "keys": [ @@ -198,6 +201,7 @@ }, { "label": "Actions", + "section": "basics", "cols": 4, "width": 1, "keys": [ @@ -265,6 +269,7 @@ }, { "label": "Function keys", + "section": "basics", "cols": 4, "width": 1, "keys": [ @@ -320,6 +325,7 @@ }, { "label": "Numpad", + "section": "basics", "cols": 6, "width": 1, "keys": [ @@ -395,6 +401,7 @@ }, { "label": "Symbols", + "section": "basics", "cols": 6, "width": 1, "keys": [ @@ -454,6 +461,7 @@ }, { "label": "Navigation", + "section": "basics", "cols": 4, "width": 1, "keys": [ @@ -493,6 +501,7 @@ }, { "label": "Media", + "section": "basics", "cols": 3, "width": 1, "keys": [ @@ -524,6 +533,7 @@ }, { "label": "Controls", + "section": "basics", "cols": 4, "width": 2, "keys": [ @@ -571,6 +581,7 @@ }, { "label": "LED controls", + "section": "basics", "cols": 4, "width": 1, "keys": [ @@ -598,6 +609,7 @@ }, { "label": "Layer keys", + "section": "basics", "cols": 4, "width": 2, "keys": [ @@ -634,5 +646,145 @@ "label": "Switch to\nLayer\u00a04" } ] + }, + { + "label": "Additional Function Keys", + "section": "extras", + "cols": 6, + "width": 1, + "keys": [ + { + "keysym": "F13", + "label": "F13" + }, + { + "keysym": "F14", + "label": "F14" + }, + { + "keysym": "F15", + "label": "F15" + }, + { + "keysym": "F16", + "label": "F16" + }, + { + "keysym": "F17", + "label": "F17" + }, + { + "keysym": "F18", + "label": "F18" + }, + { + "keysym": "F19", + "label": "F19" + }, + { + "keysym": "F20", + "label": "F20" + }, + { + "keysym": "F21", + "label": "F21" + }, + { + "keysym": "F22", + "label": "F22" + }, + { + "keysym": "F23", + "label": "F23" + }, + { + "keysym": "F24", + "label": "F24" + } + ] + }, + { + "label": "Mouse Actions", + "section": "extras", + "cols": 5, + "width": 2, + "keys": [ + { + "keysym": "MS_UP", + "label": "Mouse cursor up" + }, + { + "keysym": "MS_DOWN", + "label": "Mouse cursor down" + }, + { + "keysym": "MS_LEFT", + "label": "Mouse cursor left" + }, + { + "keysym": "MS_RIGHT", + "label": "Mouse cursor right" + }, + { + "keysym": "MS_BTN1", + "label": "Press button 1" + }, + { + "keysym": "MS_BTN2", + "label": "Press button 2" + }, + { + "keysym": "MS_BTN3", + "label": "Press button 3" + }, + { + "keysym": "MS_BTN4", + "label": "Press button 4" + }, + { + "keysym": "MS_BTN5", + "label": "Press button 5" + }, + { + "keysym": "MS_BTN6", + "label": "Press button 6" + }, + { + "keysym": "MS_BTN7", + "label": "Press button 7" + }, + { + "keysym": "MS_BTN8", + "label": "Press button 8" + }, + { + "keysym": "MS_WH_UP", + "label": "Move wheel up" + }, + { + "keysym": "MS_WH_DOWN", + "label": "Move wheel down" + }, + { + "keysym": "MS_WH_LEFT", + "label": "Move wheel left" + }, + { + "keysym": "MS_WH_RIGHT", + "label": "Move wheel right" + }, + { + "keysym": "MS_ACCEL0", + "label": "Set speed to 0" + }, + { + "keysym": "MS_ACCEL1", + "label": "Set speed to 1" + }, + { + "keysym": "MS_ACCEL2", + "label": "Set speed to 2" + } + ] } ] diff --git a/src/configurator_app.rs b/src/configurator_app.rs index bfabed31..2953ca0b 100644 --- a/src/configurator_app.rs +++ b/src/configurator_app.rs @@ -203,6 +203,16 @@ pub fn run() -> i32 { #[cfg(target_os = "windows")] windows_init(); + let css_provider = cascade! { + gtk::CssProvider::new(); + ..load_from_data(include_bytes!("style.css")).unwrap(); + }; + gtk::StyleContext::add_provider_for_screen( + &gdk::Screen::default().unwrap(), + &css_provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); + gio::resources_register_include!("compiled.gresource").unwrap(); gtk::Window::set_default_icon_name("com.system76.keyboardconfigurator"); diff --git a/src/keyboard.rs b/src/keyboard.rs index 4166f22b..83a39e44 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -16,7 +16,7 @@ use std::{ }; use crate::{show_error_dialog, Backlight, KeyboardLayer, MainWindow, Page, Picker, Testing}; -use backend::{Board, DerefCell, KeyMap, Layout, Mode}; +use backend::{Board, DerefCell, KeyMap, Keycode, Layout, Mode}; use widgets::SelectedKeys; #[derive(Default)] @@ -303,11 +303,12 @@ impl Keyboard { &self.inner().layer_stack } - pub fn has_scancode(&self, scancode_name: &str) -> bool { + // XXX + pub fn has_scancode(&self, scancode_name: &Keycode) -> bool { self.layout().scancode_from_name(scancode_name).is_some() } - pub async fn keymap_set(&self, key_index: usize, layer: usize, scancode_name: &str) { + pub async fn keymap_set(&self, key_index: usize, layer: usize, scancode_name: &Keycode) { if let Err(err) = self.board().keys()[key_index] .set_scancode(layer, scancode_name) .await @@ -354,12 +355,17 @@ impl Keyboard { for (k, v) in &keymap.map { for (layer, scancode_name) in v.iter().enumerate() { + let keycode = match Keycode::parse(scancode_name) { + Some(keycode) => keycode, + None => { + error!("Unrecognized keycode: '{}'", scancode_name); + continue; + } // XXX + }; + let n = key_indices[&k]; futures.push(Box::pin(async move { - if let Err(err) = self.board().keys()[n] - .set_scancode(layer, scancode_name) - .await - { + if let Err(err) = self.board().keys()[n].set_scancode(layer, &keycode).await { error!("{}: {:?}", fl!("error-set-keymap"), err); } })); @@ -487,10 +493,10 @@ impl Keyboard { for i in self.layout().f_keys() { let k = &self.board().keys()[key_indices[i]]; - let layer0_keycode = k.get_scancode(0).unwrap().1; - let layer1_keycode = k.get_scancode(1).unwrap().1; + let layer0_keycode = k.get_scancode(0).unwrap().1.unwrap_or_else(Keycode::none); + let layer1_keycode = k.get_scancode(1).unwrap().1.unwrap_or_else(Keycode::none); - if layer1_keycode == "ROLL_OVER" { + if layer1_keycode.is_roll_over() { continue; } @@ -591,7 +597,7 @@ impl Keyboard { let k = &keys[*i]; debug!("{:#?}", k); if let Some(layer) = self.layer() { - if let Some((_scancode, scancode_name)) = k.get_scancode(layer) { + if let Some((_scancode, Some(scancode_name))) = k.get_scancode(layer) { selected_scancodes.push(scancode_name); } } diff --git a/src/keyboard_layer.rs b/src/keyboard_layer.rs index 49f25d1e..020c7ae5 100644 --- a/src/keyboard_layer.rs +++ b/src/keyboard_layer.rs @@ -138,7 +138,7 @@ impl WidgetImpl for KeyboardLayerInner { let mut bg_alpha = 1.; if let Some(layer) = self.page.get().layer() { let scancode_name = k.get_scancode(layer).unwrap().1; - if scancode_name == "NONE" || scancode_name == "ROLL_OVER" { + if scancode_name.map_or(false, |x| x.is_none() || x.is_roll_over()) { text_alpha = 0.5; bg_alpha = 0.75; } diff --git a/src/page.rs b/src/page.rs index 8c889217..1b423238 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,6 +1,6 @@ use crate::fl; -use crate::picker::SCANCODE_LABELS; -use backend::Key; +use crate::picker::{LAYERS, SCANCODE_LABELS}; +use backend::{Key, Keycode}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Page { @@ -62,11 +62,13 @@ impl Page { pub fn get_label(&self, key: &Key) -> String { match self { Page::Layer1 | Page::Layer2 | Page::Layer3 | Page::Layer4 => { - let scancode_name = key.get_scancode(self.layer().unwrap()).unwrap().1; - SCANCODE_LABELS - .get(&scancode_name) - .unwrap_or(&scancode_name) - .into() + let (scancode, scancode_name) = key.get_scancode(self.layer().unwrap()).unwrap(); + match scancode_name { + Some(keycode) => { + keycode_label(&keycode).unwrap_or_else(|| format!("{:?}", keycode)) + } + None => format!("{}", scancode), + } } Page::Keycaps => key.physical_name.clone(), Page::Logical => key.logical_name.clone(), @@ -81,3 +83,49 @@ impl Default for Page { Self::Layer1 } } + +// TODO: represent mod-tap/layer-tap by rendering button with a seperator? +fn keycode_label(keycode: &Keycode) -> Option { + match keycode { + Keycode::Basic(mods, keycode) => { + if mods.is_empty() { + SCANCODE_LABELS.get(keycode).cloned() + } else { + let mut label = String::new(); + for name in mods.mod_names() { + let mod_label = SCANCODE_LABELS.get(name)?; + if !label.is_empty() { + label.push_str(" + "); + } + label.push_str(mod_label); + } + if keycode != "NONE" { + let keycode_label = SCANCODE_LABELS.get(keycode)?; + label.push_str(" + "); + label.push_str(keycode_label); + } + Some(label) + } + } + Keycode::MT(mods, keycode) => { + let mut label = String::new(); + for name in mods.mod_names() { + let mod_label = SCANCODE_LABELS.get(name)?; + if !label.is_empty() { + label.push_str(" + "); + } + label.push_str(mod_label); + } + let keycode_label = SCANCODE_LABELS.get(keycode)?; + label.push('\n'); + label.push_str(keycode_label); + Some(label) + } + Keycode::LT(layer, keycode) => { + let layer_id = *LAYERS.get(usize::from(*layer))?; + let layer_label = SCANCODE_LABELS.get(layer_id)?; + let keycode_label = SCANCODE_LABELS.get(keycode)?; + Some(format!("{}\n{}", layer_label, keycode_label)) + } + } +} diff --git a/src/picker/mod.rs b/src/picker/mod.rs index c159fcd7..0c2d63e1 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -1,24 +1,32 @@ use cascade::cascade; use futures::{prelude::*, stream::FuturesUnordered}; use gtk::{ + gdk, glib::{self, clone}, prelude::*, subclass::prelude::*, }; use once_cell::sync::Lazy; -use std::{cell::RefCell, collections::HashMap}; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, +}; use crate::Keyboard; -use backend::DerefCell; +use backend::{DerefCell, Keycode, Mods}; mod picker_group; mod picker_group_box; mod picker_json; mod picker_key; +mod tap_hold; use picker_group_box::PickerGroupBox; use picker_json::picker_json; use picker_key::PickerKey; +use tap_hold::TapHold; + +pub use tap_hold::LAYERS; pub static SCANCODE_LABELS: Lazy> = Lazy::new(|| { let mut labels = HashMap::new(); @@ -32,8 +40,12 @@ pub static SCANCODE_LABELS: Lazy> = Lazy::new(|| { #[derive(Default)] pub struct PickerInner { - group_box: DerefCell, + group_boxes: DerefCell>, keyboard: RefCell>, + event_controller_key: RefCell>, + selected: RefCell>, + shift: Cell, + tap_hold: DerefCell, } #[glib::object_subclass] @@ -47,26 +59,101 @@ impl ObjectImpl for PickerInner { fn constructed(&self, picker: &Picker) { self.parent_constructed(picker); - let group_box = cascade! { - PickerGroupBox::new(); - ..connect_key_pressed(clone!(@weak picker => move |name| { - picker.key_pressed(name) + let basics_group_box = cascade! { + PickerGroupBox::new("basics"); + ..connect_key_pressed(clone!(@weak picker => move |name, shift| { + picker.key_pressed(name, shift) + })); + }; + + let extras_group_box = cascade! { + PickerGroupBox::new("extras"); + ..connect_key_pressed(clone!(@weak picker => move |name, shift| { + picker.key_pressed(name, shift) })); }; + let tap_hold = cascade! { + tap_hold::TapHold::new(); + ..connect_selected(clone!(@weak picker => move |keycode| { + picker.set_keycode(keycode); + })); + }; + + // XXX translate + let stack = cascade! { + gtk::Stack::new(); + ..add_titled(&basics_group_box, "basics", "Basics"); + ..add_titled(&extras_group_box, "extras", "Extras"); + ..add_titled(&tap_hold, "tap-hold", "Tap-Hold"); + }; + + let stack_switcher = cascade! { + gtk::StackSwitcher::new(); + ..set_stack(Some(&stack)); + }; + cascade! { picker; - ..add(&group_box); + ..set_orientation(gtk::Orientation::Vertical); + ..add(&stack_switcher); + ..add(&stack); ..show_all(); }; - self.group_box.set(group_box); + self.group_boxes + .set(vec![basics_group_box, extras_group_box]); + self.tap_hold.set(tap_hold); } } impl BoxImpl for PickerInner {} -impl WidgetImpl for PickerInner {} +impl WidgetImpl for PickerInner { + fn realize(&self, widget: &Self::Type) { + self.parent_realize(widget); + + let window = widget + .toplevel() + .and_then(|x| x.downcast::().ok()); + *self.event_controller_key.borrow_mut() = window.map(|window| { + cascade! { + gtk::EventControllerKey::new(&window); + ..connect_key_pressed(clone!(@weak widget => @default-return true, move |_, keyval, _, mods| { + let key = gdk::keys::Key::from(keyval); + if key == gdk::keys::constants::Shift_L || key == gdk::keys::constants::Shift_R { + println!("Shift"); // XXX what if only one is held? + } + true + })); + ..connect_key_released(clone!(@weak widget => move |_, keyval, _, mods| { + let key = gdk::keys::Key::from(keyval); + if key == gdk::keys::constants::Shift_L || key == gdk::keys::constants::Shift_R { + println!("Unshift"); // XXX what if only one is held? + } + })); + ..connect_focus_out(clone!(@weak widget => move |_| { + println!("Unfocus"); + })); + ..connect_modifiers(clone!(@weak widget => @default-return true, move |_, mods| { + println!("Mods: {:?}", mods); + let shift = mods.contains(gdk::ModifierType::SHIFT_MASK); + //println!("Shift: {}", shift); + if shift != widget.inner().shift.get() { + widget.inner().shift.set(shift); + widget.invalidate_sensitivity(); + } + true + })); + } + }); + } + + fn unrealize(&self, widget: &Self::Type) { + self.parent_unrealize(widget); + *self.event_controller_key.borrow_mut() = None; + } +} impl ContainerImpl for PickerInner {} @@ -91,39 +178,123 @@ impl Picker { if let Some(kb) = &keyboard { // Check that scancode is available for the keyboard - self.inner() - .group_box - .set_key_visibility(|name| kb.has_scancode(name)); + for group_box in self.inner().group_boxes.iter() { + group_box.set_key_visibility(|name| { + kb.has_scancode(&Keycode::Basic(Mods::empty(), name.to_string())) + }); + } kb.set_picker(Some(&self)); } *self.inner().keyboard.borrow_mut() = keyboard; } - pub(crate) fn set_selected(&self, scancode_names: Vec) { - self.inner().group_box.set_selected(scancode_names); + pub(crate) fn set_selected(&self, scancode_names: Vec) { + for group_box in self.inner().group_boxes.iter() { + group_box.set_selected(scancode_names.clone()); + } + self.inner().tap_hold.set_selected(scancode_names.clone()); + *self.inner().selected.borrow_mut() = scancode_names; } - fn key_pressed(&self, name: String) { + fn key_pressed(&self, name: String, shift: bool) { + let mod_ = Mods::from_mod_str(&name); + if shift { + let selected = self.inner().selected.borrow(); + if selected.len() == 1 { + if let Keycode::Basic(mods, scancode_name) = &selected[0] { + if let Some(mod_) = mod_ { + self.set_keycode(Keycode::Basic( + mods.toggle_mod(mod_), + scancode_name.to_string(), + )); + return; + } else if scancode_name == "NONE" { + self.set_keycode(Keycode::Basic(*mods, name)); + return; + } + } + } + } + let keycode = if let Some(mod_) = mod_ { + Keycode::Basic(mod_, "NONE".to_string()) + } else { + Keycode::Basic(Mods::empty(), name) + }; + self.set_keycode(keycode); + } + + fn set_keycode(&self, keycode: Keycode) { let kb = match self.inner().keyboard.borrow().clone() { Some(kb) => kb, None => { return; } }; - let layer = kb.layer(); + let layer = kb.layer(); if let Some(layer) = layer { let futures = FuturesUnordered::new(); for i in kb.selected().iter() { let i = *i; - futures.push(clone!(@strong kb, @strong name => async move { - kb.keymap_set(i, layer, &name).await; + futures.push(clone!(@strong kb, @strong keycode => async move { + kb.keymap_set(i, layer, &keycode).await; })); } glib::MainContext::default().spawn_local(async { futures.collect::<()>().await }); } } + + fn invalidate_sensitivity(&self) { + return; + + let shift = self.inner().shift.get(); + + let mut allow_mods = true; + let mut allow_basic = true; + let mut allow_non_basic = true; + + if shift { + let selected = self.inner().selected.borrow(); + if selected.len() == 1 { + match &selected[0] { + Keycode::Basic(mods, keycode) => { + // Allow mods only if `keycode` is really basic? + allow_basic = keycode == "NONE"; + allow_non_basic = false; + } + Keycode::MT(..) | Keycode::LT(..) => { + allow_mods = false; + allow_basic = false; + allow_non_basic = false; + } + } + } + } + + for group_box in self.inner().group_boxes.iter() { + // TODO: What to allow? + group_box.set_key_sensitivity(|name| { + if [ + "LEFT_SHIFT", + "RIGHT_SHIFT", + "LEFT_ALT", + "RIGHT_ALT", + "LEFT_CTRL", + "RIGHT_CTRL", + "LEFT_SUPER", + "RIGHT_SUPER", + ] + .contains(&name) + { + allow_mods + } else { + allow_basic + } + // XXX non-basic? + }); + } + } } #[cfg(test)] diff --git a/src/picker/picker_group.rs b/src/picker/picker_group.rs index 31dadf27..17e7544a 100644 --- a/src/picker/picker_group.rs +++ b/src/picker/picker_group.rs @@ -1,12 +1,11 @@ use cascade::cascade; use gtk::{pango, prelude::*}; -use std::rc::Rc; use super::PickerKey; pub(super) struct PickerGroup { /// Name of keys in this group - keys: Vec>, + keys: Vec, pub vbox: gtk::Box, flow_box: gtk::FlowBox, } @@ -21,6 +20,7 @@ impl PickerGroup { } )); ..set_halign(gtk::Align::Start); ..set_margin_bottom(8); + ..show(); }; let flow_box = cascade! { @@ -29,11 +29,13 @@ impl PickerGroup { ..set_row_spacing(4); ..set_max_children_per_line(cols); ..set_min_children_per_line(cols); - ..set_filter_func(Some(Box::new(|child: >k::FlowBoxChild| child.child().unwrap().is_visible()))); + ..set_filter_func(Some(Box::new(|child: >k::FlowBoxChild| child.child().unwrap().get_visible()))); + ..show(); }; let vbox = cascade! { gtk::Box::new(gtk::Orientation::Vertical, 4); + ..set_no_show_all(true); ..add(&label); ..add(&flow_box); }; @@ -45,12 +47,12 @@ impl PickerGroup { } } - pub fn add_key(&mut self, key: Rc) { - self.flow_box.add(&key.gtk); + pub fn add_key(&mut self, key: PickerKey) { + self.flow_box.add(&key); self.keys.push(key); } - pub fn iter_keys(&self) -> impl Iterator { + pub fn keys(&self) -> impl Iterator { self.keys.iter().map(|k| k.as_ref()) } diff --git a/src/picker/picker_group_box.rs b/src/picker/picker_group_box.rs index c898fb4b..2e0edec4 100644 --- a/src/picker/picker_group_box.rs +++ b/src/picker/picker_group_box.rs @@ -6,32 +6,21 @@ use gtk::{ subclass::prelude::*, }; use once_cell::sync::Lazy; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap}; -use backend::DerefCell; +use backend::{DerefCell, Keycode}; use super::{picker_group::PickerGroup, picker_json::picker_json, picker_key::PickerKey}; const DEFAULT_COLS: usize = 3; const HSPACING: i32 = 64; const VSPACING: i32 = 32; -const PICKER_CSS: &str = r#" -button { - margin: 0; - padding: 0; -} - -.selected { - border-color: #fbb86c; - border-width: 4px; -} -"#; #[derive(Default)] pub struct PickerGroupBoxInner { groups: DerefCell>, - keys: DerefCell>>, - selected: RefCell>, + keys: DerefCell>, + selected: RefCell>, } #[glib::object_subclass] @@ -42,55 +31,11 @@ impl ObjectSubclass for PickerGroupBoxInner { } impl ObjectImpl for PickerGroupBoxInner { - fn constructed(&self, widget: &PickerGroupBox) { - self.parent_constructed(widget); - - let style_provider = cascade! { - gtk::CssProvider::new(); - ..load_from_data(&PICKER_CSS.as_bytes()).expect("Failed to parse css"); - }; - - let mut groups = Vec::new(); - let mut keys = HashMap::new(); - - for json_group in picker_json() { - let mut group = PickerGroup::new(json_group.label, json_group.cols); - - for json_key in json_group.keys { - let key = PickerKey::new( - json_key.keysym.clone(), - json_key.label, - json_group.width, - &style_provider, - ); - - group.add_key(key.clone()); - keys.insert(json_key.keysym, key); - } - - groups.push(group); - } - - for group in &groups { - group.vbox.show(); - group.vbox.set_parent(widget); - } - - self.keys.set(keys); - self.groups.set(groups); - - cascade! { - widget; - ..connect_signals(); - ..show_all(); - }; - } - fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| { vec![Signal::builder( "key-pressed", - &[String::static_type().into()], + &[String::static_type().into(), bool::static_type().into()], glib::Type::UNIT.into(), ) .build()] @@ -110,13 +55,13 @@ impl WidgetImpl for PickerGroupBoxInner { .iter() .map(|x| x.vbox.preferred_width().1) .max() - .unwrap(); + .unwrap_or(0); let natural_width = self .groups .chunks(3) .map(|row| row.iter().map(|x| x.vbox.preferred_width().1).sum::()) .max() - .unwrap() + .unwrap_or(0) + 2 * HSPACING; (minimum_width, natural_width) } @@ -129,7 +74,7 @@ impl WidgetImpl for PickerGroupBoxInner { row.iter() .map(|x| x.vbox.preferred_height().1) .max() - .unwrap() + .unwrap_or(0) }) .sum::() + (rows.len() as i32 - 1) * VSPACING; @@ -149,7 +94,7 @@ impl WidgetImpl for PickerGroupBoxInner { + (row.len() as i32 - 1) * HSPACING }) .max() - .unwrap(); + .unwrap_or(0); let mut y = 0; for row in rows { @@ -215,8 +160,39 @@ glib::wrapper! { } impl PickerGroupBox { - pub fn new() -> Self { - glib::Object::new(&[]).unwrap() + pub fn new(section: &str) -> Self { + let widget: Self = glib::Object::new(&[]).unwrap(); + + let mut groups = Vec::new(); + let mut keys = HashMap::new(); + + for json_group in picker_json() { + if json_group.section != section { + continue; + } + + let mut group = PickerGroup::new(json_group.label, json_group.cols); + + for json_key in json_group.keys { + let key = PickerKey::new(&json_key.keysym, &json_key.label, json_group.width); + + group.add_key(key.clone()); + keys.insert(json_key.keysym, key); + } + + groups.push(group); + } + + for group in &groups { + group.vbox.show(); + group.vbox.set_parent(&widget); + } + + widget.inner().keys.set(keys); + widget.inner().groups.set(groups); + widget.connect_signals(); + + widget } fn inner(&self) -> &PickerGroupBoxInner { @@ -226,53 +202,71 @@ impl PickerGroupBox { fn connect_signals(&self) { let picker = self; for group in self.inner().groups.iter() { - for key in group.iter_keys() { - let button = &key.gtk; - let name = key.name.to_string(); - button.connect_clicked(clone!(@weak picker => @default-panic, move |_| { - picker.emit_by_name::<()>("key-pressed", &[&name]); - })); + for key in group.keys() { + let button = &key; + let name = key.name().to_string(); + button.connect_clicked_with_shift( + clone!(@weak picker => @default-panic, move |_, shift| { + picker.emit_by_name::<()>("key-pressed", &[&name, &shift]); + }), + ); } } } - pub fn connect_key_pressed(&self, cb: F) -> SignalHandlerId { + pub fn connect_key_pressed(&self, cb: F) -> SignalHandlerId { self.connect_local("key-pressed", false, move |values| { - cb(values[1].get::().unwrap()); + cb( + values[1].get::().unwrap(), + values[2].get::().unwrap(), + ); None }) } - fn get_button(&self, scancode_name: &str) -> Option<>k::Button> { - self.inner().keys.get(scancode_name).map(|k| &k.gtk) - } - + // XXX need to enable/disable features; show/hide just plain keycodes pub(crate) fn set_key_visibility bool>(&self, f: F) { - for key in self.inner().keys.values() { - key.gtk.set_visible(f(&key.name)); - } - for group in self.inner().groups.iter() { + let group_visible = group.keys().fold(false, |group_visible, key| { + key.set_visible(f(&key.name())); + group_visible || key.get_visible() + }); + + group.vbox.set_visible(group_visible); group.invalidate_filter(); } } - pub(crate) fn set_selected(&self, scancode_names: Vec) { - let mut selected = self.inner().selected.borrow_mut(); - - for i in selected.iter() { - if let Some(button) = self.get_button(i) { - button.style_context().remove_class("selected"); - } + pub(crate) fn set_key_sensitivity bool>(&self, f: F) { + for key in self.inner().keys.values() { + key.set_sensitive(f(&key.name())); } + } - *selected = scancode_names; + pub(crate) fn set_selected(&self, scancode_names: Vec) { + for button in self.inner().keys.values() { + button.set_selected(false); + } - for i in selected.iter() { - if let Some(button) = self.get_button(i) { - button.style_context().add_class("selected"); + for i in scancode_names.iter() { + match i { + Keycode::Basic(mods, scancode_name) => { + if let Some(button) = self.inner().keys.get(scancode_name) { + if !(scancode_name == "NONE" && !mods.is_empty()) { + button.set_selected(true); + } + } + for scancode_name in mods.mod_names() { + if let Some(button) = self.inner().keys.get(scancode_name) { + button.set_selected(true); + } + } + } + Keycode::MT(..) | Keycode::LT(..) => {} } } + + *self.inner().selected.borrow_mut() = scancode_names; } fn rows_for_width(&self, container_width: i32) -> Vec<&[PickerGroup]> { diff --git a/src/picker/picker_json.rs b/src/picker/picker_json.rs index 106a920a..2dadce19 100644 --- a/src/picker/picker_json.rs +++ b/src/picker/picker_json.rs @@ -9,6 +9,7 @@ pub struct PickerJsonKey { #[derive(Deserialize)] pub struct PickerJsonGroup { pub label: String, + pub section: String, pub cols: u32, pub width: i32, pub keys: Vec, diff --git a/src/picker/picker_key.rs b/src/picker/picker_key.rs index a4c31018..f9f44aec 100644 --- a/src/picker/picker_key.rs +++ b/src/picker/picker_key.rs @@ -1,23 +1,33 @@ use cascade::cascade; -use gtk::prelude::*; -use std::rc::Rc; +use gtk::{ + gdk, + glib::{ + self, + translate::{from_glib, ToGlibPtr}, + }, + prelude::*, + subclass::prelude::*, +}; -pub(super) struct PickerKey { - /// Symbolic name of the key - pub(super) name: String, - // GTK button - pub(super) gtk: gtk::Button, +use backend::DerefCell; + +#[derive(Default)] +pub struct PickerKeyInner { + label: DerefCell, + name: DerefCell, } -impl PickerKey { - pub(super) fn new>( - name: String, - text: String, - width: i32, - style_provider: &P, - ) -> Rc { +#[glib::object_subclass] +impl ObjectSubclass for PickerKeyInner { + const NAME: &'static str = "S76PickerKey"; + type ParentType = gtk::Button; + type Type = PickerKey; +} + +impl ObjectImpl for PickerKeyInner { + fn constructed(&self, widget: &Self::Type) { let label = cascade! { - gtk::Label::new(Some(&text)); + gtk::Label::new(None); ..set_line_wrap(true); ..set_max_width_chars(1); ..set_margin_start(5); @@ -25,13 +35,74 @@ impl PickerKey { ..set_justify(gtk::Justification::Center); }; - let button = cascade! { - gtk::Button::new(); - ..set_size_request(48 * width, 48); - ..style_context().add_provider(style_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + cascade! { + widget; + ..style_context().add_class("picker-key"); ..add(&label); + ..show_all(); }; - Rc::new(Self { name, gtk: button }) + self.label.set(label); + } +} +impl WidgetImpl for PickerKeyInner {} +impl ContainerImpl for PickerKeyInner {} +impl BinImpl for PickerKeyInner {} +impl ButtonImpl for PickerKeyInner {} + +glib::wrapper! { + pub struct PickerKey(ObjectSubclass) + @extends gtk::Button, gtk::Bin, gtk::Container, gtk::Widget, @implements gtk::Orientable; +} + +impl PickerKey { + pub fn new(name: &str, text: &str, width: i32) -> Self { + let widget: Self = glib::Object::new(&[]).unwrap(); + widget.inner().name.set(name.to_string()); + widget.inner().label.set_label(&text); + widget.set_size_request(48 * width, 48); + widget + } + + fn inner(&self) -> &PickerKeyInner { + PickerKeyInner::from_instance(self) + } + + /// Symbolic name of the key + pub fn name(&self) -> &str { + &*self.inner().name + } + + pub fn set_selected(&self, selected: bool) { + if selected { + self.style_context().add_class("selected"); + } else { + self.style_context().remove_class("selected"); + } + } + + pub fn connect_clicked_with_shift(&self, f: F) { + self.connect_clicked(move |widget| { + let shift = gtk::current_event() + .and_then(|x| event_state(&x)) + .map_or(false, |x| x.contains(gdk::ModifierType::SHIFT_MASK)); + f(widget, shift) + }); + } +} + +// Work around binding bug +// https://github.com/gtk-rs/gtk3-rs/pull/769 +pub fn event_state(evt: &gdk::Event) -> Option { + unsafe { + let mut state = std::mem::MaybeUninit::uninit(); + if from_glib(gdk::ffi::gdk_event_get_state( + evt.to_glib_none().0, + state.as_mut_ptr(), + )) { + Some(from_glib(state.assume_init() as u32)) + } else { + None + } } } diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs new file mode 100644 index 00000000..35b5e75b --- /dev/null +++ b/src/picker/tap_hold.rs @@ -0,0 +1,231 @@ +use cascade::cascade; +use gtk::{ + glib::{self, clone, subclass::Signal}, + pango, + prelude::*, + subclass::prelude::*, +}; +use once_cell::sync::Lazy; +use std::cell::{Cell, RefCell}; + +use super::{picker_group_box::PickerGroupBox, PickerKey, SCANCODE_LABELS}; +use backend::{DerefCell, Keycode, Mods}; + +#[derive(Clone, Copy)] +enum Hold { + Mods(Mods), + Layer(u8), +} + +impl Default for Hold { + fn default() -> Self { + Self::Mods(Mods::default()) + } +} + +static MODIFIERS: &[&str] = &[ + "LEFT_SHIFT", + "LEFT_CTRL", + "LEFT_SUPER", + "LEFT_ALT", + "RIGHT_SHIFT", + "RIGHT_CTRL", + "RIGHT_SUPER", + "RIGHT_ALT", +]; +pub static LAYERS: &[&str] = &["LAYER_ACCESS_1", "FN", "LAYER_ACCESS_3", "LAYER_ACCESS_4"]; + +#[derive(Default)] +pub struct TapHoldInner { + hold: Cell, + keycode: RefCell>, + mod_buttons: DerefCell>, + layer_buttons: DerefCell>, + picker_group_box: DerefCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for TapHoldInner { + const NAME: &'static str = "S76KeyboardTapHold"; + type ParentType = gtk::Box; + type Type = TapHold; +} + +impl ObjectImpl for TapHoldInner { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder( + "selected", + &[Keycode::static_type().into()], + glib::Type::UNIT.into(), + ) + .build()] + }); + SIGNALS.as_ref() + } + + fn constructed(&self, widget: &Self::Type) { + self.parent_constructed(widget); + + let layout = backend::Layout::from_board("system76/launch_1").unwrap(); + + let picker_group_box = cascade! { + PickerGroupBox::new("basics"); + ..connect_key_pressed(clone!(@weak widget => move |name, _shift| { + *widget.inner().keycode.borrow_mut() = Some(name); + widget.update(); + })); + // Correct? + ..set_key_visibility(|name| layout.scancode_from_name(&Keycode::Basic(Mods::empty(), name.to_string())).map_or(false, |code| code <= 0xff)); + }; + + let modifier_button_box = cascade! { + gtk::Box::new(gtk::Orientation::Horizontal, 0); + }; + let mut mod_buttons = Vec::new(); + for i in MODIFIERS { + let label = SCANCODE_LABELS.get(*i).unwrap(); + let mod_ = Mods::from_mod_str(*i).unwrap(); + let button = cascade! { + PickerKey::new(i, label, 2); + ..connect_clicked_with_shift(clone!(@weak widget => move |_, shift| { + let mut new_mods = mod_; + if shift { + if let Hold::Mods(mods) = widget.inner().hold.get() { + new_mods = mods.toggle_mod(mod_); + } + } + widget.inner().hold.set(Hold::Mods(new_mods)); + widget.update(); + })); + }; + modifier_button_box.add(&button); + mod_buttons.push(button); + } + self.mod_buttons.set(mod_buttons); + + let layer_button_box = cascade! { + gtk::Box::new(gtk::Orientation::Horizontal, 0); + }; + let mut layer_buttons = Vec::new(); + for (n, i) in LAYERS.iter().enumerate() { + let label = SCANCODE_LABELS.get(*i).unwrap(); + let button = cascade! { + PickerKey::new(i, label, 2); + ..connect_clicked(clone!(@weak widget => move |_| { + widget.inner().hold.set(Hold::Layer(n as u8)); + widget.update(); + + })); + }; + layer_button_box.add(&button); + layer_buttons.push(button); + } + self.layer_buttons.set(layer_buttons); + + // TODO: select monifier/layer; multiple select; when both are selected, set keycode + + cascade! { + widget; + ..set_orientation(gtk::Orientation::Vertical); + ..add(&cascade! { + gtk::Label::new(Some("1. Select action(s) to use when the key is held.")); + ..set_attributes(Some(&cascade! { + pango::AttrList::new(); + ..insert(pango::AttrInt::new_weight(pango::Weight::Bold)); + })); + ..set_halign(gtk::Align::Start); + }); + ..add(&modifier_button_box); + ..add(&layer_button_box); + ..add(&cascade! { + gtk::Label::new(Some("2. Select an action to use when the key is tapped.")); + ..set_attributes(Some(&cascade! { + pango::AttrList::new(); + ..insert(pango::AttrInt::new_weight(pango::Weight::Bold)); + })); + ..set_halign(gtk::Align::Start); + }); + ..add(&picker_group_box); + }; + + self.picker_group_box.set(picker_group_box); + } +} + +impl BoxImpl for TapHoldInner {} +impl WidgetImpl for TapHoldInner {} +impl ContainerImpl for TapHoldInner {} + +glib::wrapper! { + pub struct TapHold(ObjectSubclass) + @extends gtk::Box, gtk::Container, gtk::Widget, @implements gtk::Orientable; +} + +impl TapHold { + pub fn new() -> Self { + glib::Object::new(&[]).unwrap() + } + + fn inner(&self) -> &TapHoldInner { + TapHoldInner::from_instance(self) + } + + fn update(&self) { + let keycode = self.inner().keycode.borrow(); + let keycode = keycode.as_deref().unwrap_or("NONE"); + match self.inner().hold.get() { + Hold::Mods(mods) => { + if !mods.is_empty() { + self.emit_by_name::<()>("selected", &[&Keycode::MT(mods, keycode.to_string())]); + } + } + Hold::Layer(layer) => { + self.emit_by_name::<()>("selected", &[&Keycode::LT(layer, keycode.to_string())]); + } + } + } + + // XXX naming vs set_selected + pub fn connect_selected(&self, cb: F) -> glib::SignalHandlerId { + self.connect_local("selected", false, move |values| { + cb(values[1].get::().unwrap()); + None + }) + } + + pub(crate) fn set_selected(&self, scancode_names: Vec) { + // XXX how to handle > 1? + let (mods, layer, keycode) = if scancode_names.len() == 1 { + match scancode_names.into_iter().next().unwrap() { + Keycode::MT(mods, keycode) => (mods, None, Some(keycode)), + Keycode::LT(layer, keycode) => (Mods::empty(), Some(layer), Some(keycode)), + Keycode::Basic(..) => Default::default(), + } + } else { + Default::default() + }; + + // TODO how to deal with internal state? + + for i in self.inner().mod_buttons.iter() { + // XXX left vs right + let mod_ = Mods::from_mod_str(i.name()).unwrap(); + i.set_selected( + mods.contains(mod_) && (mods.contains(Mods::RIGHT) == mod_.contains(Mods::RIGHT)), + ); + } + + for (n, i) in self.inner().layer_buttons.iter().enumerate() { + i.set_selected(Some(n as u8) == layer); + } + + if let Some(keycode) = keycode { + self.inner() + .picker_group_box + .set_selected(vec![Keycode::Basic(Mods::empty(), keycode)]); + } else { + self.inner().picker_group_box.set_selected(Vec::new()); + } + } +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 00000000..927a475c --- /dev/null +++ b/src/style.css @@ -0,0 +1,9 @@ +.picker-key { + margin: 0; + padding: 0; +} + +.picker-key.selected { + border-color: #fbb86c; + border-width: 4px; +} From 095d032b5b5e09566cfa91dfdac6747672a1c011 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 1 Sep 2022 13:45:31 -0700 Subject: [PATCH 02/13] Fixes for extras, tap-hold, shift-select Update sensitivity based on state of `shift` key. Distinguish basic from non-basic keycodes. Non-basic not allowed with shift select. Show "Left" or "Right" only once when multiple modifiers are combined. Update how internal state is handled in tap-hold, render with line separator, and handle shift-select. Show QMK features only on QMK keyboard, using a `is_qmk` `meta.json` setting. Only allow shift-select where this is enabled. Also only apply QMK features in backend crate when QMK layout is used. --- Cargo.toml | 3 +- backend/src/keycode.rs | 2 + backend/src/layout/meta.rs | 4 +- backend/src/layout/mod.rs | 71 +++++++---- layouts/picker.json | 24 ++-- layouts/system76/launch_1/meta.json | 2 +- layouts/system76/launch_2/meta.json | 2 +- layouts/system76/launch_lite_1/meta.json | 2 +- src/keyboard.rs | 7 +- src/keyboard_layer.rs | 38 ++++-- src/page.rs | 83 +++++++----- src/picker/mod.rs | 153 ++++++++++++----------- src/picker/tap_hold.rs | 75 ++++++++--- 13 files changed, 283 insertions(+), 183 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1eb06e53..03e57595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,7 @@ members = [ "tools", "ffi", "backend", "widgets" ] [dependencies] cascade = "1" futures = "0.3.13" -# 3.24 is packaged by focal -gtk = { version = "0.15.0", features = ["v3_24"] } +gtk = { version = "0.15.0", features = ["v3_22"] } libc = "0.2" once_cell = "1.4" pangocairo = "0.15.0" diff --git a/backend/src/keycode.rs b/backend/src/keycode.rs index 001fe57f..3e22af9c 100644 --- a/backend/src/keycode.rs +++ b/backend/src/keycode.rs @@ -127,6 +127,7 @@ impl std::fmt::Display for Keycode { write!(f, " | ")?; } write!(f, "{}", mod_name)?; + has_mod = true; } if !(scancode_name == "NONE" && has_mod) { write!(f, "{}", scancode_name)?; @@ -140,6 +141,7 @@ impl std::fmt::Display for Keycode { write!(f, " | ")?; } write!(f, "{}", mod_name)?; + has_mod = true; } write!(f, ", {})", scancode_name)?; } diff --git a/backend/src/layout/meta.rs b/backend/src/layout/meta.rs index c62bd6fc..f2f08e14 100644 --- a/backend/src/layout/meta.rs +++ b/backend/src/layout/meta.rs @@ -20,9 +20,9 @@ pub struct Meta { pub has_brightness: bool, /// Has LED with color (i.e. not monochrome) pub has_color: bool, - /// Supports mod-tap bindings (assumes QMK mod-tap encoding) + /// Supports mod-tap and other QMK features #[serde(default)] - pub has_mod_tap: bool, + pub is_qmk: bool, #[serde(default)] /// Disable "Invert F Keys" option pub no_fn_f: bool, diff --git a/backend/src/layout/mod.rs b/backend/src/layout/mod.rs index b73f4b6d..5a2c57cf 100644 --- a/backend/src/layout/mod.rs +++ b/backend/src/layout/mod.rs @@ -1,3 +1,4 @@ +use once_cell::sync::Lazy; use std::{collections::HashMap, fs, path::Path}; mod meta; @@ -61,6 +62,14 @@ macro_rules! keyboards { // Calls the `keyboards!` macro include!(concat!(env!("OUT_DIR"), "/keyboards.rs")); +pub fn is_qmk_basic(name: &str) -> bool { + static QMK_KEYMAP: Lazy> = + Lazy::new(|| parse_keymap_json(layout_data("system76/launch_1").unwrap().2).0); + QMK_KEYMAP + .get(name) + .map_or(false, |scancode| *scancode <= 0xff) +} + impl Layout { pub fn from_data( meta_json: &str, @@ -131,35 +140,36 @@ impl Layout { /// Get the scancode number corresponding to a name pub fn scancode_to_name(&self, scancode: u16) -> Option { - // XXX only on QMK? - if scancode >= QK_MOD_TAP && scancode <= QK_MOD_TAP_MAX { - let mods = Mods::from_bits((scancode >> 8) & 0x1f)?; - let kc = scancode & 0xff; - let kc_name = self.scancode_names.get(&kc)?; - Some(Keycode::MT(mods, kc_name.clone())) - } else if scancode >= QK_LAYER_TAP && scancode <= QK_LAYER_TAP_MAX { - let layer = ((scancode >> 8) & 0xf) as u8; - let kc = scancode & 0xff; - let kc_name = self.scancode_names.get(&kc)?; - Some(Keycode::LT(layer, kc_name.clone())) - } else if scancode >= QK_MODS && scancode <= QK_MODS_MAX { - let mods = Mods::from_bits((scancode >> 8) & 0x1f)?; - let kc = scancode & 0xff; - let kc_name = self.scancode_names.get(&kc)?; - Some(Keycode::Basic(mods, kc_name.clone())) - } else { - let kc_name = self.scancode_names.get(&scancode)?; - if let Some(mods) = Mods::from_mod_str(kc_name) { - Some(Keycode::Basic(mods, "NONE".to_string())) - } else { - Some(Keycode::Basic(Mods::empty(), kc_name.clone())) + if self.meta.is_qmk { + if scancode >= QK_MOD_TAP && scancode <= QK_MOD_TAP_MAX { + let mods = Mods::from_bits((scancode >> 8) & 0x1f)?; + let kc = scancode & 0xff; + let kc_name = self.scancode_names.get(&kc)?; + return Some(Keycode::MT(mods, kc_name.clone())); + } else if scancode >= QK_LAYER_TAP && scancode <= QK_LAYER_TAP_MAX { + let layer = ((scancode >> 8) & 0xf) as u8; + let kc = scancode & 0xff; + let kc_name = self.scancode_names.get(&kc)?; + return Some(Keycode::LT(layer, kc_name.clone())); + } else if scancode >= QK_MODS && scancode <= QK_MODS_MAX { + let mods = Mods::from_bits((scancode >> 8) & 0x1f)?; + let kc = scancode & 0xff; + let kc_name = self.scancode_names.get(&kc)?; + return Some(Keycode::Basic(mods, kc_name.clone())); } } + let kc_name = self.scancode_names.get(&scancode)?; + if let Some(mods) = Mods::from_mod_str(kc_name) { + Some(Keycode::Basic(mods, "NONE".to_string())) + } else { + Some(Keycode::Basic(Mods::empty(), kc_name.clone())) + } } /// Get the name corresponding to a scancode number pub fn scancode_from_name(&self, name: &Keycode) -> Option { match name { + Keycode::MT(_, _) | Keycode::LT(_, _) if !self.meta.is_qmk => None, Keycode::MT(mods, keycode_name) => { let kc = *self.keymap.get(keycode_name)?; Some(QK_MOD_TAP | (mods.bits() << 8) | (kc & 0xff)) @@ -174,17 +184,26 @@ impl Layout { } Keycode::Basic(mods, keycode_name) => { if mods.is_empty() { - self.keymap.get(keycode_name).copied() - } else if let Some(mod_name) = mods.as_mod_str() { - self.keymap.get(mod_name).copied() - } else { + return self.keymap.get(keycode_name).copied(); + } else if keycode_name == "NONE" { + if let Some(mod_name) = mods.as_mod_str() { + return self.keymap.get(mod_name).copied(); + } + } + if self.meta.is_qmk { let kc = *self.keymap.get(keycode_name)?; Some((mods.bits() << 8) | (kc & 0xff)) + } else { + None } } } } + pub fn has_scancode(&self, scancode_name: &str) -> bool { + self.keymap.get(scancode_name).is_some() + } + pub fn f_keys(&self) -> impl Iterator { self.default.map.iter().filter_map(|(k, v)| { if let Some(num) = v[0].strip_prefix('F') { diff --git a/layouts/picker.json b/layouts/picker.json index 4c40d630..698d634b 100644 --- a/layouts/picker.json +++ b/layouts/picker.json @@ -169,30 +169,30 @@ "keysym": "LEFT_ALT", "label": "Left Alt" }, - { - "keysym": "RIGHT_ALT", - "label": "Right Alt" - }, { "keysym": "LEFT_CTRL", "label": "Left Ctrl" }, - { - "keysym": "RIGHT_CTRL", - "label": "Right Ctrl" - }, { "keysym": "LEFT_SHIFT", "label": "Left Shift" }, - { - "keysym": "RIGHT_SHIFT", - "label": "Right Shift" - }, { "keysym": "LEFT_SUPER", "label": "Left Super" }, + { + "keysym": "RIGHT_ALT", + "label": "Right Alt" + }, + { + "keysym": "RIGHT_CTRL", + "label": "Right Ctrl" + }, + { + "keysym": "RIGHT_SHIFT", + "label": "Right Shift" + }, { "keysym": "RIGHT_SUPER", "label": "Right Super" diff --git a/layouts/system76/launch_1/meta.json b/layouts/system76/launch_1/meta.json index 7a70c50c..133c9396 100644 --- a/layouts/system76/launch_1/meta.json +++ b/layouts/system76/launch_1/meta.json @@ -5,7 +5,7 @@ "num_layers": 4, "has_brightness": true, "has_color": true, - "has_mod_tap": true, + "is_qmk": true, "no_fn_f": true, "pressed_color": "#202020", "keyboard": "system76/launch_1" diff --git a/layouts/system76/launch_2/meta.json b/layouts/system76/launch_2/meta.json index efa64865..a6afa5e8 100644 --- a/layouts/system76/launch_2/meta.json +++ b/layouts/system76/launch_2/meta.json @@ -5,7 +5,7 @@ "num_layers": 4, "has_brightness": true, "has_color": true, - "has_mod_tap": true, + "is_qmk": true, "no_fn_f": true, "pressed_color": "#202020", "keyboard": "system76/launch_2" diff --git a/layouts/system76/launch_lite_1/meta.json b/layouts/system76/launch_lite_1/meta.json index 69d0ad3c..1bb66ac8 100644 --- a/layouts/system76/launch_lite_1/meta.json +++ b/layouts/system76/launch_lite_1/meta.json @@ -5,7 +5,7 @@ "num_layers": 4, "has_brightness": true, "has_color": true, - "has_mod_tap": true, + "is_qmk": true, "no_fn_f": true, "pressed_color": "#202020", "keyboard": "system76/launch_lite_1" diff --git a/src/keyboard.rs b/src/keyboard.rs index 83a39e44..452b2856 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -283,7 +283,7 @@ impl Keyboard { } } - fn layout(&self) -> &Layout { + pub fn layout(&self) -> &Layout { &self.inner().board.layout() } @@ -303,11 +303,6 @@ impl Keyboard { &self.inner().layer_stack } - // XXX - pub fn has_scancode(&self, scancode_name: &Keycode) -> bool { - self.layout().scancode_from_name(scancode_name).is_some() - } - pub async fn keymap_set(&self, key_index: usize, layer: usize, scancode_name: &Keycode) { if let Err(err) = self.board().keys()[key_index] .set_scancode(layer, scancode_name) diff --git a/src/keyboard_layer.rs b/src/keyboard_layer.rs index 020c7ae5..0370c88b 100644 --- a/src/keyboard_layer.rs +++ b/src/keyboard_layer.rs @@ -161,18 +161,36 @@ impl WidgetImpl for KeyboardLayerInner { cr.stroke().unwrap(); } - // Draw label - let text = widget.page().get_label(k); - let layout = cascade! { - widget.create_pango_layout(Some(&text)); - ..set_width((w * pango::SCALE as f64) as i32); - ..set_alignment(pango::Alignment::Center); - }; - let text_height = layout.pixel_size().1 as f64; + // Draw labels, with line seperators if multiple + let labels = widget.page().get_label(k); + let layouts: Vec<_> = labels + .iter() + .map(|text| { + cascade! { + widget.create_pango_layout(Some(text)); + ..set_width((w * pango::SCALE as f64) as i32); + ..set_alignment(pango::Alignment::Center); + } + }) + .collect(); + let total_height = layouts + .iter() + .map(|layout| layout.pixel_size().1 as f64) + .sum::(); cr.new_path(); - cr.move_to(x, y + (h - text_height) / 2.); cr.set_source_rgba(fg.0, fg.1, fg.2, text_alpha); - pangocairo::show_layout(cr, &layout); + cr.set_line_width(1.); + cr.move_to(x, y + (h - total_height) / 2.); + for (i, layout) in layouts.iter().enumerate() { + pangocairo::show_layout(cr, &layout); + if i < layouts.len() - 1 { + let text_height = layout.pixel_size().1 as f64; + cr.rel_move_to(0.0, text_height); + cr.rel_line_to(w, 0.0); + cr.rel_move_to(-w, 1.0); + } + } + cr.stroke().unwrap(); } Inhibit(false) diff --git a/src/page.rs b/src/page.rs index 1b423238..2def818e 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,6 +1,13 @@ use crate::fl; use crate::picker::{LAYERS, SCANCODE_LABELS}; -use backend::{Key, Keycode}; +use backend::{Key, Keycode, Mods}; + +static MOD_LABELS: &[(Mods, &str)] = &[ + (Mods::CTRL, "Ctrl"), + (Mods::SHIFT, "Shift"), + (Mods::ALT, "Alt"), + (Mods::SUPER, "Super"), +]; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Page { @@ -59,21 +66,21 @@ impl Page { .into_iter() } - pub fn get_label(&self, key: &Key) -> String { + pub fn get_label(&self, key: &Key) -> Vec { match self { Page::Layer1 | Page::Layer2 | Page::Layer3 | Page::Layer4 => { let (scancode, scancode_name) = key.get_scancode(self.layer().unwrap()).unwrap(); match scancode_name { Some(keycode) => { - keycode_label(&keycode).unwrap_or_else(|| format!("{:?}", keycode)) + keycode_label(&keycode).unwrap_or_else(|| vec![format!("{:?}", keycode)]) } - None => format!("{}", scancode), + None => vec![format!("{}", scancode)], } } - Page::Keycaps => key.physical_name.clone(), - Page::Logical => key.logical_name.clone(), - Page::Electrical => key.electrical_name.clone(), - Page::Leds => key.led_name.clone(), + Page::Keycaps => vec![key.physical_name.clone()], + Page::Logical => vec![key.logical_name.clone()], + Page::Electrical => vec![key.electrical_name.clone()], + Page::Leds => vec![key.led_name.clone()], } } } @@ -85,47 +92,55 @@ impl Default for Page { } // TODO: represent mod-tap/layer-tap by rendering button with a seperator? -fn keycode_label(keycode: &Keycode) -> Option { +fn keycode_label(keycode: &Keycode) -> Option> { match keycode { Keycode::Basic(mods, keycode) => { if mods.is_empty() { - SCANCODE_LABELS.get(keycode).cloned() + Some(vec![SCANCODE_LABELS.get(keycode)?.clone()]) } else { - let mut label = String::new(); - for name in mods.mod_names() { - let mod_label = SCANCODE_LABELS.get(name)?; - if !label.is_empty() { - label.push_str(" + "); - } - label.push_str(mod_label); - } + let mut label = mods_label(*mods); if keycode != "NONE" { let keycode_label = SCANCODE_LABELS.get(keycode)?; label.push_str(" + "); label.push_str(keycode_label); } - Some(label) + Some(vec![label]) } } Keycode::MT(mods, keycode) => { - let mut label = String::new(); - for name in mods.mod_names() { - let mod_label = SCANCODE_LABELS.get(name)?; - if !label.is_empty() { - label.push_str(" + "); - } - label.push_str(mod_label); - } - let keycode_label = SCANCODE_LABELS.get(keycode)?; - label.push('\n'); - label.push_str(keycode_label); - Some(label) + let mods_label = mods_label(*mods); + let keycode_label = SCANCODE_LABELS.get(keycode)?.clone(); + Some(vec![mods_label, keycode_label]) } Keycode::LT(layer, keycode) => { let layer_id = *LAYERS.get(usize::from(*layer))?; - let layer_label = SCANCODE_LABELS.get(layer_id)?; - let keycode_label = SCANCODE_LABELS.get(keycode)?; - Some(format!("{}\n{}", layer_label, keycode_label)) + let layer_label = SCANCODE_LABELS.get(layer_id)?.clone(); + let keycode_label = SCANCODE_LABELS.get(keycode)?.clone(); + Some(vec![layer_label, keycode_label]) + } + } +} + +fn mods_label(mods: Mods) -> String { + if mods.is_empty() { + return String::new(); + } + + let mut label = if mods.contains(Mods::RIGHT) { + "Right " + } else { + "Left " + } + .to_string(); + let mut first = true; + for (mod_, mod_label) in MOD_LABELS { + if mods.contains(*mod_) { + if !first { + label.push_str(" + "); + } + label.push_str(mod_label); + first = false; } } + label } diff --git a/src/picker/mod.rs b/src/picker/mod.rs index 0c2d63e1..8738bf37 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -13,7 +13,7 @@ use std::{ }; use crate::Keyboard; -use backend::{DerefCell, Keycode, Mods}; +use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; mod picker_group; mod picker_group_box; @@ -40,12 +40,14 @@ pub static SCANCODE_LABELS: Lazy> = Lazy::new(|| { #[derive(Default)] pub struct PickerInner { - group_boxes: DerefCell>, + stack_switcher: DerefCell, + basics_group_box: DerefCell, + extras_group_box: DerefCell, keyboard: RefCell>, - event_controller_key: RefCell>, selected: RefCell>, shift: Cell, tap_hold: DerefCell, + is_qmk: Cell, } #[glib::object_subclass] @@ -75,7 +77,7 @@ impl ObjectImpl for PickerInner { let tap_hold = cascade! { tap_hold::TapHold::new(); - ..connect_selected(clone!(@weak picker => move |keycode| { + ..connect_select(clone!(@weak picker => move |keycode| { picker.set_keycode(keycode); })); }; @@ -101,8 +103,9 @@ impl ObjectImpl for PickerInner { ..show_all(); }; - self.group_boxes - .set(vec![basics_group_box, extras_group_box]); + self.stack_switcher.set(stack_switcher); + self.basics_group_box.set(basics_group_box); + self.extras_group_box.set(extras_group_box); self.tap_hold.set(tap_hold); } } @@ -116,42 +119,28 @@ impl WidgetImpl for PickerInner { let window = widget .toplevel() .and_then(|x| x.downcast::().ok()); - *self.event_controller_key.borrow_mut() = window.map(|window| { - cascade! { - gtk::EventControllerKey::new(&window); - ..connect_key_pressed(clone!(@weak widget => @default-return true, move |_, keyval, _, mods| { - let key = gdk::keys::Key::from(keyval); - if key == gdk::keys::constants::Shift_L || key == gdk::keys::constants::Shift_R { - println!("Shift"); // XXX what if only one is held? - } - true - })); - ..connect_key_released(clone!(@weak widget => move |_, keyval, _, mods| { - let key = gdk::keys::Key::from(keyval); - if key == gdk::keys::constants::Shift_L || key == gdk::keys::constants::Shift_R { - println!("Unshift"); // XXX what if only one is held? - } - })); - ..connect_focus_out(clone!(@weak widget => move |_| { - println!("Unfocus"); - })); - ..connect_modifiers(clone!(@weak widget => @default-return true, move |_, mods| { - println!("Mods: {:?}", mods); - let shift = mods.contains(gdk::ModifierType::SHIFT_MASK); - //println!("Shift: {}", shift); - if shift != widget.inner().shift.get() { - widget.inner().shift.set(shift); - widget.invalidate_sensitivity(); - } - true - })); - } - }); + if let Some(window) = &window { + window.add_events(gdk::EventMask::FOCUS_CHANGE_MASK); + window.connect_event(clone!(@weak widget => @default-return Inhibit(false), move |_, evt| { + use gdk::keys::{Key, constants}; + let is_shift_key = matches!(evt.keyval().map(Key::from), Some(constants::Shift_L | constants::Shift_R)); + // XXX Distinguish lshift, rshift if both are held? + let shift = match evt.event_type() { + gdk::EventType::KeyPress if is_shift_key => true, + gdk::EventType::KeyRelease if is_shift_key => false, + gdk::EventType::FocusChange => false, + _ => { return Inhibit(false); } + }; + widget.inner().shift.set(shift); + widget.invalidate_sensitivity(); + widget.inner().tap_hold.set_shift(shift); + Inhibit(false) + })); + } } fn unrealize(&self, widget: &Self::Type) { self.parent_unrealize(widget); - *self.event_controller_key.borrow_mut() = None; } } @@ -171,6 +160,13 @@ impl Picker { PickerInner::from_instance(self) } + fn group_boxes(&self) -> [&PickerGroupBox; 2] { + [ + &*self.inner().basics_group_box, + &*self.inner().extras_group_box, + ] + } + pub(crate) fn set_keyboard(&self, keyboard: Option) { if let Some(old_kb) = &*self.inner().keyboard.borrow() { old_kb.set_picker(None); @@ -178,11 +174,14 @@ impl Picker { if let Some(kb) = &keyboard { // Check that scancode is available for the keyboard - for group_box in self.inner().group_boxes.iter() { - group_box.set_key_visibility(|name| { - kb.has_scancode(&Keycode::Basic(Mods::empty(), name.to_string())) - }); + for group_box in self.group_boxes() { + group_box.set_key_visibility(|name| kb.layout().has_scancode(name)); } + let is_qmk = kb.layout().meta.is_qmk; + self.inner().extras_group_box.set_visible(is_qmk); + self.inner().tap_hold.set_visible(is_qmk); + self.inner().stack_switcher.set_visible(is_qmk); + self.inner().is_qmk.set(is_qmk); kb.set_picker(Some(&self)); } @@ -190,16 +189,18 @@ impl Picker { } pub(crate) fn set_selected(&self, scancode_names: Vec) { - for group_box in self.inner().group_boxes.iter() { + for group_box in self.group_boxes() { group_box.set_selected(scancode_names.clone()); } self.inner().tap_hold.set_selected(scancode_names.clone()); *self.inner().selected.borrow_mut() = scancode_names; + + self.invalidate_sensitivity(); } fn key_pressed(&self, name: String, shift: bool) { let mod_ = Mods::from_mod_str(&name); - if shift { + if shift && self.inner().is_qmk.get() { let selected = self.inner().selected.borrow(); if selected.len() == 1 { if let Keycode::Basic(mods, scancode_name) = &selected[0] { @@ -209,6 +210,9 @@ impl Picker { scancode_name.to_string(), )); return; + } else if scancode_name == &name && !mods.is_empty() { + self.set_keycode(Keycode::Basic(*mods, "NONE".to_string())); + return; } else if scancode_name == "NONE" { self.set_keycode(Keycode::Basic(*mods, name)); return; @@ -246,52 +250,55 @@ impl Picker { } fn invalidate_sensitivity(&self) { - return; - let shift = self.inner().shift.get(); - let mut allow_mods = true; - let mut allow_basic = true; - let mut allow_non_basic = true; + let mut allow_left_mods = false; + let mut allow_right_mods = false; + let mut allow_basic = false; + let mut allow_non_basic = false; + + let mut keycode_mods = Mods::empty(); + let mut basic_keycode = None; - if shift { + if shift && self.inner().is_qmk.get() { let selected = self.inner().selected.borrow(); if selected.len() == 1 { match &selected[0] { Keycode::Basic(mods, keycode) => { // Allow mods only if `keycode` is really basic? - allow_basic = keycode == "NONE"; - allow_non_basic = false; - } - Keycode::MT(..) | Keycode::LT(..) => { - allow_mods = false; - allow_basic = false; - allow_non_basic = false; + // Allow deselecting current key + let no_mod = mods.is_empty(); + let right = mods.contains(Mods::RIGHT); + allow_left_mods = no_mod || !right; + allow_right_mods = no_mod || right; + allow_basic = keycode == "NONE" && !mods.is_empty(); + keycode_mods = *mods; + basic_keycode = Some(keycode.clone()); } + Keycode::MT(..) | Keycode::LT(..) => {} } } + } else { + allow_left_mods = true; + allow_right_mods = true; + allow_basic = true; + allow_non_basic = true; } - for group_box in self.inner().group_boxes.iter() { - // TODO: What to allow? + for group_box in self.group_boxes() { group_box.set_key_sensitivity(|name| { - if [ - "LEFT_SHIFT", - "RIGHT_SHIFT", - "LEFT_ALT", - "RIGHT_ALT", - "LEFT_CTRL", - "RIGHT_CTRL", - "LEFT_SUPER", - "RIGHT_SUPER", - ] - .contains(&name) + if ["LEFT_SHIFT", "LEFT_ALT", "LEFT_CTRL", "LEFT_SUPER"].contains(&name) { + allow_left_mods + } else if ["RIGHT_SHIFT", "RIGHT_ALT", "RIGHT_CTRL", "RIGHT_SUPER"].contains(&name) { - allow_mods - } else { + allow_right_mods + } else if basic_keycode.as_deref() == Some(name) && !keycode_mods.is_empty() { + true + } else if is_qmk_basic(name) { allow_basic + } else { + allow_non_basic } - // XXX non-basic? }); } } diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs index 35b5e75b..322aca4d 100644 --- a/src/picker/tap_hold.rs +++ b/src/picker/tap_hold.rs @@ -9,9 +9,9 @@ use once_cell::sync::Lazy; use std::cell::{Cell, RefCell}; use super::{picker_group_box::PickerGroupBox, PickerKey, SCANCODE_LABELS}; -use backend::{DerefCell, Keycode, Mods}; +use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] enum Hold { Mods(Mods), Layer(u8), @@ -37,6 +37,7 @@ pub static LAYERS: &[&str] = &["LAYER_ACCESS_1", "FN", "LAYER_ACCESS_3", "LAYER_ #[derive(Default)] pub struct TapHoldInner { + shift: Cell, hold: Cell, keycode: RefCell>, mod_buttons: DerefCell>, @@ -55,7 +56,7 @@ impl ObjectImpl for TapHoldInner { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| { vec![Signal::builder( - "selected", + "select", &[Keycode::static_type().into()], glib::Type::UNIT.into(), ) @@ -67,16 +68,15 @@ impl ObjectImpl for TapHoldInner { fn constructed(&self, widget: &Self::Type) { self.parent_constructed(widget); - let layout = backend::Layout::from_board("system76/launch_1").unwrap(); - let picker_group_box = cascade! { PickerGroupBox::new("basics"); + ..set_sensitive(false); ..connect_key_pressed(clone!(@weak widget => move |name, _shift| { *widget.inner().keycode.borrow_mut() = Some(name); widget.update(); })); // Correct? - ..set_key_visibility(|name| layout.scancode_from_name(&Keycode::Basic(Mods::empty(), name.to_string())).map_or(false, |code| code <= 0xff)); + ..set_key_visibility(|name| is_qmk_basic(name)); }; let modifier_button_box = cascade! { @@ -177,18 +177,17 @@ impl TapHold { match self.inner().hold.get() { Hold::Mods(mods) => { if !mods.is_empty() { - self.emit_by_name::<()>("selected", &[&Keycode::MT(mods, keycode.to_string())]); + self.emit_by_name::<()>("select", &[&Keycode::MT(mods, keycode.to_string())]); } } Hold::Layer(layer) => { - self.emit_by_name::<()>("selected", &[&Keycode::LT(layer, keycode.to_string())]); + self.emit_by_name::<()>("select", &[&Keycode::LT(layer, keycode.to_string())]); } } } - // XXX naming vs set_selected - pub fn connect_selected(&self, cb: F) -> glib::SignalHandlerId { - self.connect_local("selected", false, move |values| { + pub fn connect_select(&self, cb: F) -> glib::SignalHandlerId { + self.connect_local("select", false, move |values| { cb(values[1].get::().unwrap()); None }) @@ -206,10 +205,7 @@ impl TapHold { Default::default() }; - // TODO how to deal with internal state? - for i in self.inner().mod_buttons.iter() { - // XXX left vs right let mod_ = Mods::from_mod_str(i.name()).unwrap(); i.set_selected( mods.contains(mod_) && (mods.contains(Mods::RIGHT) == mod_.contains(Mods::RIGHT)), @@ -220,12 +216,61 @@ impl TapHold { i.set_selected(Some(n as u8) == layer); } - if let Some(keycode) = keycode { + if let Some(keycode) = keycode.clone() { self.inner() .picker_group_box .set_selected(vec![Keycode::Basic(Mods::empty(), keycode)]); } else { self.inner().picker_group_box.set_selected(Vec::new()); } + + self.inner().hold.set(if let Some(layer) = layer { + Hold::Layer(layer) + } else { + Hold::Mods(mods) + }); + *self.inner().keycode.borrow_mut() = keycode; + + self.invalidate_sensitivity(); + } + + pub fn set_shift(&self, shift: bool) { + self.inner().shift.set(shift); + self.invalidate_sensitivity(); + } + + fn invalidate_sensitivity(&self) { + let shift = self.inner().shift.get(); + let hold = self.inner().hold.get(); + let hold_empty = hold == Hold::Mods(Mods::empty()); + let keycode = self.inner().keycode.borrow(); + + for button in self.inner().layer_buttons.iter() { + button.set_sensitive(if shift { + hold == Hold::Mods(Mods::empty()) + } else { + true + }); + } + + for button in self.inner().mod_buttons.iter() { + button.set_sensitive(if shift { + match hold { + Hold::Mods(mods) => { + let right = button.name().starts_with("RIGHT"); + mods.is_empty() || (right == mods.contains(Mods::RIGHT)) + } + Hold::Layer(_) => false, + } + } else { + true + }); + } + + self.inner().picker_group_box.set_sensitive(if shift { + !hold_empty && keycode.is_none() + } else { + !hold_empty + }); } } From 495a29fe5a0868d9420439fc2a3d0b8eed270fba Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 12 Sep 2022 14:08:07 -0700 Subject: [PATCH 03/13] Split `picker.json` from keysym label mapping This will allow parts of the picker to be defined without this style of section, and also lays the groundwork for showing different labels for international keyboard layouts. How/if to translate is still unclear. --- layouts/keysym/README.md | 3 + layouts/keysym/base.json | 85 ++++ layouts/keysym/en_us.json | 89 ++++ layouts/picker.json | 847 +++++++-------------------------- src/picker/mod.rs | 26 +- src/picker/picker_group_box.rs | 5 +- src/picker/picker_json.rs | 8 +- 7 files changed, 370 insertions(+), 693 deletions(-) create mode 100644 layouts/keysym/README.md create mode 100644 layouts/keysym/base.json create mode 100644 layouts/keysym/en_us.json diff --git a/layouts/keysym/README.md b/layouts/keysym/README.md new file mode 100644 index 00000000..9c17e9dd --- /dev/null +++ b/layouts/keysym/README.md @@ -0,0 +1,3 @@ +`base.json` should include keys that are displayed the same way regardless of layout, while `en_us.json` has those for the English US layout that may differ in another layout. + +If it turns out that something actually should/shouldn't differ between different layouts, it can be moved between `base.json` and layout specific files. diff --git a/layouts/keysym/base.json b/layouts/keysym/base.json new file mode 100644 index 00000000..33dea09b --- /dev/null +++ b/layouts/keysym/base.json @@ -0,0 +1,85 @@ +{ + "F1": "F1", + "F2": "F2", + "F3": "F3", + "F4": "F4", + "F5": "F5", + "F6": "F6", + "F7": "F7", + "F8": "F8", + "F9": "F9", + "F10": "F10", + "F11": "F11", + "F12": "F12", + "F13": "F13", + "F14": "F14", + "F15": "F15", + "F16": "F16", + "F17": "F17", + "F18": "F18", + "F19": "F19", + "F20": "F20", + "F21": "F21", + "F22": "F22", + "F23": "F23", + "F24": "F24", + "LEFT": "Left", + "UP": "Up", + "DOWN": "Down", + "RIGHT": "Right", + "HOME": "Home", + "PGUP": "PgUp", + "PGDN": "PgDn", + "END": "End", + "RESET": "Reset", + "ROLL_OVER": "Reuse", + "NONE": "None", + "MUTE": "Mute", + "VOLUME_UP": "Vol Up", + "VOLUME_DOWN": "Vol Down", + "PLAY_PAUSE": "Play Pause", + "MEDIA_NEXT": "Next Track", + "MEDIA_PREV": "Prev Track", + "FAN_TOGGLE": "Fan Toggle", + "DISPLAY_TOGGLE": "Screen Toggle", + "BRIGHTNESS_UP": "Screen Up", + "BRIGHTNESS_DOWN": "Screen Down", + "DISPLAY_MODE": "Screen Mode", + "SUSPEND": "Suspend", + "CAMERA_TOGGLE": "Camera Toggle", + "AIRPLANE_MODE": "Airplane Mode", + "TOUCHPAD": "Touchpad Toggle", + "SYSTEM_POWER": "Power", + "KBD_TOGGLE": "LED On Off", + "KBD_UP": "LED Brighten", + "KBD_DOWN": "LED Darken", + "KBD_BKL": "LED Cycle", + "KBD_COLOR": "LED Color", + "LAYER_ACCESS_1": "Access Layer\u00a01", + "FN": "Access Layer\u00a02", + "LAYER_ACCESS_3": "Access Layer\u00a03", + "LAYER_ACCESS_4": "Access Layer\u00a04", + "LAYER_SWITCH_1": "Switch to\nLayer\u00a01", + "LAYER_SWITCH_2": "Switch to\nLayer\u00a02", + "LAYER_SWITCH_3": "Switch to\nLayer\u00a03", + "LAYER_SWITCH_4": "Switch to\nLayer\u00a04", + "MS_UP": "Mouse cursor up", + "MS_DOWN": "Mouse cursor down", + "MS_LEFT": "Mouse cursor left", + "MS_RIGHT": "Mouse cursor right", + "MS_BTN1": "Press button 1", + "MS_BTN2": "Press button 2", + "MS_BTN3": "Press button 3", + "MS_BTN4": "Press button 4", + "MS_BTN5": "Press button 5", + "MS_BTN6": "Press button 6", + "MS_BTN7": "Press button 7", + "MS_BTN8": "Press button 8", + "MS_WH_UP": "Move wheel up", + "MS_WH_DOWN": "Move wheel down", + "MS_WH_LEFT": "Move wheel left", + "MS_WH_RIGHT": "Move wheel right", + "MS_ACCEL0": "Set speed to 0", + "MS_ACCEL1": "Set speed to 1", + "MS_ACCEL2": "Set speed to 2" +} diff --git a/layouts/keysym/en_us.json b/layouts/keysym/en_us.json new file mode 100644 index 00000000..b46fcc1f --- /dev/null +++ b/layouts/keysym/en_us.json @@ -0,0 +1,89 @@ +{ + "A": "A", + "B": "B", + "C": "C", + "D": "D", + "E": "E", + "F": "F", + "G": "G", + "H": "H", + "I": "I", + "J": "J", + "K": "K", + "L": "L", + "M": "M", + "N": "N", + "O": "O", + "P": "P", + "Q": "Q", + "R": "R", + "S": "S", + "T": "T", + "U": "U", + "V": "V", + "W": "W", + "X": "X", + "Y": "Y", + "Z": "Z", + "1": "!\n1", + "2": "@\n2", + "3": "#\n3", + "4": "$\n4", + "5": "%\n5", + "6": "^\n6", + "7": "&\n7", + "8": "*\n8", + "9": "(\n9", + "0": ")\n0", + "LEFT_ALT": "Left Alt", + "LEFT_CTRL": "Left Ctrl", + "LEFT_SHIFT": "Left Shift", + "LEFT_SUPER": "Left Super", + "RIGHT_ALT": "Right Alt", + "RIGHT_CTRL": "Right Ctrl", + "RIGHT_SHIFT": "Right Shift", + "RIGHT_SUPER": "Right Super", + "ENTER": "Enter", + "BKSP": "Bksp", + "DEL": "Del", + "TAB": "Tab", + "SPACE": "Space", + "CAPS": "Caps", + "APP": "Menu", + "ESC": "Esc", + "PRINT_SCREEN": "PrtSc\nSysrq", + "INSERT": "Ins", + "SCROLL_LOCK": "Scroll Lock", + "PAUSE": "Pause\nBreak", + "NUM_LOCK": "Num Lock", + "NUM_7": "7", + "NUM_8": "8", + "NUM_9": "9", + "NUM_MINUS": "-", + "NUM_PLUS": "+", + "NUM_SLASH": "/", + "NUM_4": "4", + "NUM_5": "5", + "NUM_6": "6", + "NUM_ASTERISK": "*", + "NUM_ENTER": "Enter", + "NUM_0": "0", + "NUM_1": "1", + "NUM_2": "2", + "NUM_3": "3", + "NUM_PERIOD": ".", + "TICK": "~\n`", + "QUOTE": "\"\n'", + "SEMICOLON": ":\n;", + "MINUS": "_\n-", + "EQUALS": "+\n=", + "SLASH": "?\n/", + "COMMA": "<\n,", + "PERIOD": ">\n.", + "BACKSLASH": "|\n\\", + "BRACE_OPEN": "{\n[", + "BRACE_CLOSE": "}\n]", + "NONUS_HASH": "Non-US #", + "NONUS_BSLASH": "Non-US \\" + +} diff --git a/layouts/picker.json b/layouts/picker.json index 698d634b..fb4f5be0 100644 --- a/layouts/picker.json +++ b/layouts/picker.json @@ -5,110 +5,32 @@ "cols": 9, "width": 1, "keys": [ - { - "keysym": "A", - "label": "A" - }, - { - "keysym": "B", - "label": "B" - }, - { - "keysym": "C", - "label": "C" - }, - { - "keysym": "D", - "label": "D" - }, - { - "keysym": "E", - "label": "E" - }, - { - "keysym": "F", - "label": "F" - }, - { - "keysym": "G", - "label": "G" - }, - { - "keysym": "H", - "label": "H" - }, - { - "keysym": "I", - "label": "I" - }, - { - "keysym": "J", - "label": "J" - }, - { - "keysym": "K", - "label": "K" - }, - { - "keysym": "L", - "label": "L" - }, - { - "keysym": "M", - "label": "M" - }, - { - "keysym": "N", - "label": "N" - }, - { - "keysym": "O", - "label": "O" - }, - { - "keysym": "P", - "label": "P" - }, - { - "keysym": "Q", - "label": "Q" - }, - { - "keysym": "R", - "label": "R" - }, - { - "keysym": "S", - "label": "S" - }, - { - "keysym": "T", - "label": "T" - }, - { - "keysym": "U", - "label": "U" - }, - { - "keysym": "V", - "label": "V" - }, - { - "keysym": "W", - "label": "W" - }, - { - "keysym": "X", - "label": "X" - }, - { - "keysym": "Y", - "label": "Y" - }, - { - "keysym": "Z", - "label": "Z" - } + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z" ] }, { @@ -117,46 +39,16 @@ "cols": 4, "width": 1, "keys": [ - { - "keysym": "1", - "label": "!\n1" - }, - { - "keysym": "2", - "label": "@\n2" - }, - { - "keysym": "3", - "label": "#\n3" - }, - { - "keysym": "4", - "label": "$\n4" - }, - { - "keysym": "5", - "label": "%\n5" - }, - { - "keysym": "6", - "label": "^\n6" - }, - { - "keysym": "7", - "label": "&\n7" - }, - { - "keysym": "8", - "label": "*\n8" - }, - { - "keysym": "9", - "label": "(\n9" - }, - { - "keysym": "0", - "label": ")\n0" - } + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0" ] }, { @@ -165,38 +57,14 @@ "cols": 4, "width": 1, "keys": [ - { - "keysym": "LEFT_ALT", - "label": "Left Alt" - }, - { - "keysym": "LEFT_CTRL", - "label": "Left Ctrl" - }, - { - "keysym": "LEFT_SHIFT", - "label": "Left Shift" - }, - { - "keysym": "LEFT_SUPER", - "label": "Left Super" - }, - { - "keysym": "RIGHT_ALT", - "label": "Right Alt" - }, - { - "keysym": "RIGHT_CTRL", - "label": "Right Ctrl" - }, - { - "keysym": "RIGHT_SHIFT", - "label": "Right Shift" - }, - { - "keysym": "RIGHT_SUPER", - "label": "Right Super" - } + "LEFT_ALT", + "LEFT_CTRL", + "LEFT_SHIFT", + "LEFT_SUPER", + "RIGHT_ALT", + "RIGHT_CTRL", + "RIGHT_SHIFT", + "RIGHT_SUPER" ] }, { @@ -205,66 +73,21 @@ "cols": 4, "width": 1, "keys": [ - { - "keysym": "ENTER", - "label": "Enter" - }, - { - "keysym": "BKSP", - "label": "Bksp" - }, - { - "keysym": "DEL", - "label": "Del" - }, - { - "keysym": "TAB", - "label": "Tab" - }, - { - "keysym": "SPACE", - "label": "Space" - }, - { - "keysym": "CAPS", - "label": "Caps" - }, - { - "keysym": "APP", - "label": "Menu" - }, - { - "keysym": "ESC", - "label": "Esc" - }, - { - "keysym": "PRINT_SCREEN", - "label": "PrtSc\nSysrq" - }, - { - "keysym": "INSERT", - "label": "Ins" - }, - { - "keysym": "SCROLL_LOCK", - "label": "Scroll Lock" - }, - { - "keysym": "PAUSE", - "label": "Pause\nBreak" - }, - { - "keysym": "RESET", - "label": "Reset" - }, - { - "keysym": "ROLL_OVER", - "label": "Reuse" - }, - { - "keysym": "NONE", - "label": "None" - } + "ENTER", + "BKSP", + "DEL", + "TAB", + "SPACE", + "CAPS", + "APP", + "ESC", + "PRINT_SCREEN", + "INSERT", + "SCROLL_LOCK", + "PAUSE", + "RESET", + "ROLL_OVER", + "NONE" ] }, { @@ -273,54 +96,18 @@ "cols": 4, "width": 1, "keys": [ - { - "keysym": "F1", - "label": "F1" - }, - { - "keysym": "F2", - "label": "F2" - }, - { - "keysym": "F3", - "label": "F3" - }, - { - "keysym": "F4", - "label": "F4" - }, - { - "keysym": "F5", - "label": "F5" - }, - { - "keysym": "F6", - "label": "F6" - }, - { - "keysym": "F7", - "label": "F7" - }, - { - "keysym": "F8", - "label": "F8" - }, - { - "keysym": "F9", - "label": "F9" - }, - { - "keysym": "F10", - "label": "F10" - }, - { - "keysym": "F11", - "label": "F11" - }, - { - "keysym": "F12", - "label": "F12" - } + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12" ] }, { @@ -329,74 +116,23 @@ "cols": 6, "width": 1, "keys": [ - { - "keysym": "NUM_LOCK", - "label": "Num Lock" - }, - { - "keysym": "NUM_7", - "label": "7" - }, - { - "keysym": "NUM_8", - "label": "8" - }, - { - "keysym": "NUM_9", - "label": "9" - }, - { - "keysym": "NUM_MINUS", - "label": "-" - }, - { - "keysym": "NUM_PLUS", - "label": "+" - }, - { - "keysym": "NUM_SLASH", - "label": "/" - }, - { - "keysym": "NUM_4", - "label": "4" - }, - { - "keysym": "NUM_5", - "label": "5" - }, - { - "keysym": "NUM_6", - "label": "6" - }, - { - "keysym": "NUM_ASTERISK", - "label": "*" - }, - { - "keysym": "NUM_ENTER", - "label": "Enter" - }, - { - "keysym": "NUM_0", - "label": "0" - }, - { - "keysym": "NUM_1", - "label": "1" - }, - { - "keysym": "NUM_2", - "label": "2" - }, - { - "keysym": "NUM_3", - "label": "3" - }, - { - "keysym": "NUM_PERIOD", - "label": "." - } + "NUM_LOCK", + "NUM_7", + "NUM_8", + "NUM_9", + "NUM_MINUS", + "NUM_PLUS", + "NUM_SLASH", + "NUM_4", + "NUM_5", + "NUM_6", + "NUM_ASTERISK", + "NUM_ENTER", + "NUM_0", + "NUM_1", + "NUM_2", + "NUM_3", + "NUM_PERIOD" ] }, { @@ -405,58 +141,19 @@ "cols": 6, "width": 1, "keys": [ - { - "keysym": "TICK", - "label": "~\n`" - }, - { - "keysym": "QUOTE", - "label": "\"\n'" - }, - { - "keysym": "SEMICOLON", - "label": ":\n;" - }, - { - "keysym": "MINUS", - "label": "_\n-" - }, - { - "keysym": "EQUALS", - "label": "+\n=" - }, - { - "keysym": "SLASH", - "label": "?\n/" - }, - { - "keysym": "COMMA", - "label": "<\n," - }, - { - "keysym": "PERIOD", - "label": ">\n." - }, - { - "keysym": "BACKSLASH", - "label": "|\n\\" - }, - { - "keysym": "BRACE_OPEN", - "label": "{\n[" - }, - { - "keysym": "BRACE_CLOSE", - "label": "}\n]" - }, - { - "keysym": "NONUS_HASH", - "label": "Non-US #" - }, - { - "keysym": "NONUS_BSLASH", - "label": "Non-US \\" - } + "TICK", + "QUOTE", + "SEMICOLON", + "MINUS", + "EQUALS", + "SLASH", + "COMMA", + "PERIOD", + "BACKSLASH", + "BRACE_OPEN", + "BRACE_CLOSE", + "NONUS_HASH", + "NONUS_BSLASH" ] }, { @@ -465,38 +162,14 @@ "cols": 4, "width": 1, "keys": [ - { - "keysym": "LEFT", - "label": "Left" - }, - { - "keysym": "UP", - "label": "Up" - }, - { - "keysym": "DOWN", - "label": "Down" - }, - { - "keysym": "RIGHT", - "label": "Right" - }, - { - "keysym": "HOME", - "label": "Home" - }, - { - "keysym": "PGUP", - "label": "PgUp" - }, - { - "keysym": "PGDN", - "label": "PgDn" - }, - { - "keysym": "END", - "label": "End" - } + "LEFT", + "UP", + "DOWN", + "RIGHT", + "HOME", + "PGUP", + "PGDN", + "END" ] }, { @@ -505,30 +178,12 @@ "cols": 3, "width": 1, "keys": [ - { - "keysym": "MUTE", - "label": "Mute" - }, - { - "keysym": "VOLUME_UP", - "label": "Vol Up" - }, - { - "keysym": "VOLUME_DOWN", - "label": "Vol Down" - }, - { - "keysym": "PLAY_PAUSE", - "label": "Play Pause" - }, - { - "keysym": "MEDIA_NEXT", - "label": "Next Track" - }, - { - "keysym": "MEDIA_PREV", - "label": "Prev Track" - } + "MUTE", + "VOLUME_UP", + "VOLUME_DOWN", + "PLAY_PAUSE", + "MEDIA_NEXT", + "MEDIA_PREV" ] }, { @@ -537,46 +192,16 @@ "cols": 4, "width": 2, "keys": [ - { - "keysym": "FAN_TOGGLE", - "label": "Fan Toggle" - }, - { - "keysym": "DISPLAY_TOGGLE", - "label": "Screen Toggle" - }, - { - "keysym": "BRIGHTNESS_UP", - "label": "Screen Up" - }, - { - "keysym": "BRIGHTNESS_DOWN", - "label": "Screen Down" - }, - { - "keysym": "DISPLAY_MODE", - "label": "Screen Mode" - }, - { - "keysym": "SUSPEND", - "label": "Suspend" - }, - { - "keysym": "CAMERA_TOGGLE", - "label": "Camera Toggle" - }, - { - "keysym": "AIRPLANE_MODE", - "label": "Airplane Mode" - }, - { - "keysym": "TOUCHPAD", - "label": "Touchpad Toggle" - }, - { - "keysym": "SYSTEM_POWER", - "label": "Power" - } + "FAN_TOGGLE", + "DISPLAY_TOGGLE", + "BRIGHTNESS_UP", + "BRIGHTNESS_DOWN", + "DISPLAY_MODE", + "SUSPEND", + "CAMERA_TOGGLE", + "AIRPLANE_MODE", + "TOUCHPAD", + "SYSTEM_POWER" ] }, { @@ -585,26 +210,11 @@ "cols": 4, "width": 1, "keys": [ - { - "keysym": "KBD_TOGGLE", - "label": "LED On Off" - }, - { - "keysym": "KBD_UP", - "label": "LED Brighten" - }, - { - "keysym": "KBD_DOWN", - "label": "LED Darken" - }, - { - "keysym": "KBD_BKL", - "label": "LED Cycle" - }, - { - "keysym": "KBD_COLOR", - "label": "LED Color" - } + "KBD_TOGGLE", + "KBD_UP", + "KBD_DOWN", + "KBD_BKL", + "KBD_COLOR" ] }, { @@ -613,38 +223,14 @@ "cols": 4, "width": 2, "keys": [ - { - "keysym": "LAYER_ACCESS_1", - "label": "Access Layer\u00a01" - }, - { - "keysym": "FN", - "label": "Access Layer\u00a02" - }, - { - "keysym": "LAYER_ACCESS_3", - "label": "Access Layer\u00a03" - }, - { - "keysym": "LAYER_ACCESS_4", - "label": "Access Layer\u00a04" - }, - { - "keysym": "LAYER_SWITCH_1", - "label": "Switch to\nLayer\u00a01" - }, - { - "keysym": "LAYER_SWITCH_2", - "label": "Switch to\nLayer\u00a02" - }, - { - "keysym": "LAYER_SWITCH_3", - "label": "Switch to\nLayer\u00a03" - }, - { - "keysym": "LAYER_SWITCH_4", - "label": "Switch to\nLayer\u00a04" - } + "LAYER_ACCESS_1", + "FN", + "LAYER_ACCESS_3", + "LAYER_ACCESS_4", + "LAYER_SWITCH_1", + "LAYER_SWITCH_2", + "LAYER_SWITCH_3", + "LAYER_SWITCH_4" ] }, { @@ -653,54 +239,18 @@ "cols": 6, "width": 1, "keys": [ - { - "keysym": "F13", - "label": "F13" - }, - { - "keysym": "F14", - "label": "F14" - }, - { - "keysym": "F15", - "label": "F15" - }, - { - "keysym": "F16", - "label": "F16" - }, - { - "keysym": "F17", - "label": "F17" - }, - { - "keysym": "F18", - "label": "F18" - }, - { - "keysym": "F19", - "label": "F19" - }, - { - "keysym": "F20", - "label": "F20" - }, - { - "keysym": "F21", - "label": "F21" - }, - { - "keysym": "F22", - "label": "F22" - }, - { - "keysym": "F23", - "label": "F23" - }, - { - "keysym": "F24", - "label": "F24" - } + "F13", + "F14", + "F15", + "F16", + "F17", + "F18", + "F19", + "F20", + "F21", + "F22", + "F23", + "F24" ] }, { @@ -709,82 +259,25 @@ "cols": 5, "width": 2, "keys": [ - { - "keysym": "MS_UP", - "label": "Mouse cursor up" - }, - { - "keysym": "MS_DOWN", - "label": "Mouse cursor down" - }, - { - "keysym": "MS_LEFT", - "label": "Mouse cursor left" - }, - { - "keysym": "MS_RIGHT", - "label": "Mouse cursor right" - }, - { - "keysym": "MS_BTN1", - "label": "Press button 1" - }, - { - "keysym": "MS_BTN2", - "label": "Press button 2" - }, - { - "keysym": "MS_BTN3", - "label": "Press button 3" - }, - { - "keysym": "MS_BTN4", - "label": "Press button 4" - }, - { - "keysym": "MS_BTN5", - "label": "Press button 5" - }, - { - "keysym": "MS_BTN6", - "label": "Press button 6" - }, - { - "keysym": "MS_BTN7", - "label": "Press button 7" - }, - { - "keysym": "MS_BTN8", - "label": "Press button 8" - }, - { - "keysym": "MS_WH_UP", - "label": "Move wheel up" - }, - { - "keysym": "MS_WH_DOWN", - "label": "Move wheel down" - }, - { - "keysym": "MS_WH_LEFT", - "label": "Move wheel left" - }, - { - "keysym": "MS_WH_RIGHT", - "label": "Move wheel right" - }, - { - "keysym": "MS_ACCEL0", - "label": "Set speed to 0" - }, - { - "keysym": "MS_ACCEL1", - "label": "Set speed to 1" - }, - { - "keysym": "MS_ACCEL2", - "label": "Set speed to 2" - } + "MS_UP", + "MS_DOWN", + "MS_LEFT", + "MS_RIGHT", + "MS_BTN1", + "MS_BTN2", + "MS_BTN3", + "MS_BTN4", + "MS_BTN5", + "MS_BTN6", + "MS_BTN7", + "MS_BTN8", + "MS_WH_UP", + "MS_WH_DOWN", + "MS_WH_LEFT", + "MS_WH_RIGHT", + "MS_ACCEL0", + "MS_ACCEL1", + "MS_ACCEL2" ] } -] +] \ No newline at end of file diff --git a/src/picker/mod.rs b/src/picker/mod.rs index 8738bf37..9bb81567 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -29,12 +29,13 @@ use tap_hold::TapHold; pub use tap_hold::LAYERS; pub static SCANCODE_LABELS: Lazy> = Lazy::new(|| { - let mut labels = HashMap::new(); - for group in picker_json() { - for key in group.keys { - labels.insert(key.keysym, key.label); - } - } + let base_json = include_str!("../../layouts/keysym/base.json"); + let en_us_json = include_str!("../../layouts/keysym/en_us.json"); + let base: HashMap = serde_json::from_str(base_json).unwrap(); + let en_us: HashMap = serde_json::from_str(en_us_json).unwrap(); + + let mut labels = base; + labels.extend(en_us.into_iter()); labels }); @@ -308,7 +309,7 @@ impl Picker { mod tests { use crate::*; use backend::{layouts, Layout}; - use std::collections::HashSet; + use std::collections::{HashMap, HashSet}; #[test] fn picker_has_keys() { @@ -323,4 +324,15 @@ mod tests { } assert_eq!(missing, HashSet::new()); } + + #[test] + fn no_duplicating_base_json() { + let base_json = include_str!("../../layouts/keysym/base.json"); + let en_us_json = include_str!("../../layouts/keysym/en_us.json"); + let base: HashMap = serde_json::from_str(base_json).unwrap(); + let en_us: HashMap = serde_json::from_str(en_us_json).unwrap(); + for k in base.keys() { + assert!(!en_us.contains_key(k), "{} in both base and en_us", k); + } + } } diff --git a/src/picker/picker_group_box.rs b/src/picker/picker_group_box.rs index 2e0edec4..15a6975e 100644 --- a/src/picker/picker_group_box.rs +++ b/src/picker/picker_group_box.rs @@ -174,10 +174,11 @@ impl PickerGroupBox { let mut group = PickerGroup::new(json_group.label, json_group.cols); for json_key in json_group.keys { - let key = PickerKey::new(&json_key.keysym, &json_key.label, json_group.width); + let keysym_label = super::SCANCODE_LABELS.get(&json_key).unwrap(); + let key = PickerKey::new(&json_key, keysym_label, json_group.width); group.add_key(key.clone()); - keys.insert(json_key.keysym, key); + keys.insert(json_key, key); } groups.push(group); diff --git a/src/picker/picker_json.rs b/src/picker/picker_json.rs index 2dadce19..6c0ad780 100644 --- a/src/picker/picker_json.rs +++ b/src/picker/picker_json.rs @@ -1,18 +1,12 @@ use serde::Deserialize; -#[derive(Deserialize)] -pub struct PickerJsonKey { - pub keysym: String, - pub label: String, -} - #[derive(Deserialize)] pub struct PickerJsonGroup { pub label: String, pub section: String, pub cols: u32, pub width: i32, - pub keys: Vec, + pub keys: Vec, } pub fn picker_json() -> Vec { From 02ca84cbb9e8ca7ef6c65b2343aa16b6c6d8c4c9 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Sep 2022 15:10:37 -0700 Subject: [PATCH 04/13] Refactor picker group/group box; eliminate `picker.json`; custom groups This design allows custom widgets for specific groups, and uses one for the international keys. --- layouts/keysym/base.json | 20 +- layouts/picker.json | 283 ------------------ src/picker/group_box/basics.rs | 169 +++++++++++ src/picker/group_box/extras.rs | 44 +++ .../group/basic_group.rs} | 39 ++- src/picker/group_box/group/international.rs | 84 ++++++ src/picker/group_box/group/mod.rs | 12 + .../{picker_group_box.rs => group_box/mod.rs} | 75 +++-- src/picker/mod.rs | 11 +- src/picker/picker_json.rs | 25 -- src/picker/picker_key.rs | 6 +- src/picker/tap_hold.rs | 11 +- 12 files changed, 402 insertions(+), 377 deletions(-) delete mode 100644 layouts/picker.json create mode 100644 src/picker/group_box/basics.rs create mode 100644 src/picker/group_box/extras.rs rename src/picker/{picker_group.rs => group_box/group/basic_group.rs} (62%) create mode 100644 src/picker/group_box/group/international.rs create mode 100644 src/picker/group_box/group/mod.rs rename src/picker/{picker_group_box.rs => group_box/mod.rs} (79%) delete mode 100644 src/picker/picker_json.rs diff --git a/layouts/keysym/base.json b/layouts/keysym/base.json index 33dea09b..442540ab 100644 --- a/layouts/keysym/base.json +++ b/layouts/keysym/base.json @@ -81,5 +81,23 @@ "MS_WH_RIGHT": "Move wheel right", "MS_ACCEL0": "Set speed to 0", "MS_ACCEL1": "Set speed to 1", - "MS_ACCEL2": "Set speed to 2" + "MS_ACCEL2": "Set speed to 2", + "INT1": "INT 1", + "INT2": "INT 2", + "INT3": "INT 3", + "INT4": "INT 4", + "INT5": "INT 5", + "INT6": "INT 6", + "INT7": "INT 7", + "INT8": "INT 8", + "INT9": "INT 9", + "LANG1": "LANG 1", + "LANG2": "LANG 2", + "LANG3": "LANG 3", + "LANG4": "LANG 4", + "LANG5": "LANG 5", + "LANG6": "LANG 6", + "LANG7": "LANG 7", + "LANG8": "LANG 8", + "LANG9": "LANG 9" } diff --git a/layouts/picker.json b/layouts/picker.json deleted file mode 100644 index fb4f5be0..00000000 --- a/layouts/picker.json +++ /dev/null @@ -1,283 +0,0 @@ -[ - { - "label": "Alphabet keys", - "section": "basics", - "cols": 9, - "width": 1, - "keys": [ - "A", - "B", - "C", - "D", - "E", - "F", - "G", - "H", - "I", - "J", - "K", - "L", - "M", - "N", - "O", - "P", - "Q", - "R", - "S", - "T", - "U", - "V", - "W", - "X", - "Y", - "Z" - ] - }, - { - "label": "Number keys", - "section": "basics", - "cols": 4, - "width": 1, - "keys": [ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "0" - ] - }, - { - "label": "Modifier keys", - "section": "basics", - "cols": 4, - "width": 1, - "keys": [ - "LEFT_ALT", - "LEFT_CTRL", - "LEFT_SHIFT", - "LEFT_SUPER", - "RIGHT_ALT", - "RIGHT_CTRL", - "RIGHT_SHIFT", - "RIGHT_SUPER" - ] - }, - { - "label": "Actions", - "section": "basics", - "cols": 4, - "width": 1, - "keys": [ - "ENTER", - "BKSP", - "DEL", - "TAB", - "SPACE", - "CAPS", - "APP", - "ESC", - "PRINT_SCREEN", - "INSERT", - "SCROLL_LOCK", - "PAUSE", - "RESET", - "ROLL_OVER", - "NONE" - ] - }, - { - "label": "Function keys", - "section": "basics", - "cols": 4, - "width": 1, - "keys": [ - "F1", - "F2", - "F3", - "F4", - "F5", - "F6", - "F7", - "F8", - "F9", - "F10", - "F11", - "F12" - ] - }, - { - "label": "Numpad", - "section": "basics", - "cols": 6, - "width": 1, - "keys": [ - "NUM_LOCK", - "NUM_7", - "NUM_8", - "NUM_9", - "NUM_MINUS", - "NUM_PLUS", - "NUM_SLASH", - "NUM_4", - "NUM_5", - "NUM_6", - "NUM_ASTERISK", - "NUM_ENTER", - "NUM_0", - "NUM_1", - "NUM_2", - "NUM_3", - "NUM_PERIOD" - ] - }, - { - "label": "Symbols", - "section": "basics", - "cols": 6, - "width": 1, - "keys": [ - "TICK", - "QUOTE", - "SEMICOLON", - "MINUS", - "EQUALS", - "SLASH", - "COMMA", - "PERIOD", - "BACKSLASH", - "BRACE_OPEN", - "BRACE_CLOSE", - "NONUS_HASH", - "NONUS_BSLASH" - ] - }, - { - "label": "Navigation", - "section": "basics", - "cols": 4, - "width": 1, - "keys": [ - "LEFT", - "UP", - "DOWN", - "RIGHT", - "HOME", - "PGUP", - "PGDN", - "END" - ] - }, - { - "label": "Media", - "section": "basics", - "cols": 3, - "width": 1, - "keys": [ - "MUTE", - "VOLUME_UP", - "VOLUME_DOWN", - "PLAY_PAUSE", - "MEDIA_NEXT", - "MEDIA_PREV" - ] - }, - { - "label": "Controls", - "section": "basics", - "cols": 4, - "width": 2, - "keys": [ - "FAN_TOGGLE", - "DISPLAY_TOGGLE", - "BRIGHTNESS_UP", - "BRIGHTNESS_DOWN", - "DISPLAY_MODE", - "SUSPEND", - "CAMERA_TOGGLE", - "AIRPLANE_MODE", - "TOUCHPAD", - "SYSTEM_POWER" - ] - }, - { - "label": "LED controls", - "section": "basics", - "cols": 4, - "width": 1, - "keys": [ - "KBD_TOGGLE", - "KBD_UP", - "KBD_DOWN", - "KBD_BKL", - "KBD_COLOR" - ] - }, - { - "label": "Layer keys", - "section": "basics", - "cols": 4, - "width": 2, - "keys": [ - "LAYER_ACCESS_1", - "FN", - "LAYER_ACCESS_3", - "LAYER_ACCESS_4", - "LAYER_SWITCH_1", - "LAYER_SWITCH_2", - "LAYER_SWITCH_3", - "LAYER_SWITCH_4" - ] - }, - { - "label": "Additional Function Keys", - "section": "extras", - "cols": 6, - "width": 1, - "keys": [ - "F13", - "F14", - "F15", - "F16", - "F17", - "F18", - "F19", - "F20", - "F21", - "F22", - "F23", - "F24" - ] - }, - { - "label": "Mouse Actions", - "section": "extras", - "cols": 5, - "width": 2, - "keys": [ - "MS_UP", - "MS_DOWN", - "MS_LEFT", - "MS_RIGHT", - "MS_BTN1", - "MS_BTN2", - "MS_BTN3", - "MS_BTN4", - "MS_BTN5", - "MS_BTN6", - "MS_BTN7", - "MS_BTN8", - "MS_WH_UP", - "MS_WH_DOWN", - "MS_WH_LEFT", - "MS_WH_RIGHT", - "MS_ACCEL0", - "MS_ACCEL1", - "MS_ACCEL2" - ] - } -] \ No newline at end of file diff --git a/src/picker/group_box/basics.rs b/src/picker/group_box/basics.rs new file mode 100644 index 00000000..fca8508b --- /dev/null +++ b/src/picker/group_box/basics.rs @@ -0,0 +1,169 @@ +use super::{PickerBasicGroup, PickerGroup, PickerGroupBox, PickerKey}; + +impl PickerGroupBox { + pub fn basics() -> Self { + Self::new(vec![ + Box::new(PickerBasicGroup::new( + "Alphabet keys".to_string(), + 9, + 1, + &[ + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", + "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + ], + )), + Box::new(PickerBasicGroup::new( + "Number keys".to_string(), + 4, + 1, + &["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], + )), + Box::new(PickerBasicGroup::new( + "Modifier keys".to_string(), + 4, + 1, + &[ + "LEFT_ALT", + "LEFT_CTRL", + "LEFT_SHIFT", + "LEFT_SUPER", + "RIGHT_ALT", + "RIGHT_CTRL", + "RIGHT_SHIFT", + "RIGHT_SUPER", + ], + )), + Box::new(PickerBasicGroup::new( + "Actions".to_string(), + 4, + 1, + &[ + "ENTER", + "BKSP", + "DEL", + "TAB", + "SPACE", + "CAPS", + "APP", + "ESC", + "PRINT_SCREEN", + "INSERT", + "SCROLL_LOCK", + "PAUSE", + "RESET", + "ROLL_OVER", + "NONE", + ], + )), + Box::new(PickerBasicGroup::new( + "Function keys".to_string(), + 4, + 1, + &[ + "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + ], + )), + Box::new(PickerBasicGroup::new( + "Numpad".to_string(), + 6, + 1, + &[ + "NUM_LOCK", + "NUM_7", + "NUM_8", + "NUM_9", + "NUM_MINUS", + "NUM_PLUS", + "NUM_SLASH", + "NUM_4", + "NUM_5", + "NUM_6", + "NUM_ASTERISK", + "NUM_ENTER", + "NUM_0", + "NUM_1", + "NUM_2", + "NUM_3", + "NUM_PERIOD", + ], + )), + Box::new(PickerBasicGroup::new( + "Symbols".to_string(), + 6, + 1, + &[ + "TICK", + "QUOTE", + "SEMICOLON", + "MINUS", + "EQUALS", + "SLASH", + "COMMA", + "PERIOD", + "BACKSLASH", + "BRACE_OPEN", + "BRACE_CLOSE", + "NONUS_HASH", + "NONUS_BSLASH", + ], + )), + Box::new(PickerBasicGroup::new( + "Navigation".to_string(), + 4, + 1, + &["LEFT", "UP", "DOWN", "RIGHT", "HOME", "PGUP", "PGDN", "END"], + )), + Box::new(PickerBasicGroup::new( + "Media".to_string(), + 3, + 1, + &[ + "MUTE", + "VOLUME_UP", + "VOLUME_DOWN", + "PLAY_PAUSE", + "MEDIA_NEXT", + "MEDIA_PREV", + ], + )), + Box::new(PickerBasicGroup::new( + "Controls".to_string(), + 4, + 2, + &[ + "FAN_TOGGLE", + "DISPLAY_TOGGLE", + "BRIGHTNESS_UP", + "BRIGHTNESS_DOWN", + "DISPLAY_MODE", + "SUSPEND", + "CAMERA_TOGGLE", + "AIRPLANE_MODE", + "TOUCHPAD", + "SYSTEM_POWER", + ], + )), + Box::new(PickerBasicGroup::new( + "LED controls".to_string(), + 4, + 1, + &["KBD_TOGGLE", "KBD_UP", "KBD_DOWN", "KBD_BKL", "KBD_COLOR"], + )), + Box::new(PickerBasicGroup::new( + "Layer keys".to_string(), + 4, + 2, + &[ + "LAYER_ACCESS_1", + "FN", + "LAYER_ACCESS_3", + "LAYER_ACCESS_4", + "LAYER_SWITCH_1", + "LAYER_SWITCH_2", + "LAYER_SWITCH_3", + "LAYER_SWITCH_4", + ], + )), + ]) + } +} diff --git a/src/picker/group_box/extras.rs b/src/picker/group_box/extras.rs new file mode 100644 index 00000000..f44906d5 --- /dev/null +++ b/src/picker/group_box/extras.rs @@ -0,0 +1,44 @@ +use super::{PickerBasicGroup, PickerGroup, PickerGroupBox, PickerInternationalGroup, PickerKey}; + +impl PickerGroupBox { + pub fn extras() -> Self { + Self::new(vec![ + Box::new(PickerBasicGroup::new( + "Additional Function Keys".to_string(), + 6, + 1, + &[ + "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", + "F24", + ], + )), + Box::new(PickerBasicGroup::new( + "Mouse Actions".to_string(), + 5, + 2, + &[ + "MS_UP", + "MS_DOWN", + "MS_LEFT", + "MS_RIGHT", + "MS_BTN1", + "MS_BTN2", + "MS_BTN3", + "MS_BTN4", + "MS_BTN5", + "MS_BTN6", + "MS_BTN7", + "MS_BTN8", + "MS_WH_UP", + "MS_WH_DOWN", + "MS_WH_LEFT", + "MS_WH_RIGHT", + "MS_ACCEL0", + "MS_ACCEL1", + "MS_ACCEL2", + ], + )), + Box::new(PickerInternationalGroup::new()), + ]) + } +} diff --git a/src/picker/picker_group.rs b/src/picker/group_box/group/basic_group.rs similarity index 62% rename from src/picker/picker_group.rs rename to src/picker/group_box/group/basic_group.rs index 17e7544a..3b9ec656 100644 --- a/src/picker/picker_group.rs +++ b/src/picker/group_box/group/basic_group.rs @@ -1,17 +1,21 @@ use cascade::cascade; use gtk::{pango, prelude::*}; -use super::PickerKey; +use super::{PickerGroup, PickerKey}; -pub(super) struct PickerGroup { - /// Name of keys in this group +trait Group { + fn keys(&self) -> &[PickerKey]; + fn widget(&self) -> >k::Widget; +} + +pub struct PickerBasicGroup { keys: Vec, - pub vbox: gtk::Box, + vbox: gtk::Box, flow_box: gtk::FlowBox, } -impl PickerGroup { - pub fn new(name: String, cols: u32) -> Self { +impl PickerBasicGroup { + pub fn new(name: String, cols: u32, width: i32, key_names: &[&str]) -> Self { let label = cascade! { gtk::Label::new(Some(&name)); ..set_attributes(Some(&cascade! { @@ -40,23 +44,32 @@ impl PickerGroup { ..add(&flow_box); }; + let keys: Vec<_> = key_names + .iter() + .map(|name| PickerKey::new(name, width)) + .collect(); + for key in &keys { + flow_box.add(key); + } + Self { - keys: Vec::new(), + keys, vbox, flow_box, } } +} - pub fn add_key(&mut self, key: PickerKey) { - self.flow_box.add(&key); - self.keys.push(key); +impl PickerGroup for PickerBasicGroup { + fn keys(&self) -> &[PickerKey] { + &self.keys } - pub fn keys(&self) -> impl Iterator { - self.keys.iter().map(|k| k.as_ref()) + fn widget(&self) -> >k::Widget { + self.vbox.upcast_ref() } - pub fn invalidate_filter(&self) { + fn invalidate_filter(&self) { self.flow_box.invalidate_filter(); } } diff --git a/src/picker/group_box/group/international.rs b/src/picker/group_box/group/international.rs new file mode 100644 index 00000000..d7d1d8b6 --- /dev/null +++ b/src/picker/group_box/group/international.rs @@ -0,0 +1,84 @@ +// International section is displayed in non-standard way: two colums, +// with descriptions. + +use cascade::cascade; +use gtk::prelude::*; + +use super::{PickerGroup, PickerKey}; + +static INT_KEYS: &[(&str, &str)] = &[ + ("INT1", "JIS \\ and _"), + ("INT2", "JIS Katakana/Hiragana"), + ("INT3", "JIS JIS ¥ and |"), + ("INT4", "JIS Henkan"), + ("INT5", "JIS Muhenkan"), + ("INT6", "JIS Numpad ,"), + ("INT7", "International 7"), + ("INT8", "International 8"), + ("INT9", "International 9"), +]; +static LANG_KEYS: &[(&str, &str)] = &[ + ("LANG1", "Hangul/English"), + ("LANG2", "Hanja"), + ("LANG3", "JIS Katakana"), + ("LANG4", "JIS Hiragana"), + ("LANG5", "JIS Zenkaku/Hankaku"), + ("LANG6", "Language 6"), + ("LANG7", "Language 7"), + ("LANG8", "Language 8"), + ("LANG9", "Language 9"), +]; + +pub struct PickerInternationalGroup { + keys: Vec, + widget: gtk::Box, +} + +fn row(keys: &mut Vec, keycode: &str, description: &str) -> gtk::Box { + let key = PickerKey::new(keycode, 1); + keys.push(key.clone()); + cascade! { + gtk::Box::new(gtk::Orientation::Horizontal, 0); + ..add(&key); + ..add(>k::Label::new(Some(description))); + } +} + +// Consider how this scales +impl PickerInternationalGroup { + pub fn new() -> Self { + let mut keys = Vec::new(); + + let int_box = cascade! { + gtk::Box::new(gtk::Orientation::Vertical, 0); + }; + for (keycode, description) in INT_KEYS { + int_box.add(&row(&mut keys, keycode, description)); + } + + let lang_box = cascade! { + gtk::Box::new(gtk::Orientation::Vertical, 0); + }; + for (keycode, description) in LANG_KEYS { + lang_box.add(&row(&mut keys, keycode, description)); + } + + let widget = cascade! { + gtk::Box::new(gtk::Orientation::Horizontal, 0); + ..add(&int_box); + ..add(&lang_box); + }; + + Self { keys, widget } + } +} + +impl PickerGroup for PickerInternationalGroup { + fn keys(&self) -> &[PickerKey] { + &self.keys + } + + fn widget(&self) -> >k::Widget { + self.widget.upcast_ref() + } +} diff --git a/src/picker/group_box/group/mod.rs b/src/picker/group_box/group/mod.rs new file mode 100644 index 00000000..52e57c6f --- /dev/null +++ b/src/picker/group_box/group/mod.rs @@ -0,0 +1,12 @@ +use super::super::PickerKey; + +mod basic_group; +pub use basic_group::PickerBasicGroup; +mod international; +pub use international::PickerInternationalGroup; + +pub trait PickerGroup { + fn keys(&self) -> &[PickerKey]; + fn widget(&self) -> >k::Widget; + fn invalidate_filter(&self) {} +} diff --git a/src/picker/picker_group_box.rs b/src/picker/group_box/mod.rs similarity index 79% rename from src/picker/picker_group_box.rs rename to src/picker/group_box/mod.rs index 15a6975e..88b02738 100644 --- a/src/picker/picker_group_box.rs +++ b/src/picker/group_box/mod.rs @@ -10,7 +10,12 @@ use std::{cell::RefCell, collections::HashMap}; use backend::{DerefCell, Keycode}; -use super::{picker_group::PickerGroup, picker_json::picker_json, picker_key::PickerKey}; +use super::picker_key::PickerKey; + +mod basics; +mod extras; +mod group; +use group::{PickerBasicGroup, PickerGroup, PickerInternationalGroup}; const DEFAULT_COLS: usize = 3; const HSPACING: i32 = 64; @@ -18,7 +23,7 @@ const VSPACING: i32 = 32; #[derive(Default)] pub struct PickerGroupBoxInner { - groups: DerefCell>, + groups: DerefCell>>, keys: DerefCell>, selected: RefCell>, } @@ -53,13 +58,17 @@ impl WidgetImpl for PickerGroupBoxInner { let minimum_width = self .groups .iter() - .map(|x| x.vbox.preferred_width().1) + .map(|x| x.widget().preferred_width().1) .max() .unwrap_or(0); let natural_width = self .groups .chunks(3) - .map(|row| row.iter().map(|x| x.vbox.preferred_width().1).sum::()) + .map(|row| { + row.iter() + .map(|x| x.widget().preferred_width().1) + .sum::() + }) .max() .unwrap_or(0) + 2 * HSPACING; @@ -72,7 +81,7 @@ impl WidgetImpl for PickerGroupBoxInner { .iter() .map(|row| { row.iter() - .map(|x| x.vbox.preferred_height().1) + .map(|x| x.widget().preferred_height().1) .max() .unwrap_or(0) }) @@ -90,7 +99,9 @@ impl WidgetImpl for PickerGroupBoxInner { let total_width = rows .iter() .map(|row| { - row.iter().map(|x| x.vbox.preferred_width().1).sum::() + row.iter() + .map(|x| x.widget().preferred_width().1) + .sum::() + (row.len() as i32 - 1) * HSPACING }) .max() @@ -100,16 +111,16 @@ impl WidgetImpl for PickerGroupBoxInner { for row in rows { let mut x = (allocation.width() - total_width) / 2; for group in row { - let height = group.vbox.preferred_height().1; - let width = group.vbox.preferred_width().1; + let height = group.widget().preferred_height().1; + let width = group.widget().preferred_width().1; group - .vbox + .widget() .size_allocate(>k::Allocation::new(x, y, width, height)); x += width + HSPACING; } y += row .iter() - .map(|x| x.vbox.preferred_height().1) + .map(|x| x.widget().preferred_height().1) .max() .unwrap() + VSPACING; @@ -145,7 +156,7 @@ impl ContainerImpl for PickerGroupBoxInner { cb: >k::subclass::container::Callback, ) { for group in self.groups.iter() { - cb.call(group.vbox.upcast_ref()); + cb.call(group.widget().upcast_ref()); } } @@ -160,33 +171,17 @@ glib::wrapper! { } impl PickerGroupBox { - pub fn new(section: &str) -> Self { + fn new(groups: Vec>) -> Self { let widget: Self = glib::Object::new(&[]).unwrap(); - let mut groups = Vec::new(); let mut keys = HashMap::new(); - for json_group in picker_json() { - if json_group.section != section { - continue; - } - - let mut group = PickerGroup::new(json_group.label, json_group.cols); - - for json_key in json_group.keys { - let keysym_label = super::SCANCODE_LABELS.get(&json_key).unwrap(); - let key = PickerKey::new(&json_key, keysym_label, json_group.width); - - group.add_key(key.clone()); - keys.insert(json_key, key); - } - - groups.push(group); - } - for group in &groups { - group.vbox.show(); - group.vbox.set_parent(&widget); + group.widget().show(); + group.widget().set_parent(&widget); + for key in group.keys() { + keys.insert(key.name().to_string(), key.clone()); + } } widget.inner().keys.set(keys); @@ -226,25 +221,25 @@ impl PickerGroupBox { } // XXX need to enable/disable features; show/hide just plain keycodes - pub(crate) fn set_key_visibility bool>(&self, f: F) { + pub fn set_key_visibility bool>(&self, f: F) { for group in self.inner().groups.iter() { - let group_visible = group.keys().fold(false, |group_visible, key| { + let group_visible = group.keys().iter().fold(false, |group_visible, key| { key.set_visible(f(&key.name())); group_visible || key.get_visible() }); - group.vbox.set_visible(group_visible); + group.widget().set_visible(group_visible); group.invalidate_filter(); } } - pub(crate) fn set_key_sensitivity bool>(&self, f: F) { + pub fn set_key_sensitivity bool>(&self, f: F) { for key in self.inner().keys.values() { key.set_sensitive(f(&key.name())); } } - pub(crate) fn set_selected(&self, scancode_names: Vec) { + pub fn set_selected(&self, scancode_names: Vec) { for button in self.inner().keys.values() { button.set_selected(false); } @@ -270,14 +265,14 @@ impl PickerGroupBox { *self.inner().selected.borrow_mut() = scancode_names; } - fn rows_for_width(&self, container_width: i32) -> Vec<&[PickerGroup]> { + fn rows_for_width(&self, container_width: i32) -> Vec<&[Box]> { let mut rows = Vec::new(); let groups = &*self.inner().groups; let mut row_start = 0; let mut row_width = 0; for (i, group) in groups.iter().enumerate() { - let width = group.vbox.preferred_width().1; + let width = group.widget().preferred_width().1; row_width += width; if i != 0 { diff --git a/src/picker/mod.rs b/src/picker/mod.rs index 9bb81567..b736db8e 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -15,14 +15,11 @@ use std::{ use crate::Keyboard; use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; -mod picker_group; -mod picker_group_box; -mod picker_json; +mod group_box; mod picker_key; mod tap_hold; -use picker_group_box::PickerGroupBox; -use picker_json::picker_json; +use group_box::PickerGroupBox; use picker_key::PickerKey; use tap_hold::TapHold; @@ -63,14 +60,14 @@ impl ObjectImpl for PickerInner { self.parent_constructed(picker); let basics_group_box = cascade! { - PickerGroupBox::new("basics"); + PickerGroupBox::basics(); ..connect_key_pressed(clone!(@weak picker => move |name, shift| { picker.key_pressed(name, shift) })); }; let extras_group_box = cascade! { - PickerGroupBox::new("extras"); + PickerGroupBox::extras(); ..connect_key_pressed(clone!(@weak picker => move |name, shift| { picker.key_pressed(name, shift) })); diff --git a/src/picker/picker_json.rs b/src/picker/picker_json.rs deleted file mode 100644 index 6c0ad780..00000000 --- a/src/picker/picker_json.rs +++ /dev/null @@ -1,25 +0,0 @@ -use serde::Deserialize; - -#[derive(Deserialize)] -pub struct PickerJsonGroup { - pub label: String, - pub section: String, - pub cols: u32, - pub width: i32, - pub keys: Vec, -} - -pub fn picker_json() -> Vec { - let picker_json = include_str!("../../layouts/picker.json"); - serde_json::from_str(picker_json).unwrap() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_picker_json() { - picker_json(); - } -} diff --git a/src/picker/picker_key.rs b/src/picker/picker_key.rs index f9f44aec..b7411324 100644 --- a/src/picker/picker_key.rs +++ b/src/picker/picker_key.rs @@ -56,10 +56,12 @@ glib::wrapper! { } impl PickerKey { - pub fn new(name: &str, text: &str, width: i32) -> Self { + pub fn new(name: &str, width: i32) -> Self { + let keysym_label = super::SCANCODE_LABELS.get(name).unwrap(); + let widget: Self = glib::Object::new(&[]).unwrap(); widget.inner().name.set(name.to_string()); - widget.inner().label.set_label(&text); + widget.inner().label.set_label(&keysym_label); widget.set_size_request(48 * width, 48); widget } diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs index 322aca4d..da81a796 100644 --- a/src/picker/tap_hold.rs +++ b/src/picker/tap_hold.rs @@ -8,7 +8,7 @@ use gtk::{ use once_cell::sync::Lazy; use std::cell::{Cell, RefCell}; -use super::{picker_group_box::PickerGroupBox, PickerKey, SCANCODE_LABELS}; +use super::{PickerGroupBox, PickerKey, SCANCODE_LABELS}; use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; #[derive(Clone, Copy, PartialEq)] @@ -69,13 +69,12 @@ impl ObjectImpl for TapHoldInner { self.parent_constructed(widget); let picker_group_box = cascade! { - PickerGroupBox::new("basics"); + PickerGroupBox::basics(); ..set_sensitive(false); ..connect_key_pressed(clone!(@weak widget => move |name, _shift| { *widget.inner().keycode.borrow_mut() = Some(name); widget.update(); })); - // Correct? ..set_key_visibility(|name| is_qmk_basic(name)); }; @@ -87,7 +86,7 @@ impl ObjectImpl for TapHoldInner { let label = SCANCODE_LABELS.get(*i).unwrap(); let mod_ = Mods::from_mod_str(*i).unwrap(); let button = cascade! { - PickerKey::new(i, label, 2); + PickerKey::new(i, 2); ..connect_clicked_with_shift(clone!(@weak widget => move |_, shift| { let mut new_mods = mod_; if shift { @@ -111,7 +110,7 @@ impl ObjectImpl for TapHoldInner { for (n, i) in LAYERS.iter().enumerate() { let label = SCANCODE_LABELS.get(*i).unwrap(); let button = cascade! { - PickerKey::new(i, label, 2); + PickerKey::new(i, 2); ..connect_clicked(clone!(@weak widget => move |_| { widget.inner().hold.set(Hold::Layer(n as u8)); widget.update(); @@ -193,7 +192,7 @@ impl TapHold { }) } - pub(crate) fn set_selected(&self, scancode_names: Vec) { + pub fn set_selected(&self, scancode_names: Vec) { // XXX how to handle > 1? let (mods, layer, keycode) = if scancode_names.len() == 1 { match scancode_names.into_iter().next().unwrap() { From 8ff39cf8a5697a8a11db62b71344ffed22f285bb Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 21 Sep 2022 17:52:58 -0700 Subject: [PATCH 05/13] Show basic keycodes in picker in ANSI layout --- layouts/keysym/base.json | 2 +- layouts/keysym/en_us.json | 2 +- src/picker/group_box/basics.rs | 86 ++---------- src/picker/group_box/extras.rs | 6 +- src/picker/group_box/group/ansi.rs | 138 ++++++++++++++++++++ src/picker/group_box/group/basic_group.rs | 2 +- src/picker/group_box/group/international.rs | 2 +- src/picker/group_box/group/mod.rs | 2 + src/picker/group_box/mod.rs | 3 +- src/picker/picker_key.rs | 4 +- src/picker/tap_hold.rs | 8 +- 11 files changed, 167 insertions(+), 88 deletions(-) create mode 100644 src/picker/group_box/group/ansi.rs diff --git a/layouts/keysym/base.json b/layouts/keysym/base.json index 442540ab..b53eb6e5 100644 --- a/layouts/keysym/base.json +++ b/layouts/keysym/base.json @@ -31,7 +31,7 @@ "PGUP": "PgUp", "PGDN": "PgDn", "END": "End", - "RESET": "Reset", + "RESET": "Reset Firmware", "ROLL_OVER": "Reuse", "NONE": "None", "MUTE": "Mute", diff --git a/layouts/keysym/en_us.json b/layouts/keysym/en_us.json index b46fcc1f..70c77d9a 100644 --- a/layouts/keysym/en_us.json +++ b/layouts/keysym/en_us.json @@ -52,7 +52,7 @@ "APP": "Menu", "ESC": "Esc", "PRINT_SCREEN": "PrtSc\nSysrq", - "INSERT": "Ins", + "INSERT": "Insert", "SCROLL_LOCK": "Scroll Lock", "PAUSE": "Pause\nBreak", "NUM_LOCK": "Num Lock", diff --git a/src/picker/group_box/basics.rs b/src/picker/group_box/basics.rs index fca8508b..3d7eef89 100644 --- a/src/picker/group_box/basics.rs +++ b/src/picker/group_box/basics.rs @@ -1,53 +1,16 @@ -use super::{PickerBasicGroup, PickerGroup, PickerGroupBox, PickerKey}; +use super::{PickerAnsiGroup, PickerBasicGroup, PickerGroupBox}; impl PickerGroupBox { pub fn basics() -> Self { Self::new(vec![ + Box::new(PickerAnsiGroup::new()), Box::new(PickerBasicGroup::new( - "Alphabet keys".to_string(), - 9, - 1, - &[ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", - "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - ], - )), - Box::new(PickerBasicGroup::new( - "Number keys".to_string(), + "Other Actions".to_string(), 4, - 1, - &["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], - )), - Box::new(PickerBasicGroup::new( - "Modifier keys".to_string(), - 4, - 1, + 1.5, &[ - "LEFT_ALT", - "LEFT_CTRL", - "LEFT_SHIFT", - "LEFT_SUPER", - "RIGHT_ALT", - "RIGHT_CTRL", - "RIGHT_SHIFT", - "RIGHT_SUPER", - ], - )), - Box::new(PickerBasicGroup::new( - "Actions".to_string(), - 4, - 1, - &[ - "ENTER", - "BKSP", - "DEL", - "TAB", - "SPACE", - "CAPS", - "APP", - "ESC", - "PRINT_SCREEN", "INSERT", + "PRINT_SCREEN", "SCROLL_LOCK", "PAUSE", "RESET", @@ -55,18 +18,11 @@ impl PickerGroupBox { "NONE", ], )), - Box::new(PickerBasicGroup::new( - "Function keys".to_string(), - 4, - 1, - &[ - "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", - ], - )), + // TODO numpad Box::new(PickerBasicGroup::new( "Numpad".to_string(), 6, - 1, + 1.0, &[ "NUM_LOCK", "NUM_7", @@ -90,33 +46,19 @@ impl PickerGroupBox { Box::new(PickerBasicGroup::new( "Symbols".to_string(), 6, - 1, - &[ - "TICK", - "QUOTE", - "SEMICOLON", - "MINUS", - "EQUALS", - "SLASH", - "COMMA", - "PERIOD", - "BACKSLASH", - "BRACE_OPEN", - "BRACE_CLOSE", - "NONUS_HASH", - "NONUS_BSLASH", - ], + 1.0, + &["NONUS_HASH", "NONUS_BSLASH"], )), Box::new(PickerBasicGroup::new( "Navigation".to_string(), 4, - 1, + 1.0, &["LEFT", "UP", "DOWN", "RIGHT", "HOME", "PGUP", "PGDN", "END"], )), Box::new(PickerBasicGroup::new( "Media".to_string(), 3, - 1, + 1.0, &[ "MUTE", "VOLUME_UP", @@ -129,7 +71,7 @@ impl PickerGroupBox { Box::new(PickerBasicGroup::new( "Controls".to_string(), 4, - 2, + 2.0, &[ "FAN_TOGGLE", "DISPLAY_TOGGLE", @@ -146,13 +88,13 @@ impl PickerGroupBox { Box::new(PickerBasicGroup::new( "LED controls".to_string(), 4, - 1, + 1.0, &["KBD_TOGGLE", "KBD_UP", "KBD_DOWN", "KBD_BKL", "KBD_COLOR"], )), Box::new(PickerBasicGroup::new( "Layer keys".to_string(), 4, - 2, + 2.0, &[ "LAYER_ACCESS_1", "FN", diff --git a/src/picker/group_box/extras.rs b/src/picker/group_box/extras.rs index f44906d5..e35c9a68 100644 --- a/src/picker/group_box/extras.rs +++ b/src/picker/group_box/extras.rs @@ -1,4 +1,4 @@ -use super::{PickerBasicGroup, PickerGroup, PickerGroupBox, PickerInternationalGroup, PickerKey}; +use super::{PickerBasicGroup, PickerGroupBox, PickerInternationalGroup}; impl PickerGroupBox { pub fn extras() -> Self { @@ -6,7 +6,7 @@ impl PickerGroupBox { Box::new(PickerBasicGroup::new( "Additional Function Keys".to_string(), 6, - 1, + 1.0, &[ "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", @@ -15,7 +15,7 @@ impl PickerGroupBox { Box::new(PickerBasicGroup::new( "Mouse Actions".to_string(), 5, - 2, + 2.0, &[ "MS_UP", "MS_DOWN", diff --git a/src/picker/group_box/group/ansi.rs b/src/picker/group_box/group/ansi.rs new file mode 100644 index 00000000..b9e75122 --- /dev/null +++ b/src/picker/group_box/group/ansi.rs @@ -0,0 +1,138 @@ +use gtk::prelude::*; + +use super::{PickerGroup, PickerKey}; + +static KEY_WIDTHS: &[(f64, &[&str])] = &[ + ( + 1.5, + &[ + "DEL", + "BKSP", + "TAB", + "CAPS", + "LEFT_CTRL", + "LEFT_ALT", + "LEFT_SUPER", + "RIGHT_SUPER", + "RIGHT_CTRL", + ], + ), + (2.0, &["LEFT_SHIFT", "RIGHT_SHIFT", "ENTER"]), + (4.5, &["SPACE"]), +]; + +// TODO complete; widths? +static ROWS: &[&[&str]] = &[ + &[ + "ESC", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "DEL", + ], + &[ + "TICK", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "MINUS", "EQUALS", "BKSP", + ], + &[ + "TAB", + "Q", + "W", + "E", + "R", + "T", + "Y", + "U", + "I", + "O", + "P", + "BRACE_OPEN", + "BRACE_CLOSE", + "BACKSLASH", + ], + &[ + "CAPS", + "A", + "S", + "D", + "F", + "G", + "H", + "J", + "K", + "L", + "SEMICOLON", + "QUOTE", + "ENTER", + ], + &[ + "LEFT_SHIFT", + "Z", + "X", + "C", + "V", + "B", + "N", + "M", + "COMMA", + "PERIOD", + "SLASH", + "RIGHT_SHIFT", + ], + &[ + "LEFT_CTRL", + "LEFT_ALT", + "LEFT_SUPER", + "SPACE", + "RIGHT_SUPER", + "RIGHT_ALT", + "APP", + "RIGHT_CTRL", + ], +]; + +pub struct PickerAnsiGroup { + keys: Vec, + widget: gtk::Fixed, +} + +impl PickerAnsiGroup { + pub fn new() -> Self { + let mut keys = Vec::new(); + let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0); + + let fixed = gtk::Fixed::new(); + + let mut y = 0; + for row in ROWS { + let mut x = 0; + for name in *row { + let width = KEY_WIDTHS + .iter() + .find_map(|(width, keys)| { + if keys.contains(name) { + Some(*width) + } else { + None + } + }) + .unwrap_or(1.0); + let key = PickerKey::new(name, width); + fixed.put(&key, x, y); + keys.push(key); + x += (48.0 * width) as i32 + } + y += 48; + } + + PickerAnsiGroup { + keys, + widget: fixed, + } + } +} + +impl PickerGroup for PickerAnsiGroup { + fn keys(&self) -> &[PickerKey] { + &self.keys + } + + fn widget(&self) -> >k::Widget { + self.widget.upcast_ref() + } +} diff --git a/src/picker/group_box/group/basic_group.rs b/src/picker/group_box/group/basic_group.rs index 3b9ec656..80077dc5 100644 --- a/src/picker/group_box/group/basic_group.rs +++ b/src/picker/group_box/group/basic_group.rs @@ -15,7 +15,7 @@ pub struct PickerBasicGroup { } impl PickerBasicGroup { - pub fn new(name: String, cols: u32, width: i32, key_names: &[&str]) -> Self { + pub fn new(name: String, cols: u32, width: f64, key_names: &[&str]) -> Self { let label = cascade! { gtk::Label::new(Some(&name)); ..set_attributes(Some(&cascade! { diff --git a/src/picker/group_box/group/international.rs b/src/picker/group_box/group/international.rs index d7d1d8b6..8df8272c 100644 --- a/src/picker/group_box/group/international.rs +++ b/src/picker/group_box/group/international.rs @@ -35,7 +35,7 @@ pub struct PickerInternationalGroup { } fn row(keys: &mut Vec, keycode: &str, description: &str) -> gtk::Box { - let key = PickerKey::new(keycode, 1); + let key = PickerKey::new(keycode, 1.0); keys.push(key.clone()); cascade! { gtk::Box::new(gtk::Orientation::Horizontal, 0); diff --git a/src/picker/group_box/group/mod.rs b/src/picker/group_box/group/mod.rs index 52e57c6f..fdd2efe8 100644 --- a/src/picker/group_box/group/mod.rs +++ b/src/picker/group_box/group/mod.rs @@ -1,5 +1,7 @@ use super::super::PickerKey; +mod ansi; +pub use ansi::PickerAnsiGroup; mod basic_group; pub use basic_group::PickerBasicGroup; mod international; diff --git a/src/picker/group_box/mod.rs b/src/picker/group_box/mod.rs index 88b02738..5d41b743 100644 --- a/src/picker/group_box/mod.rs +++ b/src/picker/group_box/mod.rs @@ -1,4 +1,3 @@ -use cascade::cascade; use gtk::{ gdk, glib::{self, clone, subclass::Signal, SignalHandlerId}, @@ -15,7 +14,7 @@ use super::picker_key::PickerKey; mod basics; mod extras; mod group; -use group::{PickerBasicGroup, PickerGroup, PickerInternationalGroup}; +use group::*; const DEFAULT_COLS: usize = 3; const HSPACING: i32 = 64; diff --git a/src/picker/picker_key.rs b/src/picker/picker_key.rs index b7411324..611b25b2 100644 --- a/src/picker/picker_key.rs +++ b/src/picker/picker_key.rs @@ -56,13 +56,13 @@ glib::wrapper! { } impl PickerKey { - pub fn new(name: &str, width: i32) -> Self { + pub fn new(name: &str, width: f64) -> Self { let keysym_label = super::SCANCODE_LABELS.get(name).unwrap(); let widget: Self = glib::Object::new(&[]).unwrap(); widget.inner().name.set(name.to_string()); widget.inner().label.set_label(&keysym_label); - widget.set_size_request(48 * width, 48); + widget.set_size_request((48.0 * width) as i32, 48); widget } diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs index da81a796..1209a9bf 100644 --- a/src/picker/tap_hold.rs +++ b/src/picker/tap_hold.rs @@ -8,7 +8,7 @@ use gtk::{ use once_cell::sync::Lazy; use std::cell::{Cell, RefCell}; -use super::{PickerGroupBox, PickerKey, SCANCODE_LABELS}; +use super::{PickerGroupBox, PickerKey}; use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; #[derive(Clone, Copy, PartialEq)] @@ -83,10 +83,9 @@ impl ObjectImpl for TapHoldInner { }; let mut mod_buttons = Vec::new(); for i in MODIFIERS { - let label = SCANCODE_LABELS.get(*i).unwrap(); let mod_ = Mods::from_mod_str(*i).unwrap(); let button = cascade! { - PickerKey::new(i, 2); + PickerKey::new(i, 2.0); ..connect_clicked_with_shift(clone!(@weak widget => move |_, shift| { let mut new_mods = mod_; if shift { @@ -108,9 +107,8 @@ impl ObjectImpl for TapHoldInner { }; let mut layer_buttons = Vec::new(); for (n, i) in LAYERS.iter().enumerate() { - let label = SCANCODE_LABELS.get(*i).unwrap(); let button = cascade! { - PickerKey::new(i, 2); + PickerKey::new(i, 2.0); ..connect_clicked(clone!(@weak widget => move |_| { widget.inner().hold.set(Hold::Layer(n as u8)); widget.update(); From c6b646f76170dfb17431a503f8f78a07e5d4db68 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Sat, 24 Sep 2022 09:25:33 -0700 Subject: [PATCH 06/13] WIP style --- src/picker/group_box/group/ansi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/picker/group_box/group/ansi.rs b/src/picker/group_box/group/ansi.rs index b9e75122..95d8235e 100644 --- a/src/picker/group_box/group/ansi.rs +++ b/src/picker/group_box/group/ansi.rs @@ -115,9 +115,9 @@ impl PickerAnsiGroup { let key = PickerKey::new(name, width); fixed.put(&key, x, y); keys.push(key); - x += (48.0 * width) as i32 + x += (48.0 * width) as i32 + 4 } - y += 48; + y += 48 + 4; } PickerAnsiGroup { From d98e4e87cf7f15834145d86175c6b08fb70a8095 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 12 Oct 2022 14:12:52 -0700 Subject: [PATCH 07/13] Update `PickerGroupBox` to only allocate space it uses Now it is centered using `halign`. This can now be better positioned relative to other widgets. --- src/picker/group_box/mod.rs | 82 ++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/src/picker/group_box/mod.rs b/src/picker/group_box/mod.rs index 5d41b743..e9b184ca 100644 --- a/src/picker/group_box/mod.rs +++ b/src/picker/group_box/mod.rs @@ -46,6 +46,11 @@ impl ObjectImpl for PickerGroupBoxInner { }); SIGNALS.as_ref() } + + fn constructed(&self, widget: &Self::Type) { + self.parent_constructed(widget); + widget.set_halign(gtk::Align::Center); + } } impl WidgetImpl for PickerGroupBoxInner { @@ -76,39 +81,46 @@ impl WidgetImpl for PickerGroupBoxInner { fn preferred_height_for_width(&self, widget: &Self::Type, width: i32) -> (i32, i32) { let rows = widget.rows_for_width(width); - let height = rows - .iter() - .map(|row| { - row.iter() - .map(|x| x.widget().preferred_height().1) - .max() - .unwrap_or(0) - }) - .sum::() - + (rows.len() as i32 - 1) * VSPACING; - + let height = total_height_for_rows(&rows); (height, height) } + fn adjust_size_allocation( + &self, + obj: &Self::Type, + orientation: gtk::Orientation, + minimum_size: &mut i32, + natural_size: &mut i32, + allocated_pos: &mut i32, + allocated_size: &mut i32, + ) { + // For centering to work, adjust natural width to be the portion of + // allocated width that will actually be used after reflowing + // children. + if orientation == gtk::Orientation::Horizontal { + let rows = obj.rows_for_width(*allocated_size); + let total_width = max_width_for_rows(&rows); + *natural_size = (*natural_size).min(total_width); + } + + self.parent_adjust_size_allocation( + obj, + orientation, + minimum_size, + natural_size, + allocated_pos, + allocated_size, + ); + } + fn size_allocate(&self, obj: &Self::Type, allocation: >k::Allocation) { self.parent_size_allocate(obj, allocation); let rows = obj.rows_for_width(allocation.width()); - let total_width = rows - .iter() - .map(|row| { - row.iter() - .map(|x| x.widget().preferred_width().1) - .sum::() - + (row.len() as i32 - 1) * HSPACING - }) - .max() - .unwrap_or(0); - let mut y = 0; for row in rows { - let mut x = (allocation.width() - total_width) / 2; + let mut x = 0; for group in row { let height = group.widget().preferred_height().1; let width = group.widget().preferred_width().1; @@ -291,3 +303,27 @@ impl PickerGroupBox { rows } } + +fn max_width_for_rows(rows: &[&[Box]]) -> i32 { + rows.iter() + .map(|row| { + row.iter() + .map(|x| x.widget().preferred_width().1) + .sum::() + + (row.len() as i32 - 1) * HSPACING + }) + .max() + .unwrap_or(0) +} + +fn total_height_for_rows(rows: &[&[Box]]) -> i32 { + rows.iter() + .map(|row| { + row.iter() + .map(|x| x.widget().preferred_height().1) + .max() + .unwrap_or(0) + }) + .sum::() + + (rows.len() as i32 - 1) * VSPACING +} From 71de47de0f6fe39eb6d23b40948b06e538aafa56 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 17 Oct 2022 09:34:02 -0700 Subject: [PATCH 08/13] Set `PickerGroupBox` width to largest group, center `Picker` This more or less works. Scaling in increments doesn't seem to be possible, so centering is really only possible for the group box alone, or with a limit to width. --- src/picker/group_box/mod.rs | 48 ++----------------------------------- src/picker/mod.rs | 1 + 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/src/picker/group_box/mod.rs b/src/picker/group_box/mod.rs index e9b184ca..b6cdf66e 100644 --- a/src/picker/group_box/mod.rs +++ b/src/picker/group_box/mod.rs @@ -46,11 +46,6 @@ impl ObjectImpl for PickerGroupBoxInner { }); SIGNALS.as_ref() } - - fn constructed(&self, widget: &Self::Type) { - self.parent_constructed(widget); - widget.set_halign(gtk::Align::Center); - } } impl WidgetImpl for PickerGroupBoxInner { @@ -59,24 +54,13 @@ impl WidgetImpl for PickerGroupBoxInner { } fn preferred_width(&self, _widget: &Self::Type) -> (i32, i32) { - let minimum_width = self + let width = self .groups .iter() .map(|x| x.widget().preferred_width().1) .max() .unwrap_or(0); - let natural_width = self - .groups - .chunks(3) - .map(|row| { - row.iter() - .map(|x| x.widget().preferred_width().1) - .sum::() - }) - .max() - .unwrap_or(0) - + 2 * HSPACING; - (minimum_width, natural_width) + (width, width) } fn preferred_height_for_width(&self, widget: &Self::Type, width: i32) -> (i32, i32) { @@ -85,34 +69,6 @@ impl WidgetImpl for PickerGroupBoxInner { (height, height) } - fn adjust_size_allocation( - &self, - obj: &Self::Type, - orientation: gtk::Orientation, - minimum_size: &mut i32, - natural_size: &mut i32, - allocated_pos: &mut i32, - allocated_size: &mut i32, - ) { - // For centering to work, adjust natural width to be the portion of - // allocated width that will actually be used after reflowing - // children. - if orientation == gtk::Orientation::Horizontal { - let rows = obj.rows_for_width(*allocated_size); - let total_width = max_width_for_rows(&rows); - *natural_size = (*natural_size).min(total_width); - } - - self.parent_adjust_size_allocation( - obj, - orientation, - minimum_size, - natural_size, - allocated_pos, - allocated_size, - ); - } - fn size_allocate(&self, obj: &Self::Type, allocation: >k::Allocation) { self.parent_size_allocate(obj, allocation); diff --git a/src/picker/mod.rs b/src/picker/mod.rs index b736db8e..fae2f61c 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -96,6 +96,7 @@ impl ObjectImpl for PickerInner { cascade! { picker; ..set_orientation(gtk::Orientation::Vertical); + ..set_halign(gtk::Align::Center); ..add(&stack_switcher); ..add(&stack); ..show_all(); From 8871dbeb62731a0fa202edb3c2e556394936fef0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 17 Oct 2022 09:56:21 -0700 Subject: [PATCH 09/13] Use `PickerGroupBox` for modifiers in tap-hold --- src/picker/group_box/group/ansi.rs | 2 +- src/picker/group_box/group/international.rs | 2 +- src/picker/group_box/mod.rs | 4 +- src/picker/mod.rs | 1 + src/picker/tap_hold.rs | 128 ++++++++++---------- 5 files changed, 66 insertions(+), 71 deletions(-) diff --git a/src/picker/group_box/group/ansi.rs b/src/picker/group_box/group/ansi.rs index 95d8235e..08822982 100644 --- a/src/picker/group_box/group/ansi.rs +++ b/src/picker/group_box/group/ansi.rs @@ -2,6 +2,7 @@ use gtk::prelude::*; use super::{PickerGroup, PickerKey}; +// TODO: somehow acount for spacing in widths? static KEY_WIDTHS: &[(f64, &[&str])] = &[ ( 1.5, @@ -21,7 +22,6 @@ static KEY_WIDTHS: &[(f64, &[&str])] = &[ (4.5, &["SPACE"]), ]; -// TODO complete; widths? static ROWS: &[&[&str]] = &[ &[ "ESC", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "DEL", diff --git a/src/picker/group_box/group/international.rs b/src/picker/group_box/group/international.rs index 8df8272c..e3e3e693 100644 --- a/src/picker/group_box/group/international.rs +++ b/src/picker/group_box/group/international.rs @@ -38,7 +38,7 @@ fn row(keys: &mut Vec, keycode: &str, description: &str) -> gtk::Box let key = PickerKey::new(keycode, 1.0); keys.push(key.clone()); cascade! { - gtk::Box::new(gtk::Orientation::Horizontal, 0); + gtk::Box::new(gtk::Orientation::Horizontal, 8); ..add(&key); ..add(>k::Label::new(Some(description))); } diff --git a/src/picker/group_box/mod.rs b/src/picker/group_box/mod.rs index b6cdf66e..ce502120 100644 --- a/src/picker/group_box/mod.rs +++ b/src/picker/group_box/mod.rs @@ -14,7 +14,7 @@ use super::picker_key::PickerKey; mod basics; mod extras; mod group; -use group::*; +pub use group::*; const DEFAULT_COLS: usize = 3; const HSPACING: i32 = 64; @@ -138,7 +138,7 @@ glib::wrapper! { } impl PickerGroupBox { - fn new(groups: Vec>) -> Self { + pub fn new(groups: Vec>) -> Self { let widget: Self = glib::Object::new(&[]).unwrap(); let mut keys = HashMap::new(); diff --git a/src/picker/mod.rs b/src/picker/mod.rs index fae2f61c..3d6d31ae 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -95,6 +95,7 @@ impl ObjectImpl for PickerInner { cascade! { picker; + ..set_spacing(8); ..set_orientation(gtk::Orientation::Vertical); ..set_halign(gtk::Align::Center); ..add(&stack_switcher); diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs index 1209a9bf..91f27437 100644 --- a/src/picker/tap_hold.rs +++ b/src/picker/tap_hold.rs @@ -8,7 +8,7 @@ use gtk::{ use once_cell::sync::Lazy; use std::cell::{Cell, RefCell}; -use super::{PickerGroupBox, PickerKey}; +use super::{group_box::PickerBasicGroup, PickerGroupBox, PickerKey}; use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; #[derive(Clone, Copy, PartialEq)] @@ -40,8 +40,7 @@ pub struct TapHoldInner { shift: Cell, hold: Cell, keycode: RefCell>, - mod_buttons: DerefCell>, - layer_buttons: DerefCell>, + hold_group_box: DerefCell, picker_group_box: DerefCell, } @@ -78,52 +77,42 @@ impl ObjectImpl for TapHoldInner { ..set_key_visibility(|name| is_qmk_basic(name)); }; - let modifier_button_box = cascade! { - gtk::Box::new(gtk::Orientation::Horizontal, 0); - }; - let mut mod_buttons = Vec::new(); - for i in MODIFIERS { - let mod_ = Mods::from_mod_str(*i).unwrap(); - let button = cascade! { - PickerKey::new(i, 2.0); - ..connect_clicked_with_shift(clone!(@weak widget => move |_, shift| { + let hold_group_box = cascade! { + PickerGroupBox::new(vec![ + Box::new(PickerBasicGroup::new( + "Standard Modifiers".to_string(), + 4, + 1.5, + MODIFIERS, + )), + Box::new(PickerBasicGroup::new( + "Access Layer Modifiers".to_string(), + 4, + 1.5, + LAYERS, + )), + ]); + ..connect_key_pressed(clone!(@weak widget => move |name, shift| { + let new_hold = if let Some(mod_) = Mods::from_mod_str(&name) { let mut new_mods = mod_; if shift { if let Hold::Mods(mods) = widget.inner().hold.get() { new_mods = mods.toggle_mod(mod_); } } - widget.inner().hold.set(Hold::Mods(new_mods)); - widget.update(); - })); - }; - modifier_button_box.add(&button); - mod_buttons.push(button); - } - self.mod_buttons.set(mod_buttons); - - let layer_button_box = cascade! { - gtk::Box::new(gtk::Orientation::Horizontal, 0); + Hold::Mods(new_mods) + } else { + let n = LAYERS.iter().position(|x| *x == &name).unwrap() as u8; + Hold::Layer(n) + }; + widget.inner().hold.set(new_hold); + widget.update(); + })); }; - let mut layer_buttons = Vec::new(); - for (n, i) in LAYERS.iter().enumerate() { - let button = cascade! { - PickerKey::new(i, 2.0); - ..connect_clicked(clone!(@weak widget => move |_| { - widget.inner().hold.set(Hold::Layer(n as u8)); - widget.update(); - - })); - }; - layer_button_box.add(&button); - layer_buttons.push(button); - } - self.layer_buttons.set(layer_buttons); - - // TODO: select monifier/layer; multiple select; when both are selected, set keycode cascade! { widget; + ..set_spacing(8); ..set_orientation(gtk::Orientation::Vertical); ..add(&cascade! { gtk::Label::new(Some("1. Select action(s) to use when the key is held.")); @@ -133,8 +122,9 @@ impl ObjectImpl for TapHoldInner { })); ..set_halign(gtk::Align::Start); }); - ..add(&modifier_button_box); - ..add(&layer_button_box); + // TODO label groups? Use group box? + ..add(&hold_group_box); + // TODO shift click label ..add(&cascade! { gtk::Label::new(Some("2. Select an action to use when the key is tapped.")); ..set_attributes(Some(&cascade! { @@ -146,6 +136,7 @@ impl ObjectImpl for TapHoldInner { ..add(&picker_group_box); }; + self.hold_group_box.set(hold_group_box); self.picker_group_box.set(picker_group_box); } } @@ -202,16 +193,20 @@ impl TapHold { Default::default() }; - for i in self.inner().mod_buttons.iter() { - let mod_ = Mods::from_mod_str(i.name()).unwrap(); - i.set_selected( - mods.contains(mod_) && (mods.contains(Mods::RIGHT) == mod_.contains(Mods::RIGHT)), - ); + let mut selected_hold = Vec::new(); + for i in MODIFIERS { + let mod_ = Mods::from_mod_str(i).unwrap(); + if mods.contains(mod_) && (mods.contains(Mods::RIGHT) == mod_.contains(Mods::RIGHT)) { + selected_hold.push(Keycode::Basic(mod_, "NONE".to_string())); + } } - - for (n, i) in self.inner().layer_buttons.iter().enumerate() { - i.set_selected(Some(n as u8) == layer); + if let Some(layer) = layer { + selected_hold.push(Keycode::Basic( + Mods::empty(), + LAYERS[layer as usize].to_string(), + )); } + self.inner().hold_group_box.set_selected(selected_hold); if let Some(keycode) = keycode.clone() { self.inner() @@ -242,27 +237,26 @@ impl TapHold { let hold_empty = hold == Hold::Mods(Mods::empty()); let keycode = self.inner().keycode.borrow(); - for button in self.inner().layer_buttons.iter() { - button.set_sensitive(if shift { - hold == Hold::Mods(Mods::empty()) - } else { - true - }); - } - - for button in self.inner().mod_buttons.iter() { - button.set_sensitive(if shift { - match hold { - Hold::Mods(mods) => { - let right = button.name().starts_with("RIGHT"); - mods.is_empty() || (right == mods.contains(Mods::RIGHT)) + self.inner().hold_group_box.set_key_sensitivity(|name| { + let left_mod = name.starts_with("LEFT_"); + let right_mod = name.starts_with("RIGHT_"); + // Modifer + if left_mod || right_mod { + if shift { + match hold { + Hold::Mods(mods) => { + mods.is_empty() || (right_mod == mods.contains(Mods::RIGHT)) + } + Hold::Layer(_) => false, } - Hold::Layer(_) => false, + } else { + true } + // Layer } else { - true - }); - } + !shift || (hold == Hold::Mods(Mods::empty())) + } + }); self.inner().picker_group_box.set_sensitive(if shift { !hold_empty && keycode.is_none() From c08adc4410f7f8d502ae584c94b12bd8bd38b554 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 24 Oct 2022 15:34:53 -0700 Subject: [PATCH 10/13] Update various style details to be consistent with design --- src/keyboard.rs | 1 + src/picker/group_box/group/ansi.rs | 24 ++++++++++++++++-------- src/picker/mod.rs | 8 +++++++- src/picker/tap_hold.rs | 13 ++++++++----- src/style.css | 14 ++++++++++++++ 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index 452b2856..dd0153d3 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -211,6 +211,7 @@ impl Keyboard { ..add(&cascade! { gtk::Label::new(Some(&fl!("stack-keymap-desc"))); ..set_line_wrap(true); + ..set_justify(gtk::Justification::Center); ..set_max_width_chars(100); ..set_halign(gtk::Align::Center); }); diff --git a/src/picker/group_box/group/ansi.rs b/src/picker/group_box/group/ansi.rs index 08822982..ec5a078f 100644 --- a/src/picker/group_box/group/ansi.rs +++ b/src/picker/group_box/group/ansi.rs @@ -2,10 +2,15 @@ use gtk::prelude::*; use super::{PickerGroup, PickerKey}; -// TODO: somehow acount for spacing in widths? +const KEY_WIDTH: f64 = 48.0; +const KEY_SPACE: f64 = 4.0; + +// A 2U key takes same space as 2 1U including spacing +// 2 1.5U keys take same space as 3 1U +// Space bar is the same as 3 1U + 1 1.5U to line up with previous row static KEY_WIDTHS: &[(f64, &[&str])] = &[ ( - 1.5, + 1.5 * KEY_WIDTH + 0.5 * KEY_SPACE, &[ "DEL", "BKSP", @@ -18,8 +23,11 @@ static KEY_WIDTHS: &[(f64, &[&str])] = &[ "RIGHT_CTRL", ], ), - (2.0, &["LEFT_SHIFT", "RIGHT_SHIFT", "ENTER"]), - (4.5, &["SPACE"]), + ( + 2.0 * KEY_WIDTH + KEY_SPACE, + &["LEFT_SHIFT", "RIGHT_SHIFT", "ENTER"], + ), + (4.5 * KEY_WIDTH + 3.5 * KEY_SPACE, &["SPACE"]), ]; static ROWS: &[&[&str]] = &[ @@ -111,13 +119,13 @@ impl PickerAnsiGroup { None } }) - .unwrap_or(1.0); - let key = PickerKey::new(name, width); + .unwrap_or(KEY_WIDTH); + let key = PickerKey::new(name, width / KEY_WIDTH); fixed.put(&key, x, y); keys.push(key); - x += (48.0 * width) as i32 + 4 + x += width as i32 + 4 } - y += 48 + 4; + y += KEY_WIDTH as i32 + 4; } PickerAnsiGroup { diff --git a/src/picker/mod.rs b/src/picker/mod.rs index 3d6d31ae..cbffc206 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -90,6 +90,7 @@ impl ObjectImpl for PickerInner { let stack_switcher = cascade! { gtk::StackSwitcher::new(); + ..style_context().add_class("picker-stack-switcher"); ..set_stack(Some(&stack)); }; @@ -98,7 +99,12 @@ impl ObjectImpl for PickerInner { ..set_spacing(8); ..set_orientation(gtk::Orientation::Vertical); ..set_halign(gtk::Align::Center); - ..add(&stack_switcher); + ..add(&cascade! { + gtk::Box::new(gtk::Orientation::Vertical, 0); + ..add(>k::Separator::new(gtk::Orientation::Horizontal)); + ..add(&stack_switcher); + ..add(>k::Separator::new(gtk::Orientation::Horizontal)); + }); ..add(&stack); ..show_all(); }; diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs index 91f27437..da96d6e6 100644 --- a/src/picker/tap_hold.rs +++ b/src/picker/tap_hold.rs @@ -8,7 +8,7 @@ use gtk::{ use once_cell::sync::Lazy; use std::cell::{Cell, RefCell}; -use super::{group_box::PickerBasicGroup, PickerGroupBox, PickerKey}; +use super::{group_box::PickerBasicGroup, PickerGroupBox}; use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; #[derive(Clone, Copy, PartialEq)] @@ -80,13 +80,13 @@ impl ObjectImpl for TapHoldInner { let hold_group_box = cascade! { PickerGroupBox::new(vec![ Box::new(PickerBasicGroup::new( - "Standard Modifiers".to_string(), + "Modifiers".to_string(), 4, 1.5, MODIFIERS, )), Box::new(PickerBasicGroup::new( - "Access Layer Modifiers".to_string(), + "Layer Keys".to_string(), 4, 1.5, LAYERS, @@ -122,9 +122,12 @@ impl ObjectImpl for TapHoldInner { })); ..set_halign(gtk::Align::Start); }); - // TODO label groups? Use group box? ..add(&hold_group_box); - // TODO shift click label + ..add(&cascade! { + gtk::Label::new(Some("Shift + click to select multiple modifiers.")); + ..set_halign(gtk::Align::Start); + }); + // XXX grey? ..add(&cascade! { gtk::Label::new(Some("2. Select an action to use when the key is tapped.")); ..set_attributes(Some(&cascade! { diff --git a/src/style.css b/src/style.css index 927a475c..d8dafcfb 100644 --- a/src/style.css +++ b/src/style.css @@ -7,3 +7,17 @@ border-color: #fbb86c; border-width: 4px; } + +.picker-stack-switcher button { + border: none; + box-shadow: none; + background: none; +} + +.picker-stack-switcher button:checked { + border-bottom: 2px solid #fbb86c; +} + +.picker-stack-switcher button:selected { + background: black; +} From 358e265df8988622a91234aa6ee51593c5ff15d5 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 26 Oct 2022 11:33:02 -0700 Subject: [PATCH 11/13] Make some picker strings translatable --- i18n/en/system76_keyboard_configurator.ftl | 7 +++++++ src/picker/mod.rs | 8 ++++---- src/picker/tap_hold.rs | 7 ++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/i18n/en/system76_keyboard_configurator.ftl b/i18n/en/system76_keyboard_configurator.ftl index a3581c8e..8942c30a 100644 --- a/i18n/en/system76_keyboard_configurator.ftl +++ b/i18n/en/system76_keyboard_configurator.ftl @@ -85,3 +85,10 @@ test-replace-switch = Replace switch test-spurious-keypress = Spurious keypress untitled-layout = Untitled Layout + +picker-basics = Basics +picker-extras = Extras +picker-tap-hold = Tap-Hold +tap-hold-step1 = 1. Select action(s) to use when the key is held. +tap-hold-multiple-mod = Shift + click to select multiple modifiers. +tap-hold-step2 = 2. Select an action to use when the key is tapped. diff --git a/src/picker/mod.rs b/src/picker/mod.rs index cbffc206..5f8194c2 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -12,7 +12,7 @@ use std::{ collections::HashMap, }; -use crate::Keyboard; +use crate::{fl, Keyboard}; use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; mod group_box; @@ -83,9 +83,9 @@ impl ObjectImpl for PickerInner { // XXX translate let stack = cascade! { gtk::Stack::new(); - ..add_titled(&basics_group_box, "basics", "Basics"); - ..add_titled(&extras_group_box, "extras", "Extras"); - ..add_titled(&tap_hold, "tap-hold", "Tap-Hold"); + ..add_titled(&basics_group_box, "basics", &fl!("picker-basics")); + ..add_titled(&extras_group_box, "extras", &fl!("picker-extras")); + ..add_titled(&tap_hold, "tap-hold", &fl!("picker-tap-hold")); }; let stack_switcher = cascade! { diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs index da96d6e6..9a9d70dd 100644 --- a/src/picker/tap_hold.rs +++ b/src/picker/tap_hold.rs @@ -9,6 +9,7 @@ use once_cell::sync::Lazy; use std::cell::{Cell, RefCell}; use super::{group_box::PickerBasicGroup, PickerGroupBox}; +use crate::fl; use backend::{is_qmk_basic, DerefCell, Keycode, Mods}; #[derive(Clone, Copy, PartialEq)] @@ -115,7 +116,7 @@ impl ObjectImpl for TapHoldInner { ..set_spacing(8); ..set_orientation(gtk::Orientation::Vertical); ..add(&cascade! { - gtk::Label::new(Some("1. Select action(s) to use when the key is held.")); + gtk::Label::new(Some(&fl!("tap-hold-step1"))); ..set_attributes(Some(&cascade! { pango::AttrList::new(); ..insert(pango::AttrInt::new_weight(pango::Weight::Bold)); @@ -124,12 +125,12 @@ impl ObjectImpl for TapHoldInner { }); ..add(&hold_group_box); ..add(&cascade! { - gtk::Label::new(Some("Shift + click to select multiple modifiers.")); + gtk::Label::new(Some(&fl!("tap-hold-multiple-mod"))); ..set_halign(gtk::Align::Start); }); // XXX grey? ..add(&cascade! { - gtk::Label::new(Some("2. Select an action to use when the key is tapped.")); + gtk::Label::new(Some(&fl!("tap-hold-step2"))); ..set_attributes(Some(&cascade! { pango::AttrList::new(); ..insert(pango::AttrInt::new_weight(pango::Weight::Bold)); From df0e31df6206c2f7d013334c8314dca37645b226 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 16 Nov 2022 17:10:15 -0800 Subject: [PATCH 12/13] Picker style details; numpad layout in picker; shift+click text --- i18n/en/system76_keyboard_configurator.ftl | 1 + src/picker/group_box/basics.rs | 45 +++------ src/picker/group_box/extras.rs | 4 +- src/picker/group_box/group/ansi.rs | 71 +++----------- src/picker/group_box/group/basic_group.rs | 6 +- src/picker/group_box/group/international.rs | 2 +- src/picker/group_box/group/mod.rs | 5 +- src/picker/group_box/group/numpad.rs | 17 ++++ src/picker/group_box/group/variable_width.rs | 99 ++++++++++++++++++++ src/picker/group_box/mod.rs | 4 +- src/picker/mod.rs | 2 +- src/picker/picker_key.rs | 6 +- src/picker/tap_hold.rs | 20 ++-- 13 files changed, 171 insertions(+), 111 deletions(-) create mode 100644 src/picker/group_box/group/numpad.rs create mode 100644 src/picker/group_box/group/variable_width.rs diff --git a/i18n/en/system76_keyboard_configurator.ftl b/i18n/en/system76_keyboard_configurator.ftl index 8942c30a..f1af215a 100644 --- a/i18n/en/system76_keyboard_configurator.ftl +++ b/i18n/en/system76_keyboard_configurator.ftl @@ -89,6 +89,7 @@ untitled-layout = Untitled Layout picker-basics = Basics picker-extras = Extras picker-tap-hold = Tap-Hold +picker-shift-click = Shift + click to combine modifier(s) or to combine them with a keycode. tap-hold-step1 = 1. Select action(s) to use when the key is held. tap-hold-multiple-mod = Shift + click to select multiple modifiers. tap-hold-step2 = 2. Select an action to use when the key is tapped. diff --git a/src/picker/group_box/basics.rs b/src/picker/group_box/basics.rs index 3d7eef89..0493f46b 100644 --- a/src/picker/group_box/basics.rs +++ b/src/picker/group_box/basics.rs @@ -1,11 +1,11 @@ -use super::{PickerAnsiGroup, PickerBasicGroup, PickerGroupBox}; +use super::{picker_ansi_group, picker_numpad_group, PickerBasicGroup, PickerGroupBox}; impl PickerGroupBox { pub fn basics() -> Self { Self::new(vec![ - Box::new(PickerAnsiGroup::new()), + Box::new(picker_ansi_group()), Box::new(PickerBasicGroup::new( - "Other Actions".to_string(), + "Other actions", 4, 1.5, &[ @@ -18,45 +18,22 @@ impl PickerGroupBox { "NONE", ], )), - // TODO numpad + // TODO label? + Box::new(picker_numpad_group()), Box::new(PickerBasicGroup::new( - "Numpad".to_string(), - 6, - 1.0, - &[ - "NUM_LOCK", - "NUM_7", - "NUM_8", - "NUM_9", - "NUM_MINUS", - "NUM_PLUS", - "NUM_SLASH", - "NUM_4", - "NUM_5", - "NUM_6", - "NUM_ASTERISK", - "NUM_ENTER", - "NUM_0", - "NUM_1", - "NUM_2", - "NUM_3", - "NUM_PERIOD", - ], - )), - Box::new(PickerBasicGroup::new( - "Symbols".to_string(), + "Symbols", 6, 1.0, &["NONUS_HASH", "NONUS_BSLASH"], )), Box::new(PickerBasicGroup::new( - "Navigation".to_string(), + "Navigation", 4, 1.0, &["LEFT", "UP", "DOWN", "RIGHT", "HOME", "PGUP", "PGDN", "END"], )), Box::new(PickerBasicGroup::new( - "Media".to_string(), + "Media", 3, 1.0, &[ @@ -69,7 +46,7 @@ impl PickerGroupBox { ], )), Box::new(PickerBasicGroup::new( - "Controls".to_string(), + "Controls", 4, 2.0, &[ @@ -86,13 +63,13 @@ impl PickerGroupBox { ], )), Box::new(PickerBasicGroup::new( - "LED controls".to_string(), + "LED controls", 4, 1.0, &["KBD_TOGGLE", "KBD_UP", "KBD_DOWN", "KBD_BKL", "KBD_COLOR"], )), Box::new(PickerBasicGroup::new( - "Layer keys".to_string(), + "Layer keys", 4, 2.0, &[ diff --git a/src/picker/group_box/extras.rs b/src/picker/group_box/extras.rs index e35c9a68..eea9e1b8 100644 --- a/src/picker/group_box/extras.rs +++ b/src/picker/group_box/extras.rs @@ -4,7 +4,7 @@ impl PickerGroupBox { pub fn extras() -> Self { Self::new(vec![ Box::new(PickerBasicGroup::new( - "Additional Function Keys".to_string(), + "Additional Function Keys", 6, 1.0, &[ @@ -13,7 +13,7 @@ impl PickerGroupBox { ], )), Box::new(PickerBasicGroup::new( - "Mouse Actions".to_string(), + "Mouse Actions", 5, 2.0, &[ diff --git a/src/picker/group_box/group/ansi.rs b/src/picker/group_box/group/ansi.rs index ec5a078f..085e27a8 100644 --- a/src/picker/group_box/group/ansi.rs +++ b/src/picker/group_box/group/ansi.rs @@ -1,16 +1,12 @@ -use gtk::prelude::*; - -use super::{PickerGroup, PickerKey}; - -const KEY_WIDTH: f64 = 48.0; -const KEY_SPACE: f64 = 4.0; +use super::variable_width::{PickerVariableWidthGroup, KEY_SIZE, KEY_SPACE}; +use crate::fl; // A 2U key takes same space as 2 1U including spacing // 2 1.5U keys take same space as 3 1U // Space bar is the same as 3 1U + 1 1.5U to line up with previous row static KEY_WIDTHS: &[(f64, &[&str])] = &[ ( - 1.5 * KEY_WIDTH + 0.5 * KEY_SPACE, + 1.5 * KEY_SIZE + 0.5 * KEY_SPACE, &[ "DEL", "BKSP", @@ -24,10 +20,10 @@ static KEY_WIDTHS: &[(f64, &[&str])] = &[ ], ), ( - 2.0 * KEY_WIDTH + KEY_SPACE, + 2.0 * KEY_SIZE + KEY_SPACE, &["LEFT_SHIFT", "RIGHT_SHIFT", "ENTER"], ), - (4.5 * KEY_WIDTH + 3.5 * KEY_SPACE, &["SPACE"]), + (4.5 * KEY_SIZE + 3.5 * KEY_SPACE, &["SPACE"]), ]; static ROWS: &[&[&str]] = &[ @@ -94,53 +90,12 @@ static ROWS: &[&[&str]] = &[ ], ]; -pub struct PickerAnsiGroup { - keys: Vec, - widget: gtk::Fixed, -} - -impl PickerAnsiGroup { - pub fn new() -> Self { - let mut keys = Vec::new(); - let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0); - - let fixed = gtk::Fixed::new(); - - let mut y = 0; - for row in ROWS { - let mut x = 0; - for name in *row { - let width = KEY_WIDTHS - .iter() - .find_map(|(width, keys)| { - if keys.contains(name) { - Some(*width) - } else { - None - } - }) - .unwrap_or(KEY_WIDTH); - let key = PickerKey::new(name, width / KEY_WIDTH); - fixed.put(&key, x, y); - keys.push(key); - x += width as i32 + 4 - } - y += KEY_WIDTH as i32 + 4; - } - - PickerAnsiGroup { - keys, - widget: fixed, - } - } -} - -impl PickerGroup for PickerAnsiGroup { - fn keys(&self) -> &[PickerKey] { - &self.keys - } - - fn widget(&self) -> >k::Widget { - self.widget.upcast_ref() - } +pub fn picker_ansi_group() -> PickerVariableWidthGroup { + PickerVariableWidthGroup::new( + ROWS, + KEY_WIDTHS, + &[], + None, + Some(&fl!("picker-shift-click")), + ) } diff --git a/src/picker/group_box/group/basic_group.rs b/src/picker/group_box/group/basic_group.rs index 80077dc5..f0fe7ab5 100644 --- a/src/picker/group_box/group/basic_group.rs +++ b/src/picker/group_box/group/basic_group.rs @@ -15,9 +15,9 @@ pub struct PickerBasicGroup { } impl PickerBasicGroup { - pub fn new(name: String, cols: u32, width: f64, key_names: &[&str]) -> Self { + pub fn new(name: &str, cols: u32, width: f64, key_names: &[&str]) -> Self { let label = cascade! { - gtk::Label::new(Some(&name)); + gtk::Label::new(Some(name)); ..set_attributes(Some(&cascade! { pango::AttrList::new(); ..insert(pango::AttrInt::new_weight(pango::Weight::Bold)); @@ -46,7 +46,7 @@ impl PickerBasicGroup { let keys: Vec<_> = key_names .iter() - .map(|name| PickerKey::new(name, width)) + .map(|name| PickerKey::new(name, width, 1.0)) .collect(); for key in &keys { flow_box.add(key); diff --git a/src/picker/group_box/group/international.rs b/src/picker/group_box/group/international.rs index e3e3e693..576b30d8 100644 --- a/src/picker/group_box/group/international.rs +++ b/src/picker/group_box/group/international.rs @@ -35,7 +35,7 @@ pub struct PickerInternationalGroup { } fn row(keys: &mut Vec, keycode: &str, description: &str) -> gtk::Box { - let key = PickerKey::new(keycode, 1.0); + let key = PickerKey::new(keycode, 1.0, 1.0); keys.push(key.clone()); cascade! { gtk::Box::new(gtk::Orientation::Horizontal, 8); diff --git a/src/picker/group_box/group/mod.rs b/src/picker/group_box/group/mod.rs index fdd2efe8..8432401b 100644 --- a/src/picker/group_box/group/mod.rs +++ b/src/picker/group_box/group/mod.rs @@ -1,11 +1,14 @@ use super::super::PickerKey; mod ansi; -pub use ansi::PickerAnsiGroup; +pub use ansi::picker_ansi_group; mod basic_group; pub use basic_group::PickerBasicGroup; mod international; pub use international::PickerInternationalGroup; +mod numpad; +pub use numpad::picker_numpad_group; +mod variable_width; pub trait PickerGroup { fn keys(&self) -> &[PickerKey]; diff --git a/src/picker/group_box/group/numpad.rs b/src/picker/group_box/group/numpad.rs new file mode 100644 index 00000000..a23a5afe --- /dev/null +++ b/src/picker/group_box/group/numpad.rs @@ -0,0 +1,17 @@ +use super::variable_width::{PickerVariableWidthGroup, KEY_SIZE, KEY_SPACE}; + +static KEY_WIDTHS: &[(f64, &[&str])] = &[(2.0 * KEY_SIZE + KEY_SPACE, &["NUM_0"])]; + +static KEY_HEIGHTS: &[(f64, &[&str])] = &[(2.0 * KEY_SIZE + KEY_SPACE, &["NUM_PLUS", "NUM_ENTER"])]; + +static ROWS: &[&[&str]] = &[ + &["NUM_LOCK", "NUM_SLASH", "NUM_ASTERISK", "NUM_MINUS"], + &["NUM_7", "NUM_8", "NUM_9", "NUM_PLUS"], + &["NUM_4", "NUM_5", "NUM_6"], + &["NUM_1", "NUM_2", "NUM_3", "NUM_ENTER"], + &["NUM_0", "NUM_PERIOD"], +]; + +pub fn picker_numpad_group() -> PickerVariableWidthGroup { + PickerVariableWidthGroup::new(ROWS, KEY_WIDTHS, KEY_HEIGHTS, Some("Numpad"), None) +} diff --git a/src/picker/group_box/group/variable_width.rs b/src/picker/group_box/group/variable_width.rs new file mode 100644 index 00000000..4ddaecf4 --- /dev/null +++ b/src/picker/group_box/group/variable_width.rs @@ -0,0 +1,99 @@ +use cascade::cascade; +use gtk::{pango, prelude::*}; + +use super::{PickerGroup, PickerKey}; + +pub const KEY_SIZE: f64 = 48.0; +pub const KEY_SPACE: f64 = 4.0; + +pub struct PickerVariableWidthGroup { + keys: Vec, + widget: gtk::Box, +} + +impl PickerVariableWidthGroup { + pub fn new( + rows: &[&[&str]], + widths: &[(f64, &[&str])], + heights: &[(f64, &[&str])], + label: Option<&str>, + desc: Option<&str>, + ) -> Self { + let mut keys = Vec::new(); + + let vbox = cascade! { + gtk::Box::new(gtk::Orientation::Vertical, 4); + ..show(); + }; + + if let Some(label) = label { + let label = cascade! { + gtk::Label::new(Some(&label)); + ..set_attributes(Some(&cascade! { + pango::AttrList::new(); + ..insert(pango::AttrInt::new_weight(pango::Weight::Bold)); + } )); + ..set_halign(gtk::Align::Start); + ..set_margin_bottom(8); + ..show(); + }; + vbox.add(&label); + } + + let fixed = gtk::Fixed::new(); + vbox.add(&fixed); + + let mut y = 0; + for row in rows { + let mut x = 0; + for name in *row { + let width = widths + .iter() + .find_map(|(width, keys)| { + if keys.contains(name) { + Some(*width) + } else { + None + } + }) + .unwrap_or(KEY_SIZE); + let height = heights + .iter() + .find_map(|(height, keys)| { + if keys.contains(name) { + Some(*height) + } else { + None + } + }) + .unwrap_or(KEY_SIZE); + let key = PickerKey::new(name, width / KEY_SIZE, height / KEY_SIZE); + fixed.put(&key, x, y); + keys.push(key); + x += width as i32 + 4 + } + y += KEY_SIZE as i32 + 4; + } + + if let Some(desc) = desc { + let label = cascade! { + gtk::Label::new(Some(&desc)); + ..set_halign(gtk::Align::Start); + ..show(); + }; + vbox.add(&label); + } + + Self { keys, widget: vbox } + } +} + +impl PickerGroup for PickerVariableWidthGroup { + fn keys(&self) -> &[PickerKey] { + &self.keys + } + + fn widget(&self) -> >k::Widget { + self.widget.upcast_ref() + } +} diff --git a/src/picker/group_box/mod.rs b/src/picker/group_box/mod.rs index ce502120..24737c43 100644 --- a/src/picker/group_box/mod.rs +++ b/src/picker/group_box/mod.rs @@ -191,7 +191,7 @@ impl PickerGroupBox { pub fn set_key_visibility bool>(&self, f: F) { for group in self.inner().groups.iter() { let group_visible = group.keys().iter().fold(false, |group_visible, key| { - key.set_visible(f(&key.name())); + key.set_visible(f(key.name())); group_visible || key.get_visible() }); @@ -202,7 +202,7 @@ impl PickerGroupBox { pub fn set_key_sensitivity bool>(&self, f: F) { for key in self.inner().keys.values() { - key.set_sensitive(f(&key.name())); + key.set_sensitive(f(key.name())); } } diff --git a/src/picker/mod.rs b/src/picker/mod.rs index 5f8194c2..6f48a6aa 100644 --- a/src/picker/mod.rs +++ b/src/picker/mod.rs @@ -188,7 +188,7 @@ impl Picker { self.inner().tap_hold.set_visible(is_qmk); self.inner().stack_switcher.set_visible(is_qmk); self.inner().is_qmk.set(is_qmk); - kb.set_picker(Some(&self)); + kb.set_picker(Some(self)); } *self.inner().keyboard.borrow_mut() = keyboard; diff --git a/src/picker/picker_key.rs b/src/picker/picker_key.rs index 611b25b2..2c78f847 100644 --- a/src/picker/picker_key.rs +++ b/src/picker/picker_key.rs @@ -56,13 +56,13 @@ glib::wrapper! { } impl PickerKey { - pub fn new(name: &str, width: f64) -> Self { + pub fn new(name: &str, width: f64, height: f64) -> Self { let keysym_label = super::SCANCODE_LABELS.get(name).unwrap(); let widget: Self = glib::Object::new(&[]).unwrap(); widget.inner().name.set(name.to_string()); - widget.inner().label.set_label(&keysym_label); - widget.set_size_request((48.0 * width) as i32, 48); + widget.inner().label.set_label(keysym_label); + widget.set_size_request((48.0 * width) as i32, (48.0 * height) as i32); widget } diff --git a/src/picker/tap_hold.rs b/src/picker/tap_hold.rs index 9a9d70dd..6dabf3ae 100644 --- a/src/picker/tap_hold.rs +++ b/src/picker/tap_hold.rs @@ -75,19 +75,19 @@ impl ObjectImpl for TapHoldInner { *widget.inner().keycode.borrow_mut() = Some(name); widget.update(); })); - ..set_key_visibility(|name| is_qmk_basic(name)); + ..set_key_visibility(is_qmk_basic); }; let hold_group_box = cascade! { PickerGroupBox::new(vec![ Box::new(PickerBasicGroup::new( - "Modifiers".to_string(), + "Modifiers", 4, 1.5, MODIFIERS, )), Box::new(PickerBasicGroup::new( - "Layer Keys".to_string(), + "Layer Keys", 4, 1.5, LAYERS, @@ -103,7 +103,7 @@ impl ObjectImpl for TapHoldInner { } Hold::Mods(new_mods) } else { - let n = LAYERS.iter().position(|x| *x == &name).unwrap() as u8; + let n = LAYERS.iter().position(|x| *x == name).unwrap() as u8; Hold::Layer(n) }; widget.inner().hold.set(new_hold); @@ -111,6 +111,7 @@ impl ObjectImpl for TapHoldInner { })); }; + // TODO indent cascade! { widget; ..set_spacing(8); @@ -123,10 +124,14 @@ impl ObjectImpl for TapHoldInner { })); ..set_halign(gtk::Align::Start); }); - ..add(&hold_group_box); + ..add(cascade! { + &hold_group_box; + ..set_margin_start(8); + }); ..add(&cascade! { gtk::Label::new(Some(&fl!("tap-hold-multiple-mod"))); ..set_halign(gtk::Align::Start); + ..set_margin_start(8); }); // XXX grey? ..add(&cascade! { @@ -137,7 +142,10 @@ impl ObjectImpl for TapHoldInner { })); ..set_halign(gtk::Align::Start); }); - ..add(&picker_group_box); + ..add(cascade! { + &picker_group_box; + ..set_margin_start(8); + }); }; self.hold_group_box.set(hold_group_box); From c34885f49e3b2266b0813be6622d21dbe3434cf2 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 20 Mar 2023 15:36:05 -0700 Subject: [PATCH 13/13] Update order of picker basic groups --- src/picker/group_box/basics.rs | 41 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/picker/group_box/basics.rs b/src/picker/group_box/basics.rs index 0493f46b..7e5e2f84 100644 --- a/src/picker/group_box/basics.rs +++ b/src/picker/group_box/basics.rs @@ -4,6 +4,12 @@ impl PickerGroupBox { pub fn basics() -> Self { Self::new(vec![ Box::new(picker_ansi_group()), + Box::new(PickerBasicGroup::new( + "Navigation", + 4, + 1.0, + &["LEFT", "UP", "DOWN", "RIGHT", "HOME", "PGUP", "PGDN", "END"], + )), Box::new(PickerBasicGroup::new( "Other actions", 4, @@ -18,33 +24,12 @@ impl PickerGroupBox { "NONE", ], )), - // TODO label? - Box::new(picker_numpad_group()), Box::new(PickerBasicGroup::new( "Symbols", 6, 1.0, &["NONUS_HASH", "NONUS_BSLASH"], )), - Box::new(PickerBasicGroup::new( - "Navigation", - 4, - 1.0, - &["LEFT", "UP", "DOWN", "RIGHT", "HOME", "PGUP", "PGDN", "END"], - )), - Box::new(PickerBasicGroup::new( - "Media", - 3, - 1.0, - &[ - "MUTE", - "VOLUME_UP", - "VOLUME_DOWN", - "PLAY_PAUSE", - "MEDIA_NEXT", - "MEDIA_PREV", - ], - )), Box::new(PickerBasicGroup::new( "Controls", 4, @@ -68,6 +53,19 @@ impl PickerGroupBox { 1.0, &["KBD_TOGGLE", "KBD_UP", "KBD_DOWN", "KBD_BKL", "KBD_COLOR"], )), + Box::new(PickerBasicGroup::new( + "Media", + 3, + 1.0, + &[ + "MUTE", + "VOLUME_UP", + "VOLUME_DOWN", + "PLAY_PAUSE", + "MEDIA_NEXT", + "MEDIA_PREV", + ], + )), Box::new(PickerBasicGroup::new( "Layer keys", 4, @@ -83,6 +81,7 @@ impl PickerGroupBox { "LAYER_SWITCH_4", ], )), + Box::new(picker_numpad_group()), ]) } }