mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 00:16:15 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3879921b80 | |||
| b1f84da679 | |||
| 43024fcca2 | |||
| 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 InputMethodManager imm;
|
||||||
private final float scrollXScale;
|
private final float scrollXScale;
|
||||||
private final float scrollYScale;
|
private final float scrollYScale;
|
||||||
|
private final AccessibilityManager accessManager;
|
||||||
private int keyboardHint;
|
private int keyboardHint;
|
||||||
private AccessibilityManager accessManager;
|
|
||||||
|
|
||||||
private long nhandle;
|
private long nhandle;
|
||||||
|
|
||||||
|
|||||||
+7
-12
@@ -56,6 +56,13 @@ type FrameEvent struct {
|
|||||||
Source input.Source
|
Source input.Source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewEvent provides handles to the underlying window objects for the
|
||||||
|
// current display protocol.
|
||||||
|
type ViewEvent interface {
|
||||||
|
implementsViewEvent()
|
||||||
|
ImplementsEvent()
|
||||||
|
}
|
||||||
|
|
||||||
// Insets is the space taken up by
|
// Insets is the space taken up by
|
||||||
// system decoration such as translucent
|
// system decoration such as translucent
|
||||||
// system bars and software keyboards.
|
// system bars and software keyboards.
|
||||||
@@ -113,18 +120,6 @@ func DataDir() (string, error) {
|
|||||||
return dataDir()
|
return dataDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main must be called last from the program main function.
|
|
||||||
// On most platforms Main blocks forever, for Android and
|
|
||||||
// iOS it returns immediately to give control of the main
|
|
||||||
// thread back to the system.
|
|
||||||
//
|
|
||||||
// Calling Main is necessary because some operating systems
|
|
||||||
// require control of the main thread of the program for
|
|
||||||
// running windows.
|
|
||||||
func Main() {
|
|
||||||
osMain()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (FrameEvent) ImplementsEvent() {}
|
func (FrameEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+13
-28
@@ -8,18 +8,19 @@ See https://gioui.org for instructions to set up and run Gio programs.
|
|||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
|
|
||||||
Create a new [Window] by calling [NewWindow]. On mobile platforms or when Gio
|
A Window is run by calling its Event method in a loop. The first time a
|
||||||
is embedded in another project, NewWindow merely connects with a previously
|
method on Window is called, a new GUI window is created and shown. On mobile
|
||||||
created window.
|
platforms or when Gio is embedded in another project, Window merely connects
|
||||||
|
with a previously created GUI window.
|
||||||
|
|
||||||
A Window is run by calling its NextEvent method in a loop. The most important event is
|
The most important event is [FrameEvent] that prompts an update of the window
|
||||||
[FrameEvent] that prompts an update of the window contents.
|
contents.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
w := app.NewWindow()
|
w := new(app.Window)
|
||||||
for {
|
for {
|
||||||
e := w.NextEvent()
|
e := w.Event()
|
||||||
if e, ok := e.(app.FrameEvent); ok {
|
if e, ok := e.(app.FrameEvent); ok {
|
||||||
ops.Reset()
|
ops.Reset()
|
||||||
// Add operations to ops.
|
// Add operations to ops.
|
||||||
@@ -32,28 +33,12 @@ For example:
|
|||||||
A program must keep receiving events from the event channel until
|
A program must keep receiving events from the event channel until
|
||||||
[DestroyEvent] is received.
|
[DestroyEvent] is received.
|
||||||
|
|
||||||
# Main
|
# Main Thread
|
||||||
|
|
||||||
The Main function must be called from a program's main function, to hand over
|
Some GUI platform need access to the main thread of the program. To avoid a
|
||||||
control of the main thread to operating systems that need it.
|
deadlock on such platforms, at least one Window must have its Event method
|
||||||
|
called by the main goroutine. It doesn't have to be any particular Window;
|
||||||
Because Main is also blocking on some platforms, the event loop of a Window must run in a goroutine.
|
even a destroyed Window suffices.
|
||||||
|
|
||||||
For example, to display a blank but otherwise functional window:
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "gioui.org/app"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
go func() {
|
|
||||||
w := app.NewWindow()
|
|
||||||
for {
|
|
||||||
w.NextEvent()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
app.Main()
|
|
||||||
}
|
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -69,7 +69,17 @@ func (c *wlContext) Refresh() error {
|
|||||||
}
|
}
|
||||||
c.eglWin = eglWin
|
c.eglWin = eglWin
|
||||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||||
return c.Context.CreateSurface(eglSurf, width, height)
|
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.Context.MakeCurrent(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Context.ReleaseCurrent()
|
||||||
|
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||||
|
// wait for callbacks that may never arrive.
|
||||||
|
c.Context.EnableVSync(false)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *wlContext) Lock() error {
|
func (c *wlContext) Lock() error {
|
||||||
|
|||||||
+1
-1
@@ -46,8 +46,8 @@ func (c *x11Context) Refresh() error {
|
|||||||
if err := c.Context.MakeCurrent(); err != nil {
|
if err := c.Context.MakeCurrent(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer c.Context.ReleaseCurrent()
|
||||||
c.Context.EnableVSync(true)
|
c.Context.EnableVSync(true)
|
||||||
c.Context.ReleaseCurrent()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+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
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -llog
|
#cgo LDFLAGS: -llog
|
||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||||
const logLineLimit = 1024
|
const logLineLimit = 1024
|
||||||
|
|
||||||
var logTag = C.CString(appID)
|
var logTag = C.CString(ID)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Android's logcat already includes timestamps.
|
// Android's logcat already includes timestamps.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
//go:build darwin && ios
|
//go:build darwin && ios
|
||||||
// +build darwin,ios
|
// +build darwin,ios
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
package log
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
+2
-1
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
|||||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||||
[cmdBuffer presentDrawable:drawable];
|
|
||||||
[cmdBuffer commit];
|
[cmdBuffer commit];
|
||||||
|
[cmdBuffer waitUntilScheduled];
|
||||||
|
[drawable present];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
|
|||||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
UIView *view = (__bridge UIView *)viewRef;
|
UIView *view = (__bridge UIView *)viewRef;
|
||||||
return CFBridgingRetain(view.layer);
|
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = YES;
|
||||||
|
return CFBridgingRetain(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -14,7 +14,11 @@ package app
|
|||||||
|
|
||||||
CALayer *gio_layerFactory(void) {
|
CALayer *gio_layerFactory(void) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
return [CAMetalLayer layer];
|
CAMetalLayer *l = [CAMetalLayer layer];
|
||||||
|
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||||
|
l.needsDisplayOnBoundsChange = YES;
|
||||||
|
l.presentsWithTransaction = YES;
|
||||||
|
return l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
@@ -43,6 +45,8 @@ type Config struct {
|
|||||||
CustomRenderer bool
|
CustomRenderer bool
|
||||||
// Decorated reports whether window decorations are provided automatically.
|
// Decorated reports whether window decorations are provided automatically.
|
||||||
Decorated bool
|
Decorated bool
|
||||||
|
// Focused reports whether has the keyboard focus.
|
||||||
|
Focused bool
|
||||||
// decoHeight is the height of the fallback decoration for platforms such
|
// decoHeight is the height of the fallback decoration for platforms such
|
||||||
// as Wayland that may need fallback client-side decorations.
|
// as Wayland that may need fallback client-side decorations.
|
||||||
decoHeight unit.Dp
|
decoHeight unit.Dp
|
||||||
@@ -131,6 +135,28 @@ func (o Orientation) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventLoop implements the functionality required for drivers where
|
||||||
|
// window event loops must run on a separate thread.
|
||||||
|
type eventLoop struct {
|
||||||
|
win *callbacks
|
||||||
|
// wakeup is the callback to wake up the event loop.
|
||||||
|
wakeup func()
|
||||||
|
// driverFuncs is a channel of functions to run the next
|
||||||
|
// time the window loop waits for events.
|
||||||
|
driverFuncs chan func()
|
||||||
|
// invalidates is notified when an invalidate is requested by the client.
|
||||||
|
invalidates chan struct{}
|
||||||
|
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||||
|
immediateInvalidates chan struct{}
|
||||||
|
// events is where the platform backend delivers events bound for the
|
||||||
|
// user program.
|
||||||
|
events chan event.Event
|
||||||
|
frames chan *op.Ops
|
||||||
|
frameAck chan struct{}
|
||||||
|
// delivering avoids re-entrant event delivery.
|
||||||
|
delivering bool
|
||||||
|
}
|
||||||
|
|
||||||
type frameEvent struct {
|
type frameEvent struct {
|
||||||
FrameEvent
|
FrameEvent
|
||||||
|
|
||||||
@@ -147,9 +173,19 @@ type context interface {
|
|||||||
Unlock()
|
Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver is the interface for the platform implementation
|
// basicDriver is the subset of [driver] that may be called even after
|
||||||
|
// a window is destroyed.
|
||||||
|
type basicDriver interface {
|
||||||
|
// Event blocks until an even is available and returns it.
|
||||||
|
Event() event.Event
|
||||||
|
// Invalidate requests a FrameEvent.
|
||||||
|
Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// driver is the interface for the platform implementation
|
||||||
// of a window.
|
// of a window.
|
||||||
type driver interface {
|
type driver interface {
|
||||||
|
basicDriver
|
||||||
// SetAnimating sets the animation flag. When the window is animating,
|
// SetAnimating sets the animation flag. When the window is animating,
|
||||||
// FrameEvents are delivered as fast as the display can handle them.
|
// FrameEvents are delivered as fast as the display can handle them.
|
||||||
SetAnimating(anim bool)
|
SetAnimating(anim bool)
|
||||||
@@ -166,17 +202,23 @@ type driver interface {
|
|||||||
// SetCursor updates the current cursor to name.
|
// SetCursor updates the current cursor to name.
|
||||||
SetCursor(cursor pointer.Cursor)
|
SetCursor(cursor pointer.Cursor)
|
||||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
||||||
Wakeup()
|
// Wakeup()
|
||||||
// Perform actions on the window.
|
// Perform actions on the window.
|
||||||
Perform(system.Action)
|
Perform(system.Action)
|
||||||
// EditorStateChanged notifies the driver that the editor state changed.
|
// EditorStateChanged notifies the driver that the editor state changed.
|
||||||
EditorStateChanged(old, new editorState)
|
EditorStateChanged(old, new editorState)
|
||||||
|
// Run a function on the window thread.
|
||||||
|
Run(f func())
|
||||||
|
// Frame receives a frame.
|
||||||
|
Frame(frame *op.Ops)
|
||||||
|
// ProcessEvent processes an event.
|
||||||
|
ProcessEvent(e event.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowRendezvous struct {
|
type windowRendezvous struct {
|
||||||
in chan windowAndConfig
|
in chan windowAndConfig
|
||||||
out chan windowAndConfig
|
out chan windowAndConfig
|
||||||
errs chan error
|
windows chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type windowAndConfig struct {
|
type windowAndConfig struct {
|
||||||
@@ -186,32 +228,137 @@ type windowAndConfig struct {
|
|||||||
|
|
||||||
func newWindowRendezvous() *windowRendezvous {
|
func newWindowRendezvous() *windowRendezvous {
|
||||||
wr := &windowRendezvous{
|
wr := &windowRendezvous{
|
||||||
in: make(chan windowAndConfig),
|
in: make(chan windowAndConfig),
|
||||||
out: make(chan windowAndConfig),
|
out: make(chan windowAndConfig),
|
||||||
errs: make(chan error),
|
windows: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
var main windowAndConfig
|
in := wr.in
|
||||||
|
var window windowAndConfig
|
||||||
var out chan windowAndConfig
|
var out chan windowAndConfig
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case w := <-wr.in:
|
case w := <-in:
|
||||||
var err error
|
window = w
|
||||||
if main.window != nil {
|
|
||||||
err = errors.New("multiple windows are not supported")
|
|
||||||
}
|
|
||||||
wr.errs <- err
|
|
||||||
main = w
|
|
||||||
out = wr.out
|
out = wr.out
|
||||||
case out <- main:
|
case out <- window:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return wr
|
return wr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wakeupEvent) ImplementsEvent() {}
|
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||||
func (ConfigEvent) ImplementsEvent() {}
|
return &eventLoop{
|
||||||
|
win: w,
|
||||||
|
wakeup: wakeup,
|
||||||
|
events: make(chan event.Event),
|
||||||
|
invalidates: make(chan struct{}, 1),
|
||||||
|
immediateInvalidates: make(chan struct{}),
|
||||||
|
frames: make(chan *op.Ops),
|
||||||
|
frameAck: make(chan struct{}),
|
||||||
|
driverFuncs: make(chan func(), 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame receives a frame and waits for its processing. It is called by
|
||||||
|
// the client goroutine.
|
||||||
|
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||||
|
e.frames <- frame
|
||||||
|
<-e.frameAck
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event returns the next available event. It is called by the client
|
||||||
|
// goroutine.
|
||||||
|
func (e *eventLoop) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt := <-e.events
|
||||||
|
// Receiving a flushEvent indicates to the platform backend that
|
||||||
|
// all previous events have been processed by the user program.
|
||||||
|
if _, ok := evt.(flushEvent); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate requests invalidation of the window. It is called by the client
|
||||||
|
// goroutine.
|
||||||
|
func (e *eventLoop) Invalidate() {
|
||||||
|
select {
|
||||||
|
case e.immediateInvalidates <- struct{}{}:
|
||||||
|
// The event loop was waiting, no need for a wakeup.
|
||||||
|
case e.invalidates <- struct{}{}:
|
||||||
|
// The event loop is sleeping, wake it up.
|
||||||
|
e.wakeup()
|
||||||
|
default:
|
||||||
|
// A redraw is pending.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run f in the window loop thread. It is called by the client goroutine.
|
||||||
|
func (e *eventLoop) Run(f func()) {
|
||||||
|
e.driverFuncs <- f
|
||||||
|
e.wakeup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushEvents delivers pending events to the client.
|
||||||
|
func (e *eventLoop) FlushEvents() {
|
||||||
|
if e.delivering {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.delivering = true
|
||||||
|
defer func() { e.delivering = false }()
|
||||||
|
for {
|
||||||
|
evt, ok := e.win.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e.deliverEvent(evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||||
|
var frames <-chan *op.Ops
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-e.driverFuncs:
|
||||||
|
f()
|
||||||
|
case frame := <-frames:
|
||||||
|
// The client called FrameEvent.Frame.
|
||||||
|
frames = nil
|
||||||
|
e.win.ProcessFrame(frame, e.frameAck)
|
||||||
|
case e.events <- evt:
|
||||||
|
switch evt.(type) {
|
||||||
|
case flushEvent, DestroyEvent:
|
||||||
|
// DestroyEvents are not flushed.
|
||||||
|
return
|
||||||
|
case FrameEvent:
|
||||||
|
frames = e.frames
|
||||||
|
}
|
||||||
|
evt = theFlushEvent
|
||||||
|
case <-e.invalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
case <-e.immediateInvalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventLoop) Wakeup() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case f := <-e.driverFuncs:
|
||||||
|
f()
|
||||||
|
case <-e.invalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
case <-e.immediateInvalidates:
|
||||||
|
e.win.Invalidate()
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func walkActions(actions system.Action, do func(system.Action)) {
|
func walkActions(actions system.Action, do func(system.Action)) {
|
||||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||||
@@ -221,3 +368,6 @@ func walkActions(actions system.Action, do func(system.Action)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wakeupEvent) ImplementsEvent() {}
|
||||||
|
func (ConfigEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+111
-80
@@ -137,8 +137,10 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/input"
|
"gioui.org/io/input"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
@@ -150,6 +152,7 @@ import (
|
|||||||
|
|
||||||
type window struct {
|
type window struct {
|
||||||
callbacks *callbacks
|
callbacks *callbacks
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
view C.jobject
|
view C.jobject
|
||||||
handle cgo.Handle
|
handle cgo.Handle
|
||||||
@@ -158,12 +161,13 @@ type window struct {
|
|||||||
fontScale float32
|
fontScale float32
|
||||||
insets pixelInsets
|
insets pixelInsets
|
||||||
|
|
||||||
stage Stage
|
visible bool
|
||||||
started bool
|
started bool
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
win *C.ANativeWindow
|
win *C.ANativeWindow
|
||||||
config Config
|
config Config
|
||||||
|
inputHint key.InputHint
|
||||||
|
|
||||||
semantic struct {
|
semantic struct {
|
||||||
hoverID input.SemanticID
|
hoverID input.SemanticID
|
||||||
@@ -201,9 +205,9 @@ type pixelInsets struct {
|
|||||||
top, bottom, left, right int
|
top, bottom, left, right int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewEvent is sent whenever the Window's underlying Android view
|
// AndroidViewEvent is sent whenever the Window's underlying Android view
|
||||||
// changes.
|
// changes.
|
||||||
type ViewEvent struct {
|
type AndroidViewEvent struct {
|
||||||
// View is a JNI global reference to the android.view.View
|
// View is a JNI global reference to the android.view.View
|
||||||
// instance backing the Window. The reference is valid until
|
// instance backing the Window. The reference is valid until
|
||||||
// the next ViewEvent is received.
|
// the next ViewEvent is received.
|
||||||
@@ -487,24 +491,30 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
|||||||
})
|
})
|
||||||
view = C.jni_NewGlobalRef(env, view)
|
view = C.jni_NewGlobalRef(env, view)
|
||||||
wopts := <-mainWindow.out
|
wopts := <-mainWindow.out
|
||||||
|
var cnf Config
|
||||||
w, ok := windows[wopts.window]
|
w, ok := windows[wopts.window]
|
||||||
if !ok {
|
if !ok {
|
||||||
w = &window{
|
w = &window{
|
||||||
callbacks: wopts.window,
|
callbacks: wopts.window,
|
||||||
}
|
}
|
||||||
|
w.loop = newEventLoop(w.callbacks, w.wakeup)
|
||||||
|
w.callbacks.SetDriver(w)
|
||||||
|
cnf.apply(unit.Metric{}, wopts.options)
|
||||||
windows[wopts.window] = w
|
windows[wopts.window] = w
|
||||||
|
} else {
|
||||||
|
cnf = w.config
|
||||||
}
|
}
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
if w.view != 0 {
|
if w.view != 0 {
|
||||||
w.detach(env)
|
w.detach(env)
|
||||||
}
|
}
|
||||||
w.view = view
|
w.view = view
|
||||||
|
w.visible = false
|
||||||
w.handle = cgo.NewHandle(w)
|
w.handle = cgo.NewHandle(w)
|
||||||
w.callbacks.SetDriver(w)
|
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
w.Configure(wopts.options)
|
w.setConfig(env, cnf)
|
||||||
w.SetInputHint(key.HintAny)
|
w.SetInputHint(w.inputHint)
|
||||||
w.setStage(StagePaused)
|
w.processEvent(AndroidViewEvent{View: uintptr(view)})
|
||||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
|
||||||
return C.jlong(w.handle)
|
return C.jlong(w.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +528,7 @@ func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle
|
|||||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.started = false
|
w.started = false
|
||||||
w.setStage(StagePaused)
|
w.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onStartView
|
//export Java_org_gioui_GioView_onStartView
|
||||||
@@ -534,7 +544,7 @@ func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.
|
|||||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||||
w := cgo.Handle(handle).Value().(*window)
|
w := cgo.Handle(handle).Value().(*window)
|
||||||
w.win = nil
|
w.win = nil
|
||||||
w.setStage(StagePaused)
|
w.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||||
@@ -556,9 +566,7 @@ func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
|
|||||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.loadConfig(env, class)
|
w.loadConfig(env, class)
|
||||||
if w.stage >= StageInactive {
|
w.draw(env, true)
|
||||||
w.draw(env, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onFrameCallback
|
//export Java_org_gioui_GioView_onFrameCallback
|
||||||
@@ -567,10 +575,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
|||||||
if !exist {
|
if !exist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if w.stage < StageInactive {
|
if w.visible && w.animating {
|
||||||
return
|
|
||||||
}
|
|
||||||
if w.animating {
|
|
||||||
w.draw(env, false)
|
w.draw(env, false)
|
||||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||||
}
|
}
|
||||||
@@ -579,7 +584,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
|||||||
//export Java_org_gioui_GioView_onBack
|
//export Java_org_gioui_GioView_onBack
|
||||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
if w.callbacks.Event(key.Event{Name: key.NameBack}) {
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
return C.JNI_TRUE
|
return C.JNI_TRUE
|
||||||
}
|
}
|
||||||
return C.JNI_FALSE
|
return C.JNI_FALSE
|
||||||
@@ -588,7 +593,8 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
|||||||
//export Java_org_gioui_GioView_onFocusChange
|
//export Java_org_gioui_GioView_onFocusChange
|
||||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||||
w := cgo.Handle(view).Value().(*window)
|
w := cgo.Handle(view).Value().(*window)
|
||||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
w.config.Focused = focus == C.JNI_TRUE
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_onWindowInsets
|
//export Java_org_gioui_GioView_onWindowInsets
|
||||||
@@ -600,9 +606,7 @@ func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C
|
|||||||
left: int(left),
|
left: int(left),
|
||||||
right: int(right),
|
right: int(right),
|
||||||
}
|
}
|
||||||
if w.stage >= StageInactive {
|
w.draw(env, true)
|
||||||
w.draw(env, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
||||||
@@ -663,6 +667,34 @@ func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.processEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) processEvent(e event.Event) bool {
|
||||||
|
if !w.callbacks.ProcessEvent(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
||||||
for _, ch := range sem.Children {
|
for _, ch := range sem.Children {
|
||||||
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
||||||
@@ -767,8 +799,7 @@ func (w *window) semIDFor(virtID C.jint) input.SemanticID {
|
|||||||
|
|
||||||
func (w *window) detach(env *C.JNIEnv) {
|
func (w *window) detach(env *C.JNIEnv) {
|
||||||
callVoidMethod(env, w.view, gioView.unregister)
|
callVoidMethod(env, w.view, gioView.unregister)
|
||||||
w.callbacks.Event(ViewEvent{})
|
w.processEvent(AndroidViewEvent{})
|
||||||
w.callbacks.SetDriver(nil)
|
|
||||||
w.handle.Delete()
|
w.handle.Delete()
|
||||||
C.jni_DeleteGlobalRef(env, w.view)
|
C.jni_DeleteGlobalRef(env, w.view)
|
||||||
w.view = 0
|
w.view = 0
|
||||||
@@ -779,18 +810,10 @@ func (w *window) setVisible(env *C.JNIEnv) {
|
|||||||
if width == 0 || height == 0 {
|
if width == 0 || height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.setStage(StageRunning)
|
w.visible = true
|
||||||
w.draw(env, true)
|
w.draw(env, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setStage(stage Stage) {
|
|
||||||
if stage == w.stage {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.stage = stage
|
|
||||||
w.callbacks.Event(StageEvent{stage})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) setVisual(visID int) error {
|
func (w *window) setVisual(visID int) error {
|
||||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||||
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
||||||
@@ -827,10 +850,13 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||||
|
if !w.visible {
|
||||||
|
return
|
||||||
|
}
|
||||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
if size.X == 0 || size.Y == 0 {
|
if size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
@@ -844,7 +870,7 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
|||||||
Left: unit.Dp(w.insets.left) * dppp,
|
Left: unit.Dp(w.insets.left) * dppp,
|
||||||
Right: unit.Dp(w.insets.right) * dppp,
|
Right: unit.Dp(w.insets.right) * dppp,
|
||||||
}
|
}
|
||||||
w.callbacks.Event(frameEvent{
|
w.processEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -944,7 +970,7 @@ func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.j
|
|||||||
if pressed == C.JNI_TRUE {
|
if pressed == C.JNI_TRUE {
|
||||||
state = key.Press
|
state = key.Press
|
||||||
}
|
}
|
||||||
w.callbacks.Event(key.Event{Name: n, State: state})
|
w.processEvent(key.Event{Name: n, State: state})
|
||||||
}
|
}
|
||||||
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||||
w.callbacks.EditorInsert(string(rune(r)))
|
w.callbacks.EditorInsert(string(rune(r)))
|
||||||
@@ -994,7 +1020,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.callbacks.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: src,
|
Source: src,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -1146,6 +1172,8 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
|
w.inputHint = mode
|
||||||
|
|
||||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||||
const (
|
const (
|
||||||
TYPE_NULL = 0
|
TYPE_NULL = 0
|
||||||
@@ -1289,12 +1317,9 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
|||||||
return C.jni_FindClass(env, cn)
|
return C.jni_FindClass(env, cn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func osMain() {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
}
|
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
|
||||||
mainWindow.in <- windowAndConfig{window, options}
|
mainWindow.in <- windowAndConfig{window, options}
|
||||||
return <-mainWindow.errs
|
<-mainWindow.windows
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||||
@@ -1313,7 +1338,7 @@ func (w *window) ReadClipboard() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := goString(env, C.jstring(c))
|
content := goString(env, C.jstring(c))
|
||||||
w.callbacks.Event(transfer.DataEvent{
|
w.processEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -1323,42 +1348,46 @@ func (w *window) ReadClipboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Configure(options []Option) {
|
func (w *window) Configure(options []Option) {
|
||||||
|
cnf := w.config
|
||||||
|
cnf.apply(unit.Metric{}, options)
|
||||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||||
prev := w.config
|
w.setConfig(env, cnf)
|
||||||
cnf := w.config
|
|
||||||
cnf.apply(unit.Metric{}, options)
|
|
||||||
// Decorations are never disabled.
|
|
||||||
cnf.Decorated = true
|
|
||||||
|
|
||||||
if prev.Orientation != cnf.Orientation {
|
|
||||||
w.config.Orientation = cnf.Orientation
|
|
||||||
setOrientation(env, w.view, cnf.Orientation)
|
|
||||||
}
|
|
||||||
if prev.NavigationColor != cnf.NavigationColor {
|
|
||||||
w.config.NavigationColor = cnf.NavigationColor
|
|
||||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
|
||||||
}
|
|
||||||
if prev.StatusColor != cnf.StatusColor {
|
|
||||||
w.config.StatusColor = cnf.StatusColor
|
|
||||||
setStatusColor(env, w.view, cnf.StatusColor)
|
|
||||||
}
|
|
||||||
if prev.Mode != cnf.Mode {
|
|
||||||
switch cnf.Mode {
|
|
||||||
case Fullscreen:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
|
||||||
w.config.Mode = Fullscreen
|
|
||||||
case Windowed:
|
|
||||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
|
||||||
w.config.Mode = Windowed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cnf.Decorated != prev.Decorated {
|
|
||||||
w.config.Decorated = cnf.Decorated
|
|
||||||
}
|
|
||||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
|
||||||
|
prev := w.config
|
||||||
|
// Decorations are never disabled.
|
||||||
|
cnf.Decorated = true
|
||||||
|
|
||||||
|
if prev.Orientation != cnf.Orientation {
|
||||||
|
w.config.Orientation = cnf.Orientation
|
||||||
|
setOrientation(env, w.view, cnf.Orientation)
|
||||||
|
}
|
||||||
|
if prev.NavigationColor != cnf.NavigationColor {
|
||||||
|
w.config.NavigationColor = cnf.NavigationColor
|
||||||
|
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||||
|
}
|
||||||
|
if prev.StatusColor != cnf.StatusColor {
|
||||||
|
w.config.StatusColor = cnf.StatusColor
|
||||||
|
setStatusColor(env, w.view, cnf.StatusColor)
|
||||||
|
}
|
||||||
|
if prev.Mode != cnf.Mode {
|
||||||
|
switch cnf.Mode {
|
||||||
|
case Fullscreen:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||||
|
w.config.Mode = Fullscreen
|
||||||
|
case Windowed:
|
||||||
|
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||||
|
w.config.Mode = Windowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cnf.Decorated != prev.Decorated {
|
||||||
|
w.config.Decorated = cnf.Decorated
|
||||||
|
}
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||||
@@ -1367,9 +1396,10 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) wakeup() {
|
||||||
runOnMain(func(env *C.JNIEnv) {
|
runOnMain(func(env *C.JNIEnv) {
|
||||||
w.callbacks.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1460,4 +1490,5 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (AndroidViewEvent) implementsViewEvent() {}
|
||||||
|
func (AndroidViewEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+7
-9
@@ -15,8 +15,8 @@ __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
|
|||||||
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
|
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
|
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
|
||||||
|
|
||||||
static bool isMainThread() {
|
static int isMainThread() {
|
||||||
return [NSThread isMainThread];
|
return [NSThread isMainThread] ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSUInteger nsstringLength(CFTypeRef cstr) {
|
static NSUInteger nsstringLength(CFTypeRef cstr) {
|
||||||
@@ -77,7 +77,7 @@ var mainFuncs = make(chan func(), 1)
|
|||||||
|
|
||||||
// runOnMain runs the function on the main thread.
|
// runOnMain runs the function on the main thread.
|
||||||
func runOnMain(f func()) {
|
func runOnMain(f func()) {
|
||||||
if C.isMainThread() {
|
if isMainThread() {
|
||||||
f()
|
f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -87,6 +87,10 @@ func runOnMain(f func()) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isMainThread() bool {
|
||||||
|
return C.isMainThread() != 0
|
||||||
|
}
|
||||||
|
|
||||||
//export gio_dispatchMainFuncs
|
//export gio_dispatchMainFuncs
|
||||||
func gio_dispatchMainFuncs() {
|
func gio_dispatchMainFuncs() {
|
||||||
for {
|
for {
|
||||||
@@ -259,9 +263,3 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
|||||||
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
|
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
runOnMain(func() {
|
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
#include "_cgo_export.h"
|
|
||||||
|
|
||||||
void gio_wakeupMainThread(void) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
gio_dispatchMainFuncs();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
+98
-60
@@ -12,6 +12,8 @@ package app
|
|||||||
#include <UIKit/UIKit.h>
|
#include <UIKit/UIKit.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||||
|
|
||||||
struct drawParams {
|
struct drawParams {
|
||||||
CGFloat dpi, sdpi;
|
CGFloat dpi, sdpi;
|
||||||
CGFloat width, height;
|
CGFloat width, height;
|
||||||
@@ -74,6 +76,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/cgo"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -81,14 +84,16 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct {
|
type UIKitViewEvent struct {
|
||||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||||
ViewController uintptr
|
ViewController uintptr
|
||||||
}
|
}
|
||||||
@@ -97,18 +102,17 @@ type window struct {
|
|||||||
view C.CFTypeRef
|
view C.CFTypeRef
|
||||||
w *callbacks
|
w *callbacks
|
||||||
displayLink *displayLink
|
displayLink *displayLink
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
visible bool
|
hidden bool
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
config Config
|
config Config
|
||||||
|
|
||||||
pointerMap []C.CFTypeRef
|
pointerMap []C.CFTypeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
var mainWindow = newWindowRendezvous()
|
var mainWindow = newWindowRendezvous()
|
||||||
|
|
||||||
var views = make(map[C.CFTypeRef]*window)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Darwin requires UI operations happen on the main thread only.
|
// Darwin requires UI operations happen on the main thread only.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@@ -116,48 +120,52 @@ func init() {
|
|||||||
|
|
||||||
//export onCreate
|
//export onCreate
|
||||||
func onCreate(view, controller C.CFTypeRef) {
|
func onCreate(view, controller C.CFTypeRef) {
|
||||||
|
wopts := <-mainWindow.out
|
||||||
w := &window{
|
w := &window{
|
||||||
view: view,
|
view: view,
|
||||||
|
w: wopts.window,
|
||||||
}
|
}
|
||||||
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
mainWindow.windows <- struct{}{}
|
||||||
dl, err := newDisplayLink(func() {
|
dl, err := newDisplayLink(func() {
|
||||||
w.draw(false)
|
w.draw(false)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
wopts := <-mainWindow.out
|
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||||
w.w = wopts.window
|
|
||||||
w.w.SetDriver(w)
|
|
||||||
views[view] = w
|
|
||||||
w.Configure(wopts.options)
|
w.Configure(wopts.options)
|
||||||
w.w.Event(StageEvent{Stage: StagePaused})
|
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||||
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
}
|
||||||
|
|
||||||
|
func viewFor(h C.uintptr_t) *window {
|
||||||
|
return cgo.Handle(h).Value().(*window)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onDraw
|
//export gio_onDraw
|
||||||
func gio_onDraw(view C.CFTypeRef) {
|
func gio_onDraw(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.draw(true)
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
|
if w.hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
params := C.viewDrawParams(w.view)
|
params := C.viewDrawParams(w.view)
|
||||||
if params.width == 0 || params.height == 0 {
|
if params.width == 0 || params.height == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wasVisible := w.visible
|
|
||||||
w.visible = true
|
|
||||||
if !wasVisible {
|
|
||||||
w.w.Event(StageEvent{Stage: StageRunning})
|
|
||||||
}
|
|
||||||
const inchPrDp = 1.0 / 163
|
const inchPrDp = 1.0 / 163
|
||||||
m := unit.Metric{
|
m := unit.Metric{
|
||||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||||
}
|
}
|
||||||
dppp := unit.Dp(1. / m.PxPerDp)
|
dppp := unit.Dp(1. / m.PxPerDp)
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: image.Point{
|
Size: image.Point{
|
||||||
@@ -177,26 +185,34 @@ func (w *window) draw(sync bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export onStop
|
//export onStop
|
||||||
func onStop(view C.CFTypeRef) {
|
func onStop(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.visible = false
|
w.hidden = true
|
||||||
w.w.Event(StageEvent{Stage: StagePaused})
|
}
|
||||||
|
|
||||||
|
//export onStart
|
||||||
|
func onStart(h C.uintptr_t) {
|
||||||
|
w := viewFor(h)
|
||||||
|
w.hidden = false
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDestroy
|
//export onDestroy
|
||||||
func onDestroy(view C.CFTypeRef) {
|
func onDestroy(h C.uintptr_t) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
delete(views, view)
|
w.ProcessEvent(UIKitViewEvent{})
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
w.w.Event(DestroyEvent{})
|
|
||||||
w.displayLink.Close()
|
w.displayLink.Close()
|
||||||
|
w.displayLink = nil
|
||||||
|
cgo.Handle(h).Delete()
|
||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onFocus
|
//export onFocus
|
||||||
func onFocus(view C.CFTypeRef, focus int) {
|
func onFocus(h C.uintptr_t, focus int) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
w.config.Focused = focus != 0
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLowMemory
|
//export onLowMemory
|
||||||
@@ -206,38 +222,38 @@ func onLowMemory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export onUpArrow
|
//export onUpArrow
|
||||||
func onUpArrow(view C.CFTypeRef) {
|
func onUpArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameUpArrow)
|
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDownArrow
|
//export onDownArrow
|
||||||
func onDownArrow(view C.CFTypeRef) {
|
func onDownArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameDownArrow)
|
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onLeftArrow
|
//export onLeftArrow
|
||||||
func onLeftArrow(view C.CFTypeRef) {
|
func onLeftArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameLeftArrow)
|
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onRightArrow
|
//export onRightArrow
|
||||||
func onRightArrow(view C.CFTypeRef) {
|
func onRightArrow(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameRightArrow)
|
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onDeleteBackward
|
//export onDeleteBackward
|
||||||
func onDeleteBackward(view C.CFTypeRef) {
|
func onDeleteBackward(h C.uintptr_t) {
|
||||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onText
|
//export onText
|
||||||
func onText(view, str C.CFTypeRef) {
|
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
w.w.EditorInsert(nsstringToString(str))
|
w.w.EditorInsert(nsstringToString(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
//export onTouch
|
//export onTouch
|
||||||
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||||
var kind pointer.Kind
|
var kind pointer.Kind
|
||||||
switch phase {
|
switch phase {
|
||||||
case C.UITouchPhaseBegan:
|
case C.UITouchPhaseBegan:
|
||||||
@@ -251,10 +267,10 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w := views[view]
|
w := viewFor(h)
|
||||||
t := time.Duration(float64(ti) * float64(time.Second))
|
t := time.Duration(float64(ti) * float64(time.Second))
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||||
@@ -267,7 +283,7 @@ func (w *window) ReadClipboard() {
|
|||||||
cstr := C.readClipboard()
|
cstr := C.readClipboard()
|
||||||
defer C.CFRelease(cstr)
|
defer C.CFRelease(cstr)
|
||||||
content := nsstringToString(cstr)
|
content := nsstringToString(cstr)
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -287,7 +303,7 @@ func (w *window) WriteClipboard(mime string, s []byte) {
|
|||||||
func (w *window) Configure([]Option) {
|
func (w *window) Configure([]Option) {
|
||||||
// Decorations are never disabled.
|
// Decorations are never disabled.
|
||||||
w.config.Decorated = true
|
w.config.Decorated = true
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
@@ -295,10 +311,6 @@ func (w *window) EditorStateChanged(old, new editorState) {}
|
|||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
v := w.view
|
|
||||||
if v == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if anim {
|
if anim {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
} else {
|
} else {
|
||||||
@@ -311,7 +323,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) onKeyCommand(name key.Name) {
|
func (w *window) onKeyCommand(name key.Name) {
|
||||||
w.w.Event(key.Event{
|
w.ProcessEvent(key.Event{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -350,12 +362,30 @@ func (w *window) ShowTextInput(show bool) {
|
|||||||
|
|
||||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
mainWindow.in <- windowAndConfig{win, options}
|
w.w.ProcessEvent(e)
|
||||||
return <-mainWindow.errs
|
w.loop.FlushEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
func osMain() {
|
func (w *window) Event() event.Event {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWindow(win *callbacks, options []Option) {
|
||||||
|
mainWindow.in <- windowAndConfig{win, options}
|
||||||
|
<-mainWindow.windows
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_runMain
|
//export gio_runMain
|
||||||
@@ -363,4 +393,12 @@ func gio_runMain() {
|
|||||||
runMain()
|
runMain()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (w *window) wakeup() {
|
||||||
|
runOnMain(func() {
|
||||||
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UIKitViewEvent) implementsViewEvent() {}
|
||||||
|
func (UIKitViewEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+32
-21
@@ -11,6 +11,7 @@
|
|||||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||||
|
|
||||||
@interface GioView: UIView <UIKeyInput>
|
@interface GioView: UIView <UIKeyInput>
|
||||||
|
@property uintptr_t handle;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GioViewController
|
@implementation GioViewController
|
||||||
@@ -54,33 +55,33 @@ CGFloat _keyboardHeight;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||||
UIView *drawView = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
if (drawView != nil) {
|
if (view != nil) {
|
||||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
onStart(view.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||||
UIView *drawView = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
if (drawView != nil) {
|
if (view != nil) {
|
||||||
onStop((__bridge CFTypeRef)drawView);
|
onStop(view.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated {
|
- (void)viewDidDisappear:(BOOL)animated {
|
||||||
[super viewDidDisappear:animated];
|
[super viewDidDisappear:animated];
|
||||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
onDestroy(viewRef);
|
onDestroy(view.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLayoutSubviews {
|
- (void)viewDidLayoutSubviews {
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
UIView *view = self.view.subviews[0];
|
GioView *view = (GioView *)self.view.subviews[0];
|
||||||
CGRect frame = self.view.bounds;
|
CGRect frame = self.view.bounds;
|
||||||
// Adjust view bounds to make room for the keyboard.
|
// Adjust view bounds to make room for the keyboard.
|
||||||
frame.size.height -= _keyboardHeight;
|
frame.size.height -= _keyboardHeight;
|
||||||
view.frame = frame;
|
view.frame = frame;
|
||||||
gio_onDraw((__bridge CFTypeRef)view);
|
gio_onDraw(view.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveMemoryWarning {
|
- (void)didReceiveMemoryWarning {
|
||||||
@@ -101,11 +102,10 @@ CGFloat _keyboardHeight;
|
|||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||||
CGFloat scale = view.contentScaleFactor;
|
CGFloat scale = view.contentScaleFactor;
|
||||||
NSUInteger i = 0;
|
NSUInteger i = 0;
|
||||||
NSUInteger n = [touches count];
|
NSUInteger n = [touches count];
|
||||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
|
||||||
for (UITouch *touch in touches) {
|
for (UITouch *touch in touches) {
|
||||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||||
i++;
|
i++;
|
||||||
@@ -116,7 +116,7 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
|||||||
CGPoint loc = [coalescedTouch locationInView:view];
|
CGPoint loc = [coalescedTouch locationInView:view];
|
||||||
j++;
|
j++;
|
||||||
int lastTouch = last && i == n && j == m;
|
int lastTouch = last && i == n && j == m;
|
||||||
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,13 +151,13 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
|
|
||||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus((__bridge CFTypeRef)self, YES);
|
onFocus(self.handle, YES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||||
if (self.isFirstResponder) {
|
if (self.isFirstResponder) {
|
||||||
onFocus((__bridge CFTypeRef)self, NO);
|
onFocus(self.handle, NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)insertText:(NSString *)text {
|
- (void)insertText:(NSString *)text {
|
||||||
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text);
|
onText(self.handle, (__bridge CFTypeRef)text);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)canBecomeFirstResponder {
|
- (BOOL)canBecomeFirstResponder {
|
||||||
@@ -190,23 +190,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)deleteBackward {
|
- (void)deleteBackward {
|
||||||
onDeleteBackward((__bridge CFTypeRef)self);
|
onDeleteBackward(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onUpArrow {
|
- (void)onUpArrow {
|
||||||
onUpArrow((__bridge CFTypeRef)self);
|
onUpArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onDownArrow {
|
- (void)onDownArrow {
|
||||||
onDownArrow((__bridge CFTypeRef)self);
|
onDownArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onLeftArrow {
|
- (void)onLeftArrow {
|
||||||
onLeftArrow((__bridge CFTypeRef)self);
|
onLeftArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onRightArrow {
|
- (void)onRightArrow {
|
||||||
onRightArrow((__bridge CFTypeRef)self);
|
onRightArrow(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||||
@@ -271,3 +271,14 @@ void gio_showCursor() {
|
|||||||
void gio_setCursor(NSUInteger curID) {
|
void gio_setCursor(NSUInteger curID) {
|
||||||
// Not supported.
|
// Not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||||
|
GioView *v = (__bridge GioView *)viewRef;
|
||||||
|
v.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gio_wakeupMainThread(void) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
gio_dispatchMainFuncs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
+81
-91
@@ -14,8 +14,10 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/internal/f32color"
|
"gioui.org/internal/f32color"
|
||||||
|
"gioui.org/op"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
@@ -23,7 +25,7 @@ import (
|
|||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct {
|
type JSViewEvent struct {
|
||||||
Element js.Value
|
Element js.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +56,6 @@ type window struct {
|
|||||||
composing bool
|
composing bool
|
||||||
requestFocus bool
|
requestFocus bool
|
||||||
|
|
||||||
chanAnimation chan struct{}
|
|
||||||
chanRedraw chan struct{}
|
|
||||||
|
|
||||||
config Config
|
config Config
|
||||||
inset f32.Point
|
inset f32.Point
|
||||||
scale float32
|
scale float32
|
||||||
@@ -69,7 +68,7 @@ type window struct {
|
|||||||
contextStatus contextStatus
|
contextStatus contextStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
doc := js.Global().Get("document")
|
doc := js.Global().Get("document")
|
||||||
cont := getContainer(doc)
|
cont := getContainer(doc)
|
||||||
cnv := createCanvas(doc)
|
cnv := createCanvas(doc)
|
||||||
@@ -84,7 +83,9 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
head: doc.Get("head"),
|
head: doc.Get("head"),
|
||||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
|
w: win,
|
||||||
}
|
}
|
||||||
|
w.w.SetDriver(w)
|
||||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||||
w.browserHistory = w.window.Get("history")
|
w.browserHistory = w.window.Get("history")
|
||||||
w.visualViewport = w.window.Get("visualViewport")
|
w.visualViewport = w.window.Get("visualViewport")
|
||||||
@@ -94,15 +95,13 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||||
w.screenOrientation = screen.Get("orientation")
|
w.screenOrientation = screen.Get("orientation")
|
||||||
}
|
}
|
||||||
w.chanAnimation = make(chan struct{}, 1)
|
|
||||||
w.chanRedraw = make(chan struct{}, 1)
|
|
||||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
w.chanAnimation <- struct{}{}
|
w.draw(false)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
content := args[0].String()
|
content := args[0].String()
|
||||||
go win.Event(transfer.DataEvent{
|
w.processEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -112,29 +111,12 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
})
|
})
|
||||||
w.addEventListeners()
|
w.addEventListeners()
|
||||||
w.addHistory()
|
w.addHistory()
|
||||||
w.w = win
|
|
||||||
|
|
||||||
go func() {
|
w.Configure(options)
|
||||||
defer w.cleanup()
|
w.blur()
|
||||||
w.w.SetDriver(w)
|
w.processEvent(JSViewEvent{Element: cont})
|
||||||
w.Configure(options)
|
w.resize()
|
||||||
w.blur()
|
w.draw(true)
|
||||||
w.w.Event(ViewEvent{Element: cont})
|
|
||||||
w.w.Event(StageEvent{Stage: StageRunning})
|
|
||||||
w.resize()
|
|
||||||
w.draw(true)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-w.wakeups:
|
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
case <-w.chanAnimation:
|
|
||||||
w.animCallback()
|
|
||||||
case <-w.chanRedraw:
|
|
||||||
w.draw(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainer(doc js.Value) js.Value {
|
func getContainer(doc js.Value) js.Value {
|
||||||
@@ -194,12 +176,12 @@ func (w *window) addEventListeners() {
|
|||||||
w.cnv.Set("width", 0)
|
w.cnv.Set("width", 0)
|
||||||
w.cnv.Set("height", 0)
|
w.cnv.Set("height", 0)
|
||||||
w.resize()
|
w.resize()
|
||||||
w.requestRedraw()
|
w.draw(true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.resize()
|
w.resize()
|
||||||
w.requestRedraw()
|
w.draw(true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||||
@@ -207,22 +189,11 @@ func (w *window) addEventListeners() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||||
if w.w.Event(key.Event{Name: key.NameBack}) {
|
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||||
return w.browserHistory.Call("forward")
|
return w.browserHistory.Call("forward")
|
||||||
}
|
}
|
||||||
return w.browserHistory.Call("back")
|
return w.browserHistory.Call("back")
|
||||||
})
|
})
|
||||||
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
|
||||||
ev := StageEvent{}
|
|
||||||
switch w.document.Get("visibilityState").String() {
|
|
||||||
case "hidden", "prerender", "unloaded":
|
|
||||||
ev.Stage = StagePaused
|
|
||||||
default:
|
|
||||||
ev.Stage = StageRunning
|
|
||||||
}
|
|
||||||
w.w.Event(ev)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||||
return nil
|
return nil
|
||||||
@@ -280,18 +251,20 @@ func (w *window) addEventListeners() {
|
|||||||
w.touches[i] = js.Null()
|
w.touches[i] = js.Null()
|
||||||
}
|
}
|
||||||
w.touches = w.touches[:0]
|
w.touches = w.touches[:0]
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
w.blur()
|
w.blur()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -380,10 +353,50 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
|||||||
Modifiers: modifiersFor(e),
|
Modifiers: modifiersFor(e),
|
||||||
State: ks,
|
State: ks,
|
||||||
}
|
}
|
||||||
w.w.Event(cmd)
|
w.processEvent(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.processEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) processEvent(e event.Event) bool {
|
||||||
|
if !w.w.ProcessEvent(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case w.wakeups <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if ok {
|
||||||
|
if _, destroy := evt.(DestroyEvent); destroy {
|
||||||
|
w.cleanup()
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
<-w.wakeups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.w.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||||
// KeyEvent.
|
// KeyEvent.
|
||||||
func modifiersFor(e js.Value) key.Modifiers {
|
func modifiersFor(e js.Value) key.Modifiers {
|
||||||
@@ -431,7 +444,7 @@ func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
|||||||
X: float32(x) * scale,
|
X: float32(x) * scale,
|
||||||
Y: float32(y) * scale,
|
Y: float32(y) * scale,
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: pos,
|
Position: pos,
|
||||||
@@ -481,7 +494,7 @@ func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
|||||||
if jbtns&4 != 0 {
|
if jbtns&4 != 0 {
|
||||||
btns |= pointer.ButtonTertiary
|
btns |= pointer.ButtonTertiary
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.processEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: btns,
|
Buttons: btns,
|
||||||
@@ -508,17 +521,6 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
|||||||
return jsf
|
return jsf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) animCallback() {
|
|
||||||
anim := w.animating
|
|
||||||
w.animRequested = anim
|
|
||||||
if anim {
|
|
||||||
w.requestAnimationFrame.Invoke(w.redraw)
|
|
||||||
}
|
|
||||||
if anim {
|
|
||||||
w.draw(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
@@ -574,7 +576,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Perform(system.Action) {}
|
func (w *window) Perform(system.Action) {}
|
||||||
@@ -613,23 +615,14 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
|||||||
style.Set("cursor", webCursor[cursor])
|
style.Set("cursor", webCursor[cursor])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) ShowTextInput(show bool) {
|
func (w *window) ShowTextInput(show bool) {
|
||||||
// Run in a goroutine to avoid a deadlock if the
|
// Run in a goroutine to avoid a deadlock if the
|
||||||
// focus change result in an event.
|
// focus change result in an event.
|
||||||
go func() {
|
if show {
|
||||||
if show {
|
w.focus()
|
||||||
w.focus()
|
} else {
|
||||||
} else {
|
w.blur()
|
||||||
w.blur()
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) SetInputHint(mode key.InputHint) {
|
func (w *window) SetInputHint(mode key.InputHint) {
|
||||||
@@ -646,7 +639,7 @@ func (w *window) resize() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.processEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||||
@@ -666,12 +659,19 @@ func (w *window) draw(sync bool) {
|
|||||||
if w.contextStatus == contextStatusLost {
|
if w.contextStatus == contextStatusLost {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
anim := w.animating
|
||||||
|
w.animRequested = anim
|
||||||
|
if anim {
|
||||||
|
w.requestAnimationFrame.Invoke(w.redraw)
|
||||||
|
} else if !sync {
|
||||||
|
return
|
||||||
|
}
|
||||||
size, insets, metric := w.getConfig()
|
size, insets, metric := w.getConfig()
|
||||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.w.Event(frameEvent{
|
w.processEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: size,
|
Size: size,
|
||||||
@@ -741,17 +741,6 @@ func (w *window) navigationColor(c color.NRGBA) {
|
|||||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) requestRedraw() {
|
|
||||||
select {
|
|
||||||
case w.chanRedraw <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func osMain() {
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
|
|
||||||
func translateKey(k string) (key.Name, bool) {
|
func translateKey(k string) (key.Name, bool) {
|
||||||
var n key.Name
|
var n key.Name
|
||||||
|
|
||||||
@@ -827,4 +816,5 @@ func translateKey(k string) (key.Name, bool) {
|
|||||||
return n, true
|
return n, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (JSViewEvent) implementsViewEvent() {}
|
||||||
|
func (JSViewEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+315
-213
@@ -10,16 +10,19 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/cgo"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"gioui.org/internal/f32"
|
"gioui.org/internal/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
|
||||||
_ "gioui.org/internal/cocoainit"
|
_ "gioui.org/internal/cocoainit"
|
||||||
@@ -36,9 +39,10 @@ import (
|
|||||||
#define MOUSE_DOWN 3
|
#define MOUSE_DOWN 3
|
||||||
#define MOUSE_SCROLL 4
|
#define MOUSE_SCROLL 4
|
||||||
|
|
||||||
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
__attribute__ ((visibility ("hidden"))) void gio_initApp(void);
|
||||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
|
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
|
||||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
||||||
|
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||||
|
|
||||||
static void writeClipboard(CFTypeRef str) {
|
static void writeClipboard(CFTypeRef str) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
@@ -58,148 +62,204 @@ static CFTypeRef readClipboard(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat viewHeight(CFTypeRef viewRef) {
|
static CGFloat viewHeight(CFTypeRef viewRef) {
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
@autoreleasepool {
|
||||||
return [view bounds].size.height;
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
|
return [view bounds].size.height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat viewWidth(CFTypeRef viewRef) {
|
static CGFloat viewWidth(CFTypeRef viewRef) {
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
@autoreleasepool {
|
||||||
return [view bounds].size.width;
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
|
return [view bounds].size.width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat getScreenBackingScale(void) {
|
static CGFloat getScreenBackingScale(void) {
|
||||||
return [NSScreen.mainScreen backingScaleFactor];
|
@autoreleasepool {
|
||||||
|
return [NSScreen.mainScreen backingScaleFactor];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat getViewBackingScale(CFTypeRef viewRef) {
|
static CGFloat getViewBackingScale(CFTypeRef viewRef) {
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
@autoreleasepool {
|
||||||
return [view.window backingScaleFactor];
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
|
return [view.window backingScaleFactor];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setNeedsDisplay(CFTypeRef viewRef) {
|
static void setNeedsDisplay(CFTypeRef viewRef) {
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
@autoreleasepool {
|
||||||
[view setNeedsDisplay:YES];
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
|
[view setNeedsDisplay:YES];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
|
static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
return [window cascadeTopLeftFromPoint:topLeft];
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
return [window cascadeTopLeftFromPoint:topLeft];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void makeKeyAndOrderFront(CFTypeRef windowRef) {
|
static void makeKeyAndOrderFront(CFTypeRef windowRef) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window makeKeyAndOrderFront:nil];
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void toggleFullScreen(CFTypeRef windowRef) {
|
static void toggleFullScreen(CFTypeRef windowRef) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window toggleFullScreen:nil];
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window toggleFullScreen:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
|
static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
return [window styleMask];
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
return [window styleMask];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
|
static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
window.styleMask = mask;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
window.styleMask = mask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
|
static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
window.titleVisibility = state;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
window.titleVisibility = state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
|
static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
window.titlebarAppearsTransparent = (BOOL)transparent;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
window.titlebarAppearsTransparent = (BOOL)transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
|
static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window standardWindowButton:btn].hidden = (BOOL)hide;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window standardWindowButton:btn].hidden = (BOOL)hide;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
|
static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closeWindow(CFTypeRef windowRef) {
|
static void closeWindow(CFTypeRef windowRef) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window performClose:nil];
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window performClose:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
NSSize size = NSMakeSize(width, height);
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
[window setContentSize:size];
|
NSSize size = NSMakeSize(width, height);
|
||||||
|
[window setContentSize:size];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
window.contentMinSize = NSMakeSize(width, height);
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
|
window.contentMinSize = NSMakeSize(width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
window.contentMaxSize = NSMakeSize(width, height);
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
|
window.contentMaxSize = NSMakeSize(width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
|
static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
NSRect r = NSMakeRect(x, y, w, h);
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
[window setFrame:r display:YES];
|
NSRect r = NSMakeRect(x, y, w, h);
|
||||||
|
[window setFrame:r display:YES];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hideWindow(CFTypeRef windowRef) {
|
static void hideWindow(CFTypeRef windowRef) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window miniaturize:window];
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window miniaturize:window];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void unhideWindow(CFTypeRef windowRef) {
|
static void unhideWindow(CFTypeRef windowRef) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window deminiaturize:window];
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window deminiaturize:window];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSRect getScreenFrame(CFTypeRef windowRef) {
|
static NSRect getScreenFrame(CFTypeRef windowRef) {
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
return [[window screen] frame];
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
|
return [[window screen] frame];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
|
static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
window.title = (__bridge NSString *)titleRef;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
window.title = (__bridge NSString *)titleRef;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int isWindowZoomed(CFTypeRef windowRef) {
|
static int isWindowZoomed(CFTypeRef windowRef) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
return window.zoomed ? 1 : 0;
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
return window.zoomed ? 1 : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zoomWindow(CFTypeRef windowRef) {
|
static void zoomWindow(CFTypeRef windowRef) {
|
||||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
@autoreleasepool {
|
||||||
[window zoom:nil];
|
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window zoom:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CFTypeRef layerForView(CFTypeRef viewRef) {
|
static CFTypeRef layerForView(CFTypeRef viewRef) {
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
@autoreleasepool {
|
||||||
return (__bridge CFTypeRef)view.layer;
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
|
return (__bridge CFTypeRef)view.layer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CFTypeRef windowForView(CFTypeRef viewRef) {
|
static CFTypeRef windowForView(CFTypeRef viewRef) {
|
||||||
NSView *view = (__bridge NSView *)viewRef;
|
@autoreleasepool {
|
||||||
return (__bridge CFTypeRef)view.window;
|
NSView *view = (__bridge NSView *)viewRef;
|
||||||
|
return (__bridge CFTypeRef)view.window;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void raiseWindow(CFTypeRef windowRef) {
|
static void raiseWindow(CFTypeRef windowRef) {
|
||||||
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
|
@autoreleasepool {
|
||||||
if (![currentApp isActive]) {
|
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
|
||||||
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
if (![currentApp isActive]) {
|
||||||
|
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||||
|
}
|
||||||
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||||
|
[window makeKeyAndOrderFront:nil];
|
||||||
}
|
}
|
||||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
|
||||||
[window makeKeyAndOrderFront:nil];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static CFTypeRef createInputContext(CFTypeRef clientRef) {
|
static CFTypeRef createInputContext(CFTypeRef clientRef) {
|
||||||
@@ -211,23 +271,33 @@ static CFTypeRef createInputContext(CFTypeRef clientRef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void discardMarkedText(CFTypeRef viewRef) {
|
static void discardMarkedText(CFTypeRef viewRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
||||||
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
||||||
if (view == [ctx client]) {
|
if (view == [ctx client]) {
|
||||||
[ctx discardMarkedText];
|
[ctx discardMarkedText];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
|
static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
||||||
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
||||||
if (view == [ctx client]) {
|
if (view == [ctx client]) {
|
||||||
[ctx invalidateCharacterCoordinates];
|
[ctx invalidateCharacterCoordinates];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dispatchEvent(void) {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||||
|
untilDate:[NSDate distantFuture]
|
||||||
|
inMode:NSDefaultRunLoopMode
|
||||||
|
dequeue:YES];
|
||||||
|
[NSApp sendEvent:event];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
@@ -237,9 +307,9 @@ func init() {
|
|||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewEvent notified the client of changes to the window AppKit handles.
|
// AppKitViewEvent notifies the client of changes to the window AppKit handles.
|
||||||
// The handles are retained until another ViewEvent is sent.
|
// The handles are retained until another AppKitViewEvent is sent.
|
||||||
type ViewEvent struct {
|
type AppKitViewEvent struct {
|
||||||
// View is a CFTypeRef for the NSView for the window.
|
// View is a CFTypeRef for the NSView for the window.
|
||||||
View uintptr
|
View uintptr
|
||||||
// Layer is a CFTypeRef of the CALayer of View.
|
// Layer is a CFTypeRef of the CALayer of View.
|
||||||
@@ -249,52 +319,32 @@ type ViewEvent struct {
|
|||||||
type window struct {
|
type window struct {
|
||||||
view C.CFTypeRef
|
view C.CFTypeRef
|
||||||
w *callbacks
|
w *callbacks
|
||||||
stage Stage
|
anim bool
|
||||||
|
visible bool
|
||||||
displayLink *displayLink
|
displayLink *displayLink
|
||||||
// redraw is a single entry channel for making sure only one
|
// redraw is a single entry channel for making sure only one
|
||||||
// display link redraw request is in flight.
|
// display link redraw request is in flight.
|
||||||
redraw chan struct{}
|
redraw chan struct{}
|
||||||
cursor pointer.Cursor
|
cursor pointer.Cursor
|
||||||
pointerBtns pointer.Buttons
|
pointerBtns pointer.Buttons
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
scale float32
|
scale float32
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// viewMap is the mapping from Cocoa NSViews to Go windows.
|
// launched is closed after gio_initApp returns.
|
||||||
var viewMap = make(map[C.CFTypeRef]*window)
|
|
||||||
|
|
||||||
// launched is closed when applicationDidFinishLaunching is called.
|
|
||||||
var launched = make(chan struct{})
|
var launched = make(chan struct{})
|
||||||
|
|
||||||
// nextTopLeft is the offset to use for the next window's call to
|
// nextTopLeft is the offset to use for the next window's call to
|
||||||
// cascadeTopLeftFromPoint.
|
// cascadeTopLeftFromPoint.
|
||||||
var nextTopLeft C.NSPoint
|
var nextTopLeft C.NSPoint
|
||||||
|
|
||||||
// mustView is like lookupView, except that it panics
|
// mainThreadWindow is the window currently in control of the main thread.
|
||||||
// if the view isn't mapped.
|
var mainThreadWindow *window
|
||||||
func mustView(view C.CFTypeRef) *window {
|
|
||||||
w, ok := lookupView(view)
|
|
||||||
if !ok {
|
|
||||||
panic("no window for view")
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupView(view C.CFTypeRef) (*window, bool) {
|
func windowFor(h C.uintptr_t) *window {
|
||||||
w, exists := viewMap[view]
|
return cgo.Handle(h).Value().(*window)
|
||||||
if !exists {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteView(view C.CFTypeRef) {
|
|
||||||
delete(viewMap, view)
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertView(view C.CFTypeRef, w *window) {
|
|
||||||
viewMap[view] = w
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) contextView() C.CFTypeRef {
|
func (w *window) contextView() C.CFTypeRef {
|
||||||
@@ -307,7 +357,7 @@ func (w *window) ReadClipboard() {
|
|||||||
defer C.CFRelease(cstr)
|
defer C.CFRelease(cstr)
|
||||||
}
|
}
|
||||||
content := nsstringToString(cstr)
|
content := nsstringToString(cstr)
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -420,7 +470,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
||||||
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
|
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setTitle(prev, cnf Config) {
|
func (w *window) setTitle(prev, cnf Config) {
|
||||||
@@ -471,7 +521,8 @@ func (w *window) ShowTextInput(show bool) {}
|
|||||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
if anim {
|
w.anim = anim
|
||||||
|
if w.anim && w.visible {
|
||||||
w.displayLink.Start()
|
w.displayLink.Start()
|
||||||
} else {
|
} else {
|
||||||
w.displayLink.Stop()
|
w.displayLink.Stop()
|
||||||
@@ -488,26 +539,18 @@ func (w *window) runOnMain(f func()) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setStage(stage Stage) {
|
|
||||||
if stage == w.stage {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.stage = stage
|
|
||||||
w.w.Event(StageEvent{Stage: stage})
|
|
||||||
}
|
|
||||||
|
|
||||||
//export gio_onKeys
|
//export gio_onKeys
|
||||||
func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
||||||
str := nsstringToString(cstr)
|
str := nsstringToString(cstr)
|
||||||
kmods := convertMods(mods)
|
kmods := convertMods(mods)
|
||||||
ks := key.Release
|
ks := key.Release
|
||||||
if keyDown {
|
if keyDown {
|
||||||
ks = key.Press
|
ks = key.Press
|
||||||
}
|
}
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
for _, k := range str {
|
for _, k := range str {
|
||||||
if n, ok := convertKey(k); ok {
|
if n, ok := convertKey(k); ok {
|
||||||
w.w.Event(key.Event{
|
w.ProcessEvent(key.Event{
|
||||||
Name: n,
|
Name: n,
|
||||||
Modifiers: kmods,
|
Modifiers: kmods,
|
||||||
State: ks,
|
State: ks,
|
||||||
@@ -517,15 +560,15 @@ func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onText
|
//export gio_onText
|
||||||
func gio_onText(view, cstr C.CFTypeRef) {
|
func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
|
||||||
str := nsstringToString(cstr)
|
str := nsstringToString(cstr)
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.w.EditorInsert(str)
|
w.w.EditorInsert(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onMouse
|
//export gio_onMouse
|
||||||
func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
|
func gio_onMouse(h C.uintptr_t, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
t := time.Duration(float64(ti)*float64(time.Second) + .5)
|
t := time.Duration(float64(ti)*float64(time.Second) + .5)
|
||||||
xf, yf := float32(x)*w.scale, float32(y)*w.scale
|
xf, yf := float32(x)*w.scale, float32(y)*w.scale
|
||||||
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
|
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
|
||||||
@@ -562,7 +605,7 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
|
|||||||
default:
|
default:
|
||||||
panic("invalid direction")
|
panic("invalid direction")
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: typ,
|
Kind: typ,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Time: t,
|
Time: t,
|
||||||
@@ -574,35 +617,29 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onDraw
|
//export gio_onDraw
|
||||||
func gio_onDraw(view C.CFTypeRef) {
|
func gio_onDraw(h C.uintptr_t) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.draw()
|
w.draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onFocus
|
//export gio_onFocus
|
||||||
func gio_onFocus(view C.CFTypeRef, focus C.int) {
|
func gio_onFocus(h C.uintptr_t, focus C.int) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.w.Event(key.FocusEvent{Focus: focus == 1})
|
|
||||||
if w.stage >= StageInactive {
|
|
||||||
if focus == 0 {
|
|
||||||
w.setStage(StageInactive)
|
|
||||||
} else {
|
|
||||||
w.setStage(StageRunning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.SetCursor(w.cursor)
|
w.SetCursor(w.cursor)
|
||||||
|
w.config.Focused = focus == 1
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onChangeScreen
|
//export gio_onChangeScreen
|
||||||
func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
|
func gio_onChangeScreen(h C.uintptr_t, did uint64) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.displayLink.SetDisplayID(did)
|
w.displayLink.SetDisplayID(did)
|
||||||
C.setNeedsDisplay(w.view)
|
C.setNeedsDisplay(w.view)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_hasMarkedText
|
//export gio_hasMarkedText
|
||||||
func gio_hasMarkedText(view C.CFTypeRef) C.int {
|
func gio_hasMarkedText(h C.uintptr_t) C.int {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
if state.compose.Start != -1 {
|
if state.compose.Start != -1 {
|
||||||
return 1
|
return 1
|
||||||
@@ -611,8 +648,8 @@ func gio_hasMarkedText(view C.CFTypeRef) C.int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_markedRange
|
//export gio_markedRange
|
||||||
func gio_markedRange(view C.CFTypeRef) C.NSRange {
|
func gio_markedRange(h C.uintptr_t) C.NSRange {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
rng := state.compose
|
rng := state.compose
|
||||||
start, end := rng.Start, rng.End
|
start, end := rng.Start, rng.End
|
||||||
@@ -627,8 +664,8 @@ func gio_markedRange(view C.CFTypeRef) C.NSRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_selectedRange
|
//export gio_selectedRange
|
||||||
func gio_selectedRange(view C.CFTypeRef) C.NSRange {
|
func gio_selectedRange(h C.uintptr_t) C.NSRange {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
rng := state.Selection
|
rng := state.Selection
|
||||||
start, end := rng.Start, rng.End
|
start, end := rng.Start, rng.End
|
||||||
@@ -643,14 +680,14 @@ func gio_selectedRange(view C.CFTypeRef) C.NSRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_unmarkText
|
//export gio_unmarkText
|
||||||
func gio_unmarkText(view C.CFTypeRef) {
|
func gio_unmarkText(h C.uintptr_t) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_setMarkedText
|
//export gio_setMarkedText
|
||||||
func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
|
func gio_setMarkedText(h C.uintptr_t, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
str := nsstringToString(cstr)
|
str := nsstringToString(cstr)
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
rng := state.compose
|
rng := state.compose
|
||||||
@@ -689,8 +726,8 @@ func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_substringForProposedRange
|
//export gio_substringForProposedRange
|
||||||
func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
|
func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
start, end := state.Snippet.Start, state.Snippet.End
|
start, end := state.Snippet.Start, state.Snippet.End
|
||||||
if start > end {
|
if start > end {
|
||||||
@@ -710,8 +747,8 @@ func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NS
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_insertText
|
//export gio_insertText
|
||||||
func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
|
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
rng := state.compose
|
rng := state.compose
|
||||||
if rng.Start == -1 {
|
if rng.Start == -1 {
|
||||||
@@ -735,13 +772,13 @@ func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//export gio_characterIndexForPoint
|
//export gio_characterIndexForPoint
|
||||||
func gio_characterIndexForPoint(view C.CFTypeRef, p C.NSPoint) C.NSUInteger {
|
func gio_characterIndexForPoint(h C.uintptr_t, p C.NSPoint) C.NSUInteger {
|
||||||
return C.NSNotFound
|
return C.NSNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_firstRectForCharacterRange
|
//export gio_firstRectForCharacterRange
|
||||||
func gio_firstRectForCharacterRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
|
func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
state := w.w.EditorState()
|
state := w.w.EditorState()
|
||||||
sel := state.Selection
|
sel := state.Selection
|
||||||
u16start := state.UTF16Index(sel.Start)
|
u16start := state.UTF16Index(sel.Start)
|
||||||
@@ -768,6 +805,10 @@ func (w *window) draw() {
|
|||||||
case <-w.redraw:
|
case <-w.redraw:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
w.visible = true
|
||||||
|
if w.anim {
|
||||||
|
w.SetAnimating(w.anim)
|
||||||
|
}
|
||||||
w.scale = float32(C.getViewBackingScale(w.view))
|
w.scale = float32(C.getViewBackingScale(w.view))
|
||||||
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
|
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
|
||||||
sz := image.Point{
|
sz := image.Point{
|
||||||
@@ -776,14 +817,13 @@ func (w *window) draw() {
|
|||||||
}
|
}
|
||||||
if sz != w.config.Size {
|
if sz != w.config.Size {
|
||||||
w.config.Size = sz
|
w.config.Size = sz
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
if sz.X == 0 || sz.Y == 0 {
|
if sz.X == 0 || sz.Y == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg := configFor(w.scale)
|
cfg := configFor(w.scale)
|
||||||
w.setStage(StageRunning)
|
w.ProcessEvent(frameEvent{
|
||||||
w.w.Event(frameEvent{
|
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -793,6 +833,55 @@ func (w *window) draw() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
// The main thread window deliver events in Event.
|
||||||
|
if w != mainThreadWindow {
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
if !isMainThread() {
|
||||||
|
return w.loop.Event()
|
||||||
|
}
|
||||||
|
mainThreadWindow = w
|
||||||
|
defer func() { mainThreadWindow = nil }()
|
||||||
|
for {
|
||||||
|
if evt, ok := w.loop.win.nextEvent(); ok {
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
C.dispatchEvent()
|
||||||
|
gio_dispatchMainFuncs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
if isMainThread() {
|
||||||
|
mainThreadWindow = w
|
||||||
|
defer func() { mainThreadWindow = nil }()
|
||||||
|
}
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
if isMainThread() {
|
||||||
|
mainThreadWindow = w
|
||||||
|
defer func() { mainThreadWindow = nil }()
|
||||||
|
}
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
if !isMainThread() {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mainThreadWindow = w
|
||||||
|
defer func() { mainThreadWindow = nil }()
|
||||||
|
w.loop.win.ProcessFrame(frame, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func configFor(scale float32) unit.Metric {
|
func configFor(scale float32) unit.Metric {
|
||||||
return unit.Metric{
|
return unit.Metric{
|
||||||
PxPerDp: scale,
|
PxPerDp: scale,
|
||||||
@@ -800,77 +889,87 @@ func configFor(scale float32) unit.Metric {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onClose
|
//export gio_onAttached
|
||||||
func gio_onClose(view C.CFTypeRef) {
|
func gio_onAttached(h C.uintptr_t, attached C.int) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.w.Event(ViewEvent{})
|
if attached != 0 {
|
||||||
w.w.Event(DestroyEvent{})
|
layer := C.layerForView(w.view)
|
||||||
|
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
||||||
|
} else {
|
||||||
|
w.ProcessEvent(AppKitViewEvent{})
|
||||||
|
w.visible = false
|
||||||
|
w.SetAnimating(w.anim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export gio_onDestroy
|
||||||
|
func gio_onDestroy(h C.uintptr_t) {
|
||||||
|
w := windowFor(h)
|
||||||
|
w.ProcessEvent(DestroyEvent{})
|
||||||
w.displayLink.Close()
|
w.displayLink.Close()
|
||||||
w.displayLink = nil
|
w.displayLink = nil
|
||||||
deleteView(view)
|
cgo.Handle(h).Delete()
|
||||||
C.CFRelease(w.view)
|
|
||||||
w.view = 0
|
w.view = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onHide
|
//export gio_onHide
|
||||||
func gio_onHide(view C.CFTypeRef) {
|
func gio_onHide(h C.uintptr_t) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.setStage(StagePaused)
|
w.visible = false
|
||||||
|
w.SetAnimating(w.anim)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onShow
|
//export gio_onShow
|
||||||
func gio_onShow(view C.CFTypeRef) {
|
func gio_onShow(h C.uintptr_t) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.setStage(StageRunning)
|
w.draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onFullscreen
|
//export gio_onFullscreen
|
||||||
func gio_onFullscreen(view C.CFTypeRef) {
|
func gio_onFullscreen(h C.uintptr_t) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.config.Mode = Fullscreen
|
w.config.Mode = Fullscreen
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onWindowed
|
//export gio_onWindowed
|
||||||
func gio_onWindowed(view C.CFTypeRef) {
|
func gio_onWindowed(h C.uintptr_t) {
|
||||||
w := mustView(view)
|
w := windowFor(h)
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onAppHide
|
func newWindow(win *callbacks, options []Option) {
|
||||||
func gio_onAppHide() {
|
w := &window{
|
||||||
for _, w := range viewMap {
|
redraw: make(chan struct{}, 1),
|
||||||
w.setStage(StagePaused)
|
w: win,
|
||||||
}
|
}
|
||||||
}
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
if isMainThread() {
|
||||||
//export gio_onAppShow
|
mainThreadWindow = w
|
||||||
func gio_onAppShow() {
|
defer func() { mainThreadWindow = nil }()
|
||||||
for _, w := range viewMap {
|
select {
|
||||||
w.setStage(StageRunning)
|
case <-launched:
|
||||||
|
default:
|
||||||
|
// If we're the main thread, initialize the GUI.
|
||||||
|
C.gio_initApp()
|
||||||
|
close(launched)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
<-launched
|
||||||
}
|
}
|
||||||
}
|
res := make(chan struct{}, 1)
|
||||||
|
|
||||||
//export gio_onFinishLaunching
|
|
||||||
func gio_onFinishLaunching() {
|
|
||||||
close(launched)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWindow(win *callbacks, options []Option) error {
|
|
||||||
<-launched
|
|
||||||
errch := make(chan error)
|
|
||||||
runOnMain(func() {
|
runOnMain(func() {
|
||||||
w, err := newOSWindow()
|
win.SetDriver(w)
|
||||||
if err != nil {
|
res <- struct{}{}
|
||||||
errch <- err
|
if err := w.init(); err != nil {
|
||||||
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
errch <- nil
|
|
||||||
w.w = win
|
|
||||||
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
|
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
|
||||||
|
// Release our reference now that the NSWindow has it.
|
||||||
|
C.CFRelease(w.view)
|
||||||
w.updateWindowMode()
|
w.updateWindowMode()
|
||||||
win.SetDriver(w)
|
|
||||||
w.Configure(options)
|
w.Configure(options)
|
||||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||||
@@ -880,23 +979,17 @@ func newWindow(win *callbacks, options []Option) error {
|
|||||||
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
||||||
// makeKeyAndOrderFront assumes ownership of our window reference.
|
// makeKeyAndOrderFront assumes ownership of our window reference.
|
||||||
C.makeKeyAndOrderFront(window)
|
C.makeKeyAndOrderFront(window)
|
||||||
layer := C.layerForView(w.view)
|
|
||||||
w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
|
||||||
})
|
})
|
||||||
return <-errch
|
<-res
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOSWindow() (*window, error) {
|
func (w *window) init() error {
|
||||||
view := C.gio_createView()
|
view := C.gio_createView()
|
||||||
if view == 0 {
|
if view == 0 {
|
||||||
return nil, errors.New("newOSWindows: failed to create view")
|
return errors.New("newOSWindow: failed to create view")
|
||||||
}
|
}
|
||||||
scale := float32(C.getViewBackingScale(view))
|
scale := float32(C.getViewBackingScale(view))
|
||||||
w := &window{
|
w.scale = scale
|
||||||
view: view,
|
|
||||||
scale: scale,
|
|
||||||
redraw: make(chan struct{}, 1),
|
|
||||||
}
|
|
||||||
dl, err := newDisplayLink(func() {
|
dl, err := newDisplayLink(func() {
|
||||||
select {
|
select {
|
||||||
case w.redraw <- struct{}{}:
|
case w.redraw <- struct{}{}:
|
||||||
@@ -904,20 +997,19 @@ func newOSWindow() (*window, error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.runOnMain(func() {
|
w.runOnMain(func() {
|
||||||
C.setNeedsDisplay(w.view)
|
if w.visible {
|
||||||
|
C.setNeedsDisplay(w.view)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
w.displayLink = dl
|
w.displayLink = dl
|
||||||
if err != nil {
|
if err != nil {
|
||||||
C.CFRelease(view)
|
C.CFRelease(view)
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
insertView(view, w)
|
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||||
return w, nil
|
w.view = view
|
||||||
}
|
return nil
|
||||||
|
|
||||||
func osMain() {
|
|
||||||
C.gio_main()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertKey(k rune) (key.Name, bool) {
|
func convertKey(k rune) (key.Name, bool) {
|
||||||
@@ -1004,4 +1096,14 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
|
|||||||
return kmods
|
return kmods
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (w *window) wakeup() {
|
||||||
|
runOnMain(func() {
|
||||||
|
w.loop.Wakeup()
|
||||||
|
if w != mainThreadWindow {
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AppKitViewEvent) implementsViewEvent() {}
|
||||||
|
func (AppKitViewEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+311
-60
@@ -14,40 +14,50 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
|||||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||||
|
@property uintptr_t handle;
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GioWindowDelegate
|
@implementation GioWindowDelegate
|
||||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onHide(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onShow(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onFullscreen(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onWindowed((__bridge CFTypeRef)window.contentView);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onWindowed(view.handle);
|
||||||
}
|
}
|
||||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
GioView *view = (GioView *)window.contentView;
|
||||||
gio_onChangeScreen(view, dispID);
|
gio_onChangeScreen(view.handle, dispID);
|
||||||
}
|
}
|
||||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onFocus(view.handle, 1);
|
||||||
}
|
}
|
||||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||||
NSWindow *window = (NSWindow *)[notification object];
|
NSWindow *window = (NSWindow *)[notification object];
|
||||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
GioView *view = (GioView *)window.contentView;
|
||||||
|
gio_onFocus(view.handle, 0);
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||||
if (!event.hasPreciseScrollingDeltas) {
|
if (!event.hasPreciseScrollingDeltas) {
|
||||||
// dx and dy are in rows and columns.
|
// dx and dy are in rows and columns.
|
||||||
@@ -56,10 +66,213 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
}
|
}
|
||||||
// Origin is in the lower left corner. Convert to upper left.
|
// Origin is in the lower left corner. Convert to upper left.
|
||||||
CGFloat height = view.bounds.size.height;
|
CGFloat height = view.bounds.size.height;
|
||||||
gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
@interface GioApplication: NSApplication
|
||||||
|
@end
|
||||||
|
|
||||||
|
// Variables for tracking resizes.
|
||||||
|
static struct {
|
||||||
|
NSPoint dir;
|
||||||
|
NSEvent *lastMouseDown;
|
||||||
|
NSPoint off;
|
||||||
|
} resizeState = {};
|
||||||
|
|
||||||
|
static NSBitmapImageRep *nsImageBitmap(NSImage *img) {
|
||||||
|
NSArray<NSImageRep *> *reps = img.representations;
|
||||||
|
if ([reps count] == 0) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSImageRep *rep = reps[0];
|
||||||
|
if (![rep isKindOfClass:[NSBitmapImageRep class]]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return (NSBitmapImageRep *)rep;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSCursor *lookupPrivateNSCursor(SEL name) {
|
||||||
|
if (![NSCursor respondsToSelector:name]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
id obj = [NSCursor performSelector:name];
|
||||||
|
if (![obj isKindOfClass:[NSCursor class]]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return (NSCursor *)obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL isEqualNSCursor(NSCursor *c1, SEL name2) {
|
||||||
|
NSCursor *c2 = lookupPrivateNSCursor(name2);
|
||||||
|
if (c2 == nil || !NSEqualPoints(c1.hotSpot, c2.hotSpot)) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSImage *img1 = c1.image;
|
||||||
|
NSImage *img2 = c2.image;
|
||||||
|
if (!NSEqualSizes(img1.size, img2.size)) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSBitmapImageRep *bit1 = nsImageBitmap(img1);
|
||||||
|
NSBitmapImageRep *bit2 = nsImageBitmap(img2);
|
||||||
|
if (bit1 == nil || bit2 == nil) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
NSInteger n1 = bit1.numberOfPlanes*bit1.bytesPerPlane;
|
||||||
|
NSInteger n2 = bit1.numberOfPlanes*bit1.bytesPerPlane;
|
||||||
|
if (n1 != n2) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
if (memcmp(bit1.bitmapData, bit2.bitmapData, n1) != 0) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@implementation GioApplication
|
||||||
|
- (NSEvent *)nextEventMatchingMask:(NSEventMask)mask
|
||||||
|
untilDate:(NSDate *)expiration
|
||||||
|
inMode:(NSRunLoopMode)mode
|
||||||
|
dequeue:(BOOL)deqFlag {
|
||||||
|
if ([mode isEqualToString:NSEventTrackingRunLoopMode]) {
|
||||||
|
NSEvent *l = resizeState.lastMouseDown;
|
||||||
|
if (l != nil) {
|
||||||
|
//lastMouseDown = nil;
|
||||||
|
NSCursor *cur = [NSCursor currentSystemCursor];
|
||||||
|
NSPoint dir = {};
|
||||||
|
NSPoint off = {};
|
||||||
|
NSSize wsz = [l window].frame.size;
|
||||||
|
NSPoint center = NSMakePoint(wsz.width/2, wsz.height/2);
|
||||||
|
NSPoint p = [l locationInWindow];
|
||||||
|
if (p.x >= center.x) {
|
||||||
|
dir.x = 1;
|
||||||
|
off.x = p.x - wsz.width;
|
||||||
|
} else {
|
||||||
|
dir.x = -1;
|
||||||
|
off.x = p.x;
|
||||||
|
}
|
||||||
|
if (p.y >= center.y) {
|
||||||
|
dir.y = 1;
|
||||||
|
off.y = p.y - wsz.height;
|
||||||
|
} else {
|
||||||
|
dir.y = -1;
|
||||||
|
off.y = p.y;
|
||||||
|
}
|
||||||
|
// The button down coordinate distinguish the four quadrants. Use the
|
||||||
|
// cursor image to determine the precise direction.
|
||||||
|
SEL nw = @selector(_windowResizeNorthWestCursor);
|
||||||
|
SEL n = @selector(_windowResizeNorthCursor);
|
||||||
|
SEL ne = @selector(_windowResizeNorthEastCursor);
|
||||||
|
SEL e = @selector(_windowResizeEastCursor);
|
||||||
|
SEL se = @selector(_windowResizeSouthEastCursor);
|
||||||
|
SEL s = @selector(_windowResizeSouthCursor);
|
||||||
|
SEL sw = @selector(_windowResizeSouthWestCursor);
|
||||||
|
SEL w = @selector(_windowResizeWestCursor);
|
||||||
|
SEL ns = @selector(_windowResizeNorthSouthCursor);
|
||||||
|
SEL ew = @selector(_windowResizeEastWestCursor);
|
||||||
|
SEL nwse = @selector(_windowResizeNorthWestSouthEastCursor);
|
||||||
|
SEL nesw = @selector(_windowResizeNorthEastSouthWestCursor);
|
||||||
|
BOOL match = YES;
|
||||||
|
if (dir.x != 0 && (isEqualNSCursor(cur, ew) || isEqualNSCursor(cur, w) || isEqualNSCursor(cur, e))) {
|
||||||
|
dir.y = 0;
|
||||||
|
}
|
||||||
|
if (dir.y != 0 && (isEqualNSCursor(cur, ns) || isEqualNSCursor(cur, s) || isEqualNSCursor(cur, n))) {
|
||||||
|
dir.x = 0;
|
||||||
|
}
|
||||||
|
// If none of the cursors matched, we may deduce that the resize
|
||||||
|
// direction is one of the corners. However, to ensure that at least
|
||||||
|
// one cursor matches, check the corner cursors.
|
||||||
|
if (dir.x == 1 && dir.y == 1) {
|
||||||
|
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, sw)) {
|
||||||
|
dir = NSZeroPoint;
|
||||||
|
}
|
||||||
|
} else if (dir.x == 1 && dir.y == -1) {
|
||||||
|
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, nw)) {
|
||||||
|
dir = NSZeroPoint;
|
||||||
|
}
|
||||||
|
} else if (dir.x == -1 && dir.y == 1) {
|
||||||
|
if (!isEqualNSCursor(cur, nwse) && !isEqualNSCursor(cur, se)) {
|
||||||
|
dir = NSZeroPoint;
|
||||||
|
}
|
||||||
|
} else if (dir.x == -1 && dir.y == -1) {
|
||||||
|
if (!isEqualNSCursor(cur, nesw) && !isEqualNSCursor(cur, ne)) {
|
||||||
|
dir = NSZeroPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!NSEqualPoints(dir, NSZeroPoint)) {
|
||||||
|
NSEvent *cancel = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
|
||||||
|
location:l.locationInWindow
|
||||||
|
modifierFlags:l.modifierFlags
|
||||||
|
timestamp:l.timestamp
|
||||||
|
windowNumber:l.windowNumber
|
||||||
|
context:l.context
|
||||||
|
eventNumber:l.eventNumber
|
||||||
|
clickCount:l.clickCount
|
||||||
|
pressure:l.pressure];
|
||||||
|
resizeState.off = off;
|
||||||
|
resizeState.dir = dir;
|
||||||
|
return cancel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:deqFlag];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GioWindow: NSWindow
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation GioWindow
|
||||||
|
- (void)sendEvent:(NSEvent *)evt {
|
||||||
|
if (evt.type == NSEventTypeLeftMouseDown) {
|
||||||
|
resizeState.lastMouseDown = evt;
|
||||||
|
}
|
||||||
|
NSPoint dir = resizeState.dir;
|
||||||
|
if (NSEqualPoints(dir, NSZeroPoint)) {
|
||||||
|
[super sendEvent:evt];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (evt.type) {
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
case NSEventTypeLeftMouseUp:
|
||||||
|
resizeState.dir = NSZeroPoint;
|
||||||
|
resizeState.lastMouseDown = nil;
|
||||||
|
return;
|
||||||
|
case NSEventTypeLeftMouseDragged:
|
||||||
|
// Ok to proceed.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
NSPoint loc = evt.locationInWindow;
|
||||||
|
NSPoint off = resizeState.off;
|
||||||
|
loc.x -= off.x;
|
||||||
|
loc.y -= off.y;
|
||||||
|
NSRect frame = [self frame];
|
||||||
|
NSSize min = [self minSize];
|
||||||
|
NSSize max = [self maxSize];
|
||||||
|
CGFloat width = frame.size.width;
|
||||||
|
if (dir.x > 0) {
|
||||||
|
width = loc.x;
|
||||||
|
} else if (dir.x < 0) {
|
||||||
|
width -= loc.x;
|
||||||
|
}
|
||||||
|
width = MIN(max.width, MAX(min.width, width));
|
||||||
|
if (dir.x < 0) {
|
||||||
|
frame.origin.x += frame.size.width - width;
|
||||||
|
}
|
||||||
|
frame.size.width = width;
|
||||||
|
CGFloat height = frame.size.height;
|
||||||
|
if (dir.y > 0) {
|
||||||
|
height = loc.y;
|
||||||
|
} else if (dir.y < 0) {
|
||||||
|
height -= loc.y;
|
||||||
|
}
|
||||||
|
height = MIN(max.height, MAX(min.height, height));
|
||||||
|
if (dir.y < 0) {
|
||||||
|
frame.origin.y += frame.size.height - height;
|
||||||
|
}
|
||||||
|
frame.size.height = height;
|
||||||
|
[self setFrame:frame display:YES animate:NO];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GioView
|
@implementation GioView
|
||||||
@@ -70,11 +283,11 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||||
// Don't know why.
|
// Don't know why.
|
||||||
- (void)drawRect:(NSRect)r {
|
- (void)drawRect:(NSRect)r {
|
||||||
gio_onDraw((__bridge CFTypeRef)self);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (void)displayLayer:(CALayer *)layer {
|
- (void)displayLayer:(CALayer *)layer {
|
||||||
layer.contentsScale = self.window.backingScaleFactor;
|
layer.contentsScale = self.window.backingScaleFactor;
|
||||||
gio_onDraw((__bridge CFTypeRef)self);
|
gio_onDraw(self.handle);
|
||||||
}
|
}
|
||||||
- (CALayer *)makeBackingLayer {
|
- (CALayer *)makeBackingLayer {
|
||||||
CALayer *layer = gio_layerFactory();
|
CALayer *layer = gio_layerFactory();
|
||||||
@@ -82,9 +295,7 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
- (void)viewDidMoveToWindow {
|
- (void)viewDidMoveToWindow {
|
||||||
if (self.window == nil) {
|
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||||
gio_onClose((__bridge CFTypeRef)self);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
- (void)mouseDown:(NSEvent *)event {
|
- (void)mouseDown:(NSEvent *)event {
|
||||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||||
@@ -124,32 +335,31 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
- (void)keyDown:(NSEvent *)event {
|
- (void)keyDown:(NSEvent *)event {
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||||
}
|
}
|
||||||
- (void)keyUp:(NSEvent *)event {
|
- (void)keyUp:(NSEvent *)event {
|
||||||
NSString *keys = [event charactersIgnoringModifiers];
|
NSString *keys = [event charactersIgnoringModifiers];
|
||||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||||
}
|
}
|
||||||
- (void)insertText:(id)string {
|
- (void)insertText:(id)string {
|
||||||
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
|
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||||
}
|
}
|
||||||
- (void)doCommandBySelector:(SEL)sel {
|
- (void)doCommandBySelector:(SEL)sel {
|
||||||
// Don't pass commands up the responder chain.
|
// Don't pass commands up the responder chain.
|
||||||
// They will end up in a beep.
|
// They will end up in a beep.
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)hasMarkedText {
|
- (BOOL)hasMarkedText {
|
||||||
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
|
int res = gio_hasMarkedText(self.handle);
|
||||||
return res ? YES : NO;
|
return res ? YES : NO;
|
||||||
}
|
}
|
||||||
- (NSRange)markedRange {
|
- (NSRange)markedRange {
|
||||||
return gio_markedRange((__bridge CFTypeRef)self);
|
return gio_markedRange(self.handle);
|
||||||
}
|
}
|
||||||
- (NSRange)selectedRange {
|
- (NSRange)selectedRange {
|
||||||
return gio_selectedRange((__bridge CFTypeRef)self);
|
return gio_selectedRange(self.handle);
|
||||||
}
|
}
|
||||||
- (void)unmarkText {
|
- (void)unmarkText {
|
||||||
gio_unmarkText((__bridge CFTypeRef)self);
|
gio_unmarkText(self.handle);
|
||||||
}
|
}
|
||||||
- (void)setMarkedText:(id)string
|
- (void)setMarkedText:(id)string
|
||||||
selectedRange:(NSRange)selRange
|
selectedRange:(NSRange)selRange
|
||||||
@@ -161,14 +371,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
|
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||||
actualRange:(NSRangePointer)actualRange {
|
actualRange:(NSRangePointer)actualRange {
|
||||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
|
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||||
}
|
}
|
||||||
- (void)insertText:(id)string
|
- (void)insertText:(id)string
|
||||||
@@ -180,17 +390,26 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
|||||||
} else {
|
} else {
|
||||||
str = string;
|
str = string;
|
||||||
}
|
}
|
||||||
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
|
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||||
}
|
}
|
||||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||||
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
|
return gio_characterIndexForPoint(self.handle, p);
|
||||||
}
|
}
|
||||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||||
actualRange:(NSRangePointer)actual {
|
actualRange:(NSRangePointer)actual {
|
||||||
NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual);
|
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
||||||
r = [self convertRect:r toView:nil];
|
r = [self convertRect:r toView:nil];
|
||||||
return [[self window] convertRectToScreen:r];
|
return [[self window] convertRectToScreen:r];
|
||||||
}
|
}
|
||||||
|
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||||
|
gio_onShow(self.handle);
|
||||||
|
}
|
||||||
|
- (void)applicationDidHide:(NSNotification *)notification {
|
||||||
|
gio_onHide(self.handle);
|
||||||
|
}
|
||||||
|
- (void)dealloc {
|
||||||
|
gio_onDestroy(self.handle);
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// Delegates are weakly referenced from their peers. Nothing
|
// Delegates are weakly referenced from their peers. Nothing
|
||||||
@@ -240,15 +459,12 @@ void gio_showCursor() {
|
|||||||
|
|
||||||
// some cursors are not public, this tries to use a private cursor
|
// some cursors are not public, this tries to use a private cursor
|
||||||
// and uses fallback when the use of private cursor fails.
|
// and uses fallback when the use of private cursor fails.
|
||||||
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||||
if ([NSCursor respondsToSelector:cursorName]) {
|
NSCursor *cur = lookupPrivateNSCursor(cursorName);
|
||||||
id object = [NSCursor performSelector:cursorName];
|
if (cur == nil) {
|
||||||
if ([object isKindOfClass:[NSCursor class]]) {
|
cur = fallback;
|
||||||
[(NSCursor*)object set];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[fallback set];
|
[cur set];
|
||||||
}
|
}
|
||||||
|
|
||||||
void gio_setCursor(NSUInteger curID) {
|
void gio_setCursor(NSUInteger curID) {
|
||||||
@@ -272,7 +488,7 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
break;
|
break;
|
||||||
case 6: // pointer.CursorAllScroll
|
case 6: // pointer.CursorAllScroll
|
||||||
// For some reason, using _moveCursor fails on Monterey.
|
// For some reason, using _moveCursor fails on Monterey.
|
||||||
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||||
[NSCursor.arrowCursor set];
|
[NSCursor.arrowCursor set];
|
||||||
break;
|
break;
|
||||||
case 7: // pointer.CursorColResize
|
case 7: // pointer.CursorColResize
|
||||||
@@ -282,33 +498,31 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 9: // pointer.CursorGrab
|
case 9: // pointer.CursorGrab
|
||||||
// [NSCursor.openHandCursor set];
|
[NSCursor.openHandCursor set];
|
||||||
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
|
|
||||||
break;
|
break;
|
||||||
case 10: // pointer.CursorGrabbing
|
case 10: // pointer.CursorGrabbing
|
||||||
// [NSCursor.closedHandCursor set];
|
[NSCursor.closedHandCursor set];
|
||||||
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
|
|
||||||
break;
|
break;
|
||||||
case 11: // pointer.CursorNotAllowed
|
case 11: // pointer.CursorNotAllowed
|
||||||
[NSCursor.operationNotAllowedCursor set];
|
[NSCursor.operationNotAllowedCursor set];
|
||||||
break;
|
break;
|
||||||
case 12: // pointer.CursorWait
|
case 12: // pointer.CursorWait
|
||||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 13: // pointer.CursorProgress
|
case 13: // pointer.CursorProgress
|
||||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||||
break;
|
break;
|
||||||
case 14: // pointer.CursorNorthWestResize
|
case 14: // pointer.CursorNorthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 15: // pointer.CursorNorthEastResize
|
case 15: // pointer.CursorNorthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 16: // pointer.CursorSouthWestResize
|
case 16: // pointer.CursorSouthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 17: // pointer.CursorSouthEastResize
|
case 17: // pointer.CursorSouthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 18: // pointer.CursorNorthSouthResize
|
case 18: // pointer.CursorNorthSouthResize
|
||||||
[NSCursor.resizeUpDownCursor set];
|
[NSCursor.resizeUpDownCursor set];
|
||||||
@@ -329,10 +543,10 @@ void gio_setCursor(NSUInteger curID) {
|
|||||||
[NSCursor.resizeDownCursor set];
|
[NSCursor.resizeDownCursor set];
|
||||||
break;
|
break;
|
||||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
[NSCursor.arrowCursor set];
|
[NSCursor.arrowCursor set];
|
||||||
@@ -349,7 +563,7 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
|||||||
NSMiniaturizableWindowMask |
|
NSMiniaturizableWindowMask |
|
||||||
NSClosableWindowMask;
|
NSClosableWindowMask;
|
||||||
|
|
||||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
GioWindow* window = [[GioWindow alloc] initWithContentRect:rect
|
||||||
styleMask:styleMask
|
styleMask:styleMask
|
||||||
backing:NSBackingStoreBuffered
|
backing:NSBackingStoreBuffered
|
||||||
defer:NO];
|
defer:NO];
|
||||||
@@ -374,27 +588,48 @@ CFTypeRef gio_createView(void) {
|
|||||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||||
view.wantsLayer = YES;
|
view.wantsLayer = YES;
|
||||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||||
|
selector:@selector(applicationWillUnhide:)
|
||||||
|
name:NSApplicationWillUnhideNotification
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||||
|
selector:@selector(applicationDidHide:)
|
||||||
|
name:NSApplicationDidHideNotification
|
||||||
|
object:nil];
|
||||||
return CFBridgingRetain(view);
|
return CFBridgingRetain(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||||
|
@autoreleasepool {
|
||||||
|
GioView *v = (__bridge GioView *)viewRef;
|
||||||
|
v.handle = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@implementation GioAppDelegate
|
@implementation GioAppDelegate
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
[NSApp activateIgnoringOtherApps:YES];
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
gio_onFinishLaunching();
|
// Force the [NSApp run] call to return.
|
||||||
}
|
[NSApp stop:nil];
|
||||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
||||||
gio_onAppHide();
|
location:NSZeroPoint
|
||||||
}
|
modifierFlags:0
|
||||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
timestamp:0
|
||||||
gio_onAppShow();
|
windowNumber:0
|
||||||
|
context:nil
|
||||||
|
subtype:0
|
||||||
|
data1:0
|
||||||
|
data2:0];
|
||||||
|
[NSApp postEvent:dummy atStart:YES];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
void gio_main() {
|
void gio_initApp() {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
[NSApplication sharedApplication];
|
[GioApplication sharedApplication];
|
||||||
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
||||||
[NSApp setDelegate:del];
|
[NSApp setDelegate:del];
|
||||||
|
|
||||||
@@ -416,6 +651,22 @@ void gio_main() {
|
|||||||
|
|
||||||
globalWindowDel = [[GioWindowDelegate alloc] init];
|
globalWindowDel = [[GioWindowDelegate alloc] init];
|
||||||
|
|
||||||
|
// Runs until stopped by applicationDidFinishLaunching.
|
||||||
[NSApp run];
|
[NSApp run];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gio_wakeupMainThread(void) {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSEvent *dummy = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
||||||
|
location:NSZeroPoint
|
||||||
|
modifierFlags:0
|
||||||
|
timestamp:0
|
||||||
|
windowNumber:0
|
||||||
|
context:nil
|
||||||
|
subtype:0
|
||||||
|
data1:0
|
||||||
|
data2:0];
|
||||||
|
[NSApp postEvent:dummy atStart:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+29
-16
@@ -9,16 +9,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ViewEvent provides handles to the underlying window objects for the
|
|
||||||
// current display protocol.
|
|
||||||
type ViewEvent interface {
|
|
||||||
implementsViewEvent()
|
|
||||||
ImplementsEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
type X11ViewEvent struct {
|
type X11ViewEvent struct {
|
||||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||||
Display unsafe.Pointer
|
Display unsafe.Pointer
|
||||||
@@ -39,17 +33,13 @@ type WaylandViewEvent struct {
|
|||||||
func (WaylandViewEvent) implementsViewEvent() {}
|
func (WaylandViewEvent) implementsViewEvent() {}
|
||||||
func (WaylandViewEvent) ImplementsEvent() {}
|
func (WaylandViewEvent) ImplementsEvent() {}
|
||||||
|
|
||||||
func osMain() {
|
|
||||||
select {}
|
|
||||||
}
|
|
||||||
|
|
||||||
type windowDriver func(*callbacks, []Option) error
|
type windowDriver func(*callbacks, []Option) error
|
||||||
|
|
||||||
// Instead of creating files with build tags for each combination of wayland +/- x11
|
// Instead of creating files with build tags for each combination of wayland +/- x11
|
||||||
// let each driver initialize these variables with their own version of createWindow.
|
// let each driver initialize these variables with their own version of createWindow.
|
||||||
var wlDriver, x11Driver windowDriver
|
var wlDriver, x11Driver windowDriver
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
func newWindow(window *callbacks, options []Option) {
|
||||||
var errFirst error
|
var errFirst error
|
||||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
@@ -57,16 +47,39 @@ func newWindow(window *callbacks, options []Option) error {
|
|||||||
}
|
}
|
||||||
err := d(window, options)
|
err := d(window, options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
if errFirst == nil {
|
if errFirst == nil {
|
||||||
errFirst = err
|
errFirst = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errFirst != nil {
|
window.SetDriver(&dummyDriver{
|
||||||
return errFirst
|
win: window,
|
||||||
|
wakeups: make(chan event.Event, 1),
|
||||||
|
})
|
||||||
|
if errFirst == nil {
|
||||||
|
errFirst = errors.New("app: no window driver available")
|
||||||
|
}
|
||||||
|
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyDriver struct {
|
||||||
|
win *callbacks
|
||||||
|
wakeups chan event.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyDriver) Event() event.Event {
|
||||||
|
if e, ok := d.win.nextEvent(); ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return <-d.wakeups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyDriver) Invalidate() {
|
||||||
|
select {
|
||||||
|
case d.wakeups <- wakeupEvent{}:
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
return errors.New("app: no window driver available")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||||
|
|||||||
+165
-106
@@ -15,6 +15,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,10 +26,12 @@ import (
|
|||||||
"gioui.org/app/internal/xkb"
|
"gioui.org/app/internal/xkb"
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/internal/fling"
|
"gioui.org/internal/fling"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +100,9 @@ type wlDisplay struct {
|
|||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
|
|
||||||
repeat repeatState
|
repeat repeatState
|
||||||
|
poller poller
|
||||||
|
readClipClose chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type wlSeat struct {
|
type wlSeat struct {
|
||||||
@@ -137,7 +142,7 @@ type repeatState struct {
|
|||||||
delay time.Duration
|
delay time.Duration
|
||||||
|
|
||||||
key uint32
|
key uint32
|
||||||
win *callbacks
|
win *window
|
||||||
stopC chan struct{}
|
stopC chan struct{}
|
||||||
|
|
||||||
start time.Duration
|
start time.Duration
|
||||||
@@ -194,12 +199,10 @@ type window struct {
|
|||||||
dir f32.Point
|
dir f32.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
stage Stage
|
configured bool
|
||||||
dead bool
|
|
||||||
lastFrameCallback *C.struct_wl_callback
|
lastFrameCallback *C.struct_wl_callback
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
redraw bool
|
|
||||||
// The most recent configure serial waiting to be ack'ed.
|
// The most recent configure serial waiting to be ack'ed.
|
||||||
serial C.uint32_t
|
serial C.uint32_t
|
||||||
scale int
|
scale int
|
||||||
@@ -212,6 +215,10 @@ type window struct {
|
|||||||
clipReads chan transfer.DataEvent
|
clipReads chan transfer.DataEvent
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
|
||||||
|
// invMu avoids the race between the destruction of disp and
|
||||||
|
// Invalidate waking it up.
|
||||||
|
invMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type poller struct {
|
type poller struct {
|
||||||
@@ -260,25 +267,17 @@ func newWLWindow(callbacks *callbacks, options []Option) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.w = callbacks
|
w.w = callbacks
|
||||||
go func() {
|
w.w.SetDriver(w)
|
||||||
defer d.destroy()
|
|
||||||
defer w.destroy()
|
|
||||||
|
|
||||||
w.w.SetDriver(w)
|
// Finish and commit setup from createNativeWindow.
|
||||||
|
w.Configure(options)
|
||||||
|
w.draw(true)
|
||||||
|
C.wl_surface_commit(w.surf)
|
||||||
|
|
||||||
// Finish and commit setup from createNativeWindow.
|
w.ProcessEvent(WaylandViewEvent{
|
||||||
w.Configure(options)
|
Display: unsafe.Pointer(w.display()),
|
||||||
C.wl_surface_commit(w.surf)
|
Surface: unsafe.Pointer(w.surf),
|
||||||
|
})
|
||||||
w.w.Event(WaylandViewEvent{
|
|
||||||
Display: unsafe.Pointer(w.display()),
|
|
||||||
Surface: unsafe.Pointer(w.surf),
|
|
||||||
})
|
|
||||||
|
|
||||||
err := w.loop()
|
|
||||||
w.w.Event(WaylandViewEvent{})
|
|
||||||
w.w.Event(DestroyEvent{Err: err})
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,15 +548,15 @@ func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
|
|||||||
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.serial = serial
|
w.serial = serial
|
||||||
w.redraw = true
|
|
||||||
C.xdg_surface_ack_configure(wmSurf, serial)
|
C.xdg_surface_ack_configure(wmSurf, serial)
|
||||||
w.setStage(StageRunning)
|
w.configured = true
|
||||||
|
w.draw(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelClose
|
//export gio_onToplevelClose
|
||||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
w.dead = true
|
w.close(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onToplevelConfigure
|
//export gio_onToplevelConfigure
|
||||||
@@ -586,8 +585,8 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
|
|||||||
} else {
|
} else {
|
||||||
w.size.Y += int(w.config.decoHeight)
|
w.size.Y += int(w.config.decoHeight)
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.redraw = true
|
w.draw(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +644,7 @@ func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *
|
|||||||
if w.config.Mode == Minimized {
|
if w.config.Mode == Minimized {
|
||||||
// Minimized window got brought back up: it is no longer so.
|
// Minimized window got brought back up: it is no longer so.
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,7 +789,7 @@ func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Press,
|
Kind: pointer.Press,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
@@ -806,7 +805,7 @@ func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.ui
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
w := s.touchFoci[id]
|
w := s.touchFoci[id]
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Release,
|
Kind: pointer.Release,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
@@ -824,7 +823,7 @@ func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastTouch,
|
Position: w.lastTouch,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
@@ -843,7 +842,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
|||||||
s := callbackLoad(data).(*wlSeat)
|
s := callbackLoad(data).(*wlSeat)
|
||||||
for id, w := range s.touchFoci {
|
for id, w := range s.touchFoci {
|
||||||
delete(s.touchFoci, id)
|
delete(s.touchFoci, id)
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
Source: pointer.Touch,
|
Source: pointer.Touch,
|
||||||
})
|
})
|
||||||
@@ -869,7 +868,7 @@ func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.ui
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
if w.inCompositor {
|
if w.inCompositor {
|
||||||
w.inCompositor = false
|
w.inCompositor = false
|
||||||
w.w.Event(pointer.Event{Kind: pointer.Cancel})
|
w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,7 +929,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
|||||||
}
|
}
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
w.resetFling()
|
w.resetFling()
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1018,8 +1017,11 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) ReadClipboard() {
|
func (w *window) ReadClipboard() {
|
||||||
|
if w.disp.readClipClose != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.disp.readClipClose = make(chan struct{})
|
||||||
r, err := w.disp.readClipboard()
|
r, err := w.disp.readClipboard()
|
||||||
// Send empty responses on unavailable clipboards or errors.
|
|
||||||
if r == nil || err != nil {
|
if r == nil || err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1027,13 +1029,17 @@ func (w *window) ReadClipboard() {
|
|||||||
go func() {
|
go func() {
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
data, _ := io.ReadAll(r)
|
data, _ := io.ReadAll(r)
|
||||||
w.clipReads <- transfer.DataEvent{
|
e := transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(bytes.NewReader(data))
|
return io.NopCloser(bytes.NewReader(data))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
w.Wakeup()
|
select {
|
||||||
|
case w.clipReads <- e:
|
||||||
|
w.disp.wakeup()
|
||||||
|
case <-w.disp.readClipClose:
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,8 +1102,7 @@ func (w *window) Configure(options []Option) {
|
|||||||
w.config.MaxSize = cnf.MaxSize
|
w.config.MaxSize = cnf.MaxSize
|
||||||
w.setWindowConstraints()
|
w.setWindowConstraints()
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
w.redraw = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setWindowConstraints() {
|
func (w *window) setWindowConstraints() {
|
||||||
@@ -1134,7 +1139,7 @@ func (w *window) Perform(actions system.Action) {
|
|||||||
walkActions(actions, func(action system.Action) {
|
walkActions(actions, func(action system.Action) {
|
||||||
switch action {
|
switch action {
|
||||||
case system.ActionClose:
|
case system.ActionClose:
|
||||||
w.dead = true
|
w.close(nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1222,8 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||||
s.keyboardFocus = w
|
s.keyboardFocus = w
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardLeave
|
//export gio_onKeyboardLeave
|
||||||
@@ -1226,7 +1232,8 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
|||||||
s.serial = serial
|
s.serial = serial
|
||||||
s.disp.repeat.Stop(0)
|
s.disp.repeat.Stop(0)
|
||||||
w := s.keyboardFocus
|
w := s.keyboardFocus
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onKeyboardKey
|
//export gio_onKeyboardKey
|
||||||
@@ -1244,7 +1251,7 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
||||||
@@ -1279,7 +1286,7 @@ func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) {
|
|||||||
r.now = 0
|
r.now = 0
|
||||||
r.stopC = stopC
|
r.stopC = stopC
|
||||||
r.key = keyCode
|
r.key = keyCode
|
||||||
r.win = w.w
|
r.win = w
|
||||||
rate, delay := r.rate, r.delay
|
rate, delay := r.rate, r.delay
|
||||||
go func() {
|
go func() {
|
||||||
timer := time.NewTimer(delay)
|
timer := time.NewTimer(delay)
|
||||||
@@ -1337,9 +1344,9 @@ func (r *repeatState) Repeat(d *wlDisplay) {
|
|||||||
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
||||||
if ee, ok := e.(key.EditEvent); ok {
|
if ee, ok := e.(key.EditEvent); ok {
|
||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
r.win.EditorInsert(ee.Text)
|
r.win.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
r.win.Event(e)
|
r.win.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.last += delay
|
r.last += delay
|
||||||
@@ -1352,28 +1359,76 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
|
|||||||
w := callbackLoad(data).(*window)
|
w := callbackLoad(data).(*window)
|
||||||
if w.lastFrameCallback == callback {
|
if w.lastFrameCallback == callback {
|
||||||
w.lastFrameCallback = nil
|
w.lastFrameCallback = nil
|
||||||
|
w.draw(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) loop() error {
|
func (w *window) close(err error) {
|
||||||
var p poller
|
w.ProcessEvent(WaylandViewEvent{})
|
||||||
for {
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
if err := w.disp.dispatch(&p); err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
func (w *window) dispatch() {
|
||||||
select {
|
if w.disp == nil {
|
||||||
case e := <-w.clipReads:
|
<-w.wakeups
|
||||||
w.w.Event(e)
|
w.w.Invalidate()
|
||||||
case <-w.wakeups:
|
return
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
if w.dead {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
w.draw()
|
|
||||||
}
|
}
|
||||||
return nil
|
if err := w.disp.dispatch(); err != nil {
|
||||||
|
w.close(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case e := <-w.clipReads:
|
||||||
|
w.disp.readClipClose = nil
|
||||||
|
w.ProcessEvent(e)
|
||||||
|
case <-w.wakeups:
|
||||||
|
w.w.Invalidate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
w.dispatch()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, destroy := evt.(DestroyEvent); destroy {
|
||||||
|
w.destroy()
|
||||||
|
w.invMu.Lock()
|
||||||
|
w.disp.destroy()
|
||||||
|
w.disp = nil
|
||||||
|
w.invMu.Unlock()
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
select {
|
||||||
|
case w.wakeups <- struct{}{}:
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
|
if w.disp != nil {
|
||||||
|
w.disp.wakeup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindDataDevice initializes the dataDev field if and only if both
|
// bindDataDevice initializes the dataDev field if and only if both
|
||||||
@@ -1389,13 +1444,21 @@ func (d *wlDisplay) bindDataDevice() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *wlDisplay) dispatch(p *poller) error {
|
func (d *wlDisplay) dispatch() error {
|
||||||
|
// wl_display_prepare_read records the current thread for
|
||||||
|
// use in wl_display_read_events or wl_display_cancel_events.
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
dispfd := C.wl_display_get_fd(d.disp)
|
dispfd := C.wl_display_get_fd(d.disp)
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
pollfds := append(p.pollfds[:0],
|
pollfds := append(d.poller.pollfds[:0],
|
||||||
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
)
|
)
|
||||||
|
for C.wl_display_prepare_read(d.disp) != 0 {
|
||||||
|
C.wl_display_dispatch_pending(d.disp)
|
||||||
|
}
|
||||||
dispFd := &pollfds[0]
|
dispFd := &pollfds[0]
|
||||||
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
||||||
if err != syscall.EAGAIN {
|
if err != syscall.EAGAIN {
|
||||||
@@ -1406,11 +1469,25 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
|||||||
dispFd.Events |= syscall.POLLOUT
|
dispFd.Events |= syscall.POLLOUT
|
||||||
}
|
}
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
return fmt.Errorf("wayland: poll failed: %v", err)
|
return fmt.Errorf("wayland: poll failed: %v", err)
|
||||||
}
|
}
|
||||||
|
if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
|
return errors.New("wayland: display file descriptor gone")
|
||||||
|
}
|
||||||
|
// Handle events.
|
||||||
|
if dispFd.Revents&syscall.POLLIN != 0 {
|
||||||
|
if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
|
||||||
|
return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
|
||||||
|
}
|
||||||
|
C.wl_display_dispatch_pending(d.disp)
|
||||||
|
} else {
|
||||||
|
C.wl_display_cancel_read(d.disp)
|
||||||
|
}
|
||||||
// Clear notifications.
|
// Clear notifications.
|
||||||
for {
|
for {
|
||||||
_, err := syscall.Read(d.notify.read, p.buf[:])
|
_, err := syscall.Read(d.notify.read, d.poller.buf[:])
|
||||||
if err == syscall.EAGAIN {
|
if err == syscall.EAGAIN {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1418,29 +1495,15 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
|||||||
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle events
|
|
||||||
switch {
|
|
||||||
case dispFd.Revents&syscall.POLLIN != 0:
|
|
||||||
if ret, err := C.wl_display_dispatch(d.disp); ret < 0 {
|
|
||||||
return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err)
|
|
||||||
}
|
|
||||||
case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
|
||||||
return errors.New("wayland: display file descriptor gone")
|
|
||||||
}
|
|
||||||
d.repeat.Repeat(d)
|
d.repeat.Repeat(d)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
|
||||||
select {
|
|
||||||
case w.wakeups <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
w.disp.wakeup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) SetAnimating(anim bool) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
w.animating = anim
|
w.animating = anim
|
||||||
|
if anim {
|
||||||
|
w.draw(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wakeup wakes up the event loop through the notification pipe.
|
// Wakeup wakes up the event loop through the notification pipe.
|
||||||
@@ -1576,7 +1639,7 @@ func (w *window) flushScroll() {
|
|||||||
if total == (f32.Point{}) {
|
if total == (f32.Point{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1599,7 +1662,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
|||||||
X: fromFixed(x) * float32(w.scale),
|
X: fromFixed(x) * float32(w.scale),
|
||||||
Y: fromFixed(y) * float32(w.scale),
|
Y: fromFixed(y) * float32(w.scale),
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Position: w.lastPos,
|
Position: w.lastPos,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -1620,7 +1683,8 @@ func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) {
|
|||||||
if w.config.Mode != Windowed || w.config.Decorated {
|
if w.config.Mode != Windowed || w.config.Decorated {
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
border := w.w.w.metric.Dp(3)
|
_, cfg := w.getConfig()
|
||||||
|
border := cfg.Dp(3)
|
||||||
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
|
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
|
||||||
north := y <= border
|
north := y <= border
|
||||||
south := y >= size.Y-border
|
south := y >= size.Y-border
|
||||||
@@ -1674,13 +1738,10 @@ func (w *window) updateOutputs() {
|
|||||||
if found && scale != w.scale {
|
if found && scale != w.scale {
|
||||||
w.scale = scale
|
w.scale = scale
|
||||||
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
||||||
w.redraw = true
|
w.draw(true)
|
||||||
}
|
}
|
||||||
if !found {
|
if found {
|
||||||
w.setStage(StagePaused)
|
w.draw(true)
|
||||||
} else {
|
|
||||||
w.setStage(StageRunning)
|
|
||||||
w.redraw = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1692,7 +1753,10 @@ func (w *window) getConfig() (image.Point, unit.Metric) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw() {
|
func (w *window) draw(sync bool) {
|
||||||
|
if !w.configured {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.flushScroll()
|
w.flushScroll()
|
||||||
size, cfg := w.getConfig()
|
size, cfg := w.getConfig()
|
||||||
if cfg == (unit.Metric{}) {
|
if cfg == (unit.Metric{}) {
|
||||||
@@ -1700,11 +1764,9 @@ func (w *window) draw() {
|
|||||||
}
|
}
|
||||||
if size != w.config.Size {
|
if size != w.config.Size {
|
||||||
w.config.Size = size
|
w.config.Size = size
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
anim := w.animating || w.fling.anim.Active()
|
anim := w.animating || w.fling.anim.Active()
|
||||||
sync := w.redraw
|
|
||||||
w.redraw = false
|
|
||||||
// Draw animation only when not waiting for frame callback.
|
// Draw animation only when not waiting for frame callback.
|
||||||
redrawAnim := anim && w.lastFrameCallback == nil
|
redrawAnim := anim && w.lastFrameCallback == nil
|
||||||
if !redrawAnim && !sync {
|
if !redrawAnim && !sync {
|
||||||
@@ -1715,7 +1777,7 @@ func (w *window) draw() {
|
|||||||
// Use the surface as listener data for gio_onFrameDone.
|
// Use the surface as listener data for gio_onFrameDone.
|
||||||
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
||||||
}
|
}
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -1725,14 +1787,6 @@ func (w *window) draw() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setStage(s Stage) {
|
|
||||||
if s == w.stage {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.stage = s
|
|
||||||
w.w.Event(StageEvent{Stage: s})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *window) display() *C.struct_wl_display {
|
func (w *window) display() *C.struct_wl_display {
|
||||||
return w.disp.disp
|
return w.disp.disp
|
||||||
}
|
}
|
||||||
@@ -1824,6 +1878,10 @@ func newWLDisplay() (*wlDisplay, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *wlDisplay) destroy() {
|
func (d *wlDisplay) destroy() {
|
||||||
|
if d.readClipClose != nil {
|
||||||
|
close(d.readClipClose)
|
||||||
|
d.readClipClose = nil
|
||||||
|
}
|
||||||
if d.notify.write != 0 {
|
if d.notify.write != 0 {
|
||||||
syscall.Close(d.notify.write)
|
syscall.Close(d.notify.write)
|
||||||
d.notify.write = 0
|
d.notify.write = 0
|
||||||
@@ -1865,6 +1923,7 @@ func (d *wlDisplay) destroy() {
|
|||||||
if d.disp != nil {
|
if d.disp != nil {
|
||||||
C.wl_display_disconnect(d.disp)
|
C.wl_display_disconnect(d.disp)
|
||||||
callbackDelete(unsafe.Pointer(d.disp))
|
callbackDelete(unsafe.Pointer(d.disp))
|
||||||
|
d.disp = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+82
-68
@@ -19,17 +19,19 @@ import (
|
|||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/app/internal/windows"
|
"gioui.org/app/internal/windows"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
gowindows "golang.org/x/sys/windows"
|
gowindows "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ViewEvent struct {
|
type Win32ViewEvent struct {
|
||||||
HWND uintptr
|
HWND uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +39,6 @@ type window struct {
|
|||||||
hwnd syscall.Handle
|
hwnd syscall.Handle
|
||||||
hdc syscall.Handle
|
hdc syscall.Handle
|
||||||
w *callbacks
|
w *callbacks
|
||||||
stage Stage
|
|
||||||
pointerBtns pointer.Buttons
|
pointerBtns pointer.Buttons
|
||||||
|
|
||||||
// cursorIn tracks whether the cursor was inside the window according
|
// cursorIn tracks whether the cursor was inside the window according
|
||||||
@@ -49,10 +50,13 @@ type window struct {
|
|||||||
placement *windows.WindowPlacement
|
placement *windows.WindowPlacement
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
focused bool
|
|
||||||
|
|
||||||
borderSize image.Point
|
borderSize image.Point
|
||||||
config Config
|
config Config
|
||||||
|
loop *eventLoop
|
||||||
|
|
||||||
|
// invMu avoids the race between destroying the window and Invalidate.
|
||||||
|
invMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
const _WM_WAKEUP = windows.WM_USER + iota
|
const _WM_WAKEUP = windows.WM_USER + iota
|
||||||
@@ -81,40 +85,38 @@ var resources struct {
|
|||||||
cursor syscall.Handle
|
cursor syscall.Handle
|
||||||
}
|
}
|
||||||
|
|
||||||
func osMain() {
|
func newWindow(win *callbacks, options []Option) {
|
||||||
select {}
|
done := make(chan struct{})
|
||||||
}
|
|
||||||
|
|
||||||
func newWindow(window *callbacks, options []Option) error {
|
|
||||||
cerr := make(chan error)
|
|
||||||
go func() {
|
go func() {
|
||||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||||
// then thread-specific messages such as WM_QUIT are ignored.
|
// then thread-specific messages such as WM_QUIT are ignored.
|
||||||
// Instead lock the thread so window messages arrive through
|
// Instead lock the thread so window messages arrive through
|
||||||
// unfiltered GetMessage calls.
|
// unfiltered GetMessage calls.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
w, err := createNativeWindow()
|
|
||||||
|
w := &window{
|
||||||
|
w: win,
|
||||||
|
}
|
||||||
|
w.loop = newEventLoop(w.w, w.wakeup)
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
err := w.init()
|
||||||
|
done <- struct{}{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cerr <- err
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cerr <- nil
|
|
||||||
winMap.Store(w.hwnd, w)
|
winMap.Store(w.hwnd, w)
|
||||||
defer winMap.Delete(w.hwnd)
|
defer winMap.Delete(w.hwnd)
|
||||||
w.w = window
|
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
||||||
w.w.SetDriver(w)
|
|
||||||
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
|
|
||||||
w.Configure(options)
|
w.Configure(options)
|
||||||
windows.SetForegroundWindow(w.hwnd)
|
windows.SetForegroundWindow(w.hwnd)
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
// Since the window class for the cursor is null,
|
// Since the window class for the cursor is null,
|
||||||
// set it here to show the cursor.
|
// set it here to show the cursor.
|
||||||
w.SetCursor(pointer.CursorDefault)
|
w.SetCursor(pointer.CursorDefault)
|
||||||
if err := w.loop(); err != nil {
|
w.runLoop()
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
return <-cerr
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
// initResources initializes the resources global.
|
// initResources initializes the resources global.
|
||||||
@@ -149,13 +151,13 @@ func initResources() error {
|
|||||||
|
|
||||||
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
||||||
|
|
||||||
func createNativeWindow() (*window, error) {
|
func (w *window) init() error {
|
||||||
var resErr error
|
var resErr error
|
||||||
resources.once.Do(func() {
|
resources.once.Do(func() {
|
||||||
resErr = initResources()
|
resErr = initResources()
|
||||||
})
|
})
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return nil, resErr
|
return resErr
|
||||||
}
|
}
|
||||||
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
||||||
|
|
||||||
@@ -171,16 +173,15 @@ func createNativeWindow() (*window, error) {
|
|||||||
resources.handle,
|
resources.handle,
|
||||||
0)
|
0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
|
||||||
w := &window{
|
|
||||||
hwnd: hwnd,
|
|
||||||
}
|
}
|
||||||
w.hdc, err = windows.GetDC(hwnd)
|
w.hdc, err = windows.GetDC(hwnd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
windows.DestroyWindow(hwnd)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return w, nil
|
w.hwnd = hwnd
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// update() handles changes done by the user, and updates the configuration.
|
// update() handles changes done by the user, and updates the configuration.
|
||||||
@@ -197,7 +198,7 @@ func (w *window) update() {
|
|||||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||||
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
||||||
)
|
)
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||||
@@ -238,7 +239,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
e.State = key.Release
|
e.State = key.Release
|
||||||
}
|
}
|
||||||
|
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
|
|
||||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||||
@@ -259,23 +260,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
case windows.WM_MBUTTONUP:
|
case windows.WM_MBUTTONUP:
|
||||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||||
case windows.WM_CANCELMODE:
|
case windows.WM_CANCELMODE:
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Cancel,
|
Kind: pointer.Cancel,
|
||||||
})
|
})
|
||||||
case windows.WM_SETFOCUS:
|
case windows.WM_SETFOCUS:
|
||||||
w.focused = true
|
w.config.Focused = true
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case windows.WM_KILLFOCUS:
|
case windows.WM_KILLFOCUS:
|
||||||
w.focused = false
|
w.config.Focused = false
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case windows.WM_NCACTIVATE:
|
|
||||||
if w.stage >= StageInactive {
|
|
||||||
if wParam == windows.TRUE {
|
|
||||||
w.setStage(StageRunning)
|
|
||||||
} else {
|
|
||||||
w.setStage(StageInactive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case windows.WM_NCHITTEST:
|
case windows.WM_NCHITTEST:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
// Let the system handle it.
|
// Let the system handle it.
|
||||||
@@ -288,7 +281,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
case windows.WM_MOUSEMOVE:
|
case windows.WM_MOUSEMOVE:
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
@@ -301,14 +294,16 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
case windows.WM_MOUSEHWHEEL:
|
case windows.WM_MOUSEHWHEEL:
|
||||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||||
case windows.WM_DESTROY:
|
case windows.WM_DESTROY:
|
||||||
w.w.Event(ViewEvent{})
|
w.ProcessEvent(Win32ViewEvent{})
|
||||||
w.w.Event(DestroyEvent{})
|
w.ProcessEvent(DestroyEvent{})
|
||||||
if w.hdc != 0 {
|
if w.hdc != 0 {
|
||||||
windows.ReleaseDC(w.hdc)
|
windows.ReleaseDC(w.hdc)
|
||||||
w.hdc = 0
|
w.hdc = 0
|
||||||
}
|
}
|
||||||
|
w.invMu.Lock()
|
||||||
// The system destroys the HWND for us.
|
// The system destroys the HWND for us.
|
||||||
w.hwnd = 0
|
w.hwnd = 0
|
||||||
|
w.invMu.Unlock()
|
||||||
windows.PostQuitMessage(0)
|
windows.PostQuitMessage(0)
|
||||||
case windows.WM_NCCALCSIZE:
|
case windows.WM_NCCALCSIZE:
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
@@ -328,7 +323,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
// Adjust window position to avoid the extra padding in maximized
|
// Adjust window position to avoid the extra padding in maximized
|
||||||
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
||||||
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
||||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
|
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
||||||
mi := windows.GetMonitorInfo(w.hwnd)
|
mi := windows.GetMonitorInfo(w.hwnd)
|
||||||
szp.Rgrc[0] = mi.WorkArea
|
szp.Rgrc[0] = mi.WorkArea
|
||||||
return 0
|
return 0
|
||||||
@@ -339,18 +334,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
switch wParam {
|
switch wParam {
|
||||||
case windows.SIZE_MINIMIZED:
|
case windows.SIZE_MINIMIZED:
|
||||||
w.config.Mode = Minimized
|
w.config.Mode = Minimized
|
||||||
w.setStage(StagePaused)
|
|
||||||
case windows.SIZE_MAXIMIZED:
|
case windows.SIZE_MAXIMIZED:
|
||||||
w.config.Mode = Maximized
|
w.config.Mode = Maximized
|
||||||
w.setStage(StageRunning)
|
|
||||||
case windows.SIZE_RESTORED:
|
case windows.SIZE_RESTORED:
|
||||||
if w.config.Mode != Fullscreen {
|
if w.config.Mode != Fullscreen {
|
||||||
w.config.Mode = Windowed
|
w.config.Mode = Windowed
|
||||||
}
|
}
|
||||||
w.setStage(StageRunning)
|
|
||||||
}
|
}
|
||||||
case windows.WM_GETMINMAXINFO:
|
case windows.WM_GETMINMAXINFO:
|
||||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||||
var bw, bh int32
|
var bw, bh int32
|
||||||
if w.config.Decorated {
|
if w.config.Decorated {
|
||||||
r := windows.GetWindowRect(w.hwnd)
|
r := windows.GetWindowRect(w.hwnd)
|
||||||
@@ -378,7 +370,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
return windows.TRUE
|
return windows.TRUE
|
||||||
}
|
}
|
||||||
case _WM_WAKEUP:
|
case _WM_WAKEUP:
|
||||||
w.w.Event(wakeupEvent{})
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
case windows.WM_IME_STARTCOMPOSITION:
|
case windows.WM_IME_STARTCOMPOSITION:
|
||||||
imc := windows.ImmGetContext(w.hwnd)
|
imc := windows.ImmGetContext(w.hwnd)
|
||||||
if imc == 0 {
|
if imc == 0 {
|
||||||
@@ -498,7 +491,7 @@ func (w *window) hitTest(x, y int) uintptr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||||
if !w.focused {
|
if !w.config.Focused {
|
||||||
windows.SetFocus(w.hwnd)
|
windows.SetFocus(w.hwnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +511,7 @@ func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr,
|
|||||||
}
|
}
|
||||||
x, y := coordsFromlParam(lParam)
|
x, y := coordsFromlParam(lParam)
|
||||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
@@ -553,7 +546,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
|||||||
sp.Y = -dist
|
sp.Y = -dist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Scroll,
|
Kind: pointer.Scroll,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Position: p,
|
Position: p,
|
||||||
@@ -565,7 +558,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||||
func (w *window) loop() error {
|
func (w *window) runLoop() {
|
||||||
msg := new(windows.Msg)
|
msg := new(windows.Msg)
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
@@ -576,7 +569,7 @@ loop:
|
|||||||
}
|
}
|
||||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||||
case -1:
|
case -1:
|
||||||
return errors.New("GetMessage failed")
|
panic(errors.New("GetMessage failed"))
|
||||||
case 0:
|
case 0:
|
||||||
// WM_QUIT received.
|
// WM_QUIT received.
|
||||||
break loop
|
break loop
|
||||||
@@ -584,7 +577,6 @@ loop:
|
|||||||
windows.TranslateMessage(msg)
|
windows.TranslateMessage(msg)
|
||||||
windows.DispatchMessage(msg)
|
windows.DispatchMessage(msg)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) EditorStateChanged(old, new editorState) {
|
func (w *window) EditorStateChanged(old, new editorState) {
|
||||||
@@ -602,16 +594,37 @@ func (w *window) SetAnimating(anim bool) {
|
|||||||
w.animating = anim
|
w.animating = anim
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) Wakeup() {
|
func (w *window) ProcessEvent(e event.Event) {
|
||||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
w.w.ProcessEvent(e)
|
||||||
panic(err)
|
w.loop.FlushEvents()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) setStage(s Stage) {
|
func (w *window) Event() event.Event {
|
||||||
if s != w.stage {
|
return w.loop.Event()
|
||||||
w.stage = s
|
}
|
||||||
w.w.Event(StageEvent{Stage: s})
|
|
||||||
|
func (w *window) Invalidate() {
|
||||||
|
w.loop.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Run(f func()) {
|
||||||
|
w.loop.Run(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) Frame(frame *op.Ops) {
|
||||||
|
w.loop.Frame(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) wakeup() {
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
|
if w.hwnd == 0 {
|
||||||
|
w.loop.Wakeup()
|
||||||
|
w.loop.FlushEvents()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +634,7 @@ func (w *window) draw(sync bool) {
|
|||||||
}
|
}
|
||||||
dpi := windows.GetWindowDPI(w.hwnd)
|
dpi := windows.GetWindowDPI(w.hwnd)
|
||||||
cfg := configForDPI(dpi)
|
cfg := configForDPI(dpi)
|
||||||
w.w.Event(frameEvent{
|
w.ProcessEvent(frameEvent{
|
||||||
FrameEvent: FrameEvent{
|
FrameEvent: FrameEvent{
|
||||||
Now: time.Now(),
|
Now: time.Now(),
|
||||||
Size: w.config.Size,
|
Size: w.config.Size,
|
||||||
@@ -668,7 +681,7 @@ func (w *window) readClipboard() error {
|
|||||||
}
|
}
|
||||||
defer windows.GlobalUnlock(mem)
|
defer windows.GlobalUnlock(mem)
|
||||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(content))
|
return io.NopCloser(strings.NewReader(content))
|
||||||
@@ -974,4 +987,5 @@ func configForDPI(dpi int) unit.Metric {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ ViewEvent) ImplementsEvent() {}
|
func (Win32ViewEvent) implementsViewEvent() {}
|
||||||
|
func (Win32ViewEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+110
-81
@@ -38,10 +38,12 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/key"
|
"gioui.org/io/key"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
"gioui.org/io/transfer"
|
"gioui.org/io/transfer"
|
||||||
|
"gioui.org/op"
|
||||||
"gioui.org/unit"
|
"gioui.org/unit"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/unix"
|
syscall "golang.org/x/sys/unix"
|
||||||
@@ -93,12 +95,10 @@ type x11Window struct {
|
|||||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||||
wmStateMaximizedVert C.Atom
|
wmStateMaximizedVert C.Atom
|
||||||
}
|
}
|
||||||
stage Stage
|
|
||||||
metric unit.Metric
|
metric unit.Metric
|
||||||
notify struct {
|
notify struct {
|
||||||
read, write int
|
read, write int
|
||||||
}
|
}
|
||||||
dead bool
|
|
||||||
|
|
||||||
animating bool
|
animating bool
|
||||||
|
|
||||||
@@ -111,6 +111,11 @@ type x11Window struct {
|
|||||||
config Config
|
config Config
|
||||||
|
|
||||||
wakeups chan struct{}
|
wakeups chan struct{}
|
||||||
|
handler x11EventHandler
|
||||||
|
buf [100]byte
|
||||||
|
|
||||||
|
// invMy avoids the race between destroy and Invalidate.
|
||||||
|
invMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -234,7 +239,7 @@ func (w *x11Window) Configure(options []Option) {
|
|||||||
if cnf.Decorated != prev.Decorated {
|
if cnf.Decorated != prev.Decorated {
|
||||||
w.config.Decorated = cnf.Decorated
|
w.config.Decorated = cnf.Decorated
|
||||||
}
|
}
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||||
@@ -377,11 +382,47 @@ func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
|||||||
|
|
||||||
var x11OneByte = make([]byte, 1)
|
var x11OneByte = make([]byte, 1)
|
||||||
|
|
||||||
func (w *x11Window) Wakeup() {
|
func (w *x11Window) ProcessEvent(e event.Event) {
|
||||||
|
w.w.ProcessEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) shutdown(err error) {
|
||||||
|
w.ProcessEvent(X11ViewEvent{})
|
||||||
|
w.ProcessEvent(DestroyEvent{Err: err})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Event() event.Event {
|
||||||
|
for {
|
||||||
|
evt, ok := w.w.nextEvent()
|
||||||
|
if !ok {
|
||||||
|
w.dispatch()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, destroy := evt.(DestroyEvent); destroy {
|
||||||
|
w.destroy()
|
||||||
|
}
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Run(f func()) {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Frame(frame *op.Ops) {
|
||||||
|
w.w.ProcessFrame(frame, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) Invalidate() {
|
||||||
select {
|
select {
|
||||||
case w.wakeups <- struct{}{}:
|
case w.wakeups <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
|
if w.x == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||||
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||||
}
|
}
|
||||||
@@ -395,16 +436,20 @@ func (w *x11Window) window() (C.Window, int, int) {
|
|||||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) setStage(s Stage) {
|
func (w *x11Window) dispatch() {
|
||||||
if s == w.stage {
|
if w.x == nil {
|
||||||
|
// Only Invalidate can wake us up.
|
||||||
|
<-w.wakeups
|
||||||
|
w.w.Invalidate()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.stage = s
|
|
||||||
w.w.Event(StageEvent{Stage: s})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *x11Window) loop() {
|
select {
|
||||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
case <-w.wakeups:
|
||||||
|
w.w.Invalidate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
xfd := C.XConnectionNumber(w.x)
|
xfd := C.XConnectionNumber(w.x)
|
||||||
|
|
||||||
// Poll for events and notifications.
|
// Poll for events and notifications.
|
||||||
@@ -414,64 +459,52 @@ func (w *x11Window) loop() {
|
|||||||
}
|
}
|
||||||
xEvents := &pollfds[0].Revents
|
xEvents := &pollfds[0].Revents
|
||||||
// Plenty of room for a backlog of notifications.
|
// Plenty of room for a backlog of notifications.
|
||||||
buf := make([]byte, 100)
|
|
||||||
|
|
||||||
loop:
|
var syn, anim bool
|
||||||
for !w.dead {
|
// Check for pending draw events before checking animation or blocking.
|
||||||
var syn, anim bool
|
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||||
// Check for pending draw events before checking animation or blocking.
|
// poll will still block. This also prevents no-op calls to poll.
|
||||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
if syn = w.handler.handleEvents(); !syn {
|
||||||
// poll will still block. This also prevents no-op calls to poll.
|
anim = w.animating
|
||||||
if syn = h.handleEvents(); !syn {
|
if !anim {
|
||||||
anim = w.animating
|
// Clear poll events.
|
||||||
if !anim {
|
*xEvents = 0
|
||||||
// Clear poll events.
|
// Wait for X event or gio notification.
|
||||||
*xEvents = 0
|
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||||
// Wait for X event or gio notification.
|
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
}
|
||||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
switch {
|
||||||
}
|
case *xEvents&syscall.POLLIN != 0:
|
||||||
switch {
|
syn = w.handler.handleEvents()
|
||||||
case *xEvents&syscall.POLLIN != 0:
|
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
syn = h.handleEvents()
|
|
||||||
if w.dead {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear notifications.
|
}
|
||||||
for {
|
// Clear notifications.
|
||||||
_, err := syscall.Read(w.notify.read, buf)
|
for {
|
||||||
if err == syscall.EAGAIN {
|
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||||
break
|
if err == syscall.EAGAIN {
|
||||||
}
|
break
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
select {
|
if err != nil {
|
||||||
case <-w.wakeups:
|
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||||
w.w.Event(wakeupEvent{})
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
|
||||||
w.w.Event(frameEvent{
|
|
||||||
FrameEvent: FrameEvent{
|
|
||||||
Now: time.Now(),
|
|
||||||
Size: w.config.Size,
|
|
||||||
Metric: w.metric,
|
|
||||||
},
|
|
||||||
Sync: syn,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||||
|
w.ProcessEvent(frameEvent{
|
||||||
|
FrameEvent: FrameEvent{
|
||||||
|
Now: time.Now(),
|
||||||
|
Size: w.config.Size,
|
||||||
|
Metric: w.metric,
|
||||||
|
},
|
||||||
|
Sync: syn,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *x11Window) destroy() {
|
func (w *x11Window) destroy() {
|
||||||
|
w.invMu.Lock()
|
||||||
|
defer w.invMu.Unlock()
|
||||||
if w.notify.write != 0 {
|
if w.notify.write != 0 {
|
||||||
syscall.Close(w.notify.write)
|
syscall.Close(w.notify.write)
|
||||||
w.notify.write = 0
|
w.notify.write = 0
|
||||||
@@ -486,6 +519,7 @@ func (w *x11Window) destroy() {
|
|||||||
}
|
}
|
||||||
C.XDestroyWindow(w.x, w.xw)
|
C.XDestroyWindow(w.x, w.xw)
|
||||||
C.XCloseDisplay(w.x)
|
C.XCloseDisplay(w.x)
|
||||||
|
w.x = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||||
@@ -543,7 +577,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// There's no support for IME yet.
|
// There's no support for IME yet.
|
||||||
w.w.EditorInsert(ee.Text)
|
w.w.EditorInsert(ee.Text)
|
||||||
} else {
|
} else {
|
||||||
w.w.Event(e)
|
w.ProcessEvent(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case C.ButtonPress, C.ButtonRelease:
|
case C.ButtonPress, C.ButtonRelease:
|
||||||
@@ -605,10 +639,10 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
w.pointerBtns &^= btn
|
w.pointerBtns &^= btn
|
||||||
}
|
}
|
||||||
ev.Buttons = w.pointerBtns
|
ev.Buttons = w.pointerBtns
|
||||||
w.w.Event(ev)
|
w.ProcessEvent(ev)
|
||||||
case C.MotionNotify:
|
case C.MotionNotify:
|
||||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||||
w.w.Event(pointer.Event{
|
w.ProcessEvent(pointer.Event{
|
||||||
Kind: pointer.Move,
|
Kind: pointer.Move,
|
||||||
Source: pointer.Mouse,
|
Source: pointer.Mouse,
|
||||||
Buttons: w.pointerBtns,
|
Buttons: w.pointerBtns,
|
||||||
@@ -623,14 +657,16 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
// redraw only on the last expose event
|
// redraw only on the last expose event
|
||||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||||
case C.FocusIn:
|
case C.FocusIn:
|
||||||
w.w.Event(key.FocusEvent{Focus: true})
|
w.config.Focused = true
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case C.FocusOut:
|
case C.FocusOut:
|
||||||
w.w.Event(key.FocusEvent{Focus: false})
|
w.config.Focused = false
|
||||||
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
case C.ConfigureNotify: // window configuration change
|
case C.ConfigureNotify: // window configuration change
|
||||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||||
w.config.Size = sz
|
w.config.Size = sz
|
||||||
w.w.Event(ConfigEvent{Config: w.config})
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||||
}
|
}
|
||||||
// redraw will be done by a later expose event
|
// redraw will be done by a later expose event
|
||||||
case C.SelectionNotify:
|
case C.SelectionNotify:
|
||||||
@@ -652,7 +688,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||||
w.w.Event(transfer.DataEvent{
|
w.ProcessEvent(transfer.DataEvent{
|
||||||
Type: "application/text",
|
Type: "application/text",
|
||||||
Open: func() io.ReadCloser {
|
Open: func() io.ReadCloser {
|
||||||
return io.NopCloser(strings.NewReader(str))
|
return io.NopCloser(strings.NewReader(str))
|
||||||
@@ -711,7 +747,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
|||||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||||
case C.long(w.atoms.evDelWindow):
|
case C.long(w.atoms.evDelWindow):
|
||||||
w.dead = true
|
w.shutdown(nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -793,8 +829,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
wakeups: make(chan struct{}, 1),
|
wakeups: make(chan struct{}, 1),
|
||||||
config: Config{Size: cnf.Size},
|
config: Config{Size: cnf.Size},
|
||||||
}
|
}
|
||||||
|
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||||
w.notify.read = pipe[0]
|
w.notify.read = pipe[0]
|
||||||
w.notify.write = pipe[1]
|
w.notify.write = pipe[1]
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
|
||||||
if err := w.updateXkbKeymap(); err != nil {
|
if err := w.updateXkbKeymap(); err != nil {
|
||||||
w.destroy()
|
w.destroy()
|
||||||
@@ -830,19 +868,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
|||||||
// extensions
|
// extensions
|
||||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||||
|
|
||||||
go func() {
|
// make the window visible on the screen
|
||||||
w.w.SetDriver(w)
|
C.XMapWindow(dpy, win)
|
||||||
|
w.Configure(options)
|
||||||
// make the window visible on the screen
|
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||||
C.XMapWindow(dpy, win)
|
|
||||||
w.Configure(options)
|
|
||||||
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
|
||||||
w.setStage(StageRunning)
|
|
||||||
w.loop()
|
|
||||||
w.w.Event(X11ViewEvent{})
|
|
||||||
w.w.Event(DestroyEvent{Err: nil})
|
|
||||||
w.destroy()
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -25,6 +25,6 @@ func runMain() {
|
|||||||
// Indirect call, since the linker does not know the address of main when
|
// Indirect call, since the linker does not know the address of main when
|
||||||
// laying down this package.
|
// laying down this package.
|
||||||
fn := mainMain
|
fn := mainMain
|
||||||
fn()
|
go fn()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,40 +10,4 @@ type DestroyEvent struct {
|
|||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// A StageEvent is generated whenever the stage of a
|
|
||||||
// Window changes.
|
|
||||||
type StageEvent struct {
|
|
||||||
Stage Stage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stage of a Window.
|
|
||||||
type Stage uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StagePaused is the stage for windows that have no on-screen representation.
|
|
||||||
// Paused windows don't receive frames.
|
|
||||||
StagePaused Stage = iota
|
|
||||||
// StageInactive is the stage for windows that are visible, but not active.
|
|
||||||
// Inactive windows receive frames.
|
|
||||||
StageInactive
|
|
||||||
// StageRunning is for active and visible Windows.
|
|
||||||
// Running windows receive frames.
|
|
||||||
StageRunning
|
|
||||||
)
|
|
||||||
|
|
||||||
// String implements fmt.Stringer.
|
|
||||||
func (l Stage) String() string {
|
|
||||||
switch l {
|
|
||||||
case StagePaused:
|
|
||||||
return "StagePaused"
|
|
||||||
case StageInactive:
|
|
||||||
return "StageInactive"
|
|
||||||
case StageRunning:
|
|
||||||
return "StageRunning"
|
|
||||||
default:
|
|
||||||
panic("unexpected Stage value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (StageEvent) ImplementsEvent() {}
|
|
||||||
func (DestroyEvent) ImplementsEvent() {}
|
func (DestroyEvent) ImplementsEvent() {}
|
||||||
|
|||||||
+286
-515
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user