20 Commits

Author SHA1 Message Date
Elias Naur 3879921b80 app: [API] remove Main
All platforms already allow the omission of the call to Main and running
Windows on the main goroutine. This change just gets rid of Main, and
documents the special requirement on Window.Event.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:53:33 +00:00
Elias Naur b1f84da679 app: [macOS] implement custom event dispatching
To get rid of app.Main, we need to control the main thread. The macOS
[NSApp run] must be called on the main goroutine and never yields control.

Implement the escape hatch which is calling [NSApp stop] to force
[NSApp run] to return and allow us to fetch and dispatch events one at a
time.

This change is separate from the larger change removing app.Main to ease
bisecting.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:53:12 +00:00
Elias Naur 43024fcca2 app: [macOS] implement non-blocking window resizing
Despite the option to control the main thread event loop, some gestures
still block the event loop until completion. This change disables the
blocking resize gestures and implements a non-blocking replacement.

A complete replacement is left for future work, or the implementation of
https://github.com/golang/go/issues/64755.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur 9fe8b684e2 app: introduce Config.Focused that tracks the window focus state
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur 2a18a0c135 app: [macOS] synchronize rendering with Core Animation for smooth resizes
Magic incantations lifted from

https://thume.ca/2019/06/19/glitchless-metal-window-resizing/

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur 93ac0b03f1 app: [API] rename Window.NextEvent to Event to match Source.Event
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur d58d386b9b app: [API] remove StageEvent and Stage
StageEvent served only redundant purposes:

- To detect whether the window has focus. That is covered by
  key.FocusEvent.
- To detect whether the window is currently visible. That is covered by
  the absence or presence of FrameEvents.
- To detect when the window native handle is valid. That is
  covered by ViewEvent.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:52:04 +00:00
Elias Naur f3fc0d62b8 app: [API] make ViewEvent an interface on all platforms
A uniform type allows convenient nil checks and for future window
backends on platforms other than Linux (which already had ViewEvent
as an interface).

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:51:38 +00:00
Elias Naur 5e5d164929 app: [macOS] move destruction to NSView.dealloc
The dealloc method is where we're guaranteed the NSView is no longer
used anywhere.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur 1527e91a02 app: [macOS] send ViewEvents when the NSView is attached to a NSWindow
Instead of sending ViewEvents once at construction and once at destruction,
it's better to send them when the underlying NSView changes attachment.

The main advantage is that we're about to move the destruction and
emitting of DestroyEvent to the NSView's dealloc method. However, the
dealloc will not be called if user code has a strong reference to it
through a non-empty ViewEvent. By sending an empty ViewEvent when the
view is detached, well-behaving users will remove the strong reference.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur caba422d9c app: [macOS] make gio_trySetPrivateCursor static, remove its prefix
While here, don't use trySetPrivateCursor for the public openHandCursor
and closedHandCursor.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur 390242f214 app: [macOS] add missing autoreleasepools
Their absense didn't make a practical difference so far, but we're about
to refactor the macOS event processing loop where the pools do matter.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur fe1df00d02 app: merge with internal log package to remove the separate log.appID
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur 0d7f00c634 app: [macOS] use cgo.Handle for referring to Go windows from native code
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur d7528a8338 app: [macOS] use NSNotificationCenter to receive app events
Notifications don't require a list of windows nor an app delegate.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur 9bca5bfdcf app: [iOS] use cgo.Handle for referring to Go windows from native code
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur a880d6403d app: [API] make the zero-value Window useful and delete NewWindow
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 18:45:26 +00:00
Elias Naur 6879a30582 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>
2024-02-08 18:45:15 +00:00
Elias Naur 5cda660e6e app: slim down window.go by moving editorState to separate file
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 17:21:59 +00:00
Elias Naur 8cb06ffa30 app: [Wayland] fix reference to most recent metric
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-02-08 17:21:59 +00:00
29 changed files with 1963 additions and 1444 deletions
+1 -1
View File
@@ -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;
+7 -12
View File
@@ -56,6 +56,13 @@ type FrameEvent struct {
Source input.Source Source input.Source
} }
// ViewEvent provides handles to the underlying window objects for the
// current display protocol.
type ViewEvent interface {
implementsViewEvent()
ImplementsEvent()
}
// Insets is the space taken up by // Insets is the space taken up by
// system decoration such as translucent // system decoration such as translucent
// system bars and software keyboards. // system bars and software keyboards.
@@ -113,18 +120,6 @@ func DataDir() (string, error) {
return dataDir() return dataDir()
} }
// Main must be called last from the program main function.
// On most platforms Main blocks forever, for Android and
// iOS it returns immediately to give control of the main
// thread back to the system.
//
// Calling Main is necessary because some operating systems
// require control of the main thread of the program for
// running windows.
func Main() {
osMain()
}
func (FrameEvent) ImplementsEvent() {} func (FrameEvent) ImplementsEvent() {}
func init() { func init() {
+13 -28
View File
@@ -8,18 +8,19 @@ See https://gioui.org for instructions to set up and run Gio programs.
# Windows # Windows
Create a new [Window] by calling [NewWindow]. On mobile platforms or when Gio A Window is run by calling its Event method in a loop. The first time a
is embedded in another project, NewWindow merely connects with a previously method on Window is called, a new GUI window is created and shown. On mobile
created window. platforms or when Gio is embedded in another project, Window merely connects
with a previously created GUI window.
A Window is run by calling its NextEvent method in a loop. The most important event is The most important event is [FrameEvent] that prompts an update of the window
[FrameEvent] that prompts an update of the window contents. contents.
For example: For example:
w := app.NewWindow() w := new(app.Window)
for { for {
e := w.NextEvent() e := w.Event()
if e, ok := e.(app.FrameEvent); ok { if e, ok := e.(app.FrameEvent); ok {
ops.Reset() ops.Reset()
// Add operations to ops. // Add operations to ops.
@@ -32,28 +33,12 @@ For example:
A program must keep receiving events from the event channel until A program must keep receiving events from the event channel until
[DestroyEvent] is received. [DestroyEvent] is received.
# Main # Main Thread
The Main function must be called from a program's main function, to hand over Some GUI platform need access to the main thread of the program. To avoid a
control of the main thread to operating systems that need it. deadlock on such platforms, at least one Window must have its Event method
called by the main goroutine. It doesn't have to be any particular Window;
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine. even a destroyed Window suffices.
For example, to display a blank but otherwise functional window:
package main
import "gioui.org/app"
func main() {
go func() {
w := app.NewWindow()
for {
w.NextEvent()
}
}()
app.Main()
}
# Permissions # Permissions
+11 -1
View File
@@ -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
View File
@@ -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
} }
+118
View File
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: Unlicense OR MIT
package app
import (
"unicode"
"unicode/utf16"
"gioui.org/io/input"
"gioui.org/io/key"
)
type editorState struct {
input.EditorState
compose key.Range
}
func (e *editorState) Replace(r key.Range, text string) {
if r.Start > r.End {
r.Start, r.End = r.End, r.Start
}
runes := []rune(text)
newEnd := r.Start + len(runes)
adjust := func(pos int) int {
switch {
case newEnd < pos && pos <= r.End:
return newEnd
case r.End < pos:
diff := newEnd - r.End
return pos + diff
}
return pos
}
e.Selection.Start = adjust(e.Selection.Start)
e.Selection.End = adjust(e.Selection.End)
if e.compose.Start != -1 {
e.compose.Start = adjust(e.compose.Start)
e.compose.End = adjust(e.compose.End)
}
s := e.Snippet
if r.End < s.Start || r.Start > s.End {
// Discard snippet if it doesn't overlap with replacement.
s = key.Snippet{
Range: key.Range{
Start: r.Start,
End: r.Start,
},
}
}
var newSnippet []rune
snippet := []rune(s.Text)
// Append first part of existing snippet.
if end := r.Start - s.Start; end > 0 {
newSnippet = append(newSnippet, snippet[:end]...)
}
// Append replacement.
newSnippet = append(newSnippet, runes...)
// Append last part of existing snippet.
if start := r.End; start < s.End {
newSnippet = append(newSnippet, snippet[start-s.Start:]...)
}
// Adjust snippet range to include replacement.
if r.Start < s.Start {
s.Start = r.Start
}
s.End = s.Start + len(newSnippet)
s.Text = string(newSnippet)
e.Snippet = s
}
// UTF16Index converts the given index in runes into an index in utf16 characters.
func (e *editorState) UTF16Index(runes int) int {
if runes == -1 {
return -1
}
if runes < e.Snippet.Start {
// Assume runes before sippet are one UTF-16 character each.
return runes
}
chars := e.Snippet.Start
runes -= e.Snippet.Start
for _, r := range e.Snippet.Text {
if runes == 0 {
break
}
runes--
chars++
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
chars++
}
}
// Assume runes after snippets are one UTF-16 character each.
return chars + runes
}
// RunesIndex converts the given index in utf16 characters to an index in runes.
func (e *editorState) RunesIndex(chars int) int {
if chars == -1 {
return -1
}
if chars < e.Snippet.Start {
// Assume runes before offset are one UTF-16 character each.
return chars
}
runes := e.Snippet.Start
chars -= e.Snippet.Start
for _, r := range e.Snippet.Text {
if chars == 0 {
break
}
chars--
runes++
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
chars--
}
}
// Assume runes after snippets are one UTF-16 character each.
return runes + chars
}
-7
View File
@@ -1,7 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
// Package points standard output, standard error and the standard
// library package log to the platform logger.
package log
var appID = "gio"
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Unlicense OR MIT // SPDX-License-Identifier: Unlicense OR MIT
package log package app
/* /*
#cgo LDFLAGS: -llog #cgo LDFLAGS: -llog
@@ -22,7 +22,7 @@ import (
// 1024 is the truncation limit from android/log.h, plus a \n. // 1024 is the truncation limit from android/log.h, plus a \n.
const logLineLimit = 1024 const logLineLimit = 1024
var logTag = C.CString(appID) var logTag = C.CString(ID)
func init() { func init() {
// Android's logcat already includes timestamps. // Android's logcat already includes timestamps.
@@ -3,7 +3,7 @@
//go:build darwin && ios //go:build darwin && ios
// +build darwin,ios // +build darwin,ios
package log package app
/* /*
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c #cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Unlicense OR MIT // SPDX-License-Identifier: Unlicense OR MIT
package log package app
import ( import (
"log" "log"
+2 -1
View File
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef; id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef; id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer]; id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
[cmdBuffer presentDrawable:drawable];
[cmdBuffer commit]; [cmdBuffer commit];
[cmdBuffer waitUntilScheduled];
[drawable present];
} }
} }
+4 -1
View File
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
static CFTypeRef getMetalLayer(CFTypeRef viewRef) { static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
@autoreleasepool { @autoreleasepool {
UIView *view = (__bridge UIView *)viewRef; UIView *view = (__bridge UIView *)viewRef;
return CFBridgingRetain(view.layer); CAMetalLayer *l = (CAMetalLayer *)view.layer;
l.needsDisplayOnBoundsChange = YES;
l.presentsWithTransaction = YES;
return CFBridgingRetain(l);
} }
} }
+5 -1
View File
@@ -14,7 +14,11 @@ package app
CALayer *gio_layerFactory(void) { CALayer *gio_layerFactory(void) {
@autoreleasepool { @autoreleasepool {
return [CAMetalLayer layer]; CAMetalLayer *l = [CAMetalLayer layer];
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
l.needsDisplayOnBoundsChange = YES;
l.presentsWithTransaction = YES;
return l;
} }
} }
+169 -19
View File
@@ -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"
@@ -43,6 +45,8 @@ type Config struct {
CustomRenderer bool CustomRenderer bool
// Decorated reports whether window decorations are provided automatically. // Decorated reports whether window decorations are provided automatically.
Decorated bool Decorated bool
// Focused reports whether has the keyboard focus.
Focused bool
// decoHeight is the height of the fallback decoration for platforms such // decoHeight is the height of the fallback decoration for platforms such
// as Wayland that may need fallback client-side decorations. // as Wayland that may need fallback client-side decorations.
decoHeight unit.Dp decoHeight unit.Dp
@@ -131,6 +135,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 +173,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 +202,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 +228,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 +368,6 @@ func walkActions(actions system.Action, do func(system.Action)) {
} }
} }
} }
func (wakeupEvent) ImplementsEvent() {}
func (ConfigEvent) ImplementsEvent() {}
+111 -80
View File
@@ -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
@@ -158,12 +161,13 @@ type window struct {
fontScale float32 fontScale float32
insets pixelInsets insets pixelInsets
stage Stage visible bool
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
@@ -201,9 +205,9 @@ type pixelInsets struct {
top, bottom, left, right int top, bottom, left, right int
} }
// ViewEvent is sent whenever the Window's underlying Android view // AndroidViewEvent is sent whenever the Window's underlying Android view
// changes. // changes.
type ViewEvent struct { type AndroidViewEvent struct {
// View is a JNI global reference to the android.view.View // View is a JNI global reference to the android.view.View
// instance backing the Window. The reference is valid until // instance backing the Window. The reference is valid until
// the next ViewEvent is received. // the next ViewEvent is received.
@@ -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.visible = false
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.processEvent(AndroidViewEvent{View: uintptr(view)})
w.callbacks.Event(ViewEvent{View: uintptr(view)})
return C.jlong(w.handle) return C.jlong(w.handle)
} }
@@ -518,7 +528,7 @@ func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) { func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := cgo.Handle(handle).Value().(*window) w := cgo.Handle(handle).Value().(*window)
w.started = false w.started = false
w.setStage(StagePaused) w.visible = false
} }
//export Java_org_gioui_GioView_onStartView //export Java_org_gioui_GioView_onStartView
@@ -534,7 +544,7 @@ func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) { func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := cgo.Handle(handle).Value().(*window) w := cgo.Handle(handle).Value().(*window)
w.win = nil w.win = nil
w.setStage(StagePaused) w.visible = false
} }
//export Java_org_gioui_GioView_onSurfaceChanged //export Java_org_gioui_GioView_onSurfaceChanged
@@ -556,9 +566,7 @@ func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) { func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
w := cgo.Handle(view).Value().(*window) w := cgo.Handle(view).Value().(*window)
w.loadConfig(env, class) w.loadConfig(env, class)
if w.stage >= StageInactive { w.draw(env, true)
w.draw(env, true)
}
} }
//export Java_org_gioui_GioView_onFrameCallback //export Java_org_gioui_GioView_onFrameCallback
@@ -567,10 +575,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
if !exist { if !exist {
return return
} }
if w.stage < StageInactive { if w.visible && w.animating {
return
}
if w.animating {
w.draw(env, false) w.draw(env, false)
callVoidMethod(env, w.view, gioView.postFrameCallback) callVoidMethod(env, w.view, gioView.postFrameCallback)
} }
@@ -579,7 +584,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 +593,8 @@ 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.config.Focused = focus == C.JNI_TRUE
w.processEvent(ConfigEvent{Config: w.config})
} }
//export Java_org_gioui_GioView_onWindowInsets //export Java_org_gioui_GioView_onWindowInsets
@@ -600,9 +606,7 @@ func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C
left: int(left), left: int(left),
right: int(right), right: int(right),
} }
if w.stage >= StageInactive { w.draw(env, true)
w.draw(env, true)
}
} }
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo //export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
@@ -663,6 +667,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 +799,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(AndroidViewEvent{})
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
@@ -779,18 +810,10 @@ func (w *window) setVisible(env *C.JNIEnv) {
if width == 0 || height == 0 { if width == 0 || height == 0 {
return return
} }
w.setStage(StageRunning) w.visible = true
w.draw(env, true) w.draw(env, true)
} }
func (w *window) setStage(stage Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.callbacks.Event(StageEvent{stage})
}
func (w *window) setVisual(visID int) error { func (w *window) setVisual(visID int) error {
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 { if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
return errors.New("ANativeWindow_setBuffersGeometry failed") return errors.New("ANativeWindow_setBuffersGeometry failed")
@@ -827,10 +850,13 @@ func (w *window) SetAnimating(anim bool) {
} }
func (w *window) draw(env *C.JNIEnv, sync bool) { func (w *window) draw(env *C.JNIEnv, sync bool) {
if !w.visible {
return
}
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 +870,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 +970,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 +1020,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 +1172,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
@@ -1289,12 +1317,9 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
return C.jni_FindClass(env, cn) return C.jni_FindClass(env, cn)
} }
func osMain() { func newWindow(window *callbacks, options []Option) {
}
func newWindow(window *callbacks, options []Option) error {
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 +1338,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 +1348,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 +1396,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()
}) })
} }
@@ -1460,4 +1490,5 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
} }
} }
func (_ ViewEvent) ImplementsEvent() {} func (AndroidViewEvent) implementsViewEvent() {}
func (AndroidViewEvent) ImplementsEvent() {}
+7 -9
View File
@@ -15,8 +15,8 @@ __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
__attribute__ ((visibility ("hidden"))) void gio_showCursor(); __attribute__ ((visibility ("hidden"))) void gio_showCursor();
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID); __attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
static bool isMainThread() { static int isMainThread() {
return [NSThread isMainThread]; return [NSThread isMainThread] ? 1 : 0;
} }
static NSUInteger nsstringLength(CFTypeRef cstr) { static NSUInteger nsstringLength(CFTypeRef cstr) {
@@ -77,7 +77,7 @@ var mainFuncs = make(chan func(), 1)
// runOnMain runs the function on the main thread. // runOnMain runs the function on the main thread.
func runOnMain(f func()) { func runOnMain(f func()) {
if C.isMainThread() { if isMainThread() {
f() f()
return return
} }
@@ -87,6 +87,10 @@ func runOnMain(f func()) {
}() }()
} }
func isMainThread() bool {
return C.isMainThread() != 0
}
//export gio_dispatchMainFuncs //export gio_dispatchMainFuncs
func gio_dispatchMainFuncs() { func gio_dispatchMainFuncs() {
for { for {
@@ -259,9 +263,3 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
C.gio_setCursor(C.NSUInteger(macosCursorID[to])) C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
return to return to
} }
func (w *window) Wakeup() {
runOnMain(func() {
w.w.Event(wakeupEvent{})
})
}
-11
View File
@@ -1,11 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT
#import <Foundation/Foundation.h>
#include "_cgo_export.h"
void gio_wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_dispatchMainFuncs();
});
}
+98 -60
View File
@@ -12,6 +12,8 @@ package app
#include <UIKit/UIKit.h> #include <UIKit/UIKit.h>
#include <stdint.h> #include <stdint.h>
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
struct drawParams { struct drawParams {
CGFloat dpi, sdpi; CGFloat dpi, sdpi;
CGFloat width, height; CGFloat width, height;
@@ -74,6 +76,7 @@ import (
"image" "image"
"io" "io"
"runtime" "runtime"
"runtime/cgo"
"runtime/debug" "runtime/debug"
"strings" "strings"
"time" "time"
@@ -81,14 +84,16 @@ 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"
) )
type ViewEvent struct { type UIKitViewEvent struct {
// ViewController is a CFTypeRef for the UIViewController backing a Window. // ViewController is a CFTypeRef for the UIViewController backing a Window.
ViewController uintptr ViewController uintptr
} }
@@ -97,18 +102,17 @@ 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
} }
var mainWindow = newWindowRendezvous() var mainWindow = newWindowRendezvous()
var views = make(map[C.CFTypeRef]*window)
func init() { func init() {
// Darwin requires UI operations happen on the main thread only. // Darwin requires UI operations happen on the main thread only.
runtime.LockOSThread() runtime.LockOSThread()
@@ -116,48 +120,52 @@ 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 C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
w.w = wopts.window
w.w.SetDriver(w)
views[view] = w
w.Configure(wopts.options) w.Configure(wopts.options)
w.w.Event(StageEvent{Stage: StagePaused}) w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
w.w.Event(ViewEvent{ViewController: uintptr(controller)}) }
func viewFor(h C.uintptr_t) *window {
return cgo.Handle(h).Value().(*window)
} }
//export gio_onDraw //export gio_onDraw
func gio_onDraw(view C.CFTypeRef) { func gio_onDraw(h C.uintptr_t) {
w := views[view] w := viewFor(h)
w.draw(true) w.draw(true)
} }
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{
@@ -177,26 +185,34 @@ func (w *window) draw(sync bool) {
} }
//export onStop //export onStop
func onStop(view C.CFTypeRef) { func onStop(h C.uintptr_t) {
w := views[view] w := viewFor(h)
w.visible = false w.hidden = true
w.w.Event(StageEvent{Stage: StagePaused}) }
//export onStart
func onStart(h C.uintptr_t) {
w := viewFor(h)
w.hidden = false
w.draw(true)
} }
//export onDestroy //export onDestroy
func onDestroy(view C.CFTypeRef) { func onDestroy(h C.uintptr_t) {
w := views[view] w := viewFor(h)
delete(views, view) w.ProcessEvent(UIKitViewEvent{})
w.w.Event(ViewEvent{}) w.ProcessEvent(DestroyEvent{})
w.w.Event(DestroyEvent{})
w.displayLink.Close() w.displayLink.Close()
w.displayLink = nil
cgo.Handle(h).Delete()
w.view = 0 w.view = 0
} }
//export onFocus //export onFocus
func onFocus(view C.CFTypeRef, focus int) { func onFocus(h C.uintptr_t, focus int) {
w := views[view] w := viewFor(h)
w.w.Event(key.FocusEvent{Focus: focus != 0}) w.config.Focused = focus != 0
w.ProcessEvent(ConfigEvent{Config: w.config})
} }
//export onLowMemory //export onLowMemory
@@ -206,38 +222,38 @@ func onLowMemory() {
} }
//export onUpArrow //export onUpArrow
func onUpArrow(view C.CFTypeRef) { func onUpArrow(h C.uintptr_t) {
views[view].onKeyCommand(key.NameUpArrow) viewFor(h).onKeyCommand(key.NameUpArrow)
} }
//export onDownArrow //export onDownArrow
func onDownArrow(view C.CFTypeRef) { func onDownArrow(h C.uintptr_t) {
views[view].onKeyCommand(key.NameDownArrow) viewFor(h).onKeyCommand(key.NameDownArrow)
} }
//export onLeftArrow //export onLeftArrow
func onLeftArrow(view C.CFTypeRef) { func onLeftArrow(h C.uintptr_t) {
views[view].onKeyCommand(key.NameLeftArrow) viewFor(h).onKeyCommand(key.NameLeftArrow)
} }
//export onRightArrow //export onRightArrow
func onRightArrow(view C.CFTypeRef) { func onRightArrow(h C.uintptr_t) {
views[view].onKeyCommand(key.NameRightArrow) viewFor(h).onKeyCommand(key.NameRightArrow)
} }
//export onDeleteBackward //export onDeleteBackward
func onDeleteBackward(view C.CFTypeRef) { func onDeleteBackward(h C.uintptr_t) {
views[view].onKeyCommand(key.NameDeleteBackward) viewFor(h).onKeyCommand(key.NameDeleteBackward)
} }
//export onText //export onText
func onText(view, str C.CFTypeRef) { func onText(h C.uintptr_t, str C.CFTypeRef) {
w := views[view] w := viewFor(h)
w.w.EditorInsert(nsstringToString(str)) w.w.EditorInsert(nsstringToString(str))
} }
//export onTouch //export onTouch
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) { func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
var kind pointer.Kind var kind pointer.Kind
switch phase { switch phase {
case C.UITouchPhaseBegan: case C.UITouchPhaseBegan:
@@ -251,10 +267,10 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
default: default:
return return
} }
w := views[view] w := viewFor(h)
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 +283,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 +303,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 +311,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 +323,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,12 +362,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) {
mainWindow.in <- windowAndConfig{win, options} w.w.ProcessEvent(e)
return <-mainWindow.errs w.loop.FlushEvents()
} }
func osMain() { 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.windows
} }
//export gio_runMain //export gio_runMain
@@ -363,4 +393,12 @@ func gio_runMain() {
runMain() runMain()
} }
func (_ ViewEvent) ImplementsEvent() {} func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
w.loop.FlushEvents()
})
}
func (UIKitViewEvent) implementsViewEvent() {}
func (UIKitViewEvent) ImplementsEvent() {}
+32 -21
View File
@@ -11,6 +11,7 @@
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void); __attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
@interface GioView: UIView <UIKeyInput> @interface GioView: UIView <UIKeyInput>
@property uintptr_t handle;
@end @end
@implementation GioViewController @implementation GioViewController
@@ -54,33 +55,33 @@ CGFloat _keyboardHeight;
} }
- (void)applicationWillEnterForeground:(UIApplication *)application { - (void)applicationWillEnterForeground:(UIApplication *)application {
UIView *drawView = self.view.subviews[0]; GioView *view = (GioView *)self.view.subviews[0];
if (drawView != nil) { if (view != nil) {
gio_onDraw((__bridge CFTypeRef)drawView); onStart(view.handle);
} }
} }
- (void)applicationDidEnterBackground:(UIApplication *)application { - (void)applicationDidEnterBackground:(UIApplication *)application {
UIView *drawView = self.view.subviews[0]; GioView *view = (GioView *)self.view.subviews[0];
if (drawView != nil) { if (view != nil) {
onStop((__bridge CFTypeRef)drawView); onStop(view.handle);
} }
} }
- (void)viewDidDisappear:(BOOL)animated { - (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated]; [super viewDidDisappear:animated];
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0]; GioView *view = (GioView *)self.view.subviews[0];
onDestroy(viewRef); onDestroy(view.handle);
} }
- (void)viewDidLayoutSubviews { - (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews]; [super viewDidLayoutSubviews];
UIView *view = self.view.subviews[0]; GioView *view = (GioView *)self.view.subviews[0];
CGRect frame = self.view.bounds; CGRect frame = self.view.bounds;
// Adjust view bounds to make room for the keyboard. // Adjust view bounds to make room for the keyboard.
frame.size.height -= _keyboardHeight; frame.size.height -= _keyboardHeight;
view.frame = frame; view.frame = frame;
gio_onDraw((__bridge CFTypeRef)view); gio_onDraw(view.handle);
} }
- (void)didReceiveMemoryWarning { - (void)didReceiveMemoryWarning {
@@ -101,11 +102,10 @@ CGFloat _keyboardHeight;
} }
@end @end
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) { static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
CGFloat scale = view.contentScaleFactor; CGFloat scale = view.contentScaleFactor;
NSUInteger i = 0; NSUInteger i = 0;
NSUInteger n = [touches count]; NSUInteger n = [touches count];
CFTypeRef viewRef = (__bridge CFTypeRef)view;
for (UITouch *touch in touches) { for (UITouch *touch in touches) {
CFTypeRef touchRef = (__bridge CFTypeRef)touch; CFTypeRef touchRef = (__bridge CFTypeRef)touch;
i++; i++;
@@ -116,7 +116,7 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
CGPoint loc = [coalescedTouch locationInView:view]; CGPoint loc = [coalescedTouch locationInView:view];
j++; j++;
int lastTouch = last && i == n && j == m; int lastTouch = last && i == n && j == m;
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]); onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
} }
} }
} }
@@ -151,13 +151,13 @@ NSArray<UIKeyCommand *> *_keyCommands;
- (void)onWindowDidBecomeKey:(NSNotification *)note { - (void)onWindowDidBecomeKey:(NSNotification *)note {
if (self.isFirstResponder) { if (self.isFirstResponder) {
onFocus((__bridge CFTypeRef)self, YES); onFocus(self.handle, YES);
} }
} }
- (void)onWindowDidResignKey:(NSNotification *)note { - (void)onWindowDidResignKey:(NSNotification *)note {
if (self.isFirstResponder) { if (self.isFirstResponder) {
onFocus((__bridge CFTypeRef)self, NO); onFocus(self.handle, NO);
} }
} }
@@ -178,7 +178,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
} }
- (void)insertText:(NSString *)text { - (void)insertText:(NSString *)text {
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text); onText(self.handle, (__bridge CFTypeRef)text);
} }
- (BOOL)canBecomeFirstResponder { - (BOOL)canBecomeFirstResponder {
@@ -190,23 +190,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
} }
- (void)deleteBackward { - (void)deleteBackward {
onDeleteBackward((__bridge CFTypeRef)self); onDeleteBackward(self.handle);
} }
- (void)onUpArrow { - (void)onUpArrow {
onUpArrow((__bridge CFTypeRef)self); onUpArrow(self.handle);
} }
- (void)onDownArrow { - (void)onDownArrow {
onDownArrow((__bridge CFTypeRef)self); onDownArrow(self.handle);
} }
- (void)onLeftArrow { - (void)onLeftArrow {
onLeftArrow((__bridge CFTypeRef)self); onLeftArrow(self.handle);
} }
- (void)onRightArrow { - (void)onRightArrow {
onRightArrow((__bridge CFTypeRef)self); onRightArrow(self.handle);
} }
- (NSArray<UIKeyCommand *> *)keyCommands { - (NSArray<UIKeyCommand *> *)keyCommands {
@@ -271,3 +271,14 @@ void gio_showCursor() {
void gio_setCursor(NSUInteger curID) { void gio_setCursor(NSUInteger curID) {
// Not supported. // Not supported.
} }
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
GioView *v = (__bridge GioView *)viewRef;
v.handle = handle;
}
void gio_wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
gio_dispatchMainFuncs();
});
}
+81 -91
View File
@@ -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"
@@ -23,7 +25,7 @@ import (
"gioui.org/unit" "gioui.org/unit"
) )
type ViewEvent struct { type JSViewEvent struct {
Element js.Value Element js.Value
} }
@@ -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,12 @@ 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(JSViewEvent{Element: cont})
w.Configure(options) w.resize()
w.blur() w.draw(true)
w.w.Event(ViewEvent{Element: cont})
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 +176,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,22 +189,11 @@ 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")
}) })
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
ev := StageEvent{}
switch w.document.Get("visibilityState").String() {
case "hidden", "prerender", "unloaded":
ev.Stage = StagePaused
default:
ev.Stage = StageRunning
}
w.w.Event(ev)
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{} {
w.pointerEvent(pointer.Move, 0, 0, args[0]) w.pointerEvent(pointer.Move, 0, 0, args[0])
return nil return nil
@@ -280,18 +251,20 @@ 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.config.Focused = true
w.processEvent(ConfigEvent{Config: w.config})
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.config.Focused = false
w.processEvent(ConfigEvent{Config: w.config})
w.blur() w.blur()
return nil return nil
}) })
@@ -380,10 +353,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 +444,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 +494,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 +521,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 +576,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 +615,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 +639,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 +659,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,17 +741,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() {
select {}
}
func translateKey(k string) (key.Name, bool) { func translateKey(k string) (key.Name, bool) {
var n key.Name var n key.Name
@@ -827,4 +816,5 @@ func translateKey(k string) (key.Name, bool) {
return n, true return n, true
} }
func (_ ViewEvent) ImplementsEvent() {} func (JSViewEvent) implementsViewEvent() {}
func (JSViewEvent) ImplementsEvent() {}
+315 -213
View File
@@ -10,16 +10,19 @@ import (
"image" "image"
"io" "io"
"runtime" "runtime"
"runtime/cgo"
"strings" "strings"
"time" "time"
"unicode" "unicode"
"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"
@@ -36,9 +39,10 @@ import (
#define MOUSE_DOWN 3 #define MOUSE_DOWN 3
#define MOUSE_SCROLL 4 #define MOUSE_SCROLL 4
__attribute__ ((visibility ("hidden"))) void gio_main(void); __attribute__ ((visibility ("hidden"))) void gio_initApp(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight); __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
static void writeClipboard(CFTypeRef str) { static void writeClipboard(CFTypeRef str) {
@autoreleasepool { @autoreleasepool {
@@ -58,148 +62,204 @@ static CFTypeRef readClipboard(void) {
} }
static CGFloat viewHeight(CFTypeRef viewRef) { static CGFloat viewHeight(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef; @autoreleasepool {
return [view bounds].size.height; NSView *view = (__bridge NSView *)viewRef;
return [view bounds].size.height;
}
} }
static CGFloat viewWidth(CFTypeRef viewRef) { static CGFloat viewWidth(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef; @autoreleasepool {
return [view bounds].size.width; NSView *view = (__bridge NSView *)viewRef;
return [view bounds].size.width;
}
} }
static CGFloat getScreenBackingScale(void) { static CGFloat getScreenBackingScale(void) {
return [NSScreen.mainScreen backingScaleFactor]; @autoreleasepool {
return [NSScreen.mainScreen backingScaleFactor];
}
} }
static CGFloat getViewBackingScale(CFTypeRef viewRef) { static CGFloat getViewBackingScale(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef; @autoreleasepool {
return [view.window backingScaleFactor]; NSView *view = (__bridge NSView *)viewRef;
return [view.window backingScaleFactor];
}
} }
static void setNeedsDisplay(CFTypeRef viewRef) { static void setNeedsDisplay(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef; @autoreleasepool {
[view setNeedsDisplay:YES]; NSView *view = (__bridge NSView *)viewRef;
[view setNeedsDisplay:YES];
}
} }
static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) { static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
return [window cascadeTopLeftFromPoint:topLeft]; NSWindow *window = (__bridge NSWindow *)windowRef;
return [window cascadeTopLeftFromPoint:topLeft];
}
} }
static void makeKeyAndOrderFront(CFTypeRef windowRef) { static void makeKeyAndOrderFront(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window makeKeyAndOrderFront:nil]; NSWindow *window = (__bridge NSWindow *)windowRef;
[window makeKeyAndOrderFront:nil];
}
} }
static void toggleFullScreen(CFTypeRef windowRef) { static void toggleFullScreen(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window toggleFullScreen:nil]; NSWindow *window = (__bridge NSWindow *)windowRef;
[window toggleFullScreen:nil];
}
} }
static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) { static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
return [window styleMask]; NSWindow *window = (__bridge NSWindow *)windowRef;
return [window styleMask];
}
} }
static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) { static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
window.styleMask = mask; NSWindow *window = (__bridge NSWindow *)windowRef;
window.styleMask = mask;
}
} }
static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) { static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
window.titleVisibility = state; NSWindow *window = (__bridge NSWindow *)windowRef;
window.titleVisibility = state;
}
} }
static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) { static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
window.titlebarAppearsTransparent = (BOOL)transparent; NSWindow *window = (__bridge NSWindow *)windowRef;
window.titlebarAppearsTransparent = (BOOL)transparent;
}
} }
static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) { static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window standardWindowButton:btn].hidden = (BOOL)hide; NSWindow *window = (__bridge NSWindow *)windowRef;
[window standardWindowButton:btn].hidden = (BOOL)hide;
}
} }
static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) { static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window performWindowDragWithEvent:(__bridge NSEvent*)evt]; NSWindow *window = (__bridge NSWindow *)windowRef;
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
}
} }
static void closeWindow(CFTypeRef windowRef) { static void closeWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window performClose:nil]; NSWindow* window = (__bridge NSWindow *)windowRef;
[window performClose:nil];
}
} }
static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
NSSize size = NSMakeSize(width, height); NSWindow* window = (__bridge NSWindow *)windowRef;
[window setContentSize:size]; NSSize size = NSMakeSize(width, height);
[window setContentSize:size];
}
} }
static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
window.contentMinSize = NSMakeSize(width, height); NSWindow* window = (__bridge NSWindow *)windowRef;
window.contentMinSize = NSMakeSize(width, height);
}
} }
static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
window.contentMaxSize = NSMakeSize(width, height); NSWindow* window = (__bridge NSWindow *)windowRef;
window.contentMaxSize = NSMakeSize(width, height);
}
} }
static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) { static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
NSRect r = NSMakeRect(x, y, w, h); NSWindow* window = (__bridge NSWindow *)windowRef;
[window setFrame:r display:YES]; NSRect r = NSMakeRect(x, y, w, h);
[window setFrame:r display:YES];
}
} }
static void hideWindow(CFTypeRef windowRef) { static void hideWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window miniaturize:window]; NSWindow* window = (__bridge NSWindow *)windowRef;
[window miniaturize:window];
}
} }
static void unhideWindow(CFTypeRef windowRef) { static void unhideWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window deminiaturize:window]; NSWindow* window = (__bridge NSWindow *)windowRef;
[window deminiaturize:window];
}
} }
static NSRect getScreenFrame(CFTypeRef windowRef) { static NSRect getScreenFrame(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef; @autoreleasepool {
return [[window screen] frame]; NSWindow* window = (__bridge NSWindow *)windowRef;
return [[window screen] frame];
}
} }
static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) { static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
window.title = (__bridge NSString *)titleRef; NSWindow *window = (__bridge NSWindow *)windowRef;
window.title = (__bridge NSString *)titleRef;
}
} }
static int isWindowZoomed(CFTypeRef windowRef) { static int isWindowZoomed(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
return window.zoomed ? 1 : 0; NSWindow *window = (__bridge NSWindow *)windowRef;
return window.zoomed ? 1 : 0;
}
} }
static void zoomWindow(CFTypeRef windowRef) { static void zoomWindow(CFTypeRef windowRef) {
NSWindow *window = (__bridge NSWindow *)windowRef; @autoreleasepool {
[window zoom:nil]; NSWindow *window = (__bridge NSWindow *)windowRef;
[window zoom:nil];
}
} }
static CFTypeRef layerForView(CFTypeRef viewRef) { static CFTypeRef layerForView(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef; @autoreleasepool {
return (__bridge CFTypeRef)view.layer; NSView *view = (__bridge NSView *)viewRef;
return (__bridge CFTypeRef)view.layer;
}
} }
static CFTypeRef windowForView(CFTypeRef viewRef) { static CFTypeRef windowForView(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef; @autoreleasepool {
return (__bridge CFTypeRef)view.window; NSView *view = (__bridge NSView *)viewRef;
return (__bridge CFTypeRef)view.window;
}
} }
static void raiseWindow(CFTypeRef windowRef) { static void raiseWindow(CFTypeRef windowRef) {
NSRunningApplication *currentApp = [NSRunningApplication currentApplication]; @autoreleasepool {
if (![currentApp isActive]) { NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; if (![currentApp isActive]) {
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
}
NSWindow* window = (__bridge NSWindow *)windowRef;
[window makeKeyAndOrderFront:nil];
} }
NSWindow* window = (__bridge NSWindow *)windowRef;
[window makeKeyAndOrderFront:nil];
} }
static CFTypeRef createInputContext(CFTypeRef clientRef) { static CFTypeRef createInputContext(CFTypeRef clientRef) {
@@ -211,23 +271,33 @@ static CFTypeRef createInputContext(CFTypeRef clientRef) {
} }
static void discardMarkedText(CFTypeRef viewRef) { static void discardMarkedText(CFTypeRef viewRef) {
@autoreleasepool { @autoreleasepool {
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef; id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
NSTextInputContext *ctx = [NSTextInputContext currentInputContext]; NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
if (view == [ctx client]) { if (view == [ctx client]) {
[ctx discardMarkedText]; [ctx discardMarkedText];
} }
} }
} }
static void invalidateCharacterCoordinates(CFTypeRef viewRef) { static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
@autoreleasepool { @autoreleasepool {
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef; id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
NSTextInputContext *ctx = [NSTextInputContext currentInputContext]; NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
if (view == [ctx client]) { if (view == [ctx client]) {
[ctx invalidateCharacterCoordinates]; [ctx invalidateCharacterCoordinates];
} }
} }
}
static void dispatchEvent(void) {
@autoreleasepool {
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
}
} }
*/ */
import "C" import "C"
@@ -237,9 +307,9 @@ func init() {
runtime.LockOSThread() runtime.LockOSThread()
} }
// ViewEvent notified the client of changes to the window AppKit handles. // AppKitViewEvent notifies the client of changes to the window AppKit handles.
// The handles are retained until another ViewEvent is sent. // The handles are retained until another AppKitViewEvent is sent.
type ViewEvent struct { type AppKitViewEvent struct {
// View is a CFTypeRef for the NSView for the window. // View is a CFTypeRef for the NSView for the window.
View uintptr View uintptr
// Layer is a CFTypeRef of the CALayer of View. // Layer is a CFTypeRef of the CALayer of View.
@@ -249,52 +319,32 @@ type ViewEvent struct {
type window struct { type window struct {
view C.CFTypeRef view C.CFTypeRef
w *callbacks w *callbacks
stage Stage anim bool
visible bool
displayLink *displayLink displayLink *displayLink
// redraw is a single entry channel for making sure only one // redraw is a single entry channel for making sure only one
// display link redraw request is in flight. // display link redraw request is in flight.
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
} }
// viewMap is the mapping from Cocoa NSViews to Go windows. // launched is closed after gio_initApp returns.
var viewMap = make(map[C.CFTypeRef]*window)
// launched is closed when applicationDidFinishLaunching is called.
var launched = make(chan struct{}) var launched = make(chan struct{})
// nextTopLeft is the offset to use for the next window's call to // nextTopLeft is the offset to use for the next window's call to
// cascadeTopLeftFromPoint. // cascadeTopLeftFromPoint.
var nextTopLeft C.NSPoint var nextTopLeft C.NSPoint
// mustView is like lookupView, except that it panics // mainThreadWindow is the window currently in control of the main thread.
// if the view isn't mapped. var mainThreadWindow *window
func mustView(view C.CFTypeRef) *window {
w, ok := lookupView(view)
if !ok {
panic("no window for view")
}
return w
}
func lookupView(view C.CFTypeRef) (*window, bool) { func windowFor(h C.uintptr_t) *window {
w, exists := viewMap[view] return cgo.Handle(h).Value().(*window)
if !exists {
return nil, false
}
return w, true
}
func deleteView(view C.CFTypeRef) {
delete(viewMap, view)
}
func insertView(view C.CFTypeRef, w *window) {
viewMap[view] = w
} }
func (w *window) contextView() C.CFTypeRef { func (w *window) contextView() C.CFTypeRef {
@@ -307,7 +357,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 +470,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) {
@@ -471,7 +521,8 @@ func (w *window) ShowTextInput(show bool) {}
func (w *window) SetInputHint(_ key.InputHint) {} func (w *window) SetInputHint(_ key.InputHint) {}
func (w *window) SetAnimating(anim bool) { func (w *window) SetAnimating(anim bool) {
if anim { w.anim = anim
if w.anim && w.visible {
w.displayLink.Start() w.displayLink.Start()
} else { } else {
w.displayLink.Stop() w.displayLink.Stop()
@@ -488,26 +539,18 @@ func (w *window) runOnMain(f func()) {
}) })
} }
func (w *window) setStage(stage Stage) {
if stage == w.stage {
return
}
w.stage = stage
w.w.Event(StageEvent{Stage: stage})
}
//export gio_onKeys //export gio_onKeys
func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) { func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
str := nsstringToString(cstr) str := nsstringToString(cstr)
kmods := convertMods(mods) kmods := convertMods(mods)
ks := key.Release ks := key.Release
if keyDown { if keyDown {
ks = key.Press ks = key.Press
} }
w := mustView(view) w := windowFor(h)
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,
@@ -517,15 +560,15 @@ func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown
} }
//export gio_onText //export gio_onText
func gio_onText(view, cstr C.CFTypeRef) { func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
str := nsstringToString(cstr) str := nsstringToString(cstr)
w := mustView(view) w := windowFor(h)
w.w.EditorInsert(str) w.w.EditorInsert(str)
} }
//export gio_onMouse //export gio_onMouse
func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) { func gio_onMouse(h C.uintptr_t, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
w := mustView(view) w := windowFor(h)
t := time.Duration(float64(ti)*float64(time.Second) + .5) t := time.Duration(float64(ti)*float64(time.Second) + .5)
xf, yf := float32(x)*w.scale, float32(y)*w.scale xf, yf := float32(x)*w.scale, float32(y)*w.scale
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
@@ -562,7 +605,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,
@@ -574,35 +617,29 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
} }
//export gio_onDraw //export gio_onDraw
func gio_onDraw(view C.CFTypeRef) { func gio_onDraw(h C.uintptr_t) {
w := mustView(view) w := windowFor(h)
w.draw() w.draw()
} }
//export gio_onFocus //export gio_onFocus
func gio_onFocus(view C.CFTypeRef, focus C.int) { func gio_onFocus(h C.uintptr_t, focus C.int) {
w := mustView(view) w := windowFor(h)
w.w.Event(key.FocusEvent{Focus: focus == 1})
if w.stage >= StageInactive {
if focus == 0 {
w.setStage(StageInactive)
} else {
w.setStage(StageRunning)
}
}
w.SetCursor(w.cursor) w.SetCursor(w.cursor)
w.config.Focused = focus == 1
w.ProcessEvent(ConfigEvent{Config: w.config})
} }
//export gio_onChangeScreen //export gio_onChangeScreen
func gio_onChangeScreen(view C.CFTypeRef, did uint64) { func gio_onChangeScreen(h C.uintptr_t, did uint64) {
w := mustView(view) w := windowFor(h)
w.displayLink.SetDisplayID(did) w.displayLink.SetDisplayID(did)
C.setNeedsDisplay(w.view) C.setNeedsDisplay(w.view)
} }
//export gio_hasMarkedText //export gio_hasMarkedText
func gio_hasMarkedText(view C.CFTypeRef) C.int { func gio_hasMarkedText(h C.uintptr_t) C.int {
w := mustView(view) w := windowFor(h)
state := w.w.EditorState() state := w.w.EditorState()
if state.compose.Start != -1 { if state.compose.Start != -1 {
return 1 return 1
@@ -611,8 +648,8 @@ func gio_hasMarkedText(view C.CFTypeRef) C.int {
} }
//export gio_markedRange //export gio_markedRange
func gio_markedRange(view C.CFTypeRef) C.NSRange { func gio_markedRange(h C.uintptr_t) C.NSRange {
w := mustView(view) w := windowFor(h)
state := w.w.EditorState() state := w.w.EditorState()
rng := state.compose rng := state.compose
start, end := rng.Start, rng.End start, end := rng.Start, rng.End
@@ -627,8 +664,8 @@ func gio_markedRange(view C.CFTypeRef) C.NSRange {
} }
//export gio_selectedRange //export gio_selectedRange
func gio_selectedRange(view C.CFTypeRef) C.NSRange { func gio_selectedRange(h C.uintptr_t) C.NSRange {
w := mustView(view) w := windowFor(h)
state := w.w.EditorState() state := w.w.EditorState()
rng := state.Selection rng := state.Selection
start, end := rng.Start, rng.End start, end := rng.Start, rng.End
@@ -643,14 +680,14 @@ func gio_selectedRange(view C.CFTypeRef) C.NSRange {
} }
//export gio_unmarkText //export gio_unmarkText
func gio_unmarkText(view C.CFTypeRef) { func gio_unmarkText(h C.uintptr_t) {
w := mustView(view) w := windowFor(h)
w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
} }
//export gio_setMarkedText //export gio_setMarkedText
func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) { func gio_setMarkedText(h C.uintptr_t, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
w := mustView(view) w := windowFor(h)
str := nsstringToString(cstr) str := nsstringToString(cstr)
state := w.w.EditorState() state := w.w.EditorState()
rng := state.compose rng := state.compose
@@ -689,8 +726,8 @@ func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange
} }
//export gio_substringForProposedRange //export gio_substringForProposedRange
func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef { func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
w := mustView(view) w := windowFor(h)
state := w.w.EditorState() state := w.w.EditorState()
start, end := state.Snippet.Start, state.Snippet.End start, end := state.Snippet.Start, state.Snippet.End
if start > end { if start > end {
@@ -710,8 +747,8 @@ func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NS
} }
//export gio_insertText //export gio_insertText
func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) { func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
w := mustView(view) w := windowFor(h)
state := w.w.EditorState() state := w.w.EditorState()
rng := state.compose rng := state.compose
if rng.Start == -1 { if rng.Start == -1 {
@@ -735,13 +772,13 @@ func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
} }
//export gio_characterIndexForPoint //export gio_characterIndexForPoint
func gio_characterIndexForPoint(view C.CFTypeRef, p C.NSPoint) C.NSUInteger { func gio_characterIndexForPoint(h C.uintptr_t, p C.NSPoint) C.NSUInteger {
return C.NSNotFound return C.NSNotFound
} }
//export gio_firstRectForCharacterRange //export gio_firstRectForCharacterRange
func gio_firstRectForCharacterRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.NSRect { func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
w := mustView(view) w := windowFor(h)
state := w.w.EditorState() state := w.w.EditorState()
sel := state.Selection sel := state.Selection
u16start := state.UTF16Index(sel.Start) u16start := state.UTF16Index(sel.Start)
@@ -768,6 +805,10 @@ func (w *window) draw() {
case <-w.redraw: case <-w.redraw:
default: default:
} }
w.visible = true
if w.anim {
w.SetAnimating(w.anim)
}
w.scale = float32(C.getViewBackingScale(w.view)) w.scale = float32(C.getViewBackingScale(w.view))
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view)) wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
sz := image.Point{ sz := image.Point{
@@ -776,14 +817,13 @@ 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.ProcessEvent(frameEvent{
w.w.Event(frameEvent{
FrameEvent: FrameEvent{ FrameEvent: FrameEvent{
Now: time.Now(), Now: time.Now(),
Size: w.config.Size, Size: w.config.Size,
@@ -793,6 +833,55 @@ func (w *window) draw() {
}) })
} }
func (w *window) ProcessEvent(e event.Event) {
w.w.ProcessEvent(e)
// The main thread window deliver events in Event.
if w != mainThreadWindow {
w.loop.FlushEvents()
}
}
func (w *window) Event() event.Event {
if !isMainThread() {
return w.loop.Event()
}
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
for {
if evt, ok := w.loop.win.nextEvent(); ok {
return evt
}
C.dispatchEvent()
gio_dispatchMainFuncs()
}
}
func (w *window) Invalidate() {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Invalidate()
}
func (w *window) Run(f func()) {
if isMainThread() {
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
}
w.loop.Run(f)
}
func (w *window) Frame(frame *op.Ops) {
if !isMainThread() {
w.loop.Frame(frame)
return
}
mainThreadWindow = w
defer func() { mainThreadWindow = nil }()
w.loop.win.ProcessFrame(frame, nil)
}
func configFor(scale float32) unit.Metric { func configFor(scale float32) unit.Metric {
return unit.Metric{ return unit.Metric{
PxPerDp: scale, PxPerDp: scale,
@@ -800,77 +889,87 @@ func configFor(scale float32) unit.Metric {
} }
} }
//export gio_onClose //export gio_onAttached
func gio_onClose(view C.CFTypeRef) { func gio_onAttached(h C.uintptr_t, attached C.int) {
w := mustView(view) w := windowFor(h)
w.w.Event(ViewEvent{}) if attached != 0 {
w.w.Event(DestroyEvent{}) layer := C.layerForView(w.view)
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
} else {
w.ProcessEvent(AppKitViewEvent{})
w.visible = false
w.SetAnimating(w.anim)
}
}
//export gio_onDestroy
func gio_onDestroy(h C.uintptr_t) {
w := windowFor(h)
w.ProcessEvent(DestroyEvent{})
w.displayLink.Close() w.displayLink.Close()
w.displayLink = nil w.displayLink = nil
deleteView(view) cgo.Handle(h).Delete()
C.CFRelease(w.view)
w.view = 0 w.view = 0
} }
//export gio_onHide //export gio_onHide
func gio_onHide(view C.CFTypeRef) { func gio_onHide(h C.uintptr_t) {
w := mustView(view) w := windowFor(h)
w.setStage(StagePaused) w.visible = false
w.SetAnimating(w.anim)
} }
//export gio_onShow //export gio_onShow
func gio_onShow(view C.CFTypeRef) { func gio_onShow(h C.uintptr_t) {
w := mustView(view) w := windowFor(h)
w.setStage(StageRunning) w.draw()
} }
//export gio_onFullscreen //export gio_onFullscreen
func gio_onFullscreen(view C.CFTypeRef) { func gio_onFullscreen(h C.uintptr_t) {
w := mustView(view) w := windowFor(h)
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(h C.uintptr_t) {
w := mustView(view) w := windowFor(h)
w.config.Mode = Windowed w.config.Mode = Windowed
w.w.Event(ConfigEvent{Config: w.config}) w.ProcessEvent(ConfigEvent{Config: w.config})
} }
//export gio_onAppHide func newWindow(win *callbacks, options []Option) {
func gio_onAppHide() { w := &window{
for _, w := range viewMap { redraw: make(chan struct{}, 1),
w.setStage(StagePaused) w: win,
} }
} w.loop = newEventLoop(w.w, w.wakeup)
if isMainThread() {
//export gio_onAppShow mainThreadWindow = w
func gio_onAppShow() { defer func() { mainThreadWindow = nil }()
for _, w := range viewMap { select {
w.setStage(StageRunning) case <-launched:
default:
// If we're the main thread, initialize the GUI.
C.gio_initApp()
close(launched)
}
} else {
<-launched
} }
} res := make(chan struct{}, 1)
//export gio_onFinishLaunching
func gio_onFinishLaunching() {
close(launched)
}
func newWindow(win *callbacks, options []Option) error {
<-launched
errch := make(chan error)
runOnMain(func() { runOnMain(func() {
w, err := newOSWindow() win.SetDriver(w)
if err != nil { res <- struct{}{}
errch <- err 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)
// Release our reference now that the NSWindow has it.
C.CFRelease(w.view)
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,
@@ -880,23 +979,17 @@ func newWindow(win *callbacks, options []Option) error {
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
// 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)
w.w.Event(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{}{}:
@@ -904,20 +997,19 @@ func newOSWindow() (*window, error) {
return return
} }
w.runOnMain(func() { w.runOnMain(func() {
C.setNeedsDisplay(w.view) if w.visible {
C.setNeedsDisplay(w.view)
}
}) })
}) })
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) C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
return w, nil w.view = view
} return nil
func osMain() {
C.gio_main()
} }
func convertKey(k rune) (key.Name, bool) { func convertKey(k rune) (key.Name, bool) {
@@ -1004,4 +1096,14 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
return kmods return kmods
} }
func (_ ViewEvent) ImplementsEvent() {} func (w *window) wakeup() {
runOnMain(func() {
w.loop.Wakeup()
if w != mainThreadWindow {
w.loop.FlushEvents()
}
})
}
func (AppKitViewEvent) implementsViewEvent() {}
func (AppKitViewEvent) ImplementsEvent() {}
+311 -60
View File
@@ -14,40 +14,50 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
@interface GioWindowDelegate : NSObject<NSWindowDelegate> @interface GioWindowDelegate : NSObject<NSWindowDelegate>
@end @end
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
@property uintptr_t handle;
@end
@implementation GioWindowDelegate @implementation GioWindowDelegate
- (void)windowWillMiniaturize:(NSNotification *)notification { - (void)windowWillMiniaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
gio_onHide((__bridge CFTypeRef)window.contentView); GioView *view = (GioView *)window.contentView;
gio_onHide(view.handle);
} }
- (void)windowDidDeminiaturize:(NSNotification *)notification { - (void)windowDidDeminiaturize:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
gio_onShow((__bridge CFTypeRef)window.contentView); GioView *view = (GioView *)window.contentView;
gio_onShow(view.handle);
} }
- (void)windowWillEnterFullScreen:(NSNotification *)notification { - (void)windowWillEnterFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
gio_onFullscreen((__bridge CFTypeRef)window.contentView); GioView *view = (GioView *)window.contentView;
gio_onFullscreen(view.handle);
} }
- (void)windowWillExitFullScreen:(NSNotification *)notification { - (void)windowWillExitFullScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
gio_onWindowed((__bridge CFTypeRef)window.contentView); GioView *view = (GioView *)window.contentView;
gio_onWindowed(view.handle);
} }
- (void)windowDidChangeScreen:(NSNotification *)notification { - (void)windowDidChangeScreen:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue]; CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
CFTypeRef view = (__bridge CFTypeRef)window.contentView; GioView *view = (GioView *)window.contentView;
gio_onChangeScreen(view, dispID); gio_onChangeScreen(view.handle, dispID);
} }
- (void)windowDidBecomeKey:(NSNotification *)notification { - (void)windowDidBecomeKey:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
gio_onFocus((__bridge CFTypeRef)window.contentView, 1); GioView *view = (GioView *)window.contentView;
gio_onFocus(view.handle, 1);
} }
- (void)windowDidResignKey:(NSNotification *)notification { - (void)windowDidResignKey:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
gio_onFocus((__bridge CFTypeRef)window.contentView, 0); GioView *view = (GioView *)window.contentView;
gio_onFocus(view.handle, 0);
} }
@end @end
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) { static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil]; NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
if (!event.hasPreciseScrollingDeltas) { if (!event.hasPreciseScrollingDeltas) {
// dx and dy are in rows and columns. // dx and dy are in rows and columns.
@@ -56,10 +66,213 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
} }
// Origin is in the lower left corner. Convert to upper left. // Origin is in the lower left corner. Convert to upper left.
CGFloat height = view.bounds.size.height; CGFloat height = view.bounds.size.height;
gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]); gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
} }
@interface GioView : NSView <CALayerDelegate,NSTextInputClient> @interface GioApplication: NSApplication
@end
// Variables for tracking resizes.
static struct {
NSPoint dir;
NSEvent *lastMouseDown;
NSPoint off;
} resizeState = {};
static NSBitmapImageRep *nsImageBitmap(NSImage *img) {
NSArray<NSImageRep *> *reps = img.representations;
if ([reps count] == 0) {
return nil;
}
NSImageRep *rep = reps[0];
if (![rep isKindOfClass:[NSBitmapImageRep class]]) {
return nil;
}
return (NSBitmapImageRep *)rep;
}
static NSCursor *lookupPrivateNSCursor(SEL name) {
if (![NSCursor respondsToSelector:name]) {
return nil;
}
id obj = [NSCursor performSelector:name];
if (![obj isKindOfClass:[NSCursor class]]) {
return nil;
}
return (NSCursor *)obj;
}
static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
NSCursor *c2 = lookupPrivateNSCursor(name2);
if (c2 == nil || !NSEqualPoints(c1.hotSpot, c2.hotSpot)) {
return NO;
}
NSImage *img1 = c1.image;
NSImage *img2 = c2.image;
if (!NSEqualSizes(img1.size, img2.size)) {
return NO;
}
NSBitmapImageRep *bit1 = nsImageBitmap(img1);
NSBitmapImageRep *bit2 = nsImageBitmap(img2);
if (bit1 == nil || bit2 == nil) {
return NO;
}
NSInteger n1 = bit1.numberOfPlanes*bit1.bytesPerPlane;
NSInteger n2 = bit1.numberOfPlanes*bit1.bytesPerPlane;
if (n1 != n2) {
return NO;
}
if (memcmp(bit1.bitmapData, bit2.bitmapData, n1) != 0) {
return NO;
}
return YES;
}
@implementation GioApplication
- (NSEvent *)nextEventMatchingMask:(NSEventMask)mask
untilDate:(NSDate *)expiration
inMode:(NSRunLoopMode)mode
dequeue:(BOOL)deqFlag {
if ([mode isEqualToString:NSEventTrackingRunLoopMode]) {
NSEvent *l = resizeState.lastMouseDown;
if (l != nil) {
//lastMouseDown = nil;
NSCursor *cur = [NSCursor currentSystemCursor];
NSPoint dir = {};
NSPoint off = {};
NSSize wsz = [l window].frame.size;
NSPoint center = NSMakePoint(wsz.width/2, wsz.height/2);
NSPoint p = [l locationInWindow];
if (p.x >= center.x) {
dir.x = 1;
off.x = p.x - wsz.width;
} else {
dir.x = -1;
off.x = p.x;
}
if (p.y >= center.y) {
dir.y = 1;
off.y = p.y - wsz.height;
} else {
dir.y = -1;
off.y = p.y;
}
// The button down coordinate distinguish the four quadrants. Use the
// cursor image to determine the precise direction.
SEL nw = @selector(_windowResizeNorthWestCursor);
SEL n = @selector(_windowResizeNorthCursor);
SEL ne = @selector(_windowResizeNorthEastCursor);
SEL e = @selector(_windowResizeEastCursor);
SEL se = @selector(_windowResizeSouthEastCursor);
SEL s = @selector(_windowResizeSouthCursor);
SEL sw = @selector(_windowResizeSouthWestCursor);
SEL w = @selector(_windowResizeWestCursor);
SEL ns = @selector(_windowResizeNorthSouthCursor);
SEL ew = @selector(_windowResizeEastWestCursor);
SEL nwse = @selector(_windowResizeNorthWestSouthEastCursor);
SEL nesw = @selector(_windowResizeNorthEastSouthWestCursor);
BOOL match = YES;
if (dir.x != 0 && (isEqualNSCursor(cur, ew) || isEqualNSCursor(cur, w) || isEqualNSCursor(cur, e))) {
dir.y = 0;
}
if (dir.y != 0 && (isEqualNSCursor(cur, ns) || isEqualNSCursor(cur, s) || isEqualNSCursor(cur, n))) {
dir.x = 0;
}
// If none of the cursors matched, we may deduce that the resize
// direction is one of the corners. However, to ensure that at least
// one cursor matches, check the corner cursors.
if (dir.x == 1 && dir.y == 1) {
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, sw)) {
dir = NSZeroPoint;
}
} else if (dir.x == 1 && dir.y == -1) {
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, nw)) {
dir = NSZeroPoint;
}
} else if (dir.x == -1 && dir.y == 1) {
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, se)) {
dir = NSZeroPoint;
}
} else if (dir.x == -1 && dir.y == -1) {
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, ne)) {
dir = NSZeroPoint;
}
}
if (!NSEqualPoints(dir, NSZeroPoint)) {
NSEvent *cancel = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
location:l.locationInWindow
modifierFlags:l.modifierFlags
timestamp:l.timestamp
windowNumber:l.windowNumber
context:l.context
eventNumber:l.eventNumber
clickCount:l.clickCount
pressure:l.pressure];
resizeState.off = off;
resizeState.dir = dir;
return cancel;
}
}
}
return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:deqFlag];
}
@end
@interface GioWindow: NSWindow
@end
@implementation GioWindow
- (void)sendEvent:(NSEvent *)evt {
if (evt.type == NSEventTypeLeftMouseDown) {
resizeState.lastMouseDown = evt;
}
NSPoint dir = resizeState.dir;
if (NSEqualPoints(dir, NSZeroPoint)) {
[super sendEvent:evt];
return;
}
switch (evt.type) {
default:
return;
case NSEventTypeLeftMouseUp:
resizeState.dir = NSZeroPoint;
resizeState.lastMouseDown = nil;
return;
case NSEventTypeLeftMouseDragged:
// Ok to proceed.
break;
}
NSPoint loc = evt.locationInWindow;
NSPoint off = resizeState.off;
loc.x -= off.x;
loc.y -= off.y;
NSRect frame = [self frame];
NSSize min = [self minSize];
NSSize max = [self maxSize];
CGFloat width = frame.size.width;
if (dir.x > 0) {
width = loc.x;
} else if (dir.x < 0) {
width -= loc.x;
}
width = MIN(max.width, MAX(min.width, width));
if (dir.x < 0) {
frame.origin.x += frame.size.width - width;
}
frame.size.width = width;
CGFloat height = frame.size.height;
if (dir.y > 0) {
height = loc.y;
} else if (dir.y < 0) {
height -= loc.y;
}
height = MIN(max.height, MAX(min.height, height));
if (dir.y < 0) {
frame.origin.y += frame.size.height - height;
}
frame.size.height = height;
[self setFrame:frame display:YES animate:NO];
}
@end @end
@implementation GioView @implementation GioView
@@ -70,11 +283,11 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
// drawRect is called when OpenGL is used, displayLayer otherwise. // drawRect is called when OpenGL is used, displayLayer otherwise.
// Don't know why. // Don't know why.
- (void)drawRect:(NSRect)r { - (void)drawRect:(NSRect)r {
gio_onDraw((__bridge CFTypeRef)self); gio_onDraw(self.handle);
} }
- (void)displayLayer:(CALayer *)layer { - (void)displayLayer:(CALayer *)layer {
layer.contentsScale = self.window.backingScaleFactor; layer.contentsScale = self.window.backingScaleFactor;
gio_onDraw((__bridge CFTypeRef)self); gio_onDraw(self.handle);
} }
- (CALayer *)makeBackingLayer { - (CALayer *)makeBackingLayer {
CALayer *layer = gio_layerFactory(); CALayer *layer = gio_layerFactory();
@@ -82,9 +295,7 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
return layer; return layer;
} }
- (void)viewDidMoveToWindow { - (void)viewDidMoveToWindow {
if (self.window == nil) { gio_onAttached(self.handle, self.window != nil ? 1 : 0);
gio_onClose((__bridge CFTypeRef)self);
}
} }
- (void)mouseDown:(NSEvent *)event { - (void)mouseDown:(NSEvent *)event {
handleMouse(self, event, MOUSE_DOWN, 0, 0); handleMouse(self, event, MOUSE_DOWN, 0, 0);
@@ -124,32 +335,31 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
- (void)keyDown:(NSEvent *)event { - (void)keyDown:(NSEvent *)event {
[self interpretKeyEvents:[NSArray arrayWithObject:event]]; [self interpretKeyEvents:[NSArray arrayWithObject:event]];
NSString *keys = [event charactersIgnoringModifiers]; NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true); gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
} }
- (void)keyUp:(NSEvent *)event { - (void)keyUp:(NSEvent *)event {
NSString *keys = [event charactersIgnoringModifiers]; NSString *keys = [event charactersIgnoringModifiers];
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false); gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
} }
- (void)insertText:(id)string { - (void)insertText:(id)string {
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string); gio_onText(self.handle, (__bridge CFTypeRef)string);
} }
- (void)doCommandBySelector:(SEL)sel { - (void)doCommandBySelector:(SEL)sel {
// Don't pass commands up the responder chain. // Don't pass commands up the responder chain.
// They will end up in a beep. // They will end up in a beep.
} }
- (BOOL)hasMarkedText { - (BOOL)hasMarkedText {
int res = gio_hasMarkedText((__bridge CFTypeRef)self); int res = gio_hasMarkedText(self.handle);
return res ? YES : NO; return res ? YES : NO;
} }
- (NSRange)markedRange { - (NSRange)markedRange {
return gio_markedRange((__bridge CFTypeRef)self); return gio_markedRange(self.handle);
} }
- (NSRange)selectedRange { - (NSRange)selectedRange {
return gio_selectedRange((__bridge CFTypeRef)self); return gio_selectedRange(self.handle);
} }
- (void)unmarkText { - (void)unmarkText {
gio_unmarkText((__bridge CFTypeRef)self); gio_unmarkText(self.handle);
} }
- (void)setMarkedText:(id)string - (void)setMarkedText:(id)string
selectedRange:(NSRange)selRange selectedRange:(NSRange)selRange
@@ -161,14 +371,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
} else { } else {
str = string; str = string;
} }
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange); gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
} }
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText { - (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
return nil; return nil;
} }
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
actualRange:(NSRangePointer)actualRange { actualRange:(NSRangePointer)actualRange {
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange)); NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
return [[NSAttributedString alloc] initWithString:str attributes:nil]; return [[NSAttributedString alloc] initWithString:str attributes:nil];
} }
- (void)insertText:(id)string - (void)insertText:(id)string
@@ -180,17 +390,26 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
} else { } else {
str = string; str = string;
} }
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange); gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
} }
- (NSUInteger)characterIndexForPoint:(NSPoint)p { - (NSUInteger)characterIndexForPoint:(NSPoint)p {
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p); return gio_characterIndexForPoint(self.handle, p);
} }
- (NSRect)firstRectForCharacterRange:(NSRange)rng - (NSRect)firstRectForCharacterRange:(NSRange)rng
actualRange:(NSRangePointer)actual { actualRange:(NSRangePointer)actual {
NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual); NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
r = [self convertRect:r toView:nil]; r = [self convertRect:r toView:nil];
return [[self window] convertRectToScreen:r]; return [[self window] convertRectToScreen:r];
} }
- (void)applicationWillUnhide:(NSNotification *)notification {
gio_onShow(self.handle);
}
- (void)applicationDidHide:(NSNotification *)notification {
gio_onHide(self.handle);
}
- (void)dealloc {
gio_onDestroy(self.handle);
}
@end @end
// Delegates are weakly referenced from their peers. Nothing // Delegates are weakly referenced from their peers. Nothing
@@ -240,15 +459,12 @@ void gio_showCursor() {
// some cursors are not public, this tries to use a private cursor // some cursors are not public, this tries to use a private cursor
// and uses fallback when the use of private cursor fails. // and uses fallback when the use of private cursor fails.
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) { static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
if ([NSCursor respondsToSelector:cursorName]) { NSCursor *cur = lookupPrivateNSCursor(cursorName);
id object = [NSCursor performSelector:cursorName]; if (cur == nil) {
if ([object isKindOfClass:[NSCursor class]]) { cur = fallback;
[(NSCursor*)object set];
return;
}
} }
[fallback set]; [cur set];
} }
void gio_setCursor(NSUInteger curID) { void gio_setCursor(NSUInteger curID) {
@@ -272,7 +488,7 @@ void gio_setCursor(NSUInteger curID) {
break; break;
case 6: // pointer.CursorAllScroll case 6: // pointer.CursorAllScroll
// For some reason, using _moveCursor fails on Monterey. // For some reason, using _moveCursor fails on Monterey.
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor); // trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
[NSCursor.arrowCursor set]; [NSCursor.arrowCursor set];
break; break;
case 7: // pointer.CursorColResize case 7: // pointer.CursorColResize
@@ -282,33 +498,31 @@ void gio_setCursor(NSUInteger curID) {
[NSCursor.resizeUpDownCursor set]; [NSCursor.resizeUpDownCursor set];
break; break;
case 9: // pointer.CursorGrab case 9: // pointer.CursorGrab
// [NSCursor.openHandCursor set]; [NSCursor.openHandCursor set];
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
break; break;
case 10: // pointer.CursorGrabbing case 10: // pointer.CursorGrabbing
// [NSCursor.closedHandCursor set]; [NSCursor.closedHandCursor set];
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
break; break;
case 11: // pointer.CursorNotAllowed case 11: // pointer.CursorNotAllowed
[NSCursor.operationNotAllowedCursor set]; [NSCursor.operationNotAllowedCursor set];
break; break;
case 12: // pointer.CursorWait case 12: // pointer.CursorWait
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor); trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
break; break;
case 13: // pointer.CursorProgress case 13: // pointer.CursorProgress
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor); trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
break; break;
case 14: // pointer.CursorNorthWestResize case 14: // pointer.CursorNorthWestResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor); trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
break; break;
case 15: // pointer.CursorNorthEastResize case 15: // pointer.CursorNorthEastResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor); trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
break; break;
case 16: // pointer.CursorSouthWestResize case 16: // pointer.CursorSouthWestResize
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor); trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
break; break;
case 17: // pointer.CursorSouthEastResize case 17: // pointer.CursorSouthEastResize
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor); trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
break; break;
case 18: // pointer.CursorNorthSouthResize case 18: // pointer.CursorNorthSouthResize
[NSCursor.resizeUpDownCursor set]; [NSCursor.resizeUpDownCursor set];
@@ -329,10 +543,10 @@ void gio_setCursor(NSUInteger curID) {
[NSCursor.resizeDownCursor set]; [NSCursor.resizeDownCursor set];
break; break;
case 24: // pointer.CursorNorthEastSouthWestResize case 24: // pointer.CursorNorthEastSouthWestResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor); trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
break; break;
case 25: // pointer.CursorNorthWestSouthEastResize case 25: // pointer.CursorNorthWestSouthEastResize
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor); trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
break; break;
default: default:
[NSCursor.arrowCursor set]; [NSCursor.arrowCursor set];
@@ -349,7 +563,7 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
NSMiniaturizableWindowMask | NSMiniaturizableWindowMask |
NSClosableWindowMask; NSClosableWindowMask;
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect GioWindow* window = [[GioWindow alloc] initWithContentRect:rect
styleMask:styleMask styleMask:styleMask
backing:NSBackingStoreBuffered backing:NSBackingStoreBuffered
defer:NO]; defer:NO];
@@ -374,27 +588,48 @@ CFTypeRef gio_createView(void) {
GioView* view = [[GioView alloc] initWithFrame:frame]; GioView* view = [[GioView alloc] initWithFrame:frame];
view.wantsLayer = YES; view.wantsLayer = YES;
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
[[NSNotificationCenter defaultCenter] addObserver:view
selector:@selector(applicationWillUnhide:)
name:NSApplicationWillUnhideNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:view
selector:@selector(applicationDidHide:)
name:NSApplicationDidHideNotification
object:nil];
return CFBridgingRetain(view); return CFBridgingRetain(view);
} }
} }
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
@autoreleasepool {
GioView *v = (__bridge GioView *)viewRef;
v.handle = handle;
}
}
@implementation GioAppDelegate @implementation GioAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
gio_onFinishLaunching(); // Force the [NSApp run] call to return.
} [NSApp stop:nil];
- (void)applicationDidHide:(NSNotification *)aNotification { NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
gio_onAppHide(); location:NSZeroPoint
} modifierFlags:0
- (void)applicationWillUnhide:(NSNotification *)notification { timestamp:0
gio_onAppShow(); windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:dummy atStart:YES];
} }
@end @end
void gio_main() { void gio_initApp() {
@autoreleasepool { @autoreleasepool {
[NSApplication sharedApplication]; [GioApplication sharedApplication];
GioAppDelegate *del = [[GioAppDelegate alloc] init]; GioAppDelegate *del = [[GioAppDelegate alloc] init];
[NSApp setDelegate:del]; [NSApp setDelegate:del];
@@ -416,6 +651,22 @@ void gio_main() {
globalWindowDel = [[GioWindowDelegate alloc] init]; globalWindowDel = [[GioWindowDelegate alloc] init];
// Runs until stopped by applicationDidFinishLaunching.
[NSApp run]; [NSApp run];
} }
} }
void gio_wakeupMainThread(void) {
@autoreleasepool {
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSZeroPoint
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:dummy atStart:YES];
}
}
+29 -16
View File
@@ -9,16 +9,10 @@ import (
"errors" "errors"
"unsafe" "unsafe"
"gioui.org/io/event"
"gioui.org/io/pointer" "gioui.org/io/pointer"
) )
// ViewEvent provides handles to the underlying window objects for the
// current display protocol.
type ViewEvent interface {
implementsViewEvent()
ImplementsEvent()
}
type X11ViewEvent struct { type X11ViewEvent struct {
// Display is a pointer to the X11 Display created by XOpenDisplay. // Display is a pointer to the X11 Display created by XOpenDisplay.
Display unsafe.Pointer Display unsafe.Pointer
@@ -39,17 +33,13 @@ type WaylandViewEvent struct {
func (WaylandViewEvent) implementsViewEvent() {} func (WaylandViewEvent) implementsViewEvent() {}
func (WaylandViewEvent) ImplementsEvent() {} func (WaylandViewEvent) ImplementsEvent() {}
func osMain() {
select {}
}
type windowDriver func(*callbacks, []Option) error type windowDriver func(*callbacks, []Option) error
// Instead of creating files with build tags for each combination of wayland +/- x11 // Instead of creating files with build tags for each combination of wayland +/- x11
// 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 +47,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.
+165 -106
View File
@@ -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
@@ -194,12 +199,10 @@ type window struct {
dir f32.Point dir f32.Point
} }
stage Stage configured bool
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.configured = true
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,8 @@ 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.config.Focused = true
w.ProcessEvent(ConfigEvent{Config: w.config})
} }
//export gio_onKeyboardLeave //export gio_onKeyboardLeave
@@ -1226,7 +1232,8 @@ 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.config.Focused = false
w.ProcessEvent(ConfigEvent{Config: w.config})
} }
//export gio_onKeyboardKey //export gio_onKeyboardKey
@@ -1244,7 +1251,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 +1286,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 +1344,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 +1359,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 +1444,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 +1469,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 +1495,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 +1639,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 +1662,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,
@@ -1620,7 +1683,8 @@ func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) {
if w.config.Mode != Windowed || w.config.Decorated { if w.config.Mode != Windowed || w.config.Decorated {
return nil, 0 return nil, 0
} }
border := w.w.w.metric.Dp(3) _, cfg := w.getConfig()
border := cfg.Dp(3)
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
north := y <= border north := y <= border
south := y >= size.Y-border south := y >= size.Y-border
@@ -1674,13 +1738,10 @@ 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.draw(true)
} else {
w.setStage(StageRunning)
w.redraw = true
} }
} }
@@ -1692,7 +1753,10 @@ func (w *window) getConfig() (image.Point, unit.Metric) {
} }
} }
func (w *window) draw() { func (w *window) draw(sync bool) {
if !w.configured {
return
}
w.flushScroll() w.flushScroll()
size, cfg := w.getConfig() size, cfg := w.getConfig()
if cfg == (unit.Metric{}) { if cfg == (unit.Metric{}) {
@@ -1700,11 +1764,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 {
@@ -1715,7 +1777,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,
@@ -1725,14 +1787,6 @@ func (w *window) draw() {
}) })
} }
func (w *window) setStage(s Stage) {
if s == w.stage {
return
}
w.stage = s
w.w.Event(StageEvent{Stage: s})
}
func (w *window) display() *C.struct_wl_display { func (w *window) display() *C.struct_wl_display {
return w.disp.disp return w.disp.disp
} }
@@ -1824,6 +1878,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
@@ -1865,6 +1923,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
} }
} }
+82 -68
View File
@@ -19,17 +19,19 @@ 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"
"gioui.org/io/transfer" "gioui.org/io/transfer"
) )
type ViewEvent struct { type Win32ViewEvent struct {
HWND uintptr HWND uintptr
} }
@@ -37,7 +39,6 @@ type window struct {
hwnd syscall.Handle hwnd syscall.Handle
hdc syscall.Handle hdc syscall.Handle
w *callbacks w *callbacks
stage Stage
pointerBtns pointer.Buttons pointerBtns pointer.Buttons
// cursorIn tracks whether the cursor was inside the window according // cursorIn tracks whether the cursor was inside the window according
@@ -49,10 +50,13 @@ type window struct {
placement *windows.WindowPlacement placement *windows.WindowPlacement
animating bool animating bool
focused bool
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
@@ -81,40 +85,38 @@ var resources struct {
cursor syscall.Handle cursor syscall.Handle
} }
func osMain() { func newWindow(win *callbacks, options []Option) {
select {} done := make(chan struct{})
}
func newWindow(window *callbacks, options []Option) error {
cerr := make(chan error)
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(Win32ViewEvent{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 +151,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 +173,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 +198,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 +239,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,23 +260,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.config.Focused = true
w.w.Event(key.FocusEvent{Focus: true}) w.ProcessEvent(ConfigEvent{Config: w.config})
case windows.WM_KILLFOCUS: case windows.WM_KILLFOCUS:
w.focused = false w.config.Focused = false
w.w.Event(key.FocusEvent{Focus: false}) w.ProcessEvent(ConfigEvent{Config: w.config})
case windows.WM_NCACTIVATE:
if w.stage >= StageInactive {
if wParam == windows.TRUE {
w.setStage(StageRunning)
} else {
w.setStage(StageInactive)
}
}
case windows.WM_NCHITTEST: case windows.WM_NCHITTEST:
if w.config.Decorated { if w.config.Decorated {
// Let the system handle it. // Let the system handle it.
@@ -288,7 +281,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 +294,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(Win32ViewEvent{})
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 +323,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
@@ -339,18 +334,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
switch wParam { switch wParam {
case windows.SIZE_MINIMIZED: case windows.SIZE_MINIMIZED:
w.config.Mode = Minimized w.config.Mode = Minimized
w.setStage(StagePaused)
case windows.SIZE_MAXIMIZED: case windows.SIZE_MAXIMIZED:
w.config.Mode = Maximized w.config.Mode = Maximized
w.setStage(StageRunning)
case windows.SIZE_RESTORED: case windows.SIZE_RESTORED:
if w.config.Mode != Fullscreen { if w.config.Mode != Fullscreen {
w.config.Mode = Windowed w.config.Mode = Windowed
} }
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 +370,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 {
@@ -498,7 +491,7 @@ func (w *window) hitTest(x, y int) uintptr {
} }
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) { func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
if !w.focused { if !w.config.Focused {
windows.SetFocus(w.hwnd) windows.SetFocus(w.hwnd)
} }
@@ -518,7 +511,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 +546,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 +558,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 +569,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 +577,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,16 +594,37 @@ func (w *window) SetAnimating(anim bool) {
w.animating = anim w.animating = anim
} }
func (w *window) Wakeup() { func (w *window) ProcessEvent(e event.Event) {
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil { w.w.ProcessEvent(e)
panic(err) w.loop.FlushEvents()
}
} }
func (w *window) setStage(s Stage) { func (w *window) Event() event.Event {
if s != w.stage { return w.loop.Event()
w.stage = s }
w.w.Event(StageEvent{Stage: s})
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 {
panic(err)
} }
} }
@@ -621,7 +634,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 +681,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))
@@ -974,4 +987,5 @@ func configForDPI(dpi int) unit.Metric {
} }
} }
func (_ ViewEvent) ImplementsEvent() {} func (Win32ViewEvent) implementsViewEvent() {}
func (Win32ViewEvent) ImplementsEvent() {}
+110 -81
View File
@@ -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"
@@ -93,12 +95,10 @@ type x11Window struct {
// _NET_WM_STATE_MAXIMIZED_VERT // _NET_WM_STATE_MAXIMIZED_VERT
wmStateMaximizedVert C.Atom wmStateMaximizedVert C.Atom
} }
stage Stage
metric unit.Metric metric unit.Metric
notify struct { notify struct {
read, write int read, write int
} }
dead bool
animating bool animating bool
@@ -111,6 +111,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 +239,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 +382,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))
} }
@@ -395,16 +436,20 @@ func (w *x11Window) window() (C.Window, int, int) {
return w.xw, w.config.Size.X, w.config.Size.Y return w.xw, w.config.Size.X, w.config.Size.Y
} }
func (w *x11Window) setStage(s Stage) { func (w *x11Window) dispatch() {
if s == w.stage { if w.x == nil {
// Only Invalidate can wake us up.
<-w.wakeups
w.w.Invalidate()
return return
} }
w.stage = s
w.w.Event(StageEvent{Stage: s})
}
func (w *x11Window) loop() { select {
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)} 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 +459,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 +519,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 +577,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 +639,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 +657,16 @@ 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.config.Focused = true
w.ProcessEvent(ConfigEvent{Config: w.config})
case C.FocusOut: case C.FocusOut:
w.w.Event(key.FocusEvent{Focus: false}) w.config.Focused = false
w.ProcessEvent(ConfigEvent{Config: w.config})
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 +688,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 +747,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 +829,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 +868,10 @@ 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.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
} }
+1 -1
View File
@@ -25,6 +25,6 @@ func runMain() {
// Indirect call, since the linker does not know the address of main when // Indirect call, since the linker does not know the address of main when
// laying down this package. // laying down this package.
fn := mainMain fn := mainMain
fn() go fn()
}) })
} }
-36
View File
@@ -10,40 +10,4 @@ type DestroyEvent struct {
Err error Err error
} }
// A StageEvent is generated whenever the stage of a
// Window changes.
type StageEvent struct {
Stage Stage
}
// Stage of a Window.
type Stage uint8
const (
// StagePaused is the stage for windows that have no on-screen representation.
// Paused windows don't receive frames.
StagePaused Stage = iota
// StageInactive is the stage for windows that are visible, but not active.
// Inactive windows receive frames.
StageInactive
// StageRunning is for active and visible Windows.
// Running windows receive frames.
StageRunning
)
// String implements fmt.Stringer.
func (l Stage) String() string {
switch l {
case StagePaused:
return "StagePaused"
case StageInactive:
return "StageInactive"
case StageRunning:
return "StageRunning"
default:
panic("unexpected Stage value")
}
}
func (StageEvent) ImplementsEvent() {}
func (DestroyEvent) ImplementsEvent() {} func (DestroyEvent) ImplementsEvent() {}
+286 -515
View File
File diff suppressed because it is too large Load Diff