mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-01-18 18:31:41 +00:00
Keyboard bindings with fyne
This commit is contained in:
parent
97acee9f9e
commit
10f915c90f
2
go.mod
2
go.mod
@ -9,3 +9,5 @@ require (
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
|
||||
github.com/veandco/go-sdl2 v0.4.0
|
||||
)
|
||||
|
||||
replace fyne.io/fyne => ../../fyne/fyne
|
||||
|
5
go.sum
5
go.sum
@ -7,6 +7,7 @@ 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=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fyne-io/mobile v0.0.2 h1:eGmCR5lkFxk0PnPafGppLFRD5QODJfSVdrjhLjanOVg=
|
||||
github.com/fyne-io/mobile v0.0.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
@ -19,9 +20,11 @@ github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBs
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -70,11 +73,13 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03 h1:XpToik3MpT5iW3iHgNwnh3a8QwugfomvxOlyDnaOils=
|
||||
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
|
@ -23,11 +23,6 @@ type ioC0Page struct {
|
||||
type softSwitchR func(io *ioC0Page) uint8
|
||||
type softSwitchW func(io *ioC0Page, value uint8)
|
||||
|
||||
// KeyboardProvider provides a keyboard implementation
|
||||
type KeyboardProvider interface {
|
||||
GetKey(strobe bool) (key uint8, ok bool)
|
||||
}
|
||||
|
||||
// SpeakerProvider provides a speaker implementation
|
||||
type SpeakerProvider interface {
|
||||
// Click receives a speaker click. The argument is the CPU cycle when it is generated
|
||||
|
134
izapple2fyne/fyneKeyboard.go
Normal file
134
izapple2fyne/fyneKeyboard.go
Normal file
@ -0,0 +1,134 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fyne.io/fyne"
|
||||
"fyne.io/fyne/driver/desktop"
|
||||
"github.com/ivanizag/izapple2"
|
||||
)
|
||||
|
||||
type keyboard struct {
|
||||
a *izapple2.Apple2
|
||||
keyChannel *izapple2.KeyboardChannel
|
||||
|
||||
controlLeft bool
|
||||
controlRight bool
|
||||
showPages bool
|
||||
}
|
||||
|
||||
func newKeyboard(a *izapple2.Apple2) *keyboard {
|
||||
var k keyboard
|
||||
k.a = a
|
||||
k.keyChannel = izapple2.NewKeyboardChannel(a)
|
||||
return &k
|
||||
}
|
||||
|
||||
func (k *keyboard) putRune(ch rune) {
|
||||
k.keyChannel.PutRune(ch)
|
||||
}
|
||||
|
||||
// PutChar sends a character to the emulator
|
||||
func (k *keyboard) PutChar(ch uint8) {
|
||||
k.keyChannel.PutChar(ch)
|
||||
}
|
||||
|
||||
func (k *keyboard) putKeyAction(keyEvent *fyne.KeyEvent, press bool) {
|
||||
|
||||
ctrl := k.controlLeft || k.controlRight
|
||||
if press && ctrl && len(keyEvent.Name) == 1 {
|
||||
// Hacky. We relay on the letter values to be a single uppercase char.
|
||||
ch := keyEvent.Name[0]
|
||||
if ch >= 'A' && ch <= 'Z' {
|
||||
k.keyChannel.PutChar(uint8(ch) - 65 + 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch keyEvent.Name {
|
||||
case desktop.KeyControlLeft:
|
||||
k.controlLeft = press
|
||||
case desktop.KeyControlRight:
|
||||
k.controlRight = press
|
||||
}
|
||||
}
|
||||
|
||||
func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||
/*
|
||||
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 := k.controlLeft || k.controlRight
|
||||
result := uint8(0)
|
||||
switch keyEvent.Name {
|
||||
case fyne.KeyEscape:
|
||||
result = 27
|
||||
case fyne.KeyBackspace:
|
||||
result = 8
|
||||
case fyne.KeyReturn:
|
||||
result = 13
|
||||
case fyne.KeyEnter:
|
||||
result = 13
|
||||
case fyne.KeyLeft:
|
||||
result = 8
|
||||
case fyne.KeyRight:
|
||||
result = 21
|
||||
|
||||
// Apple //e
|
||||
case fyne.KeyUp:
|
||||
result = 11 // 31 in the Base64A
|
||||
case fyne.KeyDown:
|
||||
result = 10
|
||||
case fyne.KeyTab:
|
||||
result = 9
|
||||
case fyne.KeyDelete:
|
||||
result = 127 // 24 in the Base64A
|
||||
|
||||
// Base64A clone particularities
|
||||
case fyne.KeyF2:
|
||||
result = 127 // Base64A
|
||||
|
||||
// Control of the emulator
|
||||
case fyne.KeyF1:
|
||||
if ctrl {
|
||||
k.a.SendCommand(izapple2.CommandReset)
|
||||
}
|
||||
case fyne.KeyF5:
|
||||
if ctrl {
|
||||
k.a.SendCommand(izapple2.CommandShowSpeed)
|
||||
} else {
|
||||
k.a.SendCommand(izapple2.CommandToggleSpeed)
|
||||
}
|
||||
case fyne.KeyF6:
|
||||
k.a.SendCommand(izapple2.CommandToggleColor)
|
||||
case fyne.KeyF7:
|
||||
k.showPages = !k.showPages
|
||||
case fyne.KeyF9:
|
||||
k.a.SendCommand(izapple2.CommandDumpDebugInfo)
|
||||
case fyne.KeyF10:
|
||||
k.a.SendCommand(izapple2.CommandNextCharGenPage)
|
||||
case fyne.KeyF11:
|
||||
k.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
||||
case fyne.KeyF12:
|
||||
case fyne.KeyPrintScreen:
|
||||
err := izapple2.SaveSnapshot(k.a, "snapshot.png")
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
||||
} else {
|
||||
fmt.Println("Saving snapshot")
|
||||
}
|
||||
case fyne.KeyPause:
|
||||
k.a.SendCommand(izapple2.CommandPauseUnpauseEmulator)
|
||||
}
|
||||
|
||||
// Missing values 91 to 95. Usually control for [\]^_
|
||||
// On the Base64A it's control for \]./
|
||||
|
||||
if result != 0 {
|
||||
k.keyChannel.PutChar(result)
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
"fyne.io/fyne"
|
||||
"fyne.io/fyne/app"
|
||||
"fyne.io/fyne/canvas"
|
||||
"fyne.io/fyne/driver/desktop"
|
||||
"fyne.io/fyne/layout"
|
||||
"fyne.io/fyne/widget"
|
||||
)
|
||||
@ -23,12 +24,11 @@ func main() {
|
||||
defer profile.Start().Stop()
|
||||
}
|
||||
|
||||
FyneRun(a)
|
||||
fyneRun(a)
|
||||
}
|
||||
}
|
||||
|
||||
// FyneRun starts the Apple2 emulator on fyne.io
|
||||
func FyneRun(a *izapple2.Apple2) {
|
||||
func fyneRun(a *izapple2.Apple2) {
|
||||
app := app.New()
|
||||
// app.SetIcon(xxx)
|
||||
window := app.NewWindow("iz-" + a.Name)
|
||||
@ -38,14 +38,17 @@ func FyneRun(a *izapple2.Apple2) {
|
||||
top := widget.NewLabel("Top")
|
||||
bottom := widget.NewLabel("Bottom")
|
||||
right := widget.NewLabel("Right")
|
||||
|
||||
container := fyne.NewContainerWithLayout(
|
||||
layout.NewBorderLayout(top, bottom, nil, right),
|
||||
screen, top, bottom, right,
|
||||
)
|
||||
window.SetContent(container)
|
||||
window.SetPadded(false)
|
||||
|
||||
registerKeyboardEvents(a, window.Canvas())
|
||||
|
||||
go a.Run()
|
||||
|
||||
ticker := time.NewTicker(60 * time.Millisecond)
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
@ -71,3 +74,27 @@ func FyneRun(a *izapple2.Apple2) {
|
||||
app.Run()
|
||||
|
||||
}
|
||||
|
||||
func registerKeyboardEvents(a *izapple2.Apple2, canvas fyne.Canvas) {
|
||||
kp := newKeyboard(a)
|
||||
|
||||
// Koyboard events
|
||||
canvas.SetOnTypedKey(func(ke *fyne.KeyEvent) {
|
||||
//fmt.Printf("Event: %v\n", ke.Name)
|
||||
kp.putKey(ke)
|
||||
})
|
||||
canvas.SetOnTypedRune(func(ch rune) {
|
||||
//fmt.Printf("Rune: %v\n", ch)
|
||||
kp.putRune(ch)
|
||||
})
|
||||
if deskCanvas, ok := canvas.(desktop.Canvas); ok {
|
||||
deskCanvas.SetOnKeyDown(func(ke *fyne.KeyEvent) {
|
||||
kp.putKeyAction(ke, true)
|
||||
//fmt.Printf("Event down: %v\n", ke.Name)
|
||||
})
|
||||
deskCanvas.SetOnKeyUp(func(ke *fyne.KeyEvent) {
|
||||
kp.putKeyAction(ke, false)
|
||||
//fmt.Printf("Event up: %v\n", ke.Name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,11 @@ func main() {
|
||||
defer profile.Start().Stop()
|
||||
}
|
||||
|
||||
SDLRun(a)
|
||||
sdlRun(a)
|
||||
}
|
||||
}
|
||||
|
||||
// SDLRun starts the Apple2 emulator on SDL
|
||||
func SDLRun(a *izapple2.Apple2) {
|
||||
func sdlRun(a *izapple2.Apple2) {
|
||||
|
||||
window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7+8, 4*24*8,
|
||||
sdl.WINDOW_SHOWN)
|
||||
@ -39,7 +38,6 @@ func SDLRun(a *izapple2.Apple2) {
|
||||
window.SetTitle("iz-" + a.Name)
|
||||
|
||||
kp := newSDLKeyBoard(a)
|
||||
a.SetKeyboardProvider(kp)
|
||||
|
||||
s := newSDLSpeaker()
|
||||
s.start()
|
||||
@ -62,7 +60,7 @@ func SDLRun(a *izapple2.Apple2) {
|
||||
kp.putKey(t)
|
||||
j.putKey(t)
|
||||
case *sdl.TextInputEvent:
|
||||
kp.putText(t)
|
||||
kp.putText(t.GetText())
|
||||
case *sdl.JoyAxisEvent:
|
||||
j.putAxisEvent(t)
|
||||
case *sdl.JoyButtonEvent:
|
||||
|
@ -2,40 +2,27 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ivanizag/izapple2"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type sdlKeyboard struct {
|
||||
keyChannel chan uint8
|
||||
a *izapple2.Apple2
|
||||
keyChannel *izapple2.KeyboardChannel
|
||||
|
||||
showPages bool
|
||||
}
|
||||
|
||||
func newSDLKeyBoard(a *izapple2.Apple2) *sdlKeyboard {
|
||||
var k sdlKeyboard
|
||||
k.keyChannel = make(chan uint8, 100)
|
||||
k.a = a
|
||||
k.keyChannel = izapple2.NewKeyboardChannel(a)
|
||||
return &k
|
||||
}
|
||||
|
||||
func (k *sdlKeyboard) putText(textEvent *sdl.TextInputEvent) {
|
||||
text := textEvent.GetText()
|
||||
|
||||
for _, ch := range text {
|
||||
// We will use computed text only for printable ASCII chars
|
||||
if ch < ' ' || ch > '~' {
|
||||
continue
|
||||
}
|
||||
|
||||
buf := make([]uint8, 1)
|
||||
utf8.EncodeRune(buf, ch)
|
||||
|
||||
k.putChar(buf[0])
|
||||
}
|
||||
func (k *sdlKeyboard) putText(text string) {
|
||||
k.keyChannel.PutText(text)
|
||||
}
|
||||
|
||||
func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||
@ -57,7 +44,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||
|
||||
if ctrl {
|
||||
if key.Sym >= 'a' && key.Sym <= 'z' {
|
||||
k.putChar(uint8(key.Sym) - 97 + 1)
|
||||
k.keyChannel.PutChar(uint8(key.Sym) - 97 + 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -118,6 +105,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||
case sdl.K_F11:
|
||||
k.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
||||
case sdl.K_F12:
|
||||
case sdl.K_PRINTSCREEN:
|
||||
err := izapple2.SaveSnapshot(k.a, "snapshot.png")
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
||||
@ -132,20 +120,6 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||
// On the Base64A it's control for \]./
|
||||
|
||||
if result != 0 {
|
||||
k.putChar(result)
|
||||
k.keyChannel.PutChar(result)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *sdlKeyboard) putChar(ch uint8) {
|
||||
k.keyChannel <- ch
|
||||
}
|
||||
|
||||
func (k *sdlKeyboard) GetKey(_ bool) (key uint8, ok bool) {
|
||||
select {
|
||||
case key = <-k.keyChannel:
|
||||
ok = true
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
52
keyboard.go
Normal file
52
keyboard.go
Normal file
@ -0,0 +1,52 @@
|
||||
package izapple2
|
||||
|
||||
// KeyboardProvider provides a keyboard implementation
|
||||
type KeyboardProvider interface {
|
||||
GetKey(strobe bool) (key uint8, ok bool)
|
||||
}
|
||||
|
||||
// KeyboardChannel is a possible implemetation of a Keyboard provider
|
||||
type KeyboardChannel struct {
|
||||
keyChannel chan uint8
|
||||
a *Apple2
|
||||
}
|
||||
|
||||
// NewKeyboardChannel returns an instance of KeyboardChannel
|
||||
func NewKeyboardChannel(a *Apple2) *KeyboardChannel {
|
||||
var k KeyboardChannel
|
||||
k.keyChannel = make(chan uint8, 100)
|
||||
k.a = a
|
||||
a.SetKeyboardProvider(&k)
|
||||
return &k
|
||||
}
|
||||
|
||||
// PutText sends texts to the emulator as succesive chars
|
||||
func (k *KeyboardChannel) PutText(text string) {
|
||||
for _, ch := range text {
|
||||
k.PutRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// PutRune sends a rune to the emulator if it is valid printable ASCII
|
||||
func (k *KeyboardChannel) PutRune(ch rune) {
|
||||
// We will use computed text only for printable ASCII chars
|
||||
if ch >= ' ' && ch <= '~' {
|
||||
k.PutChar(uint8(ch))
|
||||
}
|
||||
}
|
||||
|
||||
// PutChar sends a character to the emulator
|
||||
func (k *KeyboardChannel) PutChar(ch uint8) {
|
||||
k.keyChannel <- ch
|
||||
}
|
||||
|
||||
// GetKey returns a pressed key if available
|
||||
func (k *KeyboardChannel) GetKey(_ bool) (key uint8, ok bool) {
|
||||
select {
|
||||
case key = <-k.keyChannel:
|
||||
ok = true
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user