diff --git a/app/src/main/java/com/hippo/ehviewer/ui/tools/CircularLayout.kt b/app/src/main/java/com/hippo/ehviewer/ui/tools/CircularLayout.kt new file mode 100644 index 000000000..73f91a7d6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/tools/CircularLayout.kt @@ -0,0 +1,46 @@ +package com.hippo.ehviewer.ui.tools + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.unit.Dp +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.roundToInt +import kotlin.math.sin + +private const val FullCircle: Float = (PI * 2).toFloat() +private const val QuarterCircle = PI / 2 + +@Composable +fun CircularLayout( + modifier: Modifier = Modifier, + radius: Dp, + content: @Composable () -> Unit, +) { + Layout( + modifier = modifier, + content = content, + ) { measurables, constraints -> + val radiusPx = radius.toPx() + val itemConstraints = constraints.copy(minWidth = 0, minHeight = 0) + val placeables = measurables.map { measurable -> measurable.measure(itemConstraints) } + val theta = FullCircle / (placeables.count()) + + layout( + width = constraints.minWidth, + height = constraints.minHeight, + ) { + placeables.forEachIndexed { i, it -> + val centerOffsetX = constraints.maxWidth / 2 - it.width / 2 + val centerOffsetY = constraints.maxHeight / 2 - it.height / 2 + val offsetX = radiusPx * cos(theta * i - QuarterCircle) + centerOffsetX + val offsetY = radiusPx * sin(theta * i - QuarterCircle) + centerOffsetY + it.place( + x = offsetX.roundToInt(), + y = offsetY.roundToInt(), + ) + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt b/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt index 344968020..8b59f95d4 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt @@ -4,6 +4,8 @@ import androidx.annotation.StringRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -25,6 +27,7 @@ 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.vector.ImageVector import androidx.compose.ui.res.stringResource @@ -115,7 +118,7 @@ class DialogState { } } - private suspend fun showNoButton(block: @Composable DismissDialogScope.() -> Unit): R { + private suspend fun showNoButton(respectDefaultWidth: Boolean = true, block: @Composable DismissDialogScope.() -> Unit): R { return suspendCancellableCoroutine { cont -> cont.invokeOnCancellation { dismiss() } content = { @@ -132,7 +135,7 @@ class DialogState { }, content = { Surface( - modifier = Modifier.width(280.dp), + modifier = with(Modifier) { if (!respectDefaultWidth) defaultMinSize(280.dp) else width(280.dp) }, shape = AlertDialogDefaults.shape, color = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, @@ -184,21 +187,22 @@ class DialogState { suspend fun showSelectItemWithIcon( vararg items: Pair, @StringRes title: Int, - ): Int = showNoButton { - LazyColumn { - stickyHeader { - Text(text = stringResource(id = title), modifier = Modifier.padding(horizontal = 16.dp).padding(top = 16.dp), style = MaterialTheme.typography.titleMedium) - } - itemsIndexed(items) { index, (icon, text) -> - ListItem( - headlineContent = { - Text(text = text, style = MaterialTheme.typography.titleMedium) - }, - modifier = Modifier.clickable { dismissWith(index) }, - leadingContent = { + ): Int = showNoButton(false) { + Column { + Text(text = stringResource(id = title), modifier = Modifier.padding(horizontal = 16.dp).padding(top = 16.dp), style = MaterialTheme.typography.titleMedium) + CircularLayout( + radius = 112.dp, + modifier = Modifier.fillMaxWidth().aspectRatio(1F), + ) { + items.forEachIndexed { index, (icon, text) -> + Column( + modifier = Modifier.clickable { dismissWith(index) }, + horizontalAlignment = Alignment.CenterHorizontally, + ) { Icon(imageVector = icon, contentDescription = null, tint = AlertDialogDefaults.iconContentColor) - }, - ) + Text(text = text, style = MaterialTheme.typography.bodySmall) + } + } } } }