Give a2ebiten a mouse, and the review of the shared code

The mouse the frontends share is little more than a scale, so a2ebiten can have
one now. Ebitengine has no mouse events, the pointer is polled when the frame is
updated, and its position is taken on the picture instead of on the window, so
it lands on the same place whatever the size of the window is. The models with a
mouse, like desktop, are of some use there now.

A pointer outside the window stays on the edge instead of wrapping around, which
Ebitengine reports as soon as the pointer leaves the picture.

NewMixer is now NewFrontMixer, so that it does not look like the NewMixer of the
audio package. The methods that the machine calls are marked as the
implementations of izapple2.MouseProvider and izapple2.JoysticksProvider, and
the note about SDL_WarpMouseInWindow is gone: it came from the SDL2 mouse and
means nothing in a package that knows nothing about SDL.
This commit is contained in:
Iván Izaguirre authored and Iván Izaguirre committed 2026-08-12 18:05:28 +02:00
1 parent 88e59ae78d
commit 1b563d7668
10 files changed
+58 -20

No files matched your search

+10 -4
View File
@@ -1,9 +1,9 @@
# The Ebitengine frontend
`a2ebiten` opens a window with [Ebitengine](https://ebitengine.org/). It has
the screen with all its modes, the sound, the diskettes dropped on the window
and the same function keys as [a2sdl](frontend_sdl2.md), and it is missing the
things around them: no joysticks and no mouse.
the screen with all its modes, the sound, the mouse, the diskettes dropped on
the window and the same function keys as [a2sdl](frontend_sdl2.md), and it is
missing the things around them: no joysticks.
## Building
@@ -58,10 +58,16 @@ so the area used is the one the pointer was last seen on. Check with F8 before
dragging, or look at the areas shown after the drop to see where the file
landed.
## The mouse
The models that use a mouse, like `desktop`, work with the pointer on the
window. Ebitengine has no mouse events, so the pointer is read on every frame,
and its position is taken on the picture and not on the window: it lands on the
same place whatever the size of the window is.
## What is missing
- **Joysticks and paddles**, and the mouse as a joystick.
- **The mouse**, so the models that use it, like `desktop`, are not much use.
- **Pasting** from the clipboard.
The window is 564x384 and can be resized. The picture is generated at a fixed
+1 -1
View File
@@ -23,7 +23,7 @@ type ebitenAudio struct {
func newEbitenAudio(a *izapple2.Apple2) *ebitenAudio {
return &ebitenAudio{
mixer: shared.NewMixer(a),
mixer: shared.NewFrontMixer(a),
}
}
+18
View File
@@ -20,6 +20,7 @@ type Game struct {
view *shared.View
keyboard *ebitenKeyboard
speaker *ebitenAudio
mouse *shared.Mouse
dropTargets *ebitenDropTargets
fontSource *text.GoTextFaceSource
@@ -41,6 +42,7 @@ var hudColor = color.RGBA{208, 241, 141, 255} // Yellow
func (g *Game) Update() error {
g.keyboard.update()
g.speaker.update()
g.updateMouse()
g.dropTargets.update()
if g.paused != g.a.IsPaused() {
@@ -71,6 +73,20 @@ func (g *Game) Update() error {
return nil
}
/*
updateMouse reads where the pointer is and whether its button is pressed.
Ebitengine has no mouse events, they are polled when the frame is updated.
The positions are on the virtual screen the game is laid out on, not on the
window, so the pointer lands on the same place of the picture whatever the size
of the window is.
*/
func (g *Game) updateMouse() {
x, y := ebiten.CursorPosition()
g.mouse.SetPosition(x, y, virtualWidth, virtualHeight)
g.mouse.SetButton(ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft))
}
func (g *Game) Draw(screen *ebiten.Image) {
if g.image != nil {
op := &ebiten.DrawImageOptions{}
@@ -128,8 +144,10 @@ func ebitenRun(a *izapple2.Apple2) {
view: view,
keyboard: newEbitenKeyBoard(a, view),
speaker: newEbitenAudio(a),
mouse: shared.NewMouse(),
dropTargets: newEbitenDropTargets(a),
}
a.SetMouseProvider(game.mouse)
var err error
game.fontSource, err = text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
+1 -1
View File
@@ -40,7 +40,7 @@ func newAudioOutput(a *izapple2.Apple2) *audioOutput {
size := int(samplesPerFrame) + 2
return &audioOutput{
mixer: shared.NewMixer(a),
mixer: shared.NewFrontMixer(a),
samplesPerFrame: samplesPerFrame,
mono: make([]float32, size),
stereo: make([]C.int16_t, 2*size),
+1 -1
View File
@@ -34,7 +34,7 @@ var theSDLAudio atomic.Pointer[sdlAudio]
func newSDLAudio(a *izapple2.Apple2) *sdlAudio {
return &sdlAudio{
mixer: shared.NewMixer(a),
mixer: shared.NewFrontMixer(a),
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ type sdl3Audio struct {
func newSDL3Audio(a *izapple2.Apple2) *sdl3Audio {
return &sdl3Audio{
mixer: shared.NewMixer(a),
mixer: shared.NewFrontMixer(a),
buf: make([]float32, bufferSamples),
}
}
+2 -2
View File
@@ -5,9 +5,9 @@ import (
"github.com/ivanizag/izapple2/audio"
)
// NewMixer creates the mixer of a machine with every sound source of it
// NewFrontMixer creates the mixer of a machine with every sound source of it
// attached, ready for the frontend to send the samples to its audio device
func NewMixer(a *izapple2.Apple2) *audio.Mixer {
func NewFrontMixer(a *izapple2.Apple2) *audio.Mixer {
mixer := audio.NewMixer(a.GetClockMhz())
for _, source := range a.GetAudioSources() {
source.SetAudioSink(mixer.NewSource())
+10 -4
View File
@@ -21,8 +21,8 @@ func (m *Mouse) SetPosition(x int, y int, width int, height int) {
return
}
m.x = uint16(65536 * x / width)
m.y = uint16(65536 * y / height)
m.x = mousePosition(x, width)
m.y = mousePosition(y, height)
}
// SetButton presses or releases the button of the mouse
@@ -30,10 +30,16 @@ func (m *Mouse) SetButton(pressed bool) {
m.pressed = pressed
}
// mousePosition turns a position on the window into the full range the machine
// reads the mouse in, keeping what is outside the window on the edge
func mousePosition(position int, size int) uint16 {
return uint16(max(min(65536*position/size, 65535), 0))
}
// izapple2.MouseProvider implementation
// ReadMouse returns the position of the pointer on the window and whether its
// button is pressed
func (m *Mouse) ReadMouse() (x uint16, y uint16, pressed bool) {
return m.x, m.y, m.pressed
}
// TODO: SDL_WarpMouseInWindow
+6
View File
@@ -22,4 +22,10 @@ func TestMouse(t *testing.T) {
if x, y, _ := m.ReadMouse(); x != 32768 || y != 16384 {
t.Errorf("the mouse moved to (%v, %v) on a window without size", x, y)
}
// The pointer outside the window stays on the edge
m.SetPosition(-10, 999, 640, 480)
if x, y, _ := m.ReadMouse(); x != 0 || y != 65535 {
t.Errorf("the mouse is at (%v, %v), expected (0, 65535)", x, y)
}
}
+8 -6
View File
@@ -130,6 +130,14 @@ func (p *Paddles) SetMouseButton(button int, pressed bool) {
p.mouseButtons[button] = pressed
}
// mouseToPaddle turns a position on the window into the value of a paddle
// centered on the center of the window
func mouseToPaddle(x int, width int) uint8 {
return uint8(max(min(x-(width/2)+paddleCentered, 255), 0))
}
// izapple2.JoysticksProvider implementation
// ReadButton returns whether a pushbutton is pressed, as the machine reads it
func (p *Paddles) ReadButton(i int) bool {
switch i {
@@ -150,9 +158,3 @@ func (p *Paddles) ReadButton(i int) bool {
func (p *Paddles) ReadPaddle(i int) (uint8, bool) {
return p.paddle[i], p.hasPaddle[i]
}
// mouseToPaddle turns a position on the window into the value of a paddle
// centered on the center of the window
func mouseToPaddle(x int, width int) uint8 {
return uint8(max(min(x-(width/2)+paddleCentered, 255), 0))
}