diff --git a/container/scroll.go b/container/scroll.go index ce9ec15ff1..dda8aa674f 100644 --- a/container/scroll.go +++ b/container/scroll.go @@ -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. diff --git a/internal/widget/scroller.go b/internal/widget/scroller.go index ee380b1f1e..3666e25c81 100644 --- a/internal/widget/scroller.go +++ b/internal/widget/scroller.go @@ -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 ( @@ -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() } diff --git a/internal/widget/scroller_internal_test.go b/internal/widget/scroller_internal_test.go index 10eeb9908b..209b00b95e 100644 --- a/internal/widget/scroller_internal_test.go +++ b/internal/widget/scroller_internal_test.go @@ -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) diff --git a/scroll.go b/scroll.go new file mode 100644 index 0000000000..98082d2fb5 --- /dev/null +++ b/scroll.go @@ -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 +) diff --git a/widget/entry.go b/widget/entry.go index dbc7afdd1d..c9d60a4d08 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -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 diff --git a/widget/gridwrap.go b/widget/gridwrap.go index bab21f2868..a4b9c9d231 100644 --- a/widget/gridwrap.go +++ b/widget/gridwrap.go @@ -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 @@ -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. diff --git a/widget/list.go b/widget/list.go index 50dddc4c5c..a460e96c5d 100644 --- a/widget/list.go +++ b/widget/list.go @@ -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. @@ -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 diff --git a/widget/list_internal_test.go b/widget/list_internal_test.go index 8178d03fb9..64be263ad6 100644 --- a/widget/list_internal_test.go +++ b/widget/list_internal_test.go @@ -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) diff --git a/widget/richtext.go b/widget/richtext.go index c3b4b74af8..502be75499 100644 --- a/widget/richtext.go +++ b/widget/richtext.go @@ -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 // diff --git a/widget/table.go b/widget/table.go index 1ebbb3f721..eede02c269 100644 --- a/widget/table.go +++ b/widget/table.go @@ -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 diff --git a/widget/table_internal_test.go b/widget/table_internal_test.go index a9d09405ee..7af0be4b6e 100644 --- a/widget/table_internal_test.go +++ b/widget/table_internal_test.go @@ -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) diff --git a/widget/textgrid.go b/widget/textgrid.go index fad401d3c0..0e5cb62f73 100644 --- a/widget/textgrid.go +++ b/widget/textgrid.go @@ -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. diff --git a/widget/tree.go b/widget/tree.go index 569130f89b..cd661074a3 100644 --- a/widget/tree.go +++ b/widget/tree.go @@ -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. @@ -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. @@ -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. @@ -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) diff --git a/widget/tree_internal_test.go b/widget/tree_internal_test.go index 5f99b008f5..11bcf7873c 100644 --- a/widget/tree_internal_test.go +++ b/widget/tree_internal_test.go @@ -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")