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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ The project includes E2E boot tests (`e2e_boot_test.go`, `e2e_woz_test.go`) that
- Main emulator code is in the root package `izapple2`
- Subpackages include: `storage`, `screen`, `fujinet`, `component`
- Frontend implementations are in `frontend/` directory
- `frontend/shared` has the code more than one frontend needs and the emulator library has no place for, like the drop targets screen
- `frontend/shared` has the code more than one frontend needs and the emulator library has no place for, like the screen each frontend shows and the drop targets

### Naming Conventions

Expand Down
14 changes: 10 additions & 4 deletions doc/frontend_ebiten.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions doc/frontend_sdl2.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ Escape mapped to what the Apple II understands. The rest:

On the Base64A, F3 is the Delete key of that keyboard.

The Apple keys are a place on the keyboard and are found by scancode, so they
work on the layouts where the alt keys produce another symbol. Losing the
window focus releases them, so one held while switching windows does not stay
pressed.

The title bar says which machine is running and shows `PAUSED!` while it is
paused. With the four panels or the character map on screen it shows what is
being displayed instead.
Expand Down
9 changes: 2 additions & 7 deletions doc/frontend_sdl3.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,8 @@ The same command line as every other frontend, see

The same as [a2sdl](frontend_sdl2.md#keys), including the paste with
Shift-Insert or Cmd-V and the Open-Apple and Closed-Apple on the alt or option
keys.

The Apple keys are found by scancode instead of by keycode. On the keyboard
layouts where the right alt is AltGr, SDL3 does not report it as an alt key,
and the Closed-Apple would never be pressed if it were looked up by keycode.
keys. Both frontends decide the keys with the same code, so they answer to the
same ones.

## What is different from SDL2

Expand All @@ -58,8 +55,6 @@ Nothing that you use, and a few things underneath:
areas of the drives it can be dropped on is shown while it moves, with the
one under the pointer marked. SDL2 only knows about the file once it is
dropped, and has to show the areas with F8 or after the drop.
- Losing the window focus releases the joystick keys, so an Open-Apple held
while switching windows does not stay pressed.
- Ctrl-C on the terminal quits the same way as closing the window, releasing
everything on the way out.
- The scaling of the picture is a property of the renderer instead of the
Expand Down
6 changes: 4 additions & 2 deletions frontend/a2ebiten/ebitenAudio.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (

"github.com/hajimehoshi/ebiten/v2/audio"

"github.com/ivanizag/izapple2"
a2audio "github.com/ivanizag/izapple2/audio"
"github.com/ivanizag/izapple2/frontend/shared"
)

// ebitenAudio sends the mixed audio of the machine to the ebiten audio
Expand All @@ -19,9 +21,9 @@ type ebitenAudio struct {
samples []float32
}

func newEbitenAudio(clockMhz float64) *ebitenAudio {
func newEbitenAudio(a *izapple2.Apple2) *ebitenAudio {
return &ebitenAudio{
mixer: a2audio.NewMixer(clockMhz),
mixer: shared.NewFrontMixer(a),
}
}

Expand Down
17 changes: 0 additions & 17 deletions frontend/a2ebiten/ebitenDropTargets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"image"
"io/fs"
"os"

Expand Down Expand Up @@ -106,22 +105,6 @@ func (d *ebitenDropTargets) dropped() int {
return drive
}

// showing returns whether the areas take over the screen, either because the
// user asked for them with F8 or because a file has just been dropped
func (d *ebitenDropTargets) showing(requested bool) bool {
return requested || d.targets.Flashing()
}

// snapshot returns the screen with the areas, highlighting the drive that got
// the last file while the flash lasts, or the one under the pointer
func (d *ebitenDropTargets) snapshot() *image.RGBA {
selected := d.targets.FlashDrive()
if !d.targets.Flashing() {
selected = d.pointedDrive()
}
return d.targets.Snapshot(selected)
}

// pointedDrive returns the drive the mouse pointer is on. The positions are on
// the virtual screen the game is laid out on, not on the window.
func (d *ebitenDropTargets) pointedDrive() int {
Expand Down
153 changes: 49 additions & 104 deletions frontend/a2ebiten/ebitenKeyboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,25 @@ import (
"fmt"

"github.com/ivanizag/izapple2"
"github.com/ivanizag/izapple2/screen"
"github.com/ivanizag/izapple2/frontend/shared"

"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)

// ebitenKeyboard turns the key events of Ebitengine into the keys the
// frontends share
type ebitenKeyboard struct {
a *izapple2.Apple2
keyChannel *izapple2.KeyboardChannel
keyboard *shared.Keyboard

showHelp bool
showPages bool
showCharGen bool
showAltText bool
showFreq bool
showDropTargets bool
screenMode int
showFreq bool // The speed on the HUD, only this frontend has one

debug bool
}

func newEbitenKeyBoard(a *izapple2.Apple2) *ebitenKeyboard {
func newEbitenKeyBoard(a *izapple2.Apple2, view *shared.View) *ebitenKeyboard {
var k ebitenKeyboard
k.a = a
k.keyChannel = izapple2.NewKeyboardChannel(a)

k.screenMode = screen.ScreenModeColorScanlines
k.keyboard = shared.NewKeyboard(a, view)
return &k
}

Expand All @@ -54,123 +46,76 @@ func (k *ebitenKeyboard) update() {
}

func (k *ebitenKeyboard) putText(text string) {
k.keyChannel.PutText(text)
k.keyboard.PutText(text)
}

func (k *ebitenKeyboard) putKey(key ebiten.Key) {
/*
See "Apple II reference manual", page 5

To get keys as understood by the Apple2 hardware run:
10 A=PEEK(49152)
20 PRINT A, A - 128
30 GOTO 10
*/

ctrl := ebiten.IsKeyPressed(ebiten.KeyControl)
shift := ebiten.IsKeyPressed(ebiten.KeyShift)

if ctrl {
if key >= ebiten.KeyA && key <= ebiten.KeyZ {
fmt.Println("Control Key: ", key.String())
k.keyChannel.PutChar(uint8(key-ebiten.KeyA) - 97 + 1)
return
}
if ctrl && key >= ebiten.KeyA && key <= ebiten.KeyZ {
k.keyboard.PutCtrlLetter('a' + rune(key-ebiten.KeyA))
return
}

result := uint8(0)
if ctrl && key == ebiten.KeyF5 {
// Only this frontend draws the speed, the others report it
k.showFreq = !k.showFreq
return
}

k.keyboard.PutKey(ebitenKey(key), ctrl, shift)
}

// ebitenKey translates a key of Ebitengine
func ebitenKey(key ebiten.Key) shared.Key {
switch key {
case ebiten.KeyEscape:
result = 27
return shared.KeyEscape
case ebiten.KeyBackspace:
result = 8
case ebiten.KeyEnter:
result = 13
case ebiten.KeyNumpadEnter:
result = 13
return shared.KeyBackspace
case ebiten.KeyEnter, ebiten.KeyNumpadEnter:
return shared.KeyReturn
case ebiten.KeyTab:
return shared.KeyTab
case ebiten.KeyDelete:
return shared.KeyDelete
case ebiten.KeyLeft:
if ctrl {
result = 31 // Base64A
} else {
result = 8
}
return shared.KeyLeft
case ebiten.KeyRight:
result = 21

// Apple //e
return shared.KeyRight
case ebiten.KeyUp:
result = 11 // 31 in the Base64A
return shared.KeyUp
case ebiten.KeyDown:
result = 10
case ebiten.KeyTab:
result = 9
case ebiten.KeyDelete:
result = 127 // 24 in the Base64A
return shared.KeyDown

// Base64A clone particularities
case ebiten.KeyF3:
result = 127 // Base64A

// Control of the emulator
case ebiten.KeyF1:
k.showHelp = !k.showHelp
return shared.KeyF1
case ebiten.KeyF2:
// While the help is shown, F2 alone triggers a reset. This is handy
// when the window manager (for example KDE) captures Ctrl-F2.
if ctrl || k.showHelp {
k.a.SendCommand(izapple2.CommandReset)
k.showHelp = false
}
return shared.KeyF2
case ebiten.KeyF3:
return shared.KeyF3
case ebiten.KeyF4:
k.a.SendCommand(izapple2.CommandToggleCPUTrace)
return shared.KeyF4
case ebiten.KeyF5:
if ctrl {
k.showFreq = !k.showFreq
} else {
k.a.SendCommand(izapple2.CommandToggleSpeed)
}
return shared.KeyF5
case ebiten.KeyF6:
k.screenMode = screen.NextScreenMode(k.screenMode)
return shared.KeyF6
case ebiten.KeyF7:
k.showPages = !k.showPages
return shared.KeyF7
case ebiten.KeyF8:
k.showDropTargets = !k.showDropTargets
if k.showDropTargets {
// The help is shown on top of the drop targets, get it out of the way
k.showHelp = false
}
return shared.KeyF8
case ebiten.KeyF9:
k.a.SendCommand(izapple2.CommandDumpDebugInfo)
return shared.KeyF9
case ebiten.KeyF10:
if ctrl {
k.showCharGen = !k.showCharGen
} else if shift {
k.showAltText = !k.showAltText
} else {
k.a.SendCommand(izapple2.CommandNextCharGenPage)
}
return shared.KeyF10
case ebiten.KeyF12:
fallthrough
case ebiten.KeyPrintScreen:
if ctrl {
screen.AddScenario(k.a.GetVideoSource(), "../../screen/test_resources/")
} else {
err := screen.SaveSnapshot(k.a.GetVideoSource(), screen.ScreenModeColorScanlines, "snapshot.png")
if err != nil {
fmt.Printf("Error saving snapshoot: %v.\n.", err)
} else {
fmt.Println("Saving snapshot 'snapshot.png'")
}
}
return shared.KeyF12
case ebiten.KeyPause:
k.a.SendCommand(izapple2.CommandPauseUnpause)
return shared.KeyPause
case ebiten.KeyPrintScreen:
return shared.KeyPrintScreen
}

// Missing values 91 to 95. Usually control for [\]^_
// On the Base64A it's control for \]./

if result != 0 {
k.keyChannel.PutChar(result)
}
return shared.KeyNone
}
Loading
Loading