Skip to content

Conversation

@dweymouth
Copy link
Contributor

@dweymouth dweymouth commented Oct 8, 2025

Description:

Use an animation to smooth out the scroll, similar to what is done in web browsers and some other UI frameworks

Checklist:

  • Tests included. // <- kind of hard to unit test an animation
  • Lint and formatter run with no errors.
  • Tests all pass.

@dweymouth dweymouth marked this pull request as ready for review October 9, 2025 15:37

smoothScrollingFactor = 0.5

disableSmoothScrollingEnvKey = "FYNE_DISABLE_SMOOTH_SCROLLING"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this perhaps be better off in fyne_settings instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? On Slack yesterday Andy didn't even think it was needed at all but I ended up adding it as a relatively easy way to turn it off in unit test code. We do have a precedent of environment variables being used to control things that aren't exposed in fyne_settings, like FYNE_CACHE and FYNE_DISABLE_DPI_DETECTION.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be an unpopular opinion but I'm just kind of against environment variables like this and many of the ones we already have. It feels to me like they never are documented anywhere and just kind of rot away. As such, I get a feeling that very few actually use them so the code becomes more complicated for little gain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could just remove it and test if the fyne.CurrentApp() is the test app?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a good plan


checkSmoothScrollOnce = sync.Once{}
checkEnvDisableSmoothScrolling = func() {
checkSmoothScrollOnce.Do(func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: We actually can get away with just an if statement and a Boolean now that the code is single threaded.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we still need to worry about people who haven't completed the migration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really, race condition prevention on the old model is on a "reasonable effort" level now rather than an absolute promise

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a place where a race condition would be of little consequence if it happens. Worst case, it is loaded twice

Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little unsure about the algorithm here.

It looks like it will move a half pixel after each frame following a scroll event - is this assuming a 2x scale factor?
What about differences in how fast the user was scrolling? How does it relate to the inertial scroll on mobile?


func (s *Scroll) animateScroll(_ float32) {
diff := s.Offset.Subtract(s.targetOffset)
if math.Abs(float64(diff.X)) < 0.5 && math.Abs(float64(diff.Y)) < 0.5 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This 0.5 check seems like it should be comparing to the smooth scroll factor instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're different values which just both happen to be 0.5. This is just an absolute threshold in dp where a scroll will just snap to the requested position instead of animating (or continuing to animate). The smoothScrollingFactor is an exponential decay factor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK thanks - then in this case I have a different question - why is 0.5 the right value and should it too be a named constant?

// what fraction of the page to scroll when tapping on the scroll bar area
pageScrollFraction = float32(0.95)

smoothScrollingFactor = 0.5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 0.5? does this assume HiDPI? What about UHDPI like smart phones?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's somewhat arbitrary but it's the exponential decay factor of the animation curve. It's pixel-independent. Since we're holding this off until 2.7.1 I may try to implement a more sophisticated ease-in/ease-out curve instead of just this decaying exponential.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - it seems I misread the "*" as "+", apologies.

The curves are already coded into the animation code so maybe using the value parameter in the callback could use that instead of coding it in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was thinking about that, the issue with that for now is that there's no way to extend a running animation properly (ie when the user continues to scroll so the endpoint target for the animation needs to be adjusted on-the-fly) which is what led to me opening #5970. It doesn't look too hard to fix though so I can look into it for 2.7.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants