0
0
mirror of https://github.com/florisboard/florisboard.git synced 2024-09-19 19:42:20 +02:00

Implement smooth scrollbar in emoji palette (#2446)

* implement smooth scrollbar

* Code style and function visibility adjustments

* Apply suggestions from code review

Formatting

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
Co-authored-by: Lars Mühlbauer <59062169+lm41@users.noreply.github.com>

* Update app/src/main/kotlin/dev/patrickgold/florisboard/lib/compose/ScrollableModifiers.kt

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
Co-authored-by: Lars Mühlbauer <59062169+lm41@users.noreply.github.com>
This commit is contained in:
Kevin 2024-04-19 20:45:35 +10:00 committed by GitHub
parent 0167e1231f
commit 7351a8bfa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 105 additions and 6 deletions

View File

@ -90,6 +90,7 @@ import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.android.AndroidKeyguardManager
import dev.patrickgold.florisboard.lib.android.showShortToast
import dev.patrickgold.florisboard.lib.android.systemService
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
import dev.patrickgold.florisboard.lib.compose.safeTimes
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.snygg.ui.snyggBackground
@ -220,7 +221,7 @@ fun EmojiPaletteView(
LazyVerticalGrid(
modifier = Modifier
.fillMaxSize()
/*.florisScrollbar(lazyListState, color = contentColor.copy(alpha = 0.28f), isVertical = true)*/,
.florisScrollbar(lazyListState, color = contentColor.copy(alpha = 0.28f)),
columns = GridCells.Adaptive(minSize = EmojiBaseWidth),
state = lazyListState,
) {

View File

@ -20,8 +20,12 @@ import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
@ -40,6 +44,7 @@ import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import java.lang.Math.min
private val DefaultScrollbarSize = 4.dp
// IgnoreInVeryFastOut (basically)
@ -146,6 +151,8 @@ fun Modifier.florisScrollbar(
isInitial = false
}
val visibleItemsInfo = state.layoutInfo.visibleItemsInfo
val visibleItems = if (visibleItemsInfo.isNotEmpty()) remember { visibleItemsInfo.size } else 0
drawWithContent {
drawContent()
val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
@ -155,18 +162,18 @@ fun Modifier.florisScrollbar(
val scrollbarHeight: Float
val scrollbarOffsetX: Float
val scrollbarOffsetY: Float
val first = state.layoutInfo.visibleItemsInfo.first()
if (isVertical) {
val elementHeight = this.size.height / state.layoutInfo.totalItemsCount
scrollbarWidth = size.toPx()
scrollbarHeight = state.layoutInfo.visibleItemsInfo.size * elementHeight
scrollbarHeight = visibleItems * elementHeight
scrollbarOffsetX = this.size.width - scrollbarWidth
scrollbarOffsetY = firstVisibleElementIndex * elementHeight
scrollbarOffsetY = (firstVisibleElementIndex - percentOffset(first)) * elementHeight
} else {
val elementWidth = this.size.width / state.layoutInfo.totalItemsCount
scrollbarWidth = state.layoutInfo.visibleItemsInfo.size * elementWidth
scrollbarWidth = visibleItems * elementWidth
scrollbarHeight = size.toPx()
scrollbarOffsetX = firstVisibleElementIndex * elementWidth
scrollbarOffsetX = (firstVisibleElementIndex - percentOffset(first)) * elementWidth
scrollbarOffsetY = this.size.height - scrollbarHeight
}
@ -179,3 +186,94 @@ fun Modifier.florisScrollbar(
}
}
}
fun Modifier.florisScrollbar(
state: LazyGridState,
size: Dp = DefaultScrollbarSize,
color: Color = Color.Unspecified,
): Modifier = composed {
var isInitial by remember { mutableStateOf(true) }
val targetAlpha = if (state.isScrollInProgress || isInitial) 1f else 0f
val duration = if (state.isScrollInProgress || isInitial) 0 else 950
val alpha by animateFloatAsState(
targetValue = targetAlpha,
animationSpec = tween(durationMillis = duration, easing = ScrollbarAnimationEasing),
)
val scrollbarColor = color.takeOrElse { MaterialTheme.colors.onSurface.copy(alpha = 0.28f) }
LaunchedEffect(Unit) {
delay(1850)
isInitial = false
}
val orientation = state.layoutInfo.orientation
val visibleItemsInfo = state.layoutInfo.visibleItemsInfo
val visibleItems = if (visibleItemsInfo.isNotEmpty()) remember { visibleItemsInfo.size } else 0
val last = visibleItemsInfo.lastOrNull()
val stacks = if (last != null && orientation == Orientation.Vertical) {
remember { last.column }
} else if (last != null && orientation == Orientation.Horizontal) {
remember { last.row }
} else {
0
}
drawWithContent {
drawContent()
val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
val needDrawScrollbar = state.isScrollInProgress || isInitial || alpha > 0f
if (needDrawScrollbar && firstVisibleElementIndex != null) {
val scrollbarWidth: Float
val scrollbarHeight: Float
val scrollbarOffsetX: Float
val scrollbarOffsetY: Float
val first = state.layoutInfo.visibleItemsInfo.first()
if (orientation == Orientation.Vertical) {
val elementHeight = this.size.height / state.layoutInfo.totalItemsCount
scrollbarWidth = size.toPx()
scrollbarOffsetX = this.size.width - scrollbarWidth
scrollbarOffsetY = (firstVisibleElementIndex - stacks*percentOffset(first, orientation)) * elementHeight
scrollbarHeight = min(visibleItems * elementHeight, this.size.height - scrollbarOffsetY)
} else {
val elementWidth = this.size.width / state.layoutInfo.totalItemsCount
scrollbarHeight = size.toPx()
scrollbarOffsetX = (firstVisibleElementIndex - stacks*percentOffset(first, orientation)) * elementWidth
scrollbarOffsetY = this.size.height - scrollbarHeight
scrollbarWidth = min(visibleItems * elementWidth, this.size.height - scrollbarOffsetX)
}
drawRect(
color = scrollbarColor,
topLeft = Offset(scrollbarOffsetX, scrollbarOffsetY),
size = Size(scrollbarWidth, scrollbarHeight),
alpha = alpha,
)
}
}
}
/**
* Item's offset on main axis as a percentage of size
*/
internal fun percentOffset (
item: LazyListItemInfo,
): Float {
return item.offset.toFloat() / item.size
}
internal fun percentOffset (
item: LazyGridItemInfo,
orientation: Orientation
): Float {
val offset = if (orientation == Orientation.Horizontal) {
item.offset.x
} else {
item.offset.y
}
val size = if (orientation == Orientation.Horizontal) {
item.size.width
} else {
item.size.height
}
return offset.toFloat() / size
}