diff --git a/internal/animation/animation_test.go b/internal/animation/animation_test.go index b3f64b049c..14ff5b2ed2 100644 --- a/internal/animation/animation_test.go +++ b/internal/animation/animation_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/test" ) func tick(run *Runner) { @@ -18,6 +19,7 @@ func tick(run *Runner) { } func TestGLDriver_StartAnimation(t *testing.T) { + test.NewTempApp(t) done := make(chan float32) run := &Runner{} a := &fyne.Animation{ @@ -37,6 +39,7 @@ func TestGLDriver_StartAnimation(t *testing.T) { } func TestGLDriver_StopAnimation(t *testing.T) { + test.NewTempApp(t) done := make(chan float32) run := &Runner{} a := &fyne.Animation{ @@ -54,12 +57,13 @@ func TestGLDriver_StopAnimation(t *testing.T) { t.Error("animation was not ticked") } run.Stop(a) - run.animationMutex.RLock() + run.animationMutex.Lock() assert.Zero(t, len(run.animations)) - run.animationMutex.RUnlock() + run.animationMutex.Unlock() } func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) { + test.NewTempApp(t) var wg sync.WaitGroup run := &Runner{} @@ -107,7 +111,7 @@ func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) { wg.Wait() // animations stopped inside tick are really stopped in the next runner cycle time.Sleep(time.Second/60 + 100*time.Millisecond) - run.animationMutex.RLock() + run.animationMutex.Lock() assert.Zero(t, len(run.animations)) - run.animationMutex.RUnlock() + run.animationMutex.Unlock() } diff --git a/internal/animation/runner.go b/internal/animation/runner.go index 0acdb71776..f8ca9d0f55 100644 --- a/internal/animation/runner.go +++ b/internal/animation/runner.go @@ -11,7 +11,7 @@ import ( type Runner struct { // animationMutex synchronizes access to `animations` and `pendingAnimations` // between the runner goroutine and calls to Start and Stop - animationMutex sync.RWMutex + animationMutex sync.Mutex // animations is the list of animations that are being ticked in the current frame animations []*anim @@ -33,7 +33,7 @@ type Runner struct { // Start will register the passed application and initiate its ticking. func (r *Runner) Start(a *fyne.Animation) { r.animationMutex.Lock() - defer r.animationMutex.Unlock() + hadAnimations := len(r.pendingAnimations) > 0 || len(r.animations) > 0 if !r.runnerStarted { r.runnerStarted = true @@ -51,6 +51,21 @@ func (r *Runner) Start(a *fyne.Animation) { } r.pendingAnimations = append(r.pendingAnimations, newAnim(a)) } + r.animationMutex.Unlock() + + if !hadAnimations { + // wake up main thread if needed to begin running animations + if drv, ok := fyne.CurrentApp().Driver().(interface{ WakeUp() }); ok { + drv.WakeUp() + } + } +} + +func (r *Runner) HasAnimations() bool { + r.animationMutex.Lock() + defer r.animationMutex.Unlock() + + return len(r.pendingAnimations) > 0 || len(r.animations) > 0 } // Stop causes an animation to stop ticking (if it was still running) and removes it from the runner. @@ -86,18 +101,19 @@ func (r *Runner) Stop(a *fyne.Animation) { // TickAnimations progresses all running animations by one tick. // This will be called from the driver to update objects immediately before next paint. -func (r *Runner) TickAnimations() { +func (r *Runner) TickAnimations() (done bool) { if !r.runnerStarted { - return + return true } - done := r.runOneFrame() + done = r.runOneFrame() if done { r.animationMutex.Lock() r.runnerStarted = false r.animationMutex.Unlock() } + return done } func (r *Runner) runOneFrame() (done bool) { diff --git a/internal/driver/common/canvas.go b/internal/driver/common/canvas.go index ed0e185c3b..9e3df21078 100644 --- a/internal/driver/common/canvas.go +++ b/internal/driver/common/canvas.go @@ -349,6 +349,13 @@ func (c *Canvas) CheckDirtyAndClear() bool { // SetDirty sets canvas dirty flag atomically. func (c *Canvas) SetDirty() { c.dirty = true + + // wake up main thread in case we were called from goroutine + // TODO: hide this behind the migration flag introduced in + // https://github.com/fyne-io/fyne/pull/5425 + if drv, ok := fyne.CurrentApp().Driver().(interface{ WakeUp() }); ok { + drv.WakeUp() + } } // SetMenuTreeAndFocusMgr sets menu tree and focus manager. diff --git a/internal/driver/common/ringbuffer.go b/internal/driver/common/ringbuffer.go new file mode 100644 index 0000000000..04b9713aa4 --- /dev/null +++ b/internal/driver/common/ringbuffer.go @@ -0,0 +1,81 @@ +package common + +// RingBuffer is a growable ring buffer supporting +// enqueue and dequeue operations. It is not thread safe. +type RingBuffer[T any] struct { + buf []T + head int + len int +} + +// NewRingBuffer initializes and returns a new RingBuffer. +func NewRingBuffer[T any](initialCap int) RingBuffer[T] { + return RingBuffer[T]{ + buf: make([]T, initialCap), + } +} + +// Len returns the number of elements in the buffer. +func (r *RingBuffer[T]) Len() int { + return r.len +} + +// Push adds the value to the end of the buffer. +func (r *RingBuffer[T]) Push(value T) { + r.checkGrow() + + pos := (r.head + r.len) % len(r.buf) + r.buf[pos] = value + r.len++ +} + +// Pull removes the first item from the buffer, if any. +func (r *RingBuffer[T]) Pull() (value T, ok bool) { + if r.len == 0 { + return value, false + } + return r.pullOne(), true +} + +// PullN removes up to len(buf) items from the queue, +// copying them into the supplied buffer and returning +// the number of elements copied. +func (r *RingBuffer[T]) PullN(buf []T) int { + l := len(buf) + if r.len < l { + l = r.len + } + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + buf[i] = r.pullOne() + } + return l +} + +func (r *RingBuffer[T]) pullOne() (value T) { + emptyT := value + value = r.buf[r.head] + r.buf[r.head] = emptyT + + if r.len == 1 { + r.head = 0 + } else { + r.head = (r.head + 1) % len(r.buf) + } + r.len-- + + return value +} + +func (r *RingBuffer[T]) checkGrow() { + if l := len(r.buf); r.len == l { + newBuf := make([]T, l*2) + for i := 0; i < r.len; i++ { + newBuf[i] = r.buf[(r.head+i)%l] + } + r.head = 0 + r.buf = newBuf + } +} diff --git a/internal/driver/common/ringbuffer_test.go b/internal/driver/common/ringbuffer_test.go new file mode 100644 index 0000000000..323c67162d --- /dev/null +++ b/internal/driver/common/ringbuffer_test.go @@ -0,0 +1,46 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_RingBuffer(t *testing.T) { + buf := NewRingBuffer[int](8) + + assertDequeue := func(expect int) { + t.Helper() + got, valid := buf.Pull() + assert.True(t, valid, "got invalid pull") + assert.Equal(t, expect, got, "invalid pull result") + } + + buf.Push(1) + buf.Push(2) + buf.Push(3) + buf.Push(4) + buf.Push(5) + buf.Push(6) + buf.Push(7) + buf.Push(8) + buf.Push(9) + buf.Push(10) + + assertDequeue(1) + assertDequeue(2) + + buf.Push(11) + buf.Push(12) + buf.Push(13) + buf.Push(14) + buf.Push(15) + buf.Push(16) + buf.Push(17) + buf.Push(18) + buf.Push(19) + + for i := 3; i <= 19; i++ { + assertDequeue(i) + } +} diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index 9a862783e8..9d60923ced 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -28,7 +28,6 @@ var _ fyne.Driver = (*gLDriver)(nil) type gLDriver struct { windows []fyne.Window - done chan struct{} animation animation.Runner @@ -101,7 +100,7 @@ func (d *gLDriver) Quit() { // Only call close once to avoid panic. if running.CompareAndSwap(true, false) { - close(d.done) + d.WakeUp() } } @@ -167,7 +166,5 @@ func (d *gLDriver) SetDisableScreenBlanking(disable bool) { func NewGLDriver() *gLDriver { repository.Register("file", intRepo.NewFileRepository()) - return &gLDriver{ - done: make(chan struct{}), - } + return &gLDriver{} } diff --git a/internal/driver/glfw/event.go b/internal/driver/glfw/event.go new file mode 100644 index 0000000000..ea13cd07a3 --- /dev/null +++ b/internal/driver/glfw/event.go @@ -0,0 +1,130 @@ +package glfw + +import ( + "fyne.io/fyne/v2/internal/driver/common" +) + +type eventType int + +const ( + emptyEvent eventType = iota + moveEvent + closeEvent + mouseMoveEvent + mouseClickEvent + mouseScrollEvent + keyPressEvent + charInputEvent + focusEvent + dropEvent +) + +// union of different GLFW events we receive +type event struct { + eventType eventType + + window *window + + intArg1 int + intArg2 int + intArg3 int + intArg4 int + + float64Arg1 float64 + float64Arg2 float64 +} + +var eventQueue = common.NewRingBuffer[event](1024) + +func newCloseEvent(win *window) event { + return event{eventType: closeEvent, window: win} +} + +func newMoveEvent(win *window, x, y int) event { + return event{ + eventType: moveEvent, + window: win, + intArg1: x, + intArg2: y, + } +} + +func newMouseMoveEvent(win *window, xpos, ypos float64) event { + return event{ + eventType: mouseMoveEvent, + window: win, + float64Arg1: xpos, + float64Arg2: ypos, + } +} + +func newMouseClickEvent(win *window, btn, action, mods int) event { + return event{ + eventType: mouseClickEvent, + window: win, + intArg1: btn, + intArg2: action, + intArg3: mods, + } +} + +func newMouseScrollEvent(win *window, xoff, yoff float64) event { + return event{ + eventType: mouseScrollEvent, + window: win, + float64Arg1: xoff, + float64Arg2: yoff, + } +} + +func newKeyPressEvent(win *window, key, scancode, action, mods int) event { + return event{ + eventType: keyPressEvent, + window: win, + intArg1: key, + intArg2: scancode, + intArg3: action, + intArg4: mods, + } +} + +func newCharInputEvent(win *window, char rune) event { + return event{ + eventType: charInputEvent, + window: win, + intArg1: int(char), + } +} + +func newFocusEvent(win *window, focused bool) event { + f := 0 + if focused { + f = 1 + } + return event{ + eventType: focusEvent, + window: win, + intArg1: f, + } +} + +func processEvent(e event) { + switch e.eventType { + case closeEvent: + e.window.closed(e.window.viewport) + case moveEvent: + e.window.moved(e.window.viewport, e.intArg1, e.intArg2) + case mouseMoveEvent: + e.window.processMouseMoved(e.float64Arg1, e.float64Arg2) + case mouseClickEvent: + e.window.mouseClicked(e.window.viewport, e.intArg1, e.intArg2, e.intArg3) + case mouseScrollEvent: + e.window.mouseScrolled(e.window.viewport, e.float64Arg1, e.float64Arg2) + case keyPressEvent: + e.window.keyPressed(e.window.viewport, e.intArg1, e.intArg2, e.intArg3, e.intArg4) + case charInputEvent: + e.window.charInput(e.window.viewport, rune(e.intArg1)) + case focusEvent: + e.window.focused(e.window.viewport, e.intArg1 > 0) + } +} diff --git a/internal/driver/glfw/funcqueue.go b/internal/driver/glfw/funcqueue.go new file mode 100644 index 0000000000..cebae86175 --- /dev/null +++ b/internal/driver/glfw/funcqueue.go @@ -0,0 +1,60 @@ +package glfw + +import ( + "sync" + + "fyne.io/fyne/v2/internal/driver/common" +) + +type funcData struct { + f func() + done chan struct{} // Zero allocation signalling channel +} + +// glfwFuncQueue is a queue to hold funcs posted to +// execute on the main thread +type glfwFuncQueue struct { + mutex sync.Mutex + + buffer common.RingBuffer[funcData] +} + +func newGlfwFuncQueue() *glfwFuncQueue { + return &glfwFuncQueue{ + buffer: common.NewRingBuffer[funcData](1024), + } +} + +// Push adds a new func to the queue. It wakes up +// the driver's main thread if necessary to begin +// processing queued functions. +func (g *glfwFuncQueue) Push(f func(), wait bool) { + g.mutex.Lock() + data := funcData{f: f} + if wait { + done := common.DonePool.Get() + defer common.DonePool.Put(done) + + data.done = done + } + g.buffer.Push(data) + l := g.buffer.Len() + g.mutex.Unlock() + + if l == 1 { + // only need to wake up driver if there + // were previously no funcs queued. + // otherwise, it has already been woken. + wakeUpDriver() + } + if wait { + <-data.done + } +} + +// PullN pulls up to N funcs from the queue. +func (g *glfwFuncQueue) PullN(buf []funcData) int { + g.mutex.Lock() + defer g.mutex.Unlock() + return g.buffer.PullN(buf) +} diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index eb936f1538..36593cbe9c 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -15,13 +15,8 @@ import ( "fyne.io/fyne/v2/internal/scale" ) -type funcData struct { - f func() - done chan struct{} // Zero allocation signalling channel -} - -// channel for queuing functions on the main thread -var funcQueue = async.NewUnboundedChan[funcData]() +// for queuing functions on the main thread +var funcQueue = newGlfwFuncQueue() var running atomic.Bool var initOnce = &sync.Once{} @@ -45,15 +40,7 @@ func runOnMainWithWait(f func(), wait bool) { return } - if wait { - done := common.DonePool.Get() - defer common.DonePool.Put(done) - - funcQueue.In() <- funcData{f: f, done: done} - <-done - } else { - funcQueue.In() <- funcData{f: f} - } + funcQueue.Push(f, wait) } // Preallocate to avoid allocations on every drawSingleFrame. @@ -134,53 +121,13 @@ func (d *gLDriver) runGL() { f() } - eventTick := time.NewTicker(time.Second / 60) for { - select { - case <-d.done: - eventTick.Stop() - d.Terminate() - l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle) - if f := l.OnStopped(); f != nil { - l.QueueEvent(f) - } - return - case f := <-funcQueue.Out(): - f.f() - if f.done != nil { - f.done <- struct{}{} - } - case <-eventTick.C: - d.pollEvents() - for i := 0; i < len(d.windows); i++ { - w := d.windows[i].(*window) - if w.viewport == nil { - continue - } - - if w.viewport.ShouldClose() { - d.destroyWindow(w, i) - i-- // Trailing windows are moved forward one step. - continue - } - - expand := w.shouldExpand - fullScreen := w.fullScreen - - if expand && !fullScreen { - w.fitContent() - shouldExpand := w.shouldExpand - w.shouldExpand = false - view := w.viewport - - if shouldExpand && runtime.GOOS != "js" { - view.SetSize(w.shouldWidth, w.shouldHeight) - } - } - } + // idle until events (or d.wakeUp()) received + d.waitEvents() - d.animation.TickAnimations() - d.drawSingleFrame() + // run active loop until we are exiting, or idle again + if exit := d.runActiveLoop(); exit { + return } } } @@ -201,6 +148,92 @@ func (d *gLDriver) destroyWindow(w *window, index int) { } } +// runs a loop that invokes runSingleFrame at 60 fps until there are no more +// animations or events. uses pollEvents to check for events every frame. +func (d *gLDriver) runActiveLoop() bool { + t := time.NewTicker(time.Second / 60) + defer t.Stop() + for range t.C { + d.pollEvents() + exit, idle := d.runSingleFrame() + if exit { + return exit + } else if idle { + break + } + } + return false +} + +func (d *gLDriver) runSingleFrame() (exit, idle bool) { + // check if we're shutting down + if !running.Load() { + d.Terminate() + l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle) + if f := l.OnStopped(); f != nil { + l.QueueEvent(f) + } + return true, false + } + + // process received glfw events + noEvents := true + for { + evt, ok := eventQueue.Pull() + if !ok { + break + } + noEvents = false + processEvent(evt) + } + + // run funcs queued to main + var buf [16]funcData + funcsDone := false + for !funcsDone { + n := funcQueue.PullN(buf[:]) + for i := 0; i < n; i++ { + buf[i].f() + if buf[i].done != nil { + buf[i].done <- struct{}{} + } + buf[i] = funcData{} + } + funcsDone = n == 0 + } + + for i := 0; i < len(d.windows); i++ { + w := d.windows[i].(*window) + if w.viewport == nil { + continue + } + + if w.viewport.ShouldClose() { + d.destroyWindow(w, i) + i-- // Trailing windows are moved forward one position. + continue + } + + expand := w.shouldExpand + fullScreen := w.fullScreen + + if expand && !fullScreen { + w.fitContent() + shouldExpand := w.shouldExpand + w.shouldExpand = false + view := w.viewport + + if shouldExpand && runtime.GOOS != "js" { + view.SetSize(w.shouldWidth, w.shouldHeight) + } + } + } + animationsDone := d.animation.TickAnimations() + d.drawSingleFrame() + + return false, animationsDone && noEvents +} + func (d *gLDriver) repaintWindow(w *window) bool { canvas := w.canvas freed := false diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index 83a46788b7..3fff74a03f 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -24,6 +24,20 @@ func (d *gLDriver) pollEvents() { glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called } +func (d *gLDriver) waitEvents() { + glfw.WaitEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called +} + func (d *gLDriver) Terminate() { glfw.Terminate() } + +// WakeUp tells the driver to wake up from an idle state. +func (d *gLDriver) WakeUp() { + wakeUpDriver() +} + +// wakeUpDriver wakes up the driver but in the case a reference to it is not easily available. +func wakeUpDriver() { + glfw.PostEmptyEvent() +} diff --git a/internal/driver/glfw/loop_wasm.go b/internal/driver/glfw/loop_wasm.go index 37659c4b5b..24dc8abbf8 100644 --- a/internal/driver/glfw/loop_wasm.go +++ b/internal/driver/glfw/loop_wasm.go @@ -23,6 +23,20 @@ func (d *gLDriver) pollEvents() { glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called } +func (d *gLDriver) waitEvents() { + glfw.WaitEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called +} + func (d *gLDriver) Terminate() { glfw.Terminate() } + +// WakeUp tells the driver to wake up from an idle state. +func (d *gLDriver) WakeUp() { + wakeUpDriver() +} + +// wakeUpDriver wakes up the driver but in the case a reference to it is not easily available. +func wakeUpDriver() { + glfw.PostEmptyEvent() +} diff --git a/internal/driver/glfw/window_desktop.go b/internal/driver/glfw/window_desktop.go index 21a9822208..982cc3504a 100644 --- a/internal/driver/glfw/window_desktop.go +++ b/internal/driver/glfw/window_desktop.go @@ -385,9 +385,9 @@ func (w *window) mouseMoved(_ *glfw.Window, xpos, ypos float64) { w.processMouseMoved(xpos, ypos) } -func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { - button, modifiers := convertMouseButton(btn, mods) - mouseAction := convertAction(action) +func (w *window) mouseClicked(_ *glfw.Window, btn int, action int, mods int) { + button, modifiers := convertMouseButton(glfw.MouseButton(btn), glfw.ModifierKey(mods)) + mouseAction := convertAction(glfw.Action(action)) w.processMouseClicked(button, mouseAction, modifiers) } @@ -618,12 +618,15 @@ func convertASCII(key glfw.Key) fyne.KeyName { return fyne.KeyName(rune(key)) } -func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { - keyName := keyToName(key, scancode) - keyDesktopModifier := desktopModifier(mods) - w.driver.currentKeyModifiers = desktopModifierCorrected(mods, key, action) - keyAction := convertAction(action) - keyASCII := convertASCII(key) +func (w *window) keyPressed(_ *glfw.Window, key int, scancode int, action int, mods int) { + k := glfw.Key(key) + a := glfw.Action(action) + m := glfw.ModifierKey(mods) + keyName := keyToName(k, scancode) + keyDesktopModifier := desktopModifier(m) + w.driver.currentKeyModifiers = desktopModifierCorrected(m, k, a) + keyAction := convertAction(a) + keyASCII := convertASCII(k) w.processKeyPressed(keyName, keyASCII, scancode, keyAction, keyDesktopModifier) } @@ -758,18 +761,39 @@ func (w *window) create() { w.setDarkMode() - win.SetCloseCallback(w.closed) - win.SetPosCallback(w.moved) + // window size and refresh callbacks are processed + // synchronously since GLFW does not return from + // waitEvents until a resize is completed win.SetSizeCallback(w.resized) win.SetFramebufferSizeCallback(w.frameSized) win.SetRefreshCallback(w.refresh) win.SetContentScaleCallback(w.scaled) - win.SetCursorPosCallback(w.mouseMoved) - win.SetMouseButtonCallback(w.mouseClicked) - win.SetScrollCallback(w.mouseScrolled) - win.SetKeyCallback(w.keyPressed) - win.SetCharCallback(w.charInput) - win.SetFocusCallback(w.focused) + + // rest of callbacks queue events to be handled in main loop + win.SetCloseCallback(func(_ *glfw.Window) { + eventQueue.Push(newCloseEvent(w)) + }) + win.SetPosCallback(func(_ *glfw.Window, xpos, ypos int) { + eventQueue.Push(newMoveEvent(w, xpos, ypos)) + }) + win.SetCursorPosCallback(func(_ *glfw.Window, xpos, ypos float64) { + eventQueue.Push(newMouseMoveEvent(w, xpos, ypos)) + }) + win.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newMouseClickEvent(w, int(button), int(action), int(mods))) + }) + win.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { + eventQueue.Push(newMouseScrollEvent(w, xoff, yoff)) + }) + win.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newKeyPressEvent(w, int(key), scancode, int(action), int(mods))) + }) + win.SetCharCallback(func(_ *glfw.Window, char rune) { + eventQueue.Push(newCharInputEvent(w, char)) + }) + win.SetFocusCallback(func(_ *glfw.Window, focused bool) { + eventQueue.Push(newFocusEvent(w, focused)) + }) w.canvas.detectedScale = w.detectScale() w.canvas.scale = w.calculatedScale() diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 5e9fffff2f..05dfe8f183 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -368,7 +368,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on secondary mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -378,7 +378,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag end event on secondary mouseUp - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.Nil(t, d1.popDragEndEvent()) assert.Nil(t, d2.popDragEndEvent()) @@ -388,7 +388,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on secondary mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -398,7 +398,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag end event on secondary mouseUp - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.Nil(t, d1.popDragEndEvent()) assert.Nil(t, d2.popDragEndEvent()) @@ -408,7 +408,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -451,7 +451,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // drag end event on mouseUp - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, d1.popDragEvent()) assert.NotNil(t, d1.popDragEndEvent()) assert.Nil(t, d2.popDragEvent()) @@ -462,7 +462,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -492,7 +492,7 @@ func TestWindow_DragObjectThatMoves(t *testing.T) { // drag -1,-1 w.mouseMoved(w.viewport, 12, 12) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 10, 10) assert.Equal(t, &fyne.DragEvent{ @@ -534,9 +534,9 @@ func TestWindow_DragIntoNewObjectKeepingFocus(t *testing.T) { // drag from d1 into d2 w.mouseMoved(w.viewport, 11, 11) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 21, 11) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) // we should only have 2 mouse events on d1 assert.Equal(t, @@ -573,9 +573,9 @@ func TestWindow_NoDragEndWithoutDraggedEvent(t *testing.T) { w.mouseMoved(w.viewport, 9, 9) // mouse down (potential drag) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) // mouse release without move (not really a drag) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, do.popDragEvent(), "no drag event without move") assert.Nil(t, do.popDragEndEvent(), "no drag end event without drag event") @@ -598,7 +598,7 @@ func TestWindow_HoverableOnDragging(t *testing.T) { AbsolutePosition: fyne.NewPos(10, 10)}}, dh.popMouseInEvent(), ) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 12, 12) assert.Equal(t, &fyne.DragEvent{ @@ -636,16 +636,16 @@ func TestWindow_HoverableOnDragging(t *testing.T) { assert.Nil(t, dh.popMouseMovedEvent()) // no hover events on end of drag event - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, dh.popMouseInEvent()) assert.Nil(t, dh.popMouseMovedEvent()) assert.Nil(t, dh.popMouseOutEvent()) // mouseOut on mouse release after dragging out of area w.mouseMoved(w.viewport, 12, 12) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 28, 12) // outside the 20x20 object - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.NotNil(t, dh.popMouseOutEvent()) }) } @@ -692,7 +692,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { // - no events by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 8, 8) assert.Nil(t, h.popMouseInEvent()) assert.Equal(t, &desktop.MouseEvent{PointEvent: fyne.PointEvent{Position: fyne.NewPos(4, 4), @@ -705,7 +705,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { assert.Nil(t, dh.popMouseOutEvent()) assert.Nil(t, dh.popDragEvent()) assert.Nil(t, dh.popDragEndEvent()) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) }) // 2. move over to draggableObject and verify @@ -732,7 +732,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { // - drag begin by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 18, 18) assert.Nil(t, h.popMouseInEvent()) assert.Equal(t, &desktop.MouseEvent{PointEvent: fyne.PointEvent{Position: fyne.NewPos(14, 14), @@ -753,7 +753,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { // - drag end by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) assert.Nil(t, h.popMouseOutEvent()) @@ -882,7 +882,7 @@ func TestWindow_HoverableUnderDraggable_DragAcross(t *testing.T) { // - drag begin by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 18, 18) assert.Nil(t, h.popMouseInEvent()) assert.Equal(t, &desktop.MouseEvent{PointEvent: fyne.PointEvent{Position: fyne.NewPos(14, 14), @@ -943,7 +943,7 @@ func TestWindow_HoverableUnderDraggable_DragAcross(t *testing.T) { // - drag end by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) assert.Nil(t, h.popMouseOutEvent()) @@ -998,7 +998,7 @@ func TestWindow_HoverableUnderDraggable_Drag_draggableHoverable(t *testing.T) { // - no events by draggableObject // - drag begin by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 30, 30) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) @@ -1038,7 +1038,7 @@ func TestWindow_HoverableUnderDraggable_Drag_draggableHoverable(t *testing.T) { // - no events by draggableObject // - drag end by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) assert.Nil(t, h.popMouseOutEvent()) @@ -1065,14 +1065,14 @@ func TestWindow_DragEndWithoutTappedEvent(t *testing.T) { runOnMain(func() { w.mouseMoved(w.viewport, 11, 11) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 10, 10) // Less than drag threshold - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.NotNil(t, do.popTapEvent()) // it was slight drag, so call it a tap w.mouseMoved(w.viewport, 7, 7) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, do.popTapEvent()) }) @@ -1105,8 +1105,8 @@ func TestWindow_Tapped(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(50, 160) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, o.popSecondaryTapEvent(), "no secondary tap") if e, _ := o.popTapEvent().(*fyne.PointEvent); assert.NotNil(t, e, "tapped") { @@ -1124,8 +1124,8 @@ func TestWindow_TappedSecondary(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(50, 60) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.Nil(t, o.popTapEvent(), "no primary tap") if e, _ := o.popSecondaryTapEvent().(*fyne.PointEvent); assert.NotNil(t, e, "tapped secondary") { @@ -1146,13 +1146,13 @@ func TestWindow_TappedSecondary_OnPrimaryOnlyTarget(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(10, 25) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.False(t, tapped) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.True(t, tapped) }) @@ -1180,14 +1180,14 @@ func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(10, 80) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.False(t, tapped, "Tapped button that was clipped") w.mousePos = fyne.NewPos(10, 120) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.True(t, tapped, "Tapped button that was clipped") }) @@ -1202,20 +1202,20 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { runOnMain(func() { w.mouseMoved(w.viewport, 17, 27) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 1, tapped, "Button 1 should be tapped") tapped = 0 - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 17, 59) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 0, tapped, "button was tapped without mouse press & release on it %d", tapped) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 2, tapped, "Button 2 should be tapped") }) @@ -1238,19 +1238,19 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) { but.Resize(fyne.NewSquareSize(50)) w.mouseMoved(w.viewport, 15, 25) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) }) <-waitSingleTapped time.Sleep(d.DoubleTapDelay()) // reset runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) time.Sleep(time.Millisecond * 100) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) }) mustBeBefore := time.NewTimer(d.DoubleTapDelay()) @@ -1355,7 +1355,7 @@ func TestWindow_MouseEventContainsModifierKeys(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { require.Nil(t, m.popMouseEvent(), "no initial mouse event") - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, tt.modifier) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), int(tt.modifier)) me, _ := m.popMouseEvent().(*desktop.MouseEvent) if assert.NotNil(t, me, "mouse event triggered") { @@ -1597,13 +1597,13 @@ func TestWindow_ManualFocus(t *testing.T) { runOnMain(func() { w.mouseMoved(w.viewport, 9, 9) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 0, content.unfocusedTimes) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 0, content.unfocusedTimes) }) @@ -1630,7 +1630,7 @@ func TestWindow_ManualFocus(t *testing.T) { assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 1, content.unfocusedTimes) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 1, content.unfocusedTimes) }) @@ -2109,6 +2109,6 @@ func (s *safeWindow) charInput(viewport *glfw.Window, char rune) { func (s *safeWindow) keyPressed(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { runOnMain(func() { - s.window.keyPressed(w, key, scancode, action, mods) + s.window.keyPressed(w, int(key), scancode, int(action), int(mods)) }) } diff --git a/internal/driver/glfw/window_wasm.go b/internal/driver/glfw/window_wasm.go index 44470a1c62..2119cf4d0e 100644 --- a/internal/driver/glfw/window_wasm.go +++ b/internal/driver/glfw/window_wasm.go @@ -189,12 +189,15 @@ func (w *window) setCustomCursor(rawCursor *Cursor, isCustomCursor bool) { } func (w *window) mouseMoved(_ *glfw.Window, xpos, ypos float64) { - w.processMouseMoved(w.scaleInput(xpos), w.scaleInput(ypos)) + w.processMouseMoved(xpos, ypos) } -func (w *window) mouseClicked(viewport *glfw.Window, btn glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { - button, modifiers := convertMouseButton(btn, mods) - mouseAction := convertAction(action) +func (w *window) scaled(_ *glfw.Window, x, y float32) { +} + +func (w *window) mouseClicked(viewport *glfw.Window, btn int, action int, mods int) { + button, modifiers := convertMouseButton(glfw.MouseButton(btn), glfw.ModifierKey(mods)) + mouseAction := convertAction(glfw.Action(action)) w.processMouseClicked(button, mouseAction, modifiers) } @@ -424,11 +427,14 @@ func convertASCII(key glfw.Key) fyne.KeyName { return fyne.KeyName(rune(key)) } -func (w *window) keyPressed(viewport *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { - keyName := keyToName(key, scancode) - keyDesktopModifier := desktopModifier(mods) - keyAction := convertAction(action) - keyASCII := convertASCII(key) +func (w *window) keyPressed(viewport *glfw.Window, key int, scancode int, action int, mods int) { + k := glfw.Key(key) + a := glfw.Action(action) + m := glfw.ModifierKey(mods) + keyName := keyToName(k, scancode) + keyDesktopModifier := desktopModifier(m) + keyAction := convertAction(a) + keyASCII := convertASCII(k) w.processKeyPressed(keyName, keyASCII, scancode, keyAction, keyDesktopModifier) } @@ -516,17 +522,38 @@ func (w *window) create() { w.setDarkMode() - win.SetCloseCallback(w.closed) - win.SetPosCallback(w.moved) + // window size and refresh callbacks are processed + // synchronously since GLFW does not return from + // waitEvents until a resize is completed win.SetSizeCallback(w.resized) win.SetFramebufferSizeCallback(w.frameSized) win.SetRefreshCallback(w.refresh) - win.SetCursorPosCallback(w.mouseMoved) - win.SetMouseButtonCallback(w.mouseClicked) - win.SetScrollCallback(w.mouseScrolled) - win.SetKeyCallback(w.keyPressed) - win.SetCharCallback(w.charInput) - win.SetFocusCallback(w.focused) + + // rest of callbacks queue events to be handled in main loop + win.SetCloseCallback(func(_ *glfw.Window) { + eventQueue.Push(newCloseEvent(w)) + }) + win.SetPosCallback(func(_ *glfw.Window, xpos, ypos int) { + eventQueue.Push(newMoveEvent(w, xpos, ypos)) + }) + win.SetCursorPosCallback(func(_ *glfw.Window, xpos, ypos float64) { + eventQueue.Push(newMouseMoveEvent(w, xpos, ypos)) + }) + win.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newMouseClickEvent(w, int(button), int(action), int(mods))) + }) + win.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { + eventQueue.Push(newMouseScrollEvent(w, xoff, yoff)) + }) + win.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newKeyPressEvent(w, int(key), scancode, int(action), int(mods))) + }) + win.SetCharCallback(func(_ *glfw.Window, char rune) { + eventQueue.Push(newCharInputEvent(w, char)) + }) + win.SetFocusCallback(func(_ *glfw.Window, focused bool) { + eventQueue.Push(newFocusEvent(w, focused)) + }) w.canvas.detectedScale = w.detectScale() w.canvas.scale = w.calculatedScale() @@ -669,7 +696,3 @@ func (w *wrapInner) updateVisibility() { c.Overlays().Remove(multi) } } - -func (w *window) scaleInput(in float64) float64 { - return in -}