|
@@ -0,0 +1,340 @@
|
|
|
+// package ine, named after St. Ine of Wessex is an input to event
|
|
|
+// converter for ebitengine.
|
|
|
+// This package adapts Ebitengine's input to be event based in stead
|
|
|
+// of state based.
|
|
|
+// We reuse image.Point for coordinate values.
|
|
|
+package ine
|
|
|
+
|
|
|
+import "image"
|
|
|
+
|
|
|
+import "github.com/hajimehoshi/ebiten/v2"
|
|
|
+import "github.com/hajimehoshi/ebiten/v2/inpututil"
|
|
|
+import "github.com/hajimehoshi/ebiten/v2/exp/textinput"
|
|
|
+
|
|
|
+// Point is image.Point reused
|
|
|
+type Point = image.Point
|
|
|
+
|
|
|
+// Kind is the kind of an event or event reply.
|
|
|
+type Kind int
|
|
|
+
|
|
|
+const (
|
|
|
+ None Kind = iota // No event.
|
|
|
+ PadAttach // Attach: gamepad attached.
|
|
|
+ PadDetach // Detach: gamepad detached.
|
|
|
+ PadPress // Press; gamepad button pressed.
|
|
|
+ PadHold // Hold; gamepad button held.
|
|
|
+ PadRelease // Release: gamepad button released.
|
|
|
+ PadMove // Move gamepad axes moved.
|
|
|
+ KeyPress // Tap: keyboard key tapped
|
|
|
+ KeyHold // Push: keyboard key long push
|
|
|
+ KeyRelease // Type: keyboard key released
|
|
|
+ TouchPress // Touch: touch start
|
|
|
+ TouchHold // Rub: touch continued
|
|
|
+ TouchRelease // Retract: touch released
|
|
|
+ MousePress // Click: mouse button down.
|
|
|
+ MouseHold // Drag: mouse moved with button down.
|
|
|
+ MouseRelease // Loosen: mouse button up.
|
|
|
+ MouseMove // Move: mouse moved with or without button down.
|
|
|
+ MouseWheel // Wheel: mouse wheel events.
|
|
|
+ InputText // Text: IME text input event.
|
|
|
+ ExtraCustom // Custom is for user events. Set the event.Custom field if needed.
|
|
|
+ LastEvent // Last event index.
|
|
|
+)
|
|
|
+
|
|
|
+type CustomEvent interface {
|
|
|
+ String() string
|
|
|
+ Data() any
|
|
|
+}
|
|
|
+
|
|
|
+func (k Kind) Is(other Kind) bool {
|
|
|
+ return k == other
|
|
|
+}
|
|
|
+
|
|
|
+// String is the string repesentation of the event kind.
|
|
|
+func (k Kind) String() string {
|
|
|
+ switch k {
|
|
|
+ case PadAttach:
|
|
|
+ return "PadAttach"
|
|
|
+ case PadDetach:
|
|
|
+ return "PadDetach"
|
|
|
+ case PadPress:
|
|
|
+ return "PadPress"
|
|
|
+ case PadHold:
|
|
|
+ return "PadHold"
|
|
|
+ case PadRelease:
|
|
|
+ return "PadRelease"
|
|
|
+ case PadMove:
|
|
|
+ return "PadMove"
|
|
|
+ case KeyPress:
|
|
|
+ return "KeyPress"
|
|
|
+ case KeyHold:
|
|
|
+ return "KeyHold"
|
|
|
+ case KeyRelease:
|
|
|
+ return "KeyRelease"
|
|
|
+ case TouchPress:
|
|
|
+ return "TouchPress"
|
|
|
+ case TouchHold:
|
|
|
+ return "TouchHold"
|
|
|
+ case TouchRelease:
|
|
|
+ return "TouchRelease"
|
|
|
+ case MousePress:
|
|
|
+ return "MousePress"
|
|
|
+ case MouseHold:
|
|
|
+ return "MouseHold"
|
|
|
+ case MouseRelease:
|
|
|
+ return "MouseRelease"
|
|
|
+ case MouseMove:
|
|
|
+ return "MouseMove"
|
|
|
+ case MouseWheel:
|
|
|
+ return "MouseWheel"
|
|
|
+ case InputText:
|
|
|
+ return "InputText"
|
|
|
+ case ExtraCustom:
|
|
|
+ return "ExtraCustom"
|
|
|
+ default:
|
|
|
+ return "Unknown"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type Button = ebiten.GamepadButton
|
|
|
+type Key = ebiten.Key
|
|
|
+
|
|
|
+type MouseEvent struct {
|
|
|
+ Point
|
|
|
+ Delta Point
|
|
|
+ Wheel Point
|
|
|
+ Duration int
|
|
|
+}
|
|
|
+
|
|
|
+type TouchEvent struct {
|
|
|
+ Point
|
|
|
+ ID int
|
|
|
+ Delta Point
|
|
|
+ Wheel Point
|
|
|
+ Duration int
|
|
|
+}
|
|
|
+
|
|
|
+type PadEvent struct {
|
|
|
+ ID int
|
|
|
+ Axes []float64
|
|
|
+ Button Button
|
|
|
+ Duration int
|
|
|
+}
|
|
|
+
|
|
|
+type KeyEvent struct {
|
|
|
+ Key Key
|
|
|
+ Duration int
|
|
|
+}
|
|
|
+
|
|
|
+type InputEvent struct {
|
|
|
+ ID int
|
|
|
+ Text string
|
|
|
+}
|
|
|
+
|
|
|
+// Event contains the kind and data for an event.
|
|
|
+// Not all fields will be filled in depending on the Kind
|
|
|
+type Event struct {
|
|
|
+ // Kind is the event kind of the event
|
|
|
+ Kind
|
|
|
+
|
|
|
+ // Key is used for key events
|
|
|
+ Key KeyEvent
|
|
|
+
|
|
|
+ // Pad is used for gamepad events
|
|
|
+ Pad PadEvent
|
|
|
+
|
|
|
+ // Touch is used for touch events
|
|
|
+ Touch TouchEvent
|
|
|
+
|
|
|
+ // Mouse is used for mouse events
|
|
|
+ Mouse MouseEvent
|
|
|
+
|
|
|
+ // Input is for input events.
|
|
|
+ Input InputEvent
|
|
|
+
|
|
|
+ // Custom is used for custom events
|
|
|
+ Custom CustomEvent
|
|
|
+}
|
|
|
+
|
|
|
+// Field is an input field for IME text entry.
|
|
|
+type Field struct {
|
|
|
+ textinput.Field
|
|
|
+ Point
|
|
|
+}
|
|
|
+
|
|
|
+type State struct {
|
|
|
+ gamepads []ebiten.GamepadID
|
|
|
+ connected []ebiten.GamepadID
|
|
|
+ cx, cy int
|
|
|
+ // Text input fields in use
|
|
|
+ Fields []*Field
|
|
|
+
|
|
|
+ // Handler handles the event. It is acallback so it can fill an array or use a channel, etc.
|
|
|
+ Handler func(Event) bool
|
|
|
+}
|
|
|
+
|
|
|
+func (s *State) SetHandler(h func(Event) bool) {
|
|
|
+ s.Handler = h
|
|
|
+}
|
|
|
+
|
|
|
+func (s *State) AddField(x, y int) *Field {
|
|
|
+ field := &Field{Point: image.Pt(x, y)}
|
|
|
+ s.Fields = append(s.Fields, field)
|
|
|
+ return field
|
|
|
+}
|
|
|
+
|
|
|
+func (s State) Emit(k Kind, e Event) bool {
|
|
|
+ e.Kind = k
|
|
|
+ if s.Handler != nil {
|
|
|
+ return s.Handler(e)
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+func (s State) EmitMouse(k Kind, me MouseEvent) bool {
|
|
|
+ return s.Emit(k, Event{Mouse: me})
|
|
|
+}
|
|
|
+
|
|
|
+func (s State) EmitPad(k Kind, pe PadEvent) bool {
|
|
|
+ return s.Emit(k, Event{Pad: pe})
|
|
|
+}
|
|
|
+
|
|
|
+func (s State) EmitKey(k Kind, ke KeyEvent) bool {
|
|
|
+ return s.Emit(k, Event{Key: ke})
|
|
|
+}
|
|
|
+
|
|
|
+func (s State) EmitTouch(k Kind, te TouchEvent) bool {
|
|
|
+ return s.Emit(k, Event{Touch: te})
|
|
|
+}
|
|
|
+
|
|
|
+func (s State) EmitInput(k Kind, ie InputEvent) bool {
|
|
|
+ return s.Emit(k, Event{Input: ie})
|
|
|
+}
|
|
|
+
|
|
|
+func (s State) EmitCustom(k Kind, ce CustomEvent) bool {
|
|
|
+ return s.Emit(k, Event{Custom: ce})
|
|
|
+}
|
|
|
+
|
|
|
+func (s *State) Update() {
|
|
|
+ for _, gid := range s.gamepads {
|
|
|
+ if inpututil.IsGamepadJustDisconnected(gid) {
|
|
|
+ s.EmitPad(PadDetach, PadEvent{ID: int(gid)})
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ s.connected = inpututil.AppendJustConnectedGamepadIDs(nil)
|
|
|
+ for _, gid := range s.connected {
|
|
|
+ s.EmitPad(PadAttach, PadEvent{ID: int(gid)})
|
|
|
+ }
|
|
|
+
|
|
|
+ s.gamepads = ebiten.AppendGamepadIDs(s.gamepads)
|
|
|
+ for _, gid := range s.gamepads {
|
|
|
+ buttons := inpututil.AppendJustPressedGamepadButtons(gid, nil)
|
|
|
+ for _, button := range buttons {
|
|
|
+ s.EmitPad(PadPress, PadEvent{ID: int(gid), Button: button})
|
|
|
+ }
|
|
|
+
|
|
|
+ buttons = inpututil.AppendPressedGamepadButtons(gid, nil)
|
|
|
+ for _, button := range buttons {
|
|
|
+ dur := inpututil.GamepadButtonPressDuration(gid, button)
|
|
|
+ s.EmitPad(PadHold, PadEvent{ID: int(gid), Button: button, Duration: dur})
|
|
|
+ }
|
|
|
+
|
|
|
+ buttons = inpututil.AppendJustReleasedGamepadButtons(gid, nil)
|
|
|
+ for _, button := range buttons {
|
|
|
+ s.EmitPad(PadRelease, PadEvent{ID: int(gid), Button: button})
|
|
|
+ }
|
|
|
+
|
|
|
+ count := ebiten.GamepadAxisCount(gid)
|
|
|
+ axes := make([]float64, count)
|
|
|
+ moved := false
|
|
|
+ for axis := 0; axis < count; axis++ {
|
|
|
+ value := ebiten.GamepadAxisValue(gid, axis)
|
|
|
+ axes[axis] = value
|
|
|
+ moved = moved || ((value > 0.1) || (value < -0.1))
|
|
|
+ }
|
|
|
+ if (len(axes) > 0) && moved {
|
|
|
+ s.EmitPad(PadMove, PadEvent{ID: int(gid), Axes: axes})
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ keys := inpututil.AppendJustPressedKeys(nil)
|
|
|
+ for _, key := range keys {
|
|
|
+ s.EmitKey(KeyPress, KeyEvent{Key: key})
|
|
|
+ }
|
|
|
+
|
|
|
+ keys = inpututil.AppendPressedKeys(nil)
|
|
|
+ for _, key := range keys {
|
|
|
+ dur := inpututil.KeyPressDuration(key)
|
|
|
+ s.EmitKey(KeyHold, KeyEvent{Key: key, Duration: dur})
|
|
|
+ }
|
|
|
+
|
|
|
+ keys = inpututil.AppendJustReleasedKeys(nil)
|
|
|
+ for _, key := range keys {
|
|
|
+ s.EmitKey(KeyRelease, KeyEvent{Key: key})
|
|
|
+ }
|
|
|
+
|
|
|
+ for id, field := range s.Fields {
|
|
|
+ if field.IsFocused() {
|
|
|
+ handled, _ := field.HandleInput(field.X, field.Y)
|
|
|
+ if handled {
|
|
|
+ s.EmitInput(InputText, InputEvent{ID: id, Text: field.Text()})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ touches := inpututil.AppendJustPressedTouchIDs(nil)
|
|
|
+ for _, touch := range touches {
|
|
|
+ x, y := ebiten.TouchPosition(touch)
|
|
|
+ s.EmitTouch(TouchPress, TouchEvent{ID: int(touch), Point: image.Pt(x, y)})
|
|
|
+ }
|
|
|
+
|
|
|
+ touches = ebiten.AppendTouchIDs(nil)
|
|
|
+ for _, touch := range touches {
|
|
|
+ x, y := ebiten.TouchPosition(touch)
|
|
|
+ px, py := inpututil.TouchPositionInPreviousTick(touch)
|
|
|
+ dx, dy := x-px, y-py
|
|
|
+ dur := inpututil.TouchPressDuration(touch)
|
|
|
+ s.EmitTouch(TouchHold, TouchEvent{ID: int(touch),
|
|
|
+ Point: image.Pt(x, y),
|
|
|
+ Delta: image.Pt(dx, dy),
|
|
|
+ Duration: dur,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ touches = inpututil.AppendJustReleasedTouchIDs(nil)
|
|
|
+ for _, touch := range touches {
|
|
|
+ x, y := ebiten.TouchPosition(touch)
|
|
|
+ s.EmitTouch(TouchRelease, TouchEvent{ID: int(touch), Point: image.Pt(x, y)})
|
|
|
+ }
|
|
|
+
|
|
|
+ x, y := ebiten.CursorPosition()
|
|
|
+ dx, dy := x-s.cx, y-s.cy
|
|
|
+ at := image.Pt(x, y)
|
|
|
+ delta := image.Pt(dx, dy)
|
|
|
+
|
|
|
+ for mb := ebiten.MouseButton(0); mb < ebiten.MouseButtonMax; mb++ {
|
|
|
+ if inpututil.IsMouseButtonJustPressed(mb) {
|
|
|
+ s.EmitMouse(MousePress, MouseEvent{Point: at, Delta: delta})
|
|
|
+ }
|
|
|
+ if ebiten.IsMouseButtonPressed(mb) {
|
|
|
+ dur := inpututil.MouseButtonPressDuration(mb)
|
|
|
+ s.EmitMouse(MouseHold, MouseEvent{Point: at, Delta: delta, Duration: dur})
|
|
|
+ }
|
|
|
+ if inpututil.IsMouseButtonJustReleased(mb) {
|
|
|
+ s.EmitMouse(MouseRelease, MouseEvent{Point: at, Delta: delta})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if dx != 0 || dy != 0 {
|
|
|
+ s.EmitMouse(MouseMove, MouseEvent{Point: at, Delta: delta})
|
|
|
+ }
|
|
|
+ s.cx = x
|
|
|
+ s.cy = y
|
|
|
+
|
|
|
+ wx, wy := ebiten.Wheel()
|
|
|
+ if wx != 0 || wy != 0 {
|
|
|
+ wheel := image.Pt(int(wx), int(wy))
|
|
|
+ s.EmitMouse(MouseWheel, MouseEvent{Point: at, Delta: delta, Wheel: wheel})
|
|
|
+ }
|
|
|
+
|
|
|
+}
|