From 10f915c90f262c545bb0da98d4ae346f9008ce1f Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sat, 10 Oct 2020 17:35:25 +0200 Subject: [PATCH] Keyboard bindings with fyne --- go.mod | 2 + go.sum | 5 ++ ioC0Page.go | 5 -- izapple2fyne/fyneKeyboard.go | 134 +++++++++++++++++++++++++++++++++++ izapple2fyne/main.go | 35 +++++++-- izapple2sdl/main.go | 8 +-- izapple2sdl/sdlKeyboard.go | 40 ++--------- keyboard.go | 52 ++++++++++++++ 8 files changed, 234 insertions(+), 47 deletions(-) create mode 100644 izapple2fyne/fyneKeyboard.go create mode 100644 keyboard.go diff --git a/go.mod b/go.mod index 06c553a..e3e05a6 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 43b3307..e558590 100644 --- a/go.sum +++ b/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= diff --git a/ioC0Page.go b/ioC0Page.go index 4b3e911..fb196d0 100644 --- a/ioC0Page.go +++ b/ioC0Page.go @@ -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 diff --git a/izapple2fyne/fyneKeyboard.go b/izapple2fyne/fyneKeyboard.go new file mode 100644 index 0000000..46f260e --- /dev/null +++ b/izapple2fyne/fyneKeyboard.go @@ -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) + } +} diff --git a/izapple2fyne/main.go b/izapple2fyne/main.go index db0527d..e61d5e4 100644 --- a/izapple2fyne/main.go +++ b/izapple2fyne/main.go @@ -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) + }) + } +} diff --git a/izapple2sdl/main.go b/izapple2sdl/main.go index 9d6224e..d5575d2 100644 --- a/izapple2sdl/main.go +++ b/izapple2sdl/main.go @@ -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: diff --git a/izapple2sdl/sdlKeyboard.go b/izapple2sdl/sdlKeyboard.go index 59e0cc1..edd071e 100644 --- a/izapple2sdl/sdlKeyboard.go +++ b/izapple2sdl/sdlKeyboard.go @@ -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 -} diff --git a/keyboard.go b/keyboard.go new file mode 100644 index 0000000..5591e85 --- /dev/null +++ b/keyboard.go @@ -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 +}