Keyboard bindings with fyne

This commit is contained in:
Ivan Izaguirre 2020-10-10 17:35:25 +02:00 committed by Iván Izaguirre
parent 97acee9f9e
commit 10f915c90f
8 changed files with 234 additions and 47 deletions

2
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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

View 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)
}
}

View File

@ -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)
})
}
}

View File

@ -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:

View File

@ -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
View 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
}