Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix precise mouse wheel detection #1326

Merged
merged 1 commit into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ internal class MouseWheelScrollNode(
) {
operator fun plus(other: MouseWheelScrollDelta) = MouseWheelScrollDelta(
value = value + other.value,
shouldApplyImmediately = shouldApplyImmediately || other.shouldApplyImmediately

// Ignore [other.shouldApplyImmediately] to avoid false-positive [isPreciseWheelScroll]
// detection during animation
shouldApplyImmediately = shouldApplyImmediately
)
}
private val channel = Channel<MouseWheelScrollDelta>(capacity = Channel.UNLIMITED)
Expand Down Expand Up @@ -216,12 +219,18 @@ internal class MouseWheelScrollNode(
* Ideally it should be resolved by catching real touches from input device instead of
* waiting the next event with timeout before resetting progress flag.
*/
suspend fun waitNextScrollDelta(timeoutMillis: Long, forceApplyImmediately: Boolean = false): Boolean {
suspend fun waitNextScrollDelta(timeoutMillis: Long): Boolean {
if (timeoutMillis < 0) return false
return withTimeoutOrNull(timeoutMillis) {
channel.busyReceive()
}?.let {
targetScrollDelta = if (forceApplyImmediately) it.copy(shouldApplyImmediately = true) else it
// Keep this value unchanged during animation
// Currently, [isPreciseWheelScroll] might be unstable in case if
// a precise value is almost equal regular one.
val previousDeltaShouldApplyImmediately = targetScrollDelta.shouldApplyImmediately
targetScrollDelta = it.copy(
shouldApplyImmediately = previousDeltaShouldApplyImmediately
)
targetValue = targetScrollDelta.value.reverseIfNeeded().toFloat()
animationState = AnimationState(0f) // Reset previous animation leftover

Expand All @@ -236,14 +245,7 @@ internal class MouseWheelScrollNode(
val targetValueLeftover = targetValue - animationState.value
if (targetScrollDelta.shouldApplyImmediately || abs(targetValueLeftover) < threshold) {
dispatchMouseWheelScroll(targetValueLeftover)
requiredAnimation = waitNextScrollDelta(
timeoutMillis = ScrollProgressTimeout,

// Apply the next event without `ProgressTimeout` immediately too.
// Currently, `isPreciseWheelScroll` might be false-negative in case if
// precise value is almost equal regular one.
forceApplyImmediately = targetScrollDelta.shouldApplyImmediately
)
requiredAnimation = waitNextScrollDelta(ScrollProgressTimeout)
} else {
// Animation will start only on the next frame,
// so apply threshold immediately to avoid delays.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,11 @@ private val PointerEvent.isPreciseWheelRotation
get() = (awtEventOrNull as? MouseWheelEvent)?.isPreciseWheelRotation ?: false

private val MouseWheelEvent.isPreciseWheelRotation
get() = abs(preciseWheelRotation - wheelRotation.toDouble()) > 0.001
get() = when (DesktopPlatform.Current) {
// On Windows, even free scrolling wheels should trigger animation
DesktopPlatform.Windows -> false

// For other platforms, apply this heuristic to determine if it's
// high-precision wheel/trackpad or regular stepping mouse wheel.
else -> abs(preciseWheelRotation - wheelRotation.toDouble()) > 0.001
}
Loading