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

Fixing Scroller.ScrollToTop, alias to a new fyne.ScrollDirection type and align all collections on ScrollToOffset #5477

Merged
merged 5 commits into from
Feb 1, 2025
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
10 changes: 5 additions & 5 deletions container/scroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ type Scroll = widget.Scroll
// ScrollDirection represents the directions in which a Scroll container can scroll its child content.
//
// Since: 1.4
type ScrollDirection = widget.ScrollDirection
type ScrollDirection = fyne.ScrollDirection

// Constants for valid values of ScrollDirection.
const (
// ScrollBoth supports horizontal and vertical scrolling.
ScrollBoth ScrollDirection = widget.ScrollBoth
ScrollBoth ScrollDirection = fyne.ScrollBoth
// ScrollHorizontalOnly specifies the scrolling should only happen left to right.
ScrollHorizontalOnly = widget.ScrollHorizontalOnly
ScrollHorizontalOnly = fyne.ScrollHorizontalOnly
// ScrollVerticalOnly specifies the scrolling should only happen top to bottom.
ScrollVerticalOnly = widget.ScrollVerticalOnly
ScrollVerticalOnly = fyne.ScrollVerticalOnly
// ScrollNone turns off scrolling for this container.
//
// Since: 2.1
ScrollNone = widget.ScrollNone
ScrollNone = fyne.ScrollNone
)

// NewScroll creates a scrollable parent wrapping the specified content.
Expand Down
4 changes: 2 additions & 2 deletions internal/widget/scroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// ScrollDirection represents the directions in which a Scroll can scroll its child content.
type ScrollDirection int
type ScrollDirection = fyne.ScrollDirection

// Constants for valid values of ScrollDirection.
const (
Expand Down Expand Up @@ -437,7 +437,7 @@ func (s *Scroll) ScrollToBottom() {

// ScrollToTop will scroll content to container top
func (s *Scroll) ScrollToTop() {
s.scrollBy(0, -s.Offset.Y)
s.ScrollToOffset(fyne.Position{})
s.refreshBars()
}

Expand Down
8 changes: 7 additions & 1 deletion internal/widget/scroller_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@ func TestScrollContainer_MinSize(t *testing.T) {

func TestScrollContainer_ScrollToTop(t *testing.T) {
rect := canvas.NewRectangle(color.Black)
rect.SetMinSize(fyne.NewSize(500, 50))
rect.SetMinSize(fyne.NewSize(500, 150))
scroll := NewScroll(rect)
scroll.Resize(fyne.NewSize(25, 25))

tmpOffset := fyne.NewPos(25, 50)
scroll.ScrollToOffset(tmpOffset)
assert.Equal(t, tmpOffset, scroll.Offset)

scroll.ScrollToTop()
Y := scroll.Offset.Y
assert.Equal(t, float32(0), Y)
Expand Down
18 changes: 18 additions & 0 deletions scroll.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package fyne

// ScrollDirection represents the directions in which a scrollable container or widget can scroll its child content.
//
// Since: 2.6
type ScrollDirection int

// Constants for valid values of ScrollDirection used in containers and widgets.
const (
// ScrollBoth supports horizontal and vertical scrolling.
ScrollBoth ScrollDirection = iota
// ScrollHorizontalOnly specifies the scrolling should only happen left to right.
ScrollHorizontalOnly
// ScrollVerticalOnly specifies the scrolling should only happen top to bottom.
ScrollVerticalOnly
// ScrollNone turns off scrolling for this container.
ScrollNone
)
2 changes: 1 addition & 1 deletion widget/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type Entry struct {
// Scroll can be used to turn off the scrolling of our entry when Wrapping is WrapNone.
//
// Since: 2.4
Scroll widget.ScrollDirection
Scroll fyne.ScrollDirection

// Set a validator that this entry will check against
// Since: 1.4
Expand Down
18 changes: 5 additions & 13 deletions widget/gridwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,21 +214,14 @@ func (l *GridWrap) ScrollTo(id GridWrapItemID) {

// ScrollToBottom scrolls to the end of the list
func (l *GridWrap) ScrollToBottom() {
length := 0
if f := l.Length; f != nil {
length = f()
}
if length > 0 {
length--
}
l.scrollTo(GridWrapItemID(length))
l.Refresh()
l.scroller.ScrollToBottom()
l.offsetUpdated(l.scroller.Offset)
}

// ScrollToTop scrolls to the start of the list
func (l *GridWrap) ScrollToTop() {
l.scrollTo(0)
l.Refresh()
l.scroller.ScrollToTop()
l.offsetUpdated(l.scroller.Offset)
}

// ScrollToOffset scrolls the list to the given offset position
Expand All @@ -246,9 +239,8 @@ func (l *GridWrap) ScrollToOffset(offset float32) {
if offset > contentHeight {
offset = contentHeight
}
l.scroller.Offset.Y = offset
l.scroller.ScrollToOffset(fyne.NewPos(0, offset))
l.offsetUpdated(l.scroller.Offset)
l.Refresh()
}

// TypedKey is called if a key event happens while this GridWrap is focused.
Expand Down
18 changes: 5 additions & 13 deletions widget/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,23 +260,16 @@ func (l *List) ScrollTo(id ListItemID) {
//
// Since: 2.1
func (l *List) ScrollToBottom() {
length := 0
if f := l.Length; f != nil {
length = f()
}
if length > 0 {
length--
}
l.scrollTo(length)
l.Refresh()
l.scroller.ScrollToBottom()
l.offsetUpdated(l.scroller.Offset)
}

// ScrollToTop scrolls to the start of the list
//
// Since: 2.1
func (l *List) ScrollToTop() {
l.scrollTo(0)
l.Refresh()
l.scroller.ScrollToTop()
l.offsetUpdated(l.scroller.Offset)
}

// ScrollToOffset scrolls the list to the given offset position.
Expand All @@ -296,9 +289,8 @@ func (l *List) ScrollToOffset(offset float32) {
if offset > contentHeight {
offset = contentHeight
}
l.scroller.Offset.Y = offset
l.scroller.ScrollToOffset(fyne.NewPos(0, offset))
l.offsetUpdated(l.scroller.Offset)
l.Refresh()
}

// GetScrollOffset returns the current scroll offset position
Expand Down
5 changes: 5 additions & 0 deletions widget/list_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ func TestList_ScrollToBottom(t *testing.T) {
func TestList_ScrollToTop(t *testing.T) {
list := createList(1000)

tmpOffset := float32(50)
list.ScrollToOffset(tmpOffset)
assert.Equal(t, tmpOffset, list.offsetY)
assert.Equal(t, tmpOffset, list.scroller.Offset.Y)

offset := float32(0)
list.ScrollToTop()
assert.Equal(t, offset, list.offsetY)
Expand Down
2 changes: 1 addition & 1 deletion widget/richtext.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type RichText struct {
BaseWidget
Segments []RichTextSegment
Wrapping fyne.TextWrap
Scroll widget.ScrollDirection
Scroll fyne.ScrollDirection

// The truncation mode of the text
//
Expand Down
13 changes: 13 additions & 0 deletions widget/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,19 @@ func (t *Table) ScrollToLeading() {
t.finishScroll()
}

// ScrollToOffset scrolls the table to a specific position
//
// Since: 2.6
func (t *Table) ScrollToOffset(off fyne.Position) {
if t.content == nil {
return
}

t.content.ScrollToOffset(off)
t.offset = t.content.Offset
t.finishScroll()
}

// ScrollToTop scrolls to the first row in the table
//
// Since: 2.1
Expand Down
27 changes: 27 additions & 0 deletions widget/table_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,33 @@ func TestTable_ScrollToLeading(t *testing.T) {
assert.Equal(t, want, table.content.Offset)
}

func TestTable_ScrollToOffset(t *testing.T) {
test.NewTempApp(t)

const (
maxRows int = 6
maxCols int = 10
width float32 = 50
height float32 = 50
)

templ := canvas.NewRectangle(color.Gray16{})
templ.SetMinSize(fyne.Size{Width: width, Height: height})

table := NewTable(
func() (int, int) { return maxRows, maxCols },
func() fyne.CanvasObject { return templ },
func(TableCellID, fyne.CanvasObject) {})

w := test.NewWindow(table)
defer w.Close()

want := fyne.NewPos(48, 25)
table.ScrollToOffset(want)
assert.Equal(t, want, table.offset)
assert.Equal(t, want, table.content.Offset)
}

func TestTable_ScrollToTop(t *testing.T) {
test.NewTempApp(t)

Expand Down
2 changes: 1 addition & 1 deletion widget/textgrid.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type TextGrid struct {
// Scroll can be used to turn off the scrolling of our TextGrid.
//
// Since: 2.6
Scroll widget.ScrollDirection
Scroll fyne.ScrollDirection
}

// Append will add new lines to the end of this TextGrid.
Expand Down
57 changes: 21 additions & 36 deletions widget/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,8 @@ func (t *Tree) ScrollToBottom() {
return
}

y, size := t.findBottom()
t.scroller.Offset.Y = y + size.Height - t.scroller.Size().Height

t.scroller.ScrollToBottom()
t.offsetUpdated(t.scroller.Offset)
t.Refresh()
}

// ScrollTo scrolls to the node with the given id.
Expand All @@ -267,14 +264,30 @@ func (t *Tree) ScrollTo(uid TreeNodeID) {
}

// TODO scrolling to a node should open all parents if they aren't already
newY := t.scroller.Offset.Y
if y < t.scroller.Offset.Y {
t.scroller.Offset.Y = y
newY = y
} else if y+size.Height > t.scroller.Offset.Y+t.scroller.Size().Height {
t.scroller.Offset.Y = y + size.Height - t.scroller.Size().Height
newY = y + size.Height - t.scroller.Size().Height
}

t.scroller.ScrollToOffset(fyne.NewPos(0, newY))
t.offsetUpdated(t.scroller.Offset)
}

// ScrollToOffset scrolls the tree to the given offset position.
//
// Since: 2.6
func (t *Tree) ScrollToOffset(offset float32) {
if t.scroller == nil {
return
}
if offset < 0 {
offset = 0
}

t.scroller.ScrollToOffset(fyne.NewPos(0, offset))
t.offsetUpdated(t.scroller.Offset)
t.Refresh()
}

// ScrollToTop scrolls to the top of the tree.
Expand All @@ -285,9 +298,8 @@ func (t *Tree) ScrollToTop() {
return
}

t.scroller.Offset.Y = 0
t.scroller.ScrollToTop()
t.offsetUpdated(t.scroller.Offset)
t.Refresh()
}

// Select marks the specified node to be selected.
Expand Down Expand Up @@ -428,33 +440,6 @@ func (t *Tree) ensureOpenMap() {
}
}

func (t *Tree) findBottom() (y float32, size fyne.Size) {
sep := t.Theme().Size(theme.SizeNamePadding)
t.walkAll(func(id, _ TreeNodeID, branch bool, _ int) {
size = t.leafMinSize
if branch {
size = t.branchMinSize
}

// Root node is not rendered unless it has been customized
if t.Root == "" && id == "" {
// This is root node, skip
return
}

// If this is not the first item, add a separator
if y > 0 {
y += sep
}

y += size.Height
})
if y > 0 {
y -= sep
}
return
}

func (t *Tree) offsetAndSize(uid TreeNodeID) (y float32, size fyne.Size, found bool) {
pad := t.Theme().Size(theme.SizeNamePadding)

Expand Down
23 changes: 23 additions & 0 deletions widget/tree_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,29 @@ func TestTree_ScrollToBottom(t *testing.T) {
assert.Equal(t, want, tree.scroller.Offset.Y)
}

func TestTree_ScrollOffset(t *testing.T) {
test.NewTempApp(t)
test.ApplyTheme(t, test.NewTheme())

data := make(map[string][]string)
addTreePath(data, "A")
addTreePath(data, "B", "C")
addTreePath(data, "D", "E", "F")
tree := NewTreeWithStrings(data)
tree.OpenBranch("B")
tree.OpenBranch("D")
tree.OpenBranch("E")

w := test.NewWindow(tree)
defer w.Close()

w.Resize(fyne.NewSize(100, 100))
tree.ScrollToOffset(20)

assert.Equal(t, float32(20), tree.offset.Y)
assert.Equal(t, float32(20), tree.scroller.Offset.Y)
}

func TestTree_ScrollToSelection(t *testing.T) {
data := make(map[string][]string)
addTreePath(data, "A")
Expand Down
Loading