From 9395c2ffb301fa98e29e8e7427f2d04c30318c11 Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Mon, 12 Oct 2020 17:19:51 +0200 Subject: [PATCH] Joystick support in Fyne with glfw --- go.mod | 1 + izapple2fyne/fyneJoysticks.go | 109 ++++++++++++++++++++++++++++++++++ izapple2fyne/main.go | 40 ++++++++----- 3 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 izapple2fyne/fyneJoysticks.go diff --git a/go.mod b/go.mod index 3025e7e..10bd7a5 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( fyne.io/fyne v1.3.3 + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 github.com/pkg/profile v1.4.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd diff --git a/izapple2fyne/fyneJoysticks.go b/izapple2fyne/fyneJoysticks.go new file mode 100644 index 0000000..c89210f --- /dev/null +++ b/izapple2fyne/fyneJoysticks.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "runtime" + "time" + + "github.com/go-gl/glfw/v3.3/glfw" +) + +/* + Apple 2 supports four paddles and 3 pushbuttons. The first two paddles are +the X, Y axis of the first joystick. The second two correspond the the second +joystick. + Button 0 is the primary button of joystick 0. + Button 1 is the secondary button of joystick 0 but also the primary button of +joystick 1. + Button 2 is the secondary button of Joystick 1. +*/ + +// TODO: key as buttons as on the IIe and mouse as joystick + +type joystickInfo struct { + present bool + name string + paddles [2]uint8 + buttons [2]bool +} + +type joysticks struct { + info [2]*joystickInfo +} + +const unplugged = uint8(255) // Max resistance when unplugged + +func newJoysticks() *joysticks { + var j joysticks + return &j +} + +func (j *joysticks) start() { + pool := time.NewTicker(time.Second / 50) + go func() { + runtime.LockOSThread() + + for { + select { + case <-pool.C: + j.info[0] = j.queryJoystick(glfw.Joystick1) + j.info[1] = j.queryJoystick(glfw.Joystick2) + } + } + }() + +} + +func (j *joysticks) queryJoystick(joy glfw.Joystick) *joystickInfo { + if !joy.Present() { + return nil + } + + var info joystickInfo + info.name = joy.GetName() + buttons := joy.GetButtons() + for i, b := range buttons { + if b == glfw.Press { + info.buttons[i%2] = true + } + } + axes := joy.GetAxes() + for i := 0; i < len(info.paddles); i++ { + info.paddles[i] = unplugged + if i < len(axes) { + v := uint16((axes[i] + 1.0) / 2.0 * 256.0) + if v > 255 { + v = 255 + } + info.paddles[i] = uint8(v) + } + } + return &info +} + +func (j *joysticks) ReadButton(i int) bool { + var value bool + i0 := j.info[0] + i1 := j.info[1] + switch i { + case 0: + value = (i0 != nil) && i0.buttons[0] + case 1: + // It can be secondary of first or primary of second + value = ((i0 != nil) && i0.buttons[1]) || + (i1 != nil) && i1.buttons[0] + case 2: + value = (i1 != nil) && i1.buttons[0] + } + return value +} + +func (j *joysticks) ReadPaddle(i int) (uint8, bool) { + var value = unplugged + info := j.info[i/2] + if info != nil { + value = info.paddles[i%2] + } + fmt.Printf("%v, %v, %v\n", i, j.info[0], value) + return value, value != unplugged +} diff --git a/izapple2fyne/main.go b/izapple2fyne/main.go index bcd7eaa..ad9db59 100644 --- a/izapple2fyne/main.go +++ b/izapple2fyne/main.go @@ -19,7 +19,9 @@ import ( ) type state struct { - a *izapple2.Apple2 + a *izapple2.Apple2 + app fyne.App + win fyne.Window showPages bool } @@ -39,9 +41,9 @@ func main() { } func fyneRun(s *state) { - app := app.New() + s.app = app.New() // app.SetIcon(xxx) - window := app.NewWindow("iz-" + s.a.Name) + s.win = s.app.NewWindow("iz-" + s.a.Name) // window.SetIcon(xxx) bottom := widget.NewToolbar( @@ -66,23 +68,25 @@ func fyneRun(s *state) { theme.NewThemedResource(resourceLayersTripleSvg, nil), func() { s.showPages = !s.showPages if !s.showPages { - window.SetTitle("iz-" + s.a.Name) + s.win.SetTitle("iz-" + s.a.Name) } }), widget.NewToolbarAction( theme.NewThemedResource(resourceCameraSvg, nil), func() { err := izapple2.SaveSnapshot(s.a, "snapshot.png") if err != nil { - app.SendNotification(fyne.NewNotification(window.Title(), + s.app.SendNotification(fyne.NewNotification( + s.win.Title(), fmt.Sprintf("Error saving snapshoot: %v.\n.", err))) } else { - app.SendNotification(fyne.NewNotification(window.Title(), + s.app.SendNotification(fyne.NewNotification( + s.win.Title(), "Saving snapshot on 'snapshot.png'")) } }), widget.NewToolbarSpacer(), widget.NewToolbarAction(theme.ViewFullScreenIcon(), func() { - window.SetFullScreen(!window.FullScreen()) + s.win.SetFullScreen(!s.win.FullScreen()) }), ) @@ -92,10 +96,13 @@ func fyneRun(s *state) { layout.NewBorderLayout(nil, bottom, nil, nil), screen, bottom, ) - window.SetContent(container) - window.SetPadded(false) + s.win.SetContent(container) + s.win.SetPadded(false) - registerKeyboardEvents(s, window.Canvas()) + registerKeyboardEvents(s) + j := newJoysticks() + j.start() + s.a.SetJoysticksProvider(j) go s.a.Run() @@ -111,7 +118,7 @@ func fyneRun(s *state) { var img *image.RGBA if s.showPages { img = s.a.SnapshotParts() - window.SetTitle(fmt.Sprintf("%v %v %vx%v", s.a.Name, s.a.VideoModeName(), img.Rect.Dx()/2, img.Rect.Dy()/2)) + s.win.SetTitle(fmt.Sprintf("%v %v %vx%v", s.a.Name, s.a.VideoModeName(), img.Rect.Dx()/2, img.Rect.Dy()/2)) } else { img = s.a.Snapshot() } @@ -122,19 +129,20 @@ func fyneRun(s *state) { } }() - window.SetOnClosed(func() { + s.win.SetOnClosed(func() { done <- true }) - window.Show() - app.Run() + s.win.Show() + s.app.Run() } -func registerKeyboardEvents(s *state, canvas fyne.Canvas) { +func registerKeyboardEvents(s *state) { kp := newKeyboard(s) + canvas := s.win.Canvas() - // Koyboard events + // Events canvas.SetOnTypedKey(func(ke *fyne.KeyEvent) { //fmt.Printf("Event: %v\n", ke.Name) kp.putKey(ke)