package com.rizoma.trazo.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.rizoma.trazo.ime.KeyboardController import com.rizoma.trazo.ime.Layer import com.rizoma.trazo.ime.ShiftState import com.rizoma.trazo.ui.theme.NeonCian import com.rizoma.trazo.ui.theme.NeonRosa import com.rizoma.trazo.ui.theme.NeonVerde import com.rizoma.trazo.ui.theme.Tecla import com.rizoma.trazo.ui.theme.Tenue import com.rizoma.trazo.ui.theme.Vacio import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable fun KeyboardScreen(controller: KeyboardController) { Column( Modifier .fillMaxWidth() .background(Vacio) .padding(4.dp), ) { SuggestionBar(controller.suggestions, onPick = controller::commitSuggestion) val keys = controller.keys for (row in 0 until 3) { Row(Modifier.fillMaxWidth().height(70.dp)) { for (col in 0 until 3) { KeyCell( key = keys[row * 3 + col], shifted = controller.shifted, onChar = controller::onChar, modifier = Modifier.weight(1f).fillMaxHeight(), ) } } } Row(Modifier.fillMaxWidth().height(52.dp)) { val shiftAccent = when (controller.shift) { ShiftState.OFF -> Tenue ShiftState.ON -> NeonVerde ShiftState.LOCK -> NeonRosa } FuncKey("⇧", shiftAccent, Modifier.weight(1f), onClick = controller::onShift) FuncKey( if (controller.layer == Layer.LETTERS) "?123" else "ABC", NeonCian, Modifier.weight(1f), onClick = controller::toggleLayer, ) SpaceKey(Modifier.weight(3f), onSpace = controller::onSpace, onCursor = controller::moveCursor) BackspaceKey(Modifier.weight(1f), onBackspace = controller::onBackspace) FuncKey("↵", NeonVerde, Modifier.weight(1f), onClick = controller::onEnter) } } } @Composable private fun SuggestionBar(suggestions: List, onPick: (String) -> Unit) { Row( Modifier.fillMaxWidth().height(40.dp).padding(horizontal = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { suggestions.forEach { word -> Box( Modifier .weight(1f) .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(10.dp)) .clickable { onPick(word) }, contentAlignment = Alignment.Center, ) { Text( text = word, color = NeonVerde, fontSize = 15.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, ) } } } } @Composable private fun FuncKey(label: String, accent: Color, modifier: Modifier, onClick: () -> Unit) { Box( modifier .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(14.dp)) .background(Tecla) .clickable(onClick = onClick), contentAlignment = Alignment.Center, ) { Text( text = label, color = accent, fontSize = if (label.length > 2) 14.sp else 20.sp, fontWeight = FontWeight.Medium, ) } } /** Espacio con swipe horizontal para mover el cursor; toque = espacio. */ @Composable private fun SpaceKey(modifier: Modifier, onSpace: () -> Unit, onCursor: (Int) -> Unit) { val step = with(LocalDensity.current) { 16.dp.toPx() } Box( modifier .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(14.dp)) .background(Tecla) .pointerInput(Unit) { awaitEachGesture { val down = awaitFirstDown() var accum = 0f var moved = false while (true) { val event = awaitPointerEvent() val change = event.changes.firstOrNull { it.id == down.id } ?: break accum += change.positionChange().x change.consume() while (accum >= step) { onCursor(1); accum -= step; moved = true } while (accum <= -step) { onCursor(-1); accum += step; moved = true } if (!change.pressed) break } if (!moved) onSpace() } }, contentAlignment = Alignment.Center, ) { Text("espacio", color = Tenue, fontSize = 14.sp) } } /** Borrado con repetición al mantener pulsado. */ @Composable private fun BackspaceKey(modifier: Modifier, onBackspace: () -> Unit) { val scope = rememberCoroutineScope() Box( modifier .fillMaxHeight() .padding(3.dp) .clip(RoundedCornerShape(14.dp)) .background(Tecla) .pointerInput(Unit) { detectTapGestures( onPress = { onBackspace() val job = scope.launch { delay(380) while (true) { onBackspace() delay(55) } } try { awaitRelease() } finally { job.cancel() } }, ) }, contentAlignment = Alignment.Center, ) { Text("⌫", color = Color.White, fontSize = 20.sp) } }