tawasuyu

sergio/humanoid

HTML 13TOML 10 Definitions

⎇ 8380e44a0947bafb169773353ce2b2036aff9e70 keyboard/src/main/java/com/rizoma/trazo/ui/KeyCell.kt 94 lines · 3.4 KB History raw

Trazo: teclado MessagEase remasterizado (IME v0.1) e3dd167a1 · sergio ·

1package com.rizoma.trazo.ui
2
3import androidx.compose.foundation.background
4import androidx.compose.foundation.border
5import androidx.compose.foundation.gestures.awaitEachGesture
6import androidx.compose.foundation.gestures.awaitFirstDown
7import androidx.compose.foundation.layout.Box
8import androidx.compose.foundation.layout.fillMaxSize
9import androidx.compose.foundation.layout.padding
10import androidx.compose.foundation.shape.RoundedCornerShape
11import androidx.compose.material3.Text
12import androidx.compose.runtime.Composable
13import androidx.compose.ui.Alignment
14import androidx.compose.ui.Modifier
15import androidx.compose.ui.draw.clip
16import androidx.compose.ui.input.pointer.pointerInput
17import androidx.compose.ui.platform.LocalDensity
18import androidx.compose.ui.text.font.FontWeight
19import androidx.compose.ui.unit.dp
20import androidx.compose.ui.unit.sp
21import com.rizoma.trazo.model.Dir
22import com.rizoma.trazo.model.KeyDef
23import com.rizoma.trazo.model.directionOf
24import com.rizoma.trazo.ui.theme.NeonVerde
25import com.rizoma.trazo.ui.theme.Tecla
26import com.rizoma.trazo.ui.theme.Tenue
27
28private val hintAlignments = mapOf(
29    Dir.N to Alignment.TopCenter,
30    Dir.NE to Alignment.TopEnd,
31    Dir.E to Alignment.CenterEnd,
32    Dir.SE to Alignment.BottomEnd,
33    Dir.S to Alignment.BottomCenter,
34    Dir.SW to Alignment.BottomStart,
35    Dir.W to Alignment.CenterStart,
36    Dir.NW to Alignment.TopStart,
37)
38
39@Composable
40fun KeyCell(
41    key: KeyDef,
42    shifted: Boolean,
43    onChar: (Char) -> Unit,
44    modifier: Modifier = Modifier,
45) {
46    val tapRadiusPx = with(LocalDensity.current) { 22.dp.toPx() }
47
48    Box(
49        modifier
50            .padding(3.dp)
51            .clip(RoundedCornerShape(14.dp))
52            .background(Tecla)
53            .border(1.dp, NeonVerde.copy(alpha = 0.10f), RoundedCornerShape(14.dp))
54            .pointerInput(key, shifted) {
55                awaitEachGesture {
56                    val down = awaitFirstDown()
57                    var current = down.position
58                    while (true) {
59                        val event = awaitPointerEvent()
60                        val change = event.changes.firstOrNull { it.id == down.id } ?: event.changes.first()
61                        current = change.position
62                        change.consume()
63                        if (!change.pressed) break
64                    }
65                    val dx = current.x - down.position.x
66                    val dy = current.y - down.position.y
67                    val dir = directionOf(dx, dy, tapRadiusPx)
68                    key.charFor(dir)?.let { raw -> onChar(raw) }
69                }
70            },
71    ) {
72        // Pistas de swipe (tenues) en los bordes.
73        key.swipes.forEach { (dir, ch) ->
74            val align = hintAlignments[dir] ?: return@forEach
75            Text(
76                text = glyph(ch, shifted),
77                color = Tenue,
78                fontSize = 11.sp,
79                modifier = Modifier.align(align).padding(horizontal = 6.dp, vertical = 3.dp),
80            )
81        }
82        // Carácter central.
83        Text(
84            text = glyph(key.center, shifted),
85            color = androidx.compose.ui.graphics.Color.White,
86            fontSize = 22.sp,
87            fontWeight = FontWeight.Medium,
88            modifier = Modifier.align(Alignment.Center),
89        )
90    }
91}
92
93private fun glyph(c: Char, shifted: Boolean): String =
94    if (shifted) c.uppercaseChar().toString() else c.toString()