mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-12-28 13:22:30 +00:00
New UI of toggle group buttons
This commit is contained in:
parent
32a8c8a8ec
commit
7abec4fa5a
@ -17,8 +17,8 @@
|
||||
|
||||
package ru.tech.imageresizershrinker.core.ui.widget.buttons
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@ -26,19 +26,19 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.ButtonGroupDefaults
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MotionScheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ToggleButton
|
||||
import androidx.compose.material3.ToggleButtonDefaults
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -53,9 +53,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.max
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.gigamole.composeshadowsplus.rsblur.rsBlurShadow
|
||||
import ru.tech.imageresizershrinker.core.settings.presentation.provider.LocalSettingsState
|
||||
import ru.tech.imageresizershrinker.core.ui.theme.outlineVariant
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.ProvidesValue
|
||||
@ -130,7 +128,6 @@ fun ToggleGroupButton(
|
||||
selectedIndex: Int,
|
||||
itemContent: @Composable (item: Int) -> Unit,
|
||||
title: @Composable RowScope.() -> Unit = {},
|
||||
buttonIcon: (@Composable () -> Unit)? = null,
|
||||
onIndexChange: (Int) -> Unit,
|
||||
inactiveButtonColor: Color = MaterialTheme.colorScheme.surface,
|
||||
activeButtonColor: Color = MaterialTheme.colorScheme.secondary,
|
||||
@ -160,93 +157,70 @@ fun ToggleGroupButton(
|
||||
)
|
||||
val scrollState = rememberScrollState()
|
||||
LocalMinimumInteractiveComponentSize.ProvidesValue(Dp.Unspecified) {
|
||||
SingleChoiceSegmentedButtonRow(
|
||||
space = max(settingsState.borderWidth, 1.dp),
|
||||
modifier = Modifier
|
||||
.height(IntrinsicSize.Max)
|
||||
.then(
|
||||
if (isScrollable) {
|
||||
Modifier
|
||||
.fadingEdges(scrollState)
|
||||
.horizontalScroll(scrollState)
|
||||
} else Modifier.fillMaxWidth()
|
||||
)
|
||||
.padding(start = 6.dp, end = 6.dp, bottom = 8.dp, top = 8.dp)
|
||||
MaterialTheme(
|
||||
motionScheme = object : MotionScheme by MotionScheme.expressive() {
|
||||
override fun <T> fastSpatialSpec(): FiniteAnimationSpec<T> = tween(400)
|
||||
}
|
||||
) {
|
||||
repeat(itemCount) { index ->
|
||||
val shape = SegmentedButtonDefaults.itemShape(index, itemCount)
|
||||
val activeContainerColor = if (enabled) {
|
||||
activeButtonColor
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceContainer
|
||||
}
|
||||
val selected = index == selectedIndex
|
||||
val focus = LocalFocusManager.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(IntrinsicSize.Max)
|
||||
.then(
|
||||
if (isScrollable) {
|
||||
Modifier
|
||||
.fadingEdges(scrollState)
|
||||
.horizontalScroll(scrollState)
|
||||
} else Modifier.fillMaxWidth()
|
||||
)
|
||||
.padding(
|
||||
start = 6.dp,
|
||||
end = 6.dp,
|
||||
bottom = 8.dp,
|
||||
top = 8.dp
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
|
||||
) {
|
||||
repeat(itemCount) { index ->
|
||||
val activeContainerColor = if (enabled) {
|
||||
activeButtonColor
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceContainer
|
||||
}
|
||||
|
||||
SegmentedButton(
|
||||
enabled = enabled,
|
||||
onClick = {
|
||||
focus.clearFocus()
|
||||
haptics.performHapticFeedback(
|
||||
HapticFeedbackType.LongPress
|
||||
)
|
||||
onIndexChange(index)
|
||||
},
|
||||
icon = {
|
||||
if (buttonIcon == null) SegmentedButtonDefaults.Icon(index == selectedIndex)
|
||||
else buttonIcon()
|
||||
},
|
||||
border = BorderStroke(
|
||||
width = settingsState.borderWidth,
|
||||
color = MaterialTheme.colorScheme.outlineVariant()
|
||||
),
|
||||
selected = true,
|
||||
colors = SegmentedButtonDefaults.colors(
|
||||
activeBorderColor = animateColorAsState(
|
||||
if (selected) {
|
||||
MaterialTheme.colorScheme.outlineVariant()
|
||||
} else MaterialTheme.colorScheme.outline
|
||||
).value,
|
||||
activeContainerColor = animateColorAsState(
|
||||
if (selected) {
|
||||
activeContainerColor
|
||||
} else inactiveButtonColor
|
||||
).value,
|
||||
activeContentColor = animateColorAsState(
|
||||
contentColorFor(
|
||||
if (selected) {
|
||||
activeContainerColor
|
||||
} else inactiveButtonColor
|
||||
val selected = index == selectedIndex
|
||||
val focus = LocalFocusManager.current
|
||||
|
||||
ToggleButton(
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
focus.clearFocus()
|
||||
haptics.performHapticFeedback(
|
||||
HapticFeedbackType.LongPress
|
||||
)
|
||||
onIndexChange(index)
|
||||
},
|
||||
border = BorderStroke(
|
||||
width = settingsState.borderWidth,
|
||||
color = MaterialTheme.colorScheme.outlineVariant(
|
||||
onTopOf = if (selected) activeContainerColor
|
||||
else inactiveButtonColor
|
||||
)
|
||||
).value,
|
||||
disabledInactiveContainerColor = MaterialTheme.colorScheme.outlineVariant.copy(
|
||||
0.38f
|
||||
).compositeOver(MaterialTheme.colorScheme.surface),
|
||||
disabledActiveContainerColor = MaterialTheme.colorScheme.outlineVariant.copy(
|
||||
0.38f
|
||||
).compositeOver(MaterialTheme.colorScheme.surface)
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.then(
|
||||
if (!(settingsState.borderWidth >= 0.dp || !settingsState.drawButtonShadows)) {
|
||||
Modifier.rsBlurShadow(
|
||||
shape = SegmentedButtonDefaults.itemShape(
|
||||
index = itemCount - 1 - index,
|
||||
count = itemCount
|
||||
),
|
||||
radius = animateDpAsState(
|
||||
if (selected) 2.dp
|
||||
else 1.dp
|
||||
).value
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
shape = shape
|
||||
) {
|
||||
itemContent(index)
|
||||
colors = ToggleButtonDefaults.toggleButtonColors(
|
||||
containerColor = inactiveButtonColor,
|
||||
contentColor = contentColorFor(inactiveButtonColor),
|
||||
checkedContainerColor = activeContainerColor,
|
||||
checkedContentColor = contentColorFor(activeContainerColor)
|
||||
),
|
||||
checked = selected,
|
||||
shapes = when (index) {
|
||||
0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
|
||||
itemCount - 1 -> ButtonGroupDefaults.connectedTrailingButtonShapes()
|
||||
else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
|
||||
}
|
||||
) {
|
||||
itemContent(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +59,8 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.tech.imageresizershrinker.core.ui.theme.inverse
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.pasteColorFromClipboard
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalContainerColor
|
||||
import ru.tech.imageresizershrinker.core.ui.utils.provider.ProvideContainerDefaults
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.hapticsClickable
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.hapticsCombinedClickable
|
||||
import ru.tech.imageresizershrinker.core.ui.widget.modifier.animateShape
|
||||
@ -101,152 +103,94 @@ fun ColorSelectionRow(
|
||||
|
||||
val itemSize = 42.dp
|
||||
|
||||
LazyRow(
|
||||
state = listState,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
.fadingEdges(listState),
|
||||
userScrollEnabled = allowScroll,
|
||||
contentPadding = contentPadding,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
ProvideContainerDefaults(
|
||||
color = LocalContainerColor.current
|
||||
) {
|
||||
item {
|
||||
val background = customColor ?: MaterialTheme.colorScheme.primary
|
||||
val isSelected = customColor != null
|
||||
val shape = animateShape(
|
||||
if (isSelected) RoundedCornerShape(8.dp)
|
||||
else RoundedCornerShape(itemSize / 2)
|
||||
)
|
||||
LazyRow(
|
||||
state = listState,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
.fadingEdges(listState),
|
||||
userScrollEnabled = allowScroll,
|
||||
contentPadding = contentPadding,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
item {
|
||||
val background = customColor ?: MaterialTheme.colorScheme.primary
|
||||
val isSelected = customColor != null
|
||||
val shape = animateShape(
|
||||
if (isSelected) RoundedCornerShape(8.dp)
|
||||
else RoundedCornerShape(itemSize / 2)
|
||||
)
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.size(itemSize)
|
||||
.aspectRatio(1f)
|
||||
.scale(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 0.7f else 1f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
.rotate(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 45f else 0f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
.container(
|
||||
shape = shape,
|
||||
color = background,
|
||||
resultPadding = 0.dp
|
||||
)
|
||||
.transparencyChecker()
|
||||
.background(background, shape)
|
||||
.hapticsCombinedClickable(
|
||||
onLongClick = {
|
||||
context.pasteColorFromClipboard(
|
||||
onPastedColor = {
|
||||
val color = if (allowAlpha) Color(it)
|
||||
else Color(it).copy(1f)
|
||||
|
||||
onValueChange(color)
|
||||
customColor = color
|
||||
},
|
||||
onPastedColorFailure = { message ->
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = message,
|
||||
icon = Icons.Outlined.Error
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
showColorPicker = true
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Palette,
|
||||
contentDescription = null,
|
||||
tint = background.inverse(
|
||||
fraction = {
|
||||
if (it) 0.8f
|
||||
else 0.5f
|
||||
},
|
||||
darkMode = background.luminance() < 0.3f
|
||||
),
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.background(
|
||||
color = background.copy(alpha = 1f),
|
||||
shape = shape
|
||||
)
|
||||
.padding(4.dp)
|
||||
.rotate(
|
||||
.size(itemSize)
|
||||
.aspectRatio(1f)
|
||||
.scale(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) -45f else 0f,
|
||||
targetValue = if (isSelected) 0.7f else 1f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = defaultColors,
|
||||
key = { it.toArgb() }
|
||||
) { color ->
|
||||
val isSelected = value == color && customColor == null
|
||||
val shape = animateShape(
|
||||
if (isSelected) RoundedCornerShape(8.dp)
|
||||
else RoundedCornerShape(itemSize / 2)
|
||||
)
|
||||
.rotate(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 45f else 0f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
.container(
|
||||
shape = shape,
|
||||
color = background,
|
||||
resultPadding = 0.dp
|
||||
)
|
||||
.transparencyChecker()
|
||||
.background(background, shape)
|
||||
.hapticsCombinedClickable(
|
||||
onLongClick = {
|
||||
context.pasteColorFromClipboard(
|
||||
onPastedColor = {
|
||||
val color = if (allowAlpha) Color(it)
|
||||
else Color(it).copy(1f)
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.size(itemSize)
|
||||
.aspectRatio(1f)
|
||||
.scale(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 0.7f else 1f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
.rotate(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 45f else 0f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
.container(
|
||||
shape = shape,
|
||||
color = color,
|
||||
resultPadding = 0.dp
|
||||
)
|
||||
.transparencyChecker()
|
||||
.background(color, shape)
|
||||
.hapticsClickable {
|
||||
onValueChange(color.copy(if (allowAlpha) color.alpha else 1f))
|
||||
customColor = null
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AnimatedVisibility(isSelected) {
|
||||
onValueChange(color)
|
||||
customColor = color
|
||||
},
|
||||
onPastedColorFailure = { message ->
|
||||
scope.launch {
|
||||
toastHostState.showToast(
|
||||
message = message,
|
||||
icon = Icons.Outlined.Error
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
showColorPicker = true
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DoneAll,
|
||||
imageVector = Icons.Rounded.Palette,
|
||||
contentDescription = null,
|
||||
tint = color.inverse(
|
||||
tint = background.inverse(
|
||||
fraction = {
|
||||
if (it) 0.8f
|
||||
else 0.5f
|
||||
},
|
||||
darkMode = color.luminance() < 0.3f
|
||||
darkMode = background.luminance() < 0.3f
|
||||
),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.size(32.dp)
|
||||
.background(
|
||||
color = background.copy(alpha = 1f),
|
||||
shape = shape
|
||||
)
|
||||
.padding(4.dp)
|
||||
.rotate(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) -45f else 0f,
|
||||
@ -256,6 +200,68 @@ fun ColorSelectionRow(
|
||||
)
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = defaultColors,
|
||||
key = { it.toArgb() }
|
||||
) { color ->
|
||||
val isSelected = value == color && customColor == null
|
||||
val shape = animateShape(
|
||||
if (isSelected) RoundedCornerShape(8.dp)
|
||||
else RoundedCornerShape(itemSize / 2)
|
||||
)
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.size(itemSize)
|
||||
.aspectRatio(1f)
|
||||
.scale(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 0.7f else 1f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
.rotate(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 45f else 0f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
.container(
|
||||
shape = shape,
|
||||
color = color,
|
||||
resultPadding = 0.dp
|
||||
)
|
||||
.transparencyChecker()
|
||||
.background(color, shape)
|
||||
.hapticsClickable {
|
||||
onValueChange(color.copy(if (allowAlpha) color.alpha else 1f))
|
||||
customColor = null
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AnimatedVisibility(isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DoneAll,
|
||||
contentDescription = null,
|
||||
tint = color.inverse(
|
||||
fraction = {
|
||||
if (it) 0.8f
|
||||
else 0.5f
|
||||
},
|
||||
darkMode = color.luminance() < 0.3f
|
||||
),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.rotate(
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) -45f else 0f,
|
||||
animationSpec = tween(400)
|
||||
).value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -129,7 +129,6 @@ fun DrawLineStyleSelector(
|
||||
selectedIndex = values.indexOfFirst {
|
||||
value::class.isInstance(it)
|
||||
},
|
||||
buttonIcon = {},
|
||||
itemContent = {
|
||||
Icon(
|
||||
imageVector = values[it].getIcon(),
|
||||
|
||||
@ -136,7 +136,6 @@ fun DrawModeSelector(
|
||||
selectedIndex = values.indexOfFirst {
|
||||
value::class.isInstance(it)
|
||||
},
|
||||
buttonIcon = {},
|
||||
itemContent = {
|
||||
Icon(
|
||||
imageVector = values[it].getIcon(),
|
||||
|
||||
@ -99,7 +99,6 @@ fun DrawPathModeSelector(
|
||||
}
|
||||
}
|
||||
}.value,
|
||||
buttonIcon = {},
|
||||
activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
itemContent = {
|
||||
Icon(
|
||||
|
||||
@ -74,7 +74,6 @@ fun DefaultDrawPathModeSettingItem(
|
||||
itemCount = 17,
|
||||
title = {},
|
||||
selectedIndex = settingsState.defaultDrawPathMode,
|
||||
buttonIcon = {},
|
||||
activeButtonColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
inactiveButtonColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
itemContent = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user