From 76e3b7ff3cee23120aa66985ea5dcacb03da3e27 Mon Sep 17 00:00:00 2001 From: xescugc Date: Tue, 16 Dec 2025 14:50:40 +0100 Subject: [PATCH] ebitest: Added 'KeyTap' in order to send keyboard inputs --- ebitest.go | 8 +++ ebitest_test.go | 5 ++ game.go | 36 ++++++++++--- key.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++ ping_pong.go | 46 ++++++++++++++-- selector.go | 15 +++--- testdata/game.go | 7 ++- tic_tac_toe.go | 19 ++++++- utils.go | 1 + 9 files changed, 249 insertions(+), 21 deletions(-) create mode 100644 key.go diff --git a/ebitest.go b/ebitest.go index 7f37e81..be97bb8 100644 --- a/ebitest.go +++ b/ebitest.go @@ -190,6 +190,14 @@ func (e *Ebitest) MustNot(t *testing.T, s interface{}) { return } +// KeyTap taps all the keys at once +func (e *Ebitest) KeyTap(keys ...ebiten.Key) { + if len(keys) == 0 { + return + } + e.PingPong.KeyTapPing(Ball{KeyTap: BallKeyTap{Keys: keys}}) +} + // getSelector converts s to the right Selector initialization func (e *Ebitest) getSelector(s interface{}) *Selector { switch v := s.(type) { diff --git a/ebitest_test.go b/ebitest_test.go index ba910ba..8dbbe35 100644 --- a/ebitest_test.go +++ b/ebitest_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/go-vgo/robotgo" + "github.com/hajimehoshi/ebiten/v2" "github.com/stretchr/testify/assert" "github.com/xescugc/ebitest" "github.com/xescugc/ebitest/testdata" @@ -37,4 +38,8 @@ func TestGameButton(t *testing.T) { et.ShouldNot(t, text1) et.Should(t, text2) + + //et.KeyTap(ebiten.KeyShift, ebiten.KeyI) + et.KeyTap(ebiten.KeyI, ebiten.KeyShift) + assert.True(t, g.ClickedShiftI) } diff --git a/game.go b/game.go index 83cdf0a..f32e3c6 100644 --- a/game.go +++ b/game.go @@ -18,15 +18,19 @@ type Game struct { pingPong *PingPong - clickTTT *TicTacToe + keyTapKeys []ebiten.Key + + clickTTT *TicTacToe + keyTapTTT *TicTacToe } func newGame(ctx context.Context, g ebiten.Game, pp *PingPong) *Game { return &Game{ - game: g, - ctx: ctx, - pingPong: pp, - clickTTT: NewTicTacToe(), + game: g, + ctx: ctx, + pingPong: pp, + clickTTT: NewTicTacToe(), + keyTapTTT: NewTicTacToe(), } } @@ -55,9 +59,24 @@ func (g *Game) Update() error { return ebiten.Termination default: } - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { - g.clickTTT.Tac() + // Check for click + if g.clickTTT.HasTic() { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + g.clickTTT.Tac() + } + } + + // Check for Pressed + if g.keyTapTTT.HasTic() { + pressed := true + for _, k := range g.keyTapKeys { + pressed = pressed && ebiten.IsKeyPressed(k) + } + if pressed { + g.keyTapTTT.Tac() + } } + return g.game.Update() } @@ -69,7 +88,10 @@ func (g *Game) Draw(screen *ebiten.Image) { g.pingPong.Pong() g.pingPong.ClickPong(g.clickTTT) + g.pingPong.KeyTapPong(g.keyTapTTT, g) + g.clickTTT.Toe() + g.keyTapTTT.Toe() return } diff --git a/key.go b/key.go new file mode 100644 index 0000000..55debf5 --- /dev/null +++ b/key.go @@ -0,0 +1,133 @@ +package ebitest + +import "github.com/hajimehoshi/ebiten/v2" + +var ( + // ebitenToRobotgoKeys is a list of keys from Ebiten mapped + // to the 'robotgo' keys + ebitenToRobotgoKeys = map[ebiten.Key]string{ + ebiten.KeyA: "a", + ebiten.KeyB: "b", + ebiten.KeyC: "c", + ebiten.KeyD: "d", + ebiten.KeyE: "e", + ebiten.KeyF: "f", + ebiten.KeyG: "g", + ebiten.KeyH: "h", + ebiten.KeyI: "i", + ebiten.KeyJ: "j", + ebiten.KeyK: "k", + ebiten.KeyL: "l", + ebiten.KeyM: "m", + ebiten.KeyN: "n", + ebiten.KeyO: "o", + ebiten.KeyP: "p", + ebiten.KeyQ: "q", + ebiten.KeyR: "r", + ebiten.KeyS: "s", + ebiten.KeyT: "t", + ebiten.KeyU: "u", + ebiten.KeyV: "v", + ebiten.KeyW: "w", + ebiten.KeyX: "x", + ebiten.KeyY: "y", + ebiten.KeyZ: "z", + ebiten.KeyAltLeft: "lalt", + ebiten.KeyAltRight: "ralt", + ebiten.KeyArrowDown: "down", + ebiten.KeyArrowLeft: "left", + ebiten.KeyArrowRight: "right", + ebiten.KeyArrowUp: "up", + //ebiten.KeyBackquote + //ebiten.KeyBackslash + ebiten.KeyBackspace: "backspace", + //ebiten.KeyBracketLeft + //ebiten.KeyBracketRight + ebiten.KeyCapsLock: "capslock", + //ebiten.KeyComma + //ebiten.KeyContextMenu + ebiten.KeyControlLeft: "lctrl", + ebiten.KeyControlRight: "rctrl", + ebiten.KeyDelete: "delete", + ebiten.KeyDigit0: "0", + ebiten.KeyDigit1: "1", + ebiten.KeyDigit2: "2", + ebiten.KeyDigit3: "3", + ebiten.KeyDigit4: "4", + ebiten.KeyDigit5: "5", + ebiten.KeyDigit6: "6", + ebiten.KeyDigit7: "7", + ebiten.KeyDigit8: "8", + ebiten.KeyDigit9: "9", + ebiten.KeyEnd: "end", + ebiten.KeyEnter: "enter", + //ebiten.KeyEqual + ebiten.KeyEscape: "escape", + ebiten.KeyF1: "f1", + ebiten.KeyF2: "f2", + ebiten.KeyF3: "f3", + ebiten.KeyF4: "f4", + ebiten.KeyF5: "f5", + ebiten.KeyF6: "f6", + ebiten.KeyF7: "f7", + ebiten.KeyF8: "f8", + ebiten.KeyF9: "f9", + ebiten.KeyF10: "f10", + ebiten.KeyF11: "f11", + ebiten.KeyF12: "f12", + ebiten.KeyF13: "f13", + ebiten.KeyF14: "f14", + ebiten.KeyF15: "f15", + ebiten.KeyF16: "f16", + ebiten.KeyF17: "f17", + ebiten.KeyF18: "f18", + ebiten.KeyF19: "f19", + ebiten.KeyF20: "f20", + ebiten.KeyF21: "f21", + ebiten.KeyF22: "f22", + ebiten.KeyF23: "f23", + ebiten.KeyF24: "f24", + ebiten.KeyHome: "home", + ebiten.KeyInsert: "insert", + //ebiten.KeyIntlBackslash + ebiten.KeyMetaLeft: "lmeta", + ebiten.KeyMetaRight: "rmeta", + //ebiten.KeyMinus + ebiten.KeyNumLock: "num_lock", + ebiten.KeyNumpad0: "num0", + ebiten.KeyNumpad1: "num1", + ebiten.KeyNumpad2: "num2", + ebiten.KeyNumpad3: "num3", + ebiten.KeyNumpad4: "num4", + ebiten.KeyNumpad5: "num5", + ebiten.KeyNumpad6: "num6", + ebiten.KeyNumpad7: "num7", + ebiten.KeyNumpad8: "num8", + ebiten.KeyNumpad9: "num9", + ebiten.KeyNumpadAdd: "num+", + ebiten.KeyNumpadDecimal: "num.", + ebiten.KeyNumpadDivide: "num/", + ebiten.KeyNumpadEnter: "num_enter", + ebiten.KeyNumpadEqual: "num_equal", + ebiten.KeyNumpadMultiply: "num*", + ebiten.KeyNumpadSubtract: "num-", + ebiten.KeyPageDown: "pagedown", + ebiten.KeyPageUp: "pageup", + //ebiten.KeyPause + //ebiten.KeyPeriod + ebiten.KeyPrintScreen: "printscreen", + //ebiten.KeyQuote + //ebiten.KeyScrollLock + //ebiten.KeySemicolon + ebiten.KeyShiftLeft: "lshift", + ebiten.KeyShiftRight: "rshift", + //ebiten.KeySlash + ebiten.KeySpace: "space", + ebiten.KeyTab: "tab", + ebiten.KeyAlt: "alt", + ebiten.KeyControl: "control", + ebiten.KeyShift: "shift", + ebiten.KeyMeta: "cmd", + //ebiten.KeyMax + } +) diff --git a/ping_pong.go b/ping_pong.go index 1c024aa..32b40ad 100644 --- a/ping_pong.go +++ b/ping_pong.go @@ -2,6 +2,7 @@ package ebitest import ( "github.com/go-vgo/robotgo" + "github.com/hajimehoshi/ebiten/v2" ) // PingPong used to switch contexts between goroutines @@ -11,10 +12,18 @@ type PingPong struct { clickPing chan Ball clickPong chan struct{} + + keyTapPing chan Ball + keyTapPong chan struct{} } type Ball struct { - X, Y int + X, Y int + KeyTap BallKeyTap +} + +type BallKeyTap struct { + Keys []ebiten.Key } // NewPingPong initialines a new PingPong @@ -25,6 +34,9 @@ func NewPingPong() *PingPong { clickPing: make(chan Ball, 1), clickPong: make(chan struct{}, 1), + + keyTapPing: make(chan Ball, 1), + keyTapPong: make(chan struct{}, 1), } } @@ -43,13 +55,13 @@ func (pp *PingPong) Pong() { } } -// Ping sends a ping and waits for a Pong +// ClickPing sends a ping and waits for a Pong func (pp *PingPong) ClickPing(b Ball) { pp.clickPing <- b <-pp.clickPong } -// Pong will read on ping if any present and then +// ClickPong will read on ping if any present and then // will send to pong func (pp *PingPong) ClickPong(ttt *TicTacToe) { if len(pp.clickPing) != 0 { @@ -63,3 +75,31 @@ func (pp *PingPong) ClickPong(ttt *TicTacToe) { }() } } + +// Ping sends a ping and waits for a Pong +func (pp *PingPong) KeyTapPing(b Ball) { + pp.keyTapPing <- b + <-pp.keyTapPong +} + +// Pong will read on ping if any present and then +// will send to pong +func (pp *PingPong) KeyTapPong(ttt *TicTacToe, g *Game) { + if len(pp.keyTapPing) != 0 { + b := <-pp.keyTapPing + keys := b.KeyTap.Keys + key := ebitenToRobotgoKeys[keys[0]] + args := make([]interface{}, 0, 0) + for _, k := range keys[1:] { + args = append(args, ebitenToRobotgoKeys[k]) + } + g.keyTapKeys = keys + robotgo.KeyTap(key, args...) + ttt.Tic() + go func() { + <-ttt.toe + g.keyTapKeys = make([]ebiten.Key, 0, 0) + pp.keyTapPong <- struct{}{} + }() + } +} diff --git a/selector.go b/selector.go index 900d39e..7554bd7 100644 --- a/selector.go +++ b/selector.go @@ -8,6 +8,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/text/v2" ) +// Selector represents the thing you are searching for type Selector struct { img image.Image rect image.Rectangle @@ -15,6 +16,7 @@ type Selector struct { PingPong *PingPong } +// NewFromText crates a new Selector from a txt func NewFromText(txt string, f text.Face, c color.Color) *Selector { top := &text.DrawOptions{} top.ColorScale.ScaleWithColor(c) @@ -30,33 +32,30 @@ func NewFromText(txt string, f text.Face, c color.Color) *Selector { return NewFromImage(ebitenImageToImage(img)) } +// NewFromImage creates a new Selector from an image func NewFromImage(i image.Image) *Selector { return &Selector{ img: i, } } +// Click will click on the center of the Selectore func (s *Selector) Click() { cx, cy := s.center() - //robotgo.Move(cx, cy) - //robotgo.Click("left") s.PingPong.ClickPing(Ball{X: cx, Y: cy}) } +// center returns the center of the selector func (s *Selector) center() (int, int) { return s.rect.Min.X + (s.rect.Dx() / 2), s.rect.Min.Y + (s.rect.Dy() / 2) } +// Rec returns the image.Rectangle of the image func (s *Selector) Rec() image.Rectangle { return s.rect } +// Image returns the underlying image func (s *Selector) Image() image.Image { return s.img } - -func (s *Selector) Type(string) { -} - -func (s *Selector) KeyTap(ks ...string) { -} diff --git a/testdata/game.go b/testdata/game.go index 7f440ac..f2047cd 100644 --- a/testdata/game.go +++ b/testdata/game.go @@ -20,7 +20,8 @@ type Game struct { ui *ebitenui.UI btn *widget.Button - Clicked bool + Clicked bool + ClickedShiftI bool } func NewGame() *Game { @@ -100,6 +101,10 @@ func (g *Game) Update() error { g.Clicked = true } + if ebiten.IsKeyPressed(ebiten.KeyShift) && ebiten.IsKeyPressed(ebiten.KeyI) { + g.ClickedShiftI = true + } + // update the UI g.ui.Update() diff --git a/tic_tac_toe.go b/tic_tac_toe.go index e5870b7..d5e000d 100644 --- a/tic_tac_toe.go +++ b/tic_tac_toe.go @@ -21,18 +21,33 @@ func (ttt *TicTacToe) Tic() { ttt.tic <- struct{}{} } +// HasTic checks if Tic has content +func (ttt *TicTacToe) HasTic() bool { + return len(ttt.tic) != 0 +} + // Tac second func (ttt *TicTacToe) Tac() { - if len(ttt.tic) != 0 { + if ttt.HasTic() { <-ttt.tic ttt.tac <- struct{}{} } } +// HasTac checks if Tac has content +func (ttt *TicTacToe) HasTac() bool { + return len(ttt.tac) != 0 +} + // Toe third func (ttt *TicTacToe) Toe() { - if len(ttt.tac) != 0 { + if ttt.HasTac() { <-ttt.tac ttt.toe <- struct{}{} } } + +// HasToe checks if Toe has content +func (ttt *TicTacToe) HasToe() bool { + return len(ttt.toe) != 0 +} diff --git a/utils.go b/utils.go index 1d0ea7f..6b7b4c4 100644 --- a/utils.go +++ b/utils.go @@ -6,6 +6,7 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) +// ebitenImageToImage converts an ebiten.Image to an image.Image func ebitenImageToImage(ei *ebiten.Image) image.Image { b := ei.Bounds() img := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy()))