mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
app,app/internal/wm: introduce app.Window.Run and use it internally
app.Window implements a method for safely running functions against the underlying native window through the driverFuncs channel. However, the functions still run in a different goroutine than the one driving the native event loop, which forces the implementations in package wm to do complicated synchronization. A previous change added a mechanism to run functions in the native event loop thread. The macOS port needed this functionality, but with some care it can be generalized. That's what this change does through the new Run method. The advantage is that the thread switch dance is now confined to app.Window, with the help of a generic wm.Driver.Wakeup method. All other Driver methods can then assume they run on their event loop threads. Run is exported because it is also needed for programs that use Windows configured with CustomRenderer to control their own rendering. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -72,7 +72,10 @@ func (c *d3d11Context) Present() error {
|
||||
}
|
||||
|
||||
func (c *d3d11Context) MakeCurrent() error {
|
||||
_, width, height := c.win.HWND()
|
||||
var width, height int
|
||||
c.win.w.Run(func() {
|
||||
_, width, height = c.win.HWND()
|
||||
})
|
||||
if c.renderTarget != nil && width == c.width && height == c.height {
|
||||
c.ctx.OMSetRenderTargets(c.renderTarget, c.depthView)
|
||||
return nil
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package wm
|
||||
|
||||
/*
|
||||
#include <android/native_window_jni.h>
|
||||
#include <EGL/egl.h>
|
||||
*/
|
||||
import "C"
|
||||
@@ -35,7 +36,15 @@ func (c *context) Release() {
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.nativeWindow(c.Context.VisualID())
|
||||
var (
|
||||
win *C.ANativeWindow
|
||||
width, height int
|
||||
)
|
||||
// Run on main thread. Deadlock is avoided because MakeCurrent is only
|
||||
// called during a FrameEvent.
|
||||
c.win.callbacks.Run(func() {
|
||||
win, width, height = c.win.nativeWindow(c.Context.VisualID())
|
||||
})
|
||||
if win == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
package wm
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
@@ -34,7 +36,13 @@ func (c *glContext) Release() {
|
||||
|
||||
func (c *glContext) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.HWND()
|
||||
var (
|
||||
win windows.Handle
|
||||
width, height int
|
||||
)
|
||||
c.win.w.Run(func() {
|
||||
win, width, height = c.win.HWND()
|
||||
})
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
|
||||
@@ -43,7 +43,7 @@ func newContext(w *window) (*context, error) {
|
||||
}
|
||||
// [NSOpenGLContext setView] must run on the main thread. Fortunately,
|
||||
// newContext is only called during a [NSView draw] on the main thread.
|
||||
w.w.Func(func() {
|
||||
w.w.Run(func() {
|
||||
C.gio_setContextView(ctx, view)
|
||||
})
|
||||
c := &context{
|
||||
|
||||
@@ -42,7 +42,6 @@ import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"gioui.org/internal/f32color"
|
||||
"image"
|
||||
"image/color"
|
||||
"reflect"
|
||||
@@ -53,6 +52,8 @@ import (
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/clipboard"
|
||||
"gioui.org/io/key"
|
||||
@@ -70,24 +71,11 @@ type window struct {
|
||||
fontScale float32
|
||||
insets system.Insets
|
||||
|
||||
stage system.Stage
|
||||
started bool
|
||||
|
||||
state, newState windowState
|
||||
|
||||
// mu protects the fields following it.
|
||||
mu sync.Mutex
|
||||
win *C.ANativeWindow
|
||||
stage system.Stage
|
||||
started bool
|
||||
animating bool
|
||||
}
|
||||
|
||||
// windowState tracks the View or Activity specific state lost when Android
|
||||
// re-creates our Activity.
|
||||
type windowState struct {
|
||||
cursor *pointer.CursorName
|
||||
orientation *Orientation
|
||||
navigationColor *color.NRGBA
|
||||
statusColor *color.NRGBA
|
||||
win *C.ANativeWindow
|
||||
}
|
||||
|
||||
// gioView hold cached JNI methods for GioView.
|
||||
@@ -247,7 +235,6 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
||||
views[handle] = w
|
||||
w.loadConfig(env, class)
|
||||
w.Option(wopts.opts)
|
||||
applyStateDiff(env, view, windowState{}, w.state)
|
||||
w.setStage(system.StagePaused)
|
||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
||||
return handle
|
||||
@@ -274,7 +261,7 @@ func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.j
|
||||
func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.started = true
|
||||
if w.aNativeWindow() != nil {
|
||||
if w.win != nil {
|
||||
w.setVisible()
|
||||
}
|
||||
}
|
||||
@@ -282,18 +269,14 @@ func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.
|
||||
//export Java_org_gioui_GioView_onSurfaceDestroyed
|
||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.mu.Lock()
|
||||
w.win = nil
|
||||
w.mu.Unlock()
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||
func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
|
||||
w := views[handle]
|
||||
w.mu.Lock()
|
||||
w.win = C.ANativeWindow_fromSurface(env, surf)
|
||||
w.mu.Unlock()
|
||||
if w.started {
|
||||
w.setVisible()
|
||||
}
|
||||
@@ -323,9 +306,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
||||
if w.stage < system.StageRunning {
|
||||
return
|
||||
}
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
@@ -348,7 +329,7 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
||||
//export Java_org_gioui_GioView_onFocusChange
|
||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||
w := views[view]
|
||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||
go w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onWindowInsets
|
||||
@@ -366,8 +347,7 @@ func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C
|
||||
}
|
||||
|
||||
func (w *window) setVisible() {
|
||||
win := w.aNativeWindow()
|
||||
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
@@ -384,22 +364,15 @@ func (w *window) setStage(stage system.Stage) {
|
||||
}
|
||||
|
||||
func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) {
|
||||
win := w.aNativeWindow()
|
||||
var width, height int
|
||||
if win != nil {
|
||||
if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
if w.win != nil {
|
||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
|
||||
}
|
||||
w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
w, h := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
|
||||
width, height = int(w), int(h)
|
||||
}
|
||||
return win, width, height
|
||||
}
|
||||
|
||||
func (w *window) aNativeWindow() *C.ANativeWindow {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.win
|
||||
return w.win, width, height
|
||||
}
|
||||
|
||||
func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
|
||||
@@ -417,23 +390,16 @@ func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
if w.view == 0 {
|
||||
// View was destroyed while switching to main thread.
|
||||
return
|
||||
}
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
win := w.aNativeWindow()
|
||||
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
@@ -567,10 +533,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
if w.view == 0 {
|
||||
return
|
||||
}
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
if show {
|
||||
callVoidMethod(env, w.view, gioView.showTextInput)
|
||||
} else {
|
||||
@@ -669,7 +632,7 @@ func NewWindow(window Callbacks, opts *Options) error {
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
jstr := javaString(env, s)
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||
jvalue(android.appCtx), jvalue(jstr))
|
||||
@@ -677,71 +640,43 @@ func (w *window) WriteClipboard(s string) {
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
|
||||
jvalue(android.appCtx))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := goString(env, C.jstring(c))
|
||||
w.callbacks.Event(clipboard.Event{Text: content})
|
||||
go w.callbacks.Event(clipboard.Event{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
if o := opts.Orientation; o != nil {
|
||||
w.setState(func(state *windowState) {
|
||||
state.orientation = o
|
||||
})
|
||||
}
|
||||
if o := opts.NavigationColor; o != nil {
|
||||
w.setState(func(state *windowState) {
|
||||
state.navigationColor = o
|
||||
})
|
||||
}
|
||||
if o := opts.StatusColor; o != nil {
|
||||
w.setState(func(state *windowState) {
|
||||
state.statusColor = o
|
||||
})
|
||||
}
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
if o := opts.Orientation; o != nil {
|
||||
setOrientation(env, w.view, *o)
|
||||
}
|
||||
if o := opts.NavigationColor; o != nil {
|
||||
setNavigationColor(env, w.view, *o)
|
||||
}
|
||||
if o := opts.StatusColor; o != nil {
|
||||
setStatusColor(env, w.view, *o)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
w.setState(func(state *windowState) {
|
||||
state.cursor = &name
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
setCursor(env, w.view, name)
|
||||
})
|
||||
}
|
||||
|
||||
// setState adjust the window state on the main thread.
|
||||
func (w *window) setState(f func(state *windowState)) {
|
||||
func (w *window) Wakeup() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
f(&w.newState)
|
||||
if w.view == 0 {
|
||||
// No View attached. The state will be applied at next onCreateView.
|
||||
return
|
||||
}
|
||||
old := w.state
|
||||
state := w.newState
|
||||
applyStateDiff(env, w.view, old, state)
|
||||
w.state = state
|
||||
w.callbacks.Event(WakeupEvent{})
|
||||
})
|
||||
}
|
||||
|
||||
func applyStateDiff(env *C.JNIEnv, view C.jobject, old, state windowState) {
|
||||
if state.cursor != nil && old.cursor != state.cursor {
|
||||
setCursor(env, view, *state.cursor)
|
||||
}
|
||||
if state.orientation != nil && old.orientation != state.orientation {
|
||||
setOrientation(env, view, *state.orientation)
|
||||
}
|
||||
if state.navigationColor != nil && old.navigationColor != state.navigationColor {
|
||||
setNavigationColor(env, view, *state.navigationColor)
|
||||
}
|
||||
if state.statusColor != nil && old.statusColor != state.statusColor {
|
||||
setStatusColor(env, view, *state.statusColor)
|
||||
}
|
||||
}
|
||||
|
||||
func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
|
||||
var curID int
|
||||
switch name {
|
||||
|
||||
@@ -222,3 +222,9 @@ func windowSetCursor(from, to pointer.CursorName) pointer.CursorName {
|
||||
})
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
runOnMain(func() {
|
||||
w.w.Event(WakeupEvent{})
|
||||
})
|
||||
}
|
||||
|
||||
+11
-23
@@ -224,21 +224,17 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func() {
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
})
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
go w.w.Event(clipboard.Event{Text: content})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
runOnMain(func() {
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
})
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {}
|
||||
@@ -294,19 +290,11 @@ func (w *window) isVisible() bool {
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
if show {
|
||||
C.gio_showTextInput(w.view)
|
||||
} else {
|
||||
C.gio_hideTextInput(w.view)
|
||||
}
|
||||
C.CFRetain(v)
|
||||
runOnMain(func() {
|
||||
defer C.CFRelease(v)
|
||||
if show {
|
||||
C.gio_showTextInput(w.view)
|
||||
} else {
|
||||
C.gio_hideTextInput(w.view)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for iOS.
|
||||
|
||||
+12
-17
@@ -7,7 +7,6 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
"time"
|
||||
"unicode"
|
||||
@@ -47,7 +46,6 @@ type window struct {
|
||||
chanAnimation chan struct{}
|
||||
chanRedraw chan struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
size f32.Point
|
||||
inset f32.Point
|
||||
scale float32
|
||||
@@ -55,6 +53,7 @@ type window struct {
|
||||
// animRequested tracks whether a requestAnimationFrame callback
|
||||
// is pending.
|
||||
animRequested bool
|
||||
wakeups chan struct{}
|
||||
}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
@@ -71,6 +70,7 @@ func NewWindow(win Callbacks, opts *Options) error {
|
||||
window: js.Global().Get("window"),
|
||||
head: doc.Get("head"),
|
||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
}
|
||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||
w.browserHistory = w.window.Get("history")
|
||||
@@ -89,7 +89,7 @@ func NewWindow(win Callbacks, opts *Options) error {
|
||||
})
|
||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
content := args[0].String()
|
||||
win.Event(clipboard.Event{Text: content})
|
||||
go win.Event(clipboard.Event{Text: content})
|
||||
return nil
|
||||
})
|
||||
w.addEventListeners()
|
||||
@@ -106,6 +106,8 @@ func NewWindow(win Callbacks, opts *Options) error {
|
||||
w.draw(true)
|
||||
for {
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(WakeupEvent{})
|
||||
case <-w.chanAnimation:
|
||||
w.animCallback()
|
||||
case <-w.chanRedraw:
|
||||
@@ -349,9 +351,7 @@ func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
||||
changedTouches := e.Get("changedTouches")
|
||||
n := changedTouches.Length()
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
w.mu.Lock()
|
||||
scale := w.scale
|
||||
w.mu.Unlock()
|
||||
var mods key.Modifiers
|
||||
if e.Get("shiftKey").Bool() {
|
||||
mods |= key.ModShift
|
||||
@@ -401,9 +401,7 @@ func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
x -= rect.Get("left").Float()
|
||||
y -= rect.Get("top").Float()
|
||||
w.mu.Lock()
|
||||
scale := w.scale
|
||||
w.mu.Unlock()
|
||||
pos := f32.Point{
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
@@ -452,21 +450,17 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
||||
}
|
||||
|
||||
func (w *window) animCallback() {
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.animating = anim
|
||||
if anim && !w.animRequested {
|
||||
w.animRequested = true
|
||||
@@ -511,6 +505,13 @@ func (w *window) SetCursor(name pointer.CursorName) {
|
||||
style.Set("cursor", string(name))
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
// Run in a goroutine to avoid a deadlock if the
|
||||
// focus change result in an event.
|
||||
@@ -527,9 +528,6 @@ func (w *window) ShowTextInput(show bool) {
|
||||
func (w *window) Close() {}
|
||||
|
||||
func (w *window) resize() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
w.scale = float32(w.window.Get("devicePixelRatio").Float())
|
||||
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
@@ -570,9 +568,6 @@ func (w *window) draw(sync bool) {
|
||||
}
|
||||
|
||||
func (w *window) config() (int, int, system.Insets, unit.Metric) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
return int(w.size.X + .5), int(w.size.Y + .5), system.Insets{
|
||||
Bottom: unit.Px(w.inset.Y),
|
||||
Right: unit.Px(w.inset.X),
|
||||
|
||||
+39
-47
@@ -122,60 +122,54 @@ func (w *window) contextView() C.CFTypeRef {
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func() {
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
})
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
go w.w.Event(clipboard.Event{Text: content})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
runOnMain(func() {
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
})
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
w.runOnMain(func() {
|
||||
screenScale := float32(C.gio_getScreenBackingScale())
|
||||
cfg := configFor(screenScale)
|
||||
val := func(v unit.Value) float32 {
|
||||
return float32(cfg.Px(v)) / screenScale
|
||||
screenScale := float32(C.gio_getScreenBackingScale())
|
||||
cfg := configFor(screenScale)
|
||||
val := func(v unit.Value) float32 {
|
||||
return float32(cfg.Px(v)) / screenScale
|
||||
}
|
||||
if o := opts.Size; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
if o := opts.Size; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.MinSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setMinSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
if o := opts.MinSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setMinSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setMaxSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setMaxSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.Title; o != nil {
|
||||
title := C.CString(*o)
|
||||
defer C.free(unsafe.Pointer(title))
|
||||
C.gio_setTitle(w.window, title)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.SetWindowMode(*o)
|
||||
}
|
||||
})
|
||||
}
|
||||
if o := opts.Title; o != nil {
|
||||
title := C.CString(*o)
|
||||
defer C.free(unsafe.Pointer(title))
|
||||
C.gio_setTitle(w.window, title)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.SetWindowMode(*o)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetWindowMode(mode WindowMode) {
|
||||
@@ -212,9 +206,7 @@ func (w *window) runOnMain(f func()) {
|
||||
}
|
||||
|
||||
func (w *window) Close() {
|
||||
w.runOnMain(func() {
|
||||
C.gio_close(w.window)
|
||||
})
|
||||
C.gio_close(w.window)
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage system.Stage) {
|
||||
|
||||
@@ -179,9 +179,7 @@ type window struct {
|
||||
dead bool
|
||||
lastFrameCallback *C.struct_wl_callback
|
||||
|
||||
mu sync.Mutex
|
||||
animating bool
|
||||
opts *Options
|
||||
needAck bool
|
||||
// The most recent configure serial waiting to be ack'ed.
|
||||
serial C.uint32_t
|
||||
@@ -189,10 +187,8 @@ type window struct {
|
||||
height int
|
||||
newScale bool
|
||||
scale int
|
||||
// readClipboard tracks whether a ClipboardEvent is requested.
|
||||
readClipboard bool
|
||||
// writeClipboard is set whenever a clipboard write is requested.
|
||||
writeClipboard *string
|
||||
|
||||
wakeups chan struct{}
|
||||
}
|
||||
|
||||
type poller struct {
|
||||
@@ -319,6 +315,7 @@ func (d *wlDisplay) createNativeWindow(opts *Options) (*window, error) {
|
||||
newScale: scale != 1,
|
||||
ppdp: ppdp,
|
||||
ppsp: ppdp,
|
||||
wakeups: make(chan struct{}, 1),
|
||||
}
|
||||
w.surf = C.wl_compositor_create_surface(d.compositor)
|
||||
if w.surf == nil {
|
||||
@@ -473,10 +470,8 @@ func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
|
||||
//export gio_onXdgSurfaceConfigure
|
||||
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
||||
w := callbackLoad(data).(*window)
|
||||
w.mu.Lock()
|
||||
w.serial = serial
|
||||
w.needAck = true
|
||||
w.mu.Unlock()
|
||||
w.setStage(system.StageRunning)
|
||||
w.draw(true)
|
||||
}
|
||||
@@ -491,8 +486,6 @@ func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||
func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel, width, height C.int32_t, states *C.struct_wl_array) {
|
||||
w := callbackLoad(data).(*window)
|
||||
if width != 0 && height != 0 {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.width = int(width)
|
||||
w.height = int(height)
|
||||
w.updateOpaqueRegion()
|
||||
@@ -868,8 +861,6 @@ func (w *window) flushFling() {
|
||||
invDist := 1 / vel
|
||||
w.fling.dir.X = estx.Velocity * invDist
|
||||
w.fling.dir.Y = esty.Velocity * invDist
|
||||
// Wake up the window loop.
|
||||
w.disp.wakeup()
|
||||
}
|
||||
|
||||
//export gio_onPointerAxisSource
|
||||
@@ -897,24 +888,26 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
w.mu.Lock()
|
||||
w.readClipboard = true
|
||||
w.mu.Unlock()
|
||||
w.disp.wakeup()
|
||||
r, err := w.disp.readClipboard()
|
||||
// Send empty responses on unavailable clipboards or errors.
|
||||
if r == nil || err != nil {
|
||||
w.w.Event(clipboard.Event{})
|
||||
return
|
||||
}
|
||||
// Don't let slow clipboard transfers block event loop.
|
||||
go func() {
|
||||
defer r.Close()
|
||||
data, _ := ioutil.ReadAll(r)
|
||||
w.w.Event(clipboard.Event{Text: string(data)})
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
w.mu.Lock()
|
||||
w.writeClipboard = &s
|
||||
w.mu.Unlock()
|
||||
w.disp.wakeup()
|
||||
w.disp.writeClipboard([]byte(s))
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
w.mu.Lock()
|
||||
w.opts = opts
|
||||
w.mu.Unlock()
|
||||
w.disp.wakeup()
|
||||
w.setOptions(opts)
|
||||
}
|
||||
|
||||
func (w *window) setOptions(opts *Options) {
|
||||
@@ -1138,48 +1131,21 @@ func (w *window) loop() error {
|
||||
if err := w.disp.dispatch(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(WakeupEvent{})
|
||||
default:
|
||||
}
|
||||
if w.dead {
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
break
|
||||
}
|
||||
w.process()
|
||||
// pass false to skip unnecessary drawing.
|
||||
w.draw(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) process() {
|
||||
w.mu.Lock()
|
||||
readClipboard := w.readClipboard
|
||||
writeClipboard := w.writeClipboard
|
||||
opts := w.opts
|
||||
w.readClipboard = false
|
||||
w.writeClipboard = nil
|
||||
w.opts = nil
|
||||
w.mu.Unlock()
|
||||
if readClipboard {
|
||||
r, err := w.disp.readClipboard()
|
||||
// Send empty responses on unavailable clipboards or errors.
|
||||
if r == nil || err != nil {
|
||||
w.w.Event(clipboard.Event{})
|
||||
return
|
||||
}
|
||||
// Don't let slow clipboard transfers block event loop.
|
||||
go func() {
|
||||
defer r.Close()
|
||||
data, _ := ioutil.ReadAll(r)
|
||||
w.w.Event(clipboard.Event{Text: string(data)})
|
||||
}()
|
||||
}
|
||||
if writeClipboard != nil {
|
||||
w.disp.writeClipboard([]byte(*writeClipboard))
|
||||
}
|
||||
if opts != nil {
|
||||
w.setOptions(opts)
|
||||
}
|
||||
// pass false to skip unnecessary drawing.
|
||||
w.draw(false)
|
||||
}
|
||||
|
||||
func (d *wlDisplay) dispatch(p *poller) error {
|
||||
dispfd := C.wl_display_get_fd(d.disp)
|
||||
// Poll for events and notifications.
|
||||
@@ -1222,13 +1188,18 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
w.disp.wakeup()
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
// Wakeup wakes up the event loop through the notification pipe.
|
||||
func (d *wlDisplay) wakeup() {
|
||||
oneByte := make([]byte, 1)
|
||||
@@ -1415,12 +1386,10 @@ func (w *window) updateOutputs() {
|
||||
}
|
||||
}
|
||||
}
|
||||
w.mu.Lock()
|
||||
if found && scale != w.scale {
|
||||
w.scale = scale
|
||||
w.newScale = true
|
||||
}
|
||||
w.mu.Unlock()
|
||||
if !found {
|
||||
w.setStage(system.StagePaused)
|
||||
} else {
|
||||
@@ -1439,10 +1408,8 @@ func (w *window) config() (int, int, unit.Metric) {
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
w.flushScroll()
|
||||
w.mu.Lock()
|
||||
anim := w.animating || w.fling.anim.Active()
|
||||
dead := w.dead
|
||||
w.mu.Unlock()
|
||||
if dead || (!anim && !sync) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ type window struct {
|
||||
// placement saves the previous window position when in full screen mode.
|
||||
placement *windows.WindowPlacement
|
||||
|
||||
mu sync.Mutex
|
||||
animating bool
|
||||
|
||||
minmax winConstraints
|
||||
@@ -67,11 +66,7 @@ type window struct {
|
||||
opts *Options
|
||||
}
|
||||
|
||||
const (
|
||||
_WM_REDRAW = windows.WM_USER + iota
|
||||
_WM_CURSOR
|
||||
_WM_OPTION
|
||||
)
|
||||
const _WM_WAKEUP = windows.WM_USER + iota
|
||||
|
||||
type gpuAPI struct {
|
||||
priority int
|
||||
@@ -330,14 +325,12 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
}
|
||||
case windows.WM_SETCURSOR:
|
||||
w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
|
||||
fallthrough
|
||||
case _WM_CURSOR:
|
||||
if w.cursorIn {
|
||||
windows.SetCursor(w.cursor)
|
||||
return windows.TRUE
|
||||
}
|
||||
case _WM_OPTION:
|
||||
w.setOptions()
|
||||
case _WM_WAKEUP:
|
||||
w.w.Event(WakeupEvent{})
|
||||
}
|
||||
|
||||
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
@@ -422,9 +415,7 @@ func (w *window) loop() error {
|
||||
msg := new(windows.Msg)
|
||||
loop:
|
||||
for {
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.mu.Unlock()
|
||||
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||
w.draw(false)
|
||||
continue
|
||||
@@ -443,16 +434,11 @@ loop:
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.postRedraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) postRedraw() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil {
|
||||
func (w *window) Wakeup() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -530,18 +516,7 @@ func (w *window) readClipboard() error {
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
w.mu.Lock()
|
||||
w.opts = opts
|
||||
w.mu.Unlock()
|
||||
if err := windows.PostMessage(w.hwnd, _WM_OPTION, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setOptions() {
|
||||
w.mu.Lock()
|
||||
opts := w.opts
|
||||
w.mu.Unlock()
|
||||
if o := opts.Size; o != nil {
|
||||
dpi := windows.GetSystemDPI()
|
||||
cfg := configForDPI(dpi)
|
||||
@@ -658,8 +633,8 @@ func (w *window) SetCursor(name pointer.CursorName) {
|
||||
c = resources.cursor
|
||||
}
|
||||
w.cursor = c
|
||||
if err := windows.PostMessage(w.hwnd, _WM_CURSOR, 0, 0); err != nil {
|
||||
panic(err)
|
||||
if w.cursorIn {
|
||||
windows.SetCursor(w.cursor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-52
@@ -87,59 +87,34 @@ type x11Window struct {
|
||||
}
|
||||
dead bool
|
||||
|
||||
mu sync.Mutex
|
||||
animating bool
|
||||
opts *Options
|
||||
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
clipboard struct {
|
||||
read bool
|
||||
write *string
|
||||
content []byte
|
||||
}
|
||||
cursor pointer.CursorName
|
||||
mode WindowMode
|
||||
|
||||
wakeups chan struct{}
|
||||
}
|
||||
|
||||
func (w *x11Window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.wakeup()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) ReadClipboard() {
|
||||
w.mu.Lock()
|
||||
w.clipboard.read = true
|
||||
w.mu.Unlock()
|
||||
w.wakeup()
|
||||
C.XDeleteProperty(w.x, w.xw, w.atoms.clipboardContent)
|
||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) WriteClipboard(s string) {
|
||||
w.mu.Lock()
|
||||
w.clipboard.write = &s
|
||||
w.mu.Unlock()
|
||||
w.wakeup()
|
||||
w.clipboard.content = []byte(s)
|
||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||
}
|
||||
|
||||
func (w *x11Window) Option(opts *Options) {
|
||||
w.mu.Lock()
|
||||
w.opts = opts
|
||||
w.mu.Unlock()
|
||||
w.wakeup()
|
||||
}
|
||||
|
||||
func (w *x11Window) setOptions() {
|
||||
w.mu.Lock()
|
||||
opts := w.opts
|
||||
w.opts = nil
|
||||
w.mu.Unlock()
|
||||
if opts == nil {
|
||||
return
|
||||
}
|
||||
var shints C.XSizeHints
|
||||
if o := opts.MinSize; o != nil {
|
||||
shints.min_width = C.int(w.cfg.Px(o.Width))
|
||||
@@ -250,9 +225,6 @@ func (w *x11Window) ShowTextInput(show bool) {}
|
||||
|
||||
// Close the window.
|
||||
func (w *x11Window) Close() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
var xev C.XEvent
|
||||
ev := (*C.XClientMessageEvent)(unsafe.Pointer(&xev))
|
||||
*ev = C.XClientMessageEvent{
|
||||
@@ -270,7 +242,11 @@ func (w *x11Window) Close() {
|
||||
|
||||
var x11OneByte = make([]byte, 1)
|
||||
|
||||
func (w *x11Window) wakeup() {
|
||||
func (w *x11Window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||
}
|
||||
@@ -312,9 +288,7 @@ loop:
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
if syn = h.handleEvents(); !syn {
|
||||
w.mu.Lock()
|
||||
anim = w.animating
|
||||
w.mu.Unlock()
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
@@ -333,7 +307,6 @@ loop:
|
||||
}
|
||||
}
|
||||
}
|
||||
w.setOptions()
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, buf)
|
||||
@@ -344,6 +317,11 @@ loop:
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(WakeupEvent{})
|
||||
default:
|
||||
}
|
||||
|
||||
if anim || syn {
|
||||
w.w.Event(FrameEvent{
|
||||
@@ -358,20 +336,6 @@ loop:
|
||||
Sync: syn,
|
||||
})
|
||||
}
|
||||
w.mu.Lock()
|
||||
readClipboard := w.clipboard.read
|
||||
writeClipboard := w.clipboard.write
|
||||
w.clipboard.read = false
|
||||
w.clipboard.write = nil
|
||||
w.mu.Unlock()
|
||||
if readClipboard {
|
||||
C.XDeleteProperty(w.x, w.xw, w.atoms.clipboardContent)
|
||||
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
|
||||
}
|
||||
if writeClipboard != nil {
|
||||
w.clipboard.content = []byte(*writeClipboard)
|
||||
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
|
||||
}
|
||||
}
|
||||
w.w.Event(system.DestroyEvent{Err: nil})
|
||||
}
|
||||
@@ -681,6 +645,7 @@ func newX11Window(gioWin Callbacks, opts *Options) error {
|
||||
cfg: cfg,
|
||||
xkb: xkb,
|
||||
xkbEventBase: xkbEventBase,
|
||||
wakeups: make(chan struct{}, 1),
|
||||
}
|
||||
w.notify.read = pipe[0]
|
||||
w.notify.write = pipe[1]
|
||||
|
||||
@@ -32,6 +32,8 @@ type Options struct {
|
||||
CustomRenderer bool
|
||||
}
|
||||
|
||||
type WakeupEvent struct{}
|
||||
|
||||
type WindowMode uint8
|
||||
|
||||
const (
|
||||
@@ -59,7 +61,7 @@ type Callbacks interface {
|
||||
// Func runs a function during an Event. This is required for platforms
|
||||
// that require coordination between the rendering goroutine and the system
|
||||
// main thread.
|
||||
Func(f func())
|
||||
Run(f func())
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
@@ -99,6 +101,8 @@ type Driver interface {
|
||||
|
||||
// Close the window.
|
||||
Close()
|
||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
||||
Wakeup()
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
@@ -137,3 +141,5 @@ func newWindowRendezvous() *windowRendezvous {
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func (_ WakeupEvent) ImplementsEvent() {}
|
||||
|
||||
+94
-40
@@ -24,7 +24,7 @@ import (
|
||||
// WindowOption configures a wm.
|
||||
type Option func(opts *wm.Options)
|
||||
|
||||
// Window represents an operating system wm.
|
||||
// Window represents an operating system window.
|
||||
type Window struct {
|
||||
driver wm.Driver
|
||||
ctx wm.Context
|
||||
@@ -33,6 +33,9 @@ type Window struct {
|
||||
// driverFuncs is a channel of functions to run when
|
||||
// the Window has a valid driver.
|
||||
driverFuncs chan func()
|
||||
// wakeups wakes up the native event loop to send a
|
||||
// wm.WakeupEvent that flushes driverFuncs.
|
||||
wakeups chan struct{}
|
||||
|
||||
out chan event.Event
|
||||
in chan event.Event
|
||||
@@ -41,7 +44,8 @@ type Window struct {
|
||||
frames chan *op.Ops
|
||||
frameAck chan struct{}
|
||||
// dead is closed when the window is destroyed.
|
||||
dead chan struct{}
|
||||
dead chan struct{}
|
||||
notifyAnimate chan struct{}
|
||||
|
||||
stage system.Stage
|
||||
animating bool
|
||||
@@ -58,8 +62,7 @@ type Window struct {
|
||||
}
|
||||
|
||||
type callbacks struct {
|
||||
w *Window
|
||||
funcs chan func()
|
||||
w *Window
|
||||
}
|
||||
|
||||
// queue is an event.Queue implementation that distributes system events
|
||||
@@ -98,17 +101,18 @@ func NewWindow(options ...Option) *Window {
|
||||
}
|
||||
|
||||
w := &Window{
|
||||
in: make(chan event.Event),
|
||||
out: make(chan event.Event),
|
||||
ack: make(chan struct{}),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func()),
|
||||
dead: make(chan struct{}),
|
||||
nocontext: opts.CustomRenderer,
|
||||
in: make(chan event.Event),
|
||||
out: make(chan event.Event),
|
||||
ack: make(chan struct{}),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func(), 1),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
dead: make(chan struct{}),
|
||||
notifyAnimate: make(chan struct{}, 1),
|
||||
nocontext: opts.CustomRenderer,
|
||||
}
|
||||
w.callbacks.funcs = make(chan func())
|
||||
w.callbacks.w = w
|
||||
go w.run(opts)
|
||||
return w
|
||||
@@ -177,9 +181,9 @@ func (w *Window) processFrame(frameStart time.Time, size image.Point, frame *op.
|
||||
w.queue.q.Frame(frame)
|
||||
switch w.queue.q.TextInputState() {
|
||||
case router.TextInputOpen:
|
||||
w.driver.ShowTextInput(true)
|
||||
go w.Run(func() { w.driver.ShowTextInput(true) })
|
||||
case router.TextInputClose:
|
||||
w.driver.ShowTextInput(false)
|
||||
go w.Run(func() { w.driver.ShowTextInput(false) })
|
||||
}
|
||||
if txt, ok := w.queue.q.WriteClipboard(); ok {
|
||||
go w.WriteClipboard(txt)
|
||||
@@ -226,7 +230,7 @@ func (w *Window) Invalidate() {
|
||||
|
||||
// Option applies the options to the window.
|
||||
func (w *Window) Option(opts ...Option) {
|
||||
go w.driverDo(func() {
|
||||
go w.Run(func() {
|
||||
o := new(wm.Options)
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
@@ -239,21 +243,21 @@ func (w *Window) Option(opts ...Option) {
|
||||
// of a clipboard.Event. Multiple reads may be coalesced
|
||||
// to a single event.
|
||||
func (w *Window) ReadClipboard() {
|
||||
go w.driverDo(func() {
|
||||
go w.Run(func() {
|
||||
w.driver.ReadClipboard()
|
||||
})
|
||||
}
|
||||
|
||||
// WriteClipboard writes a string to the clipboard.
|
||||
func (w *Window) WriteClipboard(s string) {
|
||||
go w.driverDo(func() {
|
||||
go w.Run(func() {
|
||||
w.driver.WriteClipboard(s)
|
||||
})
|
||||
}
|
||||
|
||||
// SetCursorName changes the current window cursor to name.
|
||||
func (w *Window) SetCursorName(name pointer.CursorName) {
|
||||
go w.driverDo(func() {
|
||||
go w.Run(func() {
|
||||
w.driver.SetCursor(name)
|
||||
})
|
||||
}
|
||||
@@ -264,16 +268,32 @@ func (w *Window) SetCursorName(name pointer.CursorName) {
|
||||
// Currently, only macOS, Windows and X11 drivers implement this functionality,
|
||||
// all others are stubbed.
|
||||
func (w *Window) Close() {
|
||||
go w.driverDo(func() {
|
||||
go w.Run(func() {
|
||||
w.driver.Close()
|
||||
})
|
||||
}
|
||||
|
||||
// driverDo waits for the window to have a valid driver attached and calls f.
|
||||
// It does nothing if the if the window was destroyed while waiting.
|
||||
func (w *Window) driverDo(f func()) {
|
||||
// Run f in the same thread as the native window event loop, and wait for f to
|
||||
// return or the window to close. Run is guaranteed not to deadlock if it is
|
||||
// invoked during the handling of a ViewEvent, system.FrameEvent,
|
||||
// system.StageEvent; call Run in a separate goroutine to avoid deadlock in all
|
||||
// other cases.
|
||||
//
|
||||
// Note that most programs should not call Run; configuring a Window with
|
||||
// CustomRenderer is a notable exception.
|
||||
func (w *Window) Run(f func()) {
|
||||
done := make(chan struct{})
|
||||
wrapper := func() {
|
||||
f()
|
||||
close(done)
|
||||
}
|
||||
select {
|
||||
case w.driverFuncs <- f:
|
||||
case w.driverFuncs <- wrapper:
|
||||
w.wakeup()
|
||||
select {
|
||||
case <-done:
|
||||
case <-w.dead:
|
||||
}
|
||||
case <-w.dead:
|
||||
}
|
||||
}
|
||||
@@ -293,7 +313,18 @@ func (w *Window) updateAnimation() {
|
||||
}
|
||||
if animate != w.animating {
|
||||
w.animating = animate
|
||||
w.driver.SetAnimating(animate)
|
||||
select {
|
||||
case w.notifyAnimate <- struct{}{}:
|
||||
w.wakeup()
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,20 +342,39 @@ func (c *callbacks) SetDriver(d wm.Driver) {
|
||||
func (c *callbacks) Event(e event.Event) {
|
||||
select {
|
||||
case c.w.in <- e:
|
||||
for {
|
||||
select {
|
||||
case <-c.w.ack:
|
||||
return
|
||||
case f := <-c.funcs:
|
||||
f()
|
||||
}
|
||||
}
|
||||
c.w.runFuncs()
|
||||
case <-c.w.dead:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) Func(f func()) {
|
||||
c.funcs <- f
|
||||
func (w *Window) runFuncs() {
|
||||
// Flush pending runnnables.
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-w.notifyAnimate:
|
||||
w.driver.SetAnimating(w.animating)
|
||||
case f := <-w.driverFuncs:
|
||||
f()
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
// Wait for ack while running incoming runnables.
|
||||
for {
|
||||
select {
|
||||
case <-w.notifyAnimate:
|
||||
w.driver.SetAnimating(w.animating)
|
||||
case f := <-w.driverFuncs:
|
||||
f()
|
||||
case <-w.ack:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *callbacks) Run(f func()) {
|
||||
c.w.Run(f)
|
||||
}
|
||||
|
||||
func (w *Window) waitAck() {
|
||||
@@ -383,9 +433,9 @@ func (w *Window) run(opts *wm.Options) {
|
||||
return
|
||||
}
|
||||
for {
|
||||
var driverFuncs chan func()
|
||||
var wakeups chan struct{}
|
||||
if w.driver != nil {
|
||||
driverFuncs = w.driverFuncs
|
||||
wakeups = w.wakeups
|
||||
}
|
||||
var timer <-chan time.Time
|
||||
if w.delayedDraw != nil {
|
||||
@@ -398,8 +448,8 @@ func (w *Window) run(opts *wm.Options) {
|
||||
case <-w.invalidates:
|
||||
w.setNextFrame(time.Time{})
|
||||
w.updateAnimation()
|
||||
case f := <-driverFuncs:
|
||||
f()
|
||||
case <-wakeups:
|
||||
w.driver.Wakeup()
|
||||
case e := <-w.in:
|
||||
switch e2 := e.(type) {
|
||||
case system.StageEvent:
|
||||
@@ -454,6 +504,10 @@ func (w *Window) run(opts *wm.Options) {
|
||||
w.out <- e2
|
||||
w.ack <- struct{}{}
|
||||
return
|
||||
case ViewEvent:
|
||||
w.out <- e2
|
||||
w.waitAck()
|
||||
case wm.WakeupEvent:
|
||||
case event.Event:
|
||||
if w.queue.q.Queue(e2) {
|
||||
w.setNextFrame(time.Time{})
|
||||
|
||||
Reference in New Issue
Block a user