Update text edit mode

This commit is contained in:
Aleksandras Kostarevas 2024-01-09 10:56:14 +02:00
parent b237e49ece
commit adfab75086
4 changed files with 348 additions and 202 deletions

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,9L20,9A2,2 0,0 1,22 11L22,20A2,2 0,0 1,20 22L11,22A2,2 0,0 1,9 20L9,11A2,2 0,0 1,11 9z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M5,15H4a2,2 0,0 1,-2 -2V4a2,2 0,0 1,2 -2h9a2,2 0,0 1,2 2v1"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,27 @@
<vector android:height="24dp" android:viewportHeight="26"
android:viewportWidth="26" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="M1.995,15.995L23.995,15.995"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#00000000"
android:pathData="M24.005,11.995L24.005,15.995"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#00000000"
android:pathData="M5.995,10.995a1,1 0,1 0,2 0a1,1 0,1 0,-2 0z"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#00000000"
android:pathData="M11.995,10.995a1,1 0,1 0,2 0a1,1 0,1 0,-2 0z"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#00000000"
android:pathData="M2.005,11.995L2.005,15.995"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#00000000"
android:pathData="M17.995,10.995a1,1 0,1 0,2 0a1,1 0,1 0,-2 0z"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
</vector>

41
java/res/drawable/cut.xml Normal file
View File

@ -0,0 +1,41 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,6m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M6,18m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M20,4L8.12,15.88"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M14.47,14.48L20,20"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M8.12,8.12L12,12"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View File

@ -3,29 +3,26 @@ package org.futo.inputmethod.latin.uix.actions
import android.view.KeyEvent import android.view.KeyEvent
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
@ -36,6 +33,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.futo.inputmethod.latin.R import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.common.Constants
import org.futo.inputmethod.latin.uix.Action import org.futo.inputmethod.latin.uix.Action
import org.futo.inputmethod.latin.uix.ActionWindow import org.futo.inputmethod.latin.uix.ActionWindow
@ -61,9 +59,43 @@ fun IconWithColor(@DrawableRes iconId: Int, iconColor: Color, modifier: Modifier
} }
@Composable @Composable
fun RepeatableActionKey( fun TogglableKey(
onToggle: (Boolean) -> Unit,
toggled: Boolean,
modifier: Modifier = Modifier,
contents: @Composable (color: Color) -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
LaunchedEffect(isPressed) {
if(isPressed) {
onToggle(!toggled)
}
}
Surface(
modifier = modifier
.padding(4.dp)
.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current,
onClick = { }
),
shape = RoundedCornerShape(8.dp),
color = if(toggled) { MaterialTheme.colorScheme.secondary } else { MaterialTheme.colorScheme.secondaryContainer }
) {
contents(if(toggled) { MaterialTheme.colorScheme.onSecondary } else { MaterialTheme.colorScheme.onSecondaryContainer })
}
}
@Composable
fun ActionKey(
onTrigger: () -> Unit, onTrigger: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
repeatable: Boolean = true,
color: Color = MaterialTheme.colorScheme.primary,
contents: @Composable () -> Unit contents: @Composable () -> Unit
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
@ -72,217 +104,235 @@ fun RepeatableActionKey(
LaunchedEffect(isPressed) { LaunchedEffect(isPressed) {
if(isPressed) { if(isPressed) {
onTrigger() onTrigger()
delay(670L) if(repeatable) {
while(isPressed) { delay(670L)
onTrigger() while (isPressed) {
delay(50L) onTrigger()
delay(50L)
}
} }
} }
} }
Surface( Surface(
modifier = modifier.clickable( modifier = modifier
interactionSource = interactionSource, .padding(4.dp)
indication = null, .clickable(
onClick = { } interactionSource = interactionSource,
), indication = LocalIndication.current,
color = MaterialTheme.colorScheme.primary onClick = { }
),
shape = RoundedCornerShape(8.dp),
color = color
) { ) {
contents() contents()
} }
} }
@Composable @Composable
@Preview(showBackground = true) fun ArrowKeys(modifier: Modifier, sendEvent: (Int) -> Unit) {
fun TextEditScreen(onCodePoint: (Int) -> Unit = { _ -> }, onEvent: (Int, Int) -> Unit = { _, _ -> }) { Row(modifier = modifier) {
var shiftState by remember { mutableStateOf(false) } ActionKey(
var ctrlState by remember { mutableStateOf(false) } modifier = Modifier
.weight(1.0f)
.fillMaxHeight(),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_LEFT) }
) {
IconWithColor(
iconId = R.drawable.arrow_left,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
Column(modifier = Modifier
.weight(1.0f)
.fillMaxHeight()) {
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxWidth(),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_UP) }
) {
IconWithColor(
iconId = R.drawable.arrow_up,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxWidth(),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_DOWN) }
) {
IconWithColor(
iconId = R.drawable.arrow_down,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
}
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxHeight(),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_RIGHT) }
) {
IconWithColor(
iconId = R.drawable.arrow_right,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
}
}
@Composable
fun CtrlShiftMetaKeys(modifier: Modifier, ctrlState: MutableState<Boolean>, shiftState: MutableState<Boolean>) {
Row(modifier = modifier) {
TogglableKey(
onToggle = { ctrlState.value = it },
toggled = ctrlState.value,
modifier = Modifier
.weight(1.0f)
.fillMaxHeight()
) {
IconWithColor(
iconId = R.drawable.ctrl,
iconColor = it
)
}
TogglableKey(
onToggle = { shiftState.value = it },
toggled = shiftState.value,
modifier = Modifier
.weight(1.0f)
.fillMaxHeight()
) {
IconWithColor(
iconId = R.drawable.shift,
iconColor = it
)
}
}
}
@Composable
fun SideKeys(modifier: Modifier, onEvent: (Int, Int) -> Unit, onCodePoint: (Int) -> Unit) {
Column(modifier = modifier) {
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxWidth(),
repeatable = false,
color = MaterialTheme.colorScheme.primaryContainer,
onTrigger = { onEvent(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON) }
) {
IconWithColor(
iconId = R.drawable.copy,
iconColor = MaterialTheme.colorScheme.onPrimaryContainer
)
}
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxWidth(),
repeatable = false,
color = MaterialTheme.colorScheme.primaryContainer,
onTrigger = { onEvent(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON) }
) {
IconWithColor(
iconId = R.drawable.clipboard,
iconColor = MaterialTheme.colorScheme.onPrimaryContainer
)
}
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxWidth(),
repeatable = true,
color = MaterialTheme.colorScheme.primaryContainer,
onTrigger = { onCodePoint(Constants.CODE_DELETE) }
) {
IconWithColor(
iconId = R.drawable.delete,
iconColor = MaterialTheme.colorScheme.onPrimaryContainer
)
}
Row(modifier = Modifier
.weight(1.0f)
.fillMaxWidth()) {
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxHeight(),
repeatable = false,
color = MaterialTheme.colorScheme.primaryContainer,
onTrigger = { onEvent(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON) }
) {
IconWithColor(
iconId = R.drawable.undo,
iconColor = MaterialTheme.colorScheme.onPrimaryContainer
)
}
ActionKey(
modifier = Modifier
.weight(1.0f)
.fillMaxHeight(),
repeatable = false,
color = MaterialTheme.colorScheme.primaryContainer,
onTrigger = { onEvent(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON) }
) {
IconWithColor(
iconId = R.drawable.redo,
iconColor = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
}
}
@Composable
fun TextEditScreen(onCodePoint: (Int) -> Unit, onEvent: (Int, Int) -> Unit) {
val shiftState = remember { mutableStateOf(false) }
val ctrlState = remember { mutableStateOf(false) }
val metaState = 0 or val metaState = 0 or
(if(shiftState) { KeyEvent.META_SHIFT_ON } else { 0 }) or (if(shiftState.value) { KeyEvent.META_SHIFT_ON } else { 0 }) or
(if(ctrlState) { KeyEvent.META_CTRL_ON } else { 0 }) (if(ctrlState.value) { KeyEvent.META_CTRL_ON } else { 0 })
val buttonColorsUntoggled = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
val buttonColorsToggled = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.secondary,
contentColor = MaterialTheme.colorScheme.onSecondary
)
val sendEvent = { keycode: Int -> onEvent(keycode, metaState) } val sendEvent = { keycode: Int -> onEvent(keycode, metaState) }
val keySize = 48.dp Row(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier
Column { .fillMaxHeight()
Row(modifier = Modifier.fillMaxWidth()) { .weight(3.0f)) {
Spacer(modifier = Modifier.weight(1.0f)) ArrowKeys(
Box(
modifier = Modifier modifier = Modifier
.height(keySize * 2) .weight(3.0f)
.width(keySize * 3) .fillMaxWidth(),
) { sendEvent = sendEvent
RepeatableActionKey( )
modifier = Modifier CtrlShiftMetaKeys(
.align(Alignment.TopStart)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_INSERT) }
) {
Text("Ins", modifier = Modifier.padding(4.dp))
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.BottomStart)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DEL) }
) {
Text("Del", modifier = Modifier.padding(4.dp))
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.TopCenter)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_MOVE_HOME) }
) {
Text("Home", modifier = Modifier.padding(4.dp))
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.BottomCenter)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_MOVE_END) }
) {
Text("End", modifier = Modifier.padding(4.dp))
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.TopEnd)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_PAGE_UP) }
) {
Text("PgUp", modifier = Modifier.padding(4.dp))
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.BottomEnd)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_PAGE_DOWN) }
) {
Text("PgDn", modifier = Modifier.padding(4.dp))
}
}
}
Spacer(modifier = Modifier.height(32.dp))
Row(modifier = Modifier.fillMaxWidth()) {
Box(modifier = Modifier.height(92.dp)) {
Button(
onClick = { shiftState = !shiftState },
colors = if (shiftState) {
buttonColorsToggled
} else {
buttonColorsUntoggled
},
modifier = Modifier.align(Alignment.TopStart).width(keySize * 3)
) {
Row {
Text("Shift")
}
}
Button(
onClick = { ctrlState = !ctrlState },
colors = if (ctrlState) {
buttonColorsToggled
} else {
buttonColorsUntoggled
},
modifier = Modifier.align(Alignment.BottomStart).width(keySize * 2)
) {
Text("Ctrl")
}
}
Spacer(modifier = Modifier.weight(1.0f))
Box(
modifier = Modifier modifier = Modifier
.height(keySize * 2) .weight(1.0f)
.width(keySize * 3) .fillMaxWidth(),
) { ctrlState = ctrlState,
RepeatableActionKey( shiftState = shiftState
modifier = Modifier )
.align(Alignment.TopCenter)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_UP) }
) {
IconWithColor(
iconId = R.drawable.arrow_up,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.BottomCenter)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_DOWN) }
) {
IconWithColor(
iconId = R.drawable.arrow_down,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.BottomStart)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_LEFT) }
) {
IconWithColor(
iconId = R.drawable.arrow_left,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
RepeatableActionKey(
modifier = Modifier
.align(Alignment.BottomEnd)
.width(keySize)
.height(keySize),
onTrigger = { sendEvent(KeyEvent.KEYCODE_DPAD_RIGHT) }
) {
IconWithColor(
iconId = R.drawable.arrow_right,
iconColor = MaterialTheme.colorScheme.onPrimary
)
}
}
} }
SideKeys(
modifier = Modifier
.fillMaxHeight()
.weight(1.0f),
onEvent = onEvent,
onCodePoint = onCodePoint
)
} }
} }
@ -307,4 +357,12 @@ val TextEditAction = Action(
} }
} }
} }
) )
@Composable
@Preview(showBackground = true)
fun TextEditScreenPreview() {
Surface(modifier = Modifier.height(256.dp)) {
TextEditScreen(onCodePoint = { }, onEvent = { _, _ -> })
}
}