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}