Add widgets

This commit is contained in:
Trevor Slocum 2022-07-07 14:53:14 -07:00
parent 1b8a1f536a
commit 8d5f5af3c5
15 changed files with 446 additions and 120 deletions

View file

@ -5,7 +5,6 @@
[Ebitengine](https://github.com/hajimehoshi/ebiten) tool kit for creating graphical user interfaces
**Note:** This library is still in development. Breaking changes may be made until v1.0 is released.
[IME](https://en.wikipedia.org/wiki/Input_method) is not yet supported.

View file

@ -2,58 +2,36 @@ package etk
import (
"image"
"image/color"
"log"
"code.rocketnine.space/tslocum/messeji"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"github.com/hajimehoshi/ebiten/v2"
)
// TODO
var mplusNormalFont font.Face
func init() {
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
Size: 32,
DPI: dpi,
Hinting: font.HintingFull,
})
if err != nil {
log.Fatal(err)
}
}
type Button struct {
*Box
label *messeji.TextField
onSelected func() error
}
func NewButton(label string, onSelected func()) *Button {
func NewButton(label string, onSelected func() error) *Button {
textColor := Style.ButtonTextColor
if textColor == nil {
textColor = Style.TextColor
textColor = Style.TextColorDark
}
l := messeji.NewTextField(mplusNormalFont)
l := messeji.NewTextField(Style.TextFont)
l.SetText(label)
l.SetForegroundColor(textColor)
l.SetBackgroundColor(color.RGBA{0, 0, 0, 0})
l.SetBackgroundColor(transparent)
l.SetHorizontal(messeji.AlignCenter)
l.SetVertical(messeji.AlignCenter)
return &Button{
Box: NewBox(),
label: l, // TODO
Box: NewBox(),
label: l,
onSelected: onSelected,
}
}
@ -63,8 +41,20 @@ func (b *Button) SetRect(r image.Rectangle) {
b.label.SetRect(r)
}
func (b *Button) HandleMouse() (handled bool, err error) {
return false, nil
func (b *Button) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
if !clicked {
return true, nil
}
b.Lock()
onSelected := b.onSelected
if onSelected == nil {
b.Unlock()
return true, nil
}
b.Unlock()
return true, onSelected()
}
func (b *Button) HandleKeyboard() (handled bool, err error) {

View file

@ -30,4 +30,5 @@ func (g *game) Draw(screen *ebiten.Image) {
if err != nil {
panic(err)
}
}

View file

@ -4,24 +4,34 @@
package main
import (
"fmt"
"log"
"code.rocketnine.space/tslocum/etk"
"github.com/hajimehoshi/ebiten/v2"
)
func main() {
ebiten.SetWindowTitle("etk showcase")
ebiten.SetWindowTitle("etk flex example")
newButton := func(i int) *etk.Button {
return etk.NewButton(fmt.Sprintf("Button %d", i), func() error {
log.Printf("Pressed button %d", i)
return nil
})
}
g := newGame()
b1 := etk.NewButton("Button 1", nil)
b2 := etk.NewButton("Button 2", nil)
b1 := newButton(1)
b2 := newButton(2)
topFlex := etk.NewFlex()
topFlex.AddChild(b1, b2)
b3 := etk.NewButton("Button 3", nil)
b4 := etk.NewButton("Button 4", nil)
b5 := etk.NewButton("Button 5", nil)
b3 := newButton(3)
b4 := newButton(4)
b5 := newButton(5)
bottomFlex := etk.NewFlex()
bottomFlex.AddChild(b3, b4, b5)

View file

@ -4,33 +4,88 @@
package main
import (
"flag"
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"code.rocketnine.space/tslocum/etk"
"github.com/hajimehoshi/ebiten/v2"
)
func main() {
ebiten.SetWindowTitle("etk showcase")
var debugAddress string
flag.StringVar(&debugAddress, "debug", "", "serve debug information on address")
flag.Parse()
if debugAddress != "" {
go func() {
err := http.ListenAndServe(debugAddress, nil)
if err != nil {
log.Fatal(err)
}
}()
}
ebiten.SetWindowTitle("etk widget showcase")
g := newGame()
b1 := etk.NewButton("Button 1", nil)
b2 := etk.NewButton("Button 2", nil)
w := etk.NewWindow()
topFlex := etk.NewFlex()
topFlex.AddChild(b1, b2)
// Input demo.
{
buffer := etk.NewText("Press enter to append input below to this buffer.")
b3 := etk.NewButton("Button 3", nil)
b4 := etk.NewButton("Button 4", nil)
b5 := etk.NewButton("Button 5", nil)
onselected := func(text string) (handled bool) {
buffer.Write([]byte("\nInput: " + text))
return true
}
bottomFlex := etk.NewFlex()
bottomFlex.AddChild(b3, b4, b5)
input := etk.NewInput(">", "", onselected)
rootFlex := etk.NewFlex()
rootFlex.SetVertical(true)
rootFlex.AddChild(topFlex, bottomFlex)
inputDemo := etk.NewFlex()
inputDemo.SetVertical(true)
etk.SetRoot(rootFlex)
t := etk.NewText("Input")
inputDemo.AddChild(t, buffer, input)
w.AddChildWithLabel(inputDemo, "Input")
}
// Flex demo.
{
newButton := func(i int) *etk.Button {
return etk.NewButton(fmt.Sprintf("Button %d", i), func() error {
log.Printf("Pressed button %d", i)
return nil
})
}
b1 := newButton(1)
b2 := newButton(2)
topFlex := etk.NewFlex()
topFlex.AddChild(b1, b2)
b3 := newButton(3)
b4 := newButton(4)
b5 := newButton(5)
bottomFlex := etk.NewFlex()
bottomFlex.AddChild(b3, b4, b5)
flexDemo := etk.NewFlex()
flexDemo.SetVertical(true)
t := etk.NewText("Flex")
flexDemo.AddChild(t, topFlex, bottomFlex)
w.AddChildWithLabel(flexDemo, "Flex")
}
etk.SetRoot(w)
err := ebiten.RunGame(g)
if err != nil {

18
flex.go
View file

@ -2,7 +2,6 @@ package etk
import (
"image"
"log"
"github.com/hajimehoshi/ebiten/v2"
)
@ -11,8 +10,6 @@ type Flex struct {
*Box
vertical bool
lastRect image.Rectangle
}
func NewFlex() *Flex {
@ -26,11 +23,7 @@ func (f *Flex) SetRect(r image.Rectangle) {
defer f.Unlock()
f.Box.rect = r
// TODO
for _, child := range f.children {
child.SetRect(r)
}
f.reposition()
}
func (f *Flex) SetVertical(v bool) {
@ -45,7 +38,7 @@ func (f *Flex) SetVertical(v bool) {
f.reposition()
}
func (f *Flex) HandleMouse() (handled bool, err error) {
func (f *Flex) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
return false, nil
}
@ -57,11 +50,6 @@ func (f *Flex) Draw(screen *ebiten.Image) error {
f.Lock()
defer f.Unlock()
if !f.rect.Eq(f.lastRect) {
f.reposition()
f.lastRect = f.rect
}
for _, child := range f.children {
err := child.Draw(screen)
if err != nil {
@ -85,7 +73,6 @@ func (f *Flex) reposition() {
if i == l-1 {
maxY = r.Max.Y
}
log.Println(i, maxY, image.Rect(r.Min.X, minY, r.Max.X, maxY))
child.SetRect(image.Rect(r.Min.X, minY, r.Max.X, maxY))
minY = maxY
@ -102,7 +89,6 @@ func (f *Flex) reposition() {
if i == l-1 {
maxX = r.Max.X
}
log.Println(i, maxX, image.Rect(minX, r.Min.Y, maxX, r.Max.Y))
child.SetRect(image.Rect(minX, r.Min.Y, maxX, r.Max.Y))
minX = maxX

84
game.go
View file

@ -3,6 +3,9 @@ package etk
import (
"fmt"
"image"
"math"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2"
)
@ -11,6 +14,8 @@ var root Widget
var (
lastWidth, lastHeight int
lastX, lastY = -math.MaxInt, -math.MaxInt
)
func SetRoot(w Widget) {
@ -33,41 +38,59 @@ func Update() error {
panic("no root widget specified")
}
var mouseHandled bool
var keyboardHandled bool
var err error
x, y := ebiten.CursorPosition()
cursor := image.Point{x, y}
children := root.Children()
for _, child := range children {
if !mouseHandled {
mouseHandled, err = child.HandleMouse()
if err != nil {
return fmt.Errorf("failed to handle widget mouse input: %s", err)
}
}
if !keyboardHandled {
keyboardHandled, err = child.HandleKeyboard()
if err != nil {
return fmt.Errorf("failed to handle widget keyboard input: %s", err)
}
}
if mouseHandled && keyboardHandled {
return nil
if lastX == -math.MaxInt && lastY == -math.MaxInt {
lastX, lastY = x, y
}
// TODO handle touch input
var pressed bool
for _, binding := range Bindings.ConfirmMouse {
pressed = ebiten.IsMouseButtonPressed(binding)
if pressed {
break
}
}
if !mouseHandled {
_, err = root.HandleMouse()
var clicked bool
for _, binding := range Bindings.ConfirmMouse {
clicked = inpututil.IsMouseButtonJustReleased(binding)
if clicked {
break
}
}
_, _, err := update(root, cursor, pressed, clicked, false, false)
return err
}
func update(w Widget, cursor image.Point, pressed bool, clicked bool, mouseHandled bool, keyboardHandled bool) (bool, bool, error) {
var err error
children := w.Children()
for _, child := range children {
mouseHandled, keyboardHandled, err = update(child, cursor, pressed, clicked, mouseHandled, keyboardHandled)
if err != nil {
return fmt.Errorf("failed to handle widget mouse input: %s", err)
return false, false, err
} else if mouseHandled && keyboardHandled {
return true, true, nil
}
}
if !mouseHandled && cursor.In(w.Rect()) {
_, err = w.HandleMouse(cursor, pressed, clicked)
if err != nil {
return false, false, fmt.Errorf("failed to handle widget mouse input: %s", err)
}
}
if !keyboardHandled {
_, err = root.HandleKeyboard()
_, err = w.HandleKeyboard()
if err != nil {
return fmt.Errorf("failed to handle widget keyboard input: %s", err)
return false, false, fmt.Errorf("failed to handle widget keyboard input: %s", err)
}
}
return nil
return mouseHandled, keyboardHandled, nil
}
func Draw(screen *ebiten.Image) error {
@ -75,17 +98,22 @@ func Draw(screen *ebiten.Image) error {
panic("no root widget specified")
}
err := root.Draw(screen)
return draw(root, screen)
}
func draw(w Widget, screen *ebiten.Image) error {
err := w.Draw(screen)
if err != nil {
return fmt.Errorf("failed to draw widget: %s", err)
}
children := root.Children()
children := w.Children()
for _, child := range children {
err = child.Draw(screen)
err = draw(child, screen)
if err != nil {
return fmt.Errorf("failed to draw widget: %s", err)
}
}
return nil
}

12
go.mod
View file

@ -3,18 +3,18 @@ module code.rocketnine.space/tslocum/etk
go 1.18
require (
code.rocketnine.space/tslocum/messeji v1.0.0
github.com/hajimehoshi/ebiten/v2 v2.3.3
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd
code.rocketnine.space/tslocum/messeji v1.0.2
github.com/hajimehoshi/ebiten/v2 v2.3.5
golang.org/x/image v0.0.0-20220617043117-41969df76e82
)
require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220516021902-eb3e265c7661 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220622232848-a6c407ee30a0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/jezek/xgb v1.0.1 // indirect
golang.org/x/exp/shiny v0.0.0-20220609121020-a51bd0440498 // indirect
golang.org/x/exp/shiny v0.0.0-20220706164943-b4a6d9510983 // indirect
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 // indirect
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect
golang.org/x/text v0.3.7 // indirect
)

24
go.sum
View file

@ -1,15 +1,15 @@
code.rocketnine.space/tslocum/messeji v1.0.0 h1:GRZ8/ExI/syR3+0UH3cMjnJFhJnGxQOMSMoCf/v7XLM=
code.rocketnine.space/tslocum/messeji v1.0.0/go.mod h1:o3MnboWYp/W/ZsYCzga4t/pyzLfXnf6iK8R3KBJuHIM=
code.rocketnine.space/tslocum/messeji v1.0.2 h1:3/68FnXWaBDMhfUGb8FvNpVgAHY8DX+VL7pyA/CcY94=
code.rocketnine.space/tslocum/messeji v1.0.2/go.mod h1:bSXsyjvKhFXQ7GsUxWZdO2JX83xOT/VTqFCR04thk+c=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220516021902-eb3e265c7661 h1:1bpooddSK2996NWM/1TW59cchQOm9MkoV9DkhSJH1BI=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220516021902-eb3e265c7661/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220622232848-a6c407ee30a0 h1:ZWsNtyC3mgUL48DikCfjkyiaRYZ3OL2XBfn7JJs2/ZE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220622232848-a6c407ee30a0/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/hajimehoshi/bitmapfont/v2 v2.2.0 h1:E6vzlchynZj6OVohVKFqWkKW348EmDW62K5zPXDi7A8=
github.com/hajimehoshi/bitmapfont/v2 v2.2.0/go.mod h1:Llj2wTYXMuCTJEw2ATNIO6HbFPOoBYPs08qLdFAxOsQ=
github.com/hajimehoshi/ebiten/v2 v2.3.3 h1:v72UzprVvWGE+HGcypkLI9Ikd237fqzpio5idPk9KNI=
github.com/hajimehoshi/ebiten/v2 v2.3.3/go.mod h1:vxwpo0q0oSi1cIll0Q3Ui33TVZgeHuFVYzIRk7FwuVk=
github.com/hajimehoshi/ebiten/v2 v2.3.5 h1:GG2XMNu9Yf/CCopxhdIRS1IREvx3gWCZ9RMP3rKkZcc=
github.com/hajimehoshi/ebiten/v2 v2.3.5/go.mod h1:vxwpo0q0oSi1cIll0Q3Ui33TVZgeHuFVYzIRk7FwuVk=
github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
@ -34,14 +34,14 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp/shiny v0.0.0-20220609121020-a51bd0440498 h1:mJjyic/dxHcz1W6IUE8zf6+RltuO8+9mS45tTtb4F6k=
golang.org/x/exp/shiny v0.0.0-20220609121020-a51bd0440498/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/exp/shiny v0.0.0-20220706164943-b4a6d9510983 h1:z34Buq9ijQFAoTegl58EYWYLBAzEDT0BTzglEJ+AmEo=
golang.org/x/exp/shiny v0.0.0-20220706164943-b4a6d9510983/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd h1:x1GptNaTtxPAlTVIAJk61fuXg0y17h09DTxyb+VNC/k=
@ -70,8 +70,8 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 h1:z8Hj/bl9cOV2grsOpEaQFUaly0JWN3i97mo3jXKJNp0=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

63
input.go Normal file
View file

@ -0,0 +1,63 @@
package etk
import (
"image"
"github.com/hajimehoshi/ebiten/v2"
"code.rocketnine.space/tslocum/messeji"
)
type Input struct {
*Box
field *messeji.InputField
}
func NewInput(prefix string, text string, onSelected func(text string) (handled bool)) *Input {
textColor := Style.TextColorDark
/*if TextColor == nil {
textColor = Style.InputColor
}*/
i := messeji.NewInputField(Style.TextFont)
i.SetPrefix(prefix)
i.SetText(text)
i.SetForegroundColor(textColor)
i.SetBackgroundColor(Style.InputBgColor)
i.SetHandleKeyboard(true)
i.SetSelectedFunc(func() (accept bool) {
return onSelected(i.Text())
})
return &Input{
Box: NewBox(),
field: i,
}
}
// Write writes to the field's buffer.
func (i *Input) Write(p []byte) (n int, err error) {
return i.field.Write(p)
}
func (i *Input) SetRect(r image.Rectangle) {
i.Box.rect = r
i.field.SetRect(r)
}
func (i *Input) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
return false, nil
}
func (i *Input) HandleKeyboard() (handled bool, err error) {
err = i.field.Update()
return false, err
}
func (i *Input) Draw(screen *ebiten.Image) error {
// Draw label.
i.field.Draw(screen)
return nil
}

15
keybind.go Normal file
View file

@ -0,0 +1,15 @@
package etk
import "github.com/hajimehoshi/ebiten/v2"
type Shortcuts struct {
ConfirmKeyboard []ebiten.Key
ConfirmMouse []ebiten.MouseButton
ConfirmGamepad []ebiten.GamepadButton
}
var Bindings = &Shortcuts{
ConfirmKeyboard: []ebiten.Key{ebiten.KeyEnter, ebiten.KeyKPEnter},
ConfirmMouse: []ebiten.MouseButton{ebiten.MouseButtonLeft},
ConfirmGamepad: []ebiten.GamepadButton{ebiten.GamepadButton0},
}

View file

@ -1,22 +1,62 @@
package etk
import "image/color"
import (
"image/color"
"log"
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
)
var transparent = color.RGBA{0, 0, 0, 0}
func defaultFont() font.Face {
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
if err != nil {
log.Fatal(err)
}
const dpi = 72
defaultFont, err := opentype.NewFace(tt, &opentype.FaceOptions{
Size: 32,
DPI: dpi,
Hinting: font.HintingFull,
})
if err != nil {
log.Fatal(err)
}
return defaultFont
}
type Attributes struct {
TextColor color.Color
TextFont font.Face
TextColorLight color.Color
TextColorDark color.Color
TextBgColor color.Color
BorderColor color.Color
InputBgColor color.Color
ButtonTextColor color.Color
ButtonBgColor color.Color
ButtonBgColorDisabled color.Color
}
var Style = &Attributes{
TextColor: color.RGBA{0, 0, 0, 255},
TextFont: defaultFont(),
TextColorLight: color.RGBA{255, 255, 255, 255},
TextColorDark: color.RGBA{0, 0, 0, 255},
TextBgColor: transparent,
BorderColor: color.RGBA{0, 0, 0, 255},
InputBgColor: color.RGBA{0, 128, 0, 255},
ButtonBgColor: color.RGBA{255, 255, 255, 255},
ButtonBgColorDisabled: color.RGBA{110, 110, 110, 255},
}

55
text.go Normal file
View file

@ -0,0 +1,55 @@
package etk
import (
"image"
"github.com/hajimehoshi/ebiten/v2"
"code.rocketnine.space/tslocum/messeji"
)
type Text struct {
*Box
field *messeji.TextField
}
func NewText(text string) *Text {
textColor := Style.TextColorLight
l := messeji.NewTextField(Style.TextFont)
l.SetText(text)
l.SetForegroundColor(textColor)
l.SetBackgroundColor(Style.TextBgColor)
l.SetHorizontal(messeji.AlignCenter)
l.SetVertical(messeji.AlignCenter)
return &Text{
Box: NewBox(),
field: l,
}
}
// Write writes to the field's buffer.
func (t *Text) Write(p []byte) (n int, err error) {
return t.field.Write(p)
}
func (t *Text) SetRect(r image.Rectangle) {
t.Box.rect = r
t.field.SetRect(r)
}
func (t *Text) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
return false, nil
}
func (t *Text) HandleKeyboard() (handled bool, err error) {
return false, nil
}
func (t *Text) Draw(screen *ebiten.Image) error {
// Draw label.
t.field.Draw(screen)
return nil
}

View file

@ -9,7 +9,7 @@ import (
type Widget interface {
Rect() image.Rectangle
SetRect(r image.Rectangle)
HandleMouse() (handled bool, err error)
HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error)
HandleKeyboard() (handled bool, err error)
Draw(screen *ebiten.Image) error
Children() []Widget

84
window.go Normal file
View file

@ -0,0 +1,84 @@
package etk
import (
"image"
"github.com/hajimehoshi/ebiten/v2"
)
// Window displays and passes input to only one child widget at a time.
type Window struct {
*Box
allChildren []Widget
active int
labels []string
hasLabel bool
}
func NewWindow() *Window {
return &Window{
Box: NewBox(),
}
}
func (w *Window) childrenUpdated() {
if len(w.allChildren) == 0 {
w.children = nil
return
}
w.children = []Widget{w.allChildren[w.active]}
}
func (w *Window) SetRect(r image.Rectangle) {
w.Lock()
defer w.Unlock()
w.rect = r
for _, wgt := range w.children {
wgt.SetRect(r)
}
}
func (w *Window) AddChild(wgt ...Widget) {
w.allChildren = append(w.allChildren, wgt...)
for _, widget := range wgt {
widget.SetRect(w.rect)
}
blankLabels := make([]string, len(wgt))
w.labels = append(w.labels, blankLabels...)
w.childrenUpdated()
}
func (w *Window) AddChildWithLabel(wgt Widget, label string) {
w.Lock()
defer w.Unlock()
wgt.SetRect(w.rect)
w.allChildren = append(w.allChildren, wgt)
w.labels = append(w.labels, label)
if label != "" {
w.hasLabel = true
}
w.childrenUpdated()
}
func (w *Window) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
return true, nil
}
func (w *Window) HandleKeyboard() (handled bool, err error) {
return true, nil
}
func (w *Window) Draw(screen *ebiten.Image) error {
// TODO draw labels
return nil
}