Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
55 changes: 54 additions & 1 deletion internal/widget/scroller.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package widget

import (
"math"
"os"
"strings"
"sync"
"time"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/driver/desktop"
Expand Down Expand Up @@ -35,6 +41,22 @@ const (

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

smoothScrollingFactor = 0.5

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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


disableSmoothScrollingEnvKey = "FYNE_DISABLE_SMOOTH_SCROLLING"

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

)

var (
isSmoothScrollingDisabled bool

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

Copy link
Copy Markdown
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
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
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

env := os.Getenv(disableSmoothScrollingEnvKey)
isSmoothScrollingDisabled = strings.EqualFold(env, "true") || strings.EqualFold(env, "t") || env == "1"
})
}
)

type scrollBarRenderer struct {
Expand Down Expand Up @@ -493,6 +515,9 @@ type Scroll struct {
//
// Since: 2.0
OnScrolled func(fyne.Position) `json:"-"`

targetOffset fyne.Position
scrollAnimation *fyne.Animation
}

// CreateRenderer is a private method to Fyne which links this widget to its renderer
Expand Down Expand Up @@ -599,7 +624,18 @@ func (s *Scroll) refreshWithoutOffsetUpdate() {

// Scrolled is called when an input device triggers a scroll event
func (s *Scroll) Scrolled(ev *fyne.ScrollEvent) {
if s.Direction != ScrollNone {
if s.Direction == ScrollNone {
return
}

checkEnvDisableSmoothScrolling()
if s.scrollAnimation != nil {
s.targetOffset = s.targetOffset.Subtract(ev.Scrolled)
} else if !isSmoothScrollingDisabled && fyne.CurrentApp().Settings().ShowAnimations() {
s.targetOffset = s.Offset.Subtract(ev.Scrolled)
s.scrollAnimation = fyne.NewAnimation(time.Duration(math.MaxInt64), s.animateScroll)
s.scrollAnimation.Start()
} else {
s.scrollBy(ev.Scrolled.DX, ev.Scrolled.DY)
}
}
Expand Down Expand Up @@ -644,6 +680,23 @@ func (s *Scroll) updateOffset(deltaX, deltaY float32) bool {
return moved
}

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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?

s.scrollAnimation.Stop()
s.scrollAnimation = nil
s.scrollBy(diff.X, diff.Y)
return
}

oldOffset := s.Offset
s.scrollBy(diff.X*smoothScrollingFactor, diff.Y*smoothScrollingFactor)
if s.Offset == oldOffset {
s.scrollAnimation.Stop()
s.scrollAnimation = nil
}
}

func computeOffset(start, delta, outerWidth, innerWidth float32) float32 {
offset := start + delta
if offset+outerWidth >= innerWidth {
Expand Down
2 changes: 2 additions & 0 deletions test/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package test // import "fyne.io/fyne/v2/test"

import (
"net/url"
"os"
"sync"
"testing"

Expand All @@ -18,6 +19,7 @@ import (
// ensure we have a dummy app loaded and ready to test
func init() {
NewApp()
os.Setenv("FYNE_DISABLE_SMOOTH_SCROLLING", "true")
}

type app struct {
Expand Down
Loading