Add basic resizer for floating mode

This commit is contained in:
Aleksandras Kostarevas 2024-09-27 22:38:46 +03:00
parent 112b7a291a
commit 2f8d847186
2 changed files with 200 additions and 0 deletions

View 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") }
}
}
}
}

View File

@ -41,6 +41,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
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.MaterialTheme
import androidx.compose.material3.Surface
@ -60,6 +64,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
@ -703,6 +708,7 @@ class UixManager(private val latinIME: LatinIME) {
pointerInputKey: Any?,
onDragged: (Offset) -> Unit,
onDragEnd: () -> Unit,
onResizerOpen: () -> Unit,
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
) {
// Content
@ -720,6 +726,13 @@ class UixManager(private val latinIME: LatinIME) {
onDrag = { _, dragAmount -> onDragged(dragAmount)},
onDragEnd = { onDragEnd() })
}) {
IconButton(onClick = {
onResizerOpen()
}, Modifier.align(Alignment.CenterEnd)) {
Icon(Icons.Default.Menu, contentDescription = "resize")
}
Box(
modifier = Modifier.fillMaxWidth(0.6f).height(4.dp)
.align(Alignment.TopCenter).background(
@ -736,6 +749,54 @@ class UixManager(private val latinIME: LatinIME) {
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
) = with(LocalDensity.current) {
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) {
KeyboardSurface(
requiredWidthPx = size.width,
@ -778,9 +839,18 @@ class UixManager(private val latinIME: LatinIME) {
)
}
},
onResizerOpen = {
resizing.value = true
},
content = content
)
}
if(resizing.value) {
ResizerRect(onDragDelta, showResetApply = true, onApply = {
resizing.value = false
}, onReset = { })
}
}
}
}