Scroll List by pixels instead of items

This commit is contained in:
Trevor Slocum 2024-11-21 21:39:31 -08:00
parent 16faaf17c5
commit 37ba426f31
2 changed files with 73 additions and 67 deletions

View file

@ -3,15 +3,31 @@
package main
import (
"fmt"
"log"
"code.rocket9labs.com/tslocum/etk"
)
func newListExample() (etk.Widget, etk.Widget) {
text := etk.NewText(listLabel)
text.SetPadding(etk.Scale(10))
text.SetFollow(false)
const fontSize = 32
onSelected := func(index int) (accept bool) {
log.Printf("Selected item at index %d", index)
return true
}
return text, nil
ff := etk.FontFace(etk.Style.TextFont, fontSize)
m := ff.Metrics()
l := etk.NewList(int(m.HAscent+m.HDescent), onSelected)
for i := 0; i < 100; i++ {
t := etk.NewText(fmt.Sprintf("Item #%d", i+1))
t.SetVertical(etk.AlignCenter)
t.SetFont(etk.Style.TextFont, fontSize)
t.SetAutoResize(true)
l.AddChildAt(t, 0, i)
}
l.SetSelectedItem(0, 0)
return l, l
}
const listLabel = "The tab navigation to the right is a List."

116
list.go
View file

@ -72,7 +72,7 @@ func NewList(itemHeight int, onSelected func(index int) (accept bool)) *List {
return &List{
grid: NewGrid(),
itemHeight: itemHeight,
highlightColor: color.RGBA{80, 80, 80, 255},
highlightColor: color.RGBA{128, 128, 128, 255},
maxY: -1,
selectionMode: SelectRow,
selectedX: -1,
@ -107,8 +107,6 @@ func (l *List) SetRect(r image.Rectangle) {
if l.showScrollBar() {
r.Max.X -= l.scrollWidth
}
l.grid.SetRect(r)
l.selectionUpdated()
l.recreateGrid = true
}
@ -230,7 +228,6 @@ func (l *List) SetSelectedItem(x int, y int) {
defer l.Unlock()
l.selectedX, l.selectedY = x, y
l.selectionUpdated()
}
// SetScrollBarWidth sets the width of the scroll bar.
@ -334,13 +331,13 @@ func (l *List) Rows() int {
}
func (l *List) showScrollBar() bool {
return len(l.items) > l.grid.rect.Dy()/l.itemHeight
return len(l.items) > l.rect.Dy()/l.itemHeight
}
// clampOffset clamps the list offset.
func (l *List) clampOffset(offset int) int {
if offset >= len(l.items)-(l.grid.rect.Dy()/l.itemHeight) {
offset = len(l.items) - (l.grid.rect.Dy() / l.itemHeight)
if offset >= len(l.items)*l.itemHeight-l.rect.Dy() {
offset = len(l.items)*l.itemHeight - l.rect.Dy()
}
if offset < 0 {
offset = 0
@ -348,22 +345,6 @@ func (l *List) clampOffset(offset int) int {
return offset
}
func (l *List) selectionUpdated() {
if l.selectedY < l.offset {
l.offset = l.selectedY
l.recreateGrid = true
return
}
visible := l.grid.rect.Dy()/l.itemHeight - 1
if visible < 1 {
visible = 1
}
if l.selectedY > l.offset+visible {
l.offset = l.selectedY - visible
l.recreateGrid = true
}
}
// Cursor returns the cursor shape shown when a mouse cursor hovers over the
// widget, or -1 to let widgets beneath determine the cursor shape.
func (l *List) Cursor() ebiten.CursorShapeType {
@ -394,7 +375,6 @@ func (l *List) HandleKeyboard(key ebiten.Key, r rune) (handled bool, err error)
y = l.selectedY + y
if y >= 0 && y <= l.maxY {
l.selectedY = y
l.selectionUpdated()
}
}
for _, leftKey := range Bindings.MoveLeftKeyboard {
@ -444,7 +424,7 @@ func (l *List) HandleMouse(cursor image.Point, pressed bool, clicked bool) (hand
} else if scroll > maxScroll {
scroll = maxScroll
}
offset := l.clampOffset(l.offset - int(math.Round(scroll)))
offset := l.clampOffset(l.offset - int(math.Round(scroll))*3*l.itemHeight)
if offset != l.offset {
l.offset = offset
l.recreateGrid = true
@ -453,7 +433,7 @@ func (l *List) HandleMouse(cursor image.Point, pressed bool, clicked bool) (hand
if l.showScrollBar() && (pressed || l.scrollDrag) {
if pressed && cursor.In(l.scrollRect) {
dragY := cursor.Y - l.grid.rect.Min.Y
dragY := cursor.Y - l.rect.Min.Y
if dragY < 0 {
dragY = 0
} else if dragY > l.scrollRect.Dy() {
@ -468,7 +448,7 @@ func (l *List) HandleMouse(cursor image.Point, pressed bool, clicked bool) (hand
}
lastOffset := l.offset
offset := l.clampOffset(int(math.Round(float64(len(l.items)-(l.grid.rect.Dy()/l.itemHeight)) * pct)))
offset := l.clampOffset(int(math.Round(float64(len(l.items)*l.itemHeight-l.rect.Dy()) * pct)))
if offset != lastOffset {
l.offset = offset
l.recreateGrid = true
@ -483,7 +463,7 @@ func (l *List) HandleMouse(cursor image.Point, pressed bool, clicked bool) (hand
if !clicked || (cursor.X == 0 && cursor.Y == 0) {
return true, nil
}
selected := l.offset + (cursor.Y-l.grid.rect.Min.Y)/l.itemHeight
selected := (l.offset + cursor.Y - l.rect.Min.Y) / l.itemHeight
if selected >= 0 && selected <= l.maxY {
lastSelected := l.selectedY
l.selectedY = selected
@ -499,8 +479,6 @@ func (l *List) HandleMouse(cursor image.Point, pressed bool, clicked bool) (hand
}
}
l.selectionUpdated()
if selected == lastSelected && time.Since(l.selectedTime) <= Bindings.DoubleClickThreshold {
confirmedFunc := l.confirmedFunc
if confirmedFunc != nil {
@ -517,42 +495,54 @@ func (l *List) HandleMouse(cursor image.Point, pressed bool, clicked bool) (hand
return true, nil
}
func (l *List) _recreateCrid(screen *ebiten.Image) {
maxY := l.rect.Dy()/l.itemHeight + 1
if maxY < 2 {
maxY = 2
}
l.offset = l.clampOffset(l.offset)
l.grid.Clear()
rowSizes := make([]int, l.maxY+1)
for i := range rowSizes {
rowSizes[i] = l.itemHeight
}
l.grid.SetRowSizes(rowSizes...)
var y int
for i := range l.items {
if i*l.itemHeight < l.offset-l.itemHeight+1 {
continue
} else if y > maxY {
break
}
for x := range l.items[i] {
w := l.items[i][x]
if w == nil {
continue
}
l.grid.AddChildAt(w, x, y, 1, 1)
}
y++
}
r := l.rect
if l.showScrollBar() {
r.Max.X -= l.scrollWidth
}
remainder := l.offset % l.itemHeight
r.Min.Y = l.rect.Min.Y - remainder
l.grid.SetRect(r)
l.recreateGrid = false
}
// Draw draws the widget on the screen.
func (l *List) Draw(screen *ebiten.Image) error {
l.Lock()
defer l.Unlock()
if l.recreateGrid {
maxY := l.grid.rect.Dy()/l.itemHeight + 1
l.offset = l.clampOffset(l.offset)
l.grid.Clear()
rowSizes := make([]int, l.maxY+1)
for i := range rowSizes {
rowSizes[i] = l.itemHeight
}
l.grid.SetRowSizes(rowSizes...)
var y int
for i := range l.items {
if i < l.offset {
continue
} else if y >= maxY {
break
}
for x := range l.items[i] {
w := l.items[i][x]
if w == nil {
continue
}
l.grid.AddChildAt(w, x, y, 1, 1)
}
y++
}
r := l.rect
if l.showScrollBar() {
r.Max.X -= l.scrollWidth
}
l.grid.SetRect(r)
l.recreateGrid = false
l._recreateCrid(screen)
}
// Draw grid.
@ -564,8 +554,8 @@ func (l *List) Draw(screen *ebiten.Image) error {
// Highlight selection.
drawHighlight := l.selectionMode != SelectNone && l.selectedY >= 0
if drawHighlight {
x, y := l.grid.rect.Min.X, l.grid.rect.Min.Y+(l.selectedY-l.offset)*l.itemHeight
w, h := l.grid.rect.Dx(), l.itemHeight
x, y := l.rect.Min.X, l.rect.Min.Y+l.selectedY*l.itemHeight-l.offset
w, h := l.rect.Dx(), l.itemHeight
r := clampRect(image.Rect(x, y, x+w, y+h), l.rect)
if r.Dx() > 0 && r.Dy() > 0 {
screen.SubImage(r).(*ebiten.Image).Fill(l.highlightColor)
@ -595,7 +585,7 @@ func (l *List) Draw(screen *ebiten.Image) error {
}
scrollX, scrollY := l.rect.Min.X+w-l.scrollWidth, l.rect.Min.Y
pct := float64(-l.offset) / float64(len(l.items)-(l.rect.Dy()/l.itemHeight))
pct := float64(-l.offset) / float64(len(l.items)*l.itemHeight-l.rect.Dy())
scrollY -= int(float64(h-scrollBarH) * pct)
scrollBarRect := image.Rect(scrollX, scrollY, scrollX+l.scrollWidth, scrollY+scrollBarH)