package com.rizoma.trazo.model
import kotlin.math.atan2
import kotlin.math.hypot
/** Las 9 direcciones de una tecla: centro (toque) + 8 swipes. */
enum class Dir { C, N, NE, E, SE, S, SW, W, NW }
/**
* Una tecla del grid 3x3. [center] es el toque; [swipes] mapea cada dirección
* de deslizamiento a su carácter. Todo es dato → la distribución se ajusta sin tocar UI.
*/
data class KeyDef(
val center: Char,
val swipes: Map
= emptyMap(),
/** Dirección de swipe que activa mayúsculas en vez de emitir carácter (la R↑). */
val shiftDir: Dir? = null,
) {
fun charFor(dir: Dir): Char? = if (dir == Dir.C) center else swipes[dir]
}
object Layouts {
/**
* Letras — layout MessagEase ESPAÑOL AUTÉNTICO, mapeado 1:1 desde el teclado
* oficial de Exideas (Spanish). Toques A N I / D O R / T E S.
* · Acentos en swipe sobre su vocal: á(A↙) í(I↑) é(E←) ó(T↓) ú(T↑).
* · ñ en N↑, l en N↓. ü y ç en la tecla D. v en A↘.
* · O es el hub: q u p / c · b / g h j (la h, antes tecla propia, va en O↓).
* · Puntuación frecuente en la E. La R lleva mayús (shift) con trazo ↑ en la UI.
*
* Overlay de símbolos en modo letra: cada dirección SIN letra recibe el mismo símbolo
* que esa posición de tecla tiene en [symbols] (espejo posicional → el símbolo queda en
* el mismo sitio en ambas capas). Los que chocan con una letra viven solo en ?123.
* Excepción español: ¿ ¡ chocan con x/l, así que se reubican juntos en la tecla I.
*/
val letters: List = listOf(
// fila superior: A N I (símbolos espejo de 1 2 3)
KeyDef('a', mapOf(Dir.S to 'á', Dir.SE to 'v', Dir.SW to '$', Dir.E to '-')),
KeyDef(
'n',
mapOf(
Dir.N to 'ñ', Dir.S to 'l',
Dir.NW to '`', Dir.NE to '´', Dir.W to '+', Dir.E to '!', Dir.SW to '/', Dir.SE to '\\',
),
),
KeyDef(
'i',
mapOf(
Dir.NW to 'í', Dir.SW to 'x',
Dir.W to '?', Dir.S to '=', Dir.SE to '€', Dir.E to '¿', Dir.NE to '¡',
),
),
// fila media: D O R (símbolos espejo de 4 5 6)
KeyDef(
'd',
mapOf(
Dir.N to 'ü', Dir.E to 'k', Dir.S to 'ç',
Dir.NW to '{', Dir.NE to '%', Dir.W to '(', Dir.SW to '[', Dir.SE to '_',
),
),
KeyDef(
'o',
mapOf(
Dir.NW to 'q', Dir.N to 'u', Dir.NE to 'p', Dir.E to 'b',
Dir.SE to 'j', Dir.S to 'h', Dir.SW to 'g', Dir.W to 'c',
),
),
KeyDef(
'r',
mapOf(
Dir.W to 'm',
Dir.NW to '|', Dir.NE to '}', Dir.E to ')', Dir.SW to '@', Dir.SE to ']',
),
shiftDir = Dir.N, // R↑ = mayúsculas (OFF→ON→LOCK)
),
// fila inferior: T E S (símbolos espejo de 7 8 9)
KeyDef(
't',
mapOf(
Dir.N to 'ú', Dir.NE to 'y', Dir.S to 'ó',
Dir.NW to '~', Dir.W to '<', Dir.E to '*', Dir.SE to '\t',
),
),
KeyDef(
'e',
mapOf(
Dir.N to 'w', Dir.W to 'é', Dir.E to 'z',
Dir.NE to '\'', Dir.SE to ':', Dir.S to '.', Dir.SW to ',', Dir.NW to '"',
),
),
KeyDef(
's',
mapOf(
Dir.NW to 'f', Dir.N to '&',
Dir.NE to '°', Dir.W to '#', Dir.E to '>', Dir.SW to ';',
),
),
)
/**
* Números y símbolos — capa MessagEase ESPAÑOL AUTÉNTICA (captura oficial de Exideas).
* Dígitos 1-9 en marcador telefónico; cada símbolo en su dirección de trazo original.
* Desviaciones respecto a la captura (esta app no tiene celda 0 dedicada como MessagEase):
* · 0 → 5↓ (la tecla 5 va libre; toca 5 y desliza abajo).
* · ¿ ¡ no salen en la captura pero el español los exige: ¡ en 2↓, ¿ en 3↙.
*/
val symbols: List = listOf(
// fila 1: 1 2 3
KeyDef('1', mapOf(Dir.E to '-', Dir.SW to '$')),
KeyDef(
'2',
mapOf(
Dir.NW to '`', Dir.N to '^', Dir.NE to '´', Dir.W to '+',
Dir.E to '!', Dir.S to '¡', Dir.SW to '/', Dir.SE to '\\',
),
),
KeyDef('3', mapOf(Dir.W to '?', Dir.SW to '¿', Dir.S to '=', Dir.SE to '€')),
// fila 2: 4 5 6
KeyDef('4', mapOf(Dir.NW to '{', Dir.NE to '%', Dir.W to '(', Dir.SW to '[', Dir.SE to '_')),
KeyDef('5', mapOf(Dir.S to '0')),
KeyDef('6', mapOf(Dir.NW to '|', Dir.NE to '}', Dir.E to ')', Dir.SW to '@', Dir.SE to ']')),
// fila 3: 7 8 9
KeyDef('7', mapOf(Dir.NW to '~', Dir.N to '¨', Dir.W to '<', Dir.E to '*', Dir.SE to '\t')),
KeyDef('8', mapOf(Dir.NW to '"', Dir.NE to '\'', Dir.SW to ',', Dir.S to '.', Dir.SE to ':')),
KeyDef('9', mapOf(Dir.N to '&', Dir.NE to '°', Dir.W to '#', Dir.E to '>', Dir.SW to ';')),
)
}
/** Geometría: convierte el delta de un gesto en una de las 9 direcciones. */
fun directionOf(dx: Float, dy: Float, tapRadiusPx: Float): Dir {
if (hypot(dx, dy) < tapRadiusPx) return Dir.C
var deg = Math.toDegrees(atan2(dy.toDouble(), dx.toDouble())) // 0=E, 90=S (y hacia abajo)
if (deg < 0) deg += 360.0
// 8 sectores de 45° centrados en cada eje.
val order = arrayOf(Dir.E, Dir.SE, Dir.S, Dir.SW, Dir.W, Dir.NW, Dir.N, Dir.NE)
val idx = (((deg + 22.5) / 45.0).toInt()) % 8
return order[idx]
}