From 19457dadf0cbedc9d81c4ee55e42dc3c14f4b2b3 Mon Sep 17 00:00:00 2001 From: Neal McConachie Date: Sat, 10 Jul 2021 18:38:35 +0100 Subject: [PATCH] Export table scrolling methods - ScrollTo, ScrollToEnd, ScrollToStart - addresses #1892 --- widget/table.go | 44 ++++++++++++ widget/table_test.go | 159 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/widget/table.go b/widget/table.go index e1279cebbd..99fada4d18 100644 --- a/widget/table.go +++ b/widget/table.go @@ -151,6 +151,50 @@ func (t *Table) UnselectAll() { } } +// ScrollTo will scroll to the given cell without changing the selection. +// Attempting to scroll beyond the limits of the table will scroll to +// the edge of the table instead. +// +// Since: 2.1 +func (t *Table) ScrollTo(id TableCellID) { + if t.Length == nil { + return + } + + rows, cols := t.Length() + if id.Row >= rows { + id.Row = rows - 1 + } + + if id.Col >= cols { + id.Col = cols - 1 + } + + t.scrollTo(id) +} + +// ScrollToEnd will scroll to the last cell in the table +// +// Since: 2.1 +func (t *Table) ScrollToEnd() { + if t.Length == nil { + return + } + + rows, cols := t.Length() + t.scrollTo(TableCellID{ + Row: rows - 1, + Col: cols - 1, + }) +} + +// ScrollToStart will scroll to the first cell in the table +// +// Since: 2.1 +func (t *Table) ScrollToStart() { + t.scrollTo(TableCellID{0, 0}) +} + func (t *Table) scrollTo(id TableCellID) { if t.scroll == nil { return diff --git a/widget/table_test.go b/widget/table_test.go index 2bfcafba8d..2b2dd82e7f 100644 --- a/widget/table_test.go +++ b/widget/table_test.go @@ -189,6 +189,165 @@ func TestTable_Refresh(t *testing.T) { assert.Equal(t, "replaced", cellRenderer.(*tableCellsRenderer).Objects()[7].(*Label).Text) } +func TestTable_ScrollTo(t *testing.T) { + test.NewApp() + defer test.NewApp() + + // for this test the separator thickness is 0 + test.ApplyTheme(t, &separatorThicknessZeroTheme{test.Theme()}) + + // we will test a 20 row x 5 column table where each cell is 50x50 + const ( + maxRows int = 20 + maxCols int = 5 + 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() + + // these position expectations have a built-in assumption that the window + // is smaller than the size of a single table cell. + expectedOffset := func(row, col float32) fyne.Position { + return fyne.Position{ + X: col * width, + Y: row * height, + } + } + + tt := []struct { + name string + in TableCellID + want fyne.Position + }{ + { + "row 0, col 0", + TableCellID{}, + expectedOffset(0, 0), + }, + { + "row 0, col 1", + TableCellID{Row: 0, Col: 1}, + expectedOffset(0, 1), + }, + { + "row 1, col 0", + TableCellID{Row: 1, Col: 0}, + expectedOffset(1, 0), + }, + { + "row 1, col 1", + TableCellID{Row: 1, Col: 1}, + expectedOffset(1, 1), + }, + { + "second last element", + TableCellID{Row: maxRows - 2, Col: maxCols - 2}, + expectedOffset(float32(maxRows)-2, float32(maxCols)-2), + }, + { + "last element", + TableCellID{Row: maxRows - 1, Col: maxCols - 1}, + expectedOffset(float32(maxRows)-1, float32(maxCols)-1), + }, + { + "row 0, col 0 (scrolling backwards)", + TableCellID{}, + expectedOffset(0, 0), + }, + { + "row 99, col 99 (scrolling beyond the end)", + TableCellID{Row: 99, Col: 99}, + expectedOffset(float32(maxRows)-1, float32(maxCols)-1), + }, + { + "row -1, col -1 (scrolling before the start)", + TableCellID{Row: -1, Col: -1}, + expectedOffset(0, 0), + }, + } + + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + table.ScrollTo(tc.in) + assert.Equal(t, tc.want, table.offset) + assert.Equal(t, tc.want, table.scroll.Offset) + }) + } +} + +func TestTable_ScrollToEnd(t *testing.T) { + test.NewApp() + defer test.NewApp() + + // we will test a 20 row x 5 column table where each cell is 50x50 + // this table has a separator thickness of 1 + const ( + maxRows int = 20 + maxCols int = 5 + 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() + + table.ScrollToEnd() + + // element size of 50, plus separator thickness of 1 + want := fyne.Position{X: 4 * (50 + 1), Y: 19 * (50 + 1)} + assert.Equal(t, want, table.offset) + assert.Equal(t, want, table.scroll.Offset) +} + +func TestTable_ScrollToStart(t *testing.T) { + test.NewApp() + defer test.NewApp() + + const ( + maxRows int = 20 + maxCols int = 5 + 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() + + table.scrollTo(TableCellID{12, 3}) + table.ScrollToStart() + + want := fyne.Position{} + assert.Equal(t, want, table.offset) + assert.Equal(t, want, table.scroll.Offset) +} + func TestTable_Selection(t *testing.T) { test.NewApp() defer test.NewApp()