use crate::base::Point;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Mods(pub u8);
impl Mods {
pub const NONE: Mods = Mods(0);
pub const SHIFT: Mods = Mods(1);
pub const CTRL: Mods = Mods(2);
pub const ALT: Mods = Mods(4);
pub const SUPER: Mods = Mods(8);
pub const fn union(self, other: Mods) -> Mods {
Mods(self.0 | other.0)
}
pub const fn contains(self, other: Mods) -> bool {
self.0 & other.0 == other.0
}
}
impl std::ops::BitOr for Mods {
type Output = Mods;
fn bitor(self, rhs: Mods) -> Mods {
self.union(rhs)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Key {
Char(char),
Enter,
Escape,
Tab,
Backspace,
Delete,
Insert,
Home,
End,
PageUp,
PageDown,
Up,
Down,
Left,
Right,
F(u8),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct KeyEvent {
pub key: Key,
pub mods: Mods,
}
fn fold_shifted_letter(key: Key, mods: Mods) -> (Key, Mods) {
if mods.contains(Mods::SHIFT) {
if let Key::Char(c) = key {
if c.is_lowercase() || c.is_uppercase() {
let mut up = c.to_uppercase();
if let (Some(u), None) = (up.next(), up.next()) {
return (Key::Char(u), Mods(mods.0 & !Mods::SHIFT.0));
}
}
}
}
(key, mods)
}
impl KeyEvent {
pub const fn new(key: Key, mods: Mods) -> Self {
KeyEvent { key, mods }
}
pub const fn plain(key: Key) -> Self {
KeyEvent {
key,
mods: Mods::NONE,
}
}
pub fn chord(self) -> KeyChord {
KeyChord {
key: self.key,
mods: self.mods,
}
}
pub fn normalized(self) -> KeyEvent {
let (key, mods) = fold_shifted_letter(self.key, self.mods);
KeyEvent { key, mods }
}
pub fn means_char(self, c: char) -> bool {
self.normalized() == KeyEvent::plain(Key::Char(c))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MouseButton {
Left,
Middle,
Right,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MouseKind {
Down(MouseButton),
Up(MouseButton),
Move,
Drag(MouseButton),
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MouseEvent {
pub pos: Point,
pub kind: MouseKind,
pub mods: Mods,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UiEvent {
Key(KeyEvent),
Mouse(MouseEvent),
FocusIn,
FocusOut,
MouseEnter,
MouseLeave,
Paste(String),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Phase {
Capture,
Target,
Bubble,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct KeyChord {
pub key: Key,
pub mods: Mods,
}
impl KeyChord {
pub const fn new(mods: Mods, key: Key) -> Self {
KeyChord { key, mods }
}
pub fn display(&self) -> String {
let mut out = String::new();
if self.mods.contains(Mods::CTRL) {
out.push_str("Ctrl+");
}
if self.mods.contains(Mods::ALT) {
out.push_str("Alt+");
}
if self.mods.contains(Mods::SHIFT) {
out.push_str("Shift+");
}
match self.key {
Key::Char(' ') => out.push_str("Space"),
Key::Char(c) => out.extend(c.to_uppercase()),
Key::F(n) => out.push_str(&format!("F{n}")),
k => out.push_str(&format!("{k:?}")),
}
out
}
pub const fn plain(key: Key) -> Self {
KeyChord {
key,
mods: Mods::NONE,
}
}
pub const fn ctrl(key: Key) -> Self {
KeyChord {
key,
mods: Mods::CTRL,
}
}
pub fn normalized(self) -> KeyChord {
let (key, mods) = fold_shifted_letter(self.key, self.mods);
KeyChord { key, mods }
}
}
#[derive(Default)]
pub struct EventCtx {
pub(crate) stopped: bool,
pub(crate) focus_request: Option<super::tree::ViewId>,
pub(crate) damage_all: bool,
pub(crate) capture_request: Option<Option<super::tree::ViewId>>,
pub(crate) target_rect: crate::base::Rect,
pub(crate) target: Option<super::tree::ViewId>,
pub(crate) current: Option<super::tree::ViewId>,
pub(crate) current_rect: crate::base::Rect,
pub(crate) click_count: u8,
}
impl EventCtx {
pub fn stop_propagation(&mut self) {
self.stopped = true;
}
pub fn request_focus(&mut self, view: super::tree::ViewId) {
self.focus_request = Some(view);
}
pub fn capture_pointer(&mut self, view: super::tree::ViewId) {
self.capture_request = Some(Some(view));
}
pub fn release_pointer(&mut self) {
self.capture_request = Some(None);
}
pub fn target_rect(&self) -> crate::base::Rect {
self.target_rect
}
pub fn target(&self) -> Option<super::tree::ViewId> {
self.target
}
pub fn current_rect(&self) -> crate::base::Rect {
self.current_rect
}
pub fn current(&self) -> Option<super::tree::ViewId> {
self.current
}
pub fn request_repaint(&mut self) {
self.damage_all = true;
}
pub fn click_count(&self) -> u8 {
self.click_count
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shifted_letter_spellings_normalize_to_one_chord() {
let canon = KeyChord::plain(Key::Char('A')).normalized();
assert_eq!(
KeyChord::new(Mods::SHIFT, Key::Char('a')).normalized(),
canon
);
assert_eq!(
KeyChord::new(Mods::SHIFT, Key::Char('A')).normalized(),
canon
);
assert_ne!(KeyChord::plain(Key::Char('a')).normalized(), canon);
assert_eq!(
KeyChord::new(Mods::CTRL | Mods::SHIFT, Key::Char('p')).normalized(),
KeyChord::new(Mods::CTRL, Key::Char('P'))
);
assert_eq!(
KeyChord::new(Mods::SHIFT, Key::Char('с')).normalized(),
KeyChord::plain(Key::Char('С'))
);
}
#[test]
fn only_cased_single_uppercase_letters_fold() {
for k in [Key::Char('?'), Key::Char('1'), Key::Char(' ')] {
let c = KeyChord::new(Mods::SHIFT, k);
assert_eq!(c.normalized(), c);
}
let eszett = KeyChord::new(Mods::SHIFT, Key::Char('ß'));
assert_eq!(eszett.normalized(), eszett);
let f5 = KeyChord::new(Mods::SHIFT, Key::F(5));
assert_eq!(f5.normalized(), f5);
let cjk = KeyChord::new(Mods::SHIFT, Key::Char('中'));
assert_eq!(cjk.normalized(), cjk);
}
#[test]
fn means_char_is_the_letter_matching_predicate() {
assert!(KeyEvent::plain(Key::Char('A')).means_char('A'));
assert!(KeyEvent::new(Key::Char('a'), Mods::SHIFT).means_char('A'));
assert!(KeyEvent::new(Key::Char('A'), Mods::SHIFT).means_char('A'));
assert!(!KeyEvent::plain(Key::Char('a')).means_char('A'));
assert!(KeyEvent::plain(Key::Char('a')).means_char('a'));
assert!(!KeyEvent::new(Key::Char('a'), Mods::SHIFT).means_char('a'));
assert!(!KeyEvent::plain(Key::Char('A')).means_char('a'));
assert!(!KeyEvent::new(Key::Char('a'), Mods::CTRL).means_char('a'));
}
}