mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Add basic resizer for floating mode
This commit is contained in:
parent
112b7a291a
commit
2f8d847186
130
java/src/org/futo/inputmethod/latin/uix/ResizerRect.kt
Normal file
130
java/src/org/futo/inputmethod/latin/uix/ResizerRect.kt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package org.futo.inputmethod.latin.uix
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.toSize
|
||||||
|
|
||||||
|
|
||||||
|
enum class CurrentDraggingTarget {
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight,
|
||||||
|
Center
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CurrentDraggingTarget.computeOffset(size: Size): Offset = when(this) {
|
||||||
|
CurrentDraggingTarget.TopLeft -> Offset(0.0f, 0.0f)
|
||||||
|
CurrentDraggingTarget.TopRight -> Offset(size.width, 0.0f)
|
||||||
|
CurrentDraggingTarget.BottomLeft -> Offset(0.0f, size.height)
|
||||||
|
CurrentDraggingTarget.BottomRight -> Offset(size.width, size.height)
|
||||||
|
CurrentDraggingTarget.Center -> Offset(size.width * 0.5f, size.height * 0.5f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CurrentDraggingTarget.computeOffset(size: IntSize): Offset =
|
||||||
|
computeOffset(size.toSize())
|
||||||
|
|
||||||
|
private fun CurrentDraggingTarget.dragDelta(offset: Offset): DragDelta = when(this) {
|
||||||
|
CurrentDraggingTarget.TopLeft -> DragDelta(left = offset.x, top = offset.y)
|
||||||
|
CurrentDraggingTarget.TopRight -> DragDelta(right = offset.x, top = offset.y)
|
||||||
|
CurrentDraggingTarget.BottomLeft -> DragDelta(left = offset.x, bottom = offset.y)
|
||||||
|
CurrentDraggingTarget.BottomRight -> DragDelta(right = offset.x, bottom = offset.y)
|
||||||
|
CurrentDraggingTarget.Center -> DragDelta(
|
||||||
|
left = offset.x,
|
||||||
|
right = offset.x,
|
||||||
|
top = offset.y,
|
||||||
|
bottom = offset.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DragDelta(
|
||||||
|
val left: Float = 0.0f,
|
||||||
|
val top: Float = 0.0f,
|
||||||
|
val right: Float = 0.0f,
|
||||||
|
val bottom: Float = 0.0f
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.ResizerRect(onDragged: (DragDelta) -> Boolean, showResetApply: Boolean, onApply: () -> Unit, onReset: () -> Unit) {
|
||||||
|
val shape = RectangleShape
|
||||||
|
|
||||||
|
val draggingState = remember { mutableStateOf<CurrentDraggingTarget?>(null) }
|
||||||
|
val wasAccepted = remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
Box(Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f), shape)
|
||||||
|
.border(3.dp, MaterialTheme.colorScheme.primary, shape)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectDragGestures(
|
||||||
|
onDragStart = { offset ->
|
||||||
|
draggingState.value = CurrentDraggingTarget.entries.minBy {
|
||||||
|
offset.minus(it.computeOffset(size)).getDistanceSquared()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDrag = { _, amount ->
|
||||||
|
draggingState.value?.let {
|
||||||
|
wasAccepted.value = onDragged(it.dragDelta(amount))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragEnd = {
|
||||||
|
draggingState.value = null
|
||||||
|
wasAccepted.value = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
|
val primaryInverseColor = MaterialTheme.colorScheme.inversePrimary
|
||||||
|
val errorColor = MaterialTheme.colorScheme.error
|
||||||
|
val radius = with(LocalDensity.current) { 24.dp.toPx() }
|
||||||
|
|
||||||
|
Canvas(Modifier.matchParentSize(), onDraw = {
|
||||||
|
CurrentDraggingTarget.entries.forEach {
|
||||||
|
drawCircle(
|
||||||
|
color = if (!wasAccepted.value) {
|
||||||
|
errorColor
|
||||||
|
} else if (draggingState.value == it) {
|
||||||
|
primaryInverseColor
|
||||||
|
} else {
|
||||||
|
primaryColor
|
||||||
|
},
|
||||||
|
radius = radius,
|
||||||
|
center = it.computeOffset(size)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (showResetApply) {
|
||||||
|
Row(Modifier.align(Alignment.BottomCenter).padding(16.dp)) {
|
||||||
|
TextButton({ onReset() }) { Text("Reset") }
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
TextButton({ onApply() }) { Text("Apply") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,10 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.requiredWidth
|
import androidx.compose.foundation.layout.requiredWidth
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@ -60,6 +64,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
@ -703,6 +708,7 @@ class UixManager(private val latinIME: LatinIME) {
|
|||||||
pointerInputKey: Any?,
|
pointerInputKey: Any?,
|
||||||
onDragged: (Offset) -> Unit,
|
onDragged: (Offset) -> Unit,
|
||||||
onDragEnd: () -> Unit,
|
onDragEnd: () -> Unit,
|
||||||
|
onResizerOpen: () -> Unit,
|
||||||
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
|
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
|
||||||
) {
|
) {
|
||||||
// Content
|
// Content
|
||||||
@ -720,6 +726,13 @@ class UixManager(private val latinIME: LatinIME) {
|
|||||||
onDrag = { _, dragAmount -> onDragged(dragAmount)},
|
onDrag = { _, dragAmount -> onDragged(dragAmount)},
|
||||||
onDragEnd = { onDragEnd() })
|
onDragEnd = { onDragEnd() })
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
IconButton(onClick = {
|
||||||
|
onResizerOpen()
|
||||||
|
}, Modifier.align(Alignment.CenterEnd)) {
|
||||||
|
Icon(Icons.Default.Menu, contentDescription = "resize")
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth(0.6f).height(4.dp)
|
modifier = Modifier.fillMaxWidth(0.6f).height(4.dp)
|
||||||
.align(Alignment.TopCenter).background(
|
.align(Alignment.TopCenter).background(
|
||||||
@ -736,6 +749,54 @@ class UixManager(private val latinIME: LatinIME) {
|
|||||||
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
|
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
|
||||||
) = with(LocalDensity.current) {
|
) = with(LocalDensity.current) {
|
||||||
val offset = remember(size) { mutableStateOf(Offset(size.bottomOrigin.first.toFloat(), size.bottomOrigin.second.toFloat())) }
|
val offset = remember(size) { mutableStateOf(Offset(size.bottomOrigin.first.toFloat(), size.bottomOrigin.second.toFloat())) }
|
||||||
|
|
||||||
|
val resizing = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val onDragDelta: (DragDelta) -> Boolean = remember { { delta ->
|
||||||
|
// Matching the necessary coordinate space
|
||||||
|
var deltaX = delta.left
|
||||||
|
var deltaY = -delta.bottom
|
||||||
|
var deltaWidth = delta.right - delta.left
|
||||||
|
var deltaHeight = delta.bottom - delta.top
|
||||||
|
|
||||||
|
var result = true
|
||||||
|
|
||||||
|
// TODO: Limit the values so that we do not go off-screen
|
||||||
|
// If we have reached a minimum limit, return false
|
||||||
|
|
||||||
|
// Basic limiting for minimum size
|
||||||
|
val currSettings = latinIME.sizingCalculator.getSavedSettings()
|
||||||
|
val currSize = Size(
|
||||||
|
currSettings.floatingWidthDp.dp.toPx(),
|
||||||
|
currSettings.floatingHeightDp.dp.toPx()
|
||||||
|
)
|
||||||
|
|
||||||
|
if(currSize.width + deltaWidth < 200.dp.toPx()) {
|
||||||
|
deltaWidth = deltaWidth.coerceAtLeast(200.dp.toPx() - currSize.width)
|
||||||
|
deltaX = 0.0f
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currSize.height + deltaHeight < 160.dp.toPx()) {
|
||||||
|
deltaHeight = deltaHeight.coerceAtLeast(160.dp.toPx() - currSize.height)
|
||||||
|
deltaY = 0.0f
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
|
||||||
|
latinIME.sizingCalculator.editSavedSettings { settings ->
|
||||||
|
settings.copy(
|
||||||
|
floatingBottomOriginDp = Pair(
|
||||||
|
settings.floatingBottomOriginDp.first + deltaX.toDp().value,
|
||||||
|
settings.floatingBottomOriginDp.second + deltaY.toDp().value
|
||||||
|
),
|
||||||
|
floatingWidthDp = settings.floatingWidthDp + deltaWidth.toDp().value,
|
||||||
|
floatingHeightDp = settings.floatingHeightDp + deltaHeight.toDp().value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
} }
|
||||||
|
|
||||||
OffsetPositioner(offset.value) {
|
OffsetPositioner(offset.value) {
|
||||||
KeyboardSurface(
|
KeyboardSurface(
|
||||||
requiredWidthPx = size.width,
|
requiredWidthPx = size.width,
|
||||||
@ -778,9 +839,18 @@ class UixManager(private val latinIME: LatinIME) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onResizerOpen = {
|
||||||
|
resizing.value = true
|
||||||
|
},
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(resizing.value) {
|
||||||
|
ResizerRect(onDragDelta, showResetApply = true, onApply = {
|
||||||
|
resizing.value = false
|
||||||
|
}, onReset = { })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user