use std::fmt;
use bitflags::bitflags;
use zng_txt::{ToTxt, Txt};
use zng_unique_id::static_id;
use zng_var::{impl_from_and_into_var, BoxedVar, Var};
#[doc(hidden)]
pub use zng_view_api::keyboard::{Key, KeyCode};
use crate::event::{Command, CommandMetaVar, CommandMetaVarId};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum GestureKey {
Key(Key),
Code(KeyCode),
}
impl std::hash::Hash for GestureKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
GestureKey::Key(k) => match k {
Key::Char(c) => {
for c in c.to_uppercase() {
c.hash(state);
}
}
Key::Str(s) => {
unicase::UniCase::new(s).hash(state);
}
k => k.hash(state),
},
GestureKey::Code(c) => c.hash(state),
}
}
}
impl Eq for GestureKey {}
impl PartialEq for GestureKey {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Key(l0), Self::Key(r0)) => match (l0, r0) {
(Key::Char(l), Key::Char(r)) => {
let mut l = l.to_uppercase();
let mut r = r.to_uppercase();
while let (Some(l), Some(r)) = (l.next(), r.next()) {
if l != r {
return false;
}
}
l.next().is_none() && r.next().is_none()
}
(Key::Str(l), Key::Str(r)) => unicase::eq(l, r),
(l0, r0) => l0 == r0,
},
(Self::Code(l0), Self::Code(r0)) => l0 == r0,
_ => false,
}
}
}
impl fmt::Display for GestureKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GestureKey::Key(k) => match k {
Key::Char(c) => write!(f, "{}", c),
Key::Str(s) => write!(f, "{}", s),
k => write!(f, "{k:?}"),
},
GestureKey::Code(c) => write!(f, "{:?}", c),
}
}
}
impl TryFrom<Key> for GestureKey {
type Error = Key;
fn try_from(key: Key) -> Result<Self, Self::Error> {
if key.is_modifier() || key.is_composition() || key == Key::Unidentified {
Err(key)
} else {
Ok(Self::Key(key))
}
}
}
impl TryFrom<KeyCode> for GestureKey {
type Error = KeyCode;
fn try_from(key: KeyCode) -> Result<Self, Self::Error> {
if key.is_modifier() || key.is_composition() || key.is_unidentified() {
Err(key)
} else {
Ok(Self::Code(key))
}
}
}
impl std::str::FromStr for GestureKey {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match Key::from_str(s) {
Key::Str(s) => match KeyCode::from_str(&s) {
Ok(k) => {
let key = k
.try_into()
.map_err(|e| ParseError::new(format!("key `{e:?}` cannot be used in gestures")))?;
Ok(key)
}
Err(_) => Ok(Self::Key(Key::Str(s))),
},
k => {
let key = k
.try_into()
.map_err(|e| ParseError::new(format!("key `{e:?}` cannot be used in gestures")))?;
Ok(key)
}
}
}
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct KeyGesture {
pub modifiers: ModifiersState,
pub key: GestureKey,
}
impl PartialEq for KeyGesture {
fn eq(&self, other: &Self) -> bool {
self.modifiers.ambit() == other.modifiers.ambit() && self.key == other.key
}
}
impl Eq for KeyGesture {}
impl std::hash::Hash for KeyGesture {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.modifiers.ambit().hash(state);
self.key.hash(state);
}
}
impl KeyGesture {
pub fn new(modifiers: ModifiersState, key: GestureKey) -> Self {
KeyGesture { modifiers, key }
}
pub fn new_key(key: GestureKey) -> Self {
KeyGesture {
modifiers: ModifiersState::empty(),
key,
}
}
}
impl fmt::Debug for KeyGesture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("KeyGesture")
.field("modifiers", &self.modifiers)
.field("key", &self.key)
.finish()
} else {
write!(f, "{self}")
}
}
}
impl fmt::Display for KeyGesture {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.modifiers.has_super() {
write!(f, "Super+")?
}
if self.modifiers.has_ctrl() {
write!(f, "Ctrl+")?
}
if self.modifiers.has_shift() {
write!(f, "Shift+")?
}
if self.modifiers.has_alt() {
write!(f, "Alt+")?
}
write!(f, "{}", self.key)
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum ModifierGesture {
Super,
Ctrl,
Shift,
Alt,
}
impl fmt::Debug for ModifierGesture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "ModifierGesture::")?;
}
write!(f, "{self}")
}
}
impl fmt::Display for ModifierGesture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModifierGesture::Super => write!(f, "Super"),
ModifierGesture::Ctrl => write!(f, "Ctrl"),
ModifierGesture::Shift => write!(f, "Shift"),
ModifierGesture::Alt => write!(f, "Alt"),
}
}
}
impl<'a> TryFrom<&'a Key> for ModifierGesture {
type Error = &'a Key;
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
match value {
Key::Alt | Key::AltGraph => Ok(ModifierGesture::Alt),
Key::Ctrl => Ok(ModifierGesture::Ctrl),
Key::Shift => Ok(ModifierGesture::Shift),
Key::Super => Ok(ModifierGesture::Super),
key => Err(key),
}
}
}
impl TryFrom<KeyCode> for ModifierGesture {
type Error = KeyCode;
fn try_from(value: KeyCode) -> Result<Self, Self::Error> {
match value {
KeyCode::AltLeft | KeyCode::AltRight => Ok(ModifierGesture::Alt),
KeyCode::CtrlLeft | KeyCode::CtrlRight => Ok(ModifierGesture::Ctrl),
KeyCode::ShiftLeft | KeyCode::ShiftRight => Ok(ModifierGesture::Shift),
KeyCode::SuperLeft | KeyCode::SuperRight => Ok(ModifierGesture::Super),
key => Err(key),
}
}
}
impl ModifierGesture {
pub fn left_key(&self) -> (KeyCode, Key) {
match self {
ModifierGesture::Super => (KeyCode::SuperLeft, Key::Super),
ModifierGesture::Ctrl => (KeyCode::CtrlLeft, Key::Ctrl),
ModifierGesture::Shift => (KeyCode::ShiftLeft, Key::Shift),
ModifierGesture::Alt => (KeyCode::AltLeft, Key::Alt),
}
}
pub fn modifiers_state(&self) -> ModifiersState {
match self {
ModifierGesture::Super => ModifiersState::LOGO,
ModifierGesture::Ctrl => ModifiersState::CTRL,
ModifierGesture::Shift => ModifiersState::SHIFT,
ModifierGesture::Alt => ModifiersState::ALT,
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct KeyChord {
pub starter: KeyGesture,
pub complement: KeyGesture,
}
impl fmt::Debug for KeyChord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("KeyChord")
.field("starter", &self.starter)
.field("complement", &self.complement)
.finish()
} else {
write!(f, "{self}")
}
}
}
impl fmt::Display for KeyChord {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.starter, self.complement)
}
}
#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Shortcut {
Gesture(KeyGesture),
Chord(KeyChord),
Modifier(ModifierGesture),
}
impl fmt::Debug for Shortcut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
match self {
Shortcut::Gesture(g) => f.debug_tuple("Shortcut::Gesture").field(g).finish(),
Shortcut::Chord(c) => f.debug_tuple("Shortcut::Chord").field(c).finish(),
Shortcut::Modifier(m) => f.debug_tuple("Shortcut::Modifier").field(m).finish(),
}
} else {
write!(f, "{self}")
}
}
}
impl fmt::Display for Shortcut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Shortcut::Gesture(g) => fmt::Display::fmt(g, f),
Shortcut::Chord(c) => fmt::Display::fmt(c, f),
Shortcut::Modifier(m) => fmt::Display::fmt(m, f),
}
}
}
impl Shortcut {
pub fn modifiers_state(&self) -> ModifiersState {
match self {
Shortcut::Gesture(g) => g.modifiers,
Shortcut::Chord(c) => c.complement.modifiers,
Shortcut::Modifier(m) => m.modifiers_state(),
}
}
}
impl_from_and_into_var! {
fn from(shortcut: Shortcut) -> Shortcuts {
Shortcuts(vec![shortcut])
}
fn from(key_gesture: KeyGesture) -> Shortcut {
Shortcut::Gesture(key_gesture)
}
fn from(key_chord: KeyChord) -> Shortcut {
Shortcut::Chord(key_chord)
}
fn from(modifier: ModifierGesture) -> Shortcut {
Shortcut::Modifier(modifier)
}
fn from(gesture_key: GestureKey) -> Shortcut {
KeyGesture::new_key(gesture_key).into()
}
fn from(gesture_key: GestureKey) -> Shortcuts {
Shortcuts(vec![gesture_key.into()])
}
fn from(key_gesture: KeyGesture) -> Shortcuts {
Shortcuts(vec![key_gesture.into()])
}
fn from(key_chord: KeyChord) -> Shortcuts {
Shortcuts(vec![key_chord.into()])
}
fn from(modifier: ModifierGesture) -> Shortcuts {
Shortcuts(vec![modifier.into()])
}
fn from(shortcuts: Vec<Shortcut>) -> Shortcuts {
Shortcuts(shortcuts)
}
}
impl<const N: usize> From<[Shortcut; N]> for Shortcuts {
fn from(a: [Shortcut; N]) -> Self {
Shortcuts(a.into())
}
}
impl<const N: usize> crate::var::IntoVar<Shortcuts> for [Shortcut; N] {
type Var = crate::var::LocalVar<Shortcuts>;
fn into_var(self) -> Self::Var {
crate::var::LocalVar(self.into())
}
}
#[derive(Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Shortcuts(pub Vec<Shortcut>);
impl Shortcuts {
pub const fn new() -> Self {
Self(vec![])
}
pub fn from_char(character: char) -> Result<Self, char> {
if character.is_control() {
Err(character)
} else {
Ok(Self(vec![Shortcut::Gesture(KeyGesture {
modifiers: ModifiersState::empty(),
key: GestureKey::Key(Key::Char(character)),
})]))
}
}
pub fn contains(&self, shortcut: &Shortcut) -> bool {
self.0.contains(shortcut)
}
}
impl TryFrom<char> for Shortcuts {
type Error = char;
fn try_from(value: char) -> Result<Self, Self::Error> {
Shortcuts::from_char(value)
}
}
impl fmt::Debug for Shortcuts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_tuple("Shortcuts").field(&self.0).finish()
} else {
write!(f, "[")?;
if !self.0.is_empty() {
if let Shortcut::Chord(c) = &self.0[0] {
write!(f, "({c:?})")?;
} else {
write!(f, "{:?}", self.0[0])?;
}
for shortcut in &self.0[1..] {
if let Shortcut::Chord(c) = shortcut {
write!(f, ", ({c:?})")?;
} else {
write!(f, ", {shortcut:?}")?;
}
}
}
write!(f, "]")
}
}
}
impl fmt::Display for Shortcuts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.0.is_empty() {
write!(f, "{}", self.0[0])?;
for shortcut in &self.0[1..] {
write!(f, " | {shortcut}")?;
}
}
Ok(())
}
}
impl std::ops::Deref for Shortcuts {
type Target = Vec<Shortcut>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for Shortcuts {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub error: String,
}
impl ParseError {
pub fn new(error: impl ToString) -> Self {
ParseError { error: error.to_string() }
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.error.fmt(f)
}
}
impl std::error::Error for ParseError {}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct ModifiersState: u8 {
const L_SHIFT = 0b0000_0001;
const R_SHIFT = 0b0000_0010;
const SHIFT = 0b0000_0011;
const L_CTRL = 0b0000_0100;
const R_CTRL = 0b0000_1000;
const CTRL = 0b0000_1100;
const L_ALT = 0b0001_0000;
const R_ALT = 0b0010_0000;
const ALT = 0b0011_0000;
const L_LOGO = 0b0100_0000;
const R_LOGO = 0b1000_0000;
const LOGO = 0b1100_0000;
}
}
impl ModifiersState {
pub fn has_shift(self) -> bool {
self.intersects(Self::SHIFT)
}
pub fn has_ctrl(self) -> bool {
self.intersects(Self::CTRL)
}
pub fn has_alt(self) -> bool {
self.intersects(Self::ALT)
}
pub fn has_super(self) -> bool {
self.intersects(Self::LOGO)
}
pub fn is_only(self, part: ModifiersState) -> bool {
!self.is_empty() && (self - part).is_empty()
}
pub fn is_only_shift(self) -> bool {
self.is_only(ModifiersState::SHIFT)
}
pub fn is_only_ctrl(self) -> bool {
self.is_only(ModifiersState::CTRL)
}
pub fn is_only_alt(self) -> bool {
self.is_only(ModifiersState::ALT)
}
pub fn is_only_logo(self) -> bool {
self.is_only(ModifiersState::LOGO)
}
pub fn take(&mut self, part: ModifiersState) -> bool {
let r = self.intersects(part);
if r {
self.remove(part);
}
r
}
pub fn take_shift(&mut self) -> bool {
self.take(ModifiersState::SHIFT)
}
pub fn take_ctrl(&mut self) -> bool {
self.take(ModifiersState::CTRL)
}
pub fn take_alt(&mut self) -> bool {
self.take(ModifiersState::ALT)
}
pub fn take_logo(&mut self) -> bool {
self.take(ModifiersState::LOGO)
}
pub fn ambit(self) -> Self {
let mut r = Self::empty();
if self.has_alt() {
r |= Self::ALT;
}
if self.has_ctrl() {
r |= Self::CTRL;
}
if self.has_shift() {
r |= Self::SHIFT;
}
if self.has_super() {
r |= Self::LOGO;
}
r
}
pub fn into_alt(self) -> Self {
self & Self::ALT
}
pub fn into_ctrl(self) -> Self {
self & Self::CTRL
}
pub fn into_shift(self) -> Self {
self & Self::SHIFT
}
pub fn into_logo(self) -> Self {
self & Self::LOGO
}
pub fn from_code(code: KeyCode) -> ModifiersState {
match code {
KeyCode::AltLeft => Self::L_ALT,
KeyCode::AltRight => Self::R_ALT,
KeyCode::CtrlLeft => Self::L_CTRL,
KeyCode::CtrlRight => Self::R_CTRL,
KeyCode::ShiftLeft => Self::L_SHIFT,
KeyCode::ShiftRight => Self::R_SHIFT,
KeyCode::SuperLeft => Self::L_LOGO,
KeyCode::SuperRight => Self::R_LOGO,
_ => Self::empty(),
}
}
pub fn from_key(key: Key) -> ModifiersState {
match key {
Key::Alt => Self::L_ALT,
Key::AltGraph => Self::R_ALT,
Key::Shift => Self::SHIFT,
Key::Ctrl => Self::CTRL,
Key::Super => Self::LOGO,
_ => Self::empty(),
}
}
pub fn codes(self) -> Vec<KeyCode> {
let mut r = vec![];
if self.contains(Self::L_LOGO) {
r.push(KeyCode::SuperLeft);
} else if self.contains(Self::R_LOGO) {
r.push(KeyCode::SuperRight);
}
if self.contains(Self::L_CTRL) {
r.push(KeyCode::CtrlLeft);
} else if self.contains(Self::R_CTRL) {
r.push(KeyCode::CtrlRight);
}
if self.contains(Self::L_SHIFT) {
r.push(KeyCode::ShiftLeft);
} else if self.contains(Self::R_SHIFT) {
r.push(KeyCode::ShiftRight);
}
if self.contains(Self::L_ALT) {
r.push(KeyCode::AltLeft);
} else if self.contains(Self::R_ALT) {
r.push(KeyCode::AltRight);
}
r
}
pub fn keys(self) -> Vec<Key> {
let mut r = vec![];
if self.intersects(Self::LOGO) {
r.push(Key::Super);
}
if self.intersects(Self::CTRL) {
r.push(Key::Ctrl);
}
if self.intersects(Self::SHIFT) {
r.push(Key::Shift);
}
if self.contains(Self::R_ALT) {
r.push(Key::AltGraph);
} else if self.contains(Self::R_ALT) {
r.push(Key::Alt);
}
r
}
}
pub trait CommandShortcutExt {
fn shortcut(self) -> CommandMetaVar<Shortcuts>;
fn shortcut_txt(self) -> BoxedVar<Txt>
where
Self: Sized,
{
self.shortcut()
.map(|c| if c.is_empty() { Txt::from("") } else { c[0].to_txt() })
.boxed()
}
fn shortcut_filter(self) -> CommandMetaVar<ShortcutFilter>;
fn init_shortcut(self, shortcut: impl Into<Shortcuts>) -> Self;
fn init_shortcut_filter(self, filter: impl Into<ShortcutFilter>) -> Self;
}
impl CommandShortcutExt for Command {
fn shortcut(self) -> CommandMetaVar<Shortcuts> {
self.with_meta(|m| m.get_var_or_default(*COMMAND_SHORTCUT_ID))
}
fn shortcut_filter(self) -> CommandMetaVar<ShortcutFilter> {
self.with_meta(|m| m.get_var_or_default(*COMMAND_SHORTCUT_FILTER_ID))
}
fn init_shortcut(self, shortcut: impl Into<Shortcuts>) -> Self {
self.with_meta(|m| m.init_var(*COMMAND_SHORTCUT_ID, shortcut.into()));
self
}
fn init_shortcut_filter(self, filter: impl Into<ShortcutFilter>) -> Self {
self.with_meta(|m| m.init_var(*COMMAND_SHORTCUT_FILTER_ID, filter.into()));
self
}
}
bitflags! {
#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct ShortcutFilter: u8 {
const ENABLED = 0b001;
const FOCUSED = 0b010;
const CMD_ENABLED = 0b100;
}
}
static_id! {
static ref COMMAND_SHORTCUT_ID: CommandMetaVarId<Shortcuts>;
static ref COMMAND_SHORTCUT_FILTER_ID: CommandMetaVarId<ShortcutFilter>;
}
#[doc(hidden)]
#[macro_export]
macro_rules! __shortcut {
(-> + $Key:tt) => {
$crate::shortcut::KeyGesture {
key: $crate::__shortcut!(@key $Key),
modifiers: $crate::shortcut::ModifiersState::empty(),
}
};
(-> $($MODIFIER:ident)|+ + $Key:tt) => {
$crate::shortcut::KeyGesture {
key: $crate::__shortcut!(@key $Key),
modifiers: $($crate::shortcut::ModifiersState::$MODIFIER)|+,
}
};
(=> $($STARTER_MODIFIER:ident)|* + $StarterKey:tt, $($COMPLEMENT_MODIFIER:ident)|* + $ComplementKey:tt) => {
$crate::shortcut::KeyChord {
starter: $crate::__shortcut!(-> $($STARTER_MODIFIER)|* + $StarterKey),
complement: $crate::__shortcut!(-> $($COMPLEMENT_MODIFIER)|* + $ComplementKey)
}
};
(@key $Key:ident) => { $crate::shortcut::GestureKey::Key($crate::shortcut::Key::$Key) };
(@key $key_char:literal) => { $crate::shortcut::GestureKey::Key($crate::shortcut::Key::Char($key_char)) };
}
#[macro_export]
macro_rules! shortcut_macro {
(Super) => {
$crate::shortcut::Shortcut::Modifier($crate::shortcut::ModifierGesture::Super)
};
(Shift) => {
$crate::shortcut::Shortcut::Modifier($crate::shortcut::ModifierGesture::Shift)
};
(Ctrl) => {
$crate::shortcut::Shortcut::Modifier($crate::shortcut::ModifierGesture::Ctrl)
};
(Alt) => {
$crate::shortcut::Shortcut::Modifier($crate::shortcut::ModifierGesture::Alt)
};
($Key:tt) => {
$crate::shortcut::Shortcut::Gesture($crate::__shortcut!(-> + $Key))
};
($($MODIFIER:ident)|+ + $Key:tt) => {
$crate::shortcut::Shortcut::Gesture($crate::__shortcut!(-> $($MODIFIER)|+ + $Key))
};
($StarterKey:tt, $ComplementKey:tt) => {
$crate::shortcut::Shortcut::Chord($crate::__shortcut!(=>
+ $StarterKey,
+ $ComplementKey
))
};
($StarterKey:tt, $($COMPLEMENT_MODIFIER:ident)|+ + $ComplementKey:tt) => {
$crate::shortcut::Shortcut::Chord($crate::__shortcut!(=>
+ $StarterKey,
$(COMPLEMENT_MODIFIER)|* + $ComplementKey
))
};
($($STARTER_MODIFIER:ident)|+ + $StarterKey:tt, $ComplementKey:tt) => {
$crate::shortcut::Shortcut::Chord($crate::__shortcut!(=>
$($STARTER_MODIFIER)|* + $StarterKey,
+ $ComplementKey
))
};
($($STARTER_MODIFIER:ident)|+ + $StarterKey:tt, $($COMPLEMENT_MODIFIER:ident)|+ + $ComplementKey:tt) => {
$crate::shortcut::Shortcut::Chord($crate::__shortcut!(=>
$($STARTER_MODIFIER)|* + $StarterKey,
$($COMPLEMENT_MODIFIER)|* + $ComplementKey
))
};
}
#[doc(inline)]
pub use crate::shortcut_macro as shortcut;