This commit is contained in:
Trevor Slocum 2023-12-19 12:21:16 -08:00
parent 84765fcd5a
commit 801d4c27a0
7 changed files with 303 additions and 21 deletions

View File

@ -22,6 +22,7 @@
- Frame: Widget container. All child widgets are displayed at once. Child widgets are not repositioned by default.
- Grid: Highly customizable cell-based layout. Each widget added to the Grid may span multiple cells.
- Input: Text input widget. The Input widget is simply a Text widget that also accepts user input.
- List: A list of widgets as selectable items.
- Text: Text display widget.
- Window: Widget paging mechanism. Only one widget added to a window is displayed at a time.

17
doc.go
View File

@ -8,13 +8,14 @@ based on official widgets.
The following official widgets are available:
Box - Building block for creating other widgets.
Button - Clickable button.
Flex - Flexible stack-based layout. Each Flex widget may be oriented horizontally or vertically.
Frame - Widget container. All child widgets are displayed at once. Child widgets are not repositioned by default.
Grid - Highly customizable cell-based layout. Widgets added to the Grid may span multiple cells.
Input - Text input widget. The Input widget is simply a Text widget that also accepts user input.
Text - Text display widget.
Window - Widget paging mechanism. Only one widget added to a window is displayed at a time.
Box - Building block for creating other widgets.
Button - Clickable button.
Flex - Flexible stack-based layout. Each Flex widget may be oriented horizontally or vertically.
Frame - Widget container. All child widgets are displayed at once. Child widgets are not repositioned by default.
Grid - Highly customizable cell-based layout. Widgets added to the Grid may span multiple cells.
Input - Text input widget. The Input widget is simply a Text widget that also accepts user input.
List - A list of widgets as selectable items.
Text - Text display widget.
Window - Widget paging mechanism. Only one widget added to a window is displayed at a time.
*/
package etk

4
go.mod
View File

@ -3,7 +3,7 @@ module code.rocket9labs.com/tslocum/etk
go 1.18
require (
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231128010227-689683b75174
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231218071755-e4087431ad9f
github.com/hajimehoshi/ebiten/v2 v2.6.3
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44
golang.org/x/image v0.14.0
@ -13,7 +13,7 @@ require (
github.com/ebitengine/purego v0.5.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/jezek/xgb v1.1.1 // indirect
golang.org/x/exp/shiny v0.0.0-20231214170342-aacd6d4b4611 // indirect
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848 // indirect
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect

8
go.sum
View File

@ -1,5 +1,5 @@
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231128010227-689683b75174 h1:jxE3JcaE4ovMWaZHLA6MvYgkmdkdbEEUv943Bp+MoxU=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231128010227-689683b75174/go.mod h1:yLsZtW6XgrQPpqBpQfJJkueurBUUYNnkPr68cS1MtJc=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231218071755-e4087431ad9f h1:/6Mpu+9TfQyee7uJ1epZEEfGGCT67bvZKpVhWy8T7cs=
code.rocketnine.space/tslocum/messeji v1.0.6-0.20231218071755-e4087431ad9f/go.mod h1:hA/frrbchjCX75HUG/GH97X3eH8xw++AKC2F+z+uXyA=
github.com/ebitengine/purego v0.5.1 h1:hNunhThpOf1vzKl49v6YxIsXLhl92vbBEv1/2Ez3ZrY=
github.com/ebitengine/purego v0.5.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@ -12,8 +12,8 @@ github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFl
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44 h1:1ad70i0s40IpMtRm2ST+Nvr03X7mlHWtdALYkFNrlxk=
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44/go.mod h1:muweRyJCZ1mZSMiCgYbAicfnwZFoeHpNr6A6QBu+rBg=
github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e h1:ZAvbj5hI/G/EbAYAcj4yCXUNiFKefEhH0qfImDDD0/8=
golang.org/x/exp/shiny v0.0.0-20231214170342-aacd6d4b4611 h1:4KULGL0aJvCqUs5c7fA/eWELbK2rVfV9wpZTWW1Qs/I=
golang.org/x/exp/shiny v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848 h1:LnDWUUS+bxOesHc+QBvFFmS4v0ZH+Vtg0EncMANwN9Q=
golang.org/x/exp/shiny v0.0.0-20231219180239-dc181d75b848/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=

View File

@ -142,13 +142,6 @@ func (g *Grid) Draw(screen *ebiten.Image) error {
g.updated = false
}
for _, child := range g.children {
err := child.Draw(screen)
if err != nil {
return err
}
}
return nil
}

276
list.go Normal file
View File

@ -0,0 +1,276 @@
package etk
import (
"image"
"image/color"
"sync"
"github.com/hajimehoshi/ebiten/v2"
)
// SelectionMode represents a mode of selection.
type SelectionMode int
// Selection modes.
const (
// SelectNone disables selection.
SelectNone SelectionMode = iota
// SelectRow enables selection by row.
SelectRow
// SelectColumn enables selection by column.
SelectColumn
)
// List is a list of widgets. Rows or cells may optionally be selectable.
type List struct {
grid *Grid
itemHeight int
highlightColor color.RGBA
maxY int
selectionMode SelectionMode
selectedX, selectedY int
selectedFunc func(index int) (accept bool)
sync.Mutex
}
// NewList returns a new List widget.
func NewList(itemHeight int, onSelected func(index int) (accept bool)) *List {
return &List{
grid: NewGrid(),
itemHeight: itemHeight,
highlightColor: color.RGBA{255, 255, 255, 255},
maxY: -1,
selectedY: -1,
selectedFunc: onSelected,
}
}
// Rect returns the position and size of the widget.
func (l *List) Rect() image.Rectangle {
l.Lock()
defer l.Unlock()
return l.grid.Rect()
}
// SetRect sets the position and size of the widget.
func (l *List) SetRect(r image.Rectangle) {
l.Lock()
defer l.Unlock()
l.grid.SetRect(r)
}
// Background returns the background color of the widget.
func (l *List) Background() color.RGBA {
l.Lock()
defer l.Unlock()
return l.grid.Background()
}
// SetBackground sets the background color of the widget.
func (l *List) SetBackground(background color.RGBA) {
l.Lock()
defer l.Unlock()
l.grid.SetBackground(background)
}
// Focus returns the focus state of the widget.
func (l *List) Focus() bool {
l.Lock()
defer l.Unlock()
return l.grid.Focus()
}
// SetFocus sets the focus state of the widget.
func (l *List) SetFocus(focus bool) (accept bool) {
l.Lock()
defer l.Unlock()
return l.grid.SetFocus(focus)
}
// Visible returns the visibility of the widget.
func (l *List) Visible() bool {
l.Lock()
defer l.Unlock()
return l.grid.Visible()
}
// SetVisible sets the visibility of the widget.
func (l *List) SetVisible(visible bool) {
l.Lock()
defer l.Unlock()
l.grid.SetVisible(visible)
}
// SetColumnSizes sets the size of each column. A size of -1 represents an equal
// proportion of the available space.
func (l *List) SetColumnSizes(size ...int) {
l.Lock()
defer l.Unlock()
l.grid.SetColumnSizes(size...)
}
// SetItemHeight sets the height of the list items.
func (l *List) SetItemHeight(itemHeight int) {
l.Lock()
defer l.Unlock()
if l.itemHeight == itemHeight {
return
}
l.itemHeight = itemHeight
if l.maxY == -1 {
return
}
rowSizes := make([]int, l.maxY+1)
for i := range rowSizes {
rowSizes[i] = l.itemHeight
}
l.grid.SetRowSizes(rowSizes...)
}
// SetSelectionMode sets the selection mode of the list.
func (l *List) SetSelectionMode(selectionMode SelectionMode) {
l.Lock()
defer l.Unlock()
if l.selectionMode == selectionMode {
return
}
l.selectionMode = selectionMode
}
// SetHighlightColor sets the color used to highlight the currently selected item.
func (l *List) SetHighlightColor(c color.RGBA) {
l.Lock()
defer l.Unlock()
l.highlightColor = c
}
// SelectedItem returns the selected list item.
func (l *List) SelectedItem() (x int, y int) {
l.Lock()
defer l.Unlock()
return l.selectedX, l.selectedY
}
// SetSelectedItem sets the selected list item.
func (l *List) SetSelectedItem(x int, y int) {
l.Lock()
defer l.Unlock()
l.selectedX, l.selectedY = x, y
}
// SetSelectedFunc sets a handler which is called when a list item is selected.
// Providing a nil function value will remove the existing handler (if set).
// The handler may return false to return the selection to its original state.
func (l *List) SetSelectedFunc(f func(index int) (accept bool)) {
l.Lock()
defer l.Unlock()
l.selectedFunc = f
}
// Children returns the children of the widget. Children are drawn in the
// order they are returned. Keyboard and mouse events are passed to children
// in reverse order.
func (l *List) Children() []Widget {
l.Lock()
defer l.Unlock()
return l.grid.Children()
}
// AddChildAt adds a widget to the list at the specified position.
func (l *List) AddChildAt(w Widget, x int, y int) {
l.Lock()
defer l.Unlock()
l.grid.AddChildAt(&ignoreMouse{w}, x, y, 1, 1)
if y > l.maxY {
l.maxY = y
rowSizes := make([]int, l.maxY+1)
for i := range rowSizes {
rowSizes[i] = l.itemHeight
}
l.grid.SetRowSizes(rowSizes...)
}
}
// HandleKeyboard is called when a keyboard event occurs.
func (l *List) HandleKeyboard(key ebiten.Key, r rune) (handled bool, err error) {
l.Lock()
defer l.Unlock()
return l.grid.HandleKeyboard(key, r)
}
// HandleMouse is called when a mouse event occurs. Only mouse events that
// are on top of the widget are passed to the widget.
func (l *List) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
l.Lock()
defer l.Unlock()
if !clicked || (cursor.X == 0 && cursor.Y == 0) {
return true, nil
}
selected := (cursor.Y - l.grid.rect.Min.Y) / l.itemHeight
if selected >= 0 && selected <= l.maxY {
lastSelected := l.selectedY
l.selectedY = selected
if l.selectedFunc != nil {
accept := l.selectedFunc(l.selectedY)
if !accept {
l.selectedY = lastSelected
}
}
}
return true, nil
}
// Draw draws the widget on the screen.
func (l *List) Draw(screen *ebiten.Image) error {
l.Lock()
defer l.Unlock()
// Draw grid.
err := l.grid.Draw(screen)
if err != nil {
return err
}
// Highlight selection.
if l.selectionMode == SelectNone || l.selectedY < 0 {
return nil
}
x, y := l.grid.rect.Min.X, l.grid.rect.Min.Y+l.selectedY*l.itemHeight
w, h := l.grid.rect.Dx(), l.itemHeight
r := image.Rect(x, y, x+w, y+h)
screen.SubImage(r).(*ebiten.Image).Fill(l.highlightColor)
return nil
}
// Clear clears all items from the list.
func (l *List) Clear() {
l.Lock()
defer l.Unlock()
l.grid.Clear()
l.maxY = -1
l.selectedX, l.selectedY = 0, -1
}

View File

@ -49,3 +49,14 @@ type Widget interface {
// in reverse order.
Children() []Widget
}
// ignoreMouse wraps a widget to ignore mouse events.
type ignoreMouse struct {
Widget
}
// HandleMouse is called when a mouse event occurs. Only mouse events that
// are on top of the widget are passed to the widget.
func (i *ignoreMouse) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
return false, nil
}