Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@ jobs:
with:
go-version: '1.25'

- name: Test
run: make test ARGS="-v"
- name: Lint
run: make lint
# The Test cannot run on the CI so no bother
#- name: Test
#run: make test ARGS="-v"
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ help: ## Show this help
@grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v grep -F | sed -e 's/:.*##/:##/' | column -t -s '##'

.PHONY: test
test:
test: ## Runs the test
@xvfb-run go test ./... $(ARGS)

.PHONY: pprof
pprof:
pprof: ## Runs pprof server for 'cpu.out'
@go tool pprof --http=:8081 cpu.out

.PHONY: lint
lint: ## Runs the linter
@go tool staticcheck ./...
38 changes: 29 additions & 9 deletions ebitest.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import (
)

const (
baseDumpFoler = "_ebitest_dump/"
baseDumpFoler = "_ebitest_dump/"
findAllSelectors = true
)

type Ebitest struct {
Expand Down Expand Up @@ -187,7 +188,12 @@ func (e *Ebitest) MustNot(t *testing.T, s interface{}) {
msg += "\nimage at: " + p
}
require.Fail(t, msg)
return
}

// GetAll returns all the repeated instances of s or none if nothing is found
func (e *Ebitest) GetAll(s interface{}) []*Selector {
sc := e.game.GetScreen()
return e.findSelectors(sc, s, findAllSelectors)
}

// KeyTap taps all the keys at once
Expand All @@ -203,37 +209,51 @@ func (e *Ebitest) getSelector(s interface{}) *Selector {
switch v := s.(type) {
case string:
return NewFromText(v, e.options.face, e.options.color)
case image.Image:
return NewFromImage(v)
case *ebiten.Image:
return NewFromImage(ebitenImageToImage(v))
case image.Image:
return NewFromImage(v)
case *Selector:
return NewFromImage(v.Image())
default:
panic(fmt.Sprintf("Invalid Selector of type %T, the supported ones are: 'string', 'image.Image', '*ebiten.Image' and '*ebitest.Selector'", s))
}
}

// findSelector returns a Selector from ss if found
// findSelector returns a Selector from ss if found. `all` will basically mean it'll return all of them
func (e *Ebitest) findSelector(sc image.Image, ss interface{}) (*Selector, bool) {
sel := e.getSelector(ss)
sels := e.findSelectors(sc, ss, !findAllSelectors)
if len(sels) == 0 {
return nil, false
}
return sels[0], true
}

// findSelector returns a Selector from ss if found. `all` will basically mean it'll return all of them
func (e *Ebitest) findSelectors(sc image.Image, ss interface{}, all bool) []*Selector {
selectors := make([]*Selector, 0)
bsel := e.getSelector(ss)

sx := sc.Bounds().Dx()
sy := sc.Bounds().Dy()
for x := range sx {
for y := range sy {
if hasImageAt(sc, sel.Image(), x, y) {
if hasImageAt(sc, bsel.Image(), x, y) {
sel := NewFromImage(bsel.Image())
selx := sel.Image().Bounds().Dx()
sely := sel.Image().Bounds().Dy()

sel.rect = image.Rect(x, y, x+selx, y+sely)
sel.PingPong = e.PingPong
return sel, true
selectors = append(selectors, sel)
if !all {
return selectors
}
}
}
}

return sel, false
return selectors
}

// dumpErrorImages dumps a composition of the 2 images into 1 so it displays
Expand Down
10 changes: 9 additions & 1 deletion ebitest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@ func TestGameButton(t *testing.T) {
et.PingPong.Ping()

text1 := "Click Me"
text1_2 := "Click Me 2"
text2 := "Clicked Me"

t1s, _ := et.Should(t, text1)
t1_2s, _ := et.Should(t, text1_2)
et.ShouldNot(t, text2)

t1s.Click()

et.ShouldNot(t, text1)
et.Should(t, text1_2)
et.Should(t, text2)

t1_2s.Click()

et.ShouldNot(t, text1)
et.ShouldNot(t, text1_2)
assert.Len(t, et.GetAll(text2), 2)

//et.KeyTap(ebiten.KeyShift, ebiten.KeyI)
et.KeyTap(ebiten.KeyI, ebiten.KeyShift)
assert.True(t, g.ClickedShiftI)
Expand Down
2 changes: 0 additions & 2 deletions game.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,4 @@ func (g *Game) Draw(screen *ebiten.Image) {

g.clickTTT.Toe()
g.keyTapTTT.Toe()

return
}
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
)

require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d // indirect
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect
Expand Down Expand Up @@ -43,8 +44,14 @@ require (
github.com/vcaesar/tt v0.20.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/tools v0.39.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.6.1 // indirect
)

tool honnef.co/go/tools/cmd/staticcheck
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -95,8 +97,12 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -106,8 +112,12 @@ golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
4 changes: 2 additions & 2 deletions ping_pong.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (pp *PingPong) KeyTapPong(ttt *TicTacToe, g *Game) {
b := <-pp.keyTapPing
keys := b.KeyTap.Keys
key := ebitenToRobotgoKeys[keys[0]]
args := make([]interface{}, 0, 0)
args := make([]interface{}, 0)
for _, k := range keys[1:] {
args = append(args, ebitenToRobotgoKeys[k])
}
Expand All @@ -98,7 +98,7 @@ func (pp *PingPong) KeyTapPong(ttt *TicTacToe, g *Game) {
ttt.Tic()
go func() {
<-ttt.toe
g.keyTapKeys = make([]ebiten.Key, 0, 0)
g.keyTapKeys = make([]ebiten.Key, 0)
pp.keyTapPong <- struct{}{}
}()
}
Expand Down
75 changes: 40 additions & 35 deletions testdata/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,87 +15,95 @@ import (
"golang.org/x/image/font/gofont/goregular"
)

// Game object used by ebiten.
type Game struct {
ui *ebitenui.UI
btn *widget.Button
ui *ebitenui.UI

Clicked bool
ClickedShiftI bool
}

func NewGame() *Game {
// load images for button states: idle, hover, and pressed.
buttonImage, _ := loadButtonImage()

// load button text font.
face, _ := LoadFont(20)

// construct a new container that serves as the root of the UI hierarchy.
rootContainer := widget.NewContainer(
// the container will use a plain color as its background.
widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(color.NRGBA{0x13, 0x1a, 0x22, 0xff})),

// the container will use an anchor layout to layout its single child widget.
widget.ContainerOpts.Layout(widget.NewAnchorLayout()),
widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(color.NRGBA{0x13, 0x1a, 0x22, 0xff})),
)

text1 := "Click Me"
text2 := "Clicked Me"
var button *widget.Button
// construct a button.
button = widget.NewButton(
// set general widget options
widget.ButtonOpts.WidgetOpts(
// instruct the container's anchor layout to center the button both horizontally and vertically.
btnRL := widget.NewContainer(
widget.ContainerOpts.Layout(widget.NewRowLayout(
widget.RowLayoutOpts.Direction(widget.DirectionVertical),
widget.RowLayoutOpts.Spacing(20),
)),
widget.ContainerOpts.WidgetOpts(
widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{
HorizontalPosition: widget.AnchorLayoutPositionCenter,
VerticalPosition: widget.AnchorLayoutPositionCenter,
}),
),
// specify the images to use.
widget.ButtonOpts.Image(buttonImage),
)

// specify the button's text, the font face, and the color.
text1 := "Click Me"
text1_2 := "Click Me 2"

text2 := "Clicked Me"
var btn1, btn2 *widget.Button
btn1 = widget.NewButton(
widget.ButtonOpts.Image(buttonImage),
widget.ButtonOpts.Text(text1, &face, &widget.ButtonTextColor{
Idle: color.White,
}),

widget.ButtonOpts.TextPadding(&widget.Insets{
Left: 30,
Right: 30,
Top: 5,
Bottom: 5,
}),

// add a handler that reacts to clicking the button.
widget.ButtonOpts.ClickedHandler(func(args *widget.ButtonClickedEventArgs) {
button.Text().Label = text2
btn1.Text().Label = text2
}),
)
btn2 = widget.NewButton(
widget.ButtonOpts.Image(buttonImage),
widget.ButtonOpts.Text(text1_2, &face, &widget.ButtonTextColor{
Idle: color.White,
}),
widget.ButtonOpts.TextPadding(&widget.Insets{
Left: 30,
Right: 30,
Top: 5,
Bottom: 5,
}),
widget.ButtonOpts.ClickedHandler(func(args *widget.ButtonClickedEventArgs) {
btn2.Text().Label = text2
}),
)

// add the button as a child of the container.
rootContainer.AddChild(button)
btnRL.AddChild(
btn1,
btn2,
)
rootContainer.AddChild(
btnRL,
)

// construct the UI.
ui := ebitenui.UI{
Container: rootContainer,
}

game := Game{
ui: &ui,
btn: button,
ui: &ui,
}

return &game
}

// Layout implements Game.
func (g *Game) Layout(outsideWidth int, outsideHeight int) (int, int) {
return outsideWidth, outsideHeight
}

// Update implements Game.
func (g *Game) Update() error {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
g.Clicked = true
Expand All @@ -105,15 +113,12 @@ func (g *Game) Update() error {
g.ClickedShiftI = true
}

// update the UI
g.ui.Update()

return nil
}

// Draw implements Ebiten's Draw method.
func (g *Game) Draw(screen *ebiten.Image) {
// draw the UI onto the screen
g.ui.Draw(screen)
}

Expand Down
2 changes: 1 addition & 1 deletion utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func ebitenImageToImage(ei *ebiten.Image) image.Image {
b := ei.Bounds()
img := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy()))

ix, iy := ei.Size()
ix, iy := ei.Bounds().Dx(), ei.Bounds().Dy()
for x := range ix {
for y := range iy {
sc := ei.At(x, y)
Expand Down