mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Add basic text edit mode
This commit is contained in:
parent
7f17d66072
commit
b237e49ece
20
java/res/drawable/arrow_down.xml
Normal file
20
java/res/drawable/arrow_down.xml
Normal 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="M12,5L12,19"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M19,12l-7,7l-7,-7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
@ -1,20 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="26dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="26">
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M20,13L6,13"
|
||||
android:pathData="M19,12L5,12"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M13,20L6,13L13,6"
|
||||
android:pathData="M12,19l-7,-7l7,-7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
|
20
java/res/drawable/arrow_left_26.xml
Normal file
20
java/res/drawable/arrow_left_26.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="26dp"
|
||||
android:height="26dp"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="26">
|
||||
<path
|
||||
android:pathData="M20,13L6,13"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M13,20L6,13L13,6"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/arrow_right.xml
Normal file
20
java/res/drawable/arrow_right.xml
Normal 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="M5,12L19,12"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,5l7,7l-7,7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/arrow_up.xml
Normal file
20
java/res/drawable/arrow_up.xml
Normal 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="M12,19L12,5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M5,12l7,-7l7,7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
20
java/res/drawable/edit_text.xml
Normal file
20
java/res/drawable/edit_text.xml
Normal 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,4H4a2,2 0,0 0,-2 2v14a2,2 0,0 0,2 2h14a2,2 0,0 0,2 -2v-7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M18.5,2.5a2.121,2.121 0,0 1,3 3L12,15l-4,1 1,-4 9.5,-9.5z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
@ -2,9 +2,11 @@
|
||||
<resources>
|
||||
<string name="voice_input_action_title">Voice Input</string>
|
||||
<string name="theme_switcher_action_title">Theme Switcher</string>
|
||||
<string name="emoji_action_title">Emojis</string>
|
||||
<string name="clipboard_action_title">Paste from Clipboard</string>
|
||||
<string name="undo_action_title">Undo</string>
|
||||
<string name="redo_action_title">Redo</string>
|
||||
<string name="text_edit_action_title">Text Editor</string>
|
||||
|
||||
<string name="amoled_dark_theme_name">AMOLED Dark Purple</string>
|
||||
<string name="classic_material_dark_theme_name">AOSP Material Dark</string>
|
||||
|
@ -573,5 +573,4 @@ Tip: You can download and remove dictionaries by going to <b>Languages &
|
||||
This resource is copied from packages/apps/Settings/res/values/strings.xml -->
|
||||
<!-- This resource is corresponding to msgid="5433275485499039199" -->
|
||||
<string name="user_dict_fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
|
||||
<string name="title_emojis">Emojis</string>
|
||||
</resources>
|
||||
|
@ -5,8 +5,6 @@ import android.content.res.Configuration
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.view.KeyCharacterMap
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.CompletionInfo
|
||||
@ -359,7 +357,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
returnBackToMainKeyboardViewFromAction()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.arrow_left),
|
||||
painter = painterResource(id = R.drawable.arrow_left_26),
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ import org.futo.inputmethod.latin.suggestions.SuggestionStripView
|
||||
import org.futo.inputmethod.latin.uix.actions.ClipboardAction
|
||||
import org.futo.inputmethod.latin.uix.actions.EmojiAction
|
||||
import org.futo.inputmethod.latin.uix.actions.RedoAction
|
||||
import org.futo.inputmethod.latin.uix.actions.TextEditAction
|
||||
import org.futo.inputmethod.latin.uix.actions.ThemeAction
|
||||
import org.futo.inputmethod.latin.uix.actions.UndoAction
|
||||
import org.futo.inputmethod.latin.uix.actions.VoiceInputAction
|
||||
@ -324,6 +325,7 @@ fun RowScope.ActionItems(onSelect: (Action) -> Unit) {
|
||||
ActionItem(UndoAction, onSelect)
|
||||
ActionItem(RedoAction, onSelect)
|
||||
ActionItem(ClipboardAction, onSelect)
|
||||
ActionItem(TextEditAction, onSelect)
|
||||
}
|
||||
|
||||
|
||||
|
@ -267,7 +267,7 @@ class PersistentEmojiState: PersistentActionState {
|
||||
|
||||
val EmojiAction = Action(
|
||||
icon = R.drawable.smile,
|
||||
name = R.string.title_emojis,
|
||||
name = R.string.emoji_action_title,
|
||||
simplePressImpl = null,
|
||||
persistentState = { manager ->
|
||||
val state = PersistentEmojiState()
|
||||
@ -283,7 +283,7 @@ val EmojiAction = Action(
|
||||
object : ActionWindow {
|
||||
@Composable
|
||||
override fun windowName(): String {
|
||||
return stringResource(R.string.title_emojis)
|
||||
return stringResource(R.string.emoji_action_title)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -0,0 +1,310 @@
|
||||
package org.futo.inputmethod.latin.uix.actions
|
||||
|
||||
import android.view.KeyEvent
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.drawscope.translate
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import org.futo.inputmethod.latin.R
|
||||
import org.futo.inputmethod.latin.uix.Action
|
||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
||||
|
||||
@Composable
|
||||
fun IconWithColor(@DrawableRes iconId: Int, iconColor: Color, modifier: Modifier = Modifier) {
|
||||
val icon = painterResource(id = iconId)
|
||||
|
||||
Canvas(modifier = modifier) {
|
||||
translate(
|
||||
left = this.size.width / 2.0f - icon.intrinsicSize.width / 2.0f,
|
||||
top = this.size.height / 2.0f - icon.intrinsicSize.height / 2.0f
|
||||
) {
|
||||
with(icon) {
|
||||
draw(
|
||||
icon.intrinsicSize,
|
||||
colorFilter = ColorFilter.tint(
|
||||
iconColor
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RepeatableActionKey(
|
||||
onTrigger: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
contents: @Composable () -> Unit
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
LaunchedEffect(isPressed) {
|
||||
if(isPressed) {
|
||||
onTrigger()
|
||||
delay(670L)
|
||||
while(isPressed) {
|
||||
onTrigger()
|
||||
delay(50L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = modifier.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
onClick = { }
|
||||
),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
contents()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun TextEditScreen(onCodePoint: (Int) -> Unit = { _ -> }, onEvent: (Int, Int) -> Unit = { _, _ -> }) {
|
||||
var shiftState by remember { mutableStateOf(false) }
|
||||
var ctrlState by remember { mutableStateOf(false) }
|
||||
|
||||
val metaState = 0 or
|
||||
(if(shiftState) { KeyEvent.META_SHIFT_ON } else { 0 }) or
|
||||
(if(ctrlState) { 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 keySize = 48.dp
|
||||
|
||||
Column {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(keySize * 2)
|
||||
.width(keySize * 3)
|
||||
) {
|
||||
RepeatableActionKey(
|
||||
modifier = Modifier
|
||||
.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
|
||||
.height(keySize * 2)
|
||||
.width(keySize * 3)
|
||||
) {
|
||||
RepeatableActionKey(
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val TextEditAction = Action(
|
||||
icon = R.drawable.edit_text,
|
||||
name = R.string.text_edit_action_title,
|
||||
simplePressImpl = null,
|
||||
persistentState = null,
|
||||
windowImpl = { manager, persistentState ->
|
||||
object : ActionWindow {
|
||||
@Composable
|
||||
override fun windowName(): String {
|
||||
return stringResource(R.string.text_edit_action_title)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun WindowContents() {
|
||||
TextEditScreen(onCodePoint = { a -> manager.sendCodePointEvent(a)}, onEvent = { a, b -> manager.sendKeyEvent(a, b) })
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue
Block a user