Add Select

This commit is contained in:
Trevor Slocum 2024-01-11 17:07:01 -08:00
parent bde57bc0ac
commit fb51eb32ad
6 changed files with 232 additions and 26 deletions

2
box.go
View file

@ -34,7 +34,7 @@ func (b *Box) Rect() image.Rectangle {
return b.rect
}
// SetRect returns the position and size of the widget.
// SetRect sets the position and size of the widget.
func (b *Box) SetRect(r image.Rectangle) {
b.Lock()
b.rect = r

View file

@ -73,7 +73,6 @@ func (b *Button) HandleMouse(cursor image.Point, pressed bool, clicked bool) (ha
// Draw draws the button on the screen.
func (b *Button) Draw(screen *ebiten.Image) error {
// TODO background color
// Draw background.
screen.SubImage(b.rect).(*ebiten.Image).Fill(Style.ButtonBgColor)

31
game.go
View file

@ -114,8 +114,13 @@ func SetDebug(debug bool) {
drawDebug = debug
}
// ScreenSize returns the current screen size.
func ScreenSize() (width int, height int) {
return lastWidth, lastHeight
}
// Layout sets the current screen size and resizes the root widget.
func Layout(outsideWidth, outsideHeight int) {
func Layout(outsideWidth int, outsideHeight int) {
if outsideWidth != lastWidth || outsideHeight != lastHeight {
lastWidth, lastHeight = outsideWidth, outsideHeight
}
@ -242,28 +247,22 @@ func Update() error {
}
func at(w Widget, p image.Point) Widget {
if w == nil {
return nil
}
if !p.In(w.Rect()) {
if w == nil || !w.Visible() {
return nil
}
for _, child := range w.Children() {
if !child.Visible() {
continue
}
if p.In(child.Rect()) {
result := at(child, p)
if result != nil {
return result
}
result := at(child, p)
if result != nil {
return result
}
}
return w
if p.In(w.Rect()) {
return w
}
return nil
}
// At returns the widget at the provided screen location.

View file

@ -15,7 +15,7 @@ type Keyboard struct {
incoming []*kibodo.Input
}
// NewBox returns a new Keyboard widget.
// NewKeyboard returns a new Keyboard widget.
func NewKeyboard() *Keyboard {
k := kibodo.NewKeyboard()
k.Show()

28
list.go
View file

@ -41,6 +41,7 @@ type List struct {
scrollAreaColor color.RGBA
scrollHandleColor color.RGBA
scrollDrag bool
drawBorder bool
sync.Mutex
}
@ -282,6 +283,11 @@ func (l *List) HandleKeyboard(key ebiten.Key, r rune) (handled bool, err error)
return l.grid.HandleKeyboard(key, r)
}
// SetDrawBorder enables or disables borders being drawn around the list.
func (l *List) SetDrawBorder(drawBorder bool) {
l.drawBorder = drawBorder
}
// 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) {
@ -380,15 +386,23 @@ func (l *List) Draw(screen *ebiten.Image) error {
}
// Highlight selection.
if l.selectionMode == SelectNone || l.selectedY < 0 {
return nil
drawHighlight := l.selectionMode != SelectNone && l.selectedY >= 0
if drawHighlight {
{
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)
}
}
{
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)
// Draw border.
if l.drawBorder {
const borderSize = 4
screen.SubImage(image.Rect(l.grid.rect.Min.X, l.grid.rect.Min.Y, l.grid.rect.Max.X, l.grid.rect.Min.Y+borderSize)).(*ebiten.Image).Fill(Style.BorderColor)
screen.SubImage(image.Rect(l.grid.rect.Min.X, l.grid.rect.Max.Y-borderSize, l.grid.rect.Max.X, l.grid.rect.Max.Y)).(*ebiten.Image).Fill(Style.BorderColor)
screen.SubImage(image.Rect(l.grid.rect.Min.X, l.grid.rect.Min.Y, l.grid.rect.Min.X+borderSize, l.grid.rect.Max.Y)).(*ebiten.Image).Fill(Style.BorderColor)
screen.SubImage(image.Rect(l.grid.rect.Max.X-borderSize, l.grid.rect.Min.Y, l.grid.rect.Max.X, l.grid.rect.Max.Y)).(*ebiten.Image).Fill(Style.BorderColor)
}
// Draw scroll bar.

194
select.go Normal file
View file

@ -0,0 +1,194 @@
package etk
import (
"image"
"image/color"
"code.rocketnine.space/tslocum/messeji"
"github.com/hajimehoshi/ebiten/v2"
)
// Select is a dropdown selection widget.
type Select struct {
*Box
label *Text
list *List
onSelect func(index int) (accept bool)
items []string
open bool
}
// NewSelect returns a new Select widget.
func NewSelect(itemHeight int, onSelect func(index int) (accept bool)) *Select {
s := &Select{
Box: NewBox(),
label: NewText(""),
onSelect: onSelect,
}
s.label.SetVertical(messeji.AlignCenter)
s.label.SetForegroundColor(Style.ButtonTextColor)
s.label.SetBackgroundColor(Style.ButtonBgColor)
s.list = NewList(itemHeight, s.selectList)
s.list.SetBackground(Style.ButtonBgColor)
s.list.SetDrawBorder(true)
s.list.SetVisible(false)
s.list.SetSelectionMode(SelectRow)
s.AddChild(s.list)
s.updateLabel()
return s
}
// SetRect sets the position and size of the widget.
func (s *Select) SetRect(r image.Rectangle) {
s.Lock()
defer s.Unlock()
s.rect = r
s.label.SetRect(r)
listRect := r.Add(image.Point{X: 0, Y: r.Dy()})
itemCount := len(s.items)
listRect.Max.Y = listRect.Min.Y + itemCount*s.list.itemHeight
_, height := ScreenSize()
if listRect.Max.Y > height {
listRect.Max.Y = height
}
s.list.SetRect(listRect)
}
// SetHighlightColor sets the color used to highlight the currently selected item.
func (s *Select) SetHighlightColor(c color.RGBA) {
s.list.SetHighlightColor(c)
}
// SetSelectedItem sets the currently selected item.
func (s *Select) SetSelectedItem(index int) {
s.Lock()
defer s.Unlock()
if index < 0 || index >= len(s.items) {
return
}
s.list.SetSelectedItem(0, index)
s.updateLabel()
}
// Children returns the children of the widget.
func (s *Select) Children() []Widget {
s.Lock()
defer s.Unlock()
return s.children
}
// AddChild adds a child to the widget. Selection options are added via AddOption.
func (s *Select) AddChild(w ...Widget) {
s.Lock()
defer s.Unlock()
s.children = append(s.children, w...)
}
// Clear removes all children from the widget.
func (s *Select) Clear() {
s.Lock()
defer s.Unlock()
s.items = nil
s.list.Clear()
s.updateLabel()
}
// AddOption adds an option to the widget.
func (s *Select) AddOption(label string) {
s.Lock()
defer s.Unlock()
s.items = append(s.items, label)
if len(s.items) == 1 {
s.list.selectedY = 0
s.updateLabel()
}
t := NewText(label)
t.SetVertical(messeji.AlignCenter)
t.SetForegroundColor(Style.ButtonTextColor)
s.list.AddChildAt(t, 0, len(s.items)-1)
}
func (s *Select) updateLabel() {
var text string
if len(s.items) > 0 && s.list.selectedY >= 0 && s.list.selectedY < len(s.items) {
text = s.items[s.list.selectedY]
}
if s.open {
text = "▼ " + text
} else {
text = "▶ " + text
}
s.label.SetText(text)
}
func (s *Select) selectList(index int) (accept bool) {
s.Lock()
s.list.grid.visible = false
s.open = false
onSelect := s.onSelect
s.Unlock()
if onSelect != nil {
if !onSelect(index) {
return false
}
}
s.list.selectedY = index
s.updateLabel()
return true
}
// SetMenuVisible sets the visibility of the dropdown menu.
func (s *Select) SetMenuVisible(visible bool) {
s.Lock()
defer s.Unlock()
s._setMenuVisible(visible)
}
func (s *Select) _setMenuVisible(visible bool) {
s.open = visible
s.list.SetVisible(visible)
s.updateLabel()
}
// HandleKeyboard is called when a keyboard event occurs.
func (s *Select) HandleKeyboard(ebiten.Key, rune) (handled bool, err error) {
return false, nil
}
// HandleMouse is called when a mouse event occurs.
func (s *Select) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
s.Lock()
defer s.Unlock()
if clicked {
s._setMenuVisible(!s.open)
}
return true, nil
}
// Draw draws the widget on the screen.
func (s *Select) Draw(screen *ebiten.Image) error {
s.Lock()
defer s.Unlock()
// Draw label.
s.label.Draw(screen)
// Draw border.
const borderSize = 4
screen.SubImage(image.Rect(s.rect.Min.X, s.rect.Min.Y, s.rect.Max.X, s.rect.Min.Y+borderSize)).(*ebiten.Image).Fill(Style.BorderColor)
screen.SubImage(image.Rect(s.rect.Min.X, s.rect.Max.Y-borderSize, s.rect.Max.X, s.rect.Max.Y)).(*ebiten.Image).Fill(Style.BorderColor)
screen.SubImage(image.Rect(s.rect.Min.X, s.rect.Min.Y, s.rect.Min.X+borderSize, s.rect.Max.Y)).(*ebiten.Image).Fill(Style.BorderColor)
screen.SubImage(image.Rect(s.rect.Max.X-borderSize, s.rect.Min.Y, s.rect.Max.X, s.rect.Max.Y)).(*ebiten.Image).Fill(Style.BorderColor)
return nil
}