1use crate::draw_ctx::DrawCtx;
9use crate::geometry::{Point, Rect};
10
11use super::key::{KeyAction, KeyCap, KeyGlyph, PaintedKey};
12use super::style::Style;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Layer {
22 Letters,
23 Shifted,
24 Numbers,
25 Symbols,
26}
27
28#[derive(Debug, Clone)]
32struct KeySpec {
33 width_units: f64,
34 cap: KeyCap,
35 action: KeyAction,
36 kind: KeyKind,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40enum KeyKind {
41 Letter,
43 Utility,
46 Return,
48}
49
50pub struct Layout {
53 rows: Vec<Vec<KeySpec>>,
54}
55
56impl Layout {
57 pub fn for_layer(layer: Layer) -> Self {
58 match layer {
59 Layer::Letters => letters_layer(false),
60 Layer::Shifted => letters_layer(true),
61 Layer::Numbers => numbers_layer(),
62 Layer::Symbols => symbols_layer(),
63 }
64 }
65
66 pub fn compute_panel_height(&self, _viewport_width: f64, style: &Style) -> f64 {
70 let rows = self.rows.len() as f64;
71 style.panel_padding_top
72 + style.panel_padding_bottom
73 + rows * style.row_height
74 + (rows - 1.0).max(0.0) * style.key_v_gap
75 }
76
77 pub fn paint(
80 &self,
81 ctx: &mut dyn DrawCtx,
82 panel: Rect,
83 style: &Style,
84 active_layer: Layer,
85 ) -> Vec<PaintedKey> {
86 let mut painted = Vec::with_capacity(self.rows.iter().map(|r| r.len()).sum());
87
88 let inner_x = panel.x + style.panel_padding_horizontal;
89 let inner_w = panel.width - 2.0 * style.panel_padding_horizontal;
90
91 let mut row_top_y = panel.y + panel.height - style.panel_padding_top;
95
96 for (row_index, row) in self.rows.iter().enumerate() {
97 let row_bottom_y = row_top_y - style.row_height;
98 let total_units: f64 = row.iter().map(|k| k.width_units).sum();
99 let total_gaps = (row.len() as f64 - 1.0).max(0.0) * style.key_h_gap;
100 let key_unit_width = (inner_w - total_gaps) / total_units.max(0.001);
101
102 let mut cursor_x = inner_x;
103 for spec in row.iter() {
104 let kw = spec.width_units * key_unit_width;
105 let rect = Rect::new(cursor_x, row_bottom_y, kw, style.row_height);
106
107 let pressed = false; paint_key(ctx, rect, spec, pressed, style, active_layer);
109
110 painted.push(PaintedKey {
111 rect,
112 action: spec.action,
113 cap: spec.cap.clone(),
114 });
115 cursor_x += kw + style.key_h_gap;
116 }
117
118 if row_index + 1 < self.rows.len() {
119 row_top_y = row_bottom_y - style.key_v_gap;
120 }
121 }
122
123 painted
124 }
125}
126
127fn letters_layer(shifted: bool) -> Layout {
132 let case = |lower: char, upper: char| if shifted { upper } else { lower };
133
134 let row_keys = |letters: &[(char, char)]| -> Vec<KeySpec> {
135 letters
136 .iter()
137 .map(|(lo, up)| {
138 let c = case(*lo, *up);
139 KeySpec {
140 width_units: 1.0,
141 cap: KeyCap::Text(c.to_string()),
142 action: KeyAction::Char(c),
143 kind: KeyKind::Letter,
144 }
145 })
146 .collect()
147 };
148
149 let mut rows: Vec<Vec<KeySpec>> = Vec::with_capacity(4);
150 rows.push(row_keys(&[
151 ('q', 'Q'),
152 ('w', 'W'),
153 ('e', 'E'),
154 ('r', 'R'),
155 ('t', 'T'),
156 ('y', 'Y'),
157 ('u', 'U'),
158 ('i', 'I'),
159 ('o', 'O'),
160 ('p', 'P'),
161 ]));
162
163 let row2 = row_keys(&[
164 ('a', 'A'),
165 ('s', 'S'),
166 ('d', 'D'),
167 ('f', 'F'),
168 ('g', 'G'),
169 ('h', 'H'),
170 ('j', 'J'),
171 ('k', 'K'),
172 ('l', 'L'),
173 ]);
174 rows.push(row2);
179
180 let mut row3: Vec<KeySpec> = Vec::with_capacity(11);
181 row3.push(KeySpec {
182 width_units: 1.5,
183 cap: KeyCap::Glyph(KeyGlyph::Shift),
184 action: KeyAction::Switch(if shifted {
185 Layer::Letters
186 } else {
187 Layer::Shifted
188 }),
189 kind: KeyKind::Utility,
190 });
191 row3.extend(row_keys(&[
192 ('z', 'Z'),
193 ('x', 'X'),
194 ('c', 'C'),
195 ('v', 'V'),
196 ('b', 'B'),
197 ('n', 'N'),
198 ('m', 'M'),
199 ]));
200 row3.push(KeySpec {
201 width_units: 1.5,
202 cap: KeyCap::Glyph(KeyGlyph::Backspace),
203 action: KeyAction::Backspace,
204 kind: KeyKind::Utility,
205 });
206 rows.push(row3);
207
208 rows.push(action_row(if shifted {
209 Layer::Shifted
210 } else {
211 Layer::Letters
212 }));
213 Layout { rows }
214}
215
216fn numbers_layer() -> Layout {
217 let digit = |c: char| KeySpec {
218 width_units: 1.0,
219 cap: KeyCap::Text(c.to_string()),
220 action: KeyAction::Char(c),
221 kind: KeyKind::Letter,
222 };
223
224 let mut rows = Vec::with_capacity(4);
225 rows.push(
226 ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
227 .iter()
228 .map(|c| digit(*c))
229 .collect(),
230 );
231 rows.push(
232 ['-', '/', ':', ';', '(', ')', '$', '&', '@', '"']
233 .iter()
234 .map(|c| digit(*c))
235 .collect(),
236 );
237
238 let mut row3 = Vec::with_capacity(9);
239 row3.push(KeySpec {
240 width_units: 1.5,
241 cap: KeyCap::Text("#+=".to_string()),
242 action: KeyAction::Switch(Layer::Symbols),
243 kind: KeyKind::Utility,
244 });
245 for c in ['.', ',', '?', '!', '\''] {
246 row3.push(digit(c));
247 }
248 row3.push(KeySpec {
249 width_units: 1.5,
250 cap: KeyCap::Glyph(KeyGlyph::Backspace),
251 action: KeyAction::Backspace,
252 kind: KeyKind::Utility,
253 });
254 rows.push(row3);
255
256 rows.push(action_row(Layer::Numbers));
257 Layout { rows }
258}
259
260fn symbols_layer() -> Layout {
261 let sym = |c: char| KeySpec {
262 width_units: 1.0,
263 cap: KeyCap::Text(c.to_string()),
264 action: KeyAction::Char(c),
265 kind: KeyKind::Letter,
266 };
267
268 let mut rows = Vec::with_capacity(4);
269 rows.push(
270 ['[', ']', '{', '}', '#', '%', '^', '*', '+', '=']
271 .iter()
272 .map(|c| sym(*c))
273 .collect(),
274 );
275 rows.push(
276 ['_', '\\', '|', '~', '<', '>', '€', '£', '¥', '·']
277 .iter()
278 .map(|c| sym(*c))
279 .collect(),
280 );
281
282 let mut row3 = Vec::with_capacity(9);
283 row3.push(KeySpec {
284 width_units: 1.5,
285 cap: KeyCap::Text("123".to_string()),
286 action: KeyAction::Switch(Layer::Numbers),
287 kind: KeyKind::Utility,
288 });
289 for c in ['.', ',', '?', '!', '\''] {
290 row3.push(sym(c));
291 }
292 row3.push(KeySpec {
293 width_units: 1.5,
294 cap: KeyCap::Glyph(KeyGlyph::Backspace),
295 action: KeyAction::Backspace,
296 kind: KeyKind::Utility,
297 });
298 rows.push(row3);
299
300 rows.push(action_row(Layer::Symbols));
301 Layout { rows }
302}
303
304fn action_row(current: Layer) -> Vec<KeySpec> {
309 let (mode_label, mode_action) = match current {
310 Layer::Letters | Layer::Shifted => ("123", KeyAction::Switch(Layer::Numbers)),
311 Layer::Numbers | Layer::Symbols => ("ABC", KeyAction::Switch(Layer::Letters)),
312 };
313 vec![
314 KeySpec {
315 width_units: 1.5,
316 cap: KeyCap::Text(mode_label.to_string()),
317 action: mode_action,
318 kind: KeyKind::Utility,
319 },
320 KeySpec {
321 width_units: 1.0,
322 cap: KeyCap::Glyph(KeyGlyph::DismissDown),
323 action: KeyAction::Dismiss,
324 kind: KeyKind::Utility,
325 },
326 KeySpec {
327 width_units: 5.0,
328 cap: KeyCap::Text("space".to_string()),
329 action: KeyAction::Space,
330 kind: KeyKind::Letter,
331 },
332 KeySpec {
333 width_units: 2.0,
334 cap: KeyCap::Glyph(KeyGlyph::Return),
335 action: KeyAction::Enter,
336 kind: KeyKind::Return,
337 },
338 ]
339}
340
341fn paint_key(
346 ctx: &mut dyn DrawCtx,
347 rect: Rect,
348 spec: &KeySpec,
349 pressed: bool,
350 style: &Style,
351 _active_layer: Layer,
352) {
353 let (bg, text_color) = match (spec.kind, pressed) {
354 (KeyKind::Letter, false) => (style.key_face_bg, style.key_face_text),
355 (KeyKind::Letter, true) => (style.key_face_bg_pressed, style.key_face_text_pressed),
356 (KeyKind::Utility, false) => (style.util_key_bg, style.util_key_text),
357 (KeyKind::Utility, true) => (style.util_key_bg_pressed, style.key_face_text_pressed),
358 (KeyKind::Return, false) => (style.return_key_bg, style.return_key_text),
359 (KeyKind::Return, true) => (style.return_key_bg_pressed, style.return_key_text),
360 };
361
362 ctx.set_fill_color(style.key_shadow);
364 ctx.begin_path();
365 ctx.rounded_rect(
366 rect.x,
367 rect.y + style.key_shadow_offset_y,
368 rect.width,
369 rect.height,
370 style.key_corner_radius,
371 );
372 ctx.fill();
373
374 ctx.set_fill_color(bg);
375 ctx.begin_path();
376 ctx.rounded_rect(
377 rect.x,
378 rect.y,
379 rect.width,
380 rect.height,
381 style.key_corner_radius,
382 );
383 ctx.fill();
384
385 ctx.set_fill_color(text_color);
386 let center = Point::new(rect.x + rect.width / 2.0, rect.y + rect.height / 2.0);
387
388 match &spec.cap {
389 KeyCap::Text(text) => {
390 let font_size = if matches!(spec.kind, KeyKind::Letter) && text.chars().count() == 1 {
391 style.letter_font_size
392 } else {
393 style.utility_font_size
394 };
395 ctx.set_font_size(font_size);
396 let approx_width = text.chars().count() as f64 * font_size * 0.55;
403 ctx.fill_text(
404 text,
405 center.x - approx_width / 2.0,
406 center.y - font_size * 0.3,
407 );
408 }
409 KeyCap::Glyph(glyph) => {
410 paint_glyph(ctx, center, style, *glyph, text_color);
411 }
412 }
413}
414
415fn paint_glyph(
416 ctx: &mut dyn DrawCtx,
417 center: Point,
418 style: &Style,
419 glyph: super::key::KeyGlyph,
420 color: crate::color::Color,
421) {
422 use super::key::KeyGlyph;
423 let r = style.utility_font_size * 0.55;
424 ctx.set_stroke_color(color);
425 ctx.set_fill_color(color);
426 ctx.set_line_width(2.0);
427 match glyph {
428 KeyGlyph::Backspace => {
429 ctx.begin_path();
430 ctx.move_to(center.x - r, center.y);
432 ctx.line_to(center.x - r * 0.4, center.y + r * 0.7);
433 ctx.line_to(center.x + r * 0.9, center.y + r * 0.7);
434 ctx.line_to(center.x + r * 0.9, center.y - r * 0.7);
435 ctx.line_to(center.x - r * 0.4, center.y - r * 0.7);
436 ctx.close_path();
437 ctx.stroke();
438 ctx.begin_path();
440 ctx.move_to(center.x - r * 0.05, center.y - r * 0.35);
441 ctx.line_to(center.x + r * 0.55, center.y + r * 0.35);
442 ctx.move_to(center.x - r * 0.05, center.y + r * 0.35);
443 ctx.line_to(center.x + r * 0.55, center.y - r * 0.35);
444 ctx.stroke();
445 }
446 KeyGlyph::Shift => {
447 ctx.begin_path();
448 ctx.move_to(center.x, center.y + r);
449 ctx.line_to(center.x - r, center.y);
450 ctx.line_to(center.x - r * 0.4, center.y);
451 ctx.line_to(center.x - r * 0.4, center.y - r * 0.6);
452 ctx.line_to(center.x + r * 0.4, center.y - r * 0.6);
453 ctx.line_to(center.x + r * 0.4, center.y);
454 ctx.line_to(center.x + r, center.y);
455 ctx.close_path();
456 ctx.stroke();
457 }
458 KeyGlyph::DismissDown => {
459 ctx.begin_path();
460 ctx.move_to(center.x - r, center.y + r * 0.3);
461 ctx.line_to(center.x, center.y - r * 0.3);
462 ctx.line_to(center.x + r, center.y + r * 0.3);
463 ctx.stroke();
464 ctx.begin_path();
465 ctx.move_to(center.x - r, center.y - r * 0.6);
466 ctx.line_to(center.x + r, center.y - r * 0.6);
467 ctx.stroke();
468 }
469 KeyGlyph::Return => {
470 ctx.begin_path();
471 ctx.move_to(center.x + r, center.y + r * 0.6);
472 ctx.line_to(center.x + r, center.y - r * 0.2);
473 ctx.line_to(center.x - r * 0.5, center.y - r * 0.2);
474 ctx.stroke();
475 ctx.begin_path();
476 ctx.move_to(center.x - r * 0.5, center.y + r * 0.3);
477 ctx.line_to(center.x - r, center.y - r * 0.2);
478 ctx.line_to(center.x - r * 0.5, center.y - r * 0.7);
479 ctx.stroke();
480 }
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn letters_layer_has_four_rows() {
490 let l = Layout::for_layer(Layer::Letters);
491 assert_eq!(l.rows.len(), 4);
492 }
493
494 #[test]
495 fn shift_key_switches_layer() {
496 let l = Layout::for_layer(Layer::Letters);
497 let row3 = &l.rows[2];
498 let shift = &row3[0];
499 match shift.action {
500 KeyAction::Switch(Layer::Shifted) => {}
501 other => panic!("expected Switch(Shifted) on row3[0], got {other:?}"),
502 }
503 }
504
505 #[test]
506 fn shifted_layer_emits_uppercase_chars() {
507 let l = Layout::for_layer(Layer::Shifted);
508 let q = &l.rows[0][0];
510 match q.action {
511 KeyAction::Char('Q') => {}
512 other => panic!("expected Char('Q'), got {other:?}"),
513 }
514 }
515
516 #[test]
517 fn numbers_layer_includes_digits() {
518 let l = Layout::for_layer(Layer::Numbers);
519 let chars: Vec<char> = l.rows[0]
520 .iter()
521 .filter_map(|k| match k.action {
522 KeyAction::Char(c) => Some(c),
523 _ => None,
524 })
525 .collect();
526 for d in ['1', '2', '3', '0'] {
527 assert!(chars.contains(&d), "missing digit {d} in numbers row 1");
528 }
529 }
530}