mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
Compare commits
17 Commits
v0.5.0
...
app-overhaul
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fe8b684e2 | |||
| 2a18a0c135 | |||
| 93ac0b03f1 | |||
| d58d386b9b | |||
| f3fc0d62b8 | |||
| 5e5d164929 | |||
| 1527e91a02 | |||
| caba422d9c | |||
| 390242f214 | |||
| fe1df00d02 | |||
| 0d7f00c634 | |||
| d7528a8338 | |||
| 9bca5bfdcf | |||
| a880d6403d | |||
| 6879a30582 | |||
| 5cda660e6e | |||
| 8cb06ffa30 |
+1
-1
@@ -65,8 +65,8 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
private final AccessibilityManager accessManager;
|
||||
private int keyboardHint;
|
||||
private AccessibilityManager accessManager;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
|
||||
@@ -56,6 +56,13 @@ type FrameEvent struct {
|
||||
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
|
||||
// system decoration such as translucent
|
||||
// system bars and software keyboards.
|
||||
|
||||
+9
-8
@@ -8,18 +8,19 @@ See https://gioui.org for instructions to set up and run Gio programs.
|
||||
|
||||
# Windows
|
||||
|
||||
Create a new [Window] by calling [NewWindow]. On mobile platforms or when Gio
|
||||
is embedded in another project, NewWindow merely connects with a previously
|
||||
created window.
|
||||
A Window is run by calling its Event method in a loop. The first time a
|
||||
method on Window is called, a new GUI window is created and shown. On mobile
|
||||
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
|
||||
[FrameEvent] that prompts an update of the window contents.
|
||||
The most important event is [FrameEvent] that prompts an update of the window
|
||||
contents.
|
||||
|
||||
For example:
|
||||
|
||||
w := app.NewWindow()
|
||||
w := new(app.Window)
|
||||
for {
|
||||
e := w.NextEvent()
|
||||
e := w.Event()
|
||||
if e, ok := e.(app.FrameEvent); ok {
|
||||
ops.Reset()
|
||||
// Add operations to ops.
|
||||
@@ -49,7 +50,7 @@ For example, to display a blank but otherwise functional window:
|
||||
go func() {
|
||||
w := app.NewWindow()
|
||||
for {
|
||||
w.NextEvent()
|
||||
w.Event()
|
||||
}
|
||||
}()
|
||||
app.Main()
|
||||
|
||||
+11
-1
@@ -69,7 +69,17 @@ func (c *wlContext) Refresh() error {
|
||||
}
|
||||
c.eglWin = 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 {
|
||||
|
||||
+1
-1
@@ -46,8 +46,8 @@ func (c *x11Context) Refresh() error {
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Context.ReleaseCurrent()
|
||||
c.Context.EnableVSync(true)
|
||||
c.Context.ReleaseCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+118
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||
const logLineLimit = 1024
|
||||
|
||||
var logTag = C.CString(appID)
|
||||
var logTag = C.CString(ID)
|
||||
|
||||
func init() {
|
||||
// Android's logcat already includes timestamps.
|
||||
@@ -3,7 +3,7 @@
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
+2
-1
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||
[cmdBuffer presentDrawable:drawable];
|
||||
[cmdBuffer commit];
|
||||
[cmdBuffer waitUntilScheduled];
|
||||
[drawable present];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
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
@@ -14,7 +14,11 @@ package app
|
||||
|
||||
CALayer *gio_layerFactory(void) {
|
||||
@autoreleasepool {
|
||||
return [CAMetalLayer layer];
|
||||
CAMetalLayer *l = [CAMetalLayer layer];
|
||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = YES;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/io/pointer"
|
||||
@@ -43,6 +45,8 @@ type Config struct {
|
||||
CustomRenderer bool
|
||||
// Decorated reports whether window decorations are provided automatically.
|
||||
Decorated bool
|
||||
// Focused reports whether has the keyboard focus.
|
||||
Focused bool
|
||||
// decoHeight is the height of the fallback decoration for platforms such
|
||||
// as Wayland that may need fallback client-side decorations.
|
||||
decoHeight unit.Dp
|
||||
@@ -131,6 +135,28 @@ func (o Orientation) String() string {
|
||||
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 {
|
||||
FrameEvent
|
||||
|
||||
@@ -147,9 +173,19 @@ type context interface {
|
||||
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.
|
||||
type driver interface {
|
||||
basicDriver
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
@@ -166,17 +202,23 @@ type driver interface {
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(cursor pointer.Cursor)
|
||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
||||
Wakeup()
|
||||
// Wakeup()
|
||||
// Perform actions on the window.
|
||||
Perform(system.Action)
|
||||
// EditorStateChanged notifies the driver that the editor state changed.
|
||||
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 {
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
errs chan error
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
windows chan struct{}
|
||||
}
|
||||
|
||||
type windowAndConfig struct {
|
||||
@@ -186,32 +228,137 @@ type windowAndConfig struct {
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
errs: make(chan error),
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
windows: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
var main windowAndConfig
|
||||
in := wr.in
|
||||
var window windowAndConfig
|
||||
var out chan windowAndConfig
|
||||
for {
|
||||
select {
|
||||
case w := <-wr.in:
|
||||
var err error
|
||||
if main.window != nil {
|
||||
err = errors.New("multiple windows are not supported")
|
||||
}
|
||||
wr.errs <- err
|
||||
main = w
|
||||
case w := <-in:
|
||||
window = w
|
||||
out = wr.out
|
||||
case out <- main:
|
||||
case out <- window:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||
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)) {
|
||||
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
-77
@@ -137,8 +137,10 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
@@ -150,6 +152,7 @@ import (
|
||||
|
||||
type window struct {
|
||||
callbacks *callbacks
|
||||
loop *eventLoop
|
||||
|
||||
view C.jobject
|
||||
handle cgo.Handle
|
||||
@@ -158,12 +161,13 @@ type window struct {
|
||||
fontScale float32
|
||||
insets pixelInsets
|
||||
|
||||
stage Stage
|
||||
visible bool
|
||||
started bool
|
||||
animating bool
|
||||
|
||||
win *C.ANativeWindow
|
||||
config Config
|
||||
win *C.ANativeWindow
|
||||
config Config
|
||||
inputHint key.InputHint
|
||||
|
||||
semantic struct {
|
||||
hoverID input.SemanticID
|
||||
@@ -201,9 +205,9 @@ type pixelInsets struct {
|
||||
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.
|
||||
type ViewEvent struct {
|
||||
type AndroidViewEvent struct {
|
||||
// View is a JNI global reference to the android.view.View
|
||||
// instance backing the Window. The reference is valid until
|
||||
// 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)
|
||||
wopts := <-mainWindow.out
|
||||
var cnf Config
|
||||
w, ok := windows[wopts.window]
|
||||
if !ok {
|
||||
w = &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
|
||||
} else {
|
||||
cnf = w.config
|
||||
}
|
||||
mainWindow.windows <- struct{}{}
|
||||
if w.view != 0 {
|
||||
w.detach(env)
|
||||
}
|
||||
w.view = view
|
||||
w.visible = false
|
||||
w.handle = cgo.NewHandle(w)
|
||||
w.callbacks.SetDriver(w)
|
||||
w.loadConfig(env, class)
|
||||
w.Configure(wopts.options)
|
||||
w.SetInputHint(key.HintAny)
|
||||
w.setStage(StagePaused)
|
||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
||||
w.setConfig(env, cnf)
|
||||
w.SetInputHint(w.inputHint)
|
||||
w.processEvent(AndroidViewEvent{View: uintptr(view)})
|
||||
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) {
|
||||
w := cgo.Handle(handle).Value().(*window)
|
||||
w.started = false
|
||||
w.setStage(StagePaused)
|
||||
w.visible = false
|
||||
}
|
||||
|
||||
//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) {
|
||||
w := cgo.Handle(handle).Value().(*window)
|
||||
w.win = nil
|
||||
w.setStage(StagePaused)
|
||||
w.visible = false
|
||||
}
|
||||
|
||||
//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) {
|
||||
w := cgo.Handle(view).Value().(*window)
|
||||
w.loadConfig(env, class)
|
||||
if w.stage >= StageInactive {
|
||||
w.draw(env, true)
|
||||
}
|
||||
w.draw(env, true)
|
||||
}
|
||||
|
||||
//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 {
|
||||
return
|
||||
}
|
||||
if w.stage < StageInactive {
|
||||
return
|
||||
}
|
||||
if w.animating {
|
||||
if w.visible && w.animating {
|
||||
w.draw(env, false)
|
||||
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
|
||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||
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_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
|
||||
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.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
|
||||
@@ -600,9 +606,7 @@ func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C
|
||||
left: int(left),
|
||||
right: int(right),
|
||||
}
|
||||
if w.stage >= StageInactive {
|
||||
w.draw(env, true)
|
||||
}
|
||||
w.draw(env, true)
|
||||
}
|
||||
|
||||
//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 {
|
||||
for _, ch := range sem.Children {
|
||||
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) {
|
||||
callVoidMethod(env, w.view, gioView.unregister)
|
||||
w.callbacks.Event(ViewEvent{})
|
||||
w.callbacks.SetDriver(nil)
|
||||
w.processEvent(AndroidViewEvent{})
|
||||
w.handle.Delete()
|
||||
C.jni_DeleteGlobalRef(env, w.view)
|
||||
w.view = 0
|
||||
@@ -779,18 +810,10 @@ func (w *window) setVisible(env *C.JNIEnv) {
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
w.setStage(StageRunning)
|
||||
w.visible = 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 {
|
||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
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) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||
if size != w.config.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 {
|
||||
return
|
||||
@@ -844,7 +870,7 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||
Left: unit.Dp(w.insets.left) * dppp,
|
||||
Right: unit.Dp(w.insets.right) * dppp,
|
||||
}
|
||||
w.callbacks.Event(frameEvent{
|
||||
w.processEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
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 {
|
||||
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).
|
||||
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:
|
||||
return
|
||||
}
|
||||
w.callbacks.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: src,
|
||||
Buttons: btns,
|
||||
@@ -1146,6 +1172,8 @@ func (w *window) ShowTextInput(show bool) {
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
w.inputHint = mode
|
||||
|
||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||
const (
|
||||
TYPE_NULL = 0
|
||||
@@ -1292,9 +1320,9 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
||||
func osMain() {
|
||||
}
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{window, options}
|
||||
return <-mainWindow.errs
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
@@ -1313,7 +1341,7 @@ func (w *window) ReadClipboard() {
|
||||
return
|
||||
}
|
||||
content := goString(env, C.jstring(c))
|
||||
w.callbacks.Event(transfer.DataEvent{
|
||||
w.processEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -1323,42 +1351,46 @@ func (w *window) ReadClipboard() {
|
||||
}
|
||||
|
||||
func (w *window) Configure(options []Option) {
|
||||
cnf := w.config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
prev := w.config
|
||||
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})
|
||||
w.setConfig(env, cnf)
|
||||
})
|
||||
}
|
||||
|
||||
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) SetCursor(cursor pointer.Cursor) {
|
||||
@@ -1367,9 +1399,10 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
w.callbacks.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1460,4 +1493,5 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (AndroidViewEvent) implementsViewEvent() {}
|
||||
func (AndroidViewEvent) ImplementsEvent() {}
|
||||
|
||||
+3
-2
@@ -260,8 +260,9 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func() {
|
||||
w.w.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
|
||||
+92
-58
@@ -12,6 +12,8 @@ package app
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
@@ -74,6 +76,7 @@ import (
|
||||
"image"
|
||||
"io"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -81,14 +84,16 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type UIKitViewEvent struct {
|
||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||
ViewController uintptr
|
||||
}
|
||||
@@ -97,18 +102,17 @@ type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
displayLink *displayLink
|
||||
loop *eventLoop
|
||||
|
||||
visible bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
hidden bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var views = make(map[C.CFTypeRef]*window)
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
@@ -116,48 +120,52 @@ func init() {
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view, controller C.CFTypeRef) {
|
||||
wopts := <-mainWindow.out
|
||||
w := &window{
|
||||
view: view,
|
||||
w: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
mainWindow.windows <- struct{}{}
|
||||
dl, err := newDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
w.displayLink = dl
|
||||
wopts := <-mainWindow.out
|
||||
w.w = wopts.window
|
||||
w.w.SetDriver(w)
|
||||
views[view] = w
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.Configure(wopts.options)
|
||||
w.w.Event(StageEvent{Stage: StagePaused})
|
||||
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
||||
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||
}
|
||||
|
||||
func viewFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
if w.hidden {
|
||||
return
|
||||
}
|
||||
params := C.viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
wasVisible := w.visible
|
||||
w.visible = true
|
||||
if !wasVisible {
|
||||
w.w.Event(StageEvent{Stage: StageRunning})
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
m := unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
}
|
||||
dppp := unit.Dp(1. / m.PxPerDp)
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
@@ -177,26 +185,34 @@ func (w *window) draw(sync bool) {
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.visible = false
|
||||
w.w.Event(StageEvent{Stage: StagePaused})
|
||||
func onStop(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = true
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = false
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
delete(views, view)
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(DestroyEvent{})
|
||||
func onDestroy(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.ProcessEvent(UIKitViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.displayLink = nil
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(view C.CFTypeRef, focus int) {
|
||||
w := views[view]
|
||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
||||
func onFocus(h C.uintptr_t, focus int) {
|
||||
w := viewFor(h)
|
||||
w.config.Focused = focus != 0
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
@@ -206,38 +222,38 @@ func onLowMemory() {
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameUpArrow)
|
||||
func onUpArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDownArrow)
|
||||
func onDownArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameLeftArrow)
|
||||
func onLeftArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameRightArrow)
|
||||
func onRightArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
||||
func onDeleteBackward(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(view, str C.CFTypeRef) {
|
||||
w := views[view]
|
||||
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||
w := viewFor(h)
|
||||
w.w.EditorInsert(nsstringToString(str))
|
||||
}
|
||||
|
||||
//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
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
@@ -251,10 +267,10 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := views[view]
|
||||
w := viewFor(h)
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
@@ -267,7 +283,7 @@ func (w *window) ReadClipboard() {
|
||||
cstr := C.readClipboard()
|
||||
defer C.CFRelease(cstr)
|
||||
content := nsstringToString(cstr)
|
||||
w.w.Event(transfer.DataEvent{
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -287,7 +303,7 @@ func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
func (w *window) Configure([]Option) {
|
||||
// Decorations are never disabled.
|
||||
w.config.Decorated = true
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
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) SetAnimating(anim bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
@@ -311,7 +323,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name key.Name) {
|
||||
w.w.Event(key.Event{
|
||||
w.ProcessEvent(key.Event{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
@@ -350,9 +362,30 @@ func (w *window) ShowTextInput(show bool) {
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
return <-mainWindow.errs
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
@@ -363,4 +396,5 @@ func gio_runMain() {
|
||||
runMain()
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (UIKitViewEvent) implementsViewEvent() {}
|
||||
func (UIKitViewEvent) ImplementsEvent() {}
|
||||
|
||||
+26
-21
@@ -11,6 +11,7 @@
|
||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||
|
||||
@interface GioView: UIView <UIKeyInput>
|
||||
@property uintptr_t handle;
|
||||
@end
|
||||
|
||||
@implementation GioViewController
|
||||
@@ -54,33 +55,33 @@ CGFloat _keyboardHeight;
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStart(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
onStop((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStop(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
||||
onDestroy(viewRef);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
onDestroy(view.handle);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
UIView *view = self.view.subviews[0];
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
CGRect frame = self.view.bounds;
|
||||
// Adjust view bounds to make room for the keyboard.
|
||||
frame.size.height -= _keyboardHeight;
|
||||
view.frame = frame;
|
||||
gio_onDraw((__bridge CFTypeRef)view);
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
@@ -101,11 +102,10 @@ CGFloat _keyboardHeight;
|
||||
}
|
||||
@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;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
||||
for (UITouch *touch in touches) {
|
||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||
i++;
|
||||
@@ -116,7 +116,7 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
||||
CGPoint loc = [coalescedTouch locationInView:view];
|
||||
j++;
|
||||
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 {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, YES);
|
||||
onFocus(self.handle, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, NO);
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text {
|
||||
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text);
|
||||
onText(self.handle, (__bridge CFTypeRef)text);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
@@ -190,23 +190,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward((__bridge CFTypeRef)self);
|
||||
onDeleteBackward(self.handle);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow((__bridge CFTypeRef)self);
|
||||
onUpArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow((__bridge CFTypeRef)self);
|
||||
onDownArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow((__bridge CFTypeRef)self);
|
||||
onLeftArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow((__bridge CFTypeRef)self);
|
||||
onRightArrow(self.handle);
|
||||
}
|
||||
|
||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||
@@ -271,3 +271,8 @@ void gio_showCursor() {
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
|
||||
+81
-87
@@ -14,8 +14,10 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
@@ -23,7 +25,7 @@ import (
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type JSViewEvent struct {
|
||||
Element js.Value
|
||||
}
|
||||
|
||||
@@ -54,9 +56,6 @@ type window struct {
|
||||
composing bool
|
||||
requestFocus bool
|
||||
|
||||
chanAnimation chan struct{}
|
||||
chanRedraw chan struct{}
|
||||
|
||||
config Config
|
||||
inset f32.Point
|
||||
scale float32
|
||||
@@ -69,7 +68,7 @@ type window struct {
|
||||
contextStatus contextStatus
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
doc := js.Global().Get("document")
|
||||
cont := getContainer(doc)
|
||||
cnv := createCanvas(doc)
|
||||
@@ -84,7 +83,9 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
head: doc.Get("head"),
|
||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
w: win,
|
||||
}
|
||||
w.w.SetDriver(w)
|
||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||
w.browserHistory = w.window.Get("history")
|
||||
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() {
|
||||
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.chanAnimation <- struct{}{}
|
||||
w.draw(false)
|
||||
return nil
|
||||
})
|
||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
content := args[0].String()
|
||||
go win.Event(transfer.DataEvent{
|
||||
w.processEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -112,29 +111,12 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
})
|
||||
w.addEventListeners()
|
||||
w.addHistory()
|
||||
w.w = win
|
||||
|
||||
go func() {
|
||||
defer w.cleanup()
|
||||
w.w.SetDriver(w)
|
||||
w.Configure(options)
|
||||
w.blur()
|
||||
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
|
||||
w.Configure(options)
|
||||
w.blur()
|
||||
w.processEvent(JSViewEvent{Element: cont})
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func getContainer(doc js.Value) js.Value {
|
||||
@@ -194,12 +176,12 @@ func (w *window) addEventListeners() {
|
||||
w.cnv.Set("width", 0)
|
||||
w.cnv.Set("height", 0)
|
||||
w.resize()
|
||||
w.requestRedraw()
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||
w.resize()
|
||||
w.requestRedraw()
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||
@@ -207,22 +189,11 @@ func (w *window) addEventListeners() {
|
||||
return nil
|
||||
})
|
||||
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("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.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||
return nil
|
||||
@@ -280,18 +251,20 @@ func (w *window) addEventListeners() {
|
||||
w.touches[i] = js.Null()
|
||||
}
|
||||
w.touches = w.touches[:0]
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
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
|
||||
})
|
||||
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()
|
||||
return nil
|
||||
})
|
||||
@@ -380,10 +353,50 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||
Modifiers: modifiersFor(e),
|
||||
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
|
||||
// KeyEvent.
|
||||
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,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
Position: pos,
|
||||
@@ -481,7 +494,7 @@ func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
||||
if jbtns&4 != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: btns,
|
||||
@@ -508,17 +521,6 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
||||
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) SetAnimating(anim bool) {
|
||||
@@ -574,7 +576,7 @@ func (w *window) Configure(options []Option) {
|
||||
if cnf.Decorated != prev.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) {}
|
||||
@@ -613,23 +615,14 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
style.Set("cursor", webCursor[cursor])
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
// Run in a goroutine to avoid a deadlock if the
|
||||
// focus change result in an event.
|
||||
go func() {
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}()
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
@@ -646,7 +639,7 @@ func (w *window) resize() {
|
||||
}
|
||||
if size != w.config.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() {
|
||||
@@ -666,12 +659,19 @@ func (w *window) draw(sync bool) {
|
||||
if w.contextStatus == contextStatusLost {
|
||||
return
|
||||
}
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
} else if !sync {
|
||||
return
|
||||
}
|
||||
size, insets, metric := w.getConfig()
|
||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.w.Event(frameEvent{
|
||||
w.processEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: size,
|
||||
@@ -741,13 +741,6 @@ func (w *window) navigationColor(c color.NRGBA) {
|
||||
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 {}
|
||||
}
|
||||
@@ -827,4 +820,5 @@ func translateKey(k string) (key.Name, bool) {
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (JSViewEvent) implementsViewEvent() {}
|
||||
func (JSViewEvent) ImplementsEvent() {}
|
||||
|
||||
+253
-204
@@ -10,16 +10,19 @@ import (
|
||||
"image"
|
||||
"io"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/internal/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
_ "gioui.org/internal/cocoainit"
|
||||
@@ -39,6 +42,7 @@ import (
|
||||
__attribute__ ((visibility ("hidden"))) void gio_main(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"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
static void writeClipboard(CFTypeRef str) {
|
||||
@autoreleasepool {
|
||||
@@ -58,148 +62,204 @@ static CFTypeRef readClipboard(void) {
|
||||
}
|
||||
|
||||
static CGFloat viewHeight(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.height;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.height;
|
||||
}
|
||||
}
|
||||
|
||||
static CGFloat viewWidth(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.width;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.width;
|
||||
}
|
||||
}
|
||||
|
||||
static CGFloat getScreenBackingScale(void) {
|
||||
return [NSScreen.mainScreen backingScaleFactor];
|
||||
@autoreleasepool {
|
||||
return [NSScreen.mainScreen backingScaleFactor];
|
||||
}
|
||||
}
|
||||
|
||||
static CGFloat getViewBackingScale(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view.window backingScaleFactor];
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view.window backingScaleFactor];
|
||||
}
|
||||
}
|
||||
|
||||
static void setNeedsDisplay(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setNeedsDisplay:YES];
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window cascadeTopLeftFromPoint:topLeft];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window cascadeTopLeftFromPoint:topLeft];
|
||||
}
|
||||
}
|
||||
|
||||
static void makeKeyAndOrderFront(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static void toggleFullScreen(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window toggleFullScreen:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window toggleFullScreen:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window styleMask];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window styleMask];
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.styleMask = mask;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.styleMask = mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titleVisibility = state;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titleVisibility = state;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titlebarAppearsTransparent = (BOOL)transparent;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titlebarAppearsTransparent = (BOOL)transparent;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window standardWindowButton:btn].hidden = (BOOL)hide;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window standardWindowButton:btn].hidden = (BOOL)hide;
|
||||
}
|
||||
}
|
||||
|
||||
static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
|
||||
}
|
||||
}
|
||||
|
||||
static void closeWindow(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window performClose:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window performClose:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSSize size = NSMakeSize(width, height);
|
||||
[window setContentSize:size];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSSize size = NSMakeSize(width, height);
|
||||
[window setContentSize:size];
|
||||
}
|
||||
}
|
||||
|
||||
static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMinSize = NSMakeSize(width, height);
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMinSize = NSMakeSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMaxSize = NSMakeSize(width, height);
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMaxSize = NSMakeSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSRect r = NSMakeRect(x, y, w, h);
|
||||
[window setFrame:r display:YES];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSRect r = NSMakeRect(x, y, w, h);
|
||||
[window setFrame:r display:YES];
|
||||
}
|
||||
}
|
||||
|
||||
static void hideWindow(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window miniaturize:window];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window miniaturize:window];
|
||||
}
|
||||
}
|
||||
|
||||
static void unhideWindow(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window deminiaturize:window];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window deminiaturize:window];
|
||||
}
|
||||
}
|
||||
|
||||
static NSRect getScreenFrame(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
return [[window screen] frame];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
return [[window screen] frame];
|
||||
}
|
||||
}
|
||||
|
||||
static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.title = (__bridge NSString *)titleRef;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.title = (__bridge NSString *)titleRef;
|
||||
}
|
||||
}
|
||||
|
||||
static int isWindowZoomed(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return window.zoomed ? 1 : 0;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return window.zoomed ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void zoomWindow(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window zoom:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window zoom:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef layerForView(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.layer;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.layer;
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef windowForView(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.window;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.window;
|
||||
}
|
||||
}
|
||||
|
||||
static void raiseWindow(CFTypeRef windowRef) {
|
||||
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
|
||||
if (![currentApp isActive]) {
|
||||
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||
@autoreleasepool {
|
||||
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
|
||||
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) {
|
||||
@@ -211,23 +271,23 @@ static CFTypeRef createInputContext(CFTypeRef clientRef) {
|
||||
}
|
||||
|
||||
static void discardMarkedText(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
@autoreleasepool {
|
||||
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
||||
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
||||
if (view == [ctx client]) {
|
||||
[ctx discardMarkedText];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
@autoreleasepool {
|
||||
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
||||
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
||||
if (view == [ctx client]) {
|
||||
[ctx invalidateCharacterCoordinates];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
@@ -237,9 +297,9 @@ func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
// ViewEvent notified the client of changes to the window AppKit handles.
|
||||
// The handles are retained until another ViewEvent is sent.
|
||||
type ViewEvent struct {
|
||||
// AppKitViewEvent notifies the client of changes to the window AppKit handles.
|
||||
// The handles are retained until another AppKitViewEvent is sent.
|
||||
type AppKitViewEvent struct {
|
||||
// View is a CFTypeRef for the NSView for the window.
|
||||
View uintptr
|
||||
// Layer is a CFTypeRef of the CALayer of View.
|
||||
@@ -249,21 +309,20 @@ type ViewEvent struct {
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
stage Stage
|
||||
anim bool
|
||||
visible bool
|
||||
displayLink *displayLink
|
||||
// redraw is a single entry channel for making sure only one
|
||||
// display link redraw request is in flight.
|
||||
redraw chan struct{}
|
||||
cursor pointer.Cursor
|
||||
pointerBtns pointer.Buttons
|
||||
loop *eventLoop
|
||||
|
||||
scale float32
|
||||
config Config
|
||||
}
|
||||
|
||||
// viewMap is the mapping from Cocoa NSViews to Go windows.
|
||||
var viewMap = make(map[C.CFTypeRef]*window)
|
||||
|
||||
// launched is closed when applicationDidFinishLaunching is called.
|
||||
var launched = make(chan struct{})
|
||||
|
||||
@@ -271,30 +330,8 @@ var launched = make(chan struct{})
|
||||
// cascadeTopLeftFromPoint.
|
||||
var nextTopLeft C.NSPoint
|
||||
|
||||
// mustView is like lookupView, except that it panics
|
||||
// if the view isn't mapped.
|
||||
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) {
|
||||
w, exists := viewMap[view]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
return w, true
|
||||
}
|
||||
|
||||
func deleteView(view C.CFTypeRef) {
|
||||
delete(viewMap, view)
|
||||
}
|
||||
|
||||
func insertView(view C.CFTypeRef, w *window) {
|
||||
viewMap[view] = w
|
||||
func windowFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
func (w *window) contextView() C.CFTypeRef {
|
||||
@@ -307,7 +344,7 @@ func (w *window) ReadClipboard() {
|
||||
defer C.CFRelease(cstr)
|
||||
}
|
||||
content := nsstringToString(cstr)
|
||||
w.w.Event(transfer.DataEvent{
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -420,7 +457,7 @@ func (w *window) Configure(options []Option) {
|
||||
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, 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) {
|
||||
@@ -471,7 +508,8 @@ func (w *window) ShowTextInput(show bool) {}
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.anim = anim
|
||||
if w.anim && w.visible {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
@@ -488,26 +526,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
|
||||
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)
|
||||
kmods := convertMods(mods)
|
||||
ks := key.Release
|
||||
if keyDown {
|
||||
ks = key.Press
|
||||
}
|
||||
w := mustView(view)
|
||||
w := windowFor(h)
|
||||
for _, k := range str {
|
||||
if n, ok := convertKey(k); ok {
|
||||
w.w.Event(key.Event{
|
||||
w.ProcessEvent(key.Event{
|
||||
Name: n,
|
||||
Modifiers: kmods,
|
||||
State: ks,
|
||||
@@ -517,15 +547,15 @@ func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown
|
||||
}
|
||||
|
||||
//export gio_onText
|
||||
func gio_onText(view, cstr C.CFTypeRef) {
|
||||
func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
|
||||
str := nsstringToString(cstr)
|
||||
w := mustView(view)
|
||||
w := windowFor(h)
|
||||
w.w.EditorInsert(str)
|
||||
}
|
||||
|
||||
//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) {
|
||||
w := mustView(view)
|
||||
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 := windowFor(h)
|
||||
t := time.Duration(float64(ti)*float64(time.Second) + .5)
|
||||
xf, yf := float32(x)*w.scale, float32(y)*w.scale
|
||||
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
|
||||
@@ -562,7 +592,7 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
|
||||
default:
|
||||
panic("invalid direction")
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: typ,
|
||||
Source: pointer.Mouse,
|
||||
Time: t,
|
||||
@@ -574,35 +604,29 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.draw()
|
||||
}
|
||||
|
||||
//export gio_onFocus
|
||||
func gio_onFocus(view C.CFTypeRef, focus C.int) {
|
||||
w := mustView(view)
|
||||
w.w.Event(key.FocusEvent{Focus: focus == 1})
|
||||
if w.stage >= StageInactive {
|
||||
if focus == 0 {
|
||||
w.setStage(StageInactive)
|
||||
} else {
|
||||
w.setStage(StageRunning)
|
||||
}
|
||||
}
|
||||
func gio_onFocus(h C.uintptr_t, focus C.int) {
|
||||
w := windowFor(h)
|
||||
w.SetCursor(w.cursor)
|
||||
w.config.Focused = focus == 1
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onChangeScreen
|
||||
func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
|
||||
w := mustView(view)
|
||||
func gio_onChangeScreen(h C.uintptr_t, did uint64) {
|
||||
w := windowFor(h)
|
||||
w.displayLink.SetDisplayID(did)
|
||||
C.setNeedsDisplay(w.view)
|
||||
}
|
||||
|
||||
//export gio_hasMarkedText
|
||||
func gio_hasMarkedText(view C.CFTypeRef) C.int {
|
||||
w := mustView(view)
|
||||
func gio_hasMarkedText(h C.uintptr_t) C.int {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
if state.compose.Start != -1 {
|
||||
return 1
|
||||
@@ -611,8 +635,8 @@ func gio_hasMarkedText(view C.CFTypeRef) C.int {
|
||||
}
|
||||
|
||||
//export gio_markedRange
|
||||
func gio_markedRange(view C.CFTypeRef) C.NSRange {
|
||||
w := mustView(view)
|
||||
func gio_markedRange(h C.uintptr_t) C.NSRange {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
rng := state.compose
|
||||
start, end := rng.Start, rng.End
|
||||
@@ -627,8 +651,8 @@ func gio_markedRange(view C.CFTypeRef) C.NSRange {
|
||||
}
|
||||
|
||||
//export gio_selectedRange
|
||||
func gio_selectedRange(view C.CFTypeRef) C.NSRange {
|
||||
w := mustView(view)
|
||||
func gio_selectedRange(h C.uintptr_t) C.NSRange {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
rng := state.Selection
|
||||
start, end := rng.Start, rng.End
|
||||
@@ -643,14 +667,14 @@ func gio_selectedRange(view C.CFTypeRef) C.NSRange {
|
||||
}
|
||||
|
||||
//export gio_unmarkText
|
||||
func gio_unmarkText(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_unmarkText(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||
}
|
||||
|
||||
//export gio_setMarkedText
|
||||
func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
|
||||
w := mustView(view)
|
||||
func gio_setMarkedText(h C.uintptr_t, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
|
||||
w := windowFor(h)
|
||||
str := nsstringToString(cstr)
|
||||
state := w.w.EditorState()
|
||||
rng := state.compose
|
||||
@@ -689,8 +713,8 @@ func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange
|
||||
}
|
||||
|
||||
//export gio_substringForProposedRange
|
||||
func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
|
||||
w := mustView(view)
|
||||
func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
start, end := state.Snippet.Start, state.Snippet.End
|
||||
if start > end {
|
||||
@@ -710,8 +734,8 @@ func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NS
|
||||
}
|
||||
|
||||
//export gio_insertText
|
||||
func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
|
||||
w := mustView(view)
|
||||
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
rng := state.compose
|
||||
if rng.Start == -1 {
|
||||
@@ -735,13 +759,13 @@ func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
|
||||
}
|
||||
|
||||
//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
|
||||
}
|
||||
|
||||
//export gio_firstRectForCharacterRange
|
||||
func gio_firstRectForCharacterRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
|
||||
w := mustView(view)
|
||||
func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
sel := state.Selection
|
||||
u16start := state.UTF16Index(sel.Start)
|
||||
@@ -768,6 +792,10 @@ func (w *window) draw() {
|
||||
case <-w.redraw:
|
||||
default:
|
||||
}
|
||||
w.visible = true
|
||||
if w.anim {
|
||||
w.SetAnimating(w.anim)
|
||||
}
|
||||
w.scale = float32(C.getViewBackingScale(w.view))
|
||||
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
|
||||
sz := image.Point{
|
||||
@@ -776,14 +804,13 @@ func (w *window) draw() {
|
||||
}
|
||||
if sz != w.config.Size {
|
||||
w.config.Size = sz
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
if sz.X == 0 || sz.Y == 0 {
|
||||
return
|
||||
}
|
||||
cfg := configFor(w.scale)
|
||||
w.setStage(StageRunning)
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
@@ -793,6 +820,27 @@ func (w *window) draw() {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func configFor(scale float32) unit.Metric {
|
||||
return unit.Metric{
|
||||
PxPerDp: scale,
|
||||
@@ -800,56 +848,54 @@ func configFor(scale float32) unit.Metric {
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onClose
|
||||
func gio_onClose(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(DestroyEvent{})
|
||||
//export gio_onAttached
|
||||
func gio_onAttached(h C.uintptr_t, attached C.int) {
|
||||
w := windowFor(h)
|
||||
if attached != 0 {
|
||||
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 = nil
|
||||
deleteView(view)
|
||||
C.CFRelease(w.view)
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export gio_onHide
|
||||
func gio_onHide(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(StagePaused)
|
||||
func gio_onHide(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.visible = false
|
||||
w.SetAnimating(w.anim)
|
||||
}
|
||||
|
||||
//export gio_onShow
|
||||
func gio_onShow(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(StageRunning)
|
||||
func gio_onShow(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.draw()
|
||||
}
|
||||
|
||||
//export gio_onFullscreen
|
||||
func gio_onFullscreen(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_onFullscreen(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.config.Mode = Fullscreen
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onWindowed
|
||||
func gio_onWindowed(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_onWindowed(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.config.Mode = Windowed
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onAppHide
|
||||
func gio_onAppHide() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(StagePaused)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onAppShow
|
||||
func gio_onAppShow() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(StageRunning)
|
||||
}
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onFinishLaunching
|
||||
@@ -857,20 +903,25 @@ func gio_onFinishLaunching() {
|
||||
close(launched)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
<-launched
|
||||
errch := make(chan error)
|
||||
res := make(chan struct{})
|
||||
runOnMain(func() {
|
||||
w, err := newOSWindow()
|
||||
if err != nil {
|
||||
errch <- err
|
||||
w := &window{
|
||||
redraw: make(chan struct{}, 1),
|
||||
w: win,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
win.SetDriver(w)
|
||||
res <- struct{}{}
|
||||
if err := w.init(); err != nil {
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
errch <- nil
|
||||
w.w = win
|
||||
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()
|
||||
win.SetDriver(w)
|
||||
w.Configure(options)
|
||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||
@@ -880,23 +931,17 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
||||
// makeKeyAndOrderFront assumes ownership of our window reference.
|
||||
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()
|
||||
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))
|
||||
w := &window{
|
||||
view: view,
|
||||
scale: scale,
|
||||
redraw: make(chan struct{}, 1),
|
||||
}
|
||||
w.scale = scale
|
||||
dl, err := newDisplayLink(func() {
|
||||
select {
|
||||
case w.redraw <- struct{}{}:
|
||||
@@ -904,16 +949,19 @@ func newOSWindow() (*window, error) {
|
||||
return
|
||||
}
|
||||
w.runOnMain(func() {
|
||||
C.setNeedsDisplay(w.view)
|
||||
if w.visible {
|
||||
C.setNeedsDisplay(w.view)
|
||||
}
|
||||
})
|
||||
})
|
||||
w.displayLink = dl
|
||||
if err != nil {
|
||||
C.CFRelease(view)
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
insertView(view, w)
|
||||
return w, nil
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.view = view
|
||||
return nil
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
@@ -1004,4 +1052,5 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
|
||||
return kmods
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (AppKitViewEvent) implementsViewEvent() {}
|
||||
func (AppKitViewEvent) ImplementsEvent() {}
|
||||
|
||||
+72
-50
@@ -14,40 +14,50 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@property uintptr_t handle;
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onHide(view.handle);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onShow(view.handle);
|
||||
}
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onFullscreen(view.handle);
|
||||
}
|
||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onWindowed((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onWindowed(view.handle);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
||||
gio_onChangeScreen(view, dispID);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onChangeScreen(view.handle, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
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 {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onFocus(view.handle, 0);
|
||||
}
|
||||
@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];
|
||||
if (!event.hasPreciseScrollingDeltas) {
|
||||
// dx and dy are in rows and columns.
|
||||
@@ -56,12 +66,9 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
}
|
||||
// Origin is in the lower left corner. Convert to upper left.
|
||||
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>
|
||||
@end
|
||||
|
||||
@implementation GioView
|
||||
- (void)setFrameSize:(NSSize)newSize {
|
||||
[super setFrameSize:newSize];
|
||||
@@ -70,11 +77,11 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||
// Don't know why.
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)displayLayer:(CALayer *)layer {
|
||||
layer.contentsScale = self.window.backingScaleFactor;
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (CALayer *)makeBackingLayer {
|
||||
CALayer *layer = gio_layerFactory();
|
||||
@@ -82,9 +89,7 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
return layer;
|
||||
}
|
||||
- (void)viewDidMoveToWindow {
|
||||
if (self.window == nil) {
|
||||
gio_onClose((__bridge CFTypeRef)self);
|
||||
}
|
||||
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
@@ -124,14 +129,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
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 {
|
||||
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 {
|
||||
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
|
||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)sel {
|
||||
// Don't pass commands up the responder chain.
|
||||
@@ -139,17 +144,17 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText {
|
||||
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
|
||||
int res = gio_hasMarkedText(self.handle);
|
||||
return res ? YES : NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return gio_markedRange((__bridge CFTypeRef)self);
|
||||
return gio_markedRange(self.handle);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return gio_selectedRange((__bridge CFTypeRef)self);
|
||||
return gio_selectedRange(self.handle);
|
||||
}
|
||||
- (void)unmarkText {
|
||||
gio_unmarkText((__bridge CFTypeRef)self);
|
||||
gio_unmarkText(self.handle);
|
||||
}
|
||||
- (void)setMarkedText:(id)string
|
||||
selectedRange:(NSRange)selRange
|
||||
@@ -161,14 +166,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return nil;
|
||||
}
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||
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];
|
||||
}
|
||||
- (void)insertText:(id)string
|
||||
@@ -180,17 +185,26 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
|
||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
|
||||
return gio_characterIndexForPoint(self.handle, p);
|
||||
}
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||
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];
|
||||
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
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
@@ -240,7 +254,7 @@ void gio_showCursor() {
|
||||
|
||||
// some cursors are not public, this tries to use a private cursor
|
||||
// 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]) {
|
||||
id object = [NSCursor performSelector:cursorName];
|
||||
if ([object isKindOfClass:[NSCursor class]]) {
|
||||
@@ -272,7 +286,7 @@ void gio_setCursor(NSUInteger curID) {
|
||||
break;
|
||||
case 6: // pointer.CursorAllScroll
|
||||
// For some reason, using _moveCursor fails on Monterey.
|
||||
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 7: // pointer.CursorColResize
|
||||
@@ -282,33 +296,31 @@ void gio_setCursor(NSUInteger curID) {
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 9: // pointer.CursorGrab
|
||||
// [NSCursor.openHandCursor set];
|
||||
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
case 10: // pointer.CursorGrabbing
|
||||
// [NSCursor.closedHandCursor set];
|
||||
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.closedHandCursor set];
|
||||
break;
|
||||
case 11: // pointer.CursorNotAllowed
|
||||
[NSCursor.operationNotAllowedCursor set];
|
||||
break;
|
||||
case 12: // pointer.CursorWait
|
||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 13: // pointer.CursorProgress
|
||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 14: // pointer.CursorNorthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 15: // pointer.CursorNorthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 16: // pointer.CursorSouthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 17: // pointer.CursorSouthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 18: // pointer.CursorNorthSouthResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
@@ -329,10 +341,10 @@ void gio_setCursor(NSUInteger curID) {
|
||||
[NSCursor.resizeDownCursor set];
|
||||
break;
|
||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
@@ -374,22 +386,32 @@ CFTypeRef gio_createView(void) {
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||
view.wantsLayer = YES;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
@autoreleasepool {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
||||
gio_onAppHide();
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onAppShow();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_main() {
|
||||
|
||||
+29
-12
@@ -9,16 +9,10 @@ import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"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 {
|
||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||
Display unsafe.Pointer
|
||||
@@ -49,7 +43,7 @@ type windowDriver func(*callbacks, []Option) error
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver windowDriver
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||
if d == nil {
|
||||
@@ -57,16 +51,39 @@ func newWindow(window *callbacks, options []Option) error {
|
||||
}
|
||||
err := d(window, options)
|
||||
if err == nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst != nil {
|
||||
return errFirst
|
||||
window.SetDriver(&dummyDriver{
|
||||
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.
|
||||
|
||||
+165
-106
@@ -15,6 +15,7 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -25,10 +26,12 @@ import (
|
||||
"gioui.org/app/internal/xkb"
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/fling"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
@@ -97,7 +100,9 @@ type wlDisplay struct {
|
||||
read, write int
|
||||
}
|
||||
|
||||
repeat repeatState
|
||||
repeat repeatState
|
||||
poller poller
|
||||
readClipClose chan struct{}
|
||||
}
|
||||
|
||||
type wlSeat struct {
|
||||
@@ -137,7 +142,7 @@ type repeatState struct {
|
||||
delay time.Duration
|
||||
|
||||
key uint32
|
||||
win *callbacks
|
||||
win *window
|
||||
stopC chan struct{}
|
||||
|
||||
start time.Duration
|
||||
@@ -194,12 +199,10 @@ type window struct {
|
||||
dir f32.Point
|
||||
}
|
||||
|
||||
stage Stage
|
||||
dead bool
|
||||
configured bool
|
||||
lastFrameCallback *C.struct_wl_callback
|
||||
|
||||
animating bool
|
||||
redraw bool
|
||||
// The most recent configure serial waiting to be ack'ed.
|
||||
serial C.uint32_t
|
||||
scale int
|
||||
@@ -212,6 +215,10 @@ type window struct {
|
||||
clipReads chan transfer.DataEvent
|
||||
|
||||
wakeups chan struct{}
|
||||
|
||||
// invMu avoids the race between the destruction of disp and
|
||||
// Invalidate waking it up.
|
||||
invMu sync.Mutex
|
||||
}
|
||||
|
||||
type poller struct {
|
||||
@@ -260,25 +267,17 @@ func newWLWindow(callbacks *callbacks, options []Option) error {
|
||||
return err
|
||||
}
|
||||
w.w = callbacks
|
||||
go func() {
|
||||
defer d.destroy()
|
||||
defer w.destroy()
|
||||
w.w.SetDriver(w)
|
||||
|
||||
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.Configure(options)
|
||||
C.wl_surface_commit(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})
|
||||
}()
|
||||
w.ProcessEvent(WaylandViewEvent{
|
||||
Display: unsafe.Pointer(w.display()),
|
||||
Surface: unsafe.Pointer(w.surf),
|
||||
})
|
||||
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) {
|
||||
w := callbackLoad(data).(*window)
|
||||
w.serial = serial
|
||||
w.redraw = true
|
||||
C.xdg_surface_ack_configure(wmSurf, serial)
|
||||
w.setStage(StageRunning)
|
||||
w.configured = true
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export gio_onToplevelClose
|
||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||
w := callbackLoad(data).(*window)
|
||||
w.dead = true
|
||||
w.close(nil)
|
||||
}
|
||||
|
||||
//export gio_onToplevelConfigure
|
||||
@@ -586,8 +585,8 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
|
||||
} else {
|
||||
w.size.Y += int(w.config.decoHeight)
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.redraw = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +644,7 @@ func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *
|
||||
if w.config.Mode == Minimized {
|
||||
// Minimized window got brought back up: it is no longer so.
|
||||
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),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Press,
|
||||
Source: pointer.Touch,
|
||||
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
|
||||
w := s.touchFoci[id]
|
||||
delete(s.touchFoci, id)
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Release,
|
||||
Source: pointer.Touch,
|
||||
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),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Position: w.lastTouch,
|
||||
Source: pointer.Touch,
|
||||
@@ -843,7 +842,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
for id, w := range s.touchFoci {
|
||||
delete(s.touchFoci, id)
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
@@ -869,7 +868,7 @@ func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.ui
|
||||
s.serial = serial
|
||||
if w.inCompositor {
|
||||
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.resetFling()
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -1018,8 +1017,11 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
if w.disp.readClipClose != nil {
|
||||
return
|
||||
}
|
||||
w.disp.readClipClose = make(chan struct{})
|
||||
r, err := w.disp.readClipboard()
|
||||
// Send empty responses on unavailable clipboards or errors.
|
||||
if r == nil || err != nil {
|
||||
return
|
||||
}
|
||||
@@ -1027,13 +1029,17 @@ func (w *window) ReadClipboard() {
|
||||
go func() {
|
||||
defer r.Close()
|
||||
data, _ := io.ReadAll(r)
|
||||
w.clipReads <- transfer.DataEvent{
|
||||
e := transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
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.setWindowConstraints()
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.redraw = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) setWindowConstraints() {
|
||||
@@ -1134,7 +1139,7 @@ func (w *window) Perform(actions system.Action) {
|
||||
walkActions(actions, func(action system.Action) {
|
||||
switch action {
|
||||
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)
|
||||
s.keyboardFocus = w
|
||||
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
|
||||
@@ -1226,7 +1232,8 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
||||
s.serial = serial
|
||||
s.disp.repeat.Stop(0)
|
||||
w := s.keyboardFocus
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//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.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
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.stopC = stopC
|
||||
r.key = keyCode
|
||||
r.win = w.w
|
||||
r.win = w
|
||||
rate, delay := r.rate, r.delay
|
||||
go func() {
|
||||
timer := time.NewTimer(delay)
|
||||
@@ -1337,9 +1344,9 @@ func (r *repeatState) Repeat(d *wlDisplay) {
|
||||
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
||||
if ee, ok := e.(key.EditEvent); ok {
|
||||
// There's no support for IME yet.
|
||||
r.win.EditorInsert(ee.Text)
|
||||
r.win.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
r.win.Event(e)
|
||||
r.win.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
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)
|
||||
if w.lastFrameCallback == callback {
|
||||
w.lastFrameCallback = nil
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) loop() error {
|
||||
var p poller
|
||||
for {
|
||||
if err := w.disp.dispatch(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case e := <-w.clipReads:
|
||||
w.w.Event(e)
|
||||
case <-w.wakeups:
|
||||
w.w.Event(wakeupEvent{})
|
||||
default:
|
||||
}
|
||||
if w.dead {
|
||||
break
|
||||
}
|
||||
w.draw()
|
||||
func (w *window) close(err error) {
|
||||
w.ProcessEvent(WaylandViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
}
|
||||
|
||||
func (w *window) dispatch() {
|
||||
if w.disp == nil {
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
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
|
||||
@@ -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)
|
||||
// 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(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]
|
||||
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
||||
if err != syscall.EAGAIN {
|
||||
@@ -1406,11 +1469,25 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
||||
dispFd.Events |= syscall.POLLOUT
|
||||
}
|
||||
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)
|
||||
}
|
||||
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.
|
||||
for {
|
||||
_, err := syscall.Read(d.notify.read, p.buf[:])
|
||||
_, err := syscall.Read(d.notify.read, d.poller.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
@@ -1418,29 +1495,15 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
w.disp.wakeup()
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Wakeup wakes up the event loop through the notification pipe.
|
||||
@@ -1576,7 +1639,7 @@ func (w *window) flushScroll() {
|
||||
if total == (f32.Point{}) {
|
||||
return
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
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),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Position: w.lastPos,
|
||||
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 {
|
||||
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
|
||||
north := y <= border
|
||||
south := y >= size.Y-border
|
||||
@@ -1674,13 +1738,10 @@ func (w *window) updateOutputs() {
|
||||
if found && scale != w.scale {
|
||||
w.scale = scale
|
||||
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
||||
w.redraw = true
|
||||
w.draw(true)
|
||||
}
|
||||
if !found {
|
||||
w.setStage(StagePaused)
|
||||
} else {
|
||||
w.setStage(StageRunning)
|
||||
w.redraw = true
|
||||
if found {
|
||||
w.draw(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()
|
||||
size, cfg := w.getConfig()
|
||||
if cfg == (unit.Metric{}) {
|
||||
@@ -1700,11 +1764,9 @@ func (w *window) draw() {
|
||||
}
|
||||
if size != w.config.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()
|
||||
sync := w.redraw
|
||||
w.redraw = false
|
||||
// Draw animation only when not waiting for frame callback.
|
||||
redrawAnim := anim && w.lastFrameCallback == nil
|
||||
if !redrawAnim && !sync {
|
||||
@@ -1715,7 +1777,7 @@ func (w *window) draw() {
|
||||
// Use the surface as listener data for gio_onFrameDone.
|
||||
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
||||
}
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
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 {
|
||||
return w.disp.disp
|
||||
}
|
||||
@@ -1824,6 +1878,10 @@ func newWLDisplay() (*wlDisplay, error) {
|
||||
}
|
||||
|
||||
func (d *wlDisplay) destroy() {
|
||||
if d.readClipClose != nil {
|
||||
close(d.readClipClose)
|
||||
d.readClipClose = nil
|
||||
}
|
||||
if d.notify.write != 0 {
|
||||
syscall.Close(d.notify.write)
|
||||
d.notify.write = 0
|
||||
@@ -1865,6 +1923,7 @@ func (d *wlDisplay) destroy() {
|
||||
if d.disp != nil {
|
||||
C.wl_display_disconnect(d.disp)
|
||||
callbackDelete(unsafe.Pointer(d.disp))
|
||||
d.disp = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+82
-64
@@ -19,17 +19,19 @@ import (
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/app/internal/windows"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
gowindows "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type Win32ViewEvent struct {
|
||||
HWND uintptr
|
||||
}
|
||||
|
||||
@@ -37,7 +39,6 @@ type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w *callbacks
|
||||
stage Stage
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
// cursorIn tracks whether the cursor was inside the window according
|
||||
@@ -49,10 +50,13 @@ type window struct {
|
||||
placement *windows.WindowPlacement
|
||||
|
||||
animating bool
|
||||
focused bool
|
||||
|
||||
borderSize image.Point
|
||||
config Config
|
||||
loop *eventLoop
|
||||
|
||||
// invMu avoids the race between destroying the window and Invalidate.
|
||||
invMu sync.Mutex
|
||||
}
|
||||
|
||||
const _WM_WAKEUP = windows.WM_USER + iota
|
||||
@@ -85,36 +89,38 @@ func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
cerr := make(chan error)
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||
// then thread-specific messages such as WM_QUIT are ignored.
|
||||
// Instead lock the thread so window messages arrive through
|
||||
// unfiltered GetMessage calls.
|
||||
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 {
|
||||
cerr <- err
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
cerr <- nil
|
||||
winMap.Store(w.hwnd, w)
|
||||
defer winMap.Delete(w.hwnd)
|
||||
w.w = window
|
||||
w.w.SetDriver(w)
|
||||
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
|
||||
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
||||
w.Configure(options)
|
||||
windows.SetForegroundWindow(w.hwnd)
|
||||
windows.SetFocus(w.hwnd)
|
||||
// Since the window class for the cursor is null,
|
||||
// set it here to show the cursor.
|
||||
w.SetCursor(pointer.CursorDefault)
|
||||
if err := w.loop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.runLoop()
|
||||
}()
|
||||
return <-cerr
|
||||
<-done
|
||||
}
|
||||
|
||||
// initResources initializes the resources global.
|
||||
@@ -149,13 +155,13 @@ func initResources() error {
|
||||
|
||||
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
||||
|
||||
func createNativeWindow() (*window, error) {
|
||||
func (w *window) init() error {
|
||||
var resErr error
|
||||
resources.once.Do(func() {
|
||||
resErr = initResources()
|
||||
})
|
||||
if resErr != nil {
|
||||
return nil, resErr
|
||||
return resErr
|
||||
}
|
||||
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
||||
|
||||
@@ -171,16 +177,15 @@ func createNativeWindow() (*window, error) {
|
||||
resources.handle,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &window{
|
||||
hwnd: hwnd,
|
||||
return err
|
||||
}
|
||||
w.hdc, err = windows.GetDC(hwnd)
|
||||
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.
|
||||
@@ -197,7 +202,7 @@ func (w *window) update() {
|
||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||
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 {
|
||||
@@ -238,7 +243,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
e.State = key.Release
|
||||
}
|
||||
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
|
||||
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
|
||||
@@ -259,23 +264,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
case windows.WM_MBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||
case windows.WM_CANCELMODE:
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
})
|
||||
case windows.WM_SETFOCUS:
|
||||
w.focused = true
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case windows.WM_KILLFOCUS:
|
||||
w.focused = false
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case windows.WM_NCACTIVATE:
|
||||
if w.stage >= StageInactive {
|
||||
if wParam == windows.TRUE {
|
||||
w.setStage(StageRunning)
|
||||
} else {
|
||||
w.setStage(StageInactive)
|
||||
}
|
||||
}
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case windows.WM_NCHITTEST:
|
||||
if w.config.Decorated {
|
||||
// Let the system handle it.
|
||||
@@ -288,7 +285,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
case windows.WM_MOUSEMOVE:
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
@@ -301,14 +298,16 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
case windows.WM_MOUSEHWHEEL:
|
||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||
case windows.WM_DESTROY:
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(DestroyEvent{})
|
||||
w.ProcessEvent(Win32ViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
if w.hdc != 0 {
|
||||
windows.ReleaseDC(w.hdc)
|
||||
w.hdc = 0
|
||||
}
|
||||
w.invMu.Lock()
|
||||
// The system destroys the HWND for us.
|
||||
w.hwnd = 0
|
||||
w.invMu.Unlock()
|
||||
windows.PostQuitMessage(0)
|
||||
case windows.WM_NCCALCSIZE:
|
||||
if w.config.Decorated {
|
||||
@@ -328,7 +327,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
// Adjust window position to avoid the extra padding in maximized
|
||||
// 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.
|
||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
|
||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
||||
mi := windows.GetMonitorInfo(w.hwnd)
|
||||
szp.Rgrc[0] = mi.WorkArea
|
||||
return 0
|
||||
@@ -339,18 +338,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
switch wParam {
|
||||
case windows.SIZE_MINIMIZED:
|
||||
w.config.Mode = Minimized
|
||||
w.setStage(StagePaused)
|
||||
case windows.SIZE_MAXIMIZED:
|
||||
w.config.Mode = Maximized
|
||||
w.setStage(StageRunning)
|
||||
case windows.SIZE_RESTORED:
|
||||
if w.config.Mode != Fullscreen {
|
||||
w.config.Mode = Windowed
|
||||
}
|
||||
w.setStage(StageRunning)
|
||||
}
|
||||
case windows.WM_GETMINMAXINFO:
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||
var bw, bh int32
|
||||
if w.config.Decorated {
|
||||
r := windows.GetWindowRect(w.hwnd)
|
||||
@@ -378,7 +374,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
return windows.TRUE
|
||||
}
|
||||
case _WM_WAKEUP:
|
||||
w.w.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
case windows.WM_IME_STARTCOMPOSITION:
|
||||
imc := windows.ImmGetContext(w.hwnd)
|
||||
if imc == 0 {
|
||||
@@ -498,7 +495,7 @@ func (w *window) hitTest(x, y int) uintptr {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -518,7 +515,7 @@ func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr,
|
||||
}
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
@@ -553,7 +550,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
||||
sp.Y = -dist
|
||||
}
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
@@ -565,7 +562,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
||||
}
|
||||
|
||||
// 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)
|
||||
loop:
|
||||
for {
|
||||
@@ -576,7 +573,7 @@ loop:
|
||||
}
|
||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||
case -1:
|
||||
return errors.New("GetMessage failed")
|
||||
panic(errors.New("GetMessage failed"))
|
||||
case 0:
|
||||
// WM_QUIT received.
|
||||
break loop
|
||||
@@ -584,7 +581,6 @@ loop:
|
||||
windows.TranslateMessage(msg)
|
||||
windows.DispatchMessage(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {
|
||||
@@ -602,16 +598,37 @@ func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) setStage(s Stage) {
|
||||
if s != w.stage {
|
||||
w.stage = s
|
||||
w.w.Event(StageEvent{Stage: s})
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func (w *window) wakeup() {
|
||||
w.invMu.Lock()
|
||||
defer w.invMu.Unlock()
|
||||
if w.hwnd == 0 {
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
return
|
||||
}
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +638,7 @@ func (w *window) draw(sync bool) {
|
||||
}
|
||||
dpi := windows.GetWindowDPI(w.hwnd)
|
||||
cfg := configForDPI(dpi)
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
@@ -668,7 +685,7 @@ func (w *window) readClipboard() error {
|
||||
}
|
||||
defer windows.GlobalUnlock(mem)
|
||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||
w.w.Event(transfer.DataEvent{
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -974,4 +991,5 @@ func configForDPI(dpi int) unit.Metric {
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (Win32ViewEvent) implementsViewEvent() {}
|
||||
func (Win32ViewEvent) ImplementsEvent() {}
|
||||
|
||||
+110
-81
@@ -38,10 +38,12 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
syscall "golang.org/x/sys/unix"
|
||||
@@ -93,12 +95,10 @@ type x11Window struct {
|
||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||
wmStateMaximizedVert C.Atom
|
||||
}
|
||||
stage Stage
|
||||
metric unit.Metric
|
||||
notify struct {
|
||||
read, write int
|
||||
}
|
||||
dead bool
|
||||
|
||||
animating bool
|
||||
|
||||
@@ -111,6 +111,11 @@ type x11Window struct {
|
||||
config Config
|
||||
|
||||
wakeups chan struct{}
|
||||
handler x11EventHandler
|
||||
buf [100]byte
|
||||
|
||||
// invMy avoids the race between destroy and Invalidate.
|
||||
invMu sync.Mutex
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -234,7 +239,7 @@ func (w *x11Window) Configure(options []Option) {
|
||||
if cnf.Decorated != prev.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) {
|
||||
@@ -377,11 +382,47 @@ func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
||||
|
||||
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 {
|
||||
case w.wakeups <- struct{}{}:
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
func (w *x11Window) setStage(s Stage) {
|
||||
if s == w.stage {
|
||||
func (w *x11Window) dispatch() {
|
||||
if w.x == nil {
|
||||
// Only Invalidate can wake us up.
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
w.stage = s
|
||||
w.w.Event(StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *x11Window) loop() {
|
||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Invalidate()
|
||||
default:
|
||||
}
|
||||
|
||||
xfd := C.XConnectionNumber(w.x)
|
||||
|
||||
// Poll for events and notifications.
|
||||
@@ -414,64 +459,52 @@ func (w *x11Window) loop() {
|
||||
}
|
||||
xEvents := &pollfds[0].Revents
|
||||
// Plenty of room for a backlog of notifications.
|
||||
buf := make([]byte, 100)
|
||||
|
||||
loop:
|
||||
for !w.dead {
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
if syn = h.handleEvents(); !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
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:
|
||||
syn = h.handleEvents()
|
||||
if w.dead {
|
||||
break loop
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
break loop
|
||||
}
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
if syn = w.handler.handleEvents(); !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
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:
|
||||
syn = w.handler.handleEvents()
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, buf)
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
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 err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
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() {
|
||||
w.invMu.Lock()
|
||||
defer w.invMu.Unlock()
|
||||
if w.notify.write != 0 {
|
||||
syscall.Close(w.notify.write)
|
||||
w.notify.write = 0
|
||||
@@ -486,6 +519,7 @@ func (w *x11Window) destroy() {
|
||||
}
|
||||
C.XDestroyWindow(w.x, w.xw)
|
||||
C.XCloseDisplay(w.x)
|
||||
w.x = nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
case C.ButtonPress, C.ButtonRelease:
|
||||
@@ -605,10 +639,10 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
w.pointerBtns &^= btn
|
||||
}
|
||||
ev.Buttons = w.pointerBtns
|
||||
w.w.Event(ev)
|
||||
w.ProcessEvent(ev)
|
||||
case C.MotionNotify:
|
||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -623,14 +657,16 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
// redraw only on the last expose event
|
||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||
case C.FocusIn:
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
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
|
||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||
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
|
||||
case C.SelectionNotify:
|
||||
@@ -652,7 +688,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
break
|
||||
}
|
||||
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",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(str))
|
||||
@@ -711,7 +747,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||
case C.long(w.atoms.evDelWindow):
|
||||
w.dead = true
|
||||
w.shutdown(nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -793,8 +829,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
wakeups: make(chan struct{}, 1),
|
||||
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.write = pipe[1]
|
||||
w.w.SetDriver(w)
|
||||
|
||||
if err := w.updateXkbKeymap(); err != nil {
|
||||
w.destroy()
|
||||
@@ -830,19 +868,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
// extensions
|
||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||
|
||||
go func() {
|
||||
w.w.SetDriver(w)
|
||||
|
||||
// make the window visible on the screen
|
||||
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()
|
||||
}()
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
w.Configure(options)
|
||||
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,40 +10,4 @@ type DestroyEvent struct {
|
||||
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() {}
|
||||
|
||||
+279
-515
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user