use crate::VirtualKey;
use enumflags2::{BitFlags, bitflags};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{fmt, num::NonZeroU16};
use windows::Win32::UI::Controls::{HOTKEYF_ALT, HOTKEYF_CONTROL, HOTKEYF_EXT, HOTKEYF_SHIFT};
#[allow(clippy::cast_possible_truncation)]
const fn convert(code: u32) -> u8 {
assert!(code <= u8::MAX as u32);
code as u8
}
#[bitflags]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HotkeyModifier {
Shift = convert(HOTKEYF_SHIFT),
Control = convert(HOTKEYF_CONTROL),
Alt = convert(HOTKEYF_ALT),
Ext = convert(HOTKEYF_EXT),
}
pub type HotkeyModifiers = BitFlags<HotkeyModifier>;
impl fmt::Display for HotkeyModifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_str())
}
}
impl HotkeyModifier {
#[must_use]
pub fn to_str(&self) -> &'static str {
match self {
Self::Shift => "Shift",
Self::Control => "Ctrl",
Self::Alt => "Alt",
Self::Ext => "Ext",
}
}
#[must_use]
pub fn to_raw(&self) -> u8 {
*self as u8
}
#[must_use]
pub fn many_from_raw(bits: u8) -> HotkeyModifiers {
HotkeyModifiers::from_bits_truncate(bits)
}
#[must_use]
pub fn single_from_raw(bits: u8) -> Option<HotkeyModifier> {
Self::try_from_primitive(bits).ok()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Hotkey {
key: VirtualKey,
modifiers: HotkeyModifiers,
}
impl Hotkey {
#[must_use]
pub fn new(key: VirtualKey, modifiers: impl Into<HotkeyModifiers>) -> Option<Self> {
if key == VirtualKey::Other(0) {
return None;
}
Some(unsafe { Self::new_unchecked(key, modifiers) })
}
#[must_use]
pub unsafe fn new_unchecked(key: VirtualKey, modifiers: impl Into<HotkeyModifiers>) -> Self {
let modifiers = modifiers.into();
Self { key, modifiers }
}
#[must_use]
pub fn key(&self) -> VirtualKey {
self.key
}
#[must_use]
pub fn modifiers(&self) -> HotkeyModifiers {
self.modifiers
}
#[must_use]
pub fn has_modifiers(&self) -> bool {
!self.modifiers.is_empty()
}
#[must_use]
pub fn from_raw(word: u16) -> Option<Self> {
if word == 0 {
return None;
}
let low = (word & 0x00FF) as u8;
let high = ((word >> 8) & 0x00FF) as u8;
let key = VirtualKey::from_raw(low);
let modifiers = HotkeyModifier::many_from_raw(high);
Some(Self { key, modifiers })
}
#[must_use]
pub unsafe fn from_raw_unchecked(word: u16) -> Self {
let low = (word & 0x00FF) as u8;
let high = ((word >> 8) & 0x00FF) as u8;
let key = VirtualKey::from_raw(low);
let modifiers = unsafe { HotkeyModifiers::from_bits_unchecked(high) };
Self { key, modifiers }
}
#[must_use]
pub fn to_raw(&self) -> u16 {
let low = u16::from(self.key.to_raw());
let high = u16::from(self.modifiers.bits()) << 8;
high | low
}
}
impl From<NonZeroU16> for Hotkey {
fn from(hk: NonZeroU16) -> Self {
Self::from_raw(hk.get()).unwrap()
}
}
impl From<Hotkey> for NonZeroU16 {
fn from(h: Hotkey) -> Self {
unsafe { NonZeroU16::new_unchecked(h.to_raw()) }
}
}
impl TryFrom<u16> for Hotkey {
type Error = ();
fn try_from(hk: u16) -> Result<Self, Self::Error> {
Self::from_raw(hk).ok_or(())
}
}
impl From<Hotkey> for u16 {
fn from(h: Hotkey) -> Self {
h.to_raw()
}
}
impl fmt::Display for Hotkey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mods = self
.modifiers
.iter()
.map(|m| m.to_str())
.collect::<Vec<_>>()
.join("+");
if self.has_modifiers() {
write!(f, "{}+{}", mods, self.key)
} else {
write!(f, "{}", self.key)
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Hotkey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = u16::deserialize(deserializer)?;
Hotkey::from_raw(raw).ok_or_else(|| serde::de::Error::custom("invalid hotkey"))
}
}