mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 10:25:37 +00:00
app: prepare Window for removal of Main and asynchronous FrameEvents
This is mostly a refactor, but there are two user-visible effects: - Window.NextEvent may be called even after DestroyEvent is returned. - Window.Invalidate always wakes up a blocking NextEvent, even when a FrameEvent cannot be generated. As a nice side-effect, X11, Wayland and Wasm no longer require separate goroutines for their window loops. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+1
-1
@@ -65,8 +65,8 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
|||||||
private final InputMethodManager imm;
|
private final InputMethodManager imm;
|
||||||
private final float scrollXScale;
|
private final float scrollXScale;
|
||||||
private final float scrollYScale;
|
private final float scrollYScale;
|
||||||
|
private final AccessibilityManager accessManager;
|
||||||
private int keyboardHint;
|
private int keyboardHint;
|
||||||
private AccessibilityManager accessManager;
|
|
||||||
|
|
||||||
private long nhandle;
|
private long nhandle;
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -69,7 +69,17 @@ func (c *wlContext) Refresh() error {
|
|||||||
}
|
}
|
||||||
c.eglWin = eglWin
|
c.eglWin = eglWin
|
||||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||||
return c.Context.CreateSurface(eglSurf, width, height)
|
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Context.MakeCurrent(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Context.ReleaseCurrent()
|
||||||
|
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||||
|
// wait for callbacks that may never arrive.
|
||||||
|
c.Context.EnableVSync(false)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Lock() error {
|
func (c *wlContext) Lock() error {
|
||||||
|
|||||||
+1
-1
@@ -46,8 +46,8 @@ func (c *x11Context) Refresh() error {
|
|||||||
if err := c.Context.MakeCurrent(); err != nil {
|
if err := c.Context.MakeCurrent(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer c.Context.ReleaseCurrent()
|
||||||
c.Context.EnableVSync(true)
|
c.Context.EnableVSync(true)
|
||||||
c.Context.ReleaseCurrent()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
@@ -131,6 +133,28 @@ func (o Orientation) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventLoop implements the functionality required for drivers where
|
||||||
|
// window event loops must run on a separate thread.
|
||||||
|
type eventLoop struct {
|
||||||
|
win *callbacks
|
||||||
|
// wakeup is the callback to wake up the event loop.
|
||||||
|
wakeup func()
|
||||||
|
// driverFuncs is a channel of functions to run the next
|
||||||
|
// time the window loop waits for events.
|
||||||
|
driverFuncs chan func()
|
||||||
|
// invalidates is notified when an invalidate is requested by the client.
|
||||||
|
invalidates chan struct{}
|
||||||
|
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||||
|
immediateInvalidates chan struct{}
|
||||||
|
// events is where the platform backend delivers events bound for the
|
||||||
|
// user program.
|
||||||
|
events chan event.Event
|
||||||
|
frames chan *op.Ops
|
||||||
|
frameAck chan struct{}
|
||||||
|
// delivering avoids re-entrant event delivery.
|
||||||
|
delivering bool
|
||||||
|
}
|
||||||
|
|
||||||
type frameEvent struct {
|
type frameEvent struct {
|
||||||
FrameEvent
|
FrameEvent
|
||||||
|
|
||||||
@@ -147,9 +171,19 @@ type context interface {
|
|||||||
Unlock()
|
Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver is the interface for the platform implementation
|
// basicDriver is the subset of [driver] that may be called even after
|
||||||
|
// a window is destroyed.
|
||||||
|
type basicDriver interface {
|
||||||
|
// Event blocks until an even is available and returns it.
|
||||||
|
Event() event.Event
|
||||||
|
// Invalidate requests a FrameEvent.
|
||||||
|
Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// driver is the interface for the platform implementation
|
||||||
// of a window.
|
// of a window.
|
||||||
type driver interface {
|
type driver interface {
|
||||||
|
basicDriver
|
||||||
// SetAnimating sets the animation flag. When the window is animating,
|
// SetAnimating sets the animation flag. When the window is animating,
|
||||||
// FrameEvents are delivered as fast as the display can handle them.
|
// FrameEvents are delivered as fast as the display can handle them.
|
||||||
SetAnimating(anim bool)
|
SetAnimating(anim bool)
|
||||||
@@ -166,17 +200,23 @@ type driver interface {
|
|||||||
// SetCursor updates the current cursor to name.
|
// SetCursor updates the current cursor to name.
|
||||||
SetCursor(cursor pointer.Cursor)
|
SetCursor(cursor pointer.Cursor)
|
||||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
||||||
Wakeup()
|
// Wakeup()
|
||||||
// Perform actions on the window.
|
// Perform actions on the window.
|
||||||
Perform(system.Action)
|
Perform(system.Action)
|
||||||
// EditorStateChanged notifies the driver that the editor state changed.
|
// EditorStateChanged notifies the driver that the editor state changed.
|
||||||
EditorStateChanged(old, new editorState)
|
EditorStateChanged(old, new editorState)
|
||||||
|
// Run a function on the window thread.
|
||||||
|
Run(f func())
|
||||||
|
// Frame receives a frame.
|
||||||
|
Frame(frame *op.Ops)
|
||||||
|
// ProcessEvent processes an event.
|
||||||
|
ProcessEvent(e event.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowRendezvous struct {
|
type windowRendezvous struct {
|
||||||
in chan windowAndConfig
|
in chan windowAndConfig
|
||||||
out chan windowAndConfig
|
out chan windowAndConfig
|
||||||
errs chan error
|
windows chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowAndConfig struct {
|
type windowAndConfig struct {
|
||||||
@@ -186,32 +226,137 @@ type windowAndConfig struct {
|
|||||||
|
|
||||||
func newWindowRendezvous() *windowRendezvous {
|
func newWindowRendezvous() *windowRendezvous {
|
||||||
wr := &windowRendezvous{
|
wr := &windowRendezvous{
|
||||||
in: make(chan windowAndConfig),
|
in: make(chan windowAndConfig),
|
||||||
out: make(chan windowAndConfig),
|
out: make(chan windowAndConfig),
|
||||||
errs: make(chan error),
|
windows: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
var main windowAndConfig
|
in := wr.in
|
||||||
|
var window windowAndConfig
|
||||||
var out chan windowAndConfig
|
var out chan windowAndConfig
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case w := <-wr.in:
|
case w := <-in:
|
||||||
var err error
|
window = w
|
||||||
if main.window != nil {
|
|
||||||
err = errors.New("multiple windows are not supported")
|
|
||||||
}
|
|
||||||
wr.errs <- err
|
|
||||||
main = w
|
|
||||||
out = wr.out
|
out = wr.out
|
||||||
case out <- main:
|
case out <- window:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return wr
|
return wr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wakeupEvent) ImplementsEvent() {}
|
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||||
func (ConfigEvent) ImplementsEvent() {}
|
return &eventLoop{
|
||||||
|
win: w,
|
||||||
|
wakeup: wakeup,
|
||||||
|
events: make(chan event.Event),
|
||||||
|
invalidates: make(chan struct{}, 1),
|
||||||
|
immediateInvalidates: make(chan struct{}),
|
||||||
|
frames: make(chan *op.Ops),
|
||||||
|
frameAck: make(chan struct{}),
|
||||||
|
driverFuncs: make(chan func(), 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame receives a frame and waits for its processing. It is called by
|
||||||
|
// the client goroutine.
|
||||||
|
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||||
|
e.frames <- frame
|
||||||
|
<-e.frameAck
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event returns the next available event. It is called by the client
|
||||||
|
// goroutine.
|
||||||
|
func (e *eventLoop) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt := <-e.events
|
||||||
|
// Receiving a flushEvent indicates to the platform backend that
|
||||||
|
// all previous events have been processed by the user program.
|
||||||
|
if _, ok := evt.(flushEvent); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate requests invalidation of the window. It is called by the client
|
||||||
|
// goroutine.
|
||||||
|
func (e *eventLoop) Invalidate() {
|
||||||
|
select {
|
||||||
|
case e.immediateInvalidates <- struct{}{}:
|
||||||
|
// The event loop was waiting, no need for a wakeup.
|
||||||
|
case e.invalidates <- struct{}{}:
|
||||||
|
// The event loop is sleeping, wake it up.
|
||||||
|
e.wakeup()
|
||||||
|
default:
|
||||||
|
// A redraw is pending.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run f in the window loop thread. It is called by the client goroutine.
|
||||||
|
func (e *eventLoop) Run(f func()) {
|
||||||
|
e.driverFuncs <- f
|
||||||
|
e.wakeup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushEvents delivers pending events to the client.
|
||||||
|
func (e *eventLoop) FlushEvents() {
|
||||||
|
if e.delivering {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.delivering = true
|
||||||
|
defer func() { e.delivering = false }()
|
||||||
|
for {
|
||||||
|
evt, ok := e.win.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e.deliverEvent(evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||||
|
var frames <-chan *op.Ops
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-e.driverFuncs:
|
||||||
|
f()
|
||||||
|
case frame := <-frames:
|
||||||
|
// The client called FrameEvent.Frame.
|
||||||
|
frames = nil
|
||||||
|
e.win.ProcessFrame(frame, e.frameAck)
|
||||||
|
case e.events <- evt:
|
||||||
|
switch evt.(type) {
|
||||||
|
case flushEvent, DestroyEvent:
|
||||||
|
// DestroyEvents are not flushed.
|
||||||
|
return
|
||||||
|
case FrameEvent:
|
||||||
|
frames = e.frames
|
||||||
|
}
|
||||||
|
evt = theFlushEvent
|
||||||
|
case <-e.invalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
case <-e.immediateInvalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventLoop) Wakeup() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-e.driverFuncs:
|
||||||
|
f()
|
||||||
|
case <-e.invalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
case <-e.immediateInvalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func walkActions(actions system.Action, do func(system.Action)) {
|
func walkActions(actions system.Action, do func(system.Action)) {
|
||||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||||
@@ -221,3 +366,6 @@ func walkActions(actions system.Action, do func(system.Action)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wakeupEvent) ImplementsEvent() {}
|
||||||
|
func (ConfigEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+96
-52
@@ -137,8 +137,10 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/input"
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
@@ -150,6 +152,7 @@ import (
|
|||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
callbacks *callbacks
|
callbacks *callbacks
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
view C.jobject
|
view C.jobject
|
||||||
handle cgo.Handle
|
handle cgo.Handle
|
||||||
@@ -162,8 +165,9 @@ type window struct {
|
|||||||
started bool
|
started bool
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
win *C.ANativeWindow
|
win *C.ANativeWindow
|
||||||
config Config
|
config Config
|
||||||
|
inputHint key.InputHint
|
||||||
|
|
||||||
semantic struct {
|
semantic struct {
|
||||||
hoverID input.SemanticID
|
hoverID input.SemanticID
|
||||||
@@ -487,24 +491,30 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
|||||||
})
|
})
|
||||||
view = C.jni_NewGlobalRef(env, view)
|
view = C.jni_NewGlobalRef(env, view)
|
||||||
wopts := <-mainWindow.out
|
wopts := <-mainWindow.out
|
||||||
|
var cnf Config
|
||||||
w, ok := windows[wopts.window]
|
w, ok := windows[wopts.window]
|
||||||
if !ok {
|
if !ok {
|
||||||
w = &window{
|
w = &window{
|
||||||
callbacks: wopts.window,
|
callbacks: wopts.window,
|
||||||
}
|
}
|
||||||
|
w.loop = newEventLoop(w.callbacks, w.wakeup)
|
||||||
|
w.callbacks.SetDriver(w)
|
||||||
|
cnf.apply(unit.Metric{}, wopts.options)
|
||||||
windows[wopts.window] = w
|
windows[wopts.window] = w
|
||||||
|
} else {
|
||||||
|
cnf = w.config
|
||||||
}
|
}
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
if w.view != 0 {
|
if w.view != 0 {
|
||||||
w.detach(env)
|
w.detach(env)
|
||||||
}
|
}
|
||||||
w.view = view
|
w.view = view
|
||||||
w.handle = cgo.NewHandle(w)
|
w.handle = cgo.NewHandle(w)
|
||||||
w.callbacks.SetDriver(w)
|
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
w.Configure(wopts.options)
|
w.setConfig(env, cnf)
|
||||||
w.SetInputHint(key.HintAny)
|
w.SetInputHint(w.inputHint)
|
||||||
w.setStage(StagePaused)
|
w.setStage(StagePaused)
|
||||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
w.processEvent(ViewEvent{View: uintptr(view)})
|
||||||
return C.jlong(w.handle)
|
return C.jlong(w.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +589,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
|||||||
//export Java_org_gioui_GioView_onBack
|
//export Java_org_gioui_GioView_onBack
|
||||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
if w.callbacks.Event(key.Event{Name: key.NameBack}) {
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
return C.JNI_TRUE
|
return C.JNI_TRUE
|
||||||
}
|
}
|
||||||
return C.JNI_FALSE
|
return C.JNI_FALSE
|
||||||
@@ -588,7 +598,7 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
|||||||
//export Java_org_gioui_GioView_onFocusChange
|
//export Java_org_gioui_GioView_onFocusChange
|
||||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
w.processEvent(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onWindowInsets
|
//export Java_org_gioui_GioView_onWindowInsets
|
||||||
@@ -663,6 +673,34 @@ func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.processEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) processEvent(e event.Event) bool {
|
||||||
|
if !w.callbacks.ProcessEvent(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
||||||
for _, ch := range sem.Children {
|
for _, ch := range sem.Children {
|
||||||
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
||||||
@@ -767,8 +805,7 @@ func (w *window) semIDFor(virtID C.jint) input.SemanticID {
|
|||||||
|
|
||||||
func (w *window) detach(env *C.JNIEnv) {
|
func (w *window) detach(env *C.JNIEnv) {
|
||||||
callVoidMethod(env, w.view, gioView.unregister)
|
callVoidMethod(env, w.view, gioView.unregister)
|
||||||
w.callbacks.Event(ViewEvent{})
|
w.processEvent(ViewEvent{})
|
||||||
w.callbacks.SetDriver(nil)
|
|
||||||
w.handle.Delete()
|
w.handle.Delete()
|
||||||
C.jni_DeleteGlobalRef(env, w.view)
|
C.jni_DeleteGlobalRef(env, w.view)
|
||||||
w.view = 0
|
w.view = 0
|
||||||
@@ -788,7 +825,7 @@ func (w *window) setStage(stage Stage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.stage = stage
|
w.stage = stage
|
||||||
w.callbacks.Event(StageEvent{stage})
|
w.processEvent(StageEvent{stage})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setVisual(visID int) error {
|
func (w *window) setVisual(visID int) error {
|
||||||
@@ -830,7 +867,7 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
if size.X == 0 || size.Y == 0 {
|
if size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
@@ -844,7 +881,7 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
Left: unit.Dp(w.insets.left) * dppp,
|
Left: unit.Dp(w.insets.left) * dppp,
|
||||||
Right: unit.Dp(w.insets.right) * dppp,
|
Right: unit.Dp(w.insets.right) * dppp,
|
||||||
}
|
}
|
||||||
w.callbacks.Event(frameEvent{
|
w.processEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -944,7 +981,7 @@ func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.j
|
|||||||
if pressed == C.JNI_TRUE {
|
if pressed == C.JNI_TRUE {
|
||||||
state = key.Press
|
state = key.Press
|
||||||
}
|
}
|
||||||
w.callbacks.Event(key.Event{Name: n, State: state})
|
w.processEvent(key.Event{Name: n, State: state})
|
||||||
}
|
}
|
||||||
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||||
w.callbacks.EditorInsert(string(rune(r)))
|
w.callbacks.EditorInsert(string(rune(r)))
|
||||||
@@ -994,7 +1031,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.callbacks.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: src,
|
Source: src,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -1146,6 +1183,8 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
|
w.inputHint = mode
|
||||||
|
|
||||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||||
const (
|
const (
|
||||||
TYPE_NULL = 0
|
TYPE_NULL = 0
|
||||||
@@ -1292,9 +1331,9 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
|||||||
func osMain() {
|
func osMain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
mainWindow.in <- windowAndConfig{window, options}
|
mainWindow.in <- windowAndConfig{window, options}
|
||||||
return <-mainWindow.errs
|
<-mainWindow.windows
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
@@ -1313,7 +1352,7 @@ func (w *window) ReadClipboard() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := goString(env, C.jstring(c))
|
content := goString(env, C.jstring(c))
|
||||||
w.callbacks.Event(transfer.DataEvent{
|
w.processEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -1323,42 +1362,46 @@ func (w *window) ReadClipboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
|
cnf := w.config
|
||||||
|
cnf.apply(unit.Metric{}, options)
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
prev := w.config
|
w.setConfig(env, cnf)
|
||||||
cnf := w.config
|
|
||||||
cnf.apply(unit.Metric{}, options)
|
|
||||||
// Decorations are never disabled.
|
|
||||||
cnf.Decorated = true
|
|
||||||
|
|
||||||
if prev.Orientation != cnf.Orientation {
|
|
||||||
w.config.Orientation = cnf.Orientation
|
|
||||||
setOrientation(env, w.view, cnf.Orientation)
|
|
||||||
}
|
|
||||||
if prev.NavigationColor != cnf.NavigationColor {
|
|
||||||
w.config.NavigationColor = cnf.NavigationColor
|
|
||||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
|
||||||
}
|
|
||||||
if prev.StatusColor != cnf.StatusColor {
|
|
||||||
w.config.StatusColor = cnf.StatusColor
|
|
||||||
setStatusColor(env, w.view, cnf.StatusColor)
|
|
||||||
}
|
|
||||||
if prev.Mode != cnf.Mode {
|
|
||||||
switch cnf.Mode {
|
|
||||||
case Fullscreen:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
|
||||||
w.config.Mode = Fullscreen
|
|
||||||
case Windowed:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
|
||||||
w.config.Mode = Windowed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cnf.Decorated != prev.Decorated {
|
|
||||||
w.config.Decorated = cnf.Decorated
|
|
||||||
}
|
|
||||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
|
||||||
|
prev := w.config
|
||||||
|
// Decorations are never disabled.
|
||||||
|
cnf.Decorated = true
|
||||||
|
|
||||||
|
if prev.Orientation != cnf.Orientation {
|
||||||
|
w.config.Orientation = cnf.Orientation
|
||||||
|
setOrientation(env, w.view, cnf.Orientation)
|
||||||
|
}
|
||||||
|
if prev.NavigationColor != cnf.NavigationColor {
|
||||||
|
w.config.NavigationColor = cnf.NavigationColor
|
||||||
|
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||||
|
}
|
||||||
|
if prev.StatusColor != cnf.StatusColor {
|
||||||
|
w.config.StatusColor = cnf.StatusColor
|
||||||
|
setStatusColor(env, w.view, cnf.StatusColor)
|
||||||
|
}
|
||||||
|
if prev.Mode != cnf.Mode {
|
||||||
|
switch cnf.Mode {
|
||||||
|
case Fullscreen:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||||
|
w.config.Mode = Fullscreen
|
||||||
|
case Windowed:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||||
|
w.config.Mode = Windowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cnf.Decorated != prev.Decorated {
|
||||||
|
w.config.Decorated = cnf.Decorated
|
||||||
|
}
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||||
@@ -1367,9 +1410,10 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) wakeup() {
|
||||||
runOnMain(func(env *C.JNIEnv) {
|
runOnMain(func(env *C.JNIEnv) {
|
||||||
w.callbacks.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -260,8 +260,9 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
|||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) wakeup() {
|
||||||
runOnMain(func() {
|
runOnMain(func() {
|
||||||
w.w.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-31
@@ -81,10 +81,12 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,10 +99,11 @@ type window struct {
|
|||||||
view C.CFTypeRef
|
view C.CFTypeRef
|
||||||
w *callbacks
|
w *callbacks
|
||||||
displayLink *displayLink
|
displayLink *displayLink
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
visible bool
|
hidden bool
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
pointerMap []C.CFTypeRef
|
pointerMap []C.CFTypeRef
|
||||||
}
|
}
|
||||||
@@ -116,23 +119,26 @@ func init() {
|
|||||||
|
|
||||||
//export onCreate
|
//export onCreate
|
||||||
func onCreate(view, controller C.CFTypeRef) {
|
func onCreate(view, controller C.CFTypeRef) {
|
||||||
|
wopts := <-mainWindow.out
|
||||||
w := &window{
|
w := &window{
|
||||||
view: view,
|
view: view,
|
||||||
|
w: wopts.window,
|
||||||
}
|
}
|
||||||
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
dl, err := newDisplayLink(func() {
|
dl, err := newDisplayLink(func() {
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
wopts := <-mainWindow.out
|
|
||||||
w.w = wopts.window
|
|
||||||
w.w.SetDriver(w)
|
|
||||||
views[view] = w
|
views[view] = w
|
||||||
w.Configure(wopts.options)
|
w.Configure(wopts.options)
|
||||||
w.w.Event(StageEvent{Stage: StagePaused})
|
w.ProcessEvent(StageEvent{Stage: StageRunning})
|
||||||
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
w.ProcessEvent(ViewEvent{ViewController: uintptr(controller)})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onDraw
|
//export gio_onDraw
|
||||||
@@ -142,22 +148,20 @@ func gio_onDraw(view C.CFTypeRef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
|
if w.hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
params := C.viewDrawParams(w.view)
|
params := C.viewDrawParams(w.view)
|
||||||
if params.width == 0 || params.height == 0 {
|
if params.width == 0 || params.height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wasVisible := w.visible
|
|
||||||
w.visible = true
|
|
||||||
if !wasVisible {
|
|
||||||
w.w.Event(StageEvent{Stage: StageRunning})
|
|
||||||
}
|
|
||||||
const inchPrDp = 1.0 / 163
|
const inchPrDp = 1.0 / 163
|
||||||
m := unit.Metric{
|
m := unit.Metric{
|
||||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||||
}
|
}
|
||||||
dppp := unit.Dp(1. / m.PxPerDp)
|
dppp := unit.Dp(1. / m.PxPerDp)
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: image.Point{
|
Size: image.Point{
|
||||||
@@ -179,24 +183,33 @@ func (w *window) draw(sync bool) {
|
|||||||
//export onStop
|
//export onStop
|
||||||
func onStop(view C.CFTypeRef) {
|
func onStop(view C.CFTypeRef) {
|
||||||
w := views[view]
|
w := views[view]
|
||||||
w.visible = false
|
w.hidden = true
|
||||||
w.w.Event(StageEvent{Stage: StagePaused})
|
w.ProcessEvent(StageEvent{Stage: StagePaused})
|
||||||
|
}
|
||||||
|
|
||||||
|
//export onStart
|
||||||
|
func onStart(view C.CFTypeRef) {
|
||||||
|
w := views[view]
|
||||||
|
w.hidden = false
|
||||||
|
w.ProcessEvent(StageEvent{Stage: StageRunning})
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDestroy
|
//export onDestroy
|
||||||
func onDestroy(view C.CFTypeRef) {
|
func onDestroy(view C.CFTypeRef) {
|
||||||
w := views[view]
|
w := views[view]
|
||||||
delete(views, view)
|
w.ProcessEvent(ViewEvent{})
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
w.w.Event(DestroyEvent{})
|
|
||||||
w.displayLink.Close()
|
w.displayLink.Close()
|
||||||
|
w.displayLink = nil
|
||||||
|
delete(views, view)
|
||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onFocus
|
//export onFocus
|
||||||
func onFocus(view C.CFTypeRef, focus int) {
|
func onFocus(view C.CFTypeRef, focus int) {
|
||||||
w := views[view]
|
w := views[view]
|
||||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
w.ProcessEvent(key.FocusEvent{Focus: focus != 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLowMemory
|
//export onLowMemory
|
||||||
@@ -254,7 +267,7 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
|
|||||||
w := views[view]
|
w := views[view]
|
||||||
t := time.Duration(float64(ti) * float64(time.Second))
|
t := time.Duration(float64(ti) * float64(time.Second))
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||||
@@ -267,7 +280,7 @@ func (w *window) ReadClipboard() {
|
|||||||
cstr := C.readClipboard()
|
cstr := C.readClipboard()
|
||||||
defer C.CFRelease(cstr)
|
defer C.CFRelease(cstr)
|
||||||
content := nsstringToString(cstr)
|
content := nsstringToString(cstr)
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -287,7 +300,7 @@ func (w *window) WriteClipboard(mime string, s []byte) {
|
|||||||
func (w *window) Configure([]Option) {
|
func (w *window) Configure([]Option) {
|
||||||
// Decorations are never disabled.
|
// Decorations are never disabled.
|
||||||
w.config.Decorated = true
|
w.config.Decorated = true
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
@@ -295,10 +308,6 @@ func (w *window) EditorStateChanged(old, new editorState) {}
|
|||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
v := w.view
|
|
||||||
if v == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if anim {
|
if anim {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
} else {
|
} else {
|
||||||
@@ -311,7 +320,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) onKeyCommand(name key.Name) {
|
func (w *window) onKeyCommand(name key.Name) {
|
||||||
w.w.Event(key.Event{
|
w.ProcessEvent(key.Event{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -350,9 +359,30 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
|
|
||||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWindow(win *callbacks, options []Option) {
|
||||||
mainWindow.in <- windowAndConfig{win, options}
|
mainWindow.in <- windowAndConfig{win, options}
|
||||||
return <-mainWindow.errs
|
<-mainWindow.windows
|
||||||
}
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ CGFloat _keyboardHeight;
|
|||||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||||
UIView *drawView = self.view.subviews[0];
|
UIView *drawView = self.view.subviews[0];
|
||||||
if (drawView != nil) {
|
if (drawView != nil) {
|
||||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
onStart((__bridge CFTypeRef)drawView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+78
-75
@@ -14,8 +14,10 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
@@ -54,9 +56,6 @@ type window struct {
|
|||||||
composing bool
|
composing bool
|
||||||
requestFocus bool
|
requestFocus bool
|
||||||
|
|
||||||
chanAnimation chan struct{}
|
|
||||||
chanRedraw chan struct{}
|
|
||||||
|
|
||||||
config Config
|
config Config
|
||||||
inset f32.Point
|
inset f32.Point
|
||||||
scale float32
|
scale float32
|
||||||
@@ -69,7 +68,7 @@ type window struct {
|
|||||||
contextStatus contextStatus
|
contextStatus contextStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
doc := js.Global().Get("document")
|
doc := js.Global().Get("document")
|
||||||
cont := getContainer(doc)
|
cont := getContainer(doc)
|
||||||
cnv := createCanvas(doc)
|
cnv := createCanvas(doc)
|
||||||
@@ -84,7 +83,9 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
head: doc.Get("head"),
|
head: doc.Get("head"),
|
||||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
|
w: win,
|
||||||
}
|
}
|
||||||
|
w.w.SetDriver(w)
|
||||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||||
w.browserHistory = w.window.Get("history")
|
w.browserHistory = w.window.Get("history")
|
||||||
w.visualViewport = w.window.Get("visualViewport")
|
w.visualViewport = w.window.Get("visualViewport")
|
||||||
@@ -94,15 +95,13 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||||
w.screenOrientation = screen.Get("orientation")
|
w.screenOrientation = screen.Get("orientation")
|
||||||
}
|
}
|
||||||
w.chanAnimation = make(chan struct{}, 1)
|
|
||||||
w.chanRedraw = make(chan struct{}, 1)
|
|
||||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
w.chanAnimation <- struct{}{}
|
w.draw(false)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
content := args[0].String()
|
content := args[0].String()
|
||||||
go win.Event(transfer.DataEvent{
|
w.processEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -112,29 +111,13 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
})
|
})
|
||||||
w.addEventListeners()
|
w.addEventListeners()
|
||||||
w.addHistory()
|
w.addHistory()
|
||||||
w.w = win
|
|
||||||
|
|
||||||
go func() {
|
w.Configure(options)
|
||||||
defer w.cleanup()
|
w.blur()
|
||||||
w.w.SetDriver(w)
|
w.processEvent(ViewEvent{Element: cont})
|
||||||
w.Configure(options)
|
w.processEvent(StageEvent{Stage: StageRunning})
|
||||||
w.blur()
|
w.resize()
|
||||||
w.w.Event(ViewEvent{Element: cont})
|
w.draw(true)
|
||||||
w.w.Event(StageEvent{Stage: StageRunning})
|
|
||||||
w.resize()
|
|
||||||
w.draw(true)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-w.wakeups:
|
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
case <-w.chanAnimation:
|
|
||||||
w.animCallback()
|
|
||||||
case <-w.chanRedraw:
|
|
||||||
w.draw(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainer(doc js.Value) js.Value {
|
func getContainer(doc js.Value) js.Value {
|
||||||
@@ -194,12 +177,12 @@ func (w *window) addEventListeners() {
|
|||||||
w.cnv.Set("width", 0)
|
w.cnv.Set("width", 0)
|
||||||
w.cnv.Set("height", 0)
|
w.cnv.Set("height", 0)
|
||||||
w.resize()
|
w.resize()
|
||||||
w.requestRedraw()
|
w.draw(true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.resize()
|
w.resize()
|
||||||
w.requestRedraw()
|
w.draw(true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||||
@@ -207,7 +190,7 @@ func (w *window) addEventListeners() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||||
if w.w.Event(key.Event{Name: key.NameBack}) {
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
return w.browserHistory.Call("forward")
|
return w.browserHistory.Call("forward")
|
||||||
}
|
}
|
||||||
return w.browserHistory.Call("back")
|
return w.browserHistory.Call("back")
|
||||||
@@ -220,7 +203,7 @@ func (w *window) addEventListeners() {
|
|||||||
default:
|
default:
|
||||||
ev.Stage = StageRunning
|
ev.Stage = StageRunning
|
||||||
}
|
}
|
||||||
w.w.Event(ev)
|
w.processEvent(ev)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||||
@@ -280,18 +263,18 @@ func (w *window) addEventListeners() {
|
|||||||
w.touches[i] = js.Null()
|
w.touches[i] = js.Null()
|
||||||
}
|
}
|
||||||
w.touches = w.touches[:0]
|
w.touches = w.touches[:0]
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.processEvent(key.FocusEvent{Focus: true})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.processEvent(key.FocusEvent{Focus: false})
|
||||||
w.blur()
|
w.blur()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -380,10 +363,50 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
|||||||
Modifiers: modifiersFor(e),
|
Modifiers: modifiersFor(e),
|
||||||
State: ks,
|
State: ks,
|
||||||
}
|
}
|
||||||
w.w.Event(cmd)
|
w.processEvent(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.processEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) processEvent(e event.Event) bool {
|
||||||
|
if !w.w.ProcessEvent(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case w.wakeups <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if ok {
|
||||||
|
if _, destroy := evt.(DestroyEvent); destroy {
|
||||||
|
w.cleanup()
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
<-w.wakeups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.w.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||||
// KeyEvent.
|
// KeyEvent.
|
||||||
func modifiersFor(e js.Value) key.Modifiers {
|
func modifiersFor(e js.Value) key.Modifiers {
|
||||||
@@ -431,7 +454,7 @@ func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
|||||||
X: float32(x) * scale,
|
X: float32(x) * scale,
|
||||||
Y: float32(y) * scale,
|
Y: float32(y) * scale,
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: pos,
|
Position: pos,
|
||||||
@@ -481,7 +504,7 @@ func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
|||||||
if jbtns&4 != 0 {
|
if jbtns&4 != 0 {
|
||||||
btns |= pointer.ButtonTertiary
|
btns |= pointer.ButtonTertiary
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -508,17 +531,6 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
|||||||
return jsf
|
return jsf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) animCallback() {
|
|
||||||
anim := w.animating
|
|
||||||
w.animRequested = anim
|
|
||||||
if anim {
|
|
||||||
w.requestAnimationFrame.Invoke(w.redraw)
|
|
||||||
}
|
|
||||||
if anim {
|
|
||||||
w.draw(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
@@ -574,7 +586,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
@@ -613,23 +625,14 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
style.Set("cursor", webCursor[cursor])
|
style.Set("cursor", webCursor[cursor])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {
|
func (w *window) ShowTextInput(show bool) {
|
||||||
// Run in a goroutine to avoid a deadlock if the
|
// Run in a goroutine to avoid a deadlock if the
|
||||||
// focus change result in an event.
|
// focus change result in an event.
|
||||||
go func() {
|
if show {
|
||||||
if show {
|
w.focus()
|
||||||
w.focus()
|
} else {
|
||||||
} else {
|
w.blur()
|
||||||
w.blur()
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
@@ -646,7 +649,7 @@ func (w *window) resize() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||||
@@ -666,12 +669,19 @@ func (w *window) draw(sync bool) {
|
|||||||
if w.contextStatus == contextStatusLost {
|
if w.contextStatus == contextStatusLost {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
anim := w.animating
|
||||||
|
w.animRequested = anim
|
||||||
|
if anim {
|
||||||
|
w.requestAnimationFrame.Invoke(w.redraw)
|
||||||
|
} else if !sync {
|
||||||
|
return
|
||||||
|
}
|
||||||
size, insets, metric := w.getConfig()
|
size, insets, metric := w.getConfig()
|
||||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.w.Event(frameEvent{
|
w.processEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: size,
|
Size: size,
|
||||||
@@ -741,13 +751,6 @@ func (w *window) navigationColor(c color.NRGBA) {
|
|||||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) requestRedraw() {
|
|
||||||
select {
|
|
||||||
case w.chanRedraw <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-46
@@ -16,10 +16,12 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/internal/f32"
|
"gioui.org/internal/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
|
||||||
_ "gioui.org/internal/cocoainit"
|
_ "gioui.org/internal/cocoainit"
|
||||||
@@ -256,6 +258,7 @@ type window struct {
|
|||||||
redraw chan struct{}
|
redraw chan struct{}
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
pointerBtns pointer.Buttons
|
pointerBtns pointer.Buttons
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
scale float32
|
scale float32
|
||||||
config Config
|
config Config
|
||||||
@@ -274,25 +277,13 @@ var nextTopLeft C.NSPoint
|
|||||||
// mustView is like lookupView, except that it panics
|
// mustView is like lookupView, except that it panics
|
||||||
// if the view isn't mapped.
|
// if the view isn't mapped.
|
||||||
func mustView(view C.CFTypeRef) *window {
|
func mustView(view C.CFTypeRef) *window {
|
||||||
w, ok := lookupView(view)
|
w, exists := viewMap[view]
|
||||||
if !ok {
|
if !exists {
|
||||||
panic("no window for view")
|
panic("no window for view")
|
||||||
}
|
}
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupView(view C.CFTypeRef) (*window, bool) {
|
|
||||||
w, exists := viewMap[view]
|
|
||||||
if !exists {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteView(view C.CFTypeRef) {
|
|
||||||
delete(viewMap, view)
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertView(view C.CFTypeRef, w *window) {
|
func insertView(view C.CFTypeRef, w *window) {
|
||||||
viewMap[view] = w
|
viewMap[view] = w
|
||||||
}
|
}
|
||||||
@@ -307,7 +298,7 @@ func (w *window) ReadClipboard() {
|
|||||||
defer C.CFRelease(cstr)
|
defer C.CFRelease(cstr)
|
||||||
}
|
}
|
||||||
content := nsstringToString(cstr)
|
content := nsstringToString(cstr)
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -420,7 +411,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
||||||
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
|
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setTitle(prev, cnf Config) {
|
func (w *window) setTitle(prev, cnf Config) {
|
||||||
@@ -493,7 +484,7 @@ func (w *window) setStage(stage Stage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.stage = stage
|
w.stage = stage
|
||||||
w.w.Event(StageEvent{Stage: stage})
|
w.ProcessEvent(StageEvent{Stage: stage})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeys
|
//export gio_onKeys
|
||||||
@@ -507,7 +498,7 @@ func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown
|
|||||||
w := mustView(view)
|
w := mustView(view)
|
||||||
for _, k := range str {
|
for _, k := range str {
|
||||||
if n, ok := convertKey(k); ok {
|
if n, ok := convertKey(k); ok {
|
||||||
w.w.Event(key.Event{
|
w.ProcessEvent(key.Event{
|
||||||
Name: n,
|
Name: n,
|
||||||
Modifiers: kmods,
|
Modifiers: kmods,
|
||||||
State: ks,
|
State: ks,
|
||||||
@@ -562,7 +553,7 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
|
|||||||
default:
|
default:
|
||||||
panic("invalid direction")
|
panic("invalid direction")
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: typ,
|
Kind: typ,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Time: t,
|
Time: t,
|
||||||
@@ -582,7 +573,7 @@ func gio_onDraw(view C.CFTypeRef) {
|
|||||||
//export gio_onFocus
|
//export gio_onFocus
|
||||||
func gio_onFocus(view C.CFTypeRef, focus C.int) {
|
func gio_onFocus(view C.CFTypeRef, focus C.int) {
|
||||||
w := mustView(view)
|
w := mustView(view)
|
||||||
w.w.Event(key.FocusEvent{Focus: focus == 1})
|
w.ProcessEvent(key.FocusEvent{Focus: focus == 1})
|
||||||
if w.stage >= StageInactive {
|
if w.stage >= StageInactive {
|
||||||
if focus == 0 {
|
if focus == 0 {
|
||||||
w.setStage(StageInactive)
|
w.setStage(StageInactive)
|
||||||
@@ -776,14 +767,14 @@ func (w *window) draw() {
|
|||||||
}
|
}
|
||||||
if sz != w.config.Size {
|
if sz != w.config.Size {
|
||||||
w.config.Size = sz
|
w.config.Size = sz
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
if sz.X == 0 || sz.Y == 0 {
|
if sz.X == 0 || sz.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg := configFor(w.scale)
|
cfg := configFor(w.scale)
|
||||||
w.setStage(StageRunning)
|
w.setStage(StageRunning)
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -793,6 +784,27 @@ func (w *window) draw() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
func configFor(scale float32) unit.Metric {
|
func configFor(scale float32) unit.Metric {
|
||||||
return unit.Metric{
|
return unit.Metric{
|
||||||
PxPerDp: scale,
|
PxPerDp: scale,
|
||||||
@@ -803,11 +815,12 @@ func configFor(scale float32) unit.Metric {
|
|||||||
//export gio_onClose
|
//export gio_onClose
|
||||||
func gio_onClose(view C.CFTypeRef) {
|
func gio_onClose(view C.CFTypeRef) {
|
||||||
w := mustView(view)
|
w := mustView(view)
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(ViewEvent{})
|
||||||
w.w.Event(DestroyEvent{})
|
w.setStage(StagePaused)
|
||||||
|
w.ProcessEvent(DestroyEvent{})
|
||||||
w.displayLink.Close()
|
w.displayLink.Close()
|
||||||
w.displayLink = nil
|
w.displayLink = nil
|
||||||
deleteView(view)
|
delete(viewMap, view)
|
||||||
C.CFRelease(w.view)
|
C.CFRelease(w.view)
|
||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
@@ -828,14 +841,14 @@ func gio_onShow(view C.CFTypeRef) {
|
|||||||
func gio_onFullscreen(view C.CFTypeRef) {
|
func gio_onFullscreen(view C.CFTypeRef) {
|
||||||
w := mustView(view)
|
w := mustView(view)
|
||||||
w.config.Mode = Fullscreen
|
w.config.Mode = Fullscreen
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onWindowed
|
//export gio_onWindowed
|
||||||
func gio_onWindowed(view C.CFTypeRef) {
|
func gio_onWindowed(view C.CFTypeRef) {
|
||||||
w := mustView(view)
|
w := mustView(view)
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onAppHide
|
//export gio_onAppHide
|
||||||
@@ -857,20 +870,23 @@ func gio_onFinishLaunching() {
|
|||||||
close(launched)
|
close(launched)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
<-launched
|
<-launched
|
||||||
errch := make(chan error)
|
res := make(chan struct{})
|
||||||
runOnMain(func() {
|
runOnMain(func() {
|
||||||
w, err := newOSWindow()
|
w := &window{
|
||||||
if err != nil {
|
redraw: make(chan struct{}, 1),
|
||||||
errch <- err
|
w: win,
|
||||||
|
}
|
||||||
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
win.SetDriver(w)
|
||||||
|
res <- struct{}{}
|
||||||
|
if err := w.init(); err != nil {
|
||||||
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
errch <- nil
|
|
||||||
w.w = win
|
|
||||||
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
|
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
|
||||||
w.updateWindowMode()
|
w.updateWindowMode()
|
||||||
win.SetDriver(w)
|
|
||||||
w.Configure(options)
|
w.Configure(options)
|
||||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||||
@@ -881,22 +897,18 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
// makeKeyAndOrderFront assumes ownership of our window reference.
|
// makeKeyAndOrderFront assumes ownership of our window reference.
|
||||||
C.makeKeyAndOrderFront(window)
|
C.makeKeyAndOrderFront(window)
|
||||||
layer := C.layerForView(w.view)
|
layer := C.layerForView(w.view)
|
||||||
w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
w.ProcessEvent(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
||||||
})
|
})
|
||||||
return <-errch
|
<-res
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOSWindow() (*window, error) {
|
func (w *window) init() error {
|
||||||
view := C.gio_createView()
|
view := C.gio_createView()
|
||||||
if view == 0 {
|
if view == 0 {
|
||||||
return nil, errors.New("newOSWindows: failed to create view")
|
return errors.New("newOSWindow: failed to create view")
|
||||||
}
|
}
|
||||||
scale := float32(C.getViewBackingScale(view))
|
scale := float32(C.getViewBackingScale(view))
|
||||||
w := &window{
|
w.scale = scale
|
||||||
view: view,
|
|
||||||
scale: scale,
|
|
||||||
redraw: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
dl, err := newDisplayLink(func() {
|
dl, err := newDisplayLink(func() {
|
||||||
select {
|
select {
|
||||||
case w.redraw <- struct{}{}:
|
case w.redraw <- struct{}{}:
|
||||||
@@ -910,10 +922,11 @@ func newOSWindow() (*window, error) {
|
|||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
if err != nil {
|
if err != nil {
|
||||||
C.CFRelease(view)
|
C.CFRelease(view)
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
insertView(view, w)
|
insertView(view, w)
|
||||||
return w, nil
|
w.view = view
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func osMain() {
|
func osMain() {
|
||||||
|
|||||||
+29
-5
@@ -9,6 +9,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ type windowDriver func(*callbacks, []Option) error
|
|||||||
// let each driver initialize these variables with their own version of createWindow.
|
// let each driver initialize these variables with their own version of createWindow.
|
||||||
var wlDriver, x11Driver windowDriver
|
var wlDriver, x11Driver windowDriver
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
var errFirst error
|
var errFirst error
|
||||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
@@ -57,16 +58,39 @@ func newWindow(window *callbacks, options []Option) error {
|
|||||||
}
|
}
|
||||||
err := d(window, options)
|
err := d(window, options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
if errFirst == nil {
|
if errFirst == nil {
|
||||||
errFirst = err
|
errFirst = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errFirst != nil {
|
window.SetDriver(&dummyDriver{
|
||||||
return errFirst
|
win: window,
|
||||||
|
wakeups: make(chan event.Event, 1),
|
||||||
|
})
|
||||||
|
if errFirst == nil {
|
||||||
|
errFirst = errors.New("app: no window driver available")
|
||||||
|
}
|
||||||
|
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyDriver struct {
|
||||||
|
win *callbacks
|
||||||
|
wakeups chan event.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyDriver) Event() event.Event {
|
||||||
|
if e, ok := d.win.nextEvent(); ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return <-d.wakeups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyDriver) Invalidate() {
|
||||||
|
select {
|
||||||
|
case d.wakeups <- wakeupEvent{}:
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
return errors.New("app: no window driver available")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||||
|
|||||||
+156
-92
@@ -15,6 +15,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,10 +26,12 @@ import (
|
|||||||
"gioui.org/app/internal/xkb"
|
"gioui.org/app/internal/xkb"
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/fling"
|
"gioui.org/internal/fling"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +100,9 @@ type wlDisplay struct {
|
|||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
|
|
||||||
repeat repeatState
|
repeat repeatState
|
||||||
|
poller poller
|
||||||
|
readClipClose chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type wlSeat struct {
|
type wlSeat struct {
|
||||||
@@ -137,7 +142,7 @@ type repeatState struct {
|
|||||||
delay time.Duration
|
delay time.Duration
|
||||||
|
|
||||||
key uint32
|
key uint32
|
||||||
win *callbacks
|
win *window
|
||||||
stopC chan struct{}
|
stopC chan struct{}
|
||||||
|
|
||||||
start time.Duration
|
start time.Duration
|
||||||
@@ -195,11 +200,9 @@ type window struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage Stage
|
stage Stage
|
||||||
dead bool
|
|
||||||
lastFrameCallback *C.struct_wl_callback
|
lastFrameCallback *C.struct_wl_callback
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
redraw bool
|
|
||||||
// The most recent configure serial waiting to be ack'ed.
|
// The most recent configure serial waiting to be ack'ed.
|
||||||
serial C.uint32_t
|
serial C.uint32_t
|
||||||
scale int
|
scale int
|
||||||
@@ -212,6 +215,10 @@ type window struct {
|
|||||||
clipReads chan transfer.DataEvent
|
clipReads chan transfer.DataEvent
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
|
||||||
|
// invMu avoids the race between the destruction of disp and
|
||||||
|
// Invalidate waking it up.
|
||||||
|
invMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type poller struct {
|
type poller struct {
|
||||||
@@ -260,25 +267,17 @@ func newWLWindow(callbacks *callbacks, options []Option) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.w = callbacks
|
w.w = callbacks
|
||||||
go func() {
|
w.w.SetDriver(w)
|
||||||
defer d.destroy()
|
|
||||||
defer w.destroy()
|
|
||||||
|
|
||||||
w.w.SetDriver(w)
|
// Finish and commit setup from createNativeWindow.
|
||||||
|
w.Configure(options)
|
||||||
|
w.draw(true)
|
||||||
|
C.wl_surface_commit(w.surf)
|
||||||
|
|
||||||
// Finish and commit setup from createNativeWindow.
|
w.ProcessEvent(WaylandViewEvent{
|
||||||
w.Configure(options)
|
Display: unsafe.Pointer(w.display()),
|
||||||
C.wl_surface_commit(w.surf)
|
Surface: unsafe.Pointer(w.surf),
|
||||||
|
})
|
||||||
w.w.Event(WaylandViewEvent{
|
|
||||||
Display: unsafe.Pointer(w.display()),
|
|
||||||
Surface: unsafe.Pointer(w.surf),
|
|
||||||
})
|
|
||||||
|
|
||||||
err := w.loop()
|
|
||||||
w.w.Event(WaylandViewEvent{})
|
|
||||||
w.w.Event(DestroyEvent{Err: err})
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,15 +548,15 @@ func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
|
|||||||
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.serial = serial
|
w.serial = serial
|
||||||
w.redraw = true
|
|
||||||
C.xdg_surface_ack_configure(wmSurf, serial)
|
C.xdg_surface_ack_configure(wmSurf, serial)
|
||||||
w.setStage(StageRunning)
|
w.setStage(StageRunning)
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelClose
|
//export gio_onToplevelClose
|
||||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.dead = true
|
w.close(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelConfigure
|
//export gio_onToplevelConfigure
|
||||||
@@ -586,8 +585,8 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
|
|||||||
} else {
|
} else {
|
||||||
w.size.Y += int(w.config.decoHeight)
|
w.size.Y += int(w.config.decoHeight)
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.redraw = true
|
w.draw(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +644,7 @@ func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *
|
|||||||
if w.config.Mode == Minimized {
|
if w.config.Mode == Minimized {
|
||||||
// Minimized window got brought back up: it is no longer so.
|
// Minimized window got brought back up: it is no longer so.
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,7 +789,7 @@ func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Press,
|
Kind: pointer.Press,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
@@ -806,7 +805,7 @@ func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.ui
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
w := s.touchFoci[id]
|
w := s.touchFoci[id]
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Release,
|
Kind: pointer.Release,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
@@ -824,7 +823,7 @@ func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
@@ -843,7 +842,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
|||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
for id, w := range s.touchFoci {
|
for id, w := range s.touchFoci {
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
@@ -869,7 +868,7 @@ func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.ui
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
if w.inCompositor {
|
if w.inCompositor {
|
||||||
w.inCompositor = false
|
w.inCompositor = false
|
||||||
w.w.Event(pointer.Event{Kind: pointer.Cancel})
|
w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,7 +929,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
|||||||
}
|
}
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
w.resetFling()
|
w.resetFling()
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1018,8 +1017,11 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ReadClipboard() {
|
func (w *window) ReadClipboard() {
|
||||||
|
if w.disp.readClipClose != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.disp.readClipClose = make(chan struct{})
|
||||||
r, err := w.disp.readClipboard()
|
r, err := w.disp.readClipboard()
|
||||||
// Send empty responses on unavailable clipboards or errors.
|
|
||||||
if r == nil || err != nil {
|
if r == nil || err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1027,13 +1029,17 @@ func (w *window) ReadClipboard() {
|
|||||||
go func() {
|
go func() {
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
data, _ := io.ReadAll(r)
|
data, _ := io.ReadAll(r)
|
||||||
w.clipReads <- transfer.DataEvent{
|
e := transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(bytes.NewReader(data))
|
return io.NopCloser(bytes.NewReader(data))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
w.Wakeup()
|
select {
|
||||||
|
case w.clipReads <- e:
|
||||||
|
w.disp.wakeup()
|
||||||
|
case <-w.disp.readClipClose:
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,8 +1102,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
w.config.MaxSize = cnf.MaxSize
|
w.config.MaxSize = cnf.MaxSize
|
||||||
w.setWindowConstraints()
|
w.setWindowConstraints()
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.redraw = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setWindowConstraints() {
|
func (w *window) setWindowConstraints() {
|
||||||
@@ -1134,7 +1139,7 @@ func (w *window) Perform(actions system.Action) {
|
|||||||
walkActions(actions, func(action system.Action) {
|
walkActions(actions, func(action system.Action) {
|
||||||
switch action {
|
switch action {
|
||||||
case system.ActionClose:
|
case system.ActionClose:
|
||||||
w.dead = true
|
w.close(nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1222,7 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
s.keyboardFocus = w
|
s.keyboardFocus = w
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.ProcessEvent(key.FocusEvent{Focus: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardLeave
|
//export gio_onKeyboardLeave
|
||||||
@@ -1226,7 +1231,7 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w := s.keyboardFocus
|
w := s.keyboardFocus
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.ProcessEvent(key.FocusEvent{Focus: false})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardKey
|
//export gio_onKeyboardKey
|
||||||
@@ -1244,7 +1249,7 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
||||||
@@ -1279,7 +1284,7 @@ func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) {
|
|||||||
r.now = 0
|
r.now = 0
|
||||||
r.stopC = stopC
|
r.stopC = stopC
|
||||||
r.key = keyCode
|
r.key = keyCode
|
||||||
r.win = w.w
|
r.win = w
|
||||||
rate, delay := r.rate, r.delay
|
rate, delay := r.rate, r.delay
|
||||||
go func() {
|
go func() {
|
||||||
timer := time.NewTimer(delay)
|
timer := time.NewTimer(delay)
|
||||||
@@ -1337,9 +1342,9 @@ func (r *repeatState) Repeat(d *wlDisplay) {
|
|||||||
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
||||||
if ee, ok := e.(key.EditEvent); ok {
|
if ee, ok := e.(key.EditEvent); ok {
|
||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
r.win.EditorInsert(ee.Text)
|
r.win.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
r.win.Event(e)
|
r.win.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.last += delay
|
r.last += delay
|
||||||
@@ -1352,28 +1357,76 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
|
|||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
if w.lastFrameCallback == callback {
|
if w.lastFrameCallback == callback {
|
||||||
w.lastFrameCallback = nil
|
w.lastFrameCallback = nil
|
||||||
|
w.draw(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) loop() error {
|
func (w *window) close(err error) {
|
||||||
var p poller
|
w.ProcessEvent(WaylandViewEvent{})
|
||||||
for {
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
if err := w.disp.dispatch(&p); err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
func (w *window) dispatch() {
|
||||||
select {
|
if w.disp == nil {
|
||||||
case e := <-w.clipReads:
|
<-w.wakeups
|
||||||
w.w.Event(e)
|
w.w.Invalidate()
|
||||||
case <-w.wakeups:
|
return
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if w.dead {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
w.draw()
|
|
||||||
}
|
}
|
||||||
return nil
|
if err := w.disp.dispatch(); err != nil {
|
||||||
|
w.close(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case e := <-w.clipReads:
|
||||||
|
w.disp.readClipClose = nil
|
||||||
|
w.ProcessEvent(e)
|
||||||
|
case <-w.wakeups:
|
||||||
|
w.w.Invalidate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
w.dispatch()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, destroy := evt.(DestroyEvent); destroy {
|
||||||
|
w.destroy()
|
||||||
|
w.invMu.Lock()
|
||||||
|
w.disp.destroy()
|
||||||
|
w.disp = nil
|
||||||
|
w.invMu.Unlock()
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
select {
|
||||||
|
case w.wakeups <- struct{}{}:
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
|
if w.disp != nil {
|
||||||
|
w.disp.wakeup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindDataDevice initializes the dataDev field if and only if both
|
// bindDataDevice initializes the dataDev field if and only if both
|
||||||
@@ -1389,13 +1442,21 @@ func (d *wlDisplay) bindDataDevice() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *wlDisplay) dispatch(p *poller) error {
|
func (d *wlDisplay) dispatch() error {
|
||||||
|
// wl_display_prepare_read records the current thread for
|
||||||
|
// use in wl_display_read_events or wl_display_cancel_events.
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
dispfd := C.wl_display_get_fd(d.disp)
|
dispfd := C.wl_display_get_fd(d.disp)
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
pollfds := append(p.pollfds[:0],
|
pollfds := append(d.poller.pollfds[:0],
|
||||||
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
)
|
)
|
||||||
|
for C.wl_display_prepare_read(d.disp) != 0 {
|
||||||
|
C.wl_display_dispatch_pending(d.disp)
|
||||||
|
}
|
||||||
dispFd := &pollfds[0]
|
dispFd := &pollfds[0]
|
||||||
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
||||||
if err != syscall.EAGAIN {
|
if err != syscall.EAGAIN {
|
||||||
@@ -1406,11 +1467,25 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
|||||||
dispFd.Events |= syscall.POLLOUT
|
dispFd.Events |= syscall.POLLOUT
|
||||||
}
|
}
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
return fmt.Errorf("wayland: poll failed: %v", err)
|
return fmt.Errorf("wayland: poll failed: %v", err)
|
||||||
}
|
}
|
||||||
|
if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
|
return errors.New("wayland: display file descriptor gone")
|
||||||
|
}
|
||||||
|
// Handle events.
|
||||||
|
if dispFd.Revents&syscall.POLLIN != 0 {
|
||||||
|
if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
|
||||||
|
return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
|
||||||
|
}
|
||||||
|
C.wl_display_dispatch_pending(d.disp)
|
||||||
|
} else {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
|
}
|
||||||
// Clear notifications.
|
// Clear notifications.
|
||||||
for {
|
for {
|
||||||
_, err := syscall.Read(d.notify.read, p.buf[:])
|
_, err := syscall.Read(d.notify.read, d.poller.buf[:])
|
||||||
if err == syscall.EAGAIN {
|
if err == syscall.EAGAIN {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1418,29 +1493,15 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
|||||||
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle events
|
|
||||||
switch {
|
|
||||||
case dispFd.Revents&syscall.POLLIN != 0:
|
|
||||||
if ret, err := C.wl_display_dispatch(d.disp); ret < 0 {
|
|
||||||
return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err)
|
|
||||||
}
|
|
||||||
case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
|
||||||
return errors.New("wayland: display file descriptor gone")
|
|
||||||
}
|
|
||||||
d.repeat.Repeat(d)
|
d.repeat.Repeat(d)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
w.disp.wakeup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
w.animating = anim
|
w.animating = anim
|
||||||
|
if anim {
|
||||||
|
w.draw(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wakeup wakes up the event loop through the notification pipe.
|
// Wakeup wakes up the event loop through the notification pipe.
|
||||||
@@ -1576,7 +1637,7 @@ func (w *window) flushScroll() {
|
|||||||
if total == (f32.Point{}) {
|
if total == (f32.Point{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1599,7 +1660,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastPos,
|
Position: w.lastPos,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1675,13 +1736,13 @@ func (w *window) updateOutputs() {
|
|||||||
if found && scale != w.scale {
|
if found && scale != w.scale {
|
||||||
w.scale = scale
|
w.scale = scale
|
||||||
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
||||||
w.redraw = true
|
w.draw(true)
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
w.setStage(StagePaused)
|
w.setStage(StagePaused)
|
||||||
} else {
|
} else {
|
||||||
w.setStage(StageRunning)
|
w.setStage(StageRunning)
|
||||||
w.redraw = true
|
w.draw(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1693,7 +1754,7 @@ func (w *window) getConfig() (image.Point, unit.Metric) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw() {
|
func (w *window) draw(sync bool) {
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
size, cfg := w.getConfig()
|
size, cfg := w.getConfig()
|
||||||
if cfg == (unit.Metric{}) {
|
if cfg == (unit.Metric{}) {
|
||||||
@@ -1701,11 +1762,9 @@ func (w *window) draw() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
anim := w.animating || w.fling.anim.Active()
|
anim := w.animating || w.fling.anim.Active()
|
||||||
sync := w.redraw
|
|
||||||
w.redraw = false
|
|
||||||
// Draw animation only when not waiting for frame callback.
|
// Draw animation only when not waiting for frame callback.
|
||||||
redrawAnim := anim && w.lastFrameCallback == nil
|
redrawAnim := anim && w.lastFrameCallback == nil
|
||||||
if !redrawAnim && !sync {
|
if !redrawAnim && !sync {
|
||||||
@@ -1716,7 +1775,7 @@ func (w *window) draw() {
|
|||||||
// Use the surface as listener data for gio_onFrameDone.
|
// Use the surface as listener data for gio_onFrameDone.
|
||||||
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
||||||
}
|
}
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -1731,7 +1790,7 @@ func (w *window) setStage(s Stage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.stage = s
|
w.stage = s
|
||||||
w.w.Event(StageEvent{Stage: s})
|
w.ProcessEvent(StageEvent{Stage: s})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) display() *C.struct_wl_display {
|
func (w *window) display() *C.struct_wl_display {
|
||||||
@@ -1825,6 +1884,10 @@ func newWLDisplay() (*wlDisplay, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *wlDisplay) destroy() {
|
func (d *wlDisplay) destroy() {
|
||||||
|
if d.readClipClose != nil {
|
||||||
|
close(d.readClipClose)
|
||||||
|
d.readClipClose = nil
|
||||||
|
}
|
||||||
if d.notify.write != 0 {
|
if d.notify.write != 0 {
|
||||||
syscall.Close(d.notify.write)
|
syscall.Close(d.notify.write)
|
||||||
d.notify.write = 0
|
d.notify.write = 0
|
||||||
@@ -1866,6 +1929,7 @@ func (d *wlDisplay) destroy() {
|
|||||||
if d.disp != nil {
|
if d.disp != nil {
|
||||||
C.wl_display_disconnect(d.disp)
|
C.wl_display_disconnect(d.disp)
|
||||||
callbackDelete(unsafe.Pointer(d.disp))
|
callbackDelete(unsafe.Pointer(d.disp))
|
||||||
|
d.disp = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+77
-40
@@ -19,10 +19,12 @@ import (
|
|||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/app/internal/windows"
|
"gioui.org/app/internal/windows"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
gowindows "golang.org/x/sys/windows"
|
gowindows "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
@@ -53,6 +55,10 @@ type window struct {
|
|||||||
|
|
||||||
borderSize image.Point
|
borderSize image.Point
|
||||||
config Config
|
config Config
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
|
// invMu avoids the race between destroying the window and Invalidate.
|
||||||
|
invMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
const _WM_WAKEUP = windows.WM_USER + iota
|
const _WM_WAKEUP = windows.WM_USER + iota
|
||||||
@@ -85,36 +91,38 @@ func osMain() {
|
|||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
cerr := make(chan error)
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||||
// then thread-specific messages such as WM_QUIT are ignored.
|
// then thread-specific messages such as WM_QUIT are ignored.
|
||||||
// Instead lock the thread so window messages arrive through
|
// Instead lock the thread so window messages arrive through
|
||||||
// unfiltered GetMessage calls.
|
// unfiltered GetMessage calls.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
w, err := createNativeWindow()
|
|
||||||
|
w := &window{
|
||||||
|
w: win,
|
||||||
|
}
|
||||||
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
err := w.init()
|
||||||
|
done <- struct{}{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cerr <- err
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cerr <- nil
|
|
||||||
winMap.Store(w.hwnd, w)
|
winMap.Store(w.hwnd, w)
|
||||||
defer winMap.Delete(w.hwnd)
|
defer winMap.Delete(w.hwnd)
|
||||||
w.w = window
|
w.ProcessEvent(ViewEvent{HWND: uintptr(w.hwnd)})
|
||||||
w.w.SetDriver(w)
|
|
||||||
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
|
|
||||||
w.Configure(options)
|
w.Configure(options)
|
||||||
windows.SetForegroundWindow(w.hwnd)
|
windows.SetForegroundWindow(w.hwnd)
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
// Since the window class for the cursor is null,
|
// Since the window class for the cursor is null,
|
||||||
// set it here to show the cursor.
|
// set it here to show the cursor.
|
||||||
w.SetCursor(pointer.CursorDefault)
|
w.SetCursor(pointer.CursorDefault)
|
||||||
if err := w.loop(); err != nil {
|
w.runLoop()
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
return <-cerr
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
// initResources initializes the resources global.
|
// initResources initializes the resources global.
|
||||||
@@ -149,13 +157,13 @@ func initResources() error {
|
|||||||
|
|
||||||
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
||||||
|
|
||||||
func createNativeWindow() (*window, error) {
|
func (w *window) init() error {
|
||||||
var resErr error
|
var resErr error
|
||||||
resources.once.Do(func() {
|
resources.once.Do(func() {
|
||||||
resErr = initResources()
|
resErr = initResources()
|
||||||
})
|
})
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return nil, resErr
|
return resErr
|
||||||
}
|
}
|
||||||
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
||||||
|
|
||||||
@@ -171,16 +179,15 @@ func createNativeWindow() (*window, error) {
|
|||||||
resources.handle,
|
resources.handle,
|
||||||
0)
|
0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
|
||||||
w := &window{
|
|
||||||
hwnd: hwnd,
|
|
||||||
}
|
}
|
||||||
w.hdc, err = windows.GetDC(hwnd)
|
w.hdc, err = windows.GetDC(hwnd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
windows.DestroyWindow(hwnd)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return w, nil
|
w.hwnd = hwnd
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// update() handles changes done by the user, and updates the configuration.
|
// update() handles changes done by the user, and updates the configuration.
|
||||||
@@ -197,7 +204,7 @@ func (w *window) update() {
|
|||||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||||
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
||||||
)
|
)
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||||
@@ -238,7 +245,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
e.State = key.Release
|
e.State = key.Release
|
||||||
}
|
}
|
||||||
|
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
|
|
||||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||||
@@ -259,15 +266,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
case windows.WM_MBUTTONUP:
|
case windows.WM_MBUTTONUP:
|
||||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||||
case windows.WM_CANCELMODE:
|
case windows.WM_CANCELMODE:
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
})
|
})
|
||||||
case windows.WM_SETFOCUS:
|
case windows.WM_SETFOCUS:
|
||||||
w.focused = true
|
w.focused = true
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.ProcessEvent(key.FocusEvent{Focus: true})
|
||||||
case windows.WM_KILLFOCUS:
|
case windows.WM_KILLFOCUS:
|
||||||
w.focused = false
|
w.focused = false
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.ProcessEvent(key.FocusEvent{Focus: false})
|
||||||
case windows.WM_NCACTIVATE:
|
case windows.WM_NCACTIVATE:
|
||||||
if w.stage >= StageInactive {
|
if w.stage >= StageInactive {
|
||||||
if wParam == windows.TRUE {
|
if wParam == windows.TRUE {
|
||||||
@@ -288,7 +295,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
case windows.WM_MOUSEMOVE:
|
case windows.WM_MOUSEMOVE:
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
@@ -301,14 +308,16 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
case windows.WM_MOUSEHWHEEL:
|
case windows.WM_MOUSEHWHEEL:
|
||||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||||
case windows.WM_DESTROY:
|
case windows.WM_DESTROY:
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(ViewEvent{})
|
||||||
w.w.Event(DestroyEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
if w.hdc != 0 {
|
if w.hdc != 0 {
|
||||||
windows.ReleaseDC(w.hdc)
|
windows.ReleaseDC(w.hdc)
|
||||||
w.hdc = 0
|
w.hdc = 0
|
||||||
}
|
}
|
||||||
|
w.invMu.Lock()
|
||||||
// The system destroys the HWND for us.
|
// The system destroys the HWND for us.
|
||||||
w.hwnd = 0
|
w.hwnd = 0
|
||||||
|
w.invMu.Unlock()
|
||||||
windows.PostQuitMessage(0)
|
windows.PostQuitMessage(0)
|
||||||
case windows.WM_NCCALCSIZE:
|
case windows.WM_NCCALCSIZE:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
@@ -328,7 +337,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
// Adjust window position to avoid the extra padding in maximized
|
// Adjust window position to avoid the extra padding in maximized
|
||||||
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
||||||
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
||||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
|
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
mi := windows.GetMonitorInfo(w.hwnd)
|
||||||
szp.Rgrc[0] = mi.WorkArea
|
szp.Rgrc[0] = mi.WorkArea
|
||||||
return 0
|
return 0
|
||||||
@@ -350,7 +359,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
w.setStage(StageRunning)
|
w.setStage(StageRunning)
|
||||||
}
|
}
|
||||||
case windows.WM_GETMINMAXINFO:
|
case windows.WM_GETMINMAXINFO:
|
||||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||||
var bw, bh int32
|
var bw, bh int32
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
r := windows.GetWindowRect(w.hwnd)
|
r := windows.GetWindowRect(w.hwnd)
|
||||||
@@ -378,7 +387,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
return windows.TRUE
|
return windows.TRUE
|
||||||
}
|
}
|
||||||
case _WM_WAKEUP:
|
case _WM_WAKEUP:
|
||||||
w.w.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
case windows.WM_IME_STARTCOMPOSITION:
|
case windows.WM_IME_STARTCOMPOSITION:
|
||||||
imc := windows.ImmGetContext(w.hwnd)
|
imc := windows.ImmGetContext(w.hwnd)
|
||||||
if imc == 0 {
|
if imc == 0 {
|
||||||
@@ -518,7 +528,7 @@ func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr,
|
|||||||
}
|
}
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
@@ -553,7 +563,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
|||||||
sp.Y = -dist
|
sp.Y = -dist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
@@ -565,7 +575,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||||
func (w *window) loop() error {
|
func (w *window) runLoop() {
|
||||||
msg := new(windows.Msg)
|
msg := new(windows.Msg)
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
@@ -576,7 +586,7 @@ loop:
|
|||||||
}
|
}
|
||||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||||
case -1:
|
case -1:
|
||||||
return errors.New("GetMessage failed")
|
panic(errors.New("GetMessage failed"))
|
||||||
case 0:
|
case 0:
|
||||||
// WM_QUIT received.
|
// WM_QUIT received.
|
||||||
break loop
|
break loop
|
||||||
@@ -584,7 +594,6 @@ loop:
|
|||||||
windows.TranslateMessage(msg)
|
windows.TranslateMessage(msg)
|
||||||
windows.DispatchMessage(msg)
|
windows.DispatchMessage(msg)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {
|
func (w *window) EditorStateChanged(old, new editorState) {
|
||||||
@@ -602,7 +611,35 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
w.animating = anim
|
w.animating = anim
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) wakeup() {
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
|
if w.hwnd == 0 {
|
||||||
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -611,7 +648,7 @@ func (w *window) Wakeup() {
|
|||||||
func (w *window) setStage(s Stage) {
|
func (w *window) setStage(s Stage) {
|
||||||
if s != w.stage {
|
if s != w.stage {
|
||||||
w.stage = s
|
w.stage = s
|
||||||
w.w.Event(StageEvent{Stage: s})
|
w.ProcessEvent(StageEvent{Stage: s})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +658,7 @@ func (w *window) draw(sync bool) {
|
|||||||
}
|
}
|
||||||
dpi := windows.GetWindowDPI(w.hwnd)
|
dpi := windows.GetWindowDPI(w.hwnd)
|
||||||
cfg := configForDPI(dpi)
|
cfg := configForDPI(dpi)
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -668,7 +705,7 @@ func (w *window) readClipboard() error {
|
|||||||
}
|
}
|
||||||
defer windows.GlobalUnlock(mem)
|
defer windows.GlobalUnlock(mem)
|
||||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
|
|||||||
+113
-76
@@ -38,10 +38,12 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/unix"
|
syscall "golang.org/x/sys/unix"
|
||||||
@@ -98,7 +100,6 @@ type x11Window struct {
|
|||||||
notify struct {
|
notify struct {
|
||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
dead bool
|
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
@@ -111,6 +112,11 @@ type x11Window struct {
|
|||||||
config Config
|
config Config
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
handler x11EventHandler
|
||||||
|
buf [100]byte
|
||||||
|
|
||||||
|
// invMy avoids the race between destroy and Invalidate.
|
||||||
|
invMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -234,7 +240,7 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||||
@@ -377,11 +383,47 @@ func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
|||||||
|
|
||||||
var x11OneByte = make([]byte, 1)
|
var x11OneByte = make([]byte, 1)
|
||||||
|
|
||||||
func (w *x11Window) Wakeup() {
|
func (w *x11Window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) shutdown(err error) {
|
||||||
|
w.ProcessEvent(X11ViewEvent{})
|
||||||
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
w.dispatch()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, destroy := evt.(DestroyEvent); destroy {
|
||||||
|
w.destroy()
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Invalidate() {
|
||||||
select {
|
select {
|
||||||
case w.wakeups <- struct{}{}:
|
case w.wakeups <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
|
if w.x == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||||
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||||
}
|
}
|
||||||
@@ -400,11 +442,23 @@ func (w *x11Window) setStage(s Stage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.stage = s
|
w.stage = s
|
||||||
w.w.Event(StageEvent{Stage: s})
|
w.ProcessEvent(StageEvent{Stage: s})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) loop() {
|
func (w *x11Window) dispatch() {
|
||||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
if w.x == nil {
|
||||||
|
// Only Invalidate can wake us up.
|
||||||
|
<-w.wakeups
|
||||||
|
w.w.Invalidate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-w.wakeups:
|
||||||
|
w.w.Invalidate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
xfd := C.XConnectionNumber(w.x)
|
xfd := C.XConnectionNumber(w.x)
|
||||||
|
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
@@ -414,64 +468,52 @@ func (w *x11Window) loop() {
|
|||||||
}
|
}
|
||||||
xEvents := &pollfds[0].Revents
|
xEvents := &pollfds[0].Revents
|
||||||
// Plenty of room for a backlog of notifications.
|
// Plenty of room for a backlog of notifications.
|
||||||
buf := make([]byte, 100)
|
|
||||||
|
|
||||||
loop:
|
var syn, anim bool
|
||||||
for !w.dead {
|
// Check for pending draw events before checking animation or blocking.
|
||||||
var syn, anim bool
|
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||||
// Check for pending draw events before checking animation or blocking.
|
// poll will still block. This also prevents no-op calls to poll.
|
||||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
if syn = w.handler.handleEvents(); !syn {
|
||||||
// poll will still block. This also prevents no-op calls to poll.
|
anim = w.animating
|
||||||
if syn = h.handleEvents(); !syn {
|
if !anim {
|
||||||
anim = w.animating
|
// Clear poll events.
|
||||||
if !anim {
|
*xEvents = 0
|
||||||
// Clear poll events.
|
// Wait for X event or gio notification.
|
||||||
*xEvents = 0
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
// Wait for X event or gio notification.
|
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
}
|
||||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
switch {
|
||||||
}
|
case *xEvents&syscall.POLLIN != 0:
|
||||||
switch {
|
syn = w.handler.handleEvents()
|
||||||
case *xEvents&syscall.POLLIN != 0:
|
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
syn = h.handleEvents()
|
|
||||||
if w.dead {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear notifications.
|
}
|
||||||
for {
|
// Clear notifications.
|
||||||
_, err := syscall.Read(w.notify.read, buf)
|
for {
|
||||||
if err == syscall.EAGAIN {
|
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||||
break
|
if err == syscall.EAGAIN {
|
||||||
}
|
break
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
select {
|
if err != nil {
|
||||||
case <-w.wakeups:
|
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
|
||||||
w.w.Event(frameEvent{
|
|
||||||
FrameEvent: FrameEvent{
|
|
||||||
Now: time.Now(),
|
|
||||||
Size: w.config.Size,
|
|
||||||
Metric: w.metric,
|
|
||||||
},
|
|
||||||
Sync: syn,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||||
|
w.ProcessEvent(frameEvent{
|
||||||
|
FrameEvent: FrameEvent{
|
||||||
|
Now: time.Now(),
|
||||||
|
Size: w.config.Size,
|
||||||
|
Metric: w.metric,
|
||||||
|
},
|
||||||
|
Sync: syn,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) destroy() {
|
func (w *x11Window) destroy() {
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
if w.notify.write != 0 {
|
if w.notify.write != 0 {
|
||||||
syscall.Close(w.notify.write)
|
syscall.Close(w.notify.write)
|
||||||
w.notify.write = 0
|
w.notify.write = 0
|
||||||
@@ -486,6 +528,7 @@ func (w *x11Window) destroy() {
|
|||||||
}
|
}
|
||||||
C.XDestroyWindow(w.x, w.xw)
|
C.XDestroyWindow(w.x, w.xw)
|
||||||
C.XCloseDisplay(w.x)
|
C.XCloseDisplay(w.x)
|
||||||
|
w.x = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||||
@@ -543,7 +586,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case C.ButtonPress, C.ButtonRelease:
|
case C.ButtonPress, C.ButtonRelease:
|
||||||
@@ -605,10 +648,10 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
w.pointerBtns &^= btn
|
w.pointerBtns &^= btn
|
||||||
}
|
}
|
||||||
ev.Buttons = w.pointerBtns
|
ev.Buttons = w.pointerBtns
|
||||||
w.w.Event(ev)
|
w.ProcessEvent(ev)
|
||||||
case C.MotionNotify:
|
case C.MotionNotify:
|
||||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -623,14 +666,14 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// redraw only on the last expose event
|
// redraw only on the last expose event
|
||||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||||
case C.FocusIn:
|
case C.FocusIn:
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.ProcessEvent(key.FocusEvent{Focus: true})
|
||||||
case C.FocusOut:
|
case C.FocusOut:
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.ProcessEvent(key.FocusEvent{Focus: false})
|
||||||
case C.ConfigureNotify: // window configuration change
|
case C.ConfigureNotify: // window configuration change
|
||||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||||
w.config.Size = sz
|
w.config.Size = sz
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
// redraw will be done by a later expose event
|
// redraw will be done by a later expose event
|
||||||
case C.SelectionNotify:
|
case C.SelectionNotify:
|
||||||
@@ -652,7 +695,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(str))
|
return io.NopCloser(strings.NewReader(str))
|
||||||
@@ -711,7 +754,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||||
case C.long(w.atoms.evDelWindow):
|
case C.long(w.atoms.evDelWindow):
|
||||||
w.dead = true
|
w.shutdown(nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -793,8 +836,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
config: Config{Size: cnf.Size},
|
config: Config{Size: cnf.Size},
|
||||||
}
|
}
|
||||||
|
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||||
w.notify.read = pipe[0]
|
w.notify.read = pipe[0]
|
||||||
w.notify.write = pipe[1]
|
w.notify.write = pipe[1]
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
|
||||||
if err := w.updateXkbKeymap(); err != nil {
|
if err := w.updateXkbKeymap(); err != nil {
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@@ -830,19 +875,11 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
// extensions
|
// extensions
|
||||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||||
|
|
||||||
go func() {
|
// make the window visible on the screen
|
||||||
w.w.SetDriver(w)
|
C.XMapWindow(dpy, win)
|
||||||
|
w.Configure(options)
|
||||||
// make the window visible on the screen
|
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||||
C.XMapWindow(dpy, win)
|
w.setStage(StageRunning)
|
||||||
w.Configure(options)
|
|
||||||
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
|
||||||
w.setStage(StageRunning)
|
|
||||||
w.loop()
|
|
||||||
w.w.Event(X11ViewEvent{})
|
|
||||||
w.w.Event(DestroyEvent{Err: nil})
|
|
||||||
w.destroy()
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+228
-337
@@ -8,6 +8,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@@ -38,32 +39,13 @@ type Option func(unit.Metric, *Config)
|
|||||||
type Window struct {
|
type Window struct {
|
||||||
ctx context
|
ctx context
|
||||||
gpu gpu.GPU
|
gpu gpu.GPU
|
||||||
|
// timer tracks the delayed invalidate goroutine.
|
||||||
// driverFuncs is a channel of functions to run when
|
timer struct {
|
||||||
// the Window has a valid driver.
|
// quit is shuts down the goroutine.
|
||||||
driverFuncs chan func(d driver)
|
quit chan struct{}
|
||||||
// wakeups wakes up the native event loop to send a
|
// update the invalidate time.
|
||||||
// WakeupEvent that flushes driverFuncs.
|
update chan time.Time
|
||||||
wakeups chan struct{}
|
}
|
||||||
// wakeupFuncs is sent wakeup functions when the driver changes.
|
|
||||||
wakeupFuncs chan func()
|
|
||||||
// redraws is notified when a redraw is requested by the client.
|
|
||||||
redraws chan struct{}
|
|
||||||
// immediateRedraws is like redraw but doesn't need a wakeup.
|
|
||||||
immediateRedraws chan struct{}
|
|
||||||
// scheduledRedraws is sent the most recent delayed redraw time.
|
|
||||||
scheduledRedraws chan time.Time
|
|
||||||
// options are the options waiting to be applied.
|
|
||||||
options chan []Option
|
|
||||||
// actions are the actions waiting to be performed.
|
|
||||||
actions chan system.Action
|
|
||||||
|
|
||||||
// out is where the platform backend delivers events bound for the
|
|
||||||
// user program.
|
|
||||||
out chan event.Event
|
|
||||||
frames chan *op.Ops
|
|
||||||
frameAck chan struct{}
|
|
||||||
destroy chan struct{}
|
|
||||||
|
|
||||||
stage Stage
|
stage Stage
|
||||||
animating bool
|
animating bool
|
||||||
@@ -72,8 +54,7 @@ type Window struct {
|
|||||||
// viewport is the latest frame size with insets applied.
|
// viewport is the latest frame size with insets applied.
|
||||||
viewport image.Rectangle
|
viewport image.Rectangle
|
||||||
// metric is the metric from the most recent frame.
|
// metric is the metric from the most recent frame.
|
||||||
metric unit.Metric
|
metric unit.Metric
|
||||||
|
|
||||||
queue input.Router
|
queue input.Router
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
decorations struct {
|
decorations struct {
|
||||||
@@ -89,11 +70,8 @@ type Window struct {
|
|||||||
*material.Theme
|
*material.Theme
|
||||||
*widget.Decorations
|
*widget.Decorations
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks callbacks
|
callbacks callbacks
|
||||||
|
|
||||||
nocontext bool
|
nocontext bool
|
||||||
|
|
||||||
// semantic data, lazily evaluated if requested by a backend to speed up
|
// semantic data, lazily evaluated if requested by a backend to speed up
|
||||||
// the cases where semantic data is not needed.
|
// the cases where semantic data is not needed.
|
||||||
semantic struct {
|
semantic struct {
|
||||||
@@ -104,25 +82,35 @@ type Window struct {
|
|||||||
tree []input.SemanticNode
|
tree []input.SemanticNode
|
||||||
ids map[input.SemanticID]input.SemanticNode
|
ids map[input.SemanticID]input.SemanticNode
|
||||||
}
|
}
|
||||||
|
|
||||||
imeState editorState
|
imeState editorState
|
||||||
|
driver driver
|
||||||
// event stores the state required for processing and delivering events
|
// basic is the driver interface that is needed even after the window is gone.
|
||||||
// from NextEvent. If we had support for range over func, this would
|
basic basicDriver
|
||||||
// be the iterator state.
|
once sync.Once
|
||||||
eventState struct {
|
initialOpts []Option
|
||||||
created bool
|
// coalesced tracks the most recent events waiting to be delivered
|
||||||
initialOpts []Option
|
// to the client.
|
||||||
wakeup func()
|
coalesced eventSummary
|
||||||
timer *time.Timer
|
// frame tracks the most recently frame event.
|
||||||
|
lastFrame struct {
|
||||||
|
sync bool
|
||||||
|
size image.Point
|
||||||
|
off image.Point
|
||||||
|
deco op.CallOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type eventSummary struct {
|
||||||
|
wakeup bool
|
||||||
|
cfg *ConfigEvent
|
||||||
|
view *ViewEvent
|
||||||
|
frame *frameEvent
|
||||||
|
stage *StageEvent
|
||||||
|
destroy *DestroyEvent
|
||||||
|
}
|
||||||
|
|
||||||
type callbacks struct {
|
type callbacks struct {
|
||||||
w *Window
|
w *Window
|
||||||
d driver
|
|
||||||
busy bool
|
|
||||||
waitEvents []event.Event
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWindow creates a new window for a set of window
|
// NewWindow creates a new window for a set of window
|
||||||
@@ -162,19 +150,7 @@ func NewWindow(options ...Option) *Window {
|
|||||||
cnf.apply(unit.Metric{}, options)
|
cnf.apply(unit.Metric{}, options)
|
||||||
|
|
||||||
w := &Window{
|
w := &Window{
|
||||||
out: make(chan event.Event),
|
nocontext: cnf.CustomRenderer,
|
||||||
immediateRedraws: make(chan struct{}),
|
|
||||||
redraws: make(chan struct{}, 1),
|
|
||||||
scheduledRedraws: make(chan time.Time, 1),
|
|
||||||
frames: make(chan *op.Ops),
|
|
||||||
frameAck: make(chan struct{}),
|
|
||||||
driverFuncs: make(chan func(d driver), 1),
|
|
||||||
wakeups: make(chan struct{}, 1),
|
|
||||||
wakeupFuncs: make(chan func()),
|
|
||||||
destroy: make(chan struct{}),
|
|
||||||
options: make(chan []Option, 1),
|
|
||||||
actions: make(chan system.Action, 1),
|
|
||||||
nocontext: cnf.CustomRenderer,
|
|
||||||
}
|
}
|
||||||
w.decorations.Theme = theme
|
w.decorations.Theme = theme
|
||||||
w.decorations.Decorations = deco
|
w.decorations.Decorations = deco
|
||||||
@@ -183,7 +159,7 @@ func NewWindow(options ...Option) *Window {
|
|||||||
w.imeState.compose = key.Range{Start: -1, End: -1}
|
w.imeState.compose = key.Range{Start: -1, End: -1}
|
||||||
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
|
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
|
||||||
w.callbacks.w = w
|
w.callbacks.w = w
|
||||||
w.eventState.initialOpts = options
|
w.initialOpts = options
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,15 +169,7 @@ func decoHeightOpt(h unit.Dp) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the window contents, input operations declare input handlers,
|
func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error {
|
||||||
// and so on. The supplied operations list completely replaces the window state
|
|
||||||
// from previous calls.
|
|
||||||
func (w *Window) update(frame *op.Ops) {
|
|
||||||
w.frames <- frame
|
|
||||||
<-w.frameAck
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) validateAndProcess(d driver, size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error {
|
|
||||||
signal := func() {
|
signal := func() {
|
||||||
if sigChan != nil {
|
if sigChan != nil {
|
||||||
// We're done with frame, let the client continue.
|
// We're done with frame, let the client continue.
|
||||||
@@ -215,7 +183,7 @@ func (w *Window) validateAndProcess(d driver, size image.Point, sync bool, frame
|
|||||||
if w.gpu == nil && !w.nocontext {
|
if w.gpu == nil && !w.nocontext {
|
||||||
var err error
|
var err error
|
||||||
if w.ctx == nil {
|
if w.ctx == nil {
|
||||||
w.ctx, err = d.NewContext()
|
w.ctx, err = w.driver.NewContext()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -294,7 +262,22 @@ func (w *Window) frame(frame *op.Ops, viewport image.Point) error {
|
|||||||
return w.gpu.Frame(frame, target, viewport)
|
return w.gpu.Frame(frame, target, viewport)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) processFrame(d driver) {
|
func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||||
|
wrapper := &w.decorations.Ops
|
||||||
|
off := op.Offset(w.lastFrame.off).Push(wrapper)
|
||||||
|
ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
|
||||||
|
off.Pop()
|
||||||
|
w.lastFrame.deco.Add(wrapper)
|
||||||
|
if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
|
||||||
|
w.destroyGPU()
|
||||||
|
w.driver.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.updateState()
|
||||||
|
w.updateCursor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) updateState() {
|
||||||
for k := range w.semantic.ids {
|
for k := range w.semantic.ids {
|
||||||
delete(w.semantic.ids, k)
|
delete(w.semantic.ids, k)
|
||||||
}
|
}
|
||||||
@@ -302,34 +285,34 @@ func (w *Window) processFrame(d driver) {
|
|||||||
q := &w.queue
|
q := &w.queue
|
||||||
switch q.TextInputState() {
|
switch q.TextInputState() {
|
||||||
case input.TextInputOpen:
|
case input.TextInputOpen:
|
||||||
d.ShowTextInput(true)
|
w.driver.ShowTextInput(true)
|
||||||
case input.TextInputClose:
|
case input.TextInputClose:
|
||||||
d.ShowTextInput(false)
|
w.driver.ShowTextInput(false)
|
||||||
}
|
}
|
||||||
if hint, ok := q.TextInputHint(); ok {
|
if hint, ok := q.TextInputHint(); ok {
|
||||||
d.SetInputHint(hint)
|
w.driver.SetInputHint(hint)
|
||||||
}
|
}
|
||||||
if mime, txt, ok := q.WriteClipboard(); ok {
|
if mime, txt, ok := q.WriteClipboard(); ok {
|
||||||
d.WriteClipboard(mime, txt)
|
w.driver.WriteClipboard(mime, txt)
|
||||||
}
|
}
|
||||||
if q.ClipboardRequested() {
|
if q.ClipboardRequested() {
|
||||||
d.ReadClipboard()
|
w.driver.ReadClipboard()
|
||||||
}
|
}
|
||||||
oldState := w.imeState
|
oldState := w.imeState
|
||||||
newState := oldState
|
newState := oldState
|
||||||
newState.EditorState = q.EditorState()
|
newState.EditorState = q.EditorState()
|
||||||
if newState != oldState {
|
if newState != oldState {
|
||||||
w.imeState = newState
|
w.imeState = newState
|
||||||
d.EditorStateChanged(oldState, newState)
|
w.driver.EditorStateChanged(oldState, newState)
|
||||||
}
|
}
|
||||||
if t, ok := q.WakeupTime(); ok {
|
if t, ok := q.WakeupTime(); ok {
|
||||||
w.setNextFrame(t)
|
w.setNextFrame(t)
|
||||||
}
|
}
|
||||||
w.updateAnimation(d)
|
w.updateAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate the window such that a [FrameEvent] will be generated immediately.
|
// Invalidate the window such that a [FrameEvent] will be generated immediately.
|
||||||
// If the window is inactive, the event is sent when the window becomes active.
|
// If the window is inactive, an unspecified event is sent instead.
|
||||||
//
|
//
|
||||||
// Note that Invalidate is intended for externally triggered updates, such as a
|
// Note that Invalidate is intended for externally triggered updates, such as a
|
||||||
// response from a network request. The [op.InvalidateCmd] command is more efficient
|
// response from a network request. The [op.InvalidateCmd] command is more efficient
|
||||||
@@ -337,16 +320,8 @@ func (w *Window) processFrame(d driver) {
|
|||||||
//
|
//
|
||||||
// Invalidate is safe for concurrent use.
|
// Invalidate is safe for concurrent use.
|
||||||
func (w *Window) Invalidate() {
|
func (w *Window) Invalidate() {
|
||||||
select {
|
w.init()
|
||||||
case w.immediateRedraws <- struct{}{}:
|
w.basic.Invalidate()
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case w.redraws <- struct{}{}:
|
|
||||||
w.wakeup()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option applies the options to the window.
|
// Option applies the options to the window.
|
||||||
@@ -354,15 +329,21 @@ func (w *Window) Option(opts ...Option) {
|
|||||||
if len(opts) == 0 {
|
if len(opts) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
w.Run(func() {
|
||||||
select {
|
cnf := Config{Decorated: w.decorations.enabled}
|
||||||
case old := <-w.options:
|
for _, opt := range opts {
|
||||||
opts = append(old, opts...)
|
opt(w.metric, &cnf)
|
||||||
case w.options <- opts:
|
|
||||||
w.wakeup()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
w.decorations.enabled = cnf.Decorated
|
||||||
|
decoHeight := w.decorations.height
|
||||||
|
if !w.decorations.enabled {
|
||||||
|
decoHeight = 0
|
||||||
|
}
|
||||||
|
opts = append(opts, decoHeightOpt(decoHeight))
|
||||||
|
w.driver.Configure(opts)
|
||||||
|
w.setNextFrame(time.Time{})
|
||||||
|
w.updateAnimation()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run f in the same thread as the native window event loop, and wait for f to
|
// Run f in the same thread as the native window event loop, and wait for f to
|
||||||
@@ -374,52 +355,64 @@ func (w *Window) Option(opts ...Option) {
|
|||||||
// Note that most programs should not call Run; configuring a Window with
|
// Note that most programs should not call Run; configuring a Window with
|
||||||
// [CustomRenderer] is a notable exception.
|
// [CustomRenderer] is a notable exception.
|
||||||
func (w *Window) Run(f func()) {
|
func (w *Window) Run(f func()) {
|
||||||
|
w.init()
|
||||||
|
if w.driver == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
w.driverDefer(func(d driver) {
|
w.driver.Run(func() {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
f()
|
f()
|
||||||
})
|
})
|
||||||
select {
|
<-done
|
||||||
case <-done:
|
|
||||||
case <-w.destroy:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// driverDefer is like Run but can be run from any context. It doesn't wait
|
func (w *Window) updateAnimation() {
|
||||||
// for f to return.
|
if w.driver == nil {
|
||||||
func (w *Window) driverDefer(f func(d driver)) {
|
return
|
||||||
select {
|
|
||||||
case w.driverFuncs <- f:
|
|
||||||
w.wakeup()
|
|
||||||
case <-w.destroy:
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) updateAnimation(d driver) {
|
|
||||||
animate := false
|
animate := false
|
||||||
if w.stage >= StageInactive && w.hasNextFrame {
|
if w.stage >= StageInactive && w.hasNextFrame {
|
||||||
if dt := time.Until(w.nextFrame); dt <= 0 {
|
if dt := time.Until(w.nextFrame); dt <= 0 {
|
||||||
animate = true
|
animate = true
|
||||||
} else {
|
} else {
|
||||||
// Schedule redraw.
|
// Schedule redraw.
|
||||||
select {
|
w.scheduleInvalidate(w.nextFrame)
|
||||||
case <-w.scheduledRedraws:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
w.scheduledRedraws <- w.nextFrame
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if animate != w.animating {
|
if animate != w.animating {
|
||||||
w.animating = animate
|
w.animating = animate
|
||||||
d.SetAnimating(animate)
|
w.driver.SetAnimating(animate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) wakeup() {
|
func (w *Window) scheduleInvalidate(t time.Time) {
|
||||||
select {
|
if w.timer.quit == nil {
|
||||||
case w.wakeups <- struct{}{}:
|
w.timer.quit = make(chan struct{})
|
||||||
default:
|
w.timer.update = make(chan time.Time)
|
||||||
|
go func() {
|
||||||
|
var timer *time.Timer
|
||||||
|
for {
|
||||||
|
var timeC <-chan time.Time
|
||||||
|
if timer != nil {
|
||||||
|
timeC = timer.C
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.timer.quit:
|
||||||
|
w.timer.quit <- struct{}{}
|
||||||
|
return
|
||||||
|
case t := <-w.timer.update:
|
||||||
|
if timer != nil {
|
||||||
|
timer.Stop()
|
||||||
|
}
|
||||||
|
timer = time.NewTimer(time.Until(t))
|
||||||
|
case <-timeC:
|
||||||
|
w.Invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
w.timer.update <- t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) setNextFrame(at time.Time) {
|
func (w *Window) setNextFrame(at time.Time) {
|
||||||
@@ -429,61 +422,19 @@ func (w *Window) setNextFrame(at time.Time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) SetDriver(d driver) {
|
func (c *callbacks) SetDriver(d basicDriver) {
|
||||||
c.d = d
|
c.w.basic = d
|
||||||
var wakeup func()
|
if d, ok := d.(driver); ok {
|
||||||
if d != nil {
|
c.w.driver = d
|
||||||
wakeup = d.Wakeup
|
|
||||||
}
|
}
|
||||||
c.w.wakeupFuncs <- wakeup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) Event(e event.Event) bool {
|
func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
|
||||||
if c.d == nil {
|
c.w.processFrame(frame, ack)
|
||||||
panic("event while no driver active")
|
}
|
||||||
}
|
|
||||||
c.waitEvents = append(c.waitEvents, e)
|
func (c *callbacks) ProcessEvent(e event.Event) bool {
|
||||||
if c.busy {
|
return c.w.processEvent(e)
|
||||||
return true
|
|
||||||
}
|
|
||||||
c.busy = true
|
|
||||||
var handled bool
|
|
||||||
for len(c.waitEvents) > 0 {
|
|
||||||
e := c.waitEvents[0]
|
|
||||||
copy(c.waitEvents, c.waitEvents[1:])
|
|
||||||
c.waitEvents = c.waitEvents[:len(c.waitEvents)-1]
|
|
||||||
handled = c.w.processEvent(c.d, e)
|
|
||||||
}
|
|
||||||
c.busy = false
|
|
||||||
select {
|
|
||||||
case <-c.w.destroy:
|
|
||||||
return handled
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
c.w.updateState(c.d)
|
|
||||||
if _, ok := e.(wakeupEvent); ok {
|
|
||||||
select {
|
|
||||||
case opts := <-c.w.options:
|
|
||||||
cnf := Config{Decorated: c.w.decorations.enabled}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(c.w.metric, &cnf)
|
|
||||||
}
|
|
||||||
c.w.decorations.enabled = cnf.Decorated
|
|
||||||
decoHeight := c.w.decorations.height
|
|
||||||
if !c.w.decorations.enabled {
|
|
||||||
decoHeight = 0
|
|
||||||
}
|
|
||||||
opts = append(opts, decoHeightOpt(decoHeight))
|
|
||||||
c.d.Configure(opts)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case acts := <-c.w.actions:
|
|
||||||
c.d.Perform(acts)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return handled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SemanticRoot returns the ID of the semantic root.
|
// SemanticRoot returns the ID of the semantic root.
|
||||||
@@ -534,13 +485,13 @@ func (c *callbacks) EditorInsert(text string) {
|
|||||||
|
|
||||||
func (c *callbacks) EditorReplace(r key.Range, text string) {
|
func (c *callbacks) EditorReplace(r key.Range, text string) {
|
||||||
c.w.imeState.Replace(r, text)
|
c.w.imeState.Replace(r, text)
|
||||||
c.Event(key.EditEvent{Range: r, Text: text})
|
c.w.driver.ProcessEvent(key.EditEvent{Range: r, Text: text})
|
||||||
c.Event(key.SnippetEvent(c.w.imeState.Snippet.Range))
|
c.w.driver.ProcessEvent(key.SnippetEvent(c.w.imeState.Snippet.Range))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) SetEditorSelection(r key.Range) {
|
func (c *callbacks) SetEditorSelection(r key.Range) {
|
||||||
c.w.imeState.Selection.Range = r
|
c.w.imeState.Selection.Range = r
|
||||||
c.Event(key.SelectionEvent(r))
|
c.w.driver.ProcessEvent(key.SelectionEvent(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) SetEditorSnippet(r key.Range) {
|
func (c *callbacks) SetEditorSnippet(r key.Range) {
|
||||||
@@ -548,7 +499,7 @@ func (c *callbacks) SetEditorSnippet(r key.Range) {
|
|||||||
// No need to expand.
|
// No need to expand.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Event(key.SnippetEvent(r))
|
c.w.driver.ProcessEvent(key.SnippetEvent(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) moveFocus(dir key.FocusDirection) {
|
func (w *Window) moveFocus(dir key.FocusDirection) {
|
||||||
@@ -578,29 +529,13 @@ func (w *Window) moveFocus(dir key.FocusDirection) {
|
|||||||
func (c *callbacks) ClickFocus() {
|
func (c *callbacks) ClickFocus() {
|
||||||
c.w.queue.ClickFocus()
|
c.w.queue.ClickFocus()
|
||||||
c.w.setNextFrame(time.Time{})
|
c.w.setNextFrame(time.Time{})
|
||||||
c.w.updateAnimation(c.d)
|
c.w.updateAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) {
|
func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) {
|
||||||
return c.w.queue.ActionAt(p)
|
return c.w.queue.ActionAt(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) waitAck(d driver) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case f := <-w.driverFuncs:
|
|
||||||
f(d)
|
|
||||||
case w.out <- theFlushEvent:
|
|
||||||
// A dummy event went through, so we know the application has processed the previous event.
|
|
||||||
return
|
|
||||||
case <-w.immediateRedraws:
|
|
||||||
// Invalidate was called during frame processing.
|
|
||||||
w.setNextFrame(time.Time{})
|
|
||||||
w.updateAnimation(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Window) destroyGPU() {
|
func (w *Window) destroyGPU() {
|
||||||
if w.gpu != nil {
|
if w.gpu != nil {
|
||||||
w.ctx.Lock()
|
w.ctx.Lock()
|
||||||
@@ -614,27 +549,6 @@ func (w *Window) destroyGPU() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitFrame waits for the client to either call [FrameEvent.Frame]
|
|
||||||
// or to continue event handling.
|
|
||||||
func (w *Window) waitFrame(d driver) *op.Ops {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case f := <-w.driverFuncs:
|
|
||||||
f(d)
|
|
||||||
case frame := <-w.frames:
|
|
||||||
// The client called FrameEvent.Frame.
|
|
||||||
return frame
|
|
||||||
case w.out <- theFlushEvent:
|
|
||||||
// The client ignored FrameEvent and continued processing
|
|
||||||
// events.
|
|
||||||
return nil
|
|
||||||
case <-w.immediateRedraws:
|
|
||||||
// Invalidate was called during frame processing.
|
|
||||||
w.setNextFrame(time.Time{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateSemantics refreshes the semantics tree, the id to node map and the ids of
|
// updateSemantics refreshes the semantics tree, the id to node map and the ids of
|
||||||
// updated nodes.
|
// updated nodes.
|
||||||
func (w *Window) updateSemantics() {
|
func (w *Window) updateSemantics() {
|
||||||
@@ -671,26 +585,46 @@ func (w *Window) collectSemanticDiffs(diffs *[]input.SemanticID, n input.Semanti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) updateState(d driver) {
|
func (c *callbacks) Invalidate() {
|
||||||
for {
|
c.w.setNextFrame(time.Time{})
|
||||||
select {
|
c.w.updateAnimation()
|
||||||
case f := <-w.driverFuncs:
|
// Guarantee a wakeup, even when not animating.
|
||||||
f(d)
|
c.w.processEvent(wakeupEvent{})
|
||||||
case <-w.redraws:
|
|
||||||
w.setNextFrame(time.Time{})
|
|
||||||
w.updateAnimation(d)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) processEvent(d driver, e event.Event) bool {
|
func (c *callbacks) nextEvent() (event.Event, bool) {
|
||||||
select {
|
s := &c.w.coalesced
|
||||||
case <-w.destroy:
|
// Every event counts as a wakeup.
|
||||||
return false
|
defer func() { s.wakeup = false }()
|
||||||
default:
|
switch {
|
||||||
|
case s.view != nil:
|
||||||
|
e := *s.view
|
||||||
|
s.view = nil
|
||||||
|
return e, true
|
||||||
|
case s.destroy != nil:
|
||||||
|
e := *s.destroy
|
||||||
|
// Clear pending events after DestroyEvent is delivered.
|
||||||
|
*s = eventSummary{}
|
||||||
|
return e, true
|
||||||
|
case s.cfg != nil:
|
||||||
|
e := *s.cfg
|
||||||
|
s.cfg = nil
|
||||||
|
return e, true
|
||||||
|
case s.stage != nil:
|
||||||
|
e := *s.stage
|
||||||
|
s.stage = nil
|
||||||
|
return e, true
|
||||||
|
case s.frame != nil:
|
||||||
|
e := *s.frame
|
||||||
|
s.frame = nil
|
||||||
|
return e.FrameEvent, true
|
||||||
|
case s.wakeup:
|
||||||
|
return wakeupEvent{}, true
|
||||||
}
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) processEvent(e event.Event) bool {
|
||||||
switch e2 := e.(type) {
|
switch e2 := e.(type) {
|
||||||
case StageEvent:
|
case StageEvent:
|
||||||
if e2.Stage < StageInactive {
|
if e2.Stage < StageInactive {
|
||||||
@@ -702,9 +636,10 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.stage = e2.Stage
|
w.stage = e2.Stage
|
||||||
w.updateAnimation(d)
|
w.updateAnimation()
|
||||||
w.out <- e
|
w.coalesced.stage = &e2
|
||||||
w.waitAck(d)
|
case wakeupEvent:
|
||||||
|
w.coalesced.wakeup = true
|
||||||
case frameEvent:
|
case frameEvent:
|
||||||
if e2.Size == (image.Point{}) {
|
if e2.Size == (image.Point{}) {
|
||||||
panic(errors.New("internal error: zero-sized Draw"))
|
panic(errors.New("internal error: zero-sized Draw"))
|
||||||
@@ -715,12 +650,9 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
}
|
}
|
||||||
w.metric = e2.Metric
|
w.metric = e2.Metric
|
||||||
w.hasNextFrame = false
|
w.hasNextFrame = false
|
||||||
e2.Frame = w.update
|
e2.Frame = w.driver.Frame
|
||||||
e2.Source = w.queue.Source()
|
e2.Source = w.queue.Source()
|
||||||
|
|
||||||
// Prepare the decorations and update the frame insets.
|
// Prepare the decorations and update the frame insets.
|
||||||
wrapper := &w.decorations.Ops
|
|
||||||
wrapper.Reset()
|
|
||||||
viewport := image.Rectangle{
|
viewport := image.Rectangle{
|
||||||
Min: image.Point{
|
Min: image.Point{
|
||||||
X: e2.Metric.Dp(e2.Insets.Left),
|
X: e2.Metric.Dp(e2.Insets.Left),
|
||||||
@@ -736,41 +668,30 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
w.queue.RevealFocus(viewport)
|
w.queue.RevealFocus(viewport)
|
||||||
}
|
}
|
||||||
w.viewport = viewport
|
w.viewport = viewport
|
||||||
viewSize := e2.Size
|
wrapper := &w.decorations.Ops
|
||||||
|
wrapper.Reset()
|
||||||
m := op.Record(wrapper)
|
m := op.Record(wrapper)
|
||||||
size, offset := w.decorate(d, e2.FrameEvent, wrapper)
|
offset := w.decorate(e2.FrameEvent, wrapper)
|
||||||
e2.FrameEvent.Size = size
|
w.lastFrame.deco = m.Stop()
|
||||||
deco := m.Stop()
|
w.lastFrame.size = e2.Size
|
||||||
w.out <- e2.FrameEvent
|
w.lastFrame.sync = e2.Sync
|
||||||
frame := w.waitFrame(d)
|
w.lastFrame.off = offset
|
||||||
var signal chan<- struct{}
|
e2.Size = e2.Size.Sub(offset)
|
||||||
if frame != nil {
|
w.coalesced.frame = &e2
|
||||||
signal = w.frameAck
|
|
||||||
off := op.Offset(offset).Push(wrapper)
|
|
||||||
ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
|
|
||||||
off.Pop()
|
|
||||||
}
|
|
||||||
deco.Add(wrapper)
|
|
||||||
if err := w.validateAndProcess(d, viewSize, e2.Sync, wrapper, signal); err != nil {
|
|
||||||
w.destroyGPU()
|
|
||||||
w.out <- DestroyEvent{Err: err}
|
|
||||||
close(w.destroy)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
w.processFrame(d)
|
|
||||||
w.updateCursor(d)
|
|
||||||
case DestroyEvent:
|
case DestroyEvent:
|
||||||
w.destroyGPU()
|
w.destroyGPU()
|
||||||
w.out <- e2
|
w.driver = nil
|
||||||
close(w.destroy)
|
if q := w.timer.quit; q != nil {
|
||||||
|
q <- struct{}{}
|
||||||
|
<-q
|
||||||
|
}
|
||||||
|
w.coalesced.destroy = &e2
|
||||||
case ViewEvent:
|
case ViewEvent:
|
||||||
w.out <- e2
|
w.coalesced.view = &e2
|
||||||
w.waitAck(d)
|
|
||||||
case ConfigEvent:
|
case ConfigEvent:
|
||||||
w.decorations.Config = e2.Config
|
w.decorations.Config = e2.Config
|
||||||
e2.Config = w.effectiveConfig()
|
e2.Config = w.effectiveConfig()
|
||||||
w.out <- e2
|
w.coalesced.cfg = &e2
|
||||||
case wakeupEvent:
|
|
||||||
case event.Event:
|
case event.Event:
|
||||||
focusDir := key.FocusDirection(-1)
|
focusDir := key.FocusDirection(-1)
|
||||||
if e, ok := e2.(key.Event); ok && e.State == key.Press {
|
if e, ok := e2.(key.Event); ok && e.State == key.Press {
|
||||||
@@ -800,10 +721,10 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
w.moveFocus(focusDir)
|
w.moveFocus(focusDir)
|
||||||
t, handled = w.queue.WakeupTime()
|
t, handled = w.queue.WakeupTime()
|
||||||
}
|
}
|
||||||
w.updateCursor(d)
|
w.updateCursor()
|
||||||
if handled {
|
if handled {
|
||||||
w.setNextFrame(t)
|
w.setNextFrame(t)
|
||||||
w.updateAnimation(d)
|
w.updateAnimation()
|
||||||
}
|
}
|
||||||
return handled
|
return handled
|
||||||
}
|
}
|
||||||
@@ -811,58 +732,22 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NextEvent blocks until an event is received from the window, such as
|
// NextEvent blocks until an event is received from the window, such as
|
||||||
// [FrameEvent]. It blocks forever if called after [DestroyEvent]
|
// [FrameEvent], or until [Invalidate] is called.
|
||||||
// has been returned.
|
|
||||||
func (w *Window) NextEvent() event.Event {
|
func (w *Window) NextEvent() event.Event {
|
||||||
state := &w.eventState
|
w.init()
|
||||||
if !state.created {
|
return w.basic.Event()
|
||||||
state.created = true
|
|
||||||
if err := newWindow(&w.callbacks, state.initialOpts); err != nil {
|
|
||||||
close(w.destroy)
|
|
||||||
return DestroyEvent{Err: err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
var (
|
|
||||||
wakeups <-chan struct{}
|
|
||||||
timeC <-chan time.Time
|
|
||||||
)
|
|
||||||
if state.wakeup != nil {
|
|
||||||
wakeups = w.wakeups
|
|
||||||
if state.timer != nil {
|
|
||||||
timeC = state.timer.C
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case t := <-w.scheduledRedraws:
|
|
||||||
if state.timer != nil {
|
|
||||||
state.timer.Stop()
|
|
||||||
}
|
|
||||||
state.timer = time.NewTimer(time.Until(t))
|
|
||||||
case e := <-w.out:
|
|
||||||
// Receiving a flushEvent indicates to the platform backend that
|
|
||||||
// all previous events have been processed by the user program.
|
|
||||||
if _, ok := e.(flushEvent); ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
case <-timeC:
|
|
||||||
select {
|
|
||||||
case w.redraws <- struct{}{}:
|
|
||||||
state.wakeup()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
case <-wakeups:
|
|
||||||
state.wakeup()
|
|
||||||
case state.wakeup = <-w.wakeupFuncs:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) updateCursor(d driver) {
|
func (w *Window) init() {
|
||||||
|
w.once.Do(func() {
|
||||||
|
newWindow(&w.callbacks, w.initialOpts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) updateCursor() {
|
||||||
if c := w.queue.Cursor(); c != w.cursor {
|
if c := w.queue.Cursor(); c != w.cursor {
|
||||||
w.cursor = c
|
w.cursor = c
|
||||||
d.SetCursor(c)
|
w.driver.SetCursor(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,9 +757,9 @@ func (w *Window) fallbackDecorate() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decorate the window if enabled and returns the corresponding Insets.
|
// decorate the window if enabled and returns the corresponding Insets.
|
||||||
func (w *Window) decorate(d driver, e FrameEvent, o *op.Ops) (size, offset image.Point) {
|
func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
|
||||||
if !w.fallbackDecorate() {
|
if !w.fallbackDecorate() {
|
||||||
return e.Size, image.Pt(0, 0)
|
return image.Pt(0, 0)
|
||||||
}
|
}
|
||||||
deco := w.decorations.Decorations
|
deco := w.decorations.Decorations
|
||||||
allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
|
allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
|
||||||
@@ -903,16 +788,17 @@ func (w *Window) decorate(d driver, e FrameEvent, o *op.Ops) (size, offset image
|
|||||||
Constraints: layout.Exact(e.Size),
|
Constraints: layout.Exact(e.Size),
|
||||||
}
|
}
|
||||||
// Update the window based on the actions on the decorations.
|
// Update the window based on the actions on the decorations.
|
||||||
w.Perform(deco.Update(gtx))
|
opts, acts := splitActions(deco.Update(gtx))
|
||||||
|
w.driver.Configure(opts)
|
||||||
|
w.driver.Perform(acts)
|
||||||
style.Layout(gtx)
|
style.Layout(gtx)
|
||||||
// Offset to place the frame content below the decorations.
|
// Offset to place the frame content below the decorations.
|
||||||
decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
|
decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
|
||||||
if w.decorations.currentHeight != decoHeight {
|
if w.decorations.currentHeight != decoHeight {
|
||||||
w.decorations.currentHeight = decoHeight
|
w.decorations.currentHeight = decoHeight
|
||||||
w.out <- ConfigEvent{Config: w.effectiveConfig()}
|
w.coalesced.cfg = &ConfigEvent{Config: w.effectiveConfig()}
|
||||||
}
|
}
|
||||||
e.Size.Y -= w.decorations.currentHeight
|
return image.Pt(0, decoHeight)
|
||||||
return e.Size, image.Pt(0, decoHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) effectiveConfig() Config {
|
func (w *Window) effectiveConfig() Config {
|
||||||
@@ -922,33 +808,38 @@ func (w *Window) effectiveConfig() Config {
|
|||||||
return cnf
|
return cnf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the actions on the window.
|
// splitActions splits options from actions and return them and the remaining
|
||||||
func (w *Window) Perform(actions system.Action) {
|
// actions.
|
||||||
|
func splitActions(actions system.Action) ([]Option, system.Action) {
|
||||||
|
var opts []Option
|
||||||
walkActions(actions, func(action system.Action) {
|
walkActions(actions, func(action system.Action) {
|
||||||
switch action {
|
switch action {
|
||||||
case system.ActionMinimize:
|
case system.ActionMinimize:
|
||||||
w.Option(Minimized.Option())
|
opts = append(opts, Minimized.Option())
|
||||||
case system.ActionMaximize:
|
case system.ActionMaximize:
|
||||||
w.Option(Maximized.Option())
|
opts = append(opts, Maximized.Option())
|
||||||
case system.ActionUnmaximize:
|
case system.ActionUnmaximize:
|
||||||
w.Option(Windowed.Option())
|
opts = append(opts, Windowed.Option())
|
||||||
|
case system.ActionFullscreen:
|
||||||
|
opts = append(opts, Fullscreen.Option())
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
actions &^= action
|
actions &^= action
|
||||||
})
|
})
|
||||||
if actions == 0 {
|
return opts, actions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the actions on the window.
|
||||||
|
func (w *Window) Perform(actions system.Action) {
|
||||||
|
opts, acts := splitActions(actions)
|
||||||
|
w.Option(opts...)
|
||||||
|
if acts == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
w.Run(func() {
|
||||||
select {
|
w.driver.Perform(actions)
|
||||||
case old := <-w.actions:
|
})
|
||||||
actions |= old
|
|
||||||
case w.actions <- actions:
|
|
||||||
w.wakeup()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title sets the title of the window.
|
// Title sets the title of the window.
|
||||||
|
|||||||
Reference in New Issue
Block a user