forked from joejulian/gio
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e47316332 | |||
| 55ae5c5b84 | |||
| 86349775b7 | |||
| 4a1b4c2642 | |||
| c900d58fb3 | |||
| 74ccc9c2c7 | |||
| 3f671afea8 | |||
| 42357a29e0 | |||
| 8fb6d3da2b | |||
| 706940ff9b | |||
| 5542aac772 | |||
| 026d3f9daa | |||
| 38fca9ae13 | |||
| e878dbc598 | |||
| 1151eac07d | |||
| 56177c55cf | |||
| e6da07a85a | |||
| 175e134478 | |||
| 46cc311d19 | |||
| b8821875ed | |||
| f6e33914d9 | |||
| a394b330e8 | |||
| 24b0c2a4a1 | |||
| 7a9ce51988 | |||
| 8242234274 | |||
| 691adf4e77 | |||
| ba1e34e570 | |||
| 0deb7b3efc | |||
| e8c73bcb37 | |||
| cf9f2bbffe | |||
| ed28861309 | |||
| 971b01d836 | |||
| 5083a23301 | |||
| 61b603d521 | |||
| 3b5148a64e | |||
| ee6cdec60b | |||
| 42ef3476cc | |||
| 98d3a2eb24 | |||
| 109226b7e9 | |||
| 477bd5c744 | |||
| 1802761c93 | |||
| 0558bb3f1c | |||
| 78ce5e3ad5 | |||
| 1be34eec6f | |||
| 44ede4eceb | |||
| 993ec907be | |||
| 35785e9c96 | |||
| 93ac0b03f1 | |||
| d58d386b9b | |||
| f3fc0d62b8 | |||
| 5e5d164929 | |||
| 1527e91a02 | |||
| caba422d9c | |||
| 390242f214 | |||
| fe1df00d02 | |||
| 0d7f00c634 | |||
| d7528a8338 | |||
| 9bca5bfdcf | |||
| a880d6403d | |||
| 6879a30582 | |||
| 5cda660e6e | |||
| 8cb06ffa30 |
+1
-1
@@ -29,7 +29,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl -s https://dl.google.com/go/go1.22.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- prepare_toolchain: |
|
||||
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
||||
cd $APPLE_TOOLCHAIN_ROOT
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.19.11.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.22.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- test_gio: |
|
||||
cd gio
|
||||
go test ./...
|
||||
|
||||
+3
-1
@@ -40,7 +40,7 @@ secrets:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.19.11.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl -s https://dl.google.com/go/go1.22.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- check_gofmt: |
|
||||
cd gio
|
||||
test -z "$(gofmt -s -l .)"
|
||||
@@ -80,6 +80,8 @@ tasks:
|
||||
unzip -q ndk.zip
|
||||
rm ndk.zip
|
||||
mv android-ndk-* ndk-bundle
|
||||
# sdkmanager needs lots of file descriptors
|
||||
ulimit -n 10000
|
||||
yes|sdkmanager --licenses
|
||||
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
||||
- test_android: |
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ environment:
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.19.11.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.22.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
cd /home/build/sdk/go/src
|
||||
./make.bash
|
||||
- test_gio: |
|
||||
|
||||
+1
-1
@@ -65,8 +65,8 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
private final AccessibilityManager accessManager;
|
||||
private int keyboardHint;
|
||||
private AccessibilityManager accessManager;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
|
||||
+11
@@ -56,6 +56,17 @@ type FrameEvent struct {
|
||||
Source input.Source
|
||||
}
|
||||
|
||||
// ViewEvent provides handles to the underlying window objects for the
|
||||
// current display protocol.
|
||||
type ViewEvent interface {
|
||||
implementsViewEvent()
|
||||
ImplementsEvent()
|
||||
// Valid will return true when the ViewEvent does contains valid handles.
|
||||
// If a window receives an invalid ViewEvent, it should deinitialize any
|
||||
// state referring to handles from a previous ViewEvent.
|
||||
Valid() bool
|
||||
}
|
||||
|
||||
// Insets is the space taken up by
|
||||
// system decoration such as translucent
|
||||
// system bars and software keyboards.
|
||||
|
||||
+9
-8
@@ -8,18 +8,19 @@ See https://gioui.org for instructions to set up and run Gio programs.
|
||||
|
||||
# Windows
|
||||
|
||||
Create a new [Window] by calling [NewWindow]. On mobile platforms or when Gio
|
||||
is embedded in another project, NewWindow merely connects with a previously
|
||||
created window.
|
||||
A Window is run by calling its Event method in a loop. The first time a
|
||||
method on Window is called, a new GUI window is created and shown. On mobile
|
||||
platforms or when Gio is embedded in another project, Window merely connects
|
||||
with a previously created GUI window.
|
||||
|
||||
A Window is run by calling its NextEvent method in a loop. The most important event is
|
||||
[FrameEvent] that prompts an update of the window contents.
|
||||
The most important event is [FrameEvent] that prompts an update of the window
|
||||
contents.
|
||||
|
||||
For example:
|
||||
|
||||
w := app.NewWindow()
|
||||
w := new(app.Window)
|
||||
for {
|
||||
e := w.NextEvent()
|
||||
e := w.Event()
|
||||
if e, ok := e.(app.FrameEvent); ok {
|
||||
ops.Reset()
|
||||
// Add operations to ops.
|
||||
@@ -49,7 +50,7 @@ For example, to display a blank but otherwise functional window:
|
||||
go func() {
|
||||
w := app.NewWindow()
|
||||
for {
|
||||
w.NextEvent()
|
||||
w.Event()
|
||||
}
|
||||
}()
|
||||
app.Main()
|
||||
|
||||
+4
-6
@@ -17,9 +17,8 @@ import (
|
||||
)
|
||||
|
||||
type androidContext struct {
|
||||
win *window
|
||||
eglSurf egl.NativeWindowType
|
||||
width, height int
|
||||
win *window
|
||||
eglSurf egl.NativeWindowType
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
@@ -45,9 +44,8 @@ func (c *androidContext) Refresh() error {
|
||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||
return err
|
||||
}
|
||||
win, width, height := c.win.nativeWindow()
|
||||
win, _, _ := c.win.nativeWindow()
|
||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||
c.width, c.height = width, height
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -55,7 +53,7 @@ func (c *androidContext) Lock() error {
|
||||
// The Android emulator creates a broken surface if it is not
|
||||
// created on the same thread as the context is made current.
|
||||
if c.eglSurf != nil {
|
||||
if err := c.Context.CreateSurface(c.eglSurf, c.width, c.height); err != nil {
|
||||
if err := c.Context.CreateSurface(c.eglSurf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.eglSurf = nil
|
||||
|
||||
+11
-1
@@ -69,7 +69,17 @@ func (c *wlContext) Refresh() error {
|
||||
}
|
||||
c.eglWin = eglWin
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
return c.Context.CreateSurface(eglSurf, width, height)
|
||||
if err := c.Context.CreateSurface(eglSurf); 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 {
|
||||
|
||||
+12
-17
@@ -5,8 +5,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/internal/egl"
|
||||
)
|
||||
|
||||
@@ -24,6 +22,18 @@ func init() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &glContext{win: w, Context: ctx}, nil
|
||||
},
|
||||
})
|
||||
@@ -37,21 +47,6 @@ func (c *glContext) Release() {
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
var (
|
||||
win windows.Handle
|
||||
width, height int
|
||||
)
|
||||
win, width, height = c.win.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
c.Context.ReleaseCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+12
-11
@@ -25,6 +25,18 @@ func init() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
@@ -37,17 +49,6 @@ func (c *x11Context) Release() {
|
||||
}
|
||||
|
||||
func (c *x11Context) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
c.Context.ReleaseCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
CALayer *gio_layerFactory(void) {
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
return [CALayer layer];
|
||||
}
|
||||
|
||||
+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"
|
||||
@@ -266,6 +266,7 @@ const (
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_NCACTIVATE = 0x0086
|
||||
WM_NCLBUTTONDBLCLK = 0x00A3
|
||||
WM_NCHITTEST = 0x0084
|
||||
WM_NCCALCSIZE = 0x0083
|
||||
WM_PAINT = 0x000F
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||
const logLineLimit = 1024
|
||||
|
||||
var logTag = C.CString(appID)
|
||||
var logTag = C.CString(ID)
|
||||
|
||||
func init() {
|
||||
// Android's logcat already includes timestamps.
|
||||
@@ -3,7 +3,7 @@
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
+2
-1
@@ -60,8 +60,9 @@ static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||
[cmdBuffer presentDrawable:drawable];
|
||||
[cmdBuffer commit];
|
||||
[cmdBuffer waitUntilScheduled];
|
||||
[drawable present];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -21,7 +21,10 @@ Class gio_layerClass(void) {
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = YES;
|
||||
return CFBridgingRetain(l);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -12,9 +12,13 @@ package app
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
CALayer *gio_layerFactory(void) {
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
return [CAMetalLayer layer];
|
||||
CAMetalLayer *l = [CAMetalLayer layer];
|
||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = presentWithTrans;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/gpu"
|
||||
"gioui.org/io/pointer"
|
||||
@@ -43,6 +45,8 @@ type Config struct {
|
||||
CustomRenderer bool
|
||||
// Decorated reports whether window decorations are provided automatically.
|
||||
Decorated bool
|
||||
// Focused reports whether has the keyboard focus.
|
||||
Focused bool
|
||||
// decoHeight is the height of the fallback decoration for platforms such
|
||||
// as Wayland that may need fallback client-side decorations.
|
||||
decoHeight unit.Dp
|
||||
@@ -131,6 +135,28 @@ func (o Orientation) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// eventLoop implements the functionality required for drivers where
|
||||
// window event loops must run on a separate thread.
|
||||
type eventLoop struct {
|
||||
win *callbacks
|
||||
// wakeup is the callback to wake up the event loop.
|
||||
wakeup func()
|
||||
// driverFuncs is a channel of functions to run the next
|
||||
// time the window loop waits for events.
|
||||
driverFuncs chan func()
|
||||
// invalidates is notified when an invalidate is requested by the client.
|
||||
invalidates chan struct{}
|
||||
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||
immediateInvalidates chan struct{}
|
||||
// events is where the platform backend delivers events bound for the
|
||||
// user program.
|
||||
events chan event.Event
|
||||
frames chan *op.Ops
|
||||
frameAck chan struct{}
|
||||
// delivering avoids re-entrant event delivery.
|
||||
delivering bool
|
||||
}
|
||||
|
||||
type frameEvent struct {
|
||||
FrameEvent
|
||||
|
||||
@@ -147,9 +173,13 @@ type context interface {
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// Driver is the interface for the platform implementation
|
||||
// driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type driver interface {
|
||||
// Event blocks until an event is available and returns it.
|
||||
Event() event.Event
|
||||
// Invalidate requests a FrameEvent.
|
||||
Invalidate()
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
@@ -166,17 +196,23 @@ type driver interface {
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(cursor pointer.Cursor)
|
||||
// Wakeup wakes up the event loop and sends a WakeupEvent.
|
||||
Wakeup()
|
||||
// Wakeup()
|
||||
// Perform actions on the window.
|
||||
Perform(system.Action)
|
||||
// EditorStateChanged notifies the driver that the editor state changed.
|
||||
EditorStateChanged(old, new editorState)
|
||||
// Run a function on the window thread.
|
||||
Run(f func())
|
||||
// Frame receives a frame.
|
||||
Frame(frame *op.Ops)
|
||||
// ProcessEvent processes an event.
|
||||
ProcessEvent(e event.Event)
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
errs chan error
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
windows chan struct{}
|
||||
}
|
||||
|
||||
type windowAndConfig struct {
|
||||
@@ -186,32 +222,137 @@ type windowAndConfig struct {
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
errs: make(chan error),
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
windows: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
var main windowAndConfig
|
||||
in := wr.in
|
||||
var window windowAndConfig
|
||||
var out chan windowAndConfig
|
||||
for {
|
||||
select {
|
||||
case w := <-wr.in:
|
||||
var err error
|
||||
if main.window != nil {
|
||||
err = errors.New("multiple windows are not supported")
|
||||
}
|
||||
wr.errs <- err
|
||||
main = w
|
||||
case w := <-in:
|
||||
window = w
|
||||
out = wr.out
|
||||
case out <- main:
|
||||
case out <- window:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||
return &eventLoop{
|
||||
win: w,
|
||||
wakeup: wakeup,
|
||||
events: make(chan event.Event),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
immediateInvalidates: make(chan struct{}),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func(), 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Frame receives a frame and waits for its processing. It is called by
|
||||
// the client goroutine.
|
||||
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||
e.frames <- frame
|
||||
<-e.frameAck
|
||||
}
|
||||
|
||||
// Event returns the next available event. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Event() event.Event {
|
||||
for {
|
||||
evt := <-e.events
|
||||
// Receiving a flushEvent indicates to the platform backend that
|
||||
// all previous events have been processed by the user program.
|
||||
if _, ok := evt.(flushEvent); ok {
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate requests invalidation of the window. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Invalidate() {
|
||||
select {
|
||||
case e.immediateInvalidates <- struct{}{}:
|
||||
// The event loop was waiting, no need for a wakeup.
|
||||
case e.invalidates <- struct{}{}:
|
||||
// The event loop is sleeping, wake it up.
|
||||
e.wakeup()
|
||||
default:
|
||||
// A redraw is pending.
|
||||
}
|
||||
}
|
||||
|
||||
// Run f in the window loop thread. It is called by the client goroutine.
|
||||
func (e *eventLoop) Run(f func()) {
|
||||
e.driverFuncs <- f
|
||||
e.wakeup()
|
||||
}
|
||||
|
||||
// FlushEvents delivers pending events to the client.
|
||||
func (e *eventLoop) FlushEvents() {
|
||||
if e.delivering {
|
||||
return
|
||||
}
|
||||
e.delivering = true
|
||||
defer func() { e.delivering = false }()
|
||||
for {
|
||||
evt, ok := e.win.nextEvent()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e.deliverEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||
var frames <-chan *op.Ops
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case frame := <-frames:
|
||||
// The client called FrameEvent.Frame.
|
||||
frames = nil
|
||||
e.win.ProcessFrame(frame, e.frameAck)
|
||||
case e.events <- evt:
|
||||
switch evt.(type) {
|
||||
case flushEvent, DestroyEvent:
|
||||
// DestroyEvents are not flushed.
|
||||
return
|
||||
case FrameEvent:
|
||||
frames = e.frames
|
||||
}
|
||||
evt = theFlushEvent
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) Wakeup() {
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkActions(actions system.Action, do func(system.Action)) {
|
||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||
@@ -221,3 +362,6 @@ func walkActions(actions system.Action, do func(system.Action)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
|
||||
+114
-77
@@ -137,8 +137,10 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/input"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
@@ -150,6 +152,7 @@ import (
|
||||
|
||||
type window struct {
|
||||
callbacks *callbacks
|
||||
loop *eventLoop
|
||||
|
||||
view C.jobject
|
||||
handle cgo.Handle
|
||||
@@ -158,12 +161,13 @@ type window struct {
|
||||
fontScale float32
|
||||
insets pixelInsets
|
||||
|
||||
stage Stage
|
||||
visible bool
|
||||
started bool
|
||||
animating bool
|
||||
|
||||
win *C.ANativeWindow
|
||||
config Config
|
||||
win *C.ANativeWindow
|
||||
config Config
|
||||
inputHint key.InputHint
|
||||
|
||||
semantic struct {
|
||||
hoverID input.SemanticID
|
||||
@@ -201,9 +205,9 @@ type pixelInsets struct {
|
||||
top, bottom, left, right int
|
||||
}
|
||||
|
||||
// ViewEvent is sent whenever the Window's underlying Android view
|
||||
// AndroidViewEvent is sent whenever the Window's underlying Android view
|
||||
// changes.
|
||||
type ViewEvent struct {
|
||||
type AndroidViewEvent struct {
|
||||
// View is a JNI global reference to the android.view.View
|
||||
// instance backing the Window. The reference is valid until
|
||||
// the next ViewEvent is received.
|
||||
@@ -487,24 +491,30 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
|
||||
})
|
||||
view = C.jni_NewGlobalRef(env, view)
|
||||
wopts := <-mainWindow.out
|
||||
var cnf Config
|
||||
w, ok := windows[wopts.window]
|
||||
if !ok {
|
||||
w = &window{
|
||||
callbacks: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.callbacks, w.wakeup)
|
||||
w.callbacks.SetDriver(w)
|
||||
cnf.apply(unit.Metric{}, wopts.options)
|
||||
windows[wopts.window] = w
|
||||
} else {
|
||||
cnf = w.config
|
||||
}
|
||||
mainWindow.windows <- struct{}{}
|
||||
if w.view != 0 {
|
||||
w.detach(env)
|
||||
}
|
||||
w.view = view
|
||||
w.visible = false
|
||||
w.handle = cgo.NewHandle(w)
|
||||
w.callbacks.SetDriver(w)
|
||||
w.loadConfig(env, class)
|
||||
w.Configure(wopts.options)
|
||||
w.SetInputHint(key.HintAny)
|
||||
w.setStage(StagePaused)
|
||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
||||
w.setConfig(env, cnf)
|
||||
w.SetInputHint(w.inputHint)
|
||||
w.processEvent(AndroidViewEvent{View: uintptr(view)})
|
||||
return C.jlong(w.handle)
|
||||
}
|
||||
|
||||
@@ -518,7 +528,7 @@ func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle
|
||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := cgo.Handle(handle).Value().(*window)
|
||||
w.started = false
|
||||
w.setStage(StagePaused)
|
||||
w.visible = false
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStartView
|
||||
@@ -534,7 +544,7 @@ func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.
|
||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := cgo.Handle(handle).Value().(*window)
|
||||
w.win = nil
|
||||
w.setStage(StagePaused)
|
||||
w.visible = false
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||
@@ -556,9 +566,7 @@ func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) {
|
||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||
w := cgo.Handle(view).Value().(*window)
|
||||
w.loadConfig(env, class)
|
||||
if w.stage >= StageInactive {
|
||||
w.draw(env, true)
|
||||
}
|
||||
w.draw(env, true)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFrameCallback
|
||||
@@ -567,10 +575,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if w.stage < StageInactive {
|
||||
return
|
||||
}
|
||||
if w.animating {
|
||||
if w.visible && w.animating {
|
||||
w.draw(env, false)
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
}
|
||||
@@ -579,7 +584,7 @@ func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view
|
||||
//export Java_org_gioui_GioView_onBack
|
||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||
w := cgo.Handle(view).Value().(*window)
|
||||
if w.callbacks.Event(key.Event{Name: key.NameBack}) {
|
||||
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||
return C.JNI_TRUE
|
||||
}
|
||||
return C.JNI_FALSE
|
||||
@@ -588,7 +593,8 @@ func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong)
|
||||
//export Java_org_gioui_GioView_onFocusChange
|
||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||
w := cgo.Handle(view).Value().(*window)
|
||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||
w.config.Focused = focus == C.JNI_TRUE
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onWindowInsets
|
||||
@@ -600,9 +606,7 @@ func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C
|
||||
left: int(left),
|
||||
right: int(right),
|
||||
}
|
||||
if w.stage >= StageInactive {
|
||||
w.draw(env, true)
|
||||
}
|
||||
w.draw(env, true)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_initializeAccessibilityNodeInfo
|
||||
@@ -663,6 +667,34 @@ func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.processEvent(e)
|
||||
}
|
||||
|
||||
func (w *window) processEvent(e event.Event) bool {
|
||||
if !w.callbacks.ProcessEvent(e) {
|
||||
return false
|
||||
}
|
||||
w.loop.FlushEvents()
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error {
|
||||
for _, ch := range sem.Children {
|
||||
err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID)))
|
||||
@@ -767,8 +799,7 @@ func (w *window) semIDFor(virtID C.jint) input.SemanticID {
|
||||
|
||||
func (w *window) detach(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, gioView.unregister)
|
||||
w.callbacks.Event(ViewEvent{})
|
||||
w.callbacks.SetDriver(nil)
|
||||
w.processEvent(AndroidViewEvent{})
|
||||
w.handle.Delete()
|
||||
C.jni_DeleteGlobalRef(env, w.view)
|
||||
w.view = 0
|
||||
@@ -779,18 +810,10 @@ func (w *window) setVisible(env *C.JNIEnv) {
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
w.setStage(StageRunning)
|
||||
w.visible = true
|
||||
w.draw(env, true)
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.callbacks.Event(StageEvent{stage})
|
||||
}
|
||||
|
||||
func (w *window) setVisual(visID int) error {
|
||||
if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
return errors.New("ANativeWindow_setBuffersGeometry failed")
|
||||
@@ -827,10 +850,13 @@ func (w *window) SetAnimating(anim bool) {
|
||||
}
|
||||
|
||||
func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
|
||||
if size != w.config.Size {
|
||||
w.config.Size = size
|
||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
if size.X == 0 || size.Y == 0 {
|
||||
return
|
||||
@@ -844,7 +870,7 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
|
||||
Left: unit.Dp(w.insets.left) * dppp,
|
||||
Right: unit.Dp(w.insets.right) * dppp,
|
||||
}
|
||||
w.callbacks.Event(frameEvent{
|
||||
w.processEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
@@ -944,7 +970,7 @@ func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.j
|
||||
if pressed == C.JNI_TRUE {
|
||||
state = key.Press
|
||||
}
|
||||
w.callbacks.Event(key.Event{Name: n, State: state})
|
||||
w.processEvent(key.Event{Name: n, State: state})
|
||||
}
|
||||
if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224).
|
||||
w.callbacks.EditorInsert(string(rune(r)))
|
||||
@@ -994,7 +1020,7 @@ func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.callbacks.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: src,
|
||||
Buttons: btns,
|
||||
@@ -1146,6 +1172,8 @@ func (w *window) ShowTextInput(show bool) {
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
w.inputHint = mode
|
||||
|
||||
// Constants defined at https://developer.android.com/reference/android/text/InputType.
|
||||
const (
|
||||
TYPE_NULL = 0
|
||||
@@ -1292,9 +1320,9 @@ func findClass(env *C.JNIEnv, name string) C.jclass {
|
||||
func osMain() {
|
||||
}
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{window, options}
|
||||
return <-mainWindow.errs
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
@@ -1313,7 +1341,7 @@ func (w *window) ReadClipboard() {
|
||||
return
|
||||
}
|
||||
content := goString(env, C.jstring(c))
|
||||
w.callbacks.Event(transfer.DataEvent{
|
||||
w.processEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -1323,42 +1351,46 @@ func (w *window) ReadClipboard() {
|
||||
}
|
||||
|
||||
func (w *window) Configure(options []Option) {
|
||||
cnf := w.config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
prev := w.config
|
||||
cnf := w.config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
// Decorations are never disabled.
|
||||
cnf.Decorated = true
|
||||
|
||||
if prev.Orientation != cnf.Orientation {
|
||||
w.config.Orientation = cnf.Orientation
|
||||
setOrientation(env, w.view, cnf.Orientation)
|
||||
}
|
||||
if prev.NavigationColor != cnf.NavigationColor {
|
||||
w.config.NavigationColor = cnf.NavigationColor
|
||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||
}
|
||||
if prev.StatusColor != cnf.StatusColor {
|
||||
w.config.StatusColor = cnf.StatusColor
|
||||
setStatusColor(env, w.view, cnf.StatusColor)
|
||||
}
|
||||
if prev.Mode != cnf.Mode {
|
||||
switch cnf.Mode {
|
||||
case Fullscreen:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||
w.config.Mode = Fullscreen
|
||||
case Windowed:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||
w.config.Mode = Windowed
|
||||
}
|
||||
}
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.callbacks.Event(ConfigEvent{Config: w.config})
|
||||
w.setConfig(env, cnf)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) setConfig(env *C.JNIEnv, cnf Config) {
|
||||
prev := w.config
|
||||
// Decorations are never disabled.
|
||||
cnf.Decorated = true
|
||||
|
||||
if prev.Orientation != cnf.Orientation {
|
||||
w.config.Orientation = cnf.Orientation
|
||||
setOrientation(env, w.view, cnf.Orientation)
|
||||
}
|
||||
if prev.NavigationColor != cnf.NavigationColor {
|
||||
w.config.NavigationColor = cnf.NavigationColor
|
||||
setNavigationColor(env, w.view, cnf.NavigationColor)
|
||||
}
|
||||
if prev.StatusColor != cnf.StatusColor {
|
||||
w.config.StatusColor = cnf.StatusColor
|
||||
setStatusColor(env, w.view, cnf.StatusColor)
|
||||
}
|
||||
if prev.Mode != cnf.Mode {
|
||||
switch cnf.Mode {
|
||||
case Fullscreen:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
|
||||
w.config.Mode = Fullscreen
|
||||
case Windowed:
|
||||
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
|
||||
w.config.Mode = Windowed
|
||||
}
|
||||
}
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
@@ -1367,9 +1399,10 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
w.callbacks.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1460,4 +1493,8 @@ func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (AndroidViewEvent) implementsViewEvent() {}
|
||||
func (AndroidViewEvent) ImplementsEvent() {}
|
||||
func (a AndroidViewEvent) Valid() bool {
|
||||
return a != (AndroidViewEvent{})
|
||||
}
|
||||
|
||||
+8
-3
@@ -75,9 +75,13 @@ var displayLinks sync.Map
|
||||
|
||||
var mainFuncs = make(chan func(), 1)
|
||||
|
||||
func isMainThread() bool {
|
||||
return bool(C.isMainThread())
|
||||
}
|
||||
|
||||
// runOnMain runs the function on the main thread.
|
||||
func runOnMain(f func()) {
|
||||
if C.isMainThread() {
|
||||
if isMainThread() {
|
||||
f()
|
||||
return
|
||||
}
|
||||
@@ -260,8 +264,9 @@ func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func() {
|
||||
w.w.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
|
||||
+140
-60
@@ -12,6 +12,9 @@ package app
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
@@ -19,6 +22,7 @@ struct drawParams {
|
||||
};
|
||||
|
||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
@@ -27,13 +31,18 @@ static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static CFTypeRef readClipboard(void) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void showTextInput(CFTypeRef viewRef) {
|
||||
@@ -73,7 +82,9 @@ import "C"
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -81,14 +92,16 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type UIKitViewEvent struct {
|
||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||
ViewController uintptr
|
||||
}
|
||||
@@ -97,18 +110,17 @@ type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
displayLink *displayLink
|
||||
loop *eventLoop
|
||||
|
||||
visible bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
hidden bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var views = make(map[C.CFTypeRef]*window)
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
@@ -116,48 +128,52 @@ func init() {
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view, controller C.CFTypeRef) {
|
||||
wopts := <-mainWindow.out
|
||||
w := &window{
|
||||
view: view,
|
||||
w: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
mainWindow.windows <- struct{}{}
|
||||
dl, err := newDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
w.displayLink = dl
|
||||
wopts := <-mainWindow.out
|
||||
w.w = wopts.window
|
||||
w.w.SetDriver(w)
|
||||
views[view] = w
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.Configure(wopts.options)
|
||||
w.w.Event(StageEvent{Stage: StagePaused})
|
||||
w.w.Event(ViewEvent{ViewController: uintptr(controller)})
|
||||
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||
}
|
||||
|
||||
func viewFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
if w.hidden {
|
||||
return
|
||||
}
|
||||
params := C.viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
wasVisible := w.visible
|
||||
w.visible = true
|
||||
if !wasVisible {
|
||||
w.w.Event(StageEvent{Stage: StageRunning})
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
m := unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
}
|
||||
dppp := unit.Dp(1. / m.PxPerDp)
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
@@ -177,26 +193,34 @@ func (w *window) draw(sync bool) {
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.visible = false
|
||||
w.w.Event(StageEvent{Stage: StagePaused})
|
||||
func onStop(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = true
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = false
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
delete(views, view)
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(DestroyEvent{})
|
||||
func onDestroy(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.ProcessEvent(UIKitViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.displayLink = nil
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(view C.CFTypeRef, focus int) {
|
||||
w := views[view]
|
||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
||||
func onFocus(h C.uintptr_t, focus int) {
|
||||
w := viewFor(h)
|
||||
w.config.Focused = focus != 0
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
@@ -206,38 +230,38 @@ func onLowMemory() {
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameUpArrow)
|
||||
func onUpArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDownArrow)
|
||||
func onDownArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameLeftArrow)
|
||||
func onLeftArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameRightArrow)
|
||||
func onRightArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
||||
func onDeleteBackward(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(view, str C.CFTypeRef) {
|
||||
w := views[view]
|
||||
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||
w := viewFor(h)
|
||||
w.w.EditorInsert(nsstringToString(str))
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var kind pointer.Kind
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
@@ -251,10 +275,10 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.C
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := views[view]
|
||||
w := viewFor(h)
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
@@ -267,7 +291,7 @@ func (w *window) ReadClipboard() {
|
||||
cstr := C.readClipboard()
|
||||
defer C.CFRelease(cstr)
|
||||
content := nsstringToString(cstr)
|
||||
w.w.Event(transfer.DataEvent{
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -287,7 +311,7 @@ func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
func (w *window) Configure([]Option) {
|
||||
// Decorations are never disabled.
|
||||
w.config.Decorated = true
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
@@ -295,10 +319,6 @@ func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
@@ -311,7 +331,7 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name key.Name) {
|
||||
w.w.Event(key.Event{
|
||||
w.ProcessEvent(key.Event{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
@@ -350,17 +370,77 @@ func (w *window) ShowTextInput(show bool) {
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
return <-mainWindow.errs
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
var mainMode = mainModeUndefined
|
||||
|
||||
const (
|
||||
mainModeUndefined = iota
|
||||
mainModeExe
|
||||
mainModeLibrary
|
||||
)
|
||||
|
||||
func osMain() {
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeExe
|
||||
var argv []*C.char
|
||||
for _, arg := range os.Args {
|
||||
a := C.CString(arg)
|
||||
defer C.free(unsafe.Pointer(a))
|
||||
argv = append(argv, a)
|
||||
}
|
||||
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||
case mainModeExe:
|
||||
panic("app.Main may be called only once")
|
||||
case mainModeLibrary:
|
||||
// Do nothing, we're embedded as a library.
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
runMain()
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeLibrary
|
||||
runMain()
|
||||
case mainModeExe:
|
||||
// Do nothing, main has already been called.
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (UIKitViewEvent) implementsViewEvent() {}
|
||||
func (UIKitViewEvent) ImplementsEvent() {}
|
||||
func (u UIKitViewEvent) Valid() bool {
|
||||
return u != (UIKitViewEvent{})
|
||||
}
|
||||
|
||||
+51
-22
@@ -11,6 +11,7 @@
|
||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||
|
||||
@interface GioView: UIView <UIKeyInput>
|
||||
@property uintptr_t handle;
|
||||
@end
|
||||
|
||||
@implementation GioViewController
|
||||
@@ -25,12 +26,13 @@ CGFloat _keyboardHeight;
|
||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||
[self.view addSubview: drawView];
|
||||
#ifndef TARGET_OS_TV
|
||||
#if !TARGET_OS_TV
|
||||
drawView.multipleTouchEnabled = YES;
|
||||
#endif
|
||||
drawView.preservesSuperviewLayoutMargins = YES;
|
||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||
#if !TARGET_OS_TV
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillChange:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
@@ -43,6 +45,7 @@ CGFloat _keyboardHeight;
|
||||
selector:@selector(keyboardWillHide:)
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationDidEnterBackground:)
|
||||
name: UIApplicationDidEnterBackgroundNotification
|
||||
@@ -54,33 +57,33 @@ CGFloat _keyboardHeight;
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStart(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
onStop((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStop(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
||||
onDestroy(viewRef);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
onDestroy(view.handle);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
UIView *view = self.view.subviews[0];
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
CGRect frame = self.view.bounds;
|
||||
// Adjust view bounds to make room for the keyboard.
|
||||
frame.size.height -= _keyboardHeight;
|
||||
view.frame = frame;
|
||||
gio_onDraw((__bridge CFTypeRef)view);
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
@@ -88,6 +91,7 @@ CGFloat _keyboardHeight;
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (void)keyboardWillChange:(NSNotification *)note {
|
||||
NSDictionary *userInfo = note.userInfo;
|
||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
@@ -99,13 +103,13 @@ CGFloat _keyboardHeight;
|
||||
_keyboardHeight = 0.0;
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
#endif
|
||||
@end
|
||||
|
||||
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
CGFloat scale = view.contentScaleFactor;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
||||
for (UITouch *touch in touches) {
|
||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||
i++;
|
||||
@@ -116,7 +120,7 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
||||
CGPoint loc = [coalescedTouch locationInView:view];
|
||||
j++;
|
||||
int lastTouch = last && i == n && j == m;
|
||||
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,13 +155,13 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
|
||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, YES);
|
||||
onFocus(self.handle, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, NO);
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +182,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text {
|
||||
onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)text);
|
||||
onText(self.handle, (__bridge CFTypeRef)text);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
@@ -190,23 +194,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward((__bridge CFTypeRef)self);
|
||||
onDeleteBackward(self.handle);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow((__bridge CFTypeRef)self);
|
||||
onUpArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow((__bridge CFTypeRef)self);
|
||||
onDownArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow((__bridge CFTypeRef)self);
|
||||
onLeftArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow((__bridge CFTypeRef)self);
|
||||
onRightArrow(self.handle);
|
||||
}
|
||||
|
||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||
@@ -271,3 +275,28 @@ void gio_showCursor() {
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
|
||||
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@end
|
||||
|
||||
@implementation _gioAppDelegate
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
||||
self.window.rootViewController = controller;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
int gio_applicationMain(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
||||
}
|
||||
}
|
||||
|
||||
+84
-87
@@ -14,8 +14,10 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/internal/f32color"
|
||||
"gioui.org/op"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
@@ -23,7 +25,7 @@ import (
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type JSViewEvent struct {
|
||||
Element js.Value
|
||||
}
|
||||
|
||||
@@ -54,9 +56,6 @@ type window struct {
|
||||
composing bool
|
||||
requestFocus bool
|
||||
|
||||
chanAnimation chan struct{}
|
||||
chanRedraw chan struct{}
|
||||
|
||||
config Config
|
||||
inset f32.Point
|
||||
scale float32
|
||||
@@ -69,7 +68,7 @@ type window struct {
|
||||
contextStatus contextStatus
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
doc := js.Global().Get("document")
|
||||
cont := getContainer(doc)
|
||||
cnv := createCanvas(doc)
|
||||
@@ -84,7 +83,9 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
head: doc.Get("head"),
|
||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
w: win,
|
||||
}
|
||||
w.w.SetDriver(w)
|
||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||
w.browserHistory = w.window.Get("history")
|
||||
w.visualViewport = w.window.Get("visualViewport")
|
||||
@@ -94,15 +95,13 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||
w.screenOrientation = screen.Get("orientation")
|
||||
}
|
||||
w.chanAnimation = make(chan struct{}, 1)
|
||||
w.chanRedraw = make(chan struct{}, 1)
|
||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
w.chanAnimation <- struct{}{}
|
||||
w.draw(false)
|
||||
return nil
|
||||
})
|
||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
content := args[0].String()
|
||||
go win.Event(transfer.DataEvent{
|
||||
w.processEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -112,29 +111,12 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
})
|
||||
w.addEventListeners()
|
||||
w.addHistory()
|
||||
w.w = win
|
||||
|
||||
go func() {
|
||||
defer w.cleanup()
|
||||
w.w.SetDriver(w)
|
||||
w.Configure(options)
|
||||
w.blur()
|
||||
w.w.Event(ViewEvent{Element: cont})
|
||||
w.w.Event(StageEvent{Stage: StageRunning})
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
for {
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(wakeupEvent{})
|
||||
case <-w.chanAnimation:
|
||||
w.animCallback()
|
||||
case <-w.chanRedraw:
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
w.Configure(options)
|
||||
w.blur()
|
||||
w.processEvent(JSViewEvent{Element: cont})
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func getContainer(doc js.Value) js.Value {
|
||||
@@ -194,12 +176,12 @@ func (w *window) addEventListeners() {
|
||||
w.cnv.Set("width", 0)
|
||||
w.cnv.Set("height", 0)
|
||||
w.resize()
|
||||
w.requestRedraw()
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||
w.resize()
|
||||
w.requestRedraw()
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||
@@ -207,22 +189,11 @@ func (w *window) addEventListeners() {
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||
if w.w.Event(key.Event{Name: key.NameBack}) {
|
||||
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||
return w.browserHistory.Call("forward")
|
||||
}
|
||||
return w.browserHistory.Call("back")
|
||||
})
|
||||
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
||||
ev := StageEvent{}
|
||||
switch w.document.Get("visibilityState").String() {
|
||||
case "hidden", "prerender", "unloaded":
|
||||
ev.Stage = StagePaused
|
||||
default:
|
||||
ev.Stage = StageRunning
|
||||
}
|
||||
w.w.Event(ev)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||
return nil
|
||||
@@ -280,18 +251,20 @@ func (w *window) addEventListeners() {
|
||||
w.touches[i] = js.Null()
|
||||
}
|
||||
w.touches = w.touches[:0]
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
w.blur()
|
||||
return nil
|
||||
})
|
||||
@@ -380,10 +353,50 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||
Modifiers: modifiersFor(e),
|
||||
State: ks,
|
||||
}
|
||||
w.w.Event(cmd)
|
||||
w.processEvent(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.processEvent(e)
|
||||
}
|
||||
|
||||
func (w *window) processEvent(e event.Event) bool {
|
||||
if !w.w.ProcessEvent(e) {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if ok {
|
||||
if _, destroy := evt.(DestroyEvent); destroy {
|
||||
w.cleanup()
|
||||
}
|
||||
return evt
|
||||
}
|
||||
<-w.wakeups
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.w.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||
// KeyEvent.
|
||||
func modifiersFor(e js.Value) key.Modifiers {
|
||||
@@ -431,7 +444,7 @@ func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
Position: pos,
|
||||
@@ -481,7 +494,7 @@ func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
||||
if jbtns&4 != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: btns,
|
||||
@@ -508,17 +521,6 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
||||
return jsf
|
||||
}
|
||||
|
||||
func (w *window) animCallback() {
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
@@ -574,7 +576,7 @@ func (w *window) Configure(options []Option) {
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) Perform(system.Action) {}
|
||||
@@ -613,23 +615,14 @@ func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
style.Set("cursor", webCursor[cursor])
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
// Run in a goroutine to avoid a deadlock if the
|
||||
// focus change result in an event.
|
||||
go func() {
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}()
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
@@ -646,7 +639,7 @@ func (w *window) resize() {
|
||||
}
|
||||
if size != w.config.Size {
|
||||
w.config.Size = size
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||
@@ -666,12 +659,19 @@ func (w *window) draw(sync bool) {
|
||||
if w.contextStatus == contextStatusLost {
|
||||
return
|
||||
}
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
} else if !sync {
|
||||
return
|
||||
}
|
||||
size, insets, metric := w.getConfig()
|
||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.w.Event(frameEvent{
|
||||
w.processEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: size,
|
||||
@@ -741,13 +741,6 @@ func (w *window) navigationColor(c color.NRGBA) {
|
||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||
}
|
||||
|
||||
func (w *window) requestRedraw() {
|
||||
select {
|
||||
case w.chanRedraw <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
}
|
||||
@@ -827,4 +820,8 @@ func translateKey(k string) (key.Name, bool) {
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (JSViewEvent) implementsViewEvent() {}
|
||||
func (JSViewEvent) ImplementsEvent() {}
|
||||
func (j JSViewEvent) Valid() bool {
|
||||
return !(j.Element.IsNull() || j.Element.IsUndefined())
|
||||
}
|
||||
|
||||
+278
-206
@@ -10,16 +10,19 @@ import (
|
||||
"image"
|
||||
"io"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gioui.org/internal/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
_ "gioui.org/internal/cocoainit"
|
||||
@@ -37,8 +40,9 @@ import (
|
||||
#define MOUSE_SCROLL 4
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(int presentWithTrans);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
static void writeClipboard(CFTypeRef str) {
|
||||
@autoreleasepool {
|
||||
@@ -58,148 +62,212 @@ static CFTypeRef readClipboard(void) {
|
||||
}
|
||||
|
||||
static CGFloat viewHeight(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.height;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.height;
|
||||
}
|
||||
}
|
||||
|
||||
static CGFloat viewWidth(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.width;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.width;
|
||||
}
|
||||
}
|
||||
|
||||
static CGFloat getScreenBackingScale(void) {
|
||||
return [NSScreen.mainScreen backingScaleFactor];
|
||||
@autoreleasepool {
|
||||
return [NSScreen.mainScreen backingScaleFactor];
|
||||
}
|
||||
}
|
||||
|
||||
static CGFloat getViewBackingScale(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view.window backingScaleFactor];
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view.window backingScaleFactor];
|
||||
}
|
||||
}
|
||||
|
||||
static void setNeedsDisplay(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setNeedsDisplay:YES];
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window cascadeTopLeftFromPoint:topLeft];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window cascadeTopLeftFromPoint:topLeft];
|
||||
}
|
||||
}
|
||||
|
||||
static void makeKeyAndOrderFront(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static void toggleFullScreen(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window toggleFullScreen:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window toggleFullScreen:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window styleMask];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window styleMask];
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.styleMask = mask;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.styleMask = mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titleVisibility = state;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titleVisibility = state;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titlebarAppearsTransparent = (BOOL)transparent;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.titlebarAppearsTransparent = (BOOL)transparent;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window standardWindowButton:btn].hidden = (BOOL)hide;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window standardWindowButton:btn].hidden = (BOOL)hide;
|
||||
}
|
||||
}
|
||||
|
||||
static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window performWindowDragWithEvent:(__bridge NSEvent*)evt];
|
||||
}
|
||||
}
|
||||
|
||||
static void closeWindow(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window performClose:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window performClose:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSSize size = NSMakeSize(width, height);
|
||||
[window setContentSize:size];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSSize size = NSMakeSize(width, height);
|
||||
[window setContentSize:size];
|
||||
}
|
||||
}
|
||||
|
||||
static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMinSize = NSMakeSize(width, height);
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMinSize = NSMakeSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMaxSize = NSMakeSize(width, height);
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMaxSize = NSMakeSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSRect r = NSMakeRect(x, y, w, h);
|
||||
[window setFrame:r display:YES];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSRect r = NSMakeRect(x, y, w, h);
|
||||
[window setFrame:r display:YES];
|
||||
}
|
||||
}
|
||||
|
||||
static void resetLayerFrame(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
NSView* view = (__bridge NSView *)viewRef;
|
||||
NSRect r = view.frame;
|
||||
view.layer.frame = r;
|
||||
}
|
||||
}
|
||||
|
||||
static void hideWindow(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window miniaturize:window];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window miniaturize:window];
|
||||
}
|
||||
}
|
||||
|
||||
static void unhideWindow(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window deminiaturize:window];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window deminiaturize:window];
|
||||
}
|
||||
}
|
||||
|
||||
static NSRect getScreenFrame(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
return [[window screen] frame];
|
||||
@autoreleasepool {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
return [[window screen] frame];
|
||||
}
|
||||
}
|
||||
|
||||
static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.title = (__bridge NSString *)titleRef;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
window.title = (__bridge NSString *)titleRef;
|
||||
}
|
||||
}
|
||||
|
||||
static int isWindowZoomed(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return window.zoomed ? 1 : 0;
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return window.zoomed ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void zoomWindow(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window zoom:nil];
|
||||
@autoreleasepool {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window zoom:nil];
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef layerForView(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.layer;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.layer;
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef windowForView(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.window;
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.window;
|
||||
}
|
||||
}
|
||||
|
||||
static void raiseWindow(CFTypeRef windowRef) {
|
||||
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
|
||||
if (![currentApp isActive]) {
|
||||
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||
@autoreleasepool {
|
||||
NSRunningApplication *currentApp = [NSRunningApplication currentApplication];
|
||||
if (![currentApp isActive]) {
|
||||
[currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||
}
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
static CFTypeRef createInputContext(CFTypeRef clientRef) {
|
||||
@@ -211,23 +279,23 @@ static CFTypeRef createInputContext(CFTypeRef clientRef) {
|
||||
}
|
||||
|
||||
static void discardMarkedText(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
@autoreleasepool {
|
||||
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
||||
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
||||
if (view == [ctx client]) {
|
||||
[ctx discardMarkedText];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void invalidateCharacterCoordinates(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
@autoreleasepool {
|
||||
id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef;
|
||||
NSTextInputContext *ctx = [NSTextInputContext currentInputContext];
|
||||
if (view == [ctx client]) {
|
||||
[ctx invalidateCharacterCoordinates];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
@@ -237,9 +305,9 @@ func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
// ViewEvent notified the client of changes to the window AppKit handles.
|
||||
// The handles are retained until another ViewEvent is sent.
|
||||
type ViewEvent struct {
|
||||
// AppKitViewEvent notifies the client of changes to the window AppKit handles.
|
||||
// The handles are retained until another AppKitViewEvent is sent.
|
||||
type AppKitViewEvent struct {
|
||||
// View is a CFTypeRef for the NSView for the window.
|
||||
View uintptr
|
||||
// Layer is a CFTypeRef of the CALayer of View.
|
||||
@@ -249,21 +317,20 @@ type ViewEvent struct {
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
stage Stage
|
||||
anim bool
|
||||
visible bool
|
||||
displayLink *displayLink
|
||||
// redraw is a single entry channel for making sure only one
|
||||
// display link redraw request is in flight.
|
||||
redraw chan struct{}
|
||||
cursor pointer.Cursor
|
||||
pointerBtns pointer.Buttons
|
||||
loop *eventLoop
|
||||
|
||||
scale float32
|
||||
config Config
|
||||
}
|
||||
|
||||
// viewMap is the mapping from Cocoa NSViews to Go windows.
|
||||
var viewMap = make(map[C.CFTypeRef]*window)
|
||||
|
||||
// launched is closed when applicationDidFinishLaunching is called.
|
||||
var launched = make(chan struct{})
|
||||
|
||||
@@ -271,30 +338,8 @@ var launched = make(chan struct{})
|
||||
// cascadeTopLeftFromPoint.
|
||||
var nextTopLeft C.NSPoint
|
||||
|
||||
// mustView is like lookupView, except that it panics
|
||||
// if the view isn't mapped.
|
||||
func mustView(view C.CFTypeRef) *window {
|
||||
w, ok := lookupView(view)
|
||||
if !ok {
|
||||
panic("no window for view")
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func lookupView(view C.CFTypeRef) (*window, bool) {
|
||||
w, exists := viewMap[view]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
return w, true
|
||||
}
|
||||
|
||||
func deleteView(view C.CFTypeRef) {
|
||||
delete(viewMap, view)
|
||||
}
|
||||
|
||||
func insertView(view C.CFTypeRef, w *window) {
|
||||
viewMap[view] = w
|
||||
func windowFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
func (w *window) contextView() C.CFTypeRef {
|
||||
@@ -307,7 +352,7 @@ func (w *window) ReadClipboard() {
|
||||
defer C.CFRelease(cstr)
|
||||
}
|
||||
content := nsstringToString(cstr)
|
||||
w.w.Event(transfer.DataEvent{
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -419,8 +464,11 @@ func (w *window) Configure(options []Option) {
|
||||
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
|
||||
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
|
||||
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
|
||||
// When toggling the titlebar, the layer doesn't update its frame
|
||||
// until the next resize. Force it.
|
||||
C.resetLayerFrame(w.view)
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) setTitle(prev, cnf Config) {
|
||||
@@ -471,7 +519,8 @@ func (w *window) ShowTextInput(show bool) {}
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.anim = anim
|
||||
if w.anim && w.visible {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
@@ -488,26 +537,18 @@ func (w *window) runOnMain(f func()) {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.w.Event(StageEvent{Stage: stage})
|
||||
}
|
||||
|
||||
//export gio_onKeys
|
||||
func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
||||
func gio_onKeys(h C.uintptr_t, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
||||
str := nsstringToString(cstr)
|
||||
kmods := convertMods(mods)
|
||||
ks := key.Release
|
||||
if keyDown {
|
||||
ks = key.Press
|
||||
}
|
||||
w := mustView(view)
|
||||
w := windowFor(h)
|
||||
for _, k := range str {
|
||||
if n, ok := convertKey(k); ok {
|
||||
w.w.Event(key.Event{
|
||||
w.ProcessEvent(key.Event{
|
||||
Name: n,
|
||||
Modifiers: kmods,
|
||||
State: ks,
|
||||
@@ -517,15 +558,15 @@ func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown
|
||||
}
|
||||
|
||||
//export gio_onText
|
||||
func gio_onText(view, cstr C.CFTypeRef) {
|
||||
func gio_onText(h C.uintptr_t, cstr C.CFTypeRef) {
|
||||
str := nsstringToString(cstr)
|
||||
w := mustView(view)
|
||||
w := windowFor(h)
|
||||
w.w.EditorInsert(str)
|
||||
}
|
||||
|
||||
//export gio_onMouse
|
||||
func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
|
||||
w := mustView(view)
|
||||
func gio_onMouse(h C.uintptr_t, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
|
||||
w := windowFor(h)
|
||||
t := time.Duration(float64(ti)*float64(time.Second) + .5)
|
||||
xf, yf := float32(x)*w.scale, float32(y)*w.scale
|
||||
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
|
||||
@@ -562,7 +603,7 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
|
||||
default:
|
||||
panic("invalid direction")
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: typ,
|
||||
Source: pointer.Mouse,
|
||||
Time: t,
|
||||
@@ -574,35 +615,29 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.draw()
|
||||
}
|
||||
|
||||
//export gio_onFocus
|
||||
func gio_onFocus(view C.CFTypeRef, focus C.int) {
|
||||
w := mustView(view)
|
||||
w.w.Event(key.FocusEvent{Focus: focus == 1})
|
||||
if w.stage >= StageInactive {
|
||||
if focus == 0 {
|
||||
w.setStage(StageInactive)
|
||||
} else {
|
||||
w.setStage(StageRunning)
|
||||
}
|
||||
}
|
||||
func gio_onFocus(h C.uintptr_t, focus C.int) {
|
||||
w := windowFor(h)
|
||||
w.SetCursor(w.cursor)
|
||||
w.config.Focused = focus == 1
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onChangeScreen
|
||||
func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
|
||||
w := mustView(view)
|
||||
func gio_onChangeScreen(h C.uintptr_t, did uint64) {
|
||||
w := windowFor(h)
|
||||
w.displayLink.SetDisplayID(did)
|
||||
C.setNeedsDisplay(w.view)
|
||||
}
|
||||
|
||||
//export gio_hasMarkedText
|
||||
func gio_hasMarkedText(view C.CFTypeRef) C.int {
|
||||
w := mustView(view)
|
||||
func gio_hasMarkedText(h C.uintptr_t) C.int {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
if state.compose.Start != -1 {
|
||||
return 1
|
||||
@@ -611,8 +646,8 @@ func gio_hasMarkedText(view C.CFTypeRef) C.int {
|
||||
}
|
||||
|
||||
//export gio_markedRange
|
||||
func gio_markedRange(view C.CFTypeRef) C.NSRange {
|
||||
w := mustView(view)
|
||||
func gio_markedRange(h C.uintptr_t) C.NSRange {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
rng := state.compose
|
||||
start, end := rng.Start, rng.End
|
||||
@@ -627,8 +662,8 @@ func gio_markedRange(view C.CFTypeRef) C.NSRange {
|
||||
}
|
||||
|
||||
//export gio_selectedRange
|
||||
func gio_selectedRange(view C.CFTypeRef) C.NSRange {
|
||||
w := mustView(view)
|
||||
func gio_selectedRange(h C.uintptr_t) C.NSRange {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
rng := state.Selection
|
||||
start, end := rng.Start, rng.End
|
||||
@@ -643,14 +678,14 @@ func gio_selectedRange(view C.CFTypeRef) C.NSRange {
|
||||
}
|
||||
|
||||
//export gio_unmarkText
|
||||
func gio_unmarkText(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_unmarkText(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
||||
}
|
||||
|
||||
//export gio_setMarkedText
|
||||
func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
|
||||
w := mustView(view)
|
||||
func gio_setMarkedText(h C.uintptr_t, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) {
|
||||
w := windowFor(h)
|
||||
str := nsstringToString(cstr)
|
||||
state := w.w.EditorState()
|
||||
rng := state.compose
|
||||
@@ -689,8 +724,8 @@ func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange
|
||||
}
|
||||
|
||||
//export gio_substringForProposedRange
|
||||
func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
|
||||
w := mustView(view)
|
||||
func gio_substringForProposedRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
start, end := state.Snippet.Start, state.Snippet.End
|
||||
if start > end {
|
||||
@@ -710,8 +745,8 @@ func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NS
|
||||
}
|
||||
|
||||
//export gio_insertText
|
||||
func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
|
||||
w := mustView(view)
|
||||
func gio_insertText(h C.uintptr_t, cstr C.CFTypeRef, crng C.NSRange) {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
rng := state.compose
|
||||
if rng.Start == -1 {
|
||||
@@ -735,13 +770,13 @@ func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) {
|
||||
}
|
||||
|
||||
//export gio_characterIndexForPoint
|
||||
func gio_characterIndexForPoint(view C.CFTypeRef, p C.NSPoint) C.NSUInteger {
|
||||
func gio_characterIndexForPoint(h C.uintptr_t, p C.NSPoint) C.NSUInteger {
|
||||
return C.NSNotFound
|
||||
}
|
||||
|
||||
//export gio_firstRectForCharacterRange
|
||||
func gio_firstRectForCharacterRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
|
||||
w := mustView(view)
|
||||
func gio_firstRectForCharacterRange(h C.uintptr_t, crng C.NSRange, actual C.NSRangePointer) C.NSRect {
|
||||
w := windowFor(h)
|
||||
state := w.w.EditorState()
|
||||
sel := state.Selection
|
||||
u16start := state.UTF16Index(sel.Start)
|
||||
@@ -768,6 +803,10 @@ func (w *window) draw() {
|
||||
case <-w.redraw:
|
||||
default:
|
||||
}
|
||||
w.visible = true
|
||||
if w.anim {
|
||||
w.SetAnimating(w.anim)
|
||||
}
|
||||
w.scale = float32(C.getViewBackingScale(w.view))
|
||||
wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
|
||||
sz := image.Point{
|
||||
@@ -776,14 +815,13 @@ func (w *window) draw() {
|
||||
}
|
||||
if sz != w.config.Size {
|
||||
w.config.Size = sz
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
if sz.X == 0 || sz.Y == 0 {
|
||||
return
|
||||
}
|
||||
cfg := configFor(w.scale)
|
||||
w.setStage(StageRunning)
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
@@ -793,6 +831,27 @@ func (w *window) draw() {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func configFor(scale float32) unit.Metric {
|
||||
return unit.Metric{
|
||||
PxPerDp: scale,
|
||||
@@ -800,56 +859,54 @@ func configFor(scale float32) unit.Metric {
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onClose
|
||||
func gio_onClose(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(DestroyEvent{})
|
||||
//export gio_onAttached
|
||||
func gio_onAttached(h C.uintptr_t, attached C.int) {
|
||||
w := windowFor(h)
|
||||
if attached != 0 {
|
||||
layer := C.layerForView(w.view)
|
||||
w.ProcessEvent(AppKitViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
||||
} else {
|
||||
w.ProcessEvent(AppKitViewEvent{})
|
||||
w.visible = false
|
||||
w.SetAnimating(w.anim)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onDestroy
|
||||
func gio_onDestroy(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.displayLink = nil
|
||||
deleteView(view)
|
||||
C.CFRelease(w.view)
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export gio_onHide
|
||||
func gio_onHide(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(StagePaused)
|
||||
func gio_onHide(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.visible = false
|
||||
w.SetAnimating(w.anim)
|
||||
}
|
||||
|
||||
//export gio_onShow
|
||||
func gio_onShow(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(StageRunning)
|
||||
func gio_onShow(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.draw()
|
||||
}
|
||||
|
||||
//export gio_onFullscreen
|
||||
func gio_onFullscreen(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_onFullscreen(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.config.Mode = Fullscreen
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onWindowed
|
||||
func gio_onWindowed(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
func gio_onWindowed(h C.uintptr_t) {
|
||||
w := windowFor(h)
|
||||
w.config.Mode = Windowed
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onAppHide
|
||||
func gio_onAppHide() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(StagePaused)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onAppShow
|
||||
func gio_onAppShow() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(StageRunning)
|
||||
}
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onFinishLaunching
|
||||
@@ -857,20 +914,27 @@ func gio_onFinishLaunching() {
|
||||
close(launched)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) error {
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
<-launched
|
||||
errch := make(chan error)
|
||||
res := make(chan struct{})
|
||||
runOnMain(func() {
|
||||
w, err := newOSWindow()
|
||||
if err != nil {
|
||||
errch <- err
|
||||
w := &window{
|
||||
redraw: make(chan struct{}, 1),
|
||||
w: win,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
win.SetDriver(w)
|
||||
res <- struct{}{}
|
||||
var cnf Config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
if err := w.init(cnf.CustomRenderer); err != nil {
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
errch <- nil
|
||||
w.w = win
|
||||
window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
|
||||
// Release our reference now that the NSWindow has it.
|
||||
C.CFRelease(w.view)
|
||||
w.updateWindowMode()
|
||||
win.SetDriver(w)
|
||||
w.Configure(options)
|
||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||
@@ -880,23 +944,21 @@ func newWindow(win *callbacks, options []Option) error {
|
||||
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
|
||||
// makeKeyAndOrderFront assumes ownership of our window reference.
|
||||
C.makeKeyAndOrderFront(window)
|
||||
layer := C.layerForView(w.view)
|
||||
w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
|
||||
})
|
||||
return <-errch
|
||||
<-res
|
||||
}
|
||||
|
||||
func newOSWindow() (*window, error) {
|
||||
view := C.gio_createView()
|
||||
func (w *window) init(customRenderer bool) error {
|
||||
presentWithTrans := 1
|
||||
if customRenderer {
|
||||
presentWithTrans = 0
|
||||
}
|
||||
view := C.gio_createView(C.int(presentWithTrans))
|
||||
if view == 0 {
|
||||
return nil, errors.New("newOSWindows: failed to create view")
|
||||
return errors.New("newOSWindow: failed to create view")
|
||||
}
|
||||
scale := float32(C.getViewBackingScale(view))
|
||||
w := &window{
|
||||
view: view,
|
||||
scale: scale,
|
||||
redraw: make(chan struct{}, 1),
|
||||
}
|
||||
w.scale = scale
|
||||
dl, err := newDisplayLink(func() {
|
||||
select {
|
||||
case w.redraw <- struct{}{}:
|
||||
@@ -904,19 +966,25 @@ func newOSWindow() (*window, error) {
|
||||
return
|
||||
}
|
||||
w.runOnMain(func() {
|
||||
C.setNeedsDisplay(w.view)
|
||||
if w.visible {
|
||||
C.setNeedsDisplay(w.view)
|
||||
}
|
||||
})
|
||||
})
|
||||
w.displayLink = dl
|
||||
if err != nil {
|
||||
C.CFRelease(view)
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
insertView(view, w)
|
||||
return w, nil
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.view = view
|
||||
return nil
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
if !isMainThread() {
|
||||
panic("app.Main must run on the main goroutine")
|
||||
}
|
||||
C.gio_main()
|
||||
}
|
||||
|
||||
@@ -1004,4 +1072,8 @@ func convertMods(mods C.NSUInteger) key.Modifiers {
|
||||
return kmods
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (AppKitViewEvent) implementsViewEvent() {}
|
||||
func (AppKitViewEvent) ImplementsEvent() {}
|
||||
func (a AppKitViewEvent) Valid() bool {
|
||||
return a != (AppKitViewEvent{})
|
||||
}
|
||||
|
||||
+89
-53
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
@@ -14,40 +14,55 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@property uintptr_t handle;
|
||||
@property BOOL presentWithTrans;
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onHide(view.handle);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onShow(view.handle);
|
||||
}
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFullscreen((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onFullscreen(view.handle);
|
||||
}
|
||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onWindowed((__bridge CFTypeRef)window.contentView);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onWindowed(view.handle);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
||||
gio_onChangeScreen(view, dispID);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onChangeScreen(view.handle, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 1);
|
||||
}
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 0);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||
if (!event.hasPreciseScrollingDeltas) {
|
||||
// dx and dy are in rows and columns.
|
||||
@@ -56,12 +71,9 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
}
|
||||
// Origin is in the lower left corner. Convert to upper left.
|
||||
CGFloat height = view.bounds.size.height;
|
||||
gio_onMouse((__bridge CFTypeRef)view, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@end
|
||||
|
||||
@implementation GioView
|
||||
- (void)setFrameSize:(NSSize)newSize {
|
||||
[super setFrameSize:newSize];
|
||||
@@ -70,21 +82,19 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||
// Don't know why.
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)displayLayer:(CALayer *)layer {
|
||||
layer.contentsScale = self.window.backingScaleFactor;
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (CALayer *)makeBackingLayer {
|
||||
CALayer *layer = gio_layerFactory();
|
||||
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||
layer.delegate = self;
|
||||
return layer;
|
||||
}
|
||||
- (void)viewDidMoveToWindow {
|
||||
if (self.window == nil) {
|
||||
gio_onClose((__bridge CFTypeRef)self);
|
||||
}
|
||||
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
@@ -124,14 +134,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||
}
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
gio_onText((__bridge CFTypeRef)self, (__bridge CFTypeRef)string);
|
||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)sel {
|
||||
// Don't pass commands up the responder chain.
|
||||
@@ -139,17 +149,17 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText {
|
||||
int res = gio_hasMarkedText((__bridge CFTypeRef)self);
|
||||
int res = gio_hasMarkedText(self.handle);
|
||||
return res ? YES : NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return gio_markedRange((__bridge CFTypeRef)self);
|
||||
return gio_markedRange(self.handle);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return gio_selectedRange((__bridge CFTypeRef)self);
|
||||
return gio_selectedRange(self.handle);
|
||||
}
|
||||
- (void)unmarkText {
|
||||
gio_unmarkText((__bridge CFTypeRef)self);
|
||||
gio_unmarkText(self.handle);
|
||||
}
|
||||
- (void)setMarkedText:(id)string
|
||||
selectedRange:(NSRange)selRange
|
||||
@@ -161,14 +171,14 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_setMarkedText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return nil;
|
||||
}
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange((__bridge CFTypeRef)self, range, actualRange));
|
||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||
}
|
||||
- (void)insertText:(id)string
|
||||
@@ -180,17 +190,34 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_insertText((__bridge CFTypeRef)self, (__bridge CFTypeRef)str, replaceRange);
|
||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||
return gio_characterIndexForPoint((__bridge CFTypeRef)self, p);
|
||||
return gio_characterIndexForPoint(self.handle, p);
|
||||
}
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||
actualRange:(NSRangePointer)actual {
|
||||
NSRect r = gio_firstRectForCharacterRange((__bridge CFTypeRef)self, rng, actual);
|
||||
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
||||
r = [self convertRect:r toView:nil];
|
||||
return [[self window] convertRectToScreen:r];
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onShow(self.handle);
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)notification {
|
||||
gio_onHide(self.handle);
|
||||
}
|
||||
- (void)dealloc {
|
||||
gio_onDestroy(self.handle);
|
||||
}
|
||||
- (BOOL) becomeFirstResponder {
|
||||
gio_onFocus(self.handle, 1);
|
||||
return [super becomeFirstResponder];
|
||||
}
|
||||
- (BOOL) resignFirstResponder {
|
||||
gio_onFocus(self.handle, 0);
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
@end
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
@@ -240,7 +267,7 @@ void gio_showCursor() {
|
||||
|
||||
// some cursors are not public, this tries to use a private cursor
|
||||
// and uses fallback when the use of private cursor fails.
|
||||
void gio_trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||
if ([NSCursor respondsToSelector:cursorName]) {
|
||||
id object = [NSCursor performSelector:cursorName];
|
||||
if ([object isKindOfClass:[NSCursor class]]) {
|
||||
@@ -272,7 +299,7 @@ void gio_setCursor(NSUInteger curID) {
|
||||
break;
|
||||
case 6: // pointer.CursorAllScroll
|
||||
// For some reason, using _moveCursor fails on Monterey.
|
||||
// gio_trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 7: // pointer.CursorColResize
|
||||
@@ -282,33 +309,31 @@ void gio_setCursor(NSUInteger curID) {
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 9: // pointer.CursorGrab
|
||||
// [NSCursor.openHandCursor set];
|
||||
gio_trySetPrivateCursor(@selector(openHandCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
case 10: // pointer.CursorGrabbing
|
||||
// [NSCursor.closedHandCursor set];
|
||||
gio_trySetPrivateCursor(@selector(closedHandCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.closedHandCursor set];
|
||||
break;
|
||||
case 11: // pointer.CursorNotAllowed
|
||||
[NSCursor.operationNotAllowedCursor set];
|
||||
break;
|
||||
case 12: // pointer.CursorWait
|
||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 13: // pointer.CursorProgress
|
||||
gio_trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 14: // pointer.CursorNorthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 15: // pointer.CursorNorthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 16: // pointer.CursorSouthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 17: // pointer.CursorSouthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 18: // pointer.CursorNorthSouthResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
@@ -329,10 +354,10 @@ void gio_setCursor(NSUInteger curID) {
|
||||
[NSCursor.resizeDownCursor set];
|
||||
break;
|
||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||
gio_trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
@@ -368,28 +393,39 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createView(void) {
|
||||
CFTypeRef gio_createView(int presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||
view.wantsLayer = YES;
|
||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationWillUnhide:)
|
||||
name:NSApplicationWillUnhideNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationDidHide:)
|
||||
name:NSApplicationDidHideNotification
|
||||
object:nil];
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
@autoreleasepool {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
||||
gio_onAppHide();
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onAppShow();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_main() {
|
||||
|
||||
+11
-12
@@ -12,13 +12,6 @@ import (
|
||||
"gioui.org/io/pointer"
|
||||
)
|
||||
|
||||
// ViewEvent provides handles to the underlying window objects for the
|
||||
// current display protocol.
|
||||
type ViewEvent interface {
|
||||
implementsViewEvent()
|
||||
ImplementsEvent()
|
||||
}
|
||||
|
||||
type X11ViewEvent struct {
|
||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||
Display unsafe.Pointer
|
||||
@@ -28,6 +21,9 @@ type X11ViewEvent struct {
|
||||
|
||||
func (X11ViewEvent) implementsViewEvent() {}
|
||||
func (X11ViewEvent) ImplementsEvent() {}
|
||||
func (x X11ViewEvent) Valid() bool {
|
||||
return x != (X11ViewEvent{})
|
||||
}
|
||||
|
||||
type WaylandViewEvent struct {
|
||||
// Display is the *wl_display returned by wl_display_connect.
|
||||
@@ -38,6 +34,9 @@ type WaylandViewEvent struct {
|
||||
|
||||
func (WaylandViewEvent) implementsViewEvent() {}
|
||||
func (WaylandViewEvent) ImplementsEvent() {}
|
||||
func (w WaylandViewEvent) Valid() bool {
|
||||
return w != (WaylandViewEvent{})
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
@@ -49,7 +48,7 @@ type windowDriver func(*callbacks, []Option) error
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver windowDriver
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||
if d == nil {
|
||||
@@ -57,16 +56,16 @@ func newWindow(window *callbacks, options []Option) error {
|
||||
}
|
||||
err := d(window, options)
|
||||
if err == nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst != nil {
|
||||
return errFirst
|
||||
if errFirst == nil {
|
||||
errFirst = errors.New("app: no window driver available")
|
||||
}
|
||||
return errors.New("app: no window driver available")
|
||||
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||
}
|
||||
|
||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||
|
||||
+167
-112
@@ -15,6 +15,7 @@ import (
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -25,10 +26,12 @@ import (
|
||||
"gioui.org/app/internal/xkb"
|
||||
"gioui.org/f32"
|
||||
"gioui.org/internal/fling"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
@@ -97,7 +100,9 @@ type wlDisplay struct {
|
||||
read, write int
|
||||
}
|
||||
|
||||
repeat repeatState
|
||||
repeat repeatState
|
||||
poller poller
|
||||
readClipClose chan struct{}
|
||||
}
|
||||
|
||||
type wlSeat struct {
|
||||
@@ -137,7 +142,7 @@ type repeatState struct {
|
||||
delay time.Duration
|
||||
|
||||
key uint32
|
||||
win *callbacks
|
||||
win *window
|
||||
stopC chan struct{}
|
||||
|
||||
start time.Duration
|
||||
@@ -194,12 +199,10 @@ type window struct {
|
||||
dir f32.Point
|
||||
}
|
||||
|
||||
stage Stage
|
||||
dead bool
|
||||
configured bool
|
||||
lastFrameCallback *C.struct_wl_callback
|
||||
|
||||
animating bool
|
||||
redraw bool
|
||||
// The most recent configure serial waiting to be ack'ed.
|
||||
serial C.uint32_t
|
||||
scale int
|
||||
@@ -212,6 +215,8 @@ type window struct {
|
||||
clipReads chan transfer.DataEvent
|
||||
|
||||
wakeups chan struct{}
|
||||
|
||||
closing bool
|
||||
}
|
||||
|
||||
type poller struct {
|
||||
@@ -260,25 +265,17 @@ func newWLWindow(callbacks *callbacks, options []Option) error {
|
||||
return err
|
||||
}
|
||||
w.w = callbacks
|
||||
go func() {
|
||||
defer d.destroy()
|
||||
defer w.destroy()
|
||||
w.w.SetDriver(w)
|
||||
|
||||
w.w.SetDriver(w)
|
||||
// Finish and commit setup from createNativeWindow.
|
||||
w.Configure(options)
|
||||
w.draw(true)
|
||||
C.wl_surface_commit(w.surf)
|
||||
|
||||
// Finish and commit setup from createNativeWindow.
|
||||
w.Configure(options)
|
||||
C.wl_surface_commit(w.surf)
|
||||
|
||||
w.w.Event(WaylandViewEvent{
|
||||
Display: unsafe.Pointer(w.display()),
|
||||
Surface: unsafe.Pointer(w.surf),
|
||||
})
|
||||
|
||||
err := w.loop()
|
||||
w.w.Event(WaylandViewEvent{})
|
||||
w.w.Event(DestroyEvent{Err: err})
|
||||
}()
|
||||
w.ProcessEvent(WaylandViewEvent{
|
||||
Display: unsafe.Pointer(w.display()),
|
||||
Surface: unsafe.Pointer(w.surf),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -549,15 +546,15 @@ func gio_onSeatName(data unsafe.Pointer, seat *C.struct_wl_seat, name *C.char) {
|
||||
func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface, serial C.uint32_t) {
|
||||
w := callbackLoad(data).(*window)
|
||||
w.serial = serial
|
||||
w.redraw = true
|
||||
C.xdg_surface_ack_configure(wmSurf, serial)
|
||||
w.setStage(StageRunning)
|
||||
w.configured = true
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export gio_onToplevelClose
|
||||
func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
|
||||
w := callbackLoad(data).(*window)
|
||||
w.dead = true
|
||||
w.closing = true
|
||||
}
|
||||
|
||||
//export gio_onToplevelConfigure
|
||||
@@ -586,8 +583,8 @@ func gio_onToplevelDecorationConfigure(data unsafe.Pointer, deco *C.struct_zxdg_
|
||||
} else {
|
||||
w.size.Y += int(w.config.decoHeight)
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.redraw = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,7 +642,7 @@ func gio_onSurfaceEnter(data unsafe.Pointer, surf *C.struct_wl_surface, output *
|
||||
if w.config.Mode == Minimized {
|
||||
// Minimized window got brought back up: it is no longer so.
|
||||
w.config.Mode = Windowed
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,7 +787,7 @@ func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.
|
||||
X: fromFixed(x) * float32(w.scale),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Press,
|
||||
Source: pointer.Touch,
|
||||
Position: w.lastTouch,
|
||||
@@ -806,7 +803,7 @@ func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.ui
|
||||
s.serial = serial
|
||||
w := s.touchFoci[id]
|
||||
delete(s.touchFoci, id)
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Release,
|
||||
Source: pointer.Touch,
|
||||
Position: w.lastTouch,
|
||||
@@ -824,7 +821,7 @@ func gio_onTouchMotion(data unsafe.Pointer, touch *C.struct_wl_touch, t C.uint32
|
||||
X: fromFixed(x) * float32(w.scale),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Position: w.lastTouch,
|
||||
Source: pointer.Touch,
|
||||
@@ -843,7 +840,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
|
||||
s := callbackLoad(data).(*wlSeat)
|
||||
for id, w := range s.touchFoci {
|
||||
delete(s.touchFoci, id)
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
@@ -869,7 +866,7 @@ func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.ui
|
||||
s.serial = serial
|
||||
if w.inCompositor {
|
||||
w.inCompositor = false
|
||||
w.w.Event(pointer.Event{Kind: pointer.Cancel})
|
||||
w.ProcessEvent(pointer.Event{Kind: pointer.Cancel})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,7 +927,7 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t,
|
||||
}
|
||||
w.flushScroll()
|
||||
w.resetFling()
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -1018,8 +1015,11 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
if w.disp.readClipClose != nil {
|
||||
return
|
||||
}
|
||||
w.disp.readClipClose = make(chan struct{})
|
||||
r, err := w.disp.readClipboard()
|
||||
// Send empty responses on unavailable clipboards or errors.
|
||||
if r == nil || err != nil {
|
||||
return
|
||||
}
|
||||
@@ -1027,13 +1027,17 @@ func (w *window) ReadClipboard() {
|
||||
go func() {
|
||||
defer r.Close()
|
||||
data, _ := io.ReadAll(r)
|
||||
w.clipReads <- transfer.DataEvent{
|
||||
e := transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(bytes.NewReader(data))
|
||||
},
|
||||
}
|
||||
w.Wakeup()
|
||||
select {
|
||||
case w.clipReads <- e:
|
||||
w.disp.wakeup()
|
||||
case <-w.disp.readClipClose:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -1096,8 +1100,7 @@ func (w *window) Configure(options []Option) {
|
||||
w.config.MaxSize = cnf.MaxSize
|
||||
w.setWindowConstraints()
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.redraw = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) setWindowConstraints() {
|
||||
@@ -1134,7 +1137,7 @@ func (w *window) Perform(actions system.Action) {
|
||||
walkActions(actions, func(action system.Action) {
|
||||
switch action {
|
||||
case system.ActionClose:
|
||||
w.dead = true
|
||||
w.closing = true
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1217,7 +1220,8 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
||||
w := callbackLoad(unsafe.Pointer(surf)).(*window)
|
||||
s.keyboardFocus = w
|
||||
s.disp.repeat.Stop(0)
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onKeyboardLeave
|
||||
@@ -1226,7 +1230,8 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
|
||||
s.serial = serial
|
||||
s.disp.repeat.Stop(0)
|
||||
w := s.keyboardFocus
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export gio_onKeyboardKey
|
||||
@@ -1244,7 +1249,7 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
|
||||
// There's no support for IME yet.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
|
||||
@@ -1279,7 +1284,7 @@ func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) {
|
||||
r.now = 0
|
||||
r.stopC = stopC
|
||||
r.key = keyCode
|
||||
r.win = w.w
|
||||
r.win = w
|
||||
rate, delay := r.rate, r.delay
|
||||
go func() {
|
||||
timer := time.NewTimer(delay)
|
||||
@@ -1337,9 +1342,9 @@ func (r *repeatState) Repeat(d *wlDisplay) {
|
||||
for _, e := range d.xkb.DispatchKey(r.key, key.Press) {
|
||||
if ee, ok := e.(key.EditEvent); ok {
|
||||
// There's no support for IME yet.
|
||||
r.win.EditorInsert(ee.Text)
|
||||
r.win.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
r.win.Event(e)
|
||||
r.win.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
r.last += delay
|
||||
@@ -1352,28 +1357,68 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
|
||||
w := callbackLoad(data).(*window)
|
||||
if w.lastFrameCallback == callback {
|
||||
w.lastFrameCallback = nil
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) loop() error {
|
||||
var p poller
|
||||
for {
|
||||
if err := w.disp.dispatch(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case e := <-w.clipReads:
|
||||
w.w.Event(e)
|
||||
case <-w.wakeups:
|
||||
w.w.Event(wakeupEvent{})
|
||||
default:
|
||||
}
|
||||
if w.dead {
|
||||
break
|
||||
}
|
||||
w.draw()
|
||||
func (w *window) close(err error) {
|
||||
w.ProcessEvent(WaylandViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
w.destroy()
|
||||
w.disp.destroy()
|
||||
w.disp = nil
|
||||
}
|
||||
|
||||
func (w *window) dispatch() {
|
||||
if w.disp == nil {
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
return nil
|
||||
if err := w.disp.dispatch(); err != nil || w.closing {
|
||||
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
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.disp.wakeup()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
// bindDataDevice initializes the dataDev field if and only if both
|
||||
@@ -1389,13 +1434,21 @@ func (d *wlDisplay) bindDataDevice() {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *wlDisplay) dispatch(p *poller) error {
|
||||
func (d *wlDisplay) dispatch() error {
|
||||
// wl_display_prepare_read records the current thread for
|
||||
// use in wl_display_read_events or wl_display_cancel_events.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
dispfd := C.wl_display_get_fd(d.disp)
|
||||
// Poll for events and notifications.
|
||||
pollfds := append(p.pollfds[:0],
|
||||
pollfds := append(d.poller.pollfds[:0],
|
||||
syscall.PollFd{Fd: int32(dispfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
syscall.PollFd{Fd: int32(d.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||
)
|
||||
for C.wl_display_prepare_read(d.disp) != 0 {
|
||||
C.wl_display_dispatch_pending(d.disp)
|
||||
}
|
||||
dispFd := &pollfds[0]
|
||||
if ret, err := C.wl_display_flush(d.disp); ret < 0 {
|
||||
if err != syscall.EAGAIN {
|
||||
@@ -1406,11 +1459,25 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
||||
dispFd.Events |= syscall.POLLOUT
|
||||
}
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
C.wl_display_cancel_read(d.disp)
|
||||
return fmt.Errorf("wayland: poll failed: %v", err)
|
||||
}
|
||||
if dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0 {
|
||||
C.wl_display_cancel_read(d.disp)
|
||||
return errors.New("wayland: display file descriptor gone")
|
||||
}
|
||||
// Handle events.
|
||||
if dispFd.Revents&syscall.POLLIN != 0 {
|
||||
if ret, err := C.wl_display_read_events(d.disp); ret < 0 {
|
||||
return fmt.Errorf("wayland: wl_display_read_events failed: %v", err)
|
||||
}
|
||||
C.wl_display_dispatch_pending(d.disp)
|
||||
} else {
|
||||
C.wl_display_cancel_read(d.disp)
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(d.notify.read, p.buf[:])
|
||||
_, err := syscall.Read(d.notify.read, d.poller.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
@@ -1418,29 +1485,15 @@ func (d *wlDisplay) dispatch(p *poller) error {
|
||||
return fmt.Errorf("wayland: read from notify pipe failed: %v", err)
|
||||
}
|
||||
}
|
||||
// Handle events
|
||||
switch {
|
||||
case dispFd.Revents&syscall.POLLIN != 0:
|
||||
if ret, err := C.wl_display_dispatch(d.disp); ret < 0 {
|
||||
return fmt.Errorf("wayland: wl_display_dispatch failed: %v", err)
|
||||
}
|
||||
case dispFd.Revents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
return errors.New("wayland: display file descriptor gone")
|
||||
}
|
||||
d.repeat.Repeat(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
w.disp.wakeup()
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Wakeup wakes up the event loop through the notification pipe.
|
||||
@@ -1452,6 +1505,10 @@ func (d *wlDisplay) wakeup() {
|
||||
}
|
||||
|
||||
func (w *window) destroy() {
|
||||
if w.lastFrameCallback != nil {
|
||||
C.wl_callback_destroy(w.lastFrameCallback)
|
||||
w.lastFrameCallback = nil
|
||||
}
|
||||
if w.cursor.surf != nil {
|
||||
C.wl_surface_destroy(w.cursor.surf)
|
||||
}
|
||||
@@ -1576,7 +1633,15 @@ func (w *window) flushScroll() {
|
||||
if total == (f32.Point{}) {
|
||||
return
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
if w.scroll.steps == (image.Point{}) {
|
||||
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
|
||||
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
|
||||
}
|
||||
// Zero scroll distance prior to calling ProcessEvent, otherwise we may recursively
|
||||
// re-process the scroll distance.
|
||||
w.scroll.dist = f32.Point{}
|
||||
w.scroll.steps = image.Point{}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -1585,12 +1650,6 @@ func (w *window) flushScroll() {
|
||||
Time: w.scroll.time,
|
||||
Modifiers: w.disp.xkb.Modifiers(),
|
||||
})
|
||||
if w.scroll.steps == (image.Point{}) {
|
||||
w.fling.xExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.X)
|
||||
w.fling.yExtrapolation.SampleDelta(w.scroll.time, -w.scroll.dist.Y)
|
||||
}
|
||||
w.scroll.dist = f32.Point{}
|
||||
w.scroll.steps = image.Point{}
|
||||
}
|
||||
|
||||
func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
||||
@@ -1599,7 +1658,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
|
||||
X: fromFixed(x) * float32(w.scale),
|
||||
Y: fromFixed(y) * float32(w.scale),
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Position: w.lastPos,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -1620,7 +1679,8 @@ func (w *window) systemGesture() (*C.struct_wl_cursor, C.uint32_t) {
|
||||
if w.config.Mode != Windowed || w.config.Decorated {
|
||||
return nil, 0
|
||||
}
|
||||
border := w.w.w.metric.Dp(3)
|
||||
_, cfg := w.getConfig()
|
||||
border := cfg.Dp(3)
|
||||
x, y, size := int(w.lastPos.X), int(w.lastPos.Y), w.config.Size
|
||||
north := y <= border
|
||||
south := y >= size.Y-border
|
||||
@@ -1674,13 +1734,10 @@ func (w *window) updateOutputs() {
|
||||
if found && scale != w.scale {
|
||||
w.scale = scale
|
||||
C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
|
||||
w.redraw = true
|
||||
w.draw(true)
|
||||
}
|
||||
if !found {
|
||||
w.setStage(StagePaused)
|
||||
} else {
|
||||
w.setStage(StageRunning)
|
||||
w.redraw = true
|
||||
if found {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1692,7 +1749,10 @@ func (w *window) getConfig() (image.Point, unit.Metric) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) draw() {
|
||||
func (w *window) draw(sync bool) {
|
||||
if !w.configured {
|
||||
return
|
||||
}
|
||||
w.flushScroll()
|
||||
size, cfg := w.getConfig()
|
||||
if cfg == (unit.Metric{}) {
|
||||
@@ -1700,11 +1760,9 @@ func (w *window) draw() {
|
||||
}
|
||||
if size != w.config.Size {
|
||||
w.config.Size = size
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
anim := w.animating || w.fling.anim.Active()
|
||||
sync := w.redraw
|
||||
w.redraw = false
|
||||
// Draw animation only when not waiting for frame callback.
|
||||
redrawAnim := anim && w.lastFrameCallback == nil
|
||||
if !redrawAnim && !sync {
|
||||
@@ -1715,7 +1773,7 @@ func (w *window) draw() {
|
||||
// Use the surface as listener data for gio_onFrameDone.
|
||||
C.wl_callback_add_listener(w.lastFrameCallback, &C.gio_callback_listener, unsafe.Pointer(w.surf))
|
||||
}
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
@@ -1725,14 +1783,6 @@ func (w *window) draw() {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) setStage(s Stage) {
|
||||
if s == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = s
|
||||
w.w.Event(StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *window) display() *C.struct_wl_display {
|
||||
return w.disp.disp
|
||||
}
|
||||
@@ -1824,6 +1874,10 @@ func newWLDisplay() (*wlDisplay, error) {
|
||||
}
|
||||
|
||||
func (d *wlDisplay) destroy() {
|
||||
if d.readClipClose != nil {
|
||||
close(d.readClipClose)
|
||||
d.readClipClose = nil
|
||||
}
|
||||
if d.notify.write != 0 {
|
||||
syscall.Close(d.notify.write)
|
||||
d.notify.write = 0
|
||||
@@ -1865,6 +1919,7 @@ func (d *wlDisplay) destroy() {
|
||||
if d.disp != nil {
|
||||
C.wl_display_disconnect(d.disp)
|
||||
callbackDelete(unsafe.Pointer(d.disp))
|
||||
d.disp = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+79
-64
@@ -19,17 +19,19 @@ import (
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/app/internal/windows"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
gowindows "golang.org/x/sys/windows"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
)
|
||||
|
||||
type ViewEvent struct {
|
||||
type Win32ViewEvent struct {
|
||||
HWND uintptr
|
||||
}
|
||||
|
||||
@@ -37,7 +39,6 @@ type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w *callbacks
|
||||
stage Stage
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
// cursorIn tracks whether the cursor was inside the window according
|
||||
@@ -49,10 +50,10 @@ type window struct {
|
||||
placement *windows.WindowPlacement
|
||||
|
||||
animating bool
|
||||
focused bool
|
||||
|
||||
borderSize image.Point
|
||||
config Config
|
||||
loop *eventLoop
|
||||
}
|
||||
|
||||
const _WM_WAKEUP = windows.WM_USER + iota
|
||||
@@ -85,36 +86,38 @@ func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func newWindow(window *callbacks, options []Option) error {
|
||||
cerr := make(chan error)
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||
// then thread-specific messages such as WM_QUIT are ignored.
|
||||
// Instead lock the thread so window messages arrive through
|
||||
// unfiltered GetMessage calls.
|
||||
runtime.LockOSThread()
|
||||
w, err := createNativeWindow()
|
||||
|
||||
w := &window{
|
||||
w: win,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
err := w.init()
|
||||
done <- struct{}{}
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
cerr <- nil
|
||||
winMap.Store(w.hwnd, w)
|
||||
defer winMap.Delete(w.hwnd)
|
||||
w.w = window
|
||||
w.w.SetDriver(w)
|
||||
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
|
||||
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
||||
w.Configure(options)
|
||||
windows.SetForegroundWindow(w.hwnd)
|
||||
windows.SetFocus(w.hwnd)
|
||||
// Since the window class for the cursor is null,
|
||||
// set it here to show the cursor.
|
||||
w.SetCursor(pointer.CursorDefault)
|
||||
if err := w.loop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.runLoop()
|
||||
}()
|
||||
return <-cerr
|
||||
<-done
|
||||
}
|
||||
|
||||
// initResources initializes the resources global.
|
||||
@@ -149,13 +152,13 @@ func initResources() error {
|
||||
|
||||
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
||||
|
||||
func createNativeWindow() (*window, error) {
|
||||
func (w *window) init() error {
|
||||
var resErr error
|
||||
resources.once.Do(func() {
|
||||
resErr = initResources()
|
||||
})
|
||||
if resErr != nil {
|
||||
return nil, resErr
|
||||
return resErr
|
||||
}
|
||||
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
||||
|
||||
@@ -171,16 +174,15 @@ func createNativeWindow() (*window, error) {
|
||||
resources.handle,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &window{
|
||||
hwnd: hwnd,
|
||||
return err
|
||||
}
|
||||
w.hdc, err = windows.GetDC(hwnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
windows.DestroyWindow(hwnd)
|
||||
return err
|
||||
}
|
||||
return w, nil
|
||||
w.hwnd = hwnd
|
||||
return nil
|
||||
}
|
||||
|
||||
// update() handles changes done by the user, and updates the configuration.
|
||||
@@ -197,7 +199,7 @@ func (w *window) update() {
|
||||
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
||||
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
||||
)
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
@@ -238,7 +240,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
e.State = key.Release
|
||||
}
|
||||
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
|
||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||
@@ -259,23 +261,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
case windows.WM_MBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||
case windows.WM_CANCELMODE:
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
})
|
||||
case windows.WM_SETFOCUS:
|
||||
w.focused = true
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case windows.WM_KILLFOCUS:
|
||||
w.focused = false
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case windows.WM_NCACTIVATE:
|
||||
if w.stage >= StageInactive {
|
||||
if wParam == windows.TRUE {
|
||||
w.setStage(StageRunning)
|
||||
} else {
|
||||
w.setStage(StageInactive)
|
||||
}
|
||||
}
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case windows.WM_NCHITTEST:
|
||||
if w.config.Decorated {
|
||||
// Let the system handle it.
|
||||
@@ -288,7 +282,7 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
case windows.WM_MOUSEMOVE:
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
@@ -301,8 +295,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
case windows.WM_MOUSEHWHEEL:
|
||||
w.scrollEvent(wParam, lParam, true, getModifiers())
|
||||
case windows.WM_DESTROY:
|
||||
w.w.Event(ViewEvent{})
|
||||
w.w.Event(DestroyEvent{})
|
||||
w.ProcessEvent(Win32ViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
if w.hdc != 0 {
|
||||
windows.ReleaseDC(w.hdc)
|
||||
w.hdc = 0
|
||||
@@ -328,10 +322,16 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
// Adjust window position to avoid the extra padding in maximized
|
||||
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
||||
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(uintptr(lParam)))
|
||||
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
||||
mi := windows.GetMonitorInfo(w.hwnd)
|
||||
szp.Rgrc[0] = mi.WorkArea
|
||||
return 0
|
||||
case windows.WM_NCLBUTTONDBLCLK:
|
||||
if !w.config.Decorated {
|
||||
// Override Windows behaviour when we
|
||||
// draw decorations.
|
||||
return 0
|
||||
}
|
||||
case windows.WM_PAINT:
|
||||
w.draw(true)
|
||||
case windows.WM_SIZE:
|
||||
@@ -339,18 +339,15 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
switch wParam {
|
||||
case windows.SIZE_MINIMIZED:
|
||||
w.config.Mode = Minimized
|
||||
w.setStage(StagePaused)
|
||||
case windows.SIZE_MAXIMIZED:
|
||||
w.config.Mode = Maximized
|
||||
w.setStage(StageRunning)
|
||||
case windows.SIZE_RESTORED:
|
||||
if w.config.Mode != Fullscreen {
|
||||
w.config.Mode = Windowed
|
||||
}
|
||||
w.setStage(StageRunning)
|
||||
}
|
||||
case windows.WM_GETMINMAXINFO:
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
||||
var bw, bh int32
|
||||
if w.config.Decorated {
|
||||
r := windows.GetWindowRect(w.hwnd)
|
||||
@@ -378,7 +375,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
||||
return windows.TRUE
|
||||
}
|
||||
case _WM_WAKEUP:
|
||||
w.w.Event(wakeupEvent{})
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
case windows.WM_IME_STARTCOMPOSITION:
|
||||
imc := windows.ImmGetContext(w.hwnd)
|
||||
if imc == 0 {
|
||||
@@ -498,7 +496,7 @@ func (w *window) hitTest(x, y int) uintptr {
|
||||
}
|
||||
|
||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||
if !w.focused {
|
||||
if !w.config.Focused {
|
||||
windows.SetFocus(w.hwnd)
|
||||
}
|
||||
|
||||
@@ -518,7 +516,7 @@ func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr,
|
||||
}
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
@@ -553,7 +551,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
||||
sp.Y = -dist
|
||||
}
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
@@ -565,7 +563,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
|
||||
}
|
||||
|
||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||
func (w *window) loop() error {
|
||||
func (w *window) runLoop() {
|
||||
msg := new(windows.Msg)
|
||||
loop:
|
||||
for {
|
||||
@@ -576,7 +574,7 @@ loop:
|
||||
}
|
||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||
case -1:
|
||||
return errors.New("GetMessage failed")
|
||||
panic(errors.New("GetMessage failed"))
|
||||
case 0:
|
||||
// WM_QUIT received.
|
||||
break loop
|
||||
@@ -584,7 +582,6 @@ loop:
|
||||
windows.TranslateMessage(msg)
|
||||
windows.DispatchMessage(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {
|
||||
@@ -602,16 +599,30 @@ func (w *window) SetAnimating(anim bool) {
|
||||
w.animating = anim
|
||||
}
|
||||
|
||||
func (w *window) Wakeup() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) setStage(s Stage) {
|
||||
if s != w.stage {
|
||||
w.stage = s
|
||||
w.w.Event(StageEvent{Stage: s})
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func (w *window) wakeup() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +632,7 @@ func (w *window) draw(sync bool) {
|
||||
}
|
||||
dpi := windows.GetWindowDPI(w.hwnd)
|
||||
cfg := configForDPI(dpi)
|
||||
w.w.Event(frameEvent{
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
@@ -668,7 +679,7 @@ func (w *window) readClipboard() error {
|
||||
}
|
||||
defer windows.GlobalUnlock(mem)
|
||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||
w.w.Event(transfer.DataEvent{
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
@@ -974,4 +985,8 @@ func configForDPI(dpi int) unit.Metric {
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
func (Win32ViewEvent) implementsViewEvent() {}
|
||||
func (Win32ViewEvent) ImplementsEvent() {}
|
||||
func (w Win32ViewEvent) Valid() bool {
|
||||
return w != (Win32ViewEvent{})
|
||||
}
|
||||
|
||||
+105
-80
@@ -38,10 +38,12 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/io/transfer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
|
||||
syscall "golang.org/x/sys/unix"
|
||||
@@ -93,12 +95,10 @@ type x11Window struct {
|
||||
// _NET_WM_STATE_MAXIMIZED_VERT
|
||||
wmStateMaximizedVert C.Atom
|
||||
}
|
||||
stage Stage
|
||||
metric unit.Metric
|
||||
notify struct {
|
||||
read, write int
|
||||
}
|
||||
dead bool
|
||||
|
||||
animating bool
|
||||
|
||||
@@ -111,6 +111,8 @@ type x11Window struct {
|
||||
config Config
|
||||
|
||||
wakeups chan struct{}
|
||||
handler x11EventHandler
|
||||
buf [100]byte
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -234,7 +236,7 @@ func (w *x11Window) Configure(options []Option) {
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *x11Window) setTitle(prev, cnf Config) {
|
||||
@@ -377,7 +379,36 @@ func (w *x11Window) sendWMStateEvent(action C.long, atom1, atom2 C.ulong) {
|
||||
|
||||
var x11OneByte = make([]byte, 1)
|
||||
|
||||
func (w *x11Window) Wakeup() {
|
||||
func (w *x11Window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
}
|
||||
|
||||
func (w *x11Window) shutdown(err error) {
|
||||
w.ProcessEvent(X11ViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{Err: err})
|
||||
w.destroy()
|
||||
}
|
||||
|
||||
func (w *x11Window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if !ok {
|
||||
w.dispatch()
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *x11Window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
func (w *x11Window) Invalidate() {
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
@@ -395,16 +426,20 @@ func (w *x11Window) window() (C.Window, int, int) {
|
||||
return w.xw, w.config.Size.X, w.config.Size.Y
|
||||
}
|
||||
|
||||
func (w *x11Window) setStage(s Stage) {
|
||||
if s == w.stage {
|
||||
func (w *x11Window) dispatch() {
|
||||
if w.x == nil {
|
||||
// Only Invalidate can wake us up.
|
||||
<-w.wakeups
|
||||
w.w.Invalidate()
|
||||
return
|
||||
}
|
||||
w.stage = s
|
||||
w.w.Event(StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *x11Window) loop() {
|
||||
h := x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Invalidate()
|
||||
default:
|
||||
}
|
||||
|
||||
xfd := C.XConnectionNumber(w.x)
|
||||
|
||||
// Poll for events and notifications.
|
||||
@@ -414,61 +449,55 @@ func (w *x11Window) loop() {
|
||||
}
|
||||
xEvents := &pollfds[0].Revents
|
||||
// Plenty of room for a backlog of notifications.
|
||||
buf := make([]byte, 100)
|
||||
|
||||
loop:
|
||||
for !w.dead {
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
if syn = h.handleEvents(); !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||
}
|
||||
switch {
|
||||
case *xEvents&syscall.POLLIN != 0:
|
||||
syn = h.handleEvents()
|
||||
if w.dead {
|
||||
break loop
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
break loop
|
||||
var syn, anim bool
|
||||
// Check for pending draw events before checking animation or blocking.
|
||||
// This fixes an issue on Xephyr where on startup XPending() > 0 but
|
||||
// poll will still block. This also prevents no-op calls to poll.
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
// handleEvents received a close request and destroyed the window.
|
||||
return
|
||||
}
|
||||
if !syn {
|
||||
anim = w.animating
|
||||
if !anim {
|
||||
// Clear poll events.
|
||||
*xEvents = 0
|
||||
// Wait for X event or gio notification.
|
||||
if _, err := syscall.Poll(pollfds, -1); err != nil && err != syscall.EINTR {
|
||||
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||
}
|
||||
switch {
|
||||
case *xEvents&syscall.POLLIN != 0:
|
||||
syn = w.handler.handleEvents()
|
||||
if w.x == nil {
|
||||
return
|
||||
}
|
||||
case *xEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, buf)
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
// Clear notifications.
|
||||
for {
|
||||
_, err := syscall.Read(w.notify.read, w.buf[:])
|
||||
if err == syscall.EAGAIN {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-w.wakeups:
|
||||
w.w.Event(wakeupEvent{})
|
||||
default:
|
||||
}
|
||||
|
||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||
w.w.Event(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: w.metric,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||
}
|
||||
}
|
||||
if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: w.config.Size,
|
||||
Metric: w.metric,
|
||||
},
|
||||
Sync: syn,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *x11Window) destroy() {
|
||||
@@ -486,6 +515,7 @@ func (w *x11Window) destroy() {
|
||||
}
|
||||
C.XDestroyWindow(w.x, w.xw)
|
||||
C.XCloseDisplay(w.x)
|
||||
w.x = nil
|
||||
}
|
||||
|
||||
// atom is a wrapper around XInternAtom. Callers should cache the result
|
||||
@@ -543,7 +573,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
// There's no support for IME yet.
|
||||
w.w.EditorInsert(ee.Text)
|
||||
} else {
|
||||
w.w.Event(e)
|
||||
w.ProcessEvent(e)
|
||||
}
|
||||
}
|
||||
case C.ButtonPress, C.ButtonRelease:
|
||||
@@ -605,10 +635,10 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
w.pointerBtns &^= btn
|
||||
}
|
||||
ev.Buttons = w.pointerBtns
|
||||
w.w.Event(ev)
|
||||
w.ProcessEvent(ev)
|
||||
case C.MotionNotify:
|
||||
mevt := (*C.XMotionEvent)(unsafe.Pointer(xev))
|
||||
w.w.Event(pointer.Event{
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: w.pointerBtns,
|
||||
@@ -623,14 +653,16 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
// redraw only on the last expose event
|
||||
redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0
|
||||
case C.FocusIn:
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.FocusOut:
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
case C.ConfigureNotify: // window configuration change
|
||||
cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
|
||||
if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
|
||||
w.config.Size = sz
|
||||
w.w.Event(ConfigEvent{Config: w.config})
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
// redraw will be done by a later expose event
|
||||
case C.SelectionNotify:
|
||||
@@ -652,7 +684,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
break
|
||||
}
|
||||
str := C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.nitems))
|
||||
w.w.Event(transfer.DataEvent{
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(str))
|
||||
@@ -711,7 +743,7 @@ func (h *x11EventHandler) handleEvents() bool {
|
||||
cevt := (*C.XClientMessageEvent)(unsafe.Pointer(xev))
|
||||
switch *(*C.long)(unsafe.Pointer(&cevt.data)) {
|
||||
case C.long(w.atoms.evDelWindow):
|
||||
w.dead = true
|
||||
w.shutdown(nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -793,8 +825,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
wakeups: make(chan struct{}, 1),
|
||||
config: Config{Size: cnf.Size},
|
||||
}
|
||||
w.handler = x11EventHandler{w: w, xev: new(C.XEvent), text: make([]byte, 4)}
|
||||
w.notify.read = pipe[0]
|
||||
w.notify.write = pipe[1]
|
||||
w.w.SetDriver(w)
|
||||
|
||||
if err := w.updateXkbKeymap(); err != nil {
|
||||
w.destroy()
|
||||
@@ -830,19 +864,10 @@ func newX11Window(gioWin *callbacks, options []Option) error {
|
||||
// extensions
|
||||
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
|
||||
|
||||
go func() {
|
||||
w.w.SetDriver(w)
|
||||
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
w.Configure(options)
|
||||
w.w.Event(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
w.setStage(StageRunning)
|
||||
w.loop()
|
||||
w.w.Event(X11ViewEvent{})
|
||||
w.w.Event(DestroyEvent{Err: nil})
|
||||
w.destroy()
|
||||
}()
|
||||
// make the window visible on the screen
|
||||
C.XMapWindow(dpy, win)
|
||||
w.Configure(options)
|
||||
w.ProcessEvent(X11ViewEvent{Display: unsafe.Pointer(dpy), Window: uintptr(win)})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,40 +10,4 @@ type DestroyEvent struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// A StageEvent is generated whenever the stage of a
|
||||
// Window changes.
|
||||
type StageEvent struct {
|
||||
Stage Stage
|
||||
}
|
||||
|
||||
// Stage of a Window.
|
||||
type Stage uint8
|
||||
|
||||
const (
|
||||
// StagePaused is the stage for windows that have no on-screen representation.
|
||||
// Paused windows don't receive frames.
|
||||
StagePaused Stage = iota
|
||||
// StageInactive is the stage for windows that are visible, but not active.
|
||||
// Inactive windows receive frames.
|
||||
StageInactive
|
||||
// StageRunning is for active and visible Windows.
|
||||
// Running windows receive frames.
|
||||
StageRunning
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (l Stage) String() string {
|
||||
switch l {
|
||||
case StagePaused:
|
||||
return "StagePaused"
|
||||
case StageInactive:
|
||||
return "StageInactive"
|
||||
case StageRunning:
|
||||
return "StageRunning"
|
||||
default:
|
||||
panic("unexpected Stage value")
|
||||
}
|
||||
}
|
||||
|
||||
func (StageEvent) ImplementsEvent() {}
|
||||
func (DestroyEvent) ImplementsEvent() {}
|
||||
|
||||
+323
-512
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -271,12 +271,13 @@ func (s *Scroll) Stop() {
|
||||
}
|
||||
|
||||
// Update state and report the scroll distance along axis.
|
||||
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, bounds image.Rectangle) int {
|
||||
func (s *Scroll) Update(cfg unit.Metric, q input.Source, t time.Time, axis Axis, scrollx, scrolly pointer.ScrollRange) int {
|
||||
total := 0
|
||||
f := pointer.Filter{
|
||||
Target: s,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||
ScrollBounds: bounds,
|
||||
Target: s,
|
||||
Kinds: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll | pointer.Cancel,
|
||||
ScrollX: scrollx,
|
||||
ScrollY: scrolly,
|
||||
}
|
||||
for {
|
||||
evt, ok := q.Event(f)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
module gioui.org
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2
|
||||
gioui.org/shader v1.0.8
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372
|
||||
github.com/go-text/typesetting v0.1.1
|
||||
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
|
||||
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
|
||||
golang.org/x/image v0.5.0
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
||||
golang.org/x/sys v0.5.0
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.7.0
|
||||
require golang.org/x/text v0.9.0
|
||||
|
||||
@@ -5,9 +5,10 @@ gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJG
|
||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
||||
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
|
||||
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@@ -28,15 +29,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
+99
-89
@@ -261,7 +261,7 @@ type texture struct {
|
||||
type blitter struct {
|
||||
ctx driver.Device
|
||||
viewport image.Point
|
||||
pipelines [3]*pipeline
|
||||
pipelines [2][3]*pipeline
|
||||
colUniforms *blitColUniforms
|
||||
texUniforms *blitTexUniforms
|
||||
linearGradientUniforms *blitLinearGradientUniforms
|
||||
@@ -560,12 +560,24 @@ func newBlitter(ctx driver.Device) *blitter {
|
||||
func (b *blitter) release() {
|
||||
b.quadVerts.Release()
|
||||
for _, p := range b.pipelines {
|
||||
p.Release()
|
||||
for _, p := range p {
|
||||
p.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
|
||||
var pipelines [3]*pipeline
|
||||
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) (pipelines [2][3]*pipeline, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
for _, p := range pipelines {
|
||||
for _, p := range p {
|
||||
if p != nil {
|
||||
p.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
blend := driver.BlendDesc{
|
||||
Enable: true,
|
||||
SrcFactor: driver.BlendFactorOne,
|
||||
@@ -583,86 +595,76 @@ func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.
|
||||
return pipelines, err
|
||||
}
|
||||
defer vsh.Release()
|
||||
{
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} {
|
||||
{
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: format,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
var vertBuffer *uniformBuffer
|
||||
if u := uniforms[materialTexture]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: driver.TextureFormatOutput,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: format,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialColor]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[i][materialColor] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
var vertBuffer *uniformBuffer
|
||||
if u := uniforms[materialTexture]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: format,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialLinearGradient]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: driver.TextureFormatOutput,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialColor]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[materialColor] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
{
|
||||
var vertBuffer *uniformBuffer
|
||||
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
pipelines[materialColor].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
defer fsh.Release()
|
||||
pipe, err := b.NewPipeline(driver.PipelineDesc{
|
||||
VertexShader: vsh,
|
||||
FragmentShader: fsh,
|
||||
BlendDesc: blend,
|
||||
VertexLayout: layout,
|
||||
PixelFormat: driver.TextureFormatOutput,
|
||||
Topology: driver.TopologyTriangleStrip,
|
||||
})
|
||||
if err != nil {
|
||||
pipelines[materialTexture].Release()
|
||||
pipelines[materialColor].Release()
|
||||
return pipelines, err
|
||||
}
|
||||
if u := uniforms[materialLinearGradient]; u != nil {
|
||||
vertBuffer = newUniformBuffer(b, u)
|
||||
}
|
||||
pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
|
||||
}
|
||||
if err != nil {
|
||||
for _, p := range pipelines {
|
||||
p.Release()
|
||||
}
|
||||
return pipelines, err
|
||||
}
|
||||
return pipelines, nil
|
||||
}
|
||||
@@ -865,7 +867,7 @@ func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
|
||||
Min: l.place.Pos,
|
||||
Max: l.place.Pos.Add(l.clip.Size()),
|
||||
}
|
||||
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
|
||||
r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy())
|
||||
f := r.layerFBOs.fbos[fbo]
|
||||
r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
|
||||
sr := f32.FRect(v)
|
||||
@@ -930,7 +932,7 @@ func (d *drawOps) newPathOp() *pathOp {
|
||||
return &d.pathOpCache[len(d.pathOpCache)-1]
|
||||
}
|
||||
|
||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
|
||||
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point) {
|
||||
npath := d.newPathOp()
|
||||
*npath = pathOp{
|
||||
parent: state.cpath,
|
||||
@@ -1055,7 +1057,7 @@ loop:
|
||||
quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
|
||||
quads.key = opKey{Key: encOp.Key}
|
||||
}
|
||||
d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
|
||||
d.addClipPath(&state, quads.aux, quads.key, bounds, off)
|
||||
quads = quadsOp{}
|
||||
case ops.TypePopClip:
|
||||
state.cpath = state.cpath.parent
|
||||
@@ -1100,7 +1102,7 @@ loop:
|
||||
// this transformed rectangle.
|
||||
k := opKey{Key: encOp.Key}
|
||||
k.SetTransform(t) // TODO: This call has no effect.
|
||||
d.addClipPath(&state, clipData, k, bnd, off, false)
|
||||
d.addClipPath(&state, clipData, k, bnd, off)
|
||||
}
|
||||
|
||||
bounds := cl.Round()
|
||||
@@ -1230,7 +1232,7 @@ func (r *renderer) prepareDrawOps(ops []imageOp) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *renderer) drawOps(isFBO bool, opOff image.Point, viewport image.Point, ops []imageOp) {
|
||||
func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) {
|
||||
var coverTex driver.Texture
|
||||
for i := 0; i < len(ops); i++ {
|
||||
img := ops[i]
|
||||
@@ -1244,9 +1246,13 @@ func (r *renderer) drawOps(isFBO bool, opOff image.Point, viewport image.Point,
|
||||
|
||||
scale, off := clipSpaceTransform(drc, viewport)
|
||||
var fbo FBO
|
||||
fboIdx := 0
|
||||
if isFBO {
|
||||
fboIdx = 1
|
||||
}
|
||||
switch img.clipType {
|
||||
case clipTypeNone:
|
||||
p := r.blitter.pipelines[m.material]
|
||||
p := r.blitter.pipelines[fboIdx][m.material]
|
||||
r.ctx.BindPipeline(p.pipeline)
|
||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||
r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
|
||||
@@ -1265,7 +1271,7 @@ func (r *renderer) drawOps(isFBO bool, opOff image.Point, viewport image.Point,
|
||||
Max: img.place.Pos.Add(drc.Size()),
|
||||
}
|
||||
coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
|
||||
p := r.pather.coverer.pipelines[m.material]
|
||||
p := r.pather.coverer.pipelines[fboIdx][m.material]
|
||||
r.ctx.BindPipeline(p.pipeline)
|
||||
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
|
||||
r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
|
||||
@@ -1273,7 +1279,11 @@ func (r *renderer) drawOps(isFBO bool, opOff image.Point, viewport image.Point,
|
||||
}
|
||||
|
||||
func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
|
||||
p := b.pipelines[mat]
|
||||
fboIdx := 0
|
||||
if fbo {
|
||||
fboIdx = 1
|
||||
}
|
||||
p := b.pipelines[fboIdx][mat]
|
||||
b.ctx.BindPipeline(p.pipeline)
|
||||
var uniforms *blitUniforms
|
||||
switch mat {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 1.9 KiB |
@@ -483,14 +483,14 @@ func TestGapsInPath(t *testing.T) {
|
||||
func TestOpacity(t *testing.T) {
|
||||
run(t, func(ops *op.Ops) {
|
||||
opc1 := paint.PushOpacity(ops, .3)
|
||||
// Fill screen to exercize the glClear optimization.
|
||||
// Fill screen to exercise the glClear optimization.
|
||||
paint.FillShape(ops, color.NRGBA{R: 255, A: 255}, clip.Rect{Max: image.Pt(1024, 1024)}.Op())
|
||||
opc2 := paint.PushOpacity(ops, .6)
|
||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(20, 10), Max: image.Pt(64, 128)}.Op())
|
||||
opc2.Pop()
|
||||
opc1.Pop()
|
||||
opc3 := paint.PushOpacity(ops, .6)
|
||||
paint.FillShape(ops, color.NRGBA{G: 255, A: 255}, clip.Rect{Min: image.Pt(50+20, 10), Max: image.Pt(50+64, 128)}.Op())
|
||||
paint.FillShape(ops, color.NRGBA{B: 255, A: 255}, clip.Ellipse(image.Rectangle{Min: image.Pt(20+20, 10), Max: image.Pt(50+64, 128)}).Op(ops))
|
||||
opc3.Pop()
|
||||
}, func(r result) {
|
||||
})
|
||||
|
||||
+9
-3
@@ -30,7 +30,7 @@ type pather struct {
|
||||
|
||||
type coverer struct {
|
||||
ctx driver.Device
|
||||
pipelines [3]*pipeline
|
||||
pipelines [2][3]*pipeline
|
||||
texUniforms *coverTexUniforms
|
||||
colUniforms *coverColUniforms
|
||||
linearGradientUniforms *coverLinearGradientUniforms
|
||||
@@ -309,7 +309,9 @@ func (p *pather) release() {
|
||||
|
||||
func (c *coverer) release() {
|
||||
for _, p := range c.pipelines {
|
||||
p.Release()
|
||||
for _, p := range p {
|
||||
p.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +407,11 @@ func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, c
|
||||
}
|
||||
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
|
||||
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
|
||||
c.pipelines[mat].UploadUniforms(c.ctx)
|
||||
fboIdx := 0
|
||||
if isFBO {
|
||||
fboIdx = 1
|
||||
}
|
||||
c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
|
||||
c.ctx.DrawArrays(0, 4)
|
||||
}
|
||||
|
||||
|
||||
+4
-7
@@ -15,10 +15,9 @@ import (
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
disp _EGLDisplay
|
||||
eglCtx *eglContext
|
||||
eglSurf _EGLSurface
|
||||
width, height int
|
||||
disp _EGLDisplay
|
||||
eglCtx *eglContext
|
||||
eglSurf _EGLSurface
|
||||
}
|
||||
|
||||
type eglContext struct {
|
||||
@@ -121,11 +120,9 @@ func (c *Context) VisualID() int {
|
||||
return c.eglCtx.visualID
|
||||
}
|
||||
|
||||
func (c *Context) CreateSurface(win NativeWindowType, width, height int) error {
|
||||
func (c *Context) CreateSurface(win NativeWindowType) error {
|
||||
eglSurf, err := createSurface(c.disp, c.eglCtx, win)
|
||||
c.eglSurf = eglSurf
|
||||
c.width = width
|
||||
c.height = height
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -361,6 +361,9 @@ func (c *Functions) GetProgrami(p Program, pname Enum) int {
|
||||
}
|
||||
func (c *Functions) GetProgramInfoLog(p Program) string {
|
||||
n := c.GetProgrami(p, INFO_LOG_LENGTH)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
syscall.Syscall6(_glGetProgramInfoLog.Addr(), 4, uintptr(p.V), uintptr(len(buf)), 0, uintptr(unsafe.Pointer(&buf[0])), 0, 0)
|
||||
return string(buf)
|
||||
|
||||
@@ -327,6 +327,9 @@ func strokePathNorm(p0, p1, p2 f32.Point, t, d float32) f32.Point {
|
||||
func rot90CW(p f32.Point) f32.Point { return f32.Pt(+p.Y, -p.X) }
|
||||
|
||||
func normPt(p f32.Point, l float32) f32.Point {
|
||||
if (p.X == 0 && p.Y == l) || (p.Y == 0 && p.X == l) {
|
||||
return f32.Point{X: p.X, Y: p.Y}
|
||||
}
|
||||
d := math.Hypot(float64(p.X), float64(p.Y))
|
||||
l64 := float64(l)
|
||||
if math.Abs(d-l64) < 1e-10 {
|
||||
|
||||
@@ -251,9 +251,10 @@ func TestFocusScroll(t *testing.T) {
|
||||
filters := []event.Filter{
|
||||
key.FocusFilter{Target: h},
|
||||
pointer.Filter{
|
||||
Target: h,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollBounds: image.Rect(-100, -100, 100, 100),
|
||||
Target: h,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollX: pointer.ScrollRange{Min: -100, Max: +100},
|
||||
ScrollY: pointer.ScrollRange{Min: -100, Max: +100},
|
||||
},
|
||||
}
|
||||
events(r, -1, filters...)
|
||||
|
||||
+7
-5
@@ -72,7 +72,7 @@ type pointerHandler struct {
|
||||
type pointerFilter struct {
|
||||
kinds pointer.Kind
|
||||
// min and max horizontal/vertical scroll
|
||||
scrollRange image.Rectangle
|
||||
scrollX, scrollY pointer.ScrollRange
|
||||
|
||||
sourceMimes []string
|
||||
targetMimes []string
|
||||
@@ -297,7 +297,8 @@ func (p *pointerFilter) Add(f event.Filter) {
|
||||
p.targetMimes = append(p.targetMimes, f.Type)
|
||||
case pointer.Filter:
|
||||
p.kinds = p.kinds | f.Kinds
|
||||
p.scrollRange = p.scrollRange.Union(f.ScrollBounds)
|
||||
p.scrollX = p.scrollX.Union(f.ScrollX)
|
||||
p.scrollY = p.scrollY.Union(f.ScrollY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +326,8 @@ func (p *pointerFilter) Matches(e event.Event) bool {
|
||||
|
||||
func (p *pointerFilter) Merge(p2 pointerFilter) {
|
||||
p.kinds = p.kinds | p2.kinds
|
||||
p.scrollRange = p.scrollRange.Union(p2.scrollRange)
|
||||
p.scrollX = p.scrollX.Union(p2.scrollX)
|
||||
p.scrollY = p.scrollY.Union(p2.scrollY)
|
||||
p.sourceMimes = append(p.sourceMimes, p2.sourceMimes...)
|
||||
p.targetMimes = append(p.targetMimes, p2.targetMimes...)
|
||||
}
|
||||
@@ -333,8 +335,8 @@ func (p *pointerFilter) Merge(p2 pointerFilter) {
|
||||
// clampScroll splits a scroll distance in the remaining scroll and the
|
||||
// scroll accepted by the filter.
|
||||
func (p *pointerFilter) clampScroll(scroll f32.Point) (left, scrolled f32.Point) {
|
||||
left.X, scrolled.X = clampSplit(scroll.X, p.scrollRange.Min.X, p.scrollRange.Max.X)
|
||||
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollRange.Min.Y, p.scrollRange.Max.Y)
|
||||
left.X, scrolled.X = clampSplit(scroll.X, p.scrollX.Min, p.scrollX.Max)
|
||||
left.Y, scrolled.Y = clampSplit(scroll.Y, p.scrollY.Min, p.scrollY.Max)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -300,9 +300,9 @@ func TestPointerPriority(t *testing.T) {
|
||||
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
||||
f1 := func(t event.Tag) event.Filter {
|
||||
return pointer.Filter{
|
||||
Target: t,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
|
||||
Target: t,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollX: pointer.ScrollRange{Max: 100},
|
||||
}
|
||||
}
|
||||
events(&r, -1, f1(handler1))
|
||||
@@ -311,9 +311,9 @@ func TestPointerPriority(t *testing.T) {
|
||||
r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
|
||||
f2 := func(t event.Tag) event.Filter {
|
||||
return pointer.Filter{
|
||||
Target: t,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
|
||||
Target: t,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollX: pointer.ScrollRange{Max: 20},
|
||||
}
|
||||
}
|
||||
events(&r, -1, f2(handler2))
|
||||
@@ -324,9 +324,10 @@ func TestPointerPriority(t *testing.T) {
|
||||
r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
|
||||
f3 := func(t event.Tag) event.Filter {
|
||||
return pointer.Filter{
|
||||
Target: t,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
|
||||
Target: t,
|
||||
Kinds: pointer.Scroll,
|
||||
ScrollX: pointer.ScrollRange{Min: -20},
|
||||
ScrollY: pointer.ScrollRange{Min: -40},
|
||||
}
|
||||
}
|
||||
events(&r, -1, f3(handler3))
|
||||
@@ -1085,6 +1086,33 @@ func TestPassCursor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialEvent(t *testing.T) {
|
||||
var ops op.Ops
|
||||
var r Router
|
||||
|
||||
rect := clip.Rect(image.Rect(0, 0, 100, 100))
|
||||
background := rect.Push(&ops)
|
||||
event.Op(&ops, 1)
|
||||
background.Pop()
|
||||
|
||||
overlayPass := pointer.PassOp{}.Push(&ops)
|
||||
overlay := rect.Push(&ops)
|
||||
event.Op(&ops, 2)
|
||||
overlay.Pop()
|
||||
overlayPass.Pop()
|
||||
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 1, Kinds: pointer.Press}))
|
||||
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 2, Kinds: pointer.Press}))
|
||||
r.Frame(&ops)
|
||||
r.Queue(pointer.Event{
|
||||
Kind: pointer.Press,
|
||||
})
|
||||
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 1, Kinds: pointer.Press}, key.FocusFilter{Target: 1}),
|
||||
key.FocusEvent{}, pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Priority: pointer.Shared})
|
||||
r.Source().Execute(key.FocusCmd{Tag: 1})
|
||||
assertEventSequence(t, events(&r, -1, pointer.Filter{Target: 2, Kinds: pointer.Press}),
|
||||
pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Priority: pointer.Foremost})
|
||||
}
|
||||
|
||||
// offer satisfies io.ReadCloser for use in data transfers.
|
||||
type offer struct {
|
||||
data string
|
||||
|
||||
+29
-30
@@ -35,10 +35,9 @@ type Router struct {
|
||||
queue keyQueue
|
||||
// The following fields have the same purpose as the fields in
|
||||
// type handler, but for key.Events.
|
||||
filter keyFilter
|
||||
nextFilter keyFilter
|
||||
processedFilter keyFilter
|
||||
scratchFilter keyFilter
|
||||
filter keyFilter
|
||||
nextFilter keyFilter
|
||||
scratchFilter keyFilter
|
||||
}
|
||||
cqueue clipboardQueue
|
||||
// states is the list of pending state changes resulting from
|
||||
@@ -275,28 +274,29 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !q.deferring {
|
||||
for i := range q.changes {
|
||||
change := &q.changes[i]
|
||||
for j, evt := range change.events {
|
||||
match := false
|
||||
switch e := evt.event.(type) {
|
||||
case key.Event:
|
||||
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
|
||||
default:
|
||||
for _, tf := range q.scratchFilters {
|
||||
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
for i := range q.changes {
|
||||
if q.deferring && i > 0 {
|
||||
break
|
||||
}
|
||||
change := &q.changes[i]
|
||||
for j, evt := range change.events {
|
||||
match := false
|
||||
switch e := evt.event.(type) {
|
||||
case key.Event:
|
||||
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
|
||||
default:
|
||||
for _, tf := range q.scratchFilters {
|
||||
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
change.events = append(change.events[:j], change.events[j+1:]...)
|
||||
// Fast forward state to last matched.
|
||||
q.collapseState(i)
|
||||
return evt.event, true
|
||||
}
|
||||
}
|
||||
if match {
|
||||
change.events = append(change.events[:j], change.events[j+1:]...)
|
||||
// Fast forward state to last matched.
|
||||
q.collapseState(i)
|
||||
return evt.event, true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,6 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
|
||||
h := q.stateFor(tf.tag)
|
||||
h.processedFilter.Merge(tf.filter)
|
||||
}
|
||||
q.key.processedFilter = append(q.key.processedFilter, q.key.scratchFilter...)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -315,15 +314,15 @@ func (q *Router) collapseState(idx int) {
|
||||
}
|
||||
first := &q.changes[0]
|
||||
first.state = q.changes[idx].state
|
||||
for i := 1; i <= idx; i++ {
|
||||
first.events = append(first.events, q.changes[i].events...)
|
||||
for _, ch := range q.changes[1 : idx+1] {
|
||||
first.events = append(first.events, ch.events...)
|
||||
}
|
||||
q.changes = append(q.changes[:1], q.changes[idx+1:]...)
|
||||
}
|
||||
|
||||
// Frame replaces the declared handlers from the supplied
|
||||
// operation list. The text input state, wakeup time and whether
|
||||
// there are active profile handlers is also saved.
|
||||
// Frame completes the current frame and starts a new with the
|
||||
// handlers from the frame argument. Remaining events are discarded,
|
||||
// unless they were deferred by a command.
|
||||
func (q *Router) Frame(frame *op.Ops) {
|
||||
var remaining []event.Event
|
||||
if n := len(q.changes); n > 0 {
|
||||
|
||||
+2
-2
@@ -37,11 +37,11 @@ For example:
|
||||
var h1, h2 *Handler
|
||||
|
||||
area := clip.Rect(...).Push(ops)
|
||||
event.Op{Tag: h1}.Add(Ops)
|
||||
event.Op(Ops, h1)
|
||||
area.Pop()
|
||||
|
||||
area := clip.Rect(...).Push(ops)
|
||||
event.Op{Tag: h2}.Add(ops)
|
||||
event.Op(Ops, h2)
|
||||
area.Pop()
|
||||
|
||||
implies a tree of two inner nodes, each with one pointer handler attached.
|
||||
|
||||
+19
-6
@@ -3,7 +3,6 @@
|
||||
package pointer
|
||||
|
||||
import (
|
||||
"image"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -61,12 +60,19 @@ type Filter struct {
|
||||
Target event.Tag
|
||||
// Kinds is a bitwise-or of event types to match.
|
||||
Kinds Kind
|
||||
// ScrollBounds describe the maximum scrollable distances in both
|
||||
// axes. Specifically, any Event e delivered to Tag will satisfy
|
||||
// ScrollX and ScrollY constrain the range of scrolling events delivered
|
||||
// to Target. Specifically, any Event e delivered to Tag will satisfy
|
||||
//
|
||||
// ScrollBounds.Min.X <= e.Scroll.X <= ScrollBounds.Max.X (horizontal axis)
|
||||
// ScrollBounds.Min.Y <= e.Scroll.Y <= ScrollBounds.Max.Y (vertical axis)
|
||||
ScrollBounds image.Rectangle
|
||||
// ScrollX.Min <= e.Scroll.X <= ScrollX.Max (horizontal axis)
|
||||
// ScrollY.Min <= e.Scroll.Y <= ScrollY.Max (vertical axis)
|
||||
ScrollX ScrollRange
|
||||
ScrollY ScrollRange
|
||||
}
|
||||
|
||||
// ScrollRange describes the range of scrolling distances in an
|
||||
// axis.
|
||||
type ScrollRange struct {
|
||||
Min, Max int
|
||||
}
|
||||
|
||||
// GrabCmd requests a pointer grab on the pointer identified by ID.
|
||||
@@ -219,6 +225,13 @@ const (
|
||||
ButtonTertiary
|
||||
)
|
||||
|
||||
func (s ScrollRange) Union(s2 ScrollRange) ScrollRange {
|
||||
return ScrollRange{
|
||||
Min: min(s.Min, s2.Min),
|
||||
Max: max(s.Max, s2.Max),
|
||||
}
|
||||
}
|
||||
|
||||
// Push the current pass mode to the pass stack and set the pass mode.
|
||||
func (p PassOp) Push(o *op.Ops) PassStack {
|
||||
id, mid := ops.PushOp(&o.Internal, ops.PassStack)
|
||||
|
||||
+6
-4
@@ -7,6 +7,7 @@ import (
|
||||
"math"
|
||||
|
||||
"gioui.org/gesture"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
)
|
||||
@@ -158,11 +159,12 @@ func (l *List) update(gtx Context) {
|
||||
max = 0
|
||||
}
|
||||
}
|
||||
scrollRange := image.Rectangle{
|
||||
Min: l.Axis.Convert(image.Pt(min, 0)),
|
||||
Max: l.Axis.Convert(image.Pt(max, 0)),
|
||||
xrange := pointer.ScrollRange{Min: min, Max: max}
|
||||
yrange := pointer.ScrollRange{}
|
||||
if l.Axis == Vertical {
|
||||
xrange, yrange = yrange, xrange
|
||||
}
|
||||
d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, gesture.Axis(l.Axis), scrollRange)
|
||||
d := l.scroll.Update(gtx.Metric, gtx.Source, gtx.Now, gesture.Axis(l.Axis), xrange, yrange)
|
||||
l.scrollDelta = d
|
||||
l.Position.Offset += d
|
||||
}
|
||||
|
||||
@@ -138,6 +138,9 @@ type Path struct {
|
||||
func (p *Path) Pos() f32.Point { return p.pen }
|
||||
|
||||
// Begin the path, storing the path data and final Op into ops.
|
||||
//
|
||||
// Caller must also call End to finish the drawing.
|
||||
// Forgetting to call it will result in a "panic: cannot mix multi ops with single ones".
|
||||
func (p *Path) Begin(o *op.Ops) {
|
||||
*p = Path{
|
||||
ops: &o.Internal,
|
||||
|
||||
+3
-1
@@ -4,6 +4,7 @@ package text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"log"
|
||||
@@ -276,7 +277,8 @@ func newShaperImpl(systemFonts bool, collection []FontFace) *shaperImpl {
|
||||
// It returns whether the face is now available for use. FontFaces are prioritized
|
||||
// in the order in which they are loaded, with the first face being the default.
|
||||
func (s *shaperImpl) Load(f FontFace) {
|
||||
s.fontMap.AddFace(f.Face.Face(), opentype.FontToDescription(f.Font))
|
||||
desc := opentype.FontToDescription(f.Font)
|
||||
s.fontMap.AddFace(f.Face.Face(), fontscan.Location{File: fmt.Sprint(desc)}, desc)
|
||||
s.addFace(f.Face.Face(), f.Font)
|
||||
}
|
||||
|
||||
|
||||
+20
-13
@@ -228,19 +228,19 @@ func (e *Editor) processPointer(gtx layout.Context) (EditorEvent, bool) {
|
||||
axis = gesture.Vertical
|
||||
smin, smax = sbounds.Min.Y, sbounds.Max.Y
|
||||
}
|
||||
var scrollRange image.Rectangle
|
||||
var scrollX, scrollY pointer.ScrollRange
|
||||
textDims := e.text.FullDimensions()
|
||||
visibleDims := e.text.Dimensions()
|
||||
if e.SingleLine {
|
||||
scrollOffX := e.text.ScrollOff().X
|
||||
scrollRange.Min.X = min(-scrollOffX, 0)
|
||||
scrollRange.Max.X = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
|
||||
scrollX.Min = min(-scrollOffX, 0)
|
||||
scrollX.Max = max(0, textDims.Size.X-(scrollOffX+visibleDims.Size.X))
|
||||
} else {
|
||||
scrollOffY := e.text.ScrollOff().Y
|
||||
scrollRange.Min.Y = -scrollOffY
|
||||
scrollRange.Max.Y = max(0, textDims.Size.Y-(scrollOffY+visibleDims.Size.Y))
|
||||
scrollY.Min = -scrollOffY
|
||||
scrollY.Max = max(0, textDims.Size.Y-(scrollOffY+visibleDims.Size.Y))
|
||||
}
|
||||
sdist := e.scroller.Update(gtx.Metric, gtx.Source, gtx.Now, axis, scrollRange)
|
||||
sdist := e.scroller.Update(gtx.Metric, gtx.Source, gtx.Now, axis, scrollX, scrollY)
|
||||
var soff int
|
||||
if e.SingleLine {
|
||||
e.text.ScrollRel(sdist, 0)
|
||||
@@ -289,6 +289,9 @@ func (e *Editor) processPointerEvent(gtx layout.Context, ev event.Event) (Editor
|
||||
Y: int(math.Round(float64(evt.Position.Y))),
|
||||
})
|
||||
gtx.Execute(key.FocusCmd{Tag: e})
|
||||
if !e.ReadOnly {
|
||||
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
||||
}
|
||||
if e.scroller.State() != gesture.StateFlinging {
|
||||
e.scrollCaret = true
|
||||
}
|
||||
@@ -312,8 +315,8 @@ func (e *Editor) processPointerEvent(gtx layout.Context, ev event.Event) (Editor
|
||||
e.text.MoveWord(1, selectionExtend)
|
||||
e.dragging = false
|
||||
case evt.NumClicks >= 3:
|
||||
e.text.MoveStart(selectionClear)
|
||||
e.text.MoveEnd(selectionExtend)
|
||||
e.text.MoveLineStart(selectionClear)
|
||||
e.text.MoveLineEnd(selectionExtend)
|
||||
e.dragging = false
|
||||
}
|
||||
}
|
||||
@@ -374,8 +377,8 @@ func (e *Editor) processKey(gtx layout.Context) (EditorEvent, bool) {
|
||||
key.Filter{Focus: e, Name: key.NameDeleteBackward, Optional: key.ModShortcutAlt | key.ModShift},
|
||||
key.Filter{Focus: e, Name: key.NameDeleteForward, Optional: key.ModShortcutAlt | key.ModShift},
|
||||
|
||||
key.Filter{Focus: e, Name: key.NameHome, Optional: key.ModShift},
|
||||
key.Filter{Focus: e, Name: key.NameEnd, Optional: key.ModShift},
|
||||
key.Filter{Focus: e, Name: key.NameHome, Optional: key.ModShortcut | key.ModShift},
|
||||
key.Filter{Focus: e, Name: key.NameEnd, Optional: key.ModShortcut | key.ModShift},
|
||||
key.Filter{Focus: e, Name: key.NamePageDown, Optional: key.ModShift},
|
||||
key.Filter{Focus: e, Name: key.NamePageUp, Optional: key.ModShift},
|
||||
condFilter(!atBeginning, key.Filter{Focus: e, Name: key.NameLeftArrow, Optional: key.ModShortcutAlt | key.ModShift}),
|
||||
@@ -395,7 +398,7 @@ func (e *Editor) processKey(gtx layout.Context) (EditorEvent, bool) {
|
||||
case key.FocusEvent:
|
||||
// Reset IME state.
|
||||
e.ime.imeState = imeState{}
|
||||
if ke.Focus {
|
||||
if ke.Focus && !e.ReadOnly {
|
||||
gtx.Execute(key.SoftKeyboardCmd{Show: true})
|
||||
}
|
||||
case key.Event:
|
||||
@@ -521,6 +524,10 @@ func (e *Editor) command(gtx layout.Context, k key.Event) (EditorEvent, bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
case key.NameHome:
|
||||
e.text.MoveTextStart(selAct)
|
||||
case key.NameEnd:
|
||||
e.text.MoveTextEnd(selAct)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -582,9 +589,9 @@ func (e *Editor) command(gtx layout.Context, k key.Event) (EditorEvent, bool) {
|
||||
case key.NamePageDown:
|
||||
e.text.MovePages(+1, selAct)
|
||||
case key.NameHome:
|
||||
e.text.MoveStart(selAct)
|
||||
e.text.MoveLineStart(selAct)
|
||||
case key.NameEnd:
|
||||
e.text.MoveEnd(selAct)
|
||||
e.text.MoveLineEnd(selAct)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
+77
-17
@@ -256,7 +256,7 @@ func TestEditor(t *testing.T) {
|
||||
// Regression test for bad in-cluster rune offset math.
|
||||
e.SetText("æbc")
|
||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 0, 3, len("æbc"))
|
||||
|
||||
textSample := "æbc\naøå••"
|
||||
@@ -268,7 +268,7 @@ func TestEditor(t *testing.T) {
|
||||
}
|
||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||
assertCaret(t, e, 0, 0, 0)
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 0, 3, len("æbc"))
|
||||
e.MoveCaret(+1, +1)
|
||||
assertCaret(t, e, 1, 0, len("æbc\n"))
|
||||
@@ -276,7 +276,7 @@ func TestEditor(t *testing.T) {
|
||||
assertCaret(t, e, 0, 3, len("æbc"))
|
||||
e.text.MoveLines(+1, selectionClear)
|
||||
assertCaret(t, e, 1, 4, len("æbc\naøå•"))
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 1, 5, len("æbc\naøå••"))
|
||||
e.MoveCaret(+1, +1)
|
||||
assertCaret(t, e, 1, 5, len("æbc\naøå••"))
|
||||
@@ -300,7 +300,7 @@ func TestEditor(t *testing.T) {
|
||||
// Test that moveLine applies x offsets from previous moves.
|
||||
e.SetText("long line\nshort")
|
||||
e.SetCaret(0, 0)
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
e.text.MoveLines(+1, selectionClear)
|
||||
e.text.MoveLines(-1, selectionClear)
|
||||
assertCaret(t, e, 0, utf8.RuneCountInString("long line"), len("long line"))
|
||||
@@ -342,14 +342,14 @@ func TestEditorRTL(t *testing.T) {
|
||||
e.MoveCaret(+1, +1)
|
||||
assertCaret(t, e, 0, 3, len("الح"))
|
||||
// Move to the "end" of the line. This moves to the left edge of the line.
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 0, 4, len("الحب"))
|
||||
|
||||
sentence := "الحب سماء لا\nتمط غير الأحلام"
|
||||
e.SetText(sentence)
|
||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||
assertCaret(t, e, 0, 0, 0)
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 0, 12, len("الحب سماء لا"))
|
||||
e.MoveCaret(+1, +1)
|
||||
assertCaret(t, e, 1, 0, len("الحب سماء لا\n"))
|
||||
@@ -361,7 +361,7 @@ func TestEditorRTL(t *testing.T) {
|
||||
assertCaret(t, e, 0, 12, len("الحب سماء لا"))
|
||||
e.text.MoveLines(+1, selectionClear)
|
||||
assertCaret(t, e, 1, 14, len("الحب سماء لا\nتمط غير الأحلا"))
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 1, 15, len("الحب سماء لا\nتمط غير الأحلام"))
|
||||
e.MoveCaret(+1, +1)
|
||||
assertCaret(t, e, 1, 15, len("الحب سماء لا\nتمط غير الأحلام"))
|
||||
@@ -417,7 +417,7 @@ func TestEditorLigature(t *testing.T) {
|
||||
assertCaret(t, e, 0, 0, 0)
|
||||
e.SetText("fl") // just a ligature
|
||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 0, 2, len("fl"))
|
||||
e.MoveCaret(-1, -1)
|
||||
assertCaret(t, e, 0, 1, len("f"))
|
||||
@@ -428,7 +428,7 @@ func TestEditorLigature(t *testing.T) {
|
||||
e.SetText("flaffl•ffi\n•fflfi") // 3 ligatures on line 0, 2 on line 1
|
||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||
assertCaret(t, e, 0, 0, 0)
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
assertCaret(t, e, 0, 10, len("ffaffl•ffi"))
|
||||
e.MoveCaret(+1, +1)
|
||||
assertCaret(t, e, 1, 0, len("ffaffl•ffi\n"))
|
||||
@@ -481,7 +481,7 @@ func TestEditorLigature(t *testing.T) {
|
||||
e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{})
|
||||
// Ensure that all runes in the final cluster of a line are properly
|
||||
// decoded when moving to the end of the line. This is a regression test.
|
||||
e.text.MoveEnd(selectionClear)
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
// The first line was broken by line wrapping, not a newline character, and has a trailing
|
||||
// whitespace. However, we should never be able to reach the "other side" of such a trailing
|
||||
// whitespace glyph.
|
||||
@@ -548,8 +548,10 @@ const (
|
||||
moveRune
|
||||
moveLine
|
||||
movePage
|
||||
moveStart
|
||||
moveEnd
|
||||
moveTextStart
|
||||
moveTextEnd
|
||||
moveLineStart
|
||||
moveLineEnd
|
||||
moveCoord
|
||||
moveWord
|
||||
deleteWord
|
||||
@@ -599,10 +601,14 @@ func TestEditorCaretConsistency(t *testing.T) {
|
||||
e.text.MoveLines(int(distance), selectionClear)
|
||||
case movePage:
|
||||
e.text.MovePages(int(distance), selectionClear)
|
||||
case moveStart:
|
||||
e.text.MoveStart(selectionClear)
|
||||
case moveEnd:
|
||||
e.text.MoveEnd(selectionClear)
|
||||
case moveLineStart:
|
||||
e.text.MoveLineStart(selectionClear)
|
||||
case moveLineEnd:
|
||||
e.text.MoveLineEnd(selectionClear)
|
||||
case moveTextStart:
|
||||
e.text.MoveTextStart(selectionClear)
|
||||
case moveTextEnd:
|
||||
e.text.MoveTextEnd(selectionClear)
|
||||
case moveCoord:
|
||||
e.text.MoveCoord(image.Pt(int(x), int(y)))
|
||||
case moveWord:
|
||||
@@ -879,7 +885,7 @@ func (editMutation) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
// to make it much narrower (which makes the lines in the editor reflow), and
|
||||
// then verifies that the updated (col, line) positions of the selected text
|
||||
// are where we expect.
|
||||
func TestEditorSelect(t *testing.T) {
|
||||
func TestEditorSelectReflow(t *testing.T) {
|
||||
e := new(Editor)
|
||||
e.SetText(`a 2 4 6 8 a
|
||||
b 2 4 6 8 b
|
||||
@@ -982,6 +988,60 @@ g 2 4 6 8 g
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditorSelectShortcuts(t *testing.T) {
|
||||
tFont := font.Font{}
|
||||
tFontSize := unit.Sp(10)
|
||||
tShaper := text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Collection()))
|
||||
var tEditor = &Editor{
|
||||
SingleLine: false,
|
||||
ReadOnly: true,
|
||||
}
|
||||
lines := "abc abc abc\ndef def def\nghi ghi ghi"
|
||||
tEditor.SetText(lines)
|
||||
type testCase struct {
|
||||
// Initial text selection.
|
||||
startPos, endPos int
|
||||
// Keyboard shortcut to execute.
|
||||
keyEvent key.Event
|
||||
// Expected text selection.
|
||||
selection string
|
||||
}
|
||||
|
||||
pos1, pos2 := 14, 21
|
||||
for n, tst := range []testCase{
|
||||
{pos1, pos2, key.Event{Name: "A", Modifiers: key.ModShortcut}, lines},
|
||||
{pos2, pos1, key.Event{Name: "A", Modifiers: key.ModShortcut}, lines},
|
||||
{pos1, pos2, key.Event{Name: key.NameHome, Modifiers: key.ModShift}, "def def d"},
|
||||
{pos1, pos2, key.Event{Name: key.NameEnd, Modifiers: key.ModShift}, "ef"},
|
||||
{pos2, pos1, key.Event{Name: key.NameHome, Modifiers: key.ModShift}, "de"},
|
||||
{pos2, pos1, key.Event{Name: key.NameEnd, Modifiers: key.ModShift}, "f def def"},
|
||||
{pos1, pos2, key.Event{Name: key.NameHome, Modifiers: key.ModShortcut | key.ModShift}, "abc abc abc\ndef def d"},
|
||||
{pos1, pos2, key.Event{Name: key.NameEnd, Modifiers: key.ModShortcut | key.ModShift}, "ef\nghi ghi ghi"},
|
||||
{pos2, pos1, key.Event{Name: key.NameHome, Modifiers: key.ModShortcut | key.ModShift}, "abc abc abc\nde"},
|
||||
{pos2, pos1, key.Event{Name: key.NameEnd, Modifiers: key.ModShortcut | key.ModShift}, "f def def\nghi ghi ghi"},
|
||||
} {
|
||||
tRouter := new(input.Router)
|
||||
gtx := layout.Context{
|
||||
Ops: new(op.Ops),
|
||||
Locale: english,
|
||||
Constraints: layout.Exact(image.Pt(100, 100)),
|
||||
Source: tRouter.Source(),
|
||||
}
|
||||
gtx.Execute(key.FocusCmd{Tag: tEditor})
|
||||
tEditor.Layout(gtx, tShaper, tFont, tFontSize, op.CallOp{}, op.CallOp{})
|
||||
|
||||
tEditor.SetCaret(tst.startPos, tst.endPos)
|
||||
if cStart, cEnd := tEditor.Selection(); cStart != tst.startPos || cEnd != tst.endPos {
|
||||
t.Errorf("TestEditorSelect %d: initial selection", n)
|
||||
}
|
||||
tRouter.Queue(tst.keyEvent)
|
||||
tEditor.Update(gtx)
|
||||
if got := tEditor.SelectedText(); got != tst.selection {
|
||||
t.Errorf("TestEditorSelect %d: Expected %q, got %q", n, tst.selection, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that an existing selection is dismissed when you press arrow keys.
|
||||
func TestSelectMove(t *testing.T) {
|
||||
e := new(Editor)
|
||||
|
||||
@@ -284,9 +284,9 @@ func TestIndexPositionBidi(t *testing.T) {
|
||||
name: "bidi rtl",
|
||||
glyphs: bidiRTLText,
|
||||
expectedXs: []fixed.Int26_6{
|
||||
2665, 3291, 3861, 4431, 4716, 5286, 5856, 6109, 6621, 7133, 2665, 2380, 1577, 985, 687, 266, // Positions on line 0.
|
||||
2646, 3272, 3842, 4412, 4697, 5267, 5837, 6090, 6602, 7114, 2646, 2380, 1577, 985, 687, 266, // Positions on line 0.
|
||||
|
||||
7886, 7118, 6350, 5582, 4814, 4529, 4231, 3933, 3667, 2300, 2585, 3155, 3667, 2300, 2015, 1709, 1117, 266, // Positions on line 1.
|
||||
7867, 7099, 6331, 5563, 4795, 4510, 4212, 3914, 3648, 2281, 2566, 3136, 3648, 2281, 2015, 1709, 1117, 266, // Positions on line 1.
|
||||
|
||||
8794, 8026, 7258, 6490, 5722, 5437, 4922, 4540, 4134, 3868, 0, 290, 860, 1430, 1715, 1989, 2559, 3071, 3583, // Positions on line 2.
|
||||
|
||||
@@ -402,7 +402,7 @@ func TestIndexPositionLines(t *testing.T) {
|
||||
xOff: fixed.Int26_6(0),
|
||||
yOff: 22,
|
||||
glyphs: 15,
|
||||
width: fixed.Int26_6(7133),
|
||||
width: fixed.Int26_6(7114),
|
||||
ascent: fixed.Int26_6(1407),
|
||||
descent: fixed.Int26_6(756),
|
||||
},
|
||||
@@ -410,7 +410,7 @@ func TestIndexPositionLines(t *testing.T) {
|
||||
xOff: fixed.Int26_6(0),
|
||||
yOff: 41,
|
||||
glyphs: 15,
|
||||
width: fixed.Int26_6(7886),
|
||||
width: fixed.Int26_6(7867),
|
||||
ascent: fixed.Int26_6(1407),
|
||||
descent: fixed.Int26_6(756),
|
||||
},
|
||||
@@ -477,18 +477,18 @@ func TestIndexPositionLines(t *testing.T) {
|
||||
glyphs: bidiRTLTextOpp,
|
||||
expectedLines: []lineInfo{
|
||||
{
|
||||
xOff: fixed.Int26_6(3107),
|
||||
xOff: fixed.Int26_6(3126),
|
||||
yOff: 22,
|
||||
glyphs: 15,
|
||||
width: fixed.Int26_6(7133),
|
||||
width: fixed.Int26_6(7114),
|
||||
ascent: fixed.Int26_6(1407),
|
||||
descent: fixed.Int26_6(756),
|
||||
},
|
||||
{
|
||||
xOff: fixed.Int26_6(2354),
|
||||
xOff: fixed.Int26_6(2373),
|
||||
yOff: 41,
|
||||
glyphs: 15,
|
||||
width: fixed.Int26_6(7886),
|
||||
width: fixed.Int26_6(7867),
|
||||
ascent: fixed.Int26_6(1407),
|
||||
descent: fixed.Int26_6(756),
|
||||
},
|
||||
|
||||
@@ -54,6 +54,9 @@ func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions {
|
||||
if !gtx.Enabled() {
|
||||
fillColor = f32color.Disabled(fillColor)
|
||||
}
|
||||
if fillWidth < int(p.Radius*2) {
|
||||
fillWidth = int(p.Radius * 2)
|
||||
}
|
||||
return shader(fillWidth, fillColor)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -247,8 +247,8 @@ func (e *Selectable) processPointer(gtx layout.Context) {
|
||||
e.text.MoveWord(1, selectionExtend)
|
||||
e.dragging = false
|
||||
case evt.NumClicks >= 3:
|
||||
e.text.MoveStart(selectionClear)
|
||||
e.text.MoveEnd(selectionExtend)
|
||||
e.text.MoveLineStart(selectionClear)
|
||||
e.text.MoveLineEnd(selectionExtend)
|
||||
e.dragging = false
|
||||
}
|
||||
}
|
||||
@@ -378,9 +378,9 @@ func (e *Selectable) command(gtx layout.Context, k key.Event) {
|
||||
case key.NamePageDown:
|
||||
e.text.MovePages(+1, selAct)
|
||||
case key.NameHome:
|
||||
e.text.MoveStart(selAct)
|
||||
e.text.MoveLineStart(selAct)
|
||||
case key.NameEnd:
|
||||
e.text.MoveEnd(selAct)
|
||||
e.text.MoveLineEnd(selAct)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-4
@@ -639,9 +639,28 @@ func (e *textView) MoveCaret(startDelta, endDelta int) {
|
||||
e.caret.end = e.moveByGraphemes(e.caret.end, endDelta)
|
||||
}
|
||||
|
||||
// MoveStart moves the caret to the start of the current line, ensuring that the resulting
|
||||
// MoveTextStart moves the caret to the start of the text.
|
||||
func (e *textView) MoveTextStart(selAct selectionAction) {
|
||||
caret := e.closestToRune(e.caret.end)
|
||||
e.caret.start = 0
|
||||
e.caret.end = caret.runes
|
||||
e.caret.xoff = -caret.x
|
||||
e.updateSelection(selAct)
|
||||
e.clampCursorToGraphemes()
|
||||
}
|
||||
|
||||
// MoveTextEnd moves the caret to the end of the text.
|
||||
func (e *textView) MoveTextEnd(selAct selectionAction) {
|
||||
caret := e.closestToRune(math.MaxInt)
|
||||
e.caret.start = caret.runes
|
||||
e.caret.xoff = fixed.I(e.params.MaxWidth) - caret.x
|
||||
e.updateSelection(selAct)
|
||||
e.clampCursorToGraphemes()
|
||||
}
|
||||
|
||||
// MoveLineStart moves the caret to the start of the current line, ensuring that the resulting
|
||||
// cursor position is on a grapheme cluster boundary.
|
||||
func (e *textView) MoveStart(selAct selectionAction) {
|
||||
func (e *textView) MoveLineStart(selAct selectionAction) {
|
||||
caret := e.closestToRune(e.caret.start)
|
||||
caret = e.closestToLineCol(caret.lineCol.line, 0)
|
||||
e.caret.start = caret.runes
|
||||
@@ -650,9 +669,9 @@ func (e *textView) MoveStart(selAct selectionAction) {
|
||||
e.clampCursorToGraphemes()
|
||||
}
|
||||
|
||||
// MoveEnd moves the caret to the end of the current line, ensuring that the resulting
|
||||
// MoveLineEnd moves the caret to the end of the current line, ensuring that the resulting
|
||||
// cursor position is on a grapheme cluster boundary.
|
||||
func (e *textView) MoveEnd(selAct selectionAction) {
|
||||
func (e *textView) MoveLineEnd(selAct selectionAction) {
|
||||
caret := e.closestToRune(e.caret.start)
|
||||
caret = e.closestToLineCol(caret.lineCol.line, math.MaxInt)
|
||||
e.caret.start = caret.runes
|
||||
|
||||
Reference in New Issue
Block a user