⎇ 5227fbf1abfb2a437968ebc63692560c3a929230
keyboard/src/main/java/com/rizoma/trazo/model/KeyLayout.kt
Ñawpa willay
ch'uya
Trazo: arreglar cursor al reposicionar + mayúsculas en R↑ y columna reordenada
1package com.rizoma.trazo.model
2
3import kotlin.math.atan2
4import kotlin.math.hypot
5
6/** Las 9 direcciones de una tecla: centro (toque) + 8 swipes. */
7enum class Dir { C, N, NE, E, SE, S, SW, W, NW }
8
9/**
10 * Una tecla del grid 3x3. [center] es el toque; [swipes] mapea cada dirección
11 * de deslizamiento a su carácter. Todo es dato → la distribución se ajusta sin tocar UI.
12 */
13data class KeyDef(
14 val center: Char,
15 val swipes: Map<Dir, Char> = emptyMap(),
16 /** Dirección de swipe que activa mayúsculas en vez de emitir carácter (la R↑). */
17 val shiftDir: Dir? = null,
18) {
19 fun charFor(dir: Dir): Char? = if (dir == Dir.C) center else swipes[dir]
20}
21
22object Layouts {
23
24 /**
25 * Letras — layout MessagEase ESPAÑOL AUTÉNTICO, mapeado 1:1 desde el teclado
26 * oficial de Exideas (Spanish). Toques A N I / D O R / T E S.
27 * · Acentos en swipe sobre su vocal: á(A↙) í(I↑) é(E←) ó(T↓) ú(T↑).
28 * · ñ en N↑, l en N↓. ü y ç en la tecla D. v en A↘.
29 * · O es el hub: q u p / c · b / g h j (la h, antes tecla propia, va en O↓).
30 * · Puntuación frecuente en la E. La R lleva mayús (shift) con trazo ↑ en la UI.
31 *
32 * Overlay de símbolos en modo letra: cada dirección SIN letra recibe el mismo símbolo
33 * que esa posición de tecla tiene en [symbols] (espejo posicional → el símbolo queda en
34 * el mismo sitio en ambas capas). Los que chocan con una letra viven solo en ?123.
35 * Excepción español: ¿ ¡ chocan con x/l, así que se reubican juntos en la tecla I.
36 */
37 val letters: List<KeyDef> = listOf(
38 // fila superior: A N I (símbolos espejo de 1 2 3)
39 KeyDef('a', mapOf(Dir.S to 'á', Dir.SE to 'v', Dir.SW to '$', Dir.E to '-')),
40 KeyDef(
41 'n',
42 mapOf(
43 Dir.N to 'ñ', Dir.S to 'l',
44 Dir.NW to '`', Dir.NE to '´', Dir.W to '+', Dir.E to '!', Dir.SW to '/', Dir.SE to '\\',
45 ),
46 ),
47 KeyDef(
48 'i',
49 mapOf(
50 Dir.NW to 'í', Dir.SW to 'x',
51 Dir.W to '?', Dir.S to '=', Dir.SE to '€', Dir.E to '¿', Dir.NE to '¡',
52 ),
53 ),
54 // fila media: D O R (símbolos espejo de 4 5 6)
55 KeyDef(
56 'd',
57 mapOf(
58 Dir.N to 'ü', Dir.E to 'k', Dir.S to 'ç',
59 Dir.NW to '{', Dir.NE to '%', Dir.W to '(', Dir.SW to '[', Dir.SE to '_',
60 ),
61 ),
62 KeyDef(
63 'o',
64 mapOf(
65 Dir.NW to 'q', Dir.N to 'u', Dir.NE to 'p', Dir.E to 'b',
66 Dir.SE to 'j', Dir.S to 'h', Dir.SW to 'g', Dir.W to 'c',
67 ),
68 ),
69 KeyDef(
70 'r',
71 mapOf(
72 Dir.W to 'm',
73 Dir.NW to '|', Dir.NE to '}', Dir.E to ')', Dir.SW to '@', Dir.SE to ']',
74 ),
75 shiftDir = Dir.N, // R↑ = mayúsculas (OFF→ON→LOCK)
76 ),
77 // fila inferior: T E S (símbolos espejo de 7 8 9)
78 KeyDef(
79 't',
80 mapOf(
81 Dir.N to 'ú', Dir.NE to 'y', Dir.S to 'ó',
82 Dir.NW to '~', Dir.W to '<', Dir.E to '*', Dir.SE to '\t',
83 ),
84 ),
85 KeyDef(
86 'e',
87 mapOf(
88 Dir.N to 'w', Dir.W to 'é', Dir.E to 'z',
89 Dir.NE to '\'', Dir.SE to ':', Dir.S to '.', Dir.SW to ',', Dir.NW to '"',
90 ),
91 ),
92 KeyDef(
93 's',
94 mapOf(
95 Dir.NW to 'f', Dir.N to '&',
96 Dir.NE to '°', Dir.W to '#', Dir.E to '>', Dir.SW to ';',
97 ),
98 ),
99 )
100
101 /**
102 * Números y símbolos — capa MessagEase ESPAÑOL AUTÉNTICA (captura oficial de Exideas).
103 * Dígitos 1-9 en marcador telefónico; cada símbolo en su dirección de trazo original.
104 * Desviaciones respecto a la captura (esta app no tiene celda 0 dedicada como MessagEase):
105 * · 0 → 5↓ (la tecla 5 va libre; toca 5 y desliza abajo).
106 * · ¿ ¡ no salen en la captura pero el español los exige: ¡ en 2↓, ¿ en 3↙.
107 */
108 val symbols: List<KeyDef> = listOf(
109 // fila 1: 1 2 3
110 KeyDef('1', mapOf(Dir.E to '-', Dir.SW to '$')),
111 KeyDef(
112 '2',
113 mapOf(
114 Dir.NW to '`', Dir.N to '^', Dir.NE to '´', Dir.W to '+',
115 Dir.E to '!', Dir.S to '¡', Dir.SW to '/', Dir.SE to '\\',
116 ),
117 ),
118 KeyDef('3', mapOf(Dir.W to '?', Dir.SW to '¿', Dir.S to '=', Dir.SE to '€')),
119 // fila 2: 4 5 6
120 KeyDef('4', mapOf(Dir.NW to '{', Dir.NE to '%', Dir.W to '(', Dir.SW to '[', Dir.SE to '_')),
121 KeyDef('5', mapOf(Dir.S to '0')),
122 KeyDef('6', mapOf(Dir.NW to '|', Dir.NE to '}', Dir.E to ')', Dir.SW to '@', Dir.SE to ']')),
123 // fila 3: 7 8 9
124 KeyDef('7', mapOf(Dir.NW to '~', Dir.N to '¨', Dir.W to '<', Dir.E to '*', Dir.SE to '\t')),
125 KeyDef('8', mapOf(Dir.NW to '"', Dir.NE to '\'', Dir.SW to ',', Dir.S to '.', Dir.SE to ':')),
126 KeyDef('9', mapOf(Dir.N to '&', Dir.NE to '°', Dir.W to '#', Dir.E to '>', Dir.SW to ';')),
127 )
128}
129
130/** Geometría: convierte el delta de un gesto en una de las 9 direcciones. */
131fun directionOf(dx: Float, dy: Float, tapRadiusPx: Float): Dir {
132 if (hypot(dx, dy) < tapRadiusPx) return Dir.C
133 var deg = Math.toDegrees(atan2(dy.toDouble(), dx.toDouble())) // 0=E, 90=S (y hacia abajo)
134 if (deg < 0) deg += 360.0
135 // 8 sectores de 45° centrados en cada eje.
136 val order = arrayOf(Dir.E, Dir.SE, Dir.S, Dir.SW, Dir.W, Dir.NW, Dir.N, Dir.NE)
137 val idx = (((deg + 22.5) / 45.0).toInt()) % 8
138 return order[idx]
139}