cadmus_core/view/
key.rs

1use super::icon::ICONS_PIXMAPS;
2use super::BORDER_RADIUS_LARGE;
3use super::{
4    Bus, Event, Hub, Id, KeyboardEvent, RenderData, RenderQueue, TextKind, View, ViewId, ID_FEEDER,
5};
6use crate::color::{Color, KEYBOARD_BG, TEXT_INVERTED_HARD, TEXT_NORMAL};
7use crate::context::Context;
8use crate::device::CURRENT_DEVICE;
9use crate::font::{font_from_style, Fonts, KBD_CHAR, KBD_LABEL};
10use crate::framebuffer::{Framebuffer, UpdateMode};
11use crate::geom::{CornerSpec, LinearDir, Rectangle};
12use crate::gesture::GestureEvent;
13use crate::input::{DeviceEvent, FingerStatus};
14use crate::unit::scale_by_dpi;
15
16#[derive(Copy, Clone, Debug, Eq, PartialEq)]
17pub enum KeyKind {
18    Output(char),
19    Delete(LinearDir),
20    Move(LinearDir),
21    Shift,
22    Return,
23    Combine,
24    Alternate,
25}
26
27use serde::de::{self, Visitor};
28use serde::{Deserialize, Deserializer};
29use std::fmt;
30
31impl<'de> Deserialize<'de> for KeyKind {
32    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33    where
34        D: Deserializer<'de>,
35    {
36        struct FieldVisitor;
37
38        const FIELDS: &[&str] = &[
39            "Shift",
40            "Sft",
41            "Return",
42            "Ret",
43            "Alternate",
44            "Alt",
45            "Combine",
46            "Cmb",
47            "MoveFwd",
48            "MoveF",
49            "MF",
50            "MoveBwd",
51            "MoveB",
52            "MB",
53            "DelFwd",
54            "DelF",
55            "DF",
56            "DelBwd",
57            "DelB",
58            "DB",
59            "Space",
60            "Spc",
61        ];
62
63        impl<'de> Visitor<'de> for FieldVisitor {
64            type Value = KeyKind;
65
66            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
67                formatter.write_str("a key name or a single character")
68            }
69
70            fn visit_str<E>(self, value: &str) -> Result<KeyKind, E>
71            where
72                E: de::Error,
73            {
74                match value {
75                    "Shift" | "Sft" => Ok(KeyKind::Shift),
76                    "Return" | "Ret" => Ok(KeyKind::Return),
77                    "Alternate" | "Alt" => Ok(KeyKind::Alternate),
78                    "Combine" | "Cmb" => Ok(KeyKind::Combine),
79                    "MoveFwd" | "MoveF" | "MF" => Ok(KeyKind::Move(LinearDir::Forward)),
80                    "MoveBwd" | "MoveB" | "MB" => Ok(KeyKind::Move(LinearDir::Backward)),
81                    "DelFwd" | "DelF" | "DF" => Ok(KeyKind::Delete(LinearDir::Forward)),
82                    "DelBwd" | "DelB" | "DB" => Ok(KeyKind::Delete(LinearDir::Backward)),
83                    "Space" | "Spc" => Ok(KeyKind::Output(' ')),
84                    _ => {
85                        if value.chars().count() != 1 {
86                            return Err(serde::de::Error::unknown_field(value, FIELDS));
87                        }
88                        value
89                            .chars()
90                            .next()
91                            .map(KeyKind::Output)
92                            .ok_or_else(|| serde::de::Error::custom("impossible"))
93                    }
94                }
95            }
96        }
97
98        deserializer.deserialize_identifier(FieldVisitor)
99    }
100}
101
102#[derive(Clone, Debug)]
103pub enum KeyLabel {
104    Char(char),
105    Text(&'static str),
106    Icon(&'static str),
107}
108
109impl KeyKind {
110    pub fn is_variable_output(&self) -> bool {
111        matches!(self, KeyKind::Output(ch) if *ch != ' ')
112    }
113
114    pub fn label(self, ratio: f32) -> KeyLabel {
115        match self {
116            KeyKind::Output(ch) => KeyLabel::Char(ch),
117            KeyKind::Delete(dir) => match dir {
118                LinearDir::Forward => KeyLabel::Icon("delete-forward"),
119                LinearDir::Backward => KeyLabel::Icon("delete-backward"),
120            },
121            KeyKind::Move(dir) => match dir {
122                LinearDir::Forward => KeyLabel::Icon(if ratio <= 1.0 {
123                    "move-forward-short"
124                } else {
125                    "move-forward"
126                }),
127                LinearDir::Backward => KeyLabel::Icon(if ratio <= 1.0 {
128                    "move-backward-short"
129                } else {
130                    "move-backward"
131                }),
132            },
133            KeyKind::Shift => {
134                if ratio < 2.0 {
135                    KeyLabel::Icon("shift")
136                } else {
137                    KeyLabel::Text("SHIFT")
138                }
139            }
140            KeyKind::Return => {
141                if ratio < 2.0 {
142                    KeyLabel::Icon("return")
143                } else {
144                    KeyLabel::Text("RETURN")
145                }
146            }
147            KeyKind::Combine => {
148                if ratio <= 1.0 {
149                    KeyLabel::Icon("combine")
150                } else {
151                    KeyLabel::Text("CMB")
152                }
153            }
154            KeyKind::Alternate => {
155                if ratio <= 1.0 {
156                    KeyLabel::Icon("alternate")
157                } else {
158                    KeyLabel::Text("ALT")
159                }
160            }
161        }
162    }
163}
164
165pub struct Key {
166    id: Id,
167    rect: Rectangle,
168    children: Vec<Box<dyn View>>,
169    kind: KeyKind,
170    pressure: u8,
171    active: bool,
172}
173
174impl Key {
175    pub fn new(rect: Rectangle, kind: KeyKind) -> Key {
176        Key {
177            id: ID_FEEDER.next(),
178            rect,
179            children: Vec::new(),
180            kind,
181            pressure: 0,
182            active: false,
183        }
184    }
185
186    pub fn kind(&self) -> &KeyKind {
187        &self.kind
188    }
189
190    pub fn update(&mut self, kind: KeyKind, rq: &mut RenderQueue) {
191        self.kind = kind;
192        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
193    }
194
195    pub fn release(&mut self, rq: &mut RenderQueue) {
196        self.pressure = 0;
197        rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
198    }
199
200    pub fn lock(&mut self) {
201        self.pressure = 2;
202    }
203}
204
205impl View for Key {
206    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, hub, bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
207    fn handle_event(
208        &mut self,
209        evt: &Event,
210        hub: &Hub,
211        bus: &mut Bus,
212        rq: &mut RenderQueue,
213        _context: &mut Context,
214    ) -> bool {
215        match *evt {
216            Event::Device(DeviceEvent::Finger {
217                status, position, ..
218            }) => match status {
219                FingerStatus::Down if self.rect.includes(position) => {
220                    self.active = true;
221                    rq.add(RenderData::no_wait(self.id, self.rect, UpdateMode::Fast));
222                    true
223                }
224                FingerStatus::Up if self.active => {
225                    self.active = false;
226                    rq.add(RenderData::no_wait(self.id, self.rect, UpdateMode::Gui));
227                    true
228                }
229                _ => false,
230            },
231            Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
232                match self.kind {
233                    KeyKind::Shift | KeyKind::Alternate | KeyKind::Combine => {
234                        if self.kind == KeyKind::Combine {
235                            self.pressure = (self.pressure + 2) % 4;
236                        } else {
237                            self.pressure = (self.pressure + 1) % 3;
238                        }
239                        rq.add(RenderData::no_wait(self.id, self.rect, UpdateMode::Gui));
240                    }
241                    _ => (),
242                }
243                bus.push_back(Event::Key(self.kind));
244                true
245            }
246            Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
247                if self.rect.includes(center) =>
248            {
249                match self.kind {
250                    KeyKind::Delete(dir) => {
251                        hub.send(Event::Keyboard(KeyboardEvent::Delete {
252                            target: TextKind::Word,
253                            dir,
254                        }))
255                        .ok();
256                    }
257                    KeyKind::Move(dir) => {
258                        hub.send(Event::Keyboard(KeyboardEvent::Move {
259                            target: TextKind::Word,
260                            dir,
261                        }))
262                        .ok();
263                    }
264                    KeyKind::Output(' ') => {
265                        hub.send(Event::ToggleNear(ViewId::KeyboardLayoutMenu, self.rect))
266                            .ok();
267                    }
268                    _ => (),
269                };
270                true
271            }
272            _ => false,
273        }
274    }
275
276    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
277    fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
278        let dpi = CURRENT_DEVICE.dpi;
279        fb.draw_rectangle(&self.rect, KEYBOARD_BG);
280        let scheme: [Color; 3] = if self.active ^ (self.pressure == 2) {
281            TEXT_INVERTED_HARD
282        } else {
283            TEXT_NORMAL
284        };
285
286        let border_radius = scale_by_dpi(BORDER_RADIUS_LARGE, dpi) as i32;
287        fb.draw_rounded_rectangle(&self.rect, &CornerSpec::Uniform(border_radius), scheme[0]);
288        let ratio = self.rect.width() as f32 / self.rect.height() as f32;
289
290        match self.kind.label(ratio) {
291            KeyLabel::Char(ch) => {
292                let font = font_from_style(fonts, &KBD_CHAR, dpi);
293                let plan = font.plan(&ch.to_string(), None, None);
294                let dx = (self.rect.width() as i32 - plan.width) / 2;
295                let dy = (self.rect.height() - font.x_heights.0) as i32 / 2;
296                let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy);
297                font.render(fb, scheme[1], &plan, pt);
298            }
299            KeyLabel::Text(label) => {
300                let font = font_from_style(fonts, &KBD_LABEL, dpi);
301                let mut plan = font.plan(label, None, None);
302                let letter_spacing = scale_by_dpi(4.0, dpi) as i32;
303                plan.space_out(letter_spacing);
304                let dx = (self.rect.width() as i32 - plan.width) / 2;
305                let dy = (self.rect.height() - font.x_heights.1) as i32 / 2;
306                let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy);
307                font.render(fb, scheme[1], &plan, pt);
308            }
309            KeyLabel::Icon(name) => {
310                let pixmap = ICONS_PIXMAPS.get(name).unwrap();
311                let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2;
312                let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2;
313                let pt = self.rect.min + pt!(dx, dy);
314                fb.draw_blended_pixmap(pixmap, pt, scheme[1]);
315            }
316        }
317    }
318
319    fn rect(&self) -> &Rectangle {
320        &self.rect
321    }
322
323    fn rect_mut(&mut self) -> &mut Rectangle {
324        &mut self.rect
325    }
326
327    fn children(&self) -> &Vec<Box<dyn View>> {
328        &self.children
329    }
330
331    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
332        &mut self.children
333    }
334
335    fn id(&self) -> Id {
336        self.id
337    }
338}